Skip to content

Commit d9c2f99

Browse files
feat: Add role prop to Text component
1 parent 55b09da commit d9c2f99

File tree

6 files changed

+170
-68
lines changed

6 files changed

+170
-68
lines changed

Libraries/Components/View/View.js

Lines changed: 1 addition & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {ViewProps} from './ViewPropTypes';
1212

1313
import flattenStyle from '../../StyleSheet/flattenStyle';
1414
import TextAncestor from '../../Text/TextAncestor';
15+
import {roleToAccessibilityRoleMapping} from '../../Utilities/AcessibilityMapping';
1516
import ViewNativeComponent from './ViewNativeComponent';
1617
import * as React from 'react';
1718

@@ -80,74 +81,6 @@ const View: React.AbstractComponent<
8081
text: ariaValueText ?? accessibilityValue?.text,
8182
};
8283

83-
// Map role values to AccessibilityRole values
84-
const roleToAccessibilityRoleMapping = {
85-
alert: 'alert',
86-
alertdialog: undefined,
87-
application: undefined,
88-
article: undefined,
89-
banner: undefined,
90-
button: 'button',
91-
cell: undefined,
92-
checkbox: 'checkbox',
93-
columnheader: undefined,
94-
combobox: 'combobox',
95-
complementary: undefined,
96-
contentinfo: undefined,
97-
definition: undefined,
98-
dialog: undefined,
99-
directory: undefined,
100-
document: undefined,
101-
feed: undefined,
102-
figure: undefined,
103-
form: undefined,
104-
grid: 'grid',
105-
group: undefined,
106-
heading: 'header',
107-
img: 'image',
108-
link: 'link',
109-
list: 'list',
110-
listitem: undefined,
111-
log: undefined,
112-
main: undefined,
113-
marquee: undefined,
114-
math: undefined,
115-
menu: 'menu',
116-
menubar: 'menubar',
117-
menuitem: 'menuitem',
118-
meter: undefined,
119-
navigation: undefined,
120-
none: 'none',
121-
note: undefined,
122-
presentation: 'none',
123-
progressbar: 'progressbar',
124-
radio: 'radio',
125-
radiogroup: 'radiogroup',
126-
region: undefined,
127-
row: undefined,
128-
rowgroup: undefined,
129-
rowheader: undefined,
130-
scrollbar: 'scrollbar',
131-
searchbox: 'search',
132-
separator: undefined,
133-
slider: 'adjustable',
134-
spinbutton: 'spinbutton',
135-
status: undefined,
136-
summary: 'summary',
137-
switch: 'switch',
138-
tab: 'tab',
139-
table: undefined,
140-
tablist: 'tablist',
141-
tabpanel: undefined,
142-
term: undefined,
143-
timer: 'timer',
144-
toolbar: 'toolbar',
145-
tooltip: undefined,
146-
tree: undefined,
147-
treegrid: undefined,
148-
treeitem: undefined,
149-
};
150-
15184
const flattenedStyle = flattenStyle(style);
15285
const newPointerEvents = flattenedStyle?.pointerEvents || pointerEvents;
15386

Libraries/Components/View/ViewAccessibility.d.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ export interface AccessibilityProps
101101

102102
'aria-live'?: ('polite' | 'assertive' | 'off') | undefined;
103103
'aria-modal'?: boolean | undefined;
104+
105+
/**
106+
* Indicates to accessibility services to treat UI component like a specific role.
107+
*/
108+
role?: Role;
104109
}
105110

106111
export type AccessibilityActionInfo = Readonly<{
@@ -286,3 +291,69 @@ export interface AccessibilityPropsIOS {
286291
*/
287292
accessibilityIgnoresInvertColors?: boolean | undefined;
288293
}
294+
295+
export type Role =
296+
| 'alert'
297+
| 'alertdialog'
298+
| 'application'
299+
| 'article'
300+
| 'banner'
301+
| 'button'
302+
| 'cell'
303+
| 'checkbox'
304+
| 'columnheader'
305+
| 'combobox'
306+
| 'complementary'
307+
| 'contentinfo'
308+
| 'definition'
309+
| 'dialog'
310+
| 'directory'
311+
| 'document'
312+
| 'feed'
313+
| 'figure'
314+
| 'form'
315+
| 'grid'
316+
| 'group'
317+
| 'heading'
318+
| 'img'
319+
| 'link'
320+
| 'list'
321+
| 'listitem'
322+
| 'log'
323+
| 'main'
324+
| 'marquee'
325+
| 'math'
326+
| 'menu'
327+
| 'menubar'
328+
| 'menuitem'
329+
| 'meter'
330+
| 'navigation'
331+
| 'none'
332+
| 'note'
333+
| 'presentation'
334+
| 'progressbar'
335+
| 'radio'
336+
| 'radiogroup'
337+
| 'region'
338+
| 'row'
339+
| 'rowgroup'
340+
| 'rowheader'
341+
| 'scrollbar'
342+
| 'searchbox'
343+
| 'separator'
344+
| 'slider'
345+
| 'spinbutton'
346+
| 'status'
347+
| 'summary'
348+
| 'switch'
349+
| 'tab'
350+
| 'table'
351+
| 'tablist'
352+
| 'tabpanel'
353+
| 'term'
354+
| 'timer'
355+
| 'toolbar'
356+
| 'tooltip'
357+
| 'tree'
358+
| 'treegrid'
359+
| 'treeitem';

Libraries/Text/Text.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {NativeText, NativeVirtualText} from './TextNativeComponent';
2121
import {type TextProps} from './TextProps';
2222
import * as React from 'react';
2323
import {useContext, useMemo, useState} from 'react';
24+
import {roleToAccessibilityRoleMapping} from '../Utilities/AcessibilityMapping';
2425

2526
/**
2627
* Text is the fundamental component for displaying text.
@@ -34,6 +35,7 @@ const Text: React.AbstractComponent<
3435
const {
3536
accessible,
3637
accessibilityLabel,
38+
accessibilityRole,
3739
allowFontScaling,
3840
'aria-busy': ariaBusy,
3941
'aria-checked': ariaChecked,
@@ -55,6 +57,7 @@ const Text: React.AbstractComponent<
5557
onResponderTerminationRequest,
5658
onStartShouldSetResponder,
5759
pressRetentionOffset,
60+
role,
5861
suppressHighlighting,
5962
...restProps
6063
} = props;
@@ -223,6 +226,9 @@ const Text: React.AbstractComponent<
223226
accessibilityState={_accessibilityState}
224227
{...eventHandlersForText}
225228
accessibilityLabel={ariaLabel ?? accessibilityLabel}
229+
accessibilityRole={
230+
role ? roleToAccessibilityRoleMapping[role] : accessibilityRole
231+
}
226232
isHighlighted={isHighlighted}
227233
isPressable={isPressable}
228234
selectable={_selectable}
@@ -246,6 +252,9 @@ const Text: React.AbstractComponent<
246252
}
247253
accessibilityLabel={ariaLabel ?? accessibilityLabel}
248254
accessibilityState={nativeTextAccessibilityState}
255+
accessibilityRole={
256+
role ? roleToAccessibilityRoleMapping[role] : accessibilityRole
257+
}
249258
allowFontScaling={allowFontScaling !== false}
250259
ellipsizeMode={ellipsizeMode ?? 'tail'}
251260
isHighlighted={isHighlighted}

Libraries/Text/TextProps.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
AccessibilityActionInfo,
1616
AccessibilityRole,
1717
AccessibilityState,
18+
Role,
1819
} from '../Components/View/ViewAccessibility';
1920
import type {TextStyleProp} from '../StyleSheet/StyleSheet';
2021
import type {
@@ -176,6 +177,11 @@ export type TextProps = $ReadOnly<{|
176177
*/
177178
pressRetentionOffset?: ?PressRetentionOffset,
178179

180+
/**
181+
* Indicates to accessibility services to treat UI component like a specific role.
182+
*/
183+
role?: ?Role,
184+
179185
/**
180186
* Lets the user select text.
181187
*
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
'use strict';
12+
13+
// Map role values to AccessibilityRole values
14+
export const roleToAccessibilityRoleMapping = {
15+
alert: 'alert',
16+
alertdialog: undefined,
17+
application: undefined,
18+
article: undefined,
19+
banner: undefined,
20+
button: 'button',
21+
cell: undefined,
22+
checkbox: 'checkbox',
23+
columnheader: undefined,
24+
combobox: 'combobox',
25+
complementary: undefined,
26+
contentinfo: undefined,
27+
definition: undefined,
28+
dialog: undefined,
29+
directory: undefined,
30+
document: undefined,
31+
feed: undefined,
32+
figure: undefined,
33+
form: undefined,
34+
grid: 'grid',
35+
group: undefined,
36+
heading: 'header',
37+
img: 'image',
38+
link: 'link',
39+
list: 'list',
40+
listitem: undefined,
41+
log: undefined,
42+
main: undefined,
43+
marquee: undefined,
44+
math: undefined,
45+
menu: 'menu',
46+
menubar: 'menubar',
47+
menuitem: 'menuitem',
48+
meter: undefined,
49+
navigation: undefined,
50+
none: 'none',
51+
note: undefined,
52+
presentation: 'none',
53+
progressbar: 'progressbar',
54+
radio: 'radio',
55+
radiogroup: 'radiogroup',
56+
region: undefined,
57+
row: undefined,
58+
rowgroup: undefined,
59+
rowheader: undefined,
60+
scrollbar: 'scrollbar',
61+
searchbox: 'search',
62+
separator: undefined,
63+
slider: 'adjustable',
64+
spinbutton: 'spinbutton',
65+
status: undefined,
66+
summary: 'summary',
67+
switch: 'switch',
68+
tab: 'tab',
69+
table: undefined,
70+
tablist: 'tablist',
71+
tabpanel: undefined,
72+
term: undefined,
73+
timer: 'timer',
74+
toolbar: 'toolbar',
75+
tooltip: undefined,
76+
tree: undefined,
77+
treegrid: undefined,
78+
treeitem: undefined,
79+
};

packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ class AccessibilityExample extends React.Component<{}> {
157157
<Text accessibilityRole="header">This is a title.</Text>
158158
</RNTesterBlock>
159159

160+
<RNTesterBlock title="Text with role = heading">
161+
<Text role="heading">This is a title.</Text>
162+
</RNTesterBlock>
163+
160164
<RNTesterBlock title="Touchable with accessibilityRole = link">
161165
<TouchableOpacity
162166
onPress={() => Alert.alert('Link has been clicked!')}

0 commit comments

Comments
 (0)