feat: added accessibility value aliases (#34535)

Summary:
This adds aliasing for accessibility Value, it's used as requested on https://github.com/facebook/react-native/issues/34424.

- [aria-valuemax](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuemax) to equivalent [accessibilityValue.max](https://reactnative.dev/docs/accessibility#accessibilityvalue)
- [aria-valuemin](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuemin) to equivalent [accessibilityValue.min](https://reactnative.dev/docs/accessibility#accessibilityvalue)
- [aria-valuenow](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuenow) to equivalent [accessibilityValue.now](https://reactnative.dev/docs/accessibility#accessibilityvalue)
- [aria-valuetext](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuetext) to equivalent [accessibilityValue.text](https://reactnative.dev/docs/accessibility#accessibilityvalue)

## Changelog

[General] [Added] - Add `aria-valuemax`, `aria-valuemin`, `aria-valuenow`, `aria-valuetext` as alias prop to `TouchableOpacity`, `View`, `Pressable` `TouchableHighlight` `TouchableBounce` `TouchableWithoutFeedback` `TouchableOpacity` components

Pull Request resolved: https://github.com/facebook/react-native/pull/34535

Test Plan:
- Enable `talkback`.
 - Open the RNTester app and navigate to the Api's tab
 - Go to the `fake Slider Example for Accessibility Value `modes section

<Image src="https://user-images.githubusercontent.com/22423684/187472543-05200d8d-2742-4096-a56c-41f81b440a97.png" height="600" width="300" />

Reviewed By: cipolleschi

Differential Revision: D39206362

Pulled By: jacdebug

fbshipit-source-id: e7ed263badac789d529dd21e961cda5302b031e3
This commit is contained in:
Daksh Bhardwaj
2022-09-09 04:48:41 -07:00
committed by Facebook GitHub Bot
parent 82e9c6ad61
commit e8739e962d
14 changed files with 444 additions and 5 deletions
@@ -50,6 +50,10 @@ type Props = $ReadOnly<{|
accessibilityRole?: ?AccessibilityRole,
accessibilityState?: ?AccessibilityState,
accessibilityValue?: ?AccessibilityValue,
'aria-valuemax'?: AccessibilityValue['max'],
'aria-valuemin'?: AccessibilityValue['min'],
'aria-valuenow'?: AccessibilityValue['now'],
'aria-valuetext'?: AccessibilityValue['text'],
accessibilityViewIsModal?: ?boolean,
accessible?: ?boolean,
@@ -240,6 +244,13 @@ function Pressable(props: Props, forwardedRef): React.Node {
_accessibilityState =
disabled != null ? {..._accessibilityState, disabled} : _accessibilityState;
const accessibilityValue = {
max: props['aria-valuemax'] ?? props.accessibilityValue?.max,
min: props['aria-valuemin'] ?? props.accessibilityValue?.min,
now: props['aria-valuenow'] ?? props.accessibilityValue?.now,
text: props['aria-valuetext'] ?? props.accessibilityValue?.text,
};
const accessibilityLiveRegion =
ariaLive === 'off' ? 'none' : ariaLive ?? props.accessibilityLiveRegion;
@@ -250,6 +261,7 @@ function Pressable(props: Props, forwardedRef): React.Node {
accessibilityLiveRegion,
accessibilityState: _accessibilityState,
focusable: focusable !== false,
accessibilityValue,
hitSlop,
};
@@ -11,6 +11,14 @@ exports[`<Pressable /> should render as expected: should deep render when mocked
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
@@ -39,6 +47,14 @@ exports[`<Pressable /> should render as expected: should deep render when not mo
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
@@ -79,6 +95,14 @@ exports[`<Pressable disabled={true} /> should be disabled when disabled is true:
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
@@ -107,6 +131,14 @@ exports[`<Pressable disabled={true} /> should be disabled when disabled is true:
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
@@ -151,6 +183,14 @@ exports[`<Pressable disabled={true} accessibilityState={{}} /> should be disable
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
@@ -179,6 +219,14 @@ exports[`<Pressable disabled={true} accessibilityState={{}} /> should be disable
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
@@ -225,6 +273,14 @@ exports[`<Pressable disabled={true} accessibilityState={{checked: true}} /> shou
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
@@ -253,6 +309,14 @@ exports[`<Pressable disabled={true} accessibilityState={{checked: true}} /> shou
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
@@ -307,6 +371,14 @@ exports[`<Pressable disabled={true} accessibilityState={{disabled: false}} /> sh
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
@@ -335,6 +407,14 @@ exports[`<Pressable disabled={true} accessibilityState={{disabled: false}} /> sh
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={true}
@@ -146,6 +146,13 @@ class TouchableBounce extends React.Component<Props, State> {
this.props['aria-selected'] ?? this.props.accessibilityState?.selected,
};
const accessibilityValue = {
max: this.props['aria-valuemax'] ?? this.props.accessibilityValue?.max,
min: this.props['aria-valuemin'] ?? this.props.accessibilityValue?.min,
now: this.props['aria-valuenow'] ?? this.props.accessibilityValue?.now,
text: this.props['aria-valuetext'] ?? this.props.accessibilityValue?.text,
};
return (
<Animated.View
style={[{transform: [{scale: this.state.scale}]}, this.props.style]}
@@ -157,7 +164,7 @@ class TouchableBounce extends React.Component<Props, State> {
accessibilityState={_accessibilityState}
accessibilityActions={this.props.accessibilityActions}
onAccessibilityAction={this.props.onAccessibilityAction}
accessibilityValue={this.props.accessibilityValue}
accessibilityValue={accessibilityValue}
accessibilityLiveRegion={accessibilityLiveRegion}
importantForAccessibility={
this.props['aria-hidden'] === true
@@ -290,10 +290,19 @@ class TouchableHighlight extends React.Component<Props, State> {
disabled: this.props.disabled,
}
: this.props.accessibilityState;
const accessibilityValue = {
max: this.props['aria-valuemax'] ?? this.props.accessibilityValue?.max,
min: this.props['aria-valuemin'] ?? this.props.accessibilityValue?.min,
now: this.props['aria-valuenow'] ?? this.props.accessibilityValue?.now,
text: this.props['aria-valuetext'] ?? this.props.accessibilityValue?.text,
};
const accessibilityLiveRegion =
this.props['aria-live'] === 'off'
? 'none'
: this.props['aria-live'] ?? this.props.accessibilityLiveRegion;
return (
<View
accessible={this.props.accessible !== false}
@@ -302,7 +311,7 @@ class TouchableHighlight extends React.Component<Props, State> {
accessibilityLanguage={this.props.accessibilityLanguage}
accessibilityRole={this.props.accessibilityRole}
accessibilityState={accessibilityState}
accessibilityValue={this.props.accessibilityValue}
accessibilityValue={accessibilityValue}
accessibilityActions={this.props.accessibilityActions}
onAccessibilityAction={this.props.onAccessibilityAction}
importantForAccessibility={
@@ -273,6 +273,13 @@ class TouchableNativeFeedback extends React.Component<Props, State> {
}
: _accessibilityState;
const accessibilityValue = {
max: this.props['aria-valuemax'] ?? this.props.accessibilityValue?.max,
min: this.props['aria-valuemin'] ?? this.props.accessibilityValue?.min,
now: this.props['aria-valuenow'] ?? this.props.accessibilityValue?.now,
text: this.props['aria-valuetext'] ?? this.props.accessibilityValue?.text,
};
const accessibilityLiveRegion =
this.props['aria-live'] === 'off'
? 'none'
@@ -296,7 +303,7 @@ class TouchableNativeFeedback extends React.Component<Props, State> {
accessibilityState: _accessibilityState,
accessibilityActions: this.props.accessibilityActions,
onAccessibilityAction: this.props.onAccessibilityAction,
accessibilityValue: this.props.accessibilityValue,
accessibilityValue: accessibilityValue,
importantForAccessibility:
this.props['aria-hidden'] === true
? 'no-hide-descendants'
@@ -235,6 +235,13 @@ class TouchableOpacity extends React.Component<Props, State> {
}
: _accessibilityState;
const accessibilityValue = {
max: this.props['aria-valuemax'] ?? this.props.accessibilityValue?.max,
min: this.props['aria-valuemin'] ?? this.props.accessibilityValue?.min,
now: this.props['aria-valuenow'] ?? this.props.accessibilityValue?.now,
text: this.props['aria-valuetext'] ?? this.props.accessibilityValue?.text,
};
const accessibilityLiveRegion =
this.props['aria-live'] === 'off'
? 'none'
@@ -250,7 +257,7 @@ class TouchableOpacity extends React.Component<Props, State> {
accessibilityState={_accessibilityState}
accessibilityActions={this.props.accessibilityActions}
onAccessibilityAction={this.props.onAccessibilityAction}
accessibilityValue={this.props.accessibilityValue}
accessibilityValue={accessibilityValue}
importantForAccessibility={
this.props['aria-hidden'] === true
? 'no-hide-descendants'
@@ -40,6 +40,10 @@ type Props = $ReadOnly<{|
accessibilityRole?: ?AccessibilityRole,
accessibilityState?: ?AccessibilityState,
accessibilityValue?: ?AccessibilityValue,
'aria-valuemax'?: AccessibilityValue['max'],
'aria-valuemin'?: AccessibilityValue['min'],
'aria-valuenow'?: AccessibilityValue['now'],
'aria-valuetext'?: AccessibilityValue['text'],
accessibilityViewIsModal?: ?boolean,
accessible?: ?boolean,
/**
@@ -91,6 +95,10 @@ const PASSTHROUGH_PROPS = [
'accessibilityLiveRegion',
'accessibilityRole',
'accessibilityValue',
'aria-valuemax',
'aria-valuemin',
'aria-valuenow',
'aria-valuetext',
'accessibilityViewIsModal',
'hitSlop',
'importantForAccessibility',
@@ -2,6 +2,14 @@
exports[`TouchableHighlight renders correctly 1`] = `
<View
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -26,6 +34,14 @@ exports[`TouchableHighlight with disabled state should be disabled when disabled
"disabled": true,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -47,6 +63,14 @@ exports[`TouchableHighlight with disabled state should be disabled when disabled
"disabled": true,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -68,6 +92,14 @@ exports[`TouchableHighlight with disabled state should disable button when acces
"disabled": true,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -90,6 +122,14 @@ exports[`TouchableHighlight with disabled state should keep accessibilityState w
"disabled": true,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -111,6 +151,14 @@ exports[`TouchableHighlight with disabled state should overwrite accessibilitySt
"disabled": true,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -11,6 +11,14 @@ exports[`<TouchableNativeFeedback /> should render as expected 1`] = `
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -34,6 +42,14 @@ exports[`<TouchableNativeFeedback disabled={false} accessibilityState={{disabled
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -57,6 +73,14 @@ exports[`<TouchableNativeFeedback disabled={true} accessibilityState={{}}> shoul
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -80,6 +104,14 @@ exports[`<TouchableNativeFeedback disabled={true} accessibilityState={{checked:
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -103,6 +135,14 @@ exports[`<TouchableNativeFeedback disabled={true} accessibilityState={{disabled:
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -126,6 +166,14 @@ exports[`<TouchableNativeFeedback disabled={true}> should be disabled when disab
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -149,6 +197,14 @@ exports[`TouchableWithoutFeedback renders correctly 1`] = `
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={false}
onClick={[Function]}
@@ -11,6 +11,14 @@ exports[`TouchableOpacity renders correctly 1`] = `
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
@@ -44,6 +52,14 @@ exports[`TouchableOpacity renders in disabled state when a disabled prop is pass
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
@@ -77,6 +93,14 @@ exports[`TouchableOpacity renders in disabled state when a key disabled in acces
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
+9 -1
View File
@@ -131,6 +131,14 @@ const View: React.AbstractComponent<
treeitem: undefined,
};
const accessibilityValue = {
max: otherProps['aria-valuemax'] ?? otherProps.accessibilityValue?.max,
min: otherProps['aria-valuemin'] ?? otherProps.accessibilityValue?.min,
now: otherProps['aria-valuenow'] ?? otherProps.accessibilityValue?.now,
text: otherProps['aria-valuetext'] ?? otherProps.accessibilityValue?.text,
};
const restWithDefaultProps = {...otherProps, accessibilityValue};
const flattenedStyle = flattenStyle(style);
const newPointerEvents = flattenedStyle?.pointerEvents || pointerEvents;
@@ -153,7 +161,7 @@ const View: React.AbstractComponent<
? 'no-hide-descendants'
: importantForAccessibility
}
{...restProps}
{...restWithDefaultProps}
style={style}
pointerEvents={newPointerEvents}
ref={forwardedRef}
@@ -488,6 +488,15 @@ export type ViewProps = $ReadOnly<{|
accessibilityState?: ?AccessibilityState,
accessibilityValue?: ?AccessibilityValue,
/**
* alias for accessibilityState
* It represents textual description of a component's value, or for range-based components, such as sliders and progress bars.
*/
'aria-valuemax'?: ?AccessibilityValue['max'],
'aria-valuemin'?: ?AccessibilityValue['min'],
'aria-valuenow'?: ?AccessibilityValue['now'],
'aria-valuetext'?: ?AccessibilityValue['text'],
/**
* Provides an array of custom actions available for accessibility.
*
@@ -12,6 +12,14 @@ exports[`<Button /> should be disabled and it should set accessibilityState to d
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
@@ -70,6 +78,14 @@ exports[`<Button /> should be disabled when disabled is empty and accessibilityS
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
@@ -128,6 +144,14 @@ exports[`<Button /> should be disabled when disabled={true} and accessibilitySta
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
@@ -186,6 +210,14 @@ exports[`<Button /> should be set importantForAccessibility={no-hide-descendants
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
@@ -240,6 +272,14 @@ exports[`<Button /> should be set importantForAccessibility={no-hide-descendants
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
@@ -294,6 +334,14 @@ exports[`<Button /> should not be disabled when disabled={false} and accessibili
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
@@ -348,6 +396,14 @@ exports[`<Button /> should not be disabled when disabled={false} and accessibili
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
@@ -402,6 +458,14 @@ exports[`<Button /> should overwrite accessibilityState with value of disabled p
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
@@ -460,6 +524,14 @@ exports[`<Button /> should render as expected 1`] = `
"selected": undefined,
}
}
accessibilityValue={
Object {
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
collapsable={false}
focusable={false}
@@ -930,6 +930,92 @@ class FakeSliderExample extends React.Component<{}, FakeSliderExampleState> {
}
}
class FakeSliderExampleForAccessibilityValue extends React.Component<
{},
FakeSliderExampleState,
> {
state: FakeSliderExampleState = {
current: 50,
textualValue: 'center',
};
increment: () => void = () => {
let newValue = this.state.current + 2;
if (newValue > 100) {
newValue = 100;
}
this.setState({
current: newValue,
});
};
decrement: () => void = () => {
let newValue = this.state.current - 2;
if (newValue < 0) {
newValue = 0;
}
this.setState({
current: newValue,
});
};
render(): React.Node {
return (
<View>
<View
accessible={true}
accessibilityLabel="Fake Slider"
accessibilityRole="adjustable"
accessibilityActions={[{name: 'increment'}, {name: 'decrement'}]}
onAccessibilityAction={event => {
switch (event.nativeEvent.actionName) {
case 'increment':
this.increment();
break;
case 'decrement':
this.decrement();
break;
}
}}
aria-valuemax={100}
aria-valuemin={0}
aria-valuetext={'slider aria value text'}
aria-valuenow={this.state.current}>
<Text>Fake Slider</Text>
</View>
<TouchableWithoutFeedback
accessible={true}
accessibilityLabel="Equalizer"
accessibilityRole="adjustable"
accessibilityActions={[{name: 'increment'}, {name: 'decrement'}]}
onAccessibilityAction={event => {
switch (event.nativeEvent.actionName) {
case 'increment':
if (this.state.textualValue === 'center') {
this.setState({textualValue: 'right'});
} else if (this.state.textualValue === 'left') {
this.setState({textualValue: 'center'});
}
break;
case 'decrement':
if (this.state.textualValue === 'center') {
this.setState({textualValue: 'left'});
} else if (this.state.textualValue === 'right') {
this.setState({textualValue: 'center'});
}
break;
}
}}
accessibilityValue={{text: this.state.textualValue}}>
<View>
<Text>Equalizer</Text>
</View>
</TouchableWithoutFeedback>
</View>
);
}
}
class AnnounceForAccessibility extends React.Component<{}> {
_handleOnPress = (): TimeoutID =>
setTimeout(
@@ -1309,6 +1395,12 @@ exports.examples = [
return <FakeSliderExample />;
},
},
{
title: 'Fake SliderExample For AccessibilityValue',
render(): React.Element<typeof FakeSliderExampleForAccessibilityValue> {
return <FakeSliderExampleForAccessibilityValue />;
},
},
{
title: 'Check if the display options are enabled',
render(): React.Element<typeof DisplayOptionsStatusExample> {