Skip to content

Commit

Permalink
Accessibility: Enhance The FormTokenField component's accesibility
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowriad committed Jun 12, 2017
1 parent 9d57a33 commit bc3c80e
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 10 deletions.
4 changes: 4 additions & 0 deletions bootstrap-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,7 @@ global.window._wpDateSettings = {
string: 'America/New_York',
},
};
global.wp = global.wp || {};
global.wp.a11y = {
speak: () => {},
};
32 changes: 29 additions & 3 deletions components/form-token-field/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { __ } from 'i18n';
import { Component } from 'element';

/**
Expand All @@ -16,6 +17,7 @@ import './style.scss';
import Token from './token';
import TokenInput from './token-input';
import SuggestionsList from './suggestions-list';
import withInstanceId from '../higher-order/with-instance-id';

const initialState = {
incompleteTokenValue: '',
Expand Down Expand Up @@ -314,6 +316,7 @@ class FormTokenField extends Component {

addNewToken( token ) {
this.addNewTokens( [ token ] );
this.speak( this.props.messages.added );

this.setState( {
incompleteTokenValue: '',
Expand All @@ -331,6 +334,7 @@ class FormTokenField extends Component {
return this.getTokenValue( item ) !== this.getTokenValue( token );
} );
this.props.onChange( newTokens );
this.speak( this.props.messages.removed );
}

getTokenValue( token ) {
Expand Down Expand Up @@ -369,6 +373,10 @@ class FormTokenField extends Component {
return take( suggestions, this.props.maxSuggestions );
}

speak( message ) {
wp.a11y.speak( message, 'assertive' );
}

getSelectedSuggestion() {
if ( this.state.selectedSuggestionIndex !== -1 ) {
return this.getMatchingSuggestions()[ this.state.selectedSuggestionIndex ];
Expand Down Expand Up @@ -416,21 +424,25 @@ class FormTokenField extends Component {
onMouseEnter={ token.onMouseEnter }
onMouseLeave={ token.onMouseLeave }
disabled={ 'error' !== status && this.props.disabled }
messages={ this.props.messages }
/>
);
}

renderInput() {
const { autoCapitalize, autoComplete, maxLength, value, placeholder } = this.props;
const { autoCapitalize, autoComplete, maxLength, value, placeholder, instanceId } = this.props;

let props = {
instanceId,
autoCapitalize,
autoComplete,
ref: this.bindInput,
key: 'input',
disabled: this.props.disabled,
value: this.state.incompleteTokenValue,
onBlur: this.onBlur,
isExpanded: this.state.isActive,
selectedSuggestionIndex: this.state.selectedSuggestionIndex,
};

if ( value.length === 0 && placeholder ) {
Expand All @@ -447,7 +459,7 @@ class FormTokenField extends Component {
}

render() {
const { disabled } = this.props;
const { disabled, placeholder, instanceId } = this.props;
const classes = classnames( 'components-form-token-field', {
'is-active': this.state.isActive,
'is-disabled': disabled,
Expand All @@ -469,6 +481,9 @@ class FormTokenField extends Component {

return (
<div { ...tokenFieldProps } >
<label htmlFor={ `components-form-token-input-${ instanceId }` } className="components-form-token__howto">
{ placeholder }
</label>
<div ref={ this.bindTokensAndInput }
className="components-form-token-field__input-container"
tabIndex="-1"
Expand All @@ -478,6 +493,7 @@ class FormTokenField extends Component {
{ this.renderTokensAndInput() }
</div>
<SuggestionsList
instanceId={ instanceId }
match={ this.props.saveTransform( this.state.incompleteTokenValue ) }
displayTransform={ this.props.displayTransform }
suggestions={ this.getMatchingSuggestions() }
Expand All @@ -487,6 +503,9 @@ class FormTokenField extends Component {
onHover={ this.onSuggestionHovered }
onSelect={ this.onSuggestionSelected }
/>
<div id={ `components-form-token-suggestions-howto-${ instanceId }` } className="components-form-token__howto">
{ __( 'Separate with commas' ) }
</div>
</div>
);
}
Expand All @@ -503,6 +522,13 @@ FormTokenField.defaultProps = {
isBorderless: false,
disabled: false,
tokenizeOnSpace: false,
messages: {
added: __( 'Item added.' ),
removed: __( 'Item removed.' ),
remove: __( 'Remove item: %s.' ),
},
};

export default FormTokenField;
FormTokenField.instances = 0;

export default withInstanceId( FormTokenField );
12 changes: 12 additions & 0 deletions components/form-token-field/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,15 @@ input[type="text"].components-form-token-field__input {
.components-form-token-field__suggestion-match {
color: $dark-gray-800;
}

.components-form-token__howto {
position: absolute;
margin: -1px;
padding: 0;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
border: 0;
word-wrap: normal;
}
19 changes: 15 additions & 4 deletions components/form-token-field/suggestions-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,33 @@ class SuggestionsList extends Component {
// why, since usually a div isn't focusable by default
// TODO does this still apply now that it's a <ul> and not a <div>?
return (
<ul ref={ this.bindList } className={ classes } tabIndex="-1">
<ul
ref={ this.bindList }
className={ classes }
tabIndex="-1"
id={ `components-form-token-suggestions-${ this.props.instanceId }` }
role="listbox"
>
{
map( this.props.suggestions, ( suggestion, index ) => {
const match = this.computeSuggestionMatch( suggestion );
const classeName = classnames( 'components-form-token-field__suggestion', {
'is-selected': index === this.props.selectedIndex,
} );

/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
return (
<li
id={ `components-form-token-suggestions-${ this.props.instanceId }-${ index }` }
role="option"
tabIndex="-1"
className={ classeName }
key={ suggestion }
onMouseDown={ this.handleMouseDown }
onClick={ this.handleClick( suggestion ) }
onMouseEnter={ this.handleHover( suggestion ) }>
onMouseEnter={ this.handleHover( suggestion ) }
aria-selected={ index === this.props.selectedIndex }
>
{ match
? (
<span>
Expand All @@ -111,7 +122,7 @@ class SuggestionsList extends Component {
}
</li>
);
/* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
/* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
} )
}
</ul>
Expand Down
13 changes: 11 additions & 2 deletions components/form-token-field/token-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,26 @@ class TokenInput extends Component {
}

render() {
const props = { ...this.props, onChange: this.onChange };
const { value, placeholder } = props;
const { value, placeholder, isExpanded, instanceId, selectedSuggestionIndex, ...props } = this.props;
const size = ( ( value.length === 0 && placeholder && placeholder.length ) || value.length ) + 1;

return (
<input
ref={ this.bindInput }
id={ `components-form-token-input-${ instanceId }` }
type="text"
{ ...props }
value={ value }
placeholder={ placeholder }
onChange={ this.onChange }
size={ size }
className="components-form-token-field__input"
role="combobox"
aria-expanded={ isExpanded }
aria-autocomplete="list"
aria-owns={ `components-form-token-suggestions-${ instanceId }` }
aria-activedescendant={ selectedSuggestionIndex ? `components-form-token-suggestions-${ instanceId }-${ selectedSuggestionIndex }` : undefined }
aria-describedby={ `components-form-token-suggestions-howto-${ this.instanceId }` }
/>
);
}
Expand Down
4 changes: 4 additions & 0 deletions components/form-token-field/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { noop } from 'lodash';
/**
* WordPress dependencies
*/
import { sprintf } from 'i18n';
import IconButton from 'components/icon-button';

function Token( {
Expand All @@ -19,6 +20,7 @@ function Token( {
onClickRemove = noop,
onMouseEnter,
onMouseLeave,
messages,
} ) {
const tokenClasses = classnames( 'components-form-token-field__token', {
'is-error': 'error' === status,
Expand All @@ -41,10 +43,12 @@ function Token( {
<span className="components-form-token-field__token-text">
{ displayTransform( value ) }
</span>

<IconButton
className="components-form-token-field__remove-token"
icon="dismiss"
onClick={ ! disabled && onClick }
label={ sprintf( messages.remove, displayTransform( value ) ) }
/>
</span>
);
Expand Down
7 changes: 7 additions & 0 deletions editor/sidebar/post-taxonomies/tags-selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { unescape, find } from 'lodash';
/**
* WordPress dependencies
*/
import { __ } from 'i18n';
import { Component } from 'element';
import { FormTokenField } from 'components';
import { getEditedPostAttribute } from '../../selectors';
Expand Down Expand Up @@ -109,6 +110,12 @@ class TagsSelector extends Component {
onChange={ this.onTagsChange }
maxSuggestions={ MAX_TERMS_SUGGESTIONS }
disabled={ loading }
placeholder={ __( 'Add New Tag' ) }
messages={ {
added: __( 'Tag added.' ),
removed: __( 'Tag removed.' ),
remove: __( 'Remove tag: %s.' ),
} }
/>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ function gutenberg_register_scripts() {
wp_register_script(
'wp-components',
gutenberg_url( 'components/build/index.js' ),
array( 'wp-element' ),
array( 'wp-element', 'wp-a11y' ),
filemtime( gutenberg_dir_path() . 'components/build/index.js' )
);
wp_register_script(
Expand Down

0 comments on commit bc3c80e

Please sign in to comment.