Skip to content

Commit

Permalink
Autocomplete: Use combobox role with ARIA attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth committed Sep 25, 2017
1 parent 1a1e760 commit 4ecc6a2
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 8 deletions.
19 changes: 14 additions & 5 deletions components/autocomplete/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import { keycodes } from '@wordpress/utils';
import './style.scss';
import Button from '../button';
import Popover from '../popover';
import withInstanceId from '../higher-order/with-instance-id';

const { ENTER, ESCAPE, UP, DOWN } = keycodes;

class Autocomplete extends Component {
export class Autocomplete extends Component {
static getInitialState() {
return {
isOpen: false,
Expand Down Expand Up @@ -180,9 +181,11 @@ class Autocomplete extends Component {
}

render() {
const { children, className } = this.props;
const { children, className, instanceId } = this.props;
const { isOpen, selectedIndex } = this.state;
const classes = classnames( 'components-autocomplete__popover', className );
const listBoxId = `components-autocomplete-listbox-${ instanceId }`;
const activeId = `components-autocomplete-item-${ instanceId }-${ selectedIndex }`;

// Blur is applied to the wrapper node, since if the child is Editable,
// the event will not have `relatedTarget` assigned.
Expand All @@ -195,20 +198,26 @@ class Autocomplete extends Component {
{ cloneElement( Children.only( children ), {
onInput: this.search,
onKeyDown: this.setSelectedIndex,
role: 'combobox',
'aria-expanded': isOpen,
'aria-activedescendant': isOpen ? activeId : null,
'aria-owns': isOpen ? listBoxId : null,
} ) }
<Popover
isOpen={ isOpen }
position="top right"
className={ classes }
>
<ul
role="menu"
id={ listBoxId }
role="listbox"
className="components-autocomplete__results"
>
{ this.getFilteredOptions().map( ( option, index ) => (
<li
key={ option.value }
role="menuitem"
id={ `components-autocomplete-item-${ instanceId }-${ index }` }
role="option"
className={ classnames( 'components-autocomplete__result', {
'is-selected': index === selectedIndex,
} ) }
Expand All @@ -225,4 +234,4 @@ class Autocomplete extends Component {
}
}

export default Autocomplete;
export default withInstanceId( Autocomplete );
14 changes: 11 additions & 3 deletions components/autocomplete/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { keycodes } from '@wordpress/utils';
/**
* Internal dependencies
*/
import Autocomplete from '../';
import { Autocomplete } from '../';

const { ENTER, ESCAPE, UP, DOWN, SPACE } = keycodes;

Expand Down Expand Up @@ -53,12 +53,16 @@ describe( 'Autocomplete', () => {
</Autocomplete>
);
const popover = wrapper.find( 'Popover' );
const clone = wrapper.find( '[data-ok]' );

expect( wrapper.state( 'isOpen' ) ).toBe( false );
expect( popover.hasClass( 'my-autocomplete' ) ).toBe( true );
expect( popover.hasClass( 'components-autocomplete__popover' ) ).toBe( true );
expect( wrapper.hasClass( 'components-autocomplete' ) ).toBe( true );
expect( wrapper.find( '[data-ok]' ) ).toHaveLength( 1 );
expect( clone ).toHaveLength( 1 );
expect( clone.prop( 'aria-expanded' ) ).toBe( false );
expect( clone.prop( 'aria-activedescendant' ) ).toBe( null );
expect( clone.prop( 'aria-owns' ) ).toBe( null );
} );

it( 'opens on absent trigger prefix search', () => {
Expand All @@ -67,8 +71,9 @@ describe( 'Autocomplete', () => {
<div contentEditable />
</Autocomplete>
);
const clone = wrapper.find( '[contentEditable]' );

wrapper.find( '[contentEditable]' ).simulate( 'input', {
clone.simulate( 'input', {
target: {
textContent: 'b',
},
Expand All @@ -78,6 +83,9 @@ describe( 'Autocomplete', () => {
expect( wrapper.state( 'selectedIndex' ) ).toBe( 0 );
expect( wrapper.state( 'search' ) ).toEqual( /b/i );
expect( wrapper.find( '.components-autocomplete__result' ) ).toHaveLength( 1 );
expect( clone.prop( 'aria-expanded' ) ).toBe( false );
expect( clone.prop( 'aria-activedescendant' ) ).toBe( null );
expect( clone.prop( 'aria-owns' ) ).toBe( null );
} );

it( 'opens on trigger prefix search', () => {
Expand Down

0 comments on commit 4ecc6a2

Please sign in to comment.