diff --git a/packages/react-native-web/src/exports/Button/__tests__/__snapshots__/index-test.js.snap b/packages/react-native-web/src/exports/Button/__tests__/__snapshots__/index-test.js.snap
index d883004d..b947cf44 100644
--- a/packages/react-native-web/src/exports/Button/__tests__/__snapshots__/index-test.js.snap
+++ b/packages/react-native-web/src/exports/Button/__tests__/__snapshots__/index-test.js.snap
@@ -33,7 +33,6 @@ exports[`components/Button prop "disabled" 1`] = `
diff --git a/packages/react-native-web/src/exports/Pressable/__tests__/__snapshots__/index-test.js.snap b/packages/react-native-web/src/exports/Pressable/__tests__/__snapshots__/index-test.js.snap
index a47f267b..52417b3b 100644
--- a/packages/react-native-web/src/exports/Pressable/__tests__/__snapshots__/index-test.js.snap
+++ b/packages/react-native-web/src/exports/Pressable/__tests__/__snapshots__/index-test.js.snap
@@ -132,7 +132,6 @@ exports[`components/Pressable prop "disabled" 1`] = `
`;
diff --git a/packages/react-native-web/src/exports/createElement/__tests__/index-test.js b/packages/react-native-web/src/exports/createElement/__tests__/index-test.js
index a4a917d1..8358d63f 100644
--- a/packages/react-native-web/src/exports/createElement/__tests__/index-test.js
+++ b/packages/react-native-web/src/exports/createElement/__tests__/index-test.js
@@ -4,6 +4,14 @@ import createElement from '..';
import React from 'react';
import { render } from '@testing-library/react';
+function getAttribute(container, attribute) {
+ return container.firstChild.getAttribute(attribute);
+}
+
+function getProperty(container, prop) {
+ return container.firstChild[prop];
+}
+
describe('exports/createElement', () => {
test('renders different DOM elements', () => {
let { container } = render(createElement('span'));
@@ -24,4 +32,461 @@ describe('exports/createElement', () => {
expect(container.firstChild.nodeName).toBe('DIV');
});
});
+
+ describe('accessibility props', () => {
+ test('accessibilityActiveDescendant', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityActiveDescendant: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-activedescendant')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityActiveDescendant: 'abc' })
+ );
+ expect(getAttribute(hasValue, 'aria-activedescendant')).toBe('abc');
+ });
+
+ test('accessibilityAtomic', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityAtomic: null }));
+ expect(getAttribute(isEmpty, 'aria-atomic')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityAtomic: true }));
+ expect(getAttribute(hasValue, 'aria-atomic')).toBe('true');
+ });
+
+ test('accessibilityAutoComplete', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityAutoComplete: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-autocomplete')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityAutoComplete: true })
+ );
+ expect(getAttribute(hasValue, 'aria-autocomplete')).toBe('true');
+ });
+
+ test('accessibilityBusy', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityBusy: null }));
+ expect(getAttribute(isEmpty, 'aria-busy')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityBusy: true }));
+ expect(getAttribute(hasValue, 'aria-busy')).toBe('true');
+ });
+
+ test('accessibilityChecked', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityChecked: null }));
+ expect(getAttribute(isEmpty, 'aria-checked')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityChecked: true }));
+ expect(getAttribute(hasValue, 'aria-checked')).toBe('true');
+ });
+
+ test('accessibilityColumnCount', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityColumnCount: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-colcount')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityColumnCount: 5 }));
+ expect(getAttribute(hasValue, 'aria-colcount')).toBe('5');
+ });
+
+ test('accessibilityColumnIndex', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityColumnIndex: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-colindex')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityColumnIndex: 5 }));
+ expect(getAttribute(hasValue, 'aria-colindex')).toBe('5');
+ });
+
+ test('accessibilityColumnSpan', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityColumnSpan: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-colspan')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityColumnSpan: 5 }));
+ expect(getAttribute(hasValue, 'aria-colspan')).toBe('5');
+ });
+
+ test('accessibilityControls', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityControls: null }));
+ expect(getAttribute(isEmpty, 'aria-controls')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityControls: 'abc' })
+ );
+ expect(getAttribute(hasValue, 'aria-controls')).toBe('abc');
+ });
+
+ test('accessibilityDescribedBy', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityDescribedBy: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-describedby')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityDescribedBy: 'abc' })
+ );
+ expect(getAttribute(hasValue, 'aria-describedby')).toBe('abc');
+ });
+
+ test('accessibilityDetails', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityDetails: null }));
+ expect(getAttribute(isEmpty, 'aria-details')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityDetails: 'abc' }));
+ expect(getAttribute(hasValue, 'aria-details')).toBe('abc');
+ });
+
+ test('accessibilityDisabled', () => {
+ const { container: isEmpty } = render(
+ createElement('button', { accessibilityDisabled: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-disabled')).toBeNull();
+ expect(getProperty(isEmpty, 'disabled')).toBe(false);
+ const { container: hasValue } = render(
+ createElement('button', { accessibilityDisabled: true })
+ );
+ expect(getAttribute(hasValue, 'aria-disabled')).toBe('true');
+ expect(getProperty(hasValue, 'disabled')).toBe(true);
+ });
+
+ test('accessibilityErrorMessage', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityErrorMessage: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-errormessage')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityErrorMessage: 'abc' })
+ );
+ expect(getAttribute(hasValue, 'aria-errormessage')).toBe('abc');
+ });
+
+ test('accessibilityExpanded', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityExpanded: null }));
+ expect(getAttribute(isEmpty, 'aria-expanded')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityExpanded: true }));
+ expect(getAttribute(hasValue, 'aria-expanded')).toBe('true');
+ });
+
+ test('accessibilityFlowTo', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityFlowTo: null }));
+ expect(getAttribute(isEmpty, 'aria-flowto')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityFlowTo: 'abc' }));
+ expect(getAttribute(hasValue, 'aria-flowto')).toBe('abc');
+ });
+
+ test('accessibilityHasPopup', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityHasPopup: null }));
+ expect(getAttribute(isEmpty, 'aria-haspopup')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityHasPopup: true }));
+ expect(getAttribute(hasValue, 'aria-haspopup')).toBe('true');
+ });
+
+ test('accessibilityHidden', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityHidden: null }));
+ expect(getAttribute(isEmpty, 'aria-hidden')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityHidden: true }));
+ expect(getAttribute(hasValue, 'aria-hidden')).toBe('true');
+ });
+
+ test('accessibilityInvalid', () => {
+ const { container: isEmpty } = render(createElement('input', { accessibilityInvalid: null }));
+ expect(getAttribute(isEmpty, 'aria-invalid')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('input', { accessibilityInvalid: true })
+ );
+ expect(getAttribute(hasValue, 'aria-invalid')).toBe('true');
+ });
+
+ test('accessibilityKeyShortcuts', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityKeyShortcuts: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-keyshortcuts')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', {
+ accessibilityKeyShortcuts: ['ArrowUp', 'Enter', 'Space', 'Alt+Shift+T']
+ })
+ );
+ expect(getAttribute(hasValue, 'aria-keyshortcuts')).toBe('ArrowUp Enter Space Alt+Shift+T');
+ });
+
+ test('accessibilityLabel', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityLabel: null }));
+ expect(getAttribute(isEmpty, 'aria-label')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityLabel: 'abc' }));
+ expect(getAttribute(hasValue, 'aria-label')).toBe('abc');
+ });
+
+ test('accessibilityLabelledBy', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityLabelledBy: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-labelledby')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityLabelledBy: 'abc' })
+ );
+ expect(getAttribute(hasValue, 'aria-labelledby')).toBe('abc');
+ });
+
+ test('accessibilityLevel', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityLevel: null }));
+ expect(getAttribute(isEmpty, 'aria-level')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityLevel: 3 }));
+ expect(getAttribute(hasValue, 'aria-level')).toBe('3');
+ });
+
+ test('accessibilityLiveRegion', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityLiveRegion: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-live')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityLiveRegion: 'polite' })
+ );
+ expect(getAttribute(hasValue, 'aria-live')).toBe('polite');
+ });
+
+ test('accessibilityModal', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityModal: null }));
+ expect(getAttribute(isEmpty, 'aria-modal')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityModal: true }));
+ expect(getAttribute(hasValue, 'aria-modal')).toBe('true');
+ });
+
+ test('accessibilityMultiline', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityMultiline: null }));
+ expect(getAttribute(isEmpty, 'aria-multiline')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityMultiline: true })
+ );
+ expect(getAttribute(hasValue, 'aria-multiline')).toBe('true');
+ });
+
+ test('accessibilityMultiSelectable', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityMultiSelectable: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-multiselectable')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityMultiSelectable: true })
+ );
+ expect(getAttribute(hasValue, 'aria-multiselectable')).toBe('true');
+ });
+
+ test('accessibilityOrientation', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityOrientation: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-orientation')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityOrientation: 'vertical' })
+ );
+ expect(getAttribute(hasValue, 'aria-orientation')).toBe('vertical');
+ });
+
+ test('accessibilityOwns', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityOwns: null }));
+ expect(getAttribute(isEmpty, 'aria-owns')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityOwns: 'abc' }));
+ expect(getAttribute(hasValue, 'aria-owns')).toBe('abc');
+ });
+
+ test('accessibilityPlaceholder', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityPlaceholder: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-placeholder')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityPlaceholder: 'MM-DD-YYYY' })
+ );
+ expect(getAttribute(hasValue, 'aria-placeholder')).toBe('MM-DD-YYYY');
+ });
+
+ test('accessibilityPosInSet', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityPosInSet: null }));
+ expect(getAttribute(isEmpty, 'aria-posinset')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityPosInSet: 3 }));
+ expect(getAttribute(hasValue, 'aria-posinset')).toBe('3');
+ });
+
+ test('accessibilityPressed', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityPressed: null }));
+ expect(getAttribute(isEmpty, 'aria-pressed')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityPressed: true }));
+ expect(getAttribute(hasValue, 'aria-pressed')).toBe('true');
+ });
+
+ test('accessibilityReadOnly', () => {
+ const { container: isEmpty } = render(
+ createElement('input', { accessibilityReadOnly: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-readonly')).toBeNull();
+ expect(getProperty(isEmpty, 'readOnly')).toBe(false);
+ const { container: hasValue } = render(
+ createElement('input', { accessibilityReadOnly: true })
+ );
+ expect(getAttribute(hasValue, 'aria-readonly')).toBe('true');
+ expect(getProperty(hasValue, 'readOnly')).toBe(true);
+ });
+
+ test('accessibilityRequired', () => {
+ const { container: isEmpty } = render(
+ createElement('input', { accessibilityRequired: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-required')).toBeNull();
+ expect(getProperty(isEmpty, 'required')).toBe(false);
+ const { container: hasValue } = render(
+ createElement('input', { accessibilityRequired: true })
+ );
+ expect(getAttribute(hasValue, 'aria-required')).toBe('true');
+ expect(getProperty(hasValue, 'required')).toBe(true);
+ });
+
+ test('accessibilityRole', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityRole: null }));
+ expect(getAttribute(isEmpty, 'role')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityRole: 'button' }));
+ expect(getAttribute(hasValue, 'role')).toBe('button');
+ expect(getAttribute(hasValue, 'tabIndex')).toBe('0');
+ const { container: roleIsNone } = render(createElement('div', { accessibilityRole: 'none' }));
+ expect(getAttribute(roleIsNone, 'role')).toBe('presentation');
+ });
+
+ test('accessibilityRoleDescription', () => {
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityRoleDescription: null })
+ );
+ expect(getAttribute(isEmpty, 'aria-roledescription')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityRoleDescription: 'abc' })
+ );
+ expect(getAttribute(hasValue, 'aria-roledescription')).toBe('abc');
+ });
+
+ test('accessibilityRowCount', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityRowCount: null }));
+ expect(getAttribute(isEmpty, 'aria-rowcount')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityRowCount: 5 }));
+ expect(getAttribute(hasValue, 'aria-rowcount')).toBe('5');
+ });
+
+ test('accessibilityRowIndex', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityRowIndex: null }));
+ expect(getAttribute(isEmpty, 'aria-rowindex')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityRowIndex: 5 }));
+ expect(getAttribute(hasValue, 'aria-rowindex')).toBe('5');
+ });
+
+ test('accessibilityRowSpan', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityRowSpan: null }));
+ expect(getAttribute(isEmpty, 'aria-rowspan')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityRowSpan: 5 }));
+ expect(getAttribute(hasValue, 'aria-rowspan')).toBe('5');
+ });
+
+ test('accessibilitySelected', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilitySelected: null }));
+ expect(getAttribute(isEmpty, 'aria-selected')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilitySelected: true }));
+ expect(getAttribute(hasValue, 'aria-selected')).toBe('true');
+ });
+
+ test('accessibilitySetSize', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilitySetSize: null }));
+ expect(getAttribute(isEmpty, 'aria-setsize')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilitySetSize: 5 }));
+ expect(getAttribute(hasValue, 'aria-setsize')).toBe('5');
+ });
+
+ test('accessibilitySort', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilitySort: null }));
+ expect(getAttribute(isEmpty, 'aria-sort')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilitySort: 'ascending' })
+ );
+ expect(getAttribute(hasValue, 'aria-sort')).toBe('ascending');
+ });
+
+ test('accessibilityValueMax', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityValueMax: null }));
+ expect(getAttribute(isEmpty, 'aria-valuemax')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityValueMax: 100 }));
+ expect(getAttribute(hasValue, 'aria-valuemax')).toBe('100');
+ });
+
+ test('accessibilityValueMin', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityValueMin: null }));
+ expect(getAttribute(isEmpty, 'aria-valuemin')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityValueMin: 10 }));
+ expect(getAttribute(hasValue, 'aria-valuemin')).toBe('10');
+ });
+
+ test('accessibilityValueNow', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityValueNow: null }));
+ expect(getAttribute(isEmpty, 'aria-valuenow')).toBeNull();
+ const { container: hasValue } = render(createElement('div', { accessibilityValueNow: 50 }));
+ expect(getAttribute(hasValue, 'aria-valuenow')).toBe('50');
+ });
+
+ test('accessibilityValueText', () => {
+ const { container: isEmpty } = render(createElement('div', { accessibilityValueText: null }));
+ expect(getAttribute(isEmpty, 'aria-valuetext')).toBeNull();
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityValueText: 'fifty' })
+ );
+ expect(getAttribute(hasValue, 'aria-valuetext')).toBe('fifty');
+ });
+
+ test('dataSet', () => {
+ const { container: hasValue } = render(
+ createElement('div', {
+ dataSet: {
+ one: '1',
+ two: '2',
+ camelCase: 'camelCase',
+ msPrefix: 'msPrefix'
+ }
+ })
+ );
+ expect(hasValue.firstChild).toMatchInlineSnapshot(`
+
+ `);
+ });
+
+ test('focusable', () => {
+ const { container: isEmpty } = render(createElement('div', { focusable: null }));
+ expect(getAttribute(isEmpty, 'tabindex')).toBeNull();
+
+ const { container: isTrue } = render(createElement('div', { focusable: true }));
+ expect(getAttribute(isTrue, 'tabindex')).toBe('0');
+
+ const { container: isFalseNativelyFocusable } = render(
+ createElement('button', { focusable: false })
+ );
+ expect(getAttribute(isFalseNativelyFocusable, 'tabindex')).toBe('-1');
+
+ const { container: isDisabledNativelyFocusable } = render(
+ createElement('button', {
+ accessibilityDisabled: true,
+ focusable: true
+ })
+ );
+ expect(getAttribute(isDisabledNativelyFocusable, 'tabindex')).toBe('-1');
+
+ const { container: isTrueNativelyFocusable } = render(
+ createElement('button', { focusable: true })
+ );
+ expect(getAttribute(isTrueNativelyFocusable, 'tabindex')).toBeNull();
+
+ const { container: isFocusableRole } = render(
+ createElement('div', { accessibilityRole: 'button', focusable: true })
+ );
+ expect(getAttribute(isFocusableRole, 'tabindex')).toBe('0');
+
+ const { container: isFalseFocusableRole } = render(
+ createElement('div', { accessibilityRole: 'button', focusable: false })
+ );
+ expect(getAttribute(isFalseFocusableRole, 'tabindex')).toBeNull();
+ });
+ });
});
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 caa26083..7bb5ec08 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
@@ -65,7 +65,7 @@ describe('modules/createDOMProps', () => {
test('when "accessibilityDisabled" is true', () => {
expect(createProps({ accessibilityRole, accessibilityDisabled: true })).toEqual(
- expect.objectContaining({ 'aria-disabled': true, disabled: true })
+ expect.objectContaining({ 'aria-disabled': true })
);
});
diff --git a/packages/react-native-web/src/modules/createDOMProps/index.js b/packages/react-native-web/src/modules/createDOMProps/index.js
index 285858e4..f246f9c3 100644
--- a/packages/react-native-web/src/modules/createDOMProps/index.js
+++ b/packages/react-native-web/src/modules/createDOMProps/index.js
@@ -16,6 +16,14 @@ import { STYLE_GROUPS } from '../../exports/StyleSheet/constants';
const emptyObject = {};
const hasOwnProperty = Object.prototype.hasOwnProperty;
+const uppercasePattern = /[A-Z]/g;
+function toHyphenLower(match) {
+ return '-' + match.toLowerCase();
+}
+function hyphenateString(str: string): string {
+ return str.replace(uppercasePattern, toHyphenLower);
+}
+
// Reset styles for heading, link, and list DOM elements
const classes = css.create(
{
@@ -50,17 +58,59 @@ const pointerEventsStyles = StyleSheet.create({
}
});
-const createDOMProps = (component, props) => {
+const createDOMProps = (elementType, props) => {
if (!props) {
props = emptyObject;
}
const {
+ accessibilityActiveDescendant,
+ accessibilityAtomic,
+ accessibilityAutoComplete,
+ accessibilityBusy,
+ accessibilityChecked,
+ accessibilityColumnCount,
+ accessibilityColumnIndex,
+ accessibilityColumnSpan,
+ accessibilityControls,
+ accessibilityDescribedBy,
+ accessibilityDetails,
accessibilityDisabled,
+ accessibilityErrorMessage,
+ accessibilityExpanded,
+ accessibilityFlowTo,
+ accessibilityHasPopup,
+ accessibilityHidden,
+ accessibilityInvalid,
+ accessibilityKeyShortcuts,
accessibilityLabel,
+ accessibilityLabelledBy,
+ accessibilityLevel,
accessibilityLiveRegion,
- accessibilityState,
- accessibilityValue,
+ accessibilityModal,
+ accessibilityMultiline,
+ accessibilityMultiSelectable,
+ accessibilityOrientation,
+ accessibilityOwns,
+ accessibilityPlaceholder,
+ accessibilityPosInSet,
+ accessibilityPressed,
+ accessibilityReadOnly,
+ accessibilityRequired,
+ /* eslint-disable */
+ accessibilityRole,
+ /* eslint-enable */
+ accessibilityRoleDescription,
+ accessibilityRowCount,
+ accessibilityRowIndex,
+ accessibilityRowSpan,
+ accessibilitySelected,
+ accessibilitySetSize,
+ accessibilitySort,
+ accessibilityValueMax,
+ accessibilityValueMin,
+ accessibilityValueNow,
+ accessibilityValueText,
classList,
dataSet,
focusable,
@@ -68,52 +118,28 @@ const createDOMProps = (component, props) => {
pointerEvents,
style: providedStyle,
testID,
- /* eslint-disable */
- accessibilityRole,
- /* eslint-enable */
+ // Deprecated
+ accessibilityState,
+ accessibilityValue,
+ // Rest
...domProps
} = props;
const disabled =
(accessibilityState != null && accessibilityState.disabled === true) || accessibilityDisabled;
+
const role = AccessibilityUtil.propsToAriaRole(props);
+
const isNativeInteractiveElement =
role === 'link' ||
- component === 'a' ||
- component === 'button' ||
- component === 'input' ||
- component === 'select' ||
- component === 'textarea' ||
+ elementType === 'a' ||
+ elementType === 'button' ||
+ elementType === 'input' ||
+ elementType === 'select' ||
+ elementType === 'textarea' ||
domProps.contentEditable != null;
- // dataSet
- if (dataSet != null) {
- for (const prop in dataSet) {
- if (hasOwnProperty.call(dataSet, prop)) {
- const value = dataSet[prop];
- if (value != null) {
- domProps[`data-${prop}`] = value;
- }
- }
- }
- }
-
- // accessibilityLabel
- if (accessibilityLabel != null) {
- domProps['aria-label'] = accessibilityLabel;
- }
-
- // accessibilityLiveRegion
- if (accessibilityLiveRegion != null) {
- domProps['aria-live'] = accessibilityLiveRegion === 'none' ? 'off' : accessibilityLiveRegion;
- }
-
- // accessibilityRole
- if (role != null) {
- domProps.role = role;
- }
-
- // accessibilityState
+ // DEPRECATED
if (accessibilityState != null) {
for (const prop in accessibilityState) {
const value = accessibilityState[prop];
@@ -121,7 +147,7 @@ const createDOMProps = (component, props) => {
if (prop === 'disabled' || prop === 'hidden') {
if (value === true) {
domProps[`aria-${prop}`] = value;
- // also set prop directly to pick up host component behaviour
+ // also set prop directly to pick up host elementType behaviour
domProps[prop] = value;
}
} else {
@@ -130,8 +156,6 @@ const createDOMProps = (component, props) => {
}
}
}
-
- // accessibilityValue
if (accessibilityValue != null) {
for (const prop in accessibilityValue) {
const value = accessibilityValue[prop];
@@ -141,20 +165,184 @@ const createDOMProps = (component, props) => {
}
}
+ // ACCESSIBILITY
+ if (accessibilityActiveDescendant != null) {
+ domProps['aria-activedescendant'] = accessibilityActiveDescendant;
+ }
+ if (accessibilityAtomic != null) {
+ domProps['aria-atomic'] = accessibilityAtomic;
+ }
+ if (accessibilityAutoComplete != null) {
+ domProps['aria-autocomplete'] = accessibilityAutoComplete;
+ }
+ if (accessibilityBusy != null) {
+ domProps['aria-busy'] = accessibilityBusy;
+ }
+ if (accessibilityChecked != null) {
+ domProps['aria-checked'] = accessibilityChecked;
+ }
+ if (accessibilityColumnCount != null) {
+ domProps['aria-colcount'] = accessibilityColumnCount;
+ }
+ if (accessibilityColumnIndex != null) {
+ domProps['aria-colindex'] = accessibilityColumnIndex;
+ }
+ if (accessibilityColumnSpan != null) {
+ domProps['aria-colspan'] = accessibilityColumnSpan;
+ }
+ if (accessibilityControls != null) {
+ domProps['aria-controls'] = accessibilityControls;
+ }
+ if (accessibilityDescribedBy != null) {
+ domProps['aria-describedby'] = accessibilityDescribedBy;
+ }
+ if (accessibilityDetails != null) {
+ domProps['aria-details'] = accessibilityDetails;
+ }
if (disabled === true) {
domProps['aria-disabled'] = true;
- domProps.disabled = true;
+ // Enhance with native semantics
+ if (
+ elementType === 'button' ||
+ elementType === 'form' ||
+ elementType === 'input' ||
+ elementType === 'select' ||
+ elementType === 'textarea'
+ ) {
+ domProps.disabled = true;
+ }
+ }
+ if (accessibilityErrorMessage != null) {
+ domProps['aria-errormessage'] = accessibilityErrorMessage;
+ }
+ if (accessibilityExpanded != null) {
+ domProps['aria-expanded'] = accessibilityExpanded;
+ }
+ if (accessibilityFlowTo != null) {
+ domProps['aria-flowto'] = accessibilityFlowTo;
+ }
+ if (accessibilityHasPopup != null) {
+ domProps['aria-haspopup'] = accessibilityHasPopup;
+ }
+ if (accessibilityHidden === true) {
+ domProps['aria-hidden'] = accessibilityHidden;
+ }
+ if (accessibilityInvalid != null) {
+ domProps['aria-invalid'] = accessibilityInvalid;
+ }
+ if (accessibilityKeyShortcuts != null && Array.isArray(accessibilityKeyShortcuts)) {
+ domProps['aria-keyshortcuts'] = accessibilityKeyShortcuts.join(' ');
+ }
+ if (accessibilityLabel != null) {
+ domProps['aria-label'] = accessibilityLabel;
+ }
+ if (accessibilityLabelledBy != null) {
+ domProps['aria-labelledby'] = accessibilityLabelledBy;
+ }
+ if (accessibilityLevel != null) {
+ domProps['aria-level'] = accessibilityLevel;
+ }
+ if (accessibilityLiveRegion != null) {
+ domProps['aria-live'] = accessibilityLiveRegion === 'none' ? 'off' : accessibilityLiveRegion;
+ }
+ if (accessibilityModal != null) {
+ domProps['aria-modal'] = accessibilityModal;
+ }
+ if (accessibilityMultiline != null) {
+ domProps['aria-multiline'] = accessibilityMultiline;
+ }
+ if (accessibilityMultiSelectable != null) {
+ domProps['aria-multiselectable'] = accessibilityMultiSelectable;
+ }
+ if (accessibilityOrientation != null) {
+ domProps['aria-orientation'] = accessibilityOrientation;
+ }
+ if (accessibilityOwns != null) {
+ domProps['aria-owns'] = accessibilityOwns;
+ }
+ if (accessibilityPlaceholder != null) {
+ domProps['aria-placeholder'] = accessibilityPlaceholder;
+ }
+ if (accessibilityPosInSet != null) {
+ domProps['aria-posinset'] = accessibilityPosInSet;
+ }
+ if (accessibilityPressed != null) {
+ domProps['aria-pressed'] = accessibilityPressed;
+ }
+ if (accessibilityReadOnly != null) {
+ domProps['aria-readonly'] = accessibilityReadOnly;
+ // Enhance with native semantics
+ if (elementType === 'input' || elementType === 'select' || elementType === 'textarea') {
+ domProps.readOnly = true;
+ }
+ }
+ if (accessibilityRequired != null) {
+ domProps['aria-required'] = accessibilityRequired;
+ // Enhance with native semantics
+ if (elementType === 'input' || elementType === 'select' || elementType === 'textarea') {
+ domProps.required = true;
+ }
+ }
+ if (role != null) {
+ // 'presentation' synonym has wider browser support
+ domProps['role'] = role === 'none' ? 'presentation' : role;
+ }
+ if (accessibilityRoleDescription != null) {
+ domProps['aria-roledescription'] = accessibilityRoleDescription;
+ }
+ if (accessibilityRowCount != null) {
+ domProps['aria-rowcount'] = accessibilityRowCount;
+ }
+ if (accessibilityRowIndex != null) {
+ domProps['aria-rowindex'] = accessibilityRowIndex;
+ }
+ if (accessibilityRowSpan != null) {
+ domProps['aria-rowspan'] = accessibilityRowSpan;
+ }
+ if (accessibilitySelected != null) {
+ domProps['aria-selected'] = accessibilitySelected;
+ }
+ if (accessibilitySetSize != null) {
+ domProps['aria-setsize'] = accessibilitySetSize;
+ }
+ if (accessibilitySort != null) {
+ domProps['aria-sort'] = accessibilitySort;
+ }
+ if (accessibilityValueMax != null) {
+ domProps['aria-valuemax'] = accessibilityValueMax;
+ }
+ if (accessibilityValueMin != null) {
+ domProps['aria-valuemin'] = accessibilityValueMin;
+ }
+ if (accessibilityValueNow != null) {
+ domProps['aria-valuenow'] = accessibilityValueNow;
+ }
+ if (accessibilityValueText != null) {
+ domProps['aria-valuetext'] = accessibilityValueText;
+ }
+
+ // "dataSet" replaced with "data-*"
+ if (dataSet != null) {
+ for (const dataProp in dataSet) {
+ if (hasOwnProperty.call(dataSet, dataProp)) {
+ const dataName = hyphenateString(dataProp);
+ const dataValue = dataSet[dataProp];
+ if (dataValue != null) {
+ domProps[`data-${dataName}`] = dataValue;
+ }
+ }
+ }
}
// FOCUS
// "focusable" indicates that an element may be a keyboard tab-stop.
if (
// These native elements are focusable by default
- component === 'a' ||
- component === 'button' ||
- component === 'input' ||
- component === 'select' ||
- component === 'textarea'
+ elementType === 'a' ||
+ elementType === 'button' ||
+ elementType === 'input' ||
+ elementType === 'select' ||
+ elementType === 'textarea'
) {
if (focusable === false || accessibilityDisabled === true) {
domProps.tabIndex = '-1';
@@ -188,10 +376,10 @@ const createDOMProps = (component, props) => {
// Additional style resets for interactive elements
const needsCursor = (role === 'button' || role === 'link') && !disabled;
const needsReset =
- component === 'a' ||
- component === 'button' ||
- component === 'li' ||
- component === 'ul' ||
+ elementType === 'a' ||
+ elementType === 'button' ||
+ elementType === 'li' ||
+ elementType === 'ul' ||
role === 'heading';
// Classic CSS styles
const finalClassList = [needsReset && classes.reset, needsCursor && classes.cursor, classList];
@@ -212,7 +400,6 @@ const createDOMProps = (component, props) => {
if (nativeID != null) {
domProps.id = nativeID;
}
-
// Automated test IDs
if (testID != null) {
domProps['data-testid'] = testID;
@@ -225,11 +412,11 @@ const createDOMProps = (component, props) => {
isNativeInteractiveElement ||
role === 'button' ||
role === 'menuitem' ||
- (focusable === true && !accessibilityDisabled)
+ (focusable === true && !disabled)
) {
const onClick = domProps.onClick;
if (onClick != null) {
- if (accessibilityDisabled) {
+ if (disabled) {
// Prevent click propagating if the element is disabled. See #1757
domProps.onClick = function(e) {
e.stopPropagation();