[add] Accessibility props for all ARIA attributes

Adds support for all ARIA equivalents via "accessibility*" props.
This commit is contained in:
Nicolas Gallagher
2021-02-05 14:45:19 -08:00
parent abf00ef9b3
commit 7e70b924ec
5 changed files with 708 additions and 58 deletions
@@ -33,7 +33,6 @@ exports[`components/Button prop "disabled" 1`] = `
<div
aria-disabled="true"
class="css-view-1dbjc4n r-backgroundColor-11mpjr4 r-borderRadius-1jkafct r-transitionProperty-1i6wzkk r-userSelect-lrvibr"
disabled=""
role="button"
style="transition-duration: 0s;"
>
@@ -132,7 +132,6 @@ exports[`components/Pressable prop "disabled" 1`] = `
<div
aria-disabled="true"
class="css-view-1dbjc4n"
disabled=""
/>
`;
@@ -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(`
<div
data-camel-case="camelCase"
data-ms-prefix="msPrefix"
data-one="1"
data-two="2"
/>
`);
});
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();
});
});
});
@@ -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 })
);
});
+242 -55
View File
@@ -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();