Fix accessibility props forwarding

Reintroduce 'accessible' and deprecate.
This commit is contained in:
Nicolas Gallagher
2021-02-11 14:33:49 -08:00
parent 7331c73f1e
commit 51f9ab73e4
13 changed files with 369 additions and 135 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ export default function AppStatePage() {
return (
<Example title="AppState">
<Text>
<Text style={{ marginTop: '1rem' }}>
AppState.currentState: <Text style={{ fontWeight: 'bold' }}>{state.currentState}</Text>
</Text>
<Text>Active count: {state.active}</Text>
+3 -2
View File
@@ -9,7 +9,7 @@ export default function ClipboardPage() {
};
return (
<Example title="Clipboard" w>
<Example title="Clipboard">
<View style={styles.buttonBox}>
<Button onPress={setString} title="Copy to clipboard" />
</View>
@@ -24,7 +24,8 @@ export default function ClipboardPage() {
const styles = StyleSheet.create({
buttonBox: {
maxWidth: 300
maxWidth: 300,
marginTop: '1rem'
},
textInput: {
borderColor: '#AAB8C2',
+1 -1
View File
@@ -20,7 +20,7 @@ export default function DimensionsPage() {
return (
<Example title="Dimensions">
<Text style={{ marginBottom: '1em' }} suppressHydrationWarnings={true}>
<Text style={{ marginVertical: '1em' }} suppressHydrationWarnings={true}>
window: {JSON.stringify(windowDims, null, 2)}
</Text>
<Text suppressHydrationWarnings={true}>screen: {JSON.stringify(screenDims, null, 2)}</Text>
@@ -34,6 +34,14 @@ exports[`components/Text prop "accessibilityLabel" value is set 1`] = `
/>
`;
exports[`components/Text prop "accessibilityLabelledBy" value is set 1`] = `
<div
aria-labelledby="123"
class="css-text-901oao"
dir="auto"
/>
`;
exports[`components/Text prop "accessibilityLiveRegion" value is set 1`] = `
<div
aria-live="polite"
@@ -67,17 +75,64 @@ exports[`components/Text prop "accessibilityRole" value is set 1`] = `
/>
`;
exports[`components/Text prop "hrefAttrs" values are set 1`] = `
exports[`components/Text prop "href" href with accessibilityRole 1`] = `
<a
class="css-reset-4rbku5 css-text-901oao"
dir="auto"
href="https://example.com"
role="presentation"
/>
`;
exports[`components/Text prop "href" value is set 1`] = `
<a
class="css-reset-4rbku5 css-text-901oao"
dir="auto"
href="https://example.com"
/>
`;
exports[`components/Text prop "hrefAttrs" null values are excluded 1`] = `
<a
class="css-reset-4rbku5 css-text-901oao"
dir="auto"
href="https://example.com"
/>
`;
exports[`components/Text prop "hrefAttrs" requires "href" 1`] = `
<div
class="css-text-901oao"
dir="auto"
download=""
href="#"
rel="noopener"
/>
`;
exports[`components/Text prop "hrefAttrs" value is set 1`] = `
<a
class="css-reset-4rbku5 css-text-901oao"
dir="auto"
download="filename.jpg"
href="https://example.com"
rel="nofollow"
target="_blank"
/>
`;
exports[`components/Text prop "lang" fr 1`] = `
<div
class="css-text-901oao"
dir="auto"
lang="fr"
/>
`;
exports[`components/Text prop "lang" undefined 1`] = `
<div
class="css-text-901oao"
dir="auto"
/>
`;
exports[`components/Text prop "nativeID" value is set 1`] = `
<div
class="css-text-901oao"
@@ -25,6 +25,13 @@ describe('components/Text', () => {
});
});
describe('prop "accessibilityLabelledBy"', () => {
test('value is set', () => {
const { container } = render(<Text accessibilityLabelledBy="123" />);
expect(container.firstChild).toMatchSnapshot();
});
});
describe('prop "accessibilityLiveRegion"', () => {
test('value is set', () => {
const { container } = render(<Text accessibilityLiveRegion="polite" />);
@@ -54,11 +61,53 @@ describe('components/Text', () => {
expect(container.firstChild).toMatchSnapshot();
});
describe('prop "href"', () => {
test('value is set', () => {
const { container } = render(<Text href="https://example.com" />);
expect(container.firstChild).toMatchSnapshot();
});
test('href with accessibilityRole', () => {
const { container } = render(<Text accessibilityRole="none" href="https://example.com" />);
expect(container.firstChild).toMatchSnapshot();
});
});
describe('prop "hrefAttrs"', () => {
test('values are set', () => {
const { container } = render(
<Text href="#" hrefAttrs={{ download: true, target: 'blank', rel: 'noopener' }} />
);
test('requires "href"', () => {
const { container } = render(<Text hrefAttrs={{ download: 'filename.jpg' }} />);
expect(container.firstChild).toMatchSnapshot();
});
test('value is set', () => {
const hrefAttrs = {
download: 'filename.jpg',
rel: 'nofollow',
target: 'blank'
};
const { container } = render(<Text href="https://example.com" hrefAttrs={hrefAttrs} />);
expect(container.firstChild).toMatchSnapshot();
});
test('null values are excluded', () => {
const hrefAttrs = {
download: null,
rel: null,
target: null
};
const { container } = render(<Text href="https://example.com" hrefAttrs={hrefAttrs} />);
expect(container.firstChild).toMatchSnapshot();
});
});
describe('prop "lang"', () => {
test('undefined', () => {
const { container } = render(<Text />);
expect(container.firstChild).toMatchSnapshot();
});
test('fr', () => {
const { container } = render(<Text lang="fr" />);
expect(container.firstChild).toMatchSnapshot();
});
});
+11 -42
View File
@@ -14,6 +14,7 @@ import * as React from 'react';
import { forwardRef, useContext, useRef } from 'react';
import createElement from '../createElement';
import css from '../StyleSheet/css';
import * as forwardedProps from '../../modules/forwardedProps';
import pick from '../../modules/pick';
import useElementLayout from '../../modules/useElementLayout';
import useMergeRefs from '../../modules/useMergeRefs';
@@ -23,49 +24,17 @@ import StyleSheet from '../StyleSheet';
import TextAncestorContext from './TextAncestorContext';
const forwardPropsList = {
accessibilityLabel: true,
accessibilityLiveRegion: true,
accessibilityRole: true,
accessibilityState: true,
accessibilityValue: true,
accessible: true,
children: true,
classList: true,
dir: true,
importantForAccessibility: true,
...forwardedProps.defaultProps,
...forwardedProps.accessibilityProps,
...forwardedProps.clickProps,
...forwardedProps.focusProps,
...forwardedProps.keyboardProps,
...forwardedProps.mouseProps,
...forwardedProps.touchProps,
...forwardedProps.styleProps,
href: true,
lang: true,
nativeID: true,
onBlur: true,
onClick: true,
onClickCapture: true,
onContextMenu: true,
onFocus: true,
onKeyDown: true,
onKeyUp: true,
onTouchCancel: true,
onTouchCancelCapture: true,
onTouchEnd: true,
onTouchEndCapture: true,
onTouchMove: true,
onTouchMoveCapture: true,
onTouchStart: true,
onTouchStartCapture: true,
pointerEvents: true,
ref: true,
style: true,
testID: true,
// unstable
dataSet: true,
onMouseDown: true,
onMouseEnter: true,
onMouseLeave: true,
onMouseMove: true,
onMouseOver: true,
onMouseOut: true,
onMouseUp: true,
onScroll: true,
onWheel: true,
href: true
pointerEvents: true
};
const pickProps = (props) => pick(props, forwardPropsList);
+11 -37
View File
@@ -13,6 +13,7 @@ import type { TextInputProps } from './types';
import { forwardRef, useCallback, useMemo, useRef } from 'react';
import createElement from '../createElement';
import css from '../StyleSheet/css';
import * as forwardedProps from '../../modules/forwardedProps';
import pick from '../../modules/pick';
import useElementLayout from '../../modules/useElementLayout';
import useLayoutEffect from '../../modules/useLayoutEffect';
@@ -46,58 +47,31 @@ const setSelection = (node, selection) => {
};
const forwardPropsList = {
accessibilityLabel: true,
accessibilityLiveRegion: true,
accessibilityRole: true,
accessibilityState: true,
accessibilityValue: true,
accessible: true,
...forwardedProps.defaultProps,
...forwardedProps.accessibilityProps,
...forwardedProps.clickProps,
...forwardedProps.focusProps,
...forwardedProps.keyboardProps,
...forwardedProps.mouseProps,
...forwardedProps.touchProps,
...forwardedProps.styleProps,
autoCapitalize: true,
autoComplete: true,
autoCorrect: true,
autoFocus: true,
children: true,
classList: true,
defaultValue: true,
dir: true,
disabled: true,
importantForAccessibility: true,
lang: true,
maxLength: true,
nativeID: true,
onBlur: true,
onChange: true,
onClick: true,
onClickCapture: true,
onContextMenu: true,
onFocus: true,
onScroll: true,
onTouchCancel: true,
onTouchCancelCapture: true,
onTouchEnd: true,
onTouchEndCapture: true,
onTouchMove: true,
onTouchMoveCapture: true,
onTouchStart: true,
onTouchStartCapture: true,
placeholder: true,
pointerEvents: true,
readOnly: true,
ref: true,
rows: true,
spellCheck: true,
style: true,
value: true,
testID: true,
type: true,
// unstable
dataSet: true,
onMouseDown: true,
onMouseEnter: true,
onMouseLeave: true,
onMouseMove: true,
onMouseOver: true,
onMouseOut: true,
onMouseUp: true
type: true
};
const pickProps = (props) => pick(props, forwardPropsList);
@@ -3,6 +3,7 @@
exports[`components/View allows "dir" to be overridden 1`] = `
<div
class="css-view-1dbjc4n"
dir="rtl"
/>
`;
@@ -30,6 +31,13 @@ exports[`components/View prop "accessibilityLabel" value is set 1`] = `
/>
`;
exports[`components/View prop "accessibilityLabelledBy" value is set 1`] = `
<div
aria-labelledby="123"
class="css-view-1dbjc4n"
/>
`;
exports[`components/View prop "accessibilityLiveRegion" value is set 1`] = `
<div
aria-live="polite"
@@ -59,6 +67,44 @@ exports[`components/View prop "accessibilityRole" value is set 1`] = `
/>
`;
exports[`components/View prop "href" href with accessibilityRole 1`] = `
<a
class="css-reset-4rbku5 css-view-1dbjc4n"
href="https://example.com"
role="presentation"
/>
`;
exports[`components/View prop "href" value is set 1`] = `
<a
class="css-reset-4rbku5 css-view-1dbjc4n"
href="https://example.com"
/>
`;
exports[`components/View prop "hrefAttrs" null values are excluded 1`] = `
<a
class="css-reset-4rbku5 css-view-1dbjc4n"
href="https://example.com"
/>
`;
exports[`components/View prop "hrefAttrs" requires "href" 1`] = `
<div
class="css-view-1dbjc4n"
/>
`;
exports[`components/View prop "hrefAttrs" value is set 1`] = `
<a
class="css-reset-4rbku5 css-view-1dbjc4n"
download="filename.jpg"
href="https://example.com"
rel="nofollow"
target="_blank"
/>
`;
exports[`components/View prop "nativeID" value is set 1`] = `
<div
class="css-view-1dbjc4n"
@@ -52,6 +52,13 @@ describe('components/View', () => {
});
});
describe('prop "accessibilityLabelledBy"', () => {
test('value is set', () => {
const { container } = render(<View accessibilityLabelledBy="123" />);
expect(container.firstChild).toMatchSnapshot();
});
});
describe('prop "accessibilityLiveRegion"', () => {
test('value is set', () => {
const { container } = render(<View accessibilityLiveRegion="polite" />);
@@ -81,6 +88,45 @@ describe('components/View', () => {
expect(container.firstChild).toMatchSnapshot();
});
describe('prop "href"', () => {
test('value is set', () => {
const { container } = render(<View href="https://example.com" />);
expect(container.firstChild).toMatchSnapshot();
});
test('href with accessibilityRole', () => {
const { container } = render(<View accessibilityRole="none" href="https://example.com" />);
expect(container.firstChild).toMatchSnapshot();
});
});
describe('prop "hrefAttrs"', () => {
test('requires "href"', () => {
const { container } = render(<View hrefAttrs={{ download: 'filename.jpg' }} />);
expect(container.firstChild).toMatchSnapshot();
});
test('value is set', () => {
const hrefAttrs = {
download: 'filename.jpg',
rel: 'nofollow',
target: 'blank'
};
const { container } = render(<View href="https://example.com" hrefAttrs={hrefAttrs} />);
expect(container.firstChild).toMatchSnapshot();
});
test('null values are excluded', () => {
const hrefAttrs = {
download: null,
rel: null,
target: null
};
const { container } = render(<View href="https://example.com" hrefAttrs={hrefAttrs} />);
expect(container.firstChild).toMatchSnapshot();
});
});
describe('prop "nativeID"', () => {
test('value is set', () => {
const { container } = render(<View nativeID="nativeID" />);
+12 -40
View File
@@ -14,6 +14,7 @@ import * as React from 'react';
import { forwardRef, useContext, useRef } from 'react';
import createElement from '../createElement';
import css from '../StyleSheet/css';
import * as forwardedProps from '../../modules/forwardedProps';
import pick from '../../modules/pick';
import useElementLayout from '../../modules/useElementLayout';
import useMergeRefs from '../../modules/useMergeRefs';
@@ -23,48 +24,19 @@ import StyleSheet from '../StyleSheet';
import TextAncestorContext from '../Text/TextAncestorContext';
const forwardPropsList = {
accessibilityDisabled: true,
accessibilityLabel: true,
accessibilityLiveRegion: true,
accessibilityRole: true,
accessibilityState: true,
accessibilityValue: true,
children: true,
classList: true,
focusable: true,
nativeID: true,
onBlur: true,
onClick: true,
onClickCapture: true,
onContextMenu: true,
onFocus: true,
onKeyDown: true,
onKeyUp: true,
onTouchCancel: true,
onTouchCancelCapture: true,
onTouchEnd: true,
onTouchEndCapture: true,
onTouchMove: true,
onTouchMoveCapture: true,
onTouchStart: true,
onTouchStartCapture: true,
pointerEvents: true,
ref: true,
style: true,
suppressHydrationWarning: true,
testID: true,
// unstable
dataSet: true,
onMouseDown: true,
onMouseEnter: true,
onMouseLeave: true,
onMouseMove: true,
onMouseOver: true,
onMouseOut: true,
onMouseUp: true,
...forwardedProps.defaultProps,
...forwardedProps.accessibilityProps,
...forwardedProps.clickProps,
...forwardedProps.focusProps,
...forwardedProps.keyboardProps,
...forwardedProps.mouseProps,
...forwardedProps.touchProps,
...forwardedProps.styleProps,
href: true,
lang: true,
onScroll: true,
onWheel: true,
href: true
pointerEvents: true
};
const pickProps = (props) => pick(props, forwardPropsList);
@@ -38,6 +38,11 @@ const propsToAccessibilityComponent = (props: Object = emptyObject) => {
return 'label';
}
// special-case for "href" which becomes a link
if (props.href != null) {
return 'a';
}
const role = propsToAriaRole(props);
if (role) {
if (role === 'heading') {
@@ -119,6 +119,7 @@ const createDOMProps = (elementType, props) => {
style: providedStyle,
testID,
// Deprecated
accessible,
accessibilityState,
accessibilityValue,
// Rest
@@ -336,6 +337,7 @@ const createDOMProps = (elementType, props) => {
// FOCUS
// "focusable" indicates that an element may be a keyboard tab-stop.
const _focusable = focusable != null ? focusable : accessible;
if (
// These native elements are focusable by default
elementType === 'a' ||
@@ -344,7 +346,7 @@ const createDOMProps = (elementType, props) => {
elementType === 'select' ||
elementType === 'textarea'
) {
if (focusable === false || accessibilityDisabled === true) {
if (_focusable === false || accessibilityDisabled === true) {
domProps.tabIndex = '-1';
}
} else if (
@@ -357,12 +359,12 @@ const createDOMProps = (elementType, props) => {
role === 'textbox' ||
role === 'switch'
) {
if (focusable !== false) {
if (_focusable !== false) {
domProps.tabIndex = '0';
}
} else {
// Everything else must explicitly set the prop
if (focusable === true) {
if (_focusable === true) {
domProps.tabIndex = '0';
}
}
@@ -412,7 +414,7 @@ const createDOMProps = (elementType, props) => {
isNativeInteractiveElement ||
role === 'button' ||
role === 'menuitem' ||
(focusable === true && !disabled)
(_focusable === true && !disabled)
) {
const onClick = domProps.onClick;
if (onClick != null) {
@@ -0,0 +1,115 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
*/
export const defaultProps = {
children: true,
dataSet: true,
nativeID: true,
ref: true,
suppressHydrationWarning: true,
testID: true
};
export const accessibilityProps = {
accessibilityActiveDescendant: true,
accessibilityAtomic: true,
accessibilityAutoComplete: true,
accessibilityBusy: true,
accessibilityChecked: true,
accessibilityColumnCount: true,
accessibilityColumnIndex: true,
accessibilityColumnSpan: true,
accessibilityControls: true,
accessibilityDescribedBy: true,
accessibilityDetails: true,
accessibilityDisabled: true,
accessibilityErrorMessage: true,
accessibilityExpanded: true,
accessibilityFlowTo: true,
accessibilityHasPopup: true,
accessibilityHidden: true,
accessibilityInvalid: true,
accessibilityKeyShortcuts: true,
accessibilityLabel: true,
accessibilityLabelledBy: true,
accessibilityLevel: true,
accessibilityLiveRegion: true,
accessibilityModal: true,
accessibilityMultiline: true,
accessibilityMultiSelectable: true,
accessibilityOrientation: true,
accessibilityOwns: true,
accessibilityPlaceholder: true,
accessibilityPosInSet: true,
accessibilityPressed: true,
accessibilityReadOnly: true,
accessibilityRequired: true,
accessibilityRole: true,
accessibilityRoleDescription: true,
accessibilityRowCount: true,
accessibilityRowIndex: true,
accessibilityRowSpan: true,
accessibilitySelected: true,
accessibilitySetSize: true,
accessibilitySort: true,
accessibilityValueMax: true,
accessibilityValueMin: true,
accessibilityValueNow: true,
accessibilityValueText: true,
dir: true,
focusable: true,
// Deprecated
accessible: true,
accessibilityState: true,
accessibilityValue: true
};
export const clickProps = {
onClick: true,
onClickCapture: true,
onContextMenu: true
};
export const focusProps = {
onBlur: true,
onFocus: true
};
export const keyboardProps = {
onKeyDown: true,
onKeyDownCapture: true,
onKeyUp: true,
onKeyUpCapture: true
};
export const mouseProps = {
onMouseDown: true,
onMouseEnter: true,
onMouseLeave: true,
onMouseMove: true,
onMouseOver: true,
onMouseOut: true,
onMouseUp: true
};
export const touchProps = {
onTouchCancel: true,
onTouchCancelCapture: true,
onTouchEnd: true,
onTouchEndCapture: true,
onTouchMove: true,
onTouchMoveCapture: true,
onTouchStart: true,
onTouchStartCapture: true
};
export const styleProps = {
classList: true,
style: true
};