diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 31ebc1f81497bf..be6cab34a348c3 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -1438,6 +1438,8 @@ function InternalTextInput(props: Props): React.Node { } else if (Platform.OS === 'android') { const style = [props.style]; const autoCapitalize = props.autoCapitalize || 'sentences'; + const _accessibilityLabelledBy = + props?.['aria-labelledby'] ?? props?.accessibilityLabelledBy; const placeholder = props.placeholder ?? ''; let children = props.children; const childCount = React.Children.count(children); @@ -1464,6 +1466,7 @@ function InternalTextInput(props: Props): React.Node { {...eventHandlers} accessible={accessible} accessibilityState={_accessibilityState} + accessibilityLabelledBy={_accessibilityLabelledBy} autoCapitalize={autoCapitalize} submitBehavior={submitBehavior} caretHidden={caretHidden} diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index feb9b207923f30..129f50d36e2d95 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -31,12 +31,25 @@ const View: React.AbstractComponent< ( { accessibilityElementsHidden, - accessibilityLiveRegion, - 'aria-live': ariaLive, accessibilityLabel, + accessibilityLabelledBy, + accessibilityLiveRegion, accessibilityRole, - 'aria-label': ariaLabel, + accessibilityState, + accessibilityValue, + 'aria-busy': ariaBusy, + 'aria-checked': ariaChecked, + 'aria-disabled': ariaDisabled, + 'aria-expanded': ariaExpanded, 'aria-hidden': ariaHidden, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + 'aria-live': ariaLive, + 'aria-selected': ariaSelected, + 'aria-valuemax': ariaValueMax, + 'aria-valuemin': ariaValueMin, + 'aria-valuenow': ariaValueNow, + 'aria-valuetext': ariaValueText, focusable, id, importantForAccessibility, @@ -49,14 +62,8 @@ const View: React.AbstractComponent< }: ViewProps, forwardedRef, ) => { - const { - accessibilityState, - 'aria-busy': ariaBusy, - 'aria-checked': ariaChecked, - 'aria-disabled': ariaDisabled, - 'aria-expanded': ariaExpanded, - 'aria-selected': ariaSelected, - } = otherProps; + const _accessibilityLabelledBy = + ariaLabelledBy?.split(/\s*,\s*/g) ?? accessibilityLabelledBy; const _accessibilityState = { busy: ariaBusy ?? accessibilityState?.busy, @@ -66,6 +73,13 @@ const View: React.AbstractComponent< selected: ariaSelected ?? accessibilityState?.selected, }; + const _accessibilityValue = { + max: ariaValueMax ?? accessibilityValue?.max, + min: ariaValueMin ?? accessibilityValue?.min, + now: ariaValueNow ?? accessibilityValue?.now, + text: ariaValueText ?? accessibilityValue?.text, + }; + // Map role values to AccessibilityRole values const roleToAccessibilityRoleMapping = { alert: 'alert', @@ -134,20 +148,13 @@ 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; return ( { ? loadingIndicatorSource.uri : null, ref: forwardedRef, - accessible: props.alt !== undefined ? true : props.accessible, accessibilityLabel: props['aria-label'] ?? props.accessibilityLabel ?? props.alt, + accessibilityLabelledBy: + props?.['aria-labelledby'] ?? props?.accessibilityLabelledBy, + accessible: props.alt !== undefined ? true : props.accessible, accessibilityState: { busy: props['aria-busy'] ?? props.accessibilityState?.busy, checked: props['aria-checked'] ?? props.accessibilityState?.checked, diff --git a/Libraries/Image/ImageProps.js b/Libraries/Image/ImageProps.js index 501d30e51c57f6..5822188c5afac5 100644 --- a/Libraries/Image/ImageProps.js +++ b/Libraries/Image/ImageProps.js @@ -92,6 +92,12 @@ export type ImageProps = {| */ 'aria-label'?: ?Stringish, + /** + * Reperesents the nativeID of the associated label. When the assistive technology focuses on the component with this props. + * + * @platform android + */ + 'aria-labelledby'?: ?string, /** * The text that's read by the screen reader when the user interacts with * the image. diff --git a/Libraries/Text/TextProps.js b/Libraries/Text/TextProps.js index 5b99f0cf0b1ea6..16e4f87b25cc03 100644 --- a/Libraries/Text/TextProps.js +++ b/Libraries/Text/TextProps.js @@ -89,6 +89,14 @@ export type TextProps = $ReadOnly<{| 'aria-disabled'?: ?boolean, 'aria-expanded'?: ?boolean, 'aria-selected'?: ?boolean, + + /** + * Reperesents the nativeID of the associated label text. When the assistive technology focuses on the component with this props, the text is read aloud. + * + * @platform android + */ + 'aria-labelledby'?: ?string, + children?: ?Node, /** diff --git a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js index dc5ab8923c2218..9efdbfdb2da011 100644 --- a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js +++ b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js @@ -1446,4 +1446,18 @@ exports.examples = [ ); }, }, + { + title: 'TextInput with aria-labelledby attribute"', + render(): React.Element { + return ( + + Phone Number + + + ); + }, + }, ];