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();