Skip to content
Open
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
23 changes: 22 additions & 1 deletion packages/react-native/Libraries/Components/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,21 @@ export type ButtonProps = Readonly<{
importantForAccessibility?: ?('auto' | 'yes' | 'no' | 'no-hide-descendants'),
accessibilityHint?: ?string,

/**
* Identifies the element that labels this button. The value should match
* the `nativeID` of the related element.
*
* @platform android
*/
accessibilityLabelledBy?: ?string | ?Array<string>,

/**
* Alias for `accessibilityLabelledBy`.
*
* @platform android
*/
'aria-labelledby'?: ?string,

/**
* A BCP 47 language tag for the screen reader to use when reading text
* content.
Expand Down Expand Up @@ -211,8 +226,10 @@ const Button: component(
'aria-disabled': ariaDisabled,
'aria-expanded': ariaExpanded,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy,
'aria-selected': ariaSelected,
importantForAccessibility,
accessibilityLabelledBy,
color,
onPress,
touchSoundDisabled,
Expand Down Expand Up @@ -269,11 +286,14 @@ const Button: component(
Platform.OS === 'android' ? title.toUpperCase() : title;

// If `no` is specified for `importantForAccessibility`, it will be changed to `no-hide-descendants` because the text inside should not be focused.
const _importantForAccessibility =
let _importantForAccessibility =
importantForAccessibility === 'no'
? 'no-hide-descendants'
: importantForAccessibility;

const _accessibilityLabelledBy =
ariaLabelledBy?.split(/\s*,\s*/g) ?? accessibilityLabelledBy;

return (
<NativeTouchable
accessible={accessible}
Expand All @@ -282,6 +302,7 @@ const Button: component(
accessibilityLabel={ariaLabel || accessibilityLabel}
accessibilityHint={accessibilityHint}
accessibilityLanguage={accessibilityLanguage}
accessibilityLabelledBy={_accessibilityLabelledBy}
accessibilityRole="button"
accessibilityState={_accessibilityState}
importantForAccessibility={_importantForAccessibility}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,14 @@ class TouchableHighlightImpl extends React.Component<

const accessibilityLabel =
this.props['aria-label'] ?? this.props.accessibilityLabel;
const accessibilityLabelledBy =
this.props['aria-labelledby']?.split(/\s*,\s*/g) ??
this.props.accessibilityLabelledBy;
return (
<View
accessible={this.props.accessible !== false}
accessibilityLabel={accessibilityLabel}
accessibilityLabelledBy={accessibilityLabelledBy}
accessibilityHint={this.props.accessibilityHint}
accessibilityLanguage={this.props.accessibilityLanguage}
accessibilityRole={this.props.accessibilityRole}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ class TouchableNativeFeedback extends React.Component<

const accessibilityLabel =
this.props['aria-label'] ?? this.props.accessibilityLabel;
const accessibilityLabelledBy =
this.props['aria-labelledby']?.split(/\s*,\s*/g) ??
this.props.accessibilityLabelledBy;
return cloneElement(
element,
{
Expand All @@ -331,6 +334,7 @@ class TouchableNativeFeedback extends React.Component<
accessibilityHint: this.props.accessibilityHint,
accessibilityLanguage: this.props.accessibilityLanguage,
accessibilityLabel: accessibilityLabel,
accessibilityLabelledBy: accessibilityLabelledBy,
accessibilityRole: this.props.accessibilityRole,
accessibilityState: _accessibilityState,
accessibilityActions: this.props.accessibilityActions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,14 @@ class TouchableOpacity extends React.Component<

const accessibilityLabel =
this.props['aria-label'] ?? this.props.accessibilityLabel;
const accessibilityLabelledBy =
this.props['aria-labelledby']?.split(/\s*,\s*/g) ??
this.props.accessibilityLabelledBy;
return (
<Animated.View
accessible={this.props.accessible !== false}
accessibilityLabel={accessibilityLabel}
accessibilityLabelledBy={accessibilityLabelledBy}
accessibilityHint={this.props.accessibilityHint}
accessibilityLanguage={this.props.accessibilityLanguage}
accessibilityRole={this.props.accessibilityRole}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ const PASSTHROUGH_PROPS = [
'accessibilityLanguage',
'accessibilityIgnoresInvertColors',
'accessibilityLabel',
'accessibilityLabelledBy',
'aria-labelledby',
'accessibilityLiveRegion',
'accessibilityRole',
'accessibilityValue',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,46 @@ describe('<Button>', () => {
});
});

describe('accessibilityLabelledBy', () => {
it('propagates accessibilityLabelledBy', () => {
const root = Fantom.createRoot();

Fantom.runTask(() => {
root.render(
<Button title="Hello" accessibilityLabelledBy="formLabel" />,
);
});

expect(
root
.getRenderedOutput({props: ['accessibilityLabelledBy']})
.toJSX(),
).toEqual(
<rn-view accessibilityLabelledBy="[formLabel]">
<rn-paragraph>HELLO</rn-paragraph>
</rn-view>,
);
});

it('maps aria-labelledby to accessibilityLabelledBy', () => {
const root = Fantom.createRoot();

Fantom.runTask(() => {
root.render(<Button title="Hello" aria-labelledby="formLabel" />);
});

expect(
root
.getRenderedOutput({props: ['accessibilityLabelledBy']})
.toJSX(),
).toEqual(
<rn-view accessibilityLabelledBy="[formLabel]">
<rn-paragraph>HELLO</rn-paragraph>
</rn-view>,
);
});
});

describe('importantForAccessibility', () => {
it('propagates "no-hide-descendants"', () => {
const root = Fantom.createRoot();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ SharedDebugStringConvertibleList AccessibilityProps::getDebugProps() const {
"accessibilityLabel",
accessibilityLabel,
defaultProps.accessibilityLabel),
debugStringConvertibleItem(
"accessibilityLabelledBy",
accessibilityLabelledBy.value,
defaultProps.accessibilityLabelledBy.value),
debugStringConvertibleItem(
"accessibilityLiveRegion",
accessibilityLiveRegion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {TouchableOpacityProps} from './Touchable/TouchableOpacity';
export interface ButtonProps extends Pick<
TouchableNativeFeedbackProps & TouchableOpacityProps,
| 'accessibilityLabel'
| 'accessibilityLabelledBy'
| 'accessibilityState'
| 'hasTVPreferredFocus'
| 'nextFocusDown'
Expand All @@ -27,6 +28,8 @@ export interface ButtonProps extends Pick<
| 'onPress'
| 'touchSoundDisabled'
> {
'aria-labelledby'?: string | undefined;

/**
* Text to display inside the button. On Android the given title will be converted to the uppercased form.
*/
Expand Down