Skip to content

Commit d57fb6e

Browse files
committed
[change] accessibilityRelationship and accessibilityState props
Adds the accessibilityState and accessibilityRelationship object props that map to ARIA props. Removes the accessibilityStates array prop that is not compatible with web accessibility services. Ref #1172
1 parent ae94551 commit d57fb6e

File tree

7 files changed

+187
-39
lines changed

7 files changed

+187
-39
lines changed

packages/react-native-web/src/exports/CheckBox/__tests__/index-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe('CheckBox', () => {
1010
describe('disabled', () => {
1111
test('when "false" a default checkbox is rendered', () => {
1212
const component = shallow(<CheckBox />);
13-
expect(component.find(checkboxSelector).prop('disabled')).toBe(false);
13+
expect(component.find(checkboxSelector).prop('disabled')).toBe(undefined);
1414
});
1515

1616
test('when "true" a disabled checkbox is rendered', () => {

packages/react-native-web/src/exports/Switch/__tests__/index-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe('components/Switch', () => {
1515
describe('disabled', () => {
1616
test('when "false" a default checkbox is rendered', () => {
1717
const component = shallow(<Switch />);
18-
expect(component.find(checkboxSelector).prop('disabled')).toBe(false);
18+
expect(component.find(checkboxSelector).prop('disabled')).toBe(undefined);
1919
});
2020

2121
test('when "true" a disabled checkbox is rendered', () => {

packages/react-native-web/src/exports/Text/TextPropTypes.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010

1111
import StyleSheetPropType from '../../modules/StyleSheetPropType';
1212
import TextStylePropTypes from './TextStylePropTypes';
13-
import { any, bool, func, number, oneOf, string } from 'prop-types';
13+
import { any, bool, func, number, object, oneOf, string } from 'prop-types';
1414

1515
const TextPropTypes = {
1616
accessibilityLabel: string,
1717
accessibilityLiveRegion: oneOf(['assertive', 'none', 'polite']),
18+
accessibilityRelationship: object,
1819
accessibilityRole: oneOf([
1920
'button',
2021
'header',
@@ -26,6 +27,7 @@ const TextPropTypes = {
2627
'text'
2728
]),
2829
accessible: bool,
30+
accessibilityState: object,
2931
children: any,
3032
importantForAccessibility: oneOf(['auto', 'no', 'no-hide-descendants', 'yes']),
3133
maxFontSizeMultiplier: number,

packages/react-native-web/src/exports/View/ViewPropTypes.js

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import EdgeInsetsPropType, { type EdgeInsetsProp } from '../EdgeInsetsPropType';
1212
import StyleSheetPropType from '../../modules/StyleSheetPropType';
1313
import ViewStylePropTypes from './ViewStylePropTypes';
14-
import { any, arrayOf, bool, func, object, oneOf, string } from 'prop-types';
14+
import { any, bool, func, object, oneOf, string } from 'prop-types';
1515
import { type StyleObj } from '../StyleSheet/StyleSheetTypes';
1616

1717
const stylePropType = StyleSheetPropType(ViewStylePropTypes);
@@ -33,8 +33,30 @@ export type ViewProps = {
3333
accessibilityComponentType?: string,
3434
accessibilityLabel?: string,
3535
accessibilityLiveRegion?: 'none' | 'polite' | 'assertive',
36+
accessibilityRelationship?: {
37+
activedescendant?: ?string,
38+
controls?: ?string,
39+
describedby?: ?string,
40+
details?: ?string,
41+
haspopup?: ?string,
42+
labelledby?: ?string,
43+
owns?: ?string
44+
},
3645
accessibilityRole?: string,
37-
accessibilityStates?: Array<string>,
46+
accessibilityState?: {
47+
busy?: ?boolean,
48+
checked?: ?boolean | 'mixed',
49+
disabled?: ?boolean,
50+
expanded?: ?boolean,
51+
grabbed?: ?boolean,
52+
hidden?: ?boolean,
53+
invalid?: ?boolean,
54+
modal?: ?boolean,
55+
pressed?: ?boolean,
56+
readonly?: ?boolean,
57+
required?: ?boolean,
58+
selected?: ?boolean
59+
},
3860
accessible?: boolean,
3961
children?: any,
4062
className?: string,
@@ -90,20 +112,9 @@ const ViewPropTypes = {
90112
accessibilityComponentType: string,
91113
accessibilityLabel: string,
92114
accessibilityLiveRegion: oneOf(['assertive', 'none', 'polite']),
115+
accessibilityRelationship: object,
93116
accessibilityRole: string,
94-
accessibilityStates: arrayOf(
95-
oneOf([
96-
'disabled',
97-
'selected',
98-
/* web-only */
99-
'busy',
100-
'checked',
101-
'expanded',
102-
'grabbed',
103-
'invalid',
104-
'pressed'
105-
])
106-
),
117+
accessibilityState: object,
107118
accessible: bool,
108119
children: any,
109120
hitSlop: EdgeInsetsPropType,

packages/react-native-web/src/modules/createDOMProps/__tests__/__snapshots__/index-test.js.snap

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,53 @@ exports[`modules/createDOMProps includes base reset style for browser-styled ele
1313
exports[`modules/createDOMProps includes cursor style for pressable roles 1`] = `"css-cursor-18t94o4"`;
1414

1515
exports[`modules/createDOMProps includes cursor style for pressable roles 2`] = `"css-cursor-18t94o4"`;
16+
17+
exports[`modules/createDOMProps prop "accessibilityRelationship" values are "id" string 1`] = `
18+
Object {
19+
"aria-activedescendant": "id",
20+
"aria-controls": "id",
21+
"aria-describedby": "id",
22+
"aria-details": "id",
23+
"aria-haspopup": "id",
24+
"aria-labelledby": "id",
25+
"aria-owns": "id",
26+
}
27+
`;
28+
29+
exports[`modules/createDOMProps prop "accessibilityRelationship" values are "undefined" 1`] = `Object {}`;
30+
31+
exports[`modules/createDOMProps prop "accessibilityState" values are "false" 1`] = `
32+
Object {
33+
"aria-busy": false,
34+
"aria-checked": false,
35+
"aria-expanded": false,
36+
"aria-grabbed": false,
37+
"aria-invalid": false,
38+
"aria-modal": false,
39+
"aria-pressed": false,
40+
"aria-readonly": false,
41+
"aria-required": false,
42+
"aria-selected": false,
43+
}
44+
`;
45+
46+
exports[`modules/createDOMProps prop "accessibilityState" values are "true" 1`] = `
47+
Object {
48+
"aria-busy": true,
49+
"aria-checked": true,
50+
"aria-disabled": true,
51+
"aria-expanded": true,
52+
"aria-grabbed": true,
53+
"aria-hidden": true,
54+
"aria-invalid": true,
55+
"aria-modal": true,
56+
"aria-pressed": true,
57+
"aria-readonly": true,
58+
"aria-required": true,
59+
"aria-selected": true,
60+
"disabled": true,
61+
"hidden": true,
62+
}
63+
`;
64+
65+
exports[`modules/createDOMProps prop "accessibilityState" values are "undefined" 1`] = `Object {}`;

packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,67 @@ describe('modules/createDOMProps', () => {
158158
expect(props.role).toEqual('button');
159159
});
160160

161-
test('prop "accessibilityStates" becomes ARIA states', () => {
162-
const accessibilityStates = ['disabled', 'selected'];
163-
const props = createProps({ accessibilityStates });
164-
expect(props['aria-disabled']).toEqual(true);
165-
expect(props['aria-selected']).toEqual(true);
161+
describe('prop "accessibilityState"', () => {
162+
function createAccessibilityState(value) {
163+
return {
164+
busy: value,
165+
checked: value,
166+
disabled: value,
167+
expanded: value,
168+
grabbed: value,
169+
hidden: value,
170+
invalid: value,
171+
modal: value,
172+
pressed: value,
173+
readonly: value,
174+
required: value,
175+
selected: value
176+
};
177+
}
178+
179+
test('values are "undefined"', () => {
180+
const accessibilityState = createAccessibilityState(undefined);
181+
const props = createProps({ accessibilityState });
182+
expect(props).toMatchSnapshot();
183+
});
184+
185+
test('values are "false"', () => {
186+
const accessibilityState = createAccessibilityState(false);
187+
const props = createProps({ accessibilityState });
188+
expect(props).toMatchSnapshot();
189+
});
190+
191+
test('values are "true"', () => {
192+
const accessibilityState = createAccessibilityState(true);
193+
const props = createProps({ accessibilityState });
194+
expect(props).toMatchSnapshot();
195+
});
196+
});
197+
198+
describe('prop "accessibilityRelationship"', () => {
199+
function createAccessibilityRelationship(value) {
200+
return {
201+
activedescendant: value,
202+
controls: value,
203+
describedby: value,
204+
details: value,
205+
haspopup: value,
206+
labelledby: value,
207+
owns: value
208+
};
209+
}
210+
211+
test('values are "undefined"', () => {
212+
const accessibilityRelationship = createAccessibilityRelationship(undefined);
213+
const props = createProps({ accessibilityRelationship });
214+
expect(props).toMatchSnapshot();
215+
});
216+
217+
test('values are "id" string', () => {
218+
const accessibilityRelationship = createAccessibilityRelationship('id');
219+
const props = createProps({ accessibilityRelationship });
220+
expect(props).toMatchSnapshot();
221+
});
166222
});
167223

168224
test('prop "className" is preserved', () => {

packages/react-native-web/src/modules/createDOMProps/index.js

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,11 @@ const createDOMProps = (component, props, styleResolver) => {
6363
const {
6464
accessibilityLabel,
6565
accessibilityLiveRegion,
66-
accessibilityStates,
66+
accessibilityRelationship,
67+
accessibilityState,
6768
classList,
6869
className: deprecatedClassName,
70+
disabled: providedDisabled,
6971
importantForAccessibility,
7072
nativeID,
7173
placeholderTextColor,
@@ -79,32 +81,59 @@ const createDOMProps = (component, props, styleResolver) => {
7981
...domProps
8082
} = props;
8183

82-
const disabled = AccessibilityUtil.isDisabled(props);
84+
const disabled =
85+
(accessibilityState != null && accessibilityState.disabled === true) || providedDisabled;
8386
const role = AccessibilityUtil.propsToAriaRole(props);
8487

85-
// GENERAL ACCESSIBILITY
86-
if (importantForAccessibility === 'no-hide-descendants') {
87-
domProps['aria-hidden'] = true;
88-
}
89-
if (accessibilityLabel && accessibilityLabel.constructor === String) {
88+
// accessibilityLabel
89+
if (accessibilityLabel != null) {
9090
domProps['aria-label'] = accessibilityLabel;
9191
}
92-
if (accessibilityLiveRegion && accessibilityLiveRegion.constructor === String) {
92+
93+
// accessibilityLiveRegion
94+
if (accessibilityLiveRegion != null) {
9395
domProps['aria-live'] = accessibilityLiveRegion === 'none' ? 'off' : accessibilityLiveRegion;
9496
}
95-
if (Array.isArray(accessibilityStates)) {
96-
for (let i = 0; i < accessibilityStates.length; i += 1) {
97-
domProps[`aria-${accessibilityStates[i]}`] = true;
97+
98+
// accessibilityRelationship
99+
if (accessibilityRelationship != null) {
100+
for (const prop in accessibilityRelationship) {
101+
const value = accessibilityRelationship[prop];
102+
if (value != null) {
103+
domProps[`aria-${prop}`] = value;
104+
}
98105
}
99106
}
100-
if (role && role.constructor === String) {
107+
108+
// accessibilityRole
109+
if (role != null) {
101110
domProps.role = role;
102111
}
103112

104-
// DISABLED
105-
if (disabled) {
106-
domProps['aria-disabled'] = disabled;
107-
domProps.disabled = disabled;
113+
// accessibilityState
114+
if (accessibilityState != null) {
115+
for (const prop in accessibilityState) {
116+
const value = accessibilityState[prop];
117+
if (value != null) {
118+
if (prop === 'disabled' || prop === 'hidden') {
119+
if (value === true) {
120+
domProps[`aria-${prop}`] = value;
121+
// also set prop directly to pick up host component behaviour
122+
domProps[prop] = value;
123+
}
124+
} else {
125+
domProps[`aria-${prop}`] = value;
126+
}
127+
}
128+
}
129+
}
130+
// legacy fallbacks
131+
if (importantForAccessibility === 'no-hide-descendants') {
132+
domProps['aria-hidden'] = true;
133+
}
134+
if (disabled === true) {
135+
domProps['aria-disabled'] = true;
136+
domProps.disabled = true;
108137
}
109138

110139
// FOCUS

0 commit comments

Comments
 (0)