-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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, attempt 2 #4161
Changes from all commits
5b1aa6c
92cdf4c
c51766d
8879025
9bb54f8
f8c7f01
fd42751
0b51368
24d1e60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,8 +18,12 @@ import { | |
resultsAriaMessage, | ||
valueEventAriaMessage, | ||
instructionsAriaMessage, | ||
type AccessibilityProp, | ||
type AccessibilityConfig, | ||
type InstructionsContext, | ||
type ValueEventContext, | ||
type ValueEventType, | ||
type InstructionEventType, | ||
} from './accessibility/index'; | ||
|
||
import { | ||
|
@@ -75,6 +79,8 @@ type FormatOptionLabelMeta = { | |
}; | ||
|
||
export type Props = { | ||
/* Custom ARIA message functions */ | ||
accessibility?: AccessibilityProp, | ||
/* Aria label (for assistive tech) */ | ||
'aria-label'?: string, | ||
/* HTML ID of an element that should be used as the label (for assistive tech) */ | ||
|
@@ -130,6 +136,8 @@ export type Props = { | |
filterOption: | ||
| (({ label: string, value: string, data: OptionType }, string) => boolean) | ||
| null, | ||
/* Sets the form attribute on the input */ | ||
form?: string, | ||
/* | ||
Formats group labels in the menu as React components | ||
|
||
|
@@ -243,8 +251,6 @@ export type Props = { | |
tabSelectsValue: boolean, | ||
/* The value of the select; reflected by the selected option */ | ||
value: ValueType, | ||
/* Sets the form attribute on the input */ | ||
form?: string, | ||
}; | ||
|
||
export const defaultProps = { | ||
|
@@ -285,6 +291,13 @@ export const defaultProps = { | |
styles: {}, | ||
tabIndex: '0', | ||
tabSelectsValue: true, | ||
accessibility: { | ||
valueFocusAriaMessage, | ||
optionFocusAriaMessage, | ||
resultsAriaMessage, | ||
valueEventAriaMessage, | ||
instructionsAriaMessage | ||
}, | ||
}; | ||
|
||
type MenuOptions = { | ||
|
@@ -322,7 +335,7 @@ export default class Select extends Component<Props, State> { | |
|
||
// Misc. Instance Properties | ||
// ------------------------------ | ||
|
||
accessibility: AccessibilityConfig; | ||
blockOptionHover: boolean = false; | ||
isComposing: boolean = false; | ||
clearFocusValueOnUpdate: boolean = false; | ||
|
@@ -364,6 +377,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); | ||
|
@@ -404,6 +418,7 @@ export default class Select extends Component<Props, State> { | |
const { options, value, menuIsOpen, 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 || | ||
|
@@ -823,26 +838,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'], | ||
}), | ||
|
@@ -1380,24 +1405,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({ | ||
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 && 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() }), | ||
}); | ||
|
@@ -1815,10 +1839,14 @@ export default class Select extends Component<Props, State> { | |
renderLiveRegion() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be good to abstract this to a separate component at this time. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for your feedback! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree! You are following the convent. Also agree that this is 1600 lines. So maybe another PR would be good to refactor some of the render* function into separate components. |
||
if (!this.state.isFocused) return null; | ||
return ( | ||
<A11yText aria-live="polite"> | ||
<p id="aria-selection-event"> {this.state.ariaLiveSelection}</p> | ||
<p id="aria-context"> {this.constructAriaLiveMessage()}</p> | ||
</A11yText> | ||
<span> | ||
<A11yText aria-live="assertive"> | ||
<p id="aria-selection-event"> {this.state.ariaLiveSelection}</p> | ||
</A11yText> | ||
<A11yText aria-live="polite"> | ||
<p id="aria-context"> {this.constructAriaLiveMessage()}</p> | ||
</A11yText> | ||
</span> | ||
); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this is a bit of semantics, but I believe
accessibility
is a bit vague for a prop.Would you be willing to consider something more along the lines of
ariaLiveConfig
? I think it better encapsulates the functionality.Additionally, this would provide more clarity and avoids confusion with perhaps other things aria/accessibility related.