diff --git a/packages/react-native-web/src/exports/CheckBox/__tests__/index-test.js b/packages/react-native-web/src/exports/CheckBox/__tests__/index-test.js
index 7f443762..74bb0c06 100644
--- a/packages/react-native-web/src/exports/CheckBox/__tests__/index-test.js
+++ b/packages/react-native-web/src/exports/CheckBox/__tests__/index-test.js
@@ -10,7 +10,7 @@ describe('CheckBox', () => {
describe('disabled', () => {
test('when "false" a default checkbox is rendered', () => {
const component = shallow();
- expect(component.find(checkboxSelector).prop('disabled')).toBe(false);
+ expect(component.find(checkboxSelector).prop('disabled')).toBe(undefined);
});
test('when "true" a disabled checkbox is rendered', () => {
diff --git a/packages/react-native-web/src/exports/Switch/__tests__/index-test.js b/packages/react-native-web/src/exports/Switch/__tests__/index-test.js
index dab69396..af390699 100644
--- a/packages/react-native-web/src/exports/Switch/__tests__/index-test.js
+++ b/packages/react-native-web/src/exports/Switch/__tests__/index-test.js
@@ -15,7 +15,7 @@ describe('components/Switch', () => {
describe('disabled', () => {
test('when "false" a default checkbox is rendered', () => {
const component = shallow();
- expect(component.find(checkboxSelector).prop('disabled')).toBe(false);
+ expect(component.find(checkboxSelector).prop('disabled')).toBe(undefined);
});
test('when "true" a disabled checkbox is rendered', () => {
diff --git a/packages/react-native-web/src/exports/Text/TextPropTypes.js b/packages/react-native-web/src/exports/Text/TextPropTypes.js
index 9adeb3f0..6359f302 100644
--- a/packages/react-native-web/src/exports/Text/TextPropTypes.js
+++ b/packages/react-native-web/src/exports/Text/TextPropTypes.js
@@ -10,11 +10,12 @@
import StyleSheetPropType from '../../modules/StyleSheetPropType';
import TextStylePropTypes from './TextStylePropTypes';
-import { any, bool, func, number, oneOf, string } from 'prop-types';
+import { any, bool, func, number, object, oneOf, string } from 'prop-types';
const TextPropTypes = {
accessibilityLabel: string,
accessibilityLiveRegion: oneOf(['assertive', 'none', 'polite']),
+ accessibilityRelationship: object,
accessibilityRole: oneOf([
'button',
'header',
@@ -26,6 +27,7 @@ const TextPropTypes = {
'text'
]),
accessible: bool,
+ accessibilityState: object,
children: any,
importantForAccessibility: oneOf(['auto', 'no', 'no-hide-descendants', 'yes']),
maxFontSizeMultiplier: number,
diff --git a/packages/react-native-web/src/exports/View/ViewPropTypes.js b/packages/react-native-web/src/exports/View/ViewPropTypes.js
index b3d654e4..0ca3046e 100644
--- a/packages/react-native-web/src/exports/View/ViewPropTypes.js
+++ b/packages/react-native-web/src/exports/View/ViewPropTypes.js
@@ -11,7 +11,7 @@
import EdgeInsetsPropType, { type EdgeInsetsProp } from '../EdgeInsetsPropType';
import StyleSheetPropType from '../../modules/StyleSheetPropType';
import ViewStylePropTypes from './ViewStylePropTypes';
-import { any, arrayOf, bool, func, object, oneOf, string } from 'prop-types';
+import { any, bool, func, object, oneOf, string } from 'prop-types';
import { type StyleObj } from '../StyleSheet/StyleSheetTypes';
const stylePropType = StyleSheetPropType(ViewStylePropTypes);
@@ -33,8 +33,30 @@ export type ViewProps = {
accessibilityComponentType?: string,
accessibilityLabel?: string,
accessibilityLiveRegion?: 'none' | 'polite' | 'assertive',
+ accessibilityRelationship?: {
+ activedescendant?: ?string,
+ controls?: ?string,
+ describedby?: ?string,
+ details?: ?string,
+ haspopup?: ?string,
+ labelledby?: ?string,
+ owns?: ?string
+ },
accessibilityRole?: string,
- accessibilityStates?: Array,
+ accessibilityState?: {
+ busy?: ?boolean,
+ checked?: ?boolean | 'mixed',
+ disabled?: ?boolean,
+ expanded?: ?boolean,
+ grabbed?: ?boolean,
+ hidden?: ?boolean,
+ invalid?: ?boolean,
+ modal?: ?boolean,
+ pressed?: ?boolean,
+ readonly?: ?boolean,
+ required?: ?boolean,
+ selected?: ?boolean
+ },
accessible?: boolean,
children?: any,
className?: string,
@@ -90,20 +112,9 @@ const ViewPropTypes = {
accessibilityComponentType: string,
accessibilityLabel: string,
accessibilityLiveRegion: oneOf(['assertive', 'none', 'polite']),
+ accessibilityRelationship: object,
accessibilityRole: string,
- accessibilityStates: arrayOf(
- oneOf([
- 'disabled',
- 'selected',
- /* web-only */
- 'busy',
- 'checked',
- 'expanded',
- 'grabbed',
- 'invalid',
- 'pressed'
- ])
- ),
+ accessibilityState: object,
accessible: bool,
children: any,
hitSlop: EdgeInsetsPropType,
diff --git a/packages/react-native-web/src/modules/createDOMProps/__tests__/__snapshots__/index-test.js.snap b/packages/react-native-web/src/modules/createDOMProps/__tests__/__snapshots__/index-test.js.snap
index ad138400..f056cdac 100644
--- a/packages/react-native-web/src/modules/createDOMProps/__tests__/__snapshots__/index-test.js.snap
+++ b/packages/react-native-web/src/modules/createDOMProps/__tests__/__snapshots__/index-test.js.snap
@@ -13,3 +13,53 @@ exports[`modules/createDOMProps includes base reset style for browser-styled ele
exports[`modules/createDOMProps includes cursor style for pressable roles 1`] = `"css-cursor-18t94o4"`;
exports[`modules/createDOMProps includes cursor style for pressable roles 2`] = `"css-cursor-18t94o4"`;
+
+exports[`modules/createDOMProps prop "accessibilityRelationship" values are "id" string 1`] = `
+Object {
+ "aria-activedescendant": "id",
+ "aria-controls": "id",
+ "aria-describedby": "id",
+ "aria-details": "id",
+ "aria-haspopup": "id",
+ "aria-labelledby": "id",
+ "aria-owns": "id",
+}
+`;
+
+exports[`modules/createDOMProps prop "accessibilityRelationship" values are "undefined" 1`] = `Object {}`;
+
+exports[`modules/createDOMProps prop "accessibilityState" values are "false" 1`] = `
+Object {
+ "aria-busy": false,
+ "aria-checked": false,
+ "aria-expanded": false,
+ "aria-grabbed": false,
+ "aria-invalid": false,
+ "aria-modal": false,
+ "aria-pressed": false,
+ "aria-readonly": false,
+ "aria-required": false,
+ "aria-selected": false,
+}
+`;
+
+exports[`modules/createDOMProps prop "accessibilityState" values are "true" 1`] = `
+Object {
+ "aria-busy": true,
+ "aria-checked": true,
+ "aria-disabled": true,
+ "aria-expanded": true,
+ "aria-grabbed": true,
+ "aria-hidden": true,
+ "aria-invalid": true,
+ "aria-modal": true,
+ "aria-pressed": true,
+ "aria-readonly": true,
+ "aria-required": true,
+ "aria-selected": true,
+ "disabled": true,
+ "hidden": true,
+}
+`;
+
+exports[`modules/createDOMProps prop "accessibilityState" values are "undefined" 1`] = `Object {}`;
diff --git a/packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js b/packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js
index 0946674b..41def13b 100644
--- a/packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js
+++ b/packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js
@@ -158,11 +158,67 @@ describe('modules/createDOMProps', () => {
expect(props.role).toEqual('button');
});
- test('prop "accessibilityStates" becomes ARIA states', () => {
- const accessibilityStates = ['disabled', 'selected'];
- const props = createProps({ accessibilityStates });
- expect(props['aria-disabled']).toEqual(true);
- expect(props['aria-selected']).toEqual(true);
+ describe('prop "accessibilityState"', () => {
+ function createAccessibilityState(value) {
+ return {
+ busy: value,
+ checked: value,
+ disabled: value,
+ expanded: value,
+ grabbed: value,
+ hidden: value,
+ invalid: value,
+ modal: value,
+ pressed: value,
+ readonly: value,
+ required: value,
+ selected: value
+ };
+ }
+
+ test('values are "undefined"', () => {
+ const accessibilityState = createAccessibilityState(undefined);
+ const props = createProps({ accessibilityState });
+ expect(props).toMatchSnapshot();
+ });
+
+ test('values are "false"', () => {
+ const accessibilityState = createAccessibilityState(false);
+ const props = createProps({ accessibilityState });
+ expect(props).toMatchSnapshot();
+ });
+
+ test('values are "true"', () => {
+ const accessibilityState = createAccessibilityState(true);
+ const props = createProps({ accessibilityState });
+ expect(props).toMatchSnapshot();
+ });
+ });
+
+ describe('prop "accessibilityRelationship"', () => {
+ function createAccessibilityRelationship(value) {
+ return {
+ activedescendant: value,
+ controls: value,
+ describedby: value,
+ details: value,
+ haspopup: value,
+ labelledby: value,
+ owns: value
+ };
+ }
+
+ test('values are "undefined"', () => {
+ const accessibilityRelationship = createAccessibilityRelationship(undefined);
+ const props = createProps({ accessibilityRelationship });
+ expect(props).toMatchSnapshot();
+ });
+
+ test('values are "id" string', () => {
+ const accessibilityRelationship = createAccessibilityRelationship('id');
+ const props = createProps({ accessibilityRelationship });
+ expect(props).toMatchSnapshot();
+ });
});
test('prop "className" is preserved', () => {
diff --git a/packages/react-native-web/src/modules/createDOMProps/index.js b/packages/react-native-web/src/modules/createDOMProps/index.js
index 65b35c64..129320ec 100644
--- a/packages/react-native-web/src/modules/createDOMProps/index.js
+++ b/packages/react-native-web/src/modules/createDOMProps/index.js
@@ -63,9 +63,11 @@ const createDOMProps = (component, props, styleResolver) => {
const {
accessibilityLabel,
accessibilityLiveRegion,
- accessibilityStates,
+ accessibilityRelationship,
+ accessibilityState,
classList,
className: deprecatedClassName,
+ disabled: providedDisabled,
importantForAccessibility,
nativeID,
placeholderTextColor,
@@ -79,32 +81,59 @@ const createDOMProps = (component, props, styleResolver) => {
...domProps
} = props;
- const disabled = AccessibilityUtil.isDisabled(props);
+ const disabled =
+ (accessibilityState != null && accessibilityState.disabled === true) || providedDisabled;
const role = AccessibilityUtil.propsToAriaRole(props);
- // GENERAL ACCESSIBILITY
- if (importantForAccessibility === 'no-hide-descendants') {
- domProps['aria-hidden'] = true;
- }
- if (accessibilityLabel && accessibilityLabel.constructor === String) {
+ // accessibilityLabel
+ if (accessibilityLabel != null) {
domProps['aria-label'] = accessibilityLabel;
}
- if (accessibilityLiveRegion && accessibilityLiveRegion.constructor === String) {
+
+ // accessibilityLiveRegion
+ if (accessibilityLiveRegion != null) {
domProps['aria-live'] = accessibilityLiveRegion === 'none' ? 'off' : accessibilityLiveRegion;
}
- if (Array.isArray(accessibilityStates)) {
- for (let i = 0; i < accessibilityStates.length; i += 1) {
- domProps[`aria-${accessibilityStates[i]}`] = true;
+
+ // accessibilityRelationship
+ if (accessibilityRelationship != null) {
+ for (const prop in accessibilityRelationship) {
+ const value = accessibilityRelationship[prop];
+ if (value != null) {
+ domProps[`aria-${prop}`] = value;
+ }
}
}
- if (role && role.constructor === String) {
+
+ // accessibilityRole
+ if (role != null) {
domProps.role = role;
}
- // DISABLED
- if (disabled) {
- domProps['aria-disabled'] = disabled;
- domProps.disabled = disabled;
+ // accessibilityState
+ if (accessibilityState != null) {
+ for (const prop in accessibilityState) {
+ const value = accessibilityState[prop];
+ if (value != null) {
+ if (prop === 'disabled' || prop === 'hidden') {
+ if (value === true) {
+ domProps[`aria-${prop}`] = value;
+ // also set prop directly to pick up host component behaviour
+ domProps[prop] = value;
+ }
+ } else {
+ domProps[`aria-${prop}`] = value;
+ }
+ }
+ }
+ }
+ // legacy fallbacks
+ if (importantForAccessibility === 'no-hide-descendants') {
+ domProps['aria-hidden'] = true;
+ }
+ if (disabled === true) {
+ domProps['aria-disabled'] = true;
+ domProps.disabled = true;
}
// FOCUS