Skip to content

Commit 62021eb

Browse files
fabOnReactfacebook-github-bot
authored andcommitted
adding importantForAccessibility for Text, Button, ImageBackground (#34245)
Summary: Previously published by [grgr-dkrk][2] as [https://github.com/facebook/react-native/issues/31296][3], fixes the following issues: 1) ImportantForAccessibility="no" does not work on Text, Button 2) ImportantForAccessibility="no-hide-descendants" does not work on Text, Button, or ImageBackground. Note: On ImageBackground, focus is prevented on the imageBackground node itself, but focus is not prevented on its descendants. Note: [Button component expected behavior for prop importantForAccessibility][4] >Some components like Button seem like atomic units, but they end up rendering a hierarchy of components for example a wrapper View with a Text inside them. Allowing those descendants to become focusable, breaks the model of these being a single component to consider and forcing no-hide-descendants makes sense here. >The other option is always to render any descendants of these elements with importantForAccessibility="no", so they can never be focusable on their own. This would have the same result, **BUT may potentially cause issues when the descendant content is supposed to automatically get moved up to the focusable ancestor to act as a label** (which is what Talkback does with unfocusable text elements by default). Note: [importantForAccessibility="no" does not allow screenreader focus on nested Text Components with accessibilityRole="link" or inline Images][5] fixes #30850 related #33690 ## Changelog [Android] [Fixed] - adding importantForAccessibility for Text, Button, ImageBackground Pull Request resolved: #34245 Test Plan: 1) testing ImageBackground importantForAccessiblity ([link to test][1]) 2) importantForAccessibility="no" does not allow screenreader focus on nested Text Components with accessibilityRole="link" or inline Images ([link to test][5]) 3) testing ImageBackground importantForAccessiblity ([link to test][6]) 4) Button with importantForAccessibility=no ([link to test][7]) [1]: #31296 (comment) "" [2]: https://github.com/grgr-dkrk "grgr-dkrk" [3]: #31296 "#31296" [4]: #31296 (comment) "expected behaviour with prop importantForAccessibility in Button component" [5]: #30850 (comment) "importantForAccessibility=no does not allow screenreader focus on nested Text Components with accessibilityRole=link or inline Images" [6]: #34245 (comment) "testing ImageBackground importantForAccessiblity" [7]: #34245 (comment) "Button with importantForAccessibility=no" Reviewed By: cipolleschi Differential Revision: D38121992 Pulled By: dmitryrykun fbshipit-source-id: 368b4dcb47d7940274820aa2e39ed5e2ca068821
1 parent 03de197 commit 62021eb

File tree

7 files changed

+400
-1
lines changed

7 files changed

+400
-1
lines changed

Libraries/Components/Button.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ type ButtonProps = $ReadOnly<{|
145145
accessibilityActions?: ?$ReadOnlyArray<AccessibilityActionInfo>,
146146
onAccessibilityAction?: ?(event: AccessibilityActionEvent) => mixed,
147147
accessibilityState?: ?AccessibilityState,
148+
149+
/**
150+
* [Android] Controlling if a view fires accessibility events and if it is reported to accessibility services.
151+
*/
152+
importantForAccessibility?: ?('auto' | 'yes' | 'no' | 'no-hide-descendants'),
148153
accessibilityHint?: ?string,
149154
accessibilityLanguage?: ?Stringish,
150155
|}>;
@@ -264,6 +269,7 @@ class Button extends React.Component<ButtonProps> {
264269
render(): React.Node {
265270
const {
266271
accessibilityLabel,
272+
importantForAccessibility,
267273
color,
268274
onPress,
269275
touchSoundDisabled,
@@ -315,6 +321,12 @@ class Button extends React.Component<ButtonProps> {
315321
const Touchable =
316322
Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity;
317323

324+
// If `no` is specified for `importantForAccessibility`, it will be changed to `no-hide-descendants` because the text inside should not be focused.
325+
const _importantForAccessibility =
326+
importantForAccessibility === 'no'
327+
? 'no-hide-descendants'
328+
: importantForAccessibility;
329+
318330
return (
319331
<Touchable
320332
accessible={accessible}
@@ -325,6 +337,7 @@ class Button extends React.Component<ButtonProps> {
325337
accessibilityLanguage={accessibilityLanguage}
326338
accessibilityRole="button"
327339
accessibilityState={accessibilityState}
340+
importantForAccessibility={_importantForAccessibility}
328341
hasTVPreferredFocus={hasTVPreferredFocus}
329342
nextFocusDown={nextFocusDown}
330343
nextFocusForward={nextFocusForward}

Libraries/Components/__tests__/Button-test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,14 @@ describe('<Button />', () => {
4646
expect(ReactTestRenderer.create( <Button title="Test Button" disabled={false} accessibilityState={{disabled: false}} />)
4747
).toMatchSnapshot();
4848
});
49+
50+
it('should be set importantForAccessibility={no-hide-descendants} when importantForAccessibility={no-hide-descendants}', () => {
51+
expect(ReactTestRenderer.create( <Button title="Test Button" importantForAccessibility={'no-hide-descendants'} />)
52+
).toMatchSnapshot();
53+
});
54+
55+
it('should be set importantForAccessibility={no-hide-descendants} when importantForAccessibility={no}', () => {
56+
expect(ReactTestRenderer.create( <Button title="Test Button" importantForAccessibility={'no'} />)
57+
).toMatchSnapshot();
58+
});
4959
});

Libraries/Components/__tests__/__snapshots__/Button-test.js.snap

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,96 @@ exports[`<Button /> should be disabled when disabled={true} and accessibilitySta
162162
</View>
163163
`;
164164

165+
exports[`<Button /> should be set importantForAccessibility={no-hide-descendants} when importantForAccessibility={no} 1`] = `
166+
<View
167+
accessibilityRole="button"
168+
accessible={true}
169+
collapsable={false}
170+
focusable={false}
171+
importantForAccessibility="no-hide-descendants"
172+
onClick={[Function]}
173+
onResponderGrant={[Function]}
174+
onResponderMove={[Function]}
175+
onResponderRelease={[Function]}
176+
onResponderTerminate={[Function]}
177+
onResponderTerminationRequest={[Function]}
178+
onStartShouldSetResponder={[Function]}
179+
style={
180+
Object {
181+
"opacity": 1,
182+
}
183+
}
184+
>
185+
<View
186+
style={
187+
Array [
188+
Object {},
189+
]
190+
}
191+
>
192+
<Text
193+
style={
194+
Array [
195+
Object {
196+
"color": "#007AFF",
197+
"fontSize": 18,
198+
"margin": 8,
199+
"textAlign": "center",
200+
},
201+
]
202+
}
203+
>
204+
Test Button
205+
</Text>
206+
</View>
207+
</View>
208+
`;
209+
210+
exports[`<Button /> should be set importantForAccessibility={no-hide-descendants} when importantForAccessibility={no-hide-descendants} 1`] = `
211+
<View
212+
accessibilityRole="button"
213+
accessible={true}
214+
collapsable={false}
215+
focusable={false}
216+
importantForAccessibility="no-hide-descendants"
217+
onClick={[Function]}
218+
onResponderGrant={[Function]}
219+
onResponderMove={[Function]}
220+
onResponderRelease={[Function]}
221+
onResponderTerminate={[Function]}
222+
onResponderTerminationRequest={[Function]}
223+
onStartShouldSetResponder={[Function]}
224+
style={
225+
Object {
226+
"opacity": 1,
227+
}
228+
}
229+
>
230+
<View
231+
style={
232+
Array [
233+
Object {},
234+
]
235+
}
236+
>
237+
<Text
238+
style={
239+
Array [
240+
Object {
241+
"color": "#007AFF",
242+
"fontSize": 18,
243+
"margin": 8,
244+
"textAlign": "center",
245+
},
246+
]
247+
}
248+
>
249+
Test Button
250+
</Text>
251+
</View>
252+
</View>
253+
`;
254+
165255
exports[`<Button /> should not be disabled when disabled={false} and accessibilityState={{disabled: false}} 1`] = `
166256
<View
167257
accessibilityRole="button"

Libraries/Image/ImageBackground.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,25 @@ class ImageBackground extends React.Component<ImageBackgroundProps> {
6666
};
6767

6868
render(): React.Node {
69-
const {children, style, imageStyle, imageRef, ...props} = this.props;
69+
const {
70+
children,
71+
style,
72+
imageStyle,
73+
imageRef,
74+
importantForAccessibility,
75+
...props
76+
} = this.props;
77+
7078
const flattenedStyle = flattenStyle(style);
7179
return (
7280
<View
7381
accessibilityIgnoresInvertColors={true}
82+
importantForAccessibility={importantForAccessibility}
7483
style={style}
7584
ref={this._captureRef}>
7685
<Image
7786
{...props}
87+
importantForAccessibility={importantForAccessibility}
7888
style={[
7989
StyleSheet.absoluteFill,
8090
{
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @emails oncall+react_native
9+
* @flow strict-local
10+
*/
11+
12+
'use strict';
13+
14+
const React = require('react');
15+
const ImageBackground = require('../ImageBackground');
16+
const render = require('../../../jest/renderer');
17+
18+
describe('<ImageBackground />', () => {
19+
it('should render as <ImageBackground> when mocked', () => {
20+
const instance = render.create(
21+
<ImageBackground
22+
style={{width: 150, height: 50}}
23+
source={{uri: 'foo-bar.jpg'}}
24+
/>,
25+
);
26+
expect(instance).toMatchSnapshot();
27+
});
28+
29+
it('should shallow render as <ImageBackground> when mocked', () => {
30+
const output = render.shallow(
31+
<ImageBackground
32+
style={{width: 150, height: 50}}
33+
source={{uri: 'foo-bar.jpg'}}
34+
/>,
35+
);
36+
expect(output).toMatchSnapshot();
37+
});
38+
39+
it('should shallow render as <ForwardRef(ImageBackground)> when not mocked', () => {
40+
jest.dontMock('../ImageBackground');
41+
42+
const output = render.shallow(
43+
<ImageBackground
44+
style={{width: 150, height: 50}}
45+
source={{uri: 'foo-bar.jpg'}}
46+
/>,
47+
);
48+
expect(output).toMatchSnapshot();
49+
});
50+
51+
it('should render as <RCTImageView> when not mocked', () => {
52+
jest.dontMock('../ImageBackground');
53+
54+
const instance = render.create(
55+
<ImageBackground
56+
style={{width: 150, height: 50}}
57+
source={{uri: 'foo-bar.jpg'}}
58+
/>,
59+
);
60+
expect(instance).toMatchSnapshot();
61+
});
62+
63+
it('should be set importantForAccessibility in <View> and <Image>', () => {
64+
const instance = render.create(
65+
<ImageBackground
66+
importantForAccessibility={'no'}
67+
style={{width: 150, height: 50}}
68+
source={{uri: 'foo-bar.jpg'}}
69+
/>,
70+
);
71+
expect(instance).toMatchSnapshot();
72+
});
73+
});
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`<ImageBackground /> should be set importantForAccessibility in <View> and <Image> 1`] = `
4+
<View
5+
accessibilityIgnoresInvertColors={true}
6+
importantForAccessibility="no"
7+
style={
8+
Object {
9+
"height": 50,
10+
"width": 150,
11+
}
12+
}
13+
>
14+
<Image
15+
importantForAccessibility="no"
16+
source={
17+
Object {
18+
"uri": "foo-bar.jpg",
19+
}
20+
}
21+
style={
22+
Array [
23+
Object {
24+
"bottom": 0,
25+
"left": 0,
26+
"position": "absolute",
27+
"right": 0,
28+
"top": 0,
29+
},
30+
Object {
31+
"height": 50,
32+
"width": 150,
33+
},
34+
undefined,
35+
]
36+
}
37+
/>
38+
</View>
39+
`;
40+
41+
exports[`<ImageBackground /> should render as <ImageBackground> when mocked 1`] = `
42+
<View
43+
accessibilityIgnoresInvertColors={true}
44+
style={
45+
Object {
46+
"height": 50,
47+
"width": 150,
48+
}
49+
}
50+
>
51+
<Image
52+
source={
53+
Object {
54+
"uri": "foo-bar.jpg",
55+
}
56+
}
57+
style={
58+
Array [
59+
Object {
60+
"bottom": 0,
61+
"left": 0,
62+
"position": "absolute",
63+
"right": 0,
64+
"top": 0,
65+
},
66+
Object {
67+
"height": 50,
68+
"width": 150,
69+
},
70+
undefined,
71+
]
72+
}
73+
/>
74+
</View>
75+
`;
76+
77+
exports[`<ImageBackground /> should render as <RCTImageView> when not mocked 1`] = `
78+
<View
79+
accessibilityIgnoresInvertColors={true}
80+
style={
81+
Object {
82+
"height": 50,
83+
"width": 150,
84+
}
85+
}
86+
>
87+
<Image
88+
source={
89+
Object {
90+
"uri": "foo-bar.jpg",
91+
}
92+
}
93+
style={
94+
Array [
95+
Object {
96+
"bottom": 0,
97+
"left": 0,
98+
"position": "absolute",
99+
"right": 0,
100+
"top": 0,
101+
},
102+
Object {
103+
"height": 50,
104+
"width": 150,
105+
},
106+
undefined,
107+
]
108+
}
109+
/>
110+
</View>
111+
`;
112+
113+
exports[`<ImageBackground /> should shallow render as <ForwardRef(ImageBackground)> when not mocked 1`] = `
114+
<ImageBackground
115+
source={
116+
Object {
117+
"uri": "foo-bar.jpg",
118+
}
119+
}
120+
style={
121+
Object {
122+
"height": 50,
123+
"width": 150,
124+
}
125+
}
126+
/>
127+
`;
128+
129+
exports[`<ImageBackground /> should shallow render as <ImageBackground> when mocked 1`] = `
130+
<ImageBackground
131+
source={
132+
Object {
133+
"uri": "foo-bar.jpg",
134+
}
135+
}
136+
style={
137+
Object {
138+
"height": 50,
139+
"width": 150,
140+
}
141+
}
142+
/>
143+
`;

0 commit comments

Comments
 (0)