Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kashkovsky intl a11y #3550

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/examples/FormExample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// @flow
import React, { Component } from 'react';

import Select from '../../src';
import { colourOptions } from '../data';


export default class FormExample extends Component<*> {
render() {
return (
<form>
<label id="select-label" htmlFor="select-example">
Test form label
</label>

<Select
aria-labelledby="select-label"
accessibility={{
optionFocusAriaMessage:
({ focusedOption, getOptionLabel }) => {
return `custom aria option focus message: ${getOptionLabel(focusedOption)}`;
}
}}
id="select-example"
className="basic-single"
classNamePrefix="select"
name="color"
options={colourOptions}
/>
</form>
);
}
}
101 changes: 79 additions & 22 deletions src/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
instructionsAriaMessage,
type InstructionsContext,
type ValueEventContext,
type ValueEventType,
type InstructionEventType,
} from './accessibility/index';

import {
Expand Down Expand Up @@ -74,6 +76,37 @@ type FormatOptionLabelMeta = {
inputValue: string,
selectValue: ValueType,
};
export type AccessibilityProp = {
valueFocusAriaMessage?: (args: {
focusedValue: OptionType,
getOptionLabel: (data: OptionType) => string,
selectValue: OptionsType
}) => string,
optionFocusAriaMessage?: (args: {
focusedOption: OptionType,
getOptionLabel: (data: OptionType) => string,
options: OptionsType
}) => string,
resultsAriaMessage?: (args: { inputValue: string, screenReaderMessage: string }) => string,
valueEventAriaMessage?: (event: ValueEventType, context: ValueEventContext) => string,
instructionsAriaMessage?: (event: InstructionEventType, context?: InstructionsContext) => string
};

export type AccessibilityConfig = {
valueFocusAriaMessage: (args: {
focusedValue: OptionType,
getOptionLabel: (data: OptionType) => string,
selectValue: OptionsType
}) => string,
optionFocusAriaMessage: (args: {
focusedOption: OptionType,
getOptionLabel: (data: OptionType) => string,
options: OptionsType
}) => string,
resultsAriaMessage: (args: { inputValue: string, screenReaderMessage: string }) => string,
valueEventAriaMessage: (event: ValueEventType, context: ValueEventContext) => string,
instructionsAriaMessage: (event: InstructionEventType, context?: InstructionsContext) => string
}

export type Props = {
/* Aria label (for assistive tech) */
Expand Down Expand Up @@ -244,6 +277,8 @@ export type Props = {
tabSelectsValue: boolean,
/* The value of the select; reflected by the selected option */
value: ValueType,
/* Custom ARIA message functions */
accessibility?: AccessibilityProp
};

export const defaultProps = {
Expand Down Expand Up @@ -284,6 +319,13 @@ export const defaultProps = {
styles: {},
tabIndex: '0',
tabSelectsValue: true,
accessibility: {
valueFocusAriaMessage,
optionFocusAriaMessage,
resultsAriaMessage,
valueEventAriaMessage,
instructionsAriaMessage
},
};

type MenuOptions = {
Expand Down Expand Up @@ -323,7 +365,7 @@ export default class Select extends Component<Props, State> {

// Misc. Instance Properties
// ------------------------------

accessibility: AccessibilityConfig;
blockOptionHover: boolean = false;
clearFocusValueOnUpdate: boolean = false;
commonProps: any; // TODO
Expand Down Expand Up @@ -364,6 +406,7 @@ export default class Select extends Component<Props, State> {
super(props);
const { value } = props;
this.cacheComponents = memoizeOne(this.cacheComponents, isEqual).bind(this);
this.accessibility = this.getAccessibilityConfig(props.accessibility);
this.cacheComponents(props.components);
this.instancePrefix =
'react-select-' + (this.props.instanceId || ++instanceId);
Expand Down Expand Up @@ -391,6 +434,7 @@ export default class Select extends Component<Props, State> {
const { options, value, inputValue } = this.props;
// re-cache custom components
this.cacheComponents(nextProps.components);
this.accessibility = this.getAccessibilityConfig(nextProps.accessibility);
// rebuild the menu options
if (
nextProps.value !== value ||
Expand Down Expand Up @@ -801,26 +845,36 @@ export default class Select extends Component<Props, State> {
// ==============================
// Helpers
// ==============================
getAccessibilityConfig (accessibilityObj?: AccessibilityProp): AccessibilityConfig {
return {
valueFocusAriaMessage,
optionFocusAriaMessage,
resultsAriaMessage,
valueEventAriaMessage,
instructionsAriaMessage,
...accessibilityObj,
};
};
announceAriaLiveSelection = ({
event,
context,
}: {
event: string,
event: ValueEventType,
context: ValueEventContext,
}) => {
this.setState({
ariaLiveSelection: valueEventAriaMessage(event, context),
ariaLiveSelection: this.accessibility.valueEventAriaMessage(event, context),
});
};
announceAriaLiveContext = ({
event,
context,
}: {
event: string,
event: InstructionEventType,
context?: InstructionsContext,
}) => {
this.setState({
ariaLiveContext: instructionsAriaMessage(event, {
ariaLiveContext: this.accessibility.instructionsAriaMessage(event, {
...context,
label: this.props['aria-label'],
}),
Expand Down Expand Up @@ -1357,24 +1411,23 @@ export default class Select extends Component<Props, State> {
const { options, menuIsOpen, inputValue, screenReaderStatus } = this.props;

// An aria live message representing the currently focused value in the select.
const focusedValueMsg = focusedValue
? valueFocusAriaMessage({
focusedValue,
getOptionLabel: this.getOptionLabel,
selectValue,
})
const focusedValueMsg = focusedValue && this.accessibility
? this.accessibility.valueFocusAriaMessage({
focusedValue,
getOptionLabel: this.getOptionLabel,
selectValue,
})
: '';
// An aria live message representing the currently focused option in the select.
const focusedOptionMsg =
focusedOption && menuIsOpen
? optionFocusAriaMessage({
focusedOption,
getOptionLabel: this.getOptionLabel,
options,
})
focusedOption && menuIsOpen ? this.accessibility.optionFocusAriaMessage({
focusedOption,
getOptionLabel: this.getOptionLabel,
options,
})
: '';
// An aria live message representing the set of focusable results and current searchterm/inputvalue.
const resultsMsg = resultsAriaMessage({
const resultsMsg = this.accessibility.resultsAriaMessage({
inputValue,
screenReaderMessage: screenReaderStatus({ count: this.countOptions() }),
});
Expand Down Expand Up @@ -1787,10 +1840,14 @@ export default class Select extends Component<Props, State> {
renderLiveRegion() {
if (!this.state.isFocused) return null;
return (
<A11yText aria-live="assertive">
<p id="aria-selection-event">&nbsp;{this.state.ariaLiveSelection}</p>
<p id="aria-context">&nbsp;{this.constructAriaLiveMessage()}</p>
</A11yText>
<span>
<A11yText aria-live="assertive">
<p id="aria-selection-event">&nbsp;{this.state.ariaLiveSelection}</p>
</A11yText>
<A11yText aria-live="polite">
<p id="aria-context">&nbsp;{this.constructAriaLiveMessage()}</p>
</A11yText>
</span>
);
}

Expand Down
1 change: 0 additions & 1 deletion src/__tests__/Select.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,6 @@ cases('Clicking Enter on a focused select', ({ props = BASIC_PROPS, expectedValu
const selectWrapper = wrapper.find(Select);
selectWrapper.instance().setState({ focusedOption: OPTIONS[0] });
selectWrapper.instance().onKeyDown(event);
console.log(event.defaultPrevented);
expect(event.defaultPrevented).toBe(expectedValue);
}, {
'while menuIsOpen && focusedOption && !isComposing > should invoke event.preventDefault': {
Expand Down
65 changes: 65 additions & 0 deletions src/__tests__/__snapshots__/Async.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ exports[`defaults - snapshot 1`] = `
options={Array []}
>
<Select
accessibility={
Object {
"instructionsAriaMessage": [Function],
"optionFocusAriaMessage": [Function],
"resultsAriaMessage": [Function],
"valueEventAriaMessage": [Function],
"valueFocusAriaMessage": [Function],
}
}
backspaceRemovesValue={true}
blurInputOnSelect={true}
cacheOptions={false}
Expand Down Expand Up @@ -83,6 +92,13 @@ exports[`defaults - snapshot 1`] = `
selectOption={[Function]}
selectProps={
Object {
"accessibility": Object {
"instructionsAriaMessage": [Function],
"optionFocusAriaMessage": [Function],
"resultsAriaMessage": [Function],
"valueEventAriaMessage": [Function],
"valueFocusAriaMessage": [Function],
},
"backspaceRemovesValue": true,
"blurInputOnSelect": true,
"cacheOptions": false,
Expand Down Expand Up @@ -186,6 +202,13 @@ exports[`defaults - snapshot 1`] = `
selectOption={[Function]}
selectProps={
Object {
"accessibility": Object {
"instructionsAriaMessage": [Function],
"optionFocusAriaMessage": [Function],
"resultsAriaMessage": [Function],
"valueEventAriaMessage": [Function],
"valueFocusAriaMessage": [Function],
},
"backspaceRemovesValue": true,
"blurInputOnSelect": true,
"cacheOptions": false,
Expand Down Expand Up @@ -281,6 +304,13 @@ exports[`defaults - snapshot 1`] = `
selectOption={[Function]}
selectProps={
Object {
"accessibility": Object {
"instructionsAriaMessage": [Function],
"optionFocusAriaMessage": [Function],
"resultsAriaMessage": [Function],
"valueEventAriaMessage": [Function],
"valueFocusAriaMessage": [Function],
},
"backspaceRemovesValue": true,
"blurInputOnSelect": true,
"cacheOptions": false,
Expand Down Expand Up @@ -376,6 +406,13 @@ exports[`defaults - snapshot 1`] = `
selectOption={[Function]}
selectProps={
Object {
"accessibility": Object {
"instructionsAriaMessage": [Function],
"optionFocusAriaMessage": [Function],
"resultsAriaMessage": [Function],
"valueEventAriaMessage": [Function],
"valueFocusAriaMessage": [Function],
},
"backspaceRemovesValue": true,
"blurInputOnSelect": true,
"cacheOptions": false,
Expand Down Expand Up @@ -475,6 +512,13 @@ exports[`defaults - snapshot 1`] = `
onFocus={[Function]}
selectProps={
Object {
"accessibility": Object {
"instructionsAriaMessage": [Function],
"optionFocusAriaMessage": [Function],
"resultsAriaMessage": [Function],
"valueEventAriaMessage": [Function],
"valueFocusAriaMessage": [Function],
},
"backspaceRemovesValue": true,
"blurInputOnSelect": true,
"cacheOptions": false,
Expand Down Expand Up @@ -658,6 +702,13 @@ exports[`defaults - snapshot 1`] = `
selectOption={[Function]}
selectProps={
Object {
"accessibility": Object {
"instructionsAriaMessage": [Function],
"optionFocusAriaMessage": [Function],
"resultsAriaMessage": [Function],
"valueEventAriaMessage": [Function],
"valueFocusAriaMessage": [Function],
},
"backspaceRemovesValue": true,
"blurInputOnSelect": true,
"cacheOptions": false,
Expand Down Expand Up @@ -752,6 +803,13 @@ exports[`defaults - snapshot 1`] = `
selectOption={[Function]}
selectProps={
Object {
"accessibility": Object {
"instructionsAriaMessage": [Function],
"optionFocusAriaMessage": [Function],
"resultsAriaMessage": [Function],
"valueEventAriaMessage": [Function],
"valueFocusAriaMessage": [Function],
},
"backspaceRemovesValue": true,
"blurInputOnSelect": true,
"cacheOptions": false,
Expand Down Expand Up @@ -854,6 +912,13 @@ exports[`defaults - snapshot 1`] = `
selectOption={[Function]}
selectProps={
Object {
"accessibility": Object {
"instructionsAriaMessage": [Function],
"optionFocusAriaMessage": [Function],
"resultsAriaMessage": [Function],
"valueEventAriaMessage": [Function],
"valueFocusAriaMessage": [Function],
},
"backspaceRemovesValue": true,
"blurInputOnSelect": true,
"cacheOptions": false,
Expand Down
Loading