From e8739e962de3398bc7e42675b1d87ab35993f705 Mon Sep 17 00:00:00 2001 From: Daksh Bhardwaj Date: Fri, 9 Sep 2022 04:48:41 -0700 Subject: [PATCH] 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 Reviewed By: cipolleschi Differential Revision: D39206362 Pulled By: jacdebug fbshipit-source-id: e7ed263badac789d529dd21e961cda5302b031e3 --- Libraries/Components/Pressable/Pressable.js | 12 +++ .../__snapshots__/Pressable-test.js.snap | 80 ++++++++++++++++ .../Components/Touchable/TouchableBounce.js | 9 +- .../Touchable/TouchableHighlight.js | 11 ++- .../Touchable/TouchableNativeFeedback.js | 9 +- .../Components/Touchable/TouchableOpacity.js | 9 +- .../Touchable/TouchableWithoutFeedback.js | 8 ++ .../TouchableHighlight-test.js.snap | 48 ++++++++++ .../TouchableNativeFeedback-test.js.snap | 56 +++++++++++ .../TouchableOpacity-test.js.snap | 24 +++++ Libraries/Components/View/View.js | 10 +- Libraries/Components/View/ViewPropTypes.js | 9 ++ .../__snapshots__/Button-test.js.snap | 72 +++++++++++++++ .../Accessibility/AccessibilityExample.js | 92 +++++++++++++++++++ 14 files changed, 444 insertions(+), 5 deletions(-) diff --git a/Libraries/Components/Pressable/Pressable.js b/Libraries/Components/Pressable/Pressable.js index 9c41c9948b0bf8..7ad0c494e0155c 100644 --- a/Libraries/Components/Pressable/Pressable.js +++ b/Libraries/Components/Pressable/Pressable.js @@ -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, }; diff --git a/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap b/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap index 4e03820efa8ea3..e348e02347e52b 100644 --- a/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap +++ b/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap @@ -11,6 +11,14 @@ exports[` 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[` 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[` 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[` 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[` 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[` 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[` shou "selected": undefined, } } + accessibilityValue={ + Object { + "max": undefined, + "min": undefined, + "now": undefined, + "text": undefined, + } + } accessible={true} collapsable={false} focusable={true} @@ -253,6 +309,14 @@ exports[` shou "selected": undefined, } } + accessibilityValue={ + Object { + "max": undefined, + "min": undefined, + "now": undefined, + "text": undefined, + } + } accessible={true} collapsable={false} focusable={true} @@ -307,6 +371,14 @@ exports[` sh "selected": undefined, } } + accessibilityValue={ + Object { + "max": undefined, + "min": undefined, + "now": undefined, + "text": undefined, + } + } accessible={true} collapsable={false} focusable={true} @@ -335,6 +407,14 @@ exports[` sh "selected": undefined, } } + accessibilityValue={ + Object { + "max": undefined, + "min": undefined, + "now": undefined, + "text": undefined, + } + } accessible={true} collapsable={false} focusable={true} diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js index 3f611f25c03e3a..8b05a64ffd896b 100644 --- a/Libraries/Components/Touchable/TouchableBounce.js +++ b/Libraries/Components/Touchable/TouchableBounce.js @@ -146,6 +146,13 @@ class TouchableBounce extends React.Component { 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 ( { accessibilityState={_accessibilityState} accessibilityActions={this.props.accessibilityActions} onAccessibilityAction={this.props.onAccessibilityAction} - accessibilityValue={this.props.accessibilityValue} + accessibilityValue={accessibilityValue} accessibilityLiveRegion={accessibilityLiveRegion} importantForAccessibility={ this.props['aria-hidden'] === true diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index 7729d067daef03..053824e0edd790 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -290,10 +290,19 @@ class TouchableHighlight extends React.Component { 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 ( { 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={ diff --git a/Libraries/Components/Touchable/TouchableNativeFeedback.js b/Libraries/Components/Touchable/TouchableNativeFeedback.js index 21a4061e13ede2..ac5b793ac85295 100644 --- a/Libraries/Components/Touchable/TouchableNativeFeedback.js +++ b/Libraries/Components/Touchable/TouchableNativeFeedback.js @@ -273,6 +273,13 @@ class TouchableNativeFeedback extends React.Component { } : _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 { 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' diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index 016f65bd8a5dde..7e6537d0c230b2 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -235,6 +235,13 @@ class TouchableOpacity extends React.Component { } : _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 { 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' diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js index ab5a019bc9173f..ab2d844253b717 100755 --- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js +++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js @@ -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', diff --git a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap index 70eaabe704a1f0..35c845e97493fc 100644 --- a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap +++ b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap @@ -2,6 +2,14 @@ exports[`TouchableHighlight renders correctly 1`] = ` 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[` shoul "selected": undefined, } } + accessibilityValue={ + Object { + "max": undefined, + "min": undefined, + "now": undefined, + "text": undefined, + } + } accessible={true} focusable={false} onClick={[Function]} @@ -80,6 +104,14 @@ exports[` 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]} diff --git a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableOpacity-test.js.snap b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableOpacity-test.js.snap index 92d94f7a1630cb..17f2e7f6f764e0 100644 --- a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableOpacity-test.js.snap +++ b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableOpacity-test.js.snap @@ -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} diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 9fbab16d0b8130..97020938e6d01b 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -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} diff --git a/Libraries/Components/View/ViewPropTypes.js b/Libraries/Components/View/ViewPropTypes.js index 2f76343639869a..c6cb8655d50532 100644 --- a/Libraries/Components/View/ViewPropTypes.js +++ b/Libraries/Components/View/ViewPropTypes.js @@ -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. * diff --git a/Libraries/Components/__tests__/__snapshots__/Button-test.js.snap b/Libraries/Components/__tests__/__snapshots__/Button-test.js.snap index 57c18ada171a0d..5b4294e0e8c850 100644 --- a/Libraries/Components/__tests__/__snapshots__/Button-test.js.snap +++ b/Libraries/Components/__tests__/__snapshots__/Button-test.js.snap @@ -12,6 +12,14 @@ exports[`