Skip to content

Commit

Permalink
[change] accessibilityRelationship and accessibilityState props
Browse files Browse the repository at this point in the history
Adds the accessibilityState and accessibilityRelationship object props that map
to ARIA props.

Removes the accessibilityStates array prop that is not compatible with web
accessibility services.

Ref #1172
  • Loading branch information
necolas committed Aug 16, 2019
1 parent e713e59 commit b2dbb57
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('CheckBox', () => {
describe('disabled', () => {
test('when "false" a default checkbox is rendered', () => {
const component = shallow(<CheckBox />);
expect(component.find(checkboxSelector).prop('disabled')).toBe(false);
expect(component.find(checkboxSelector).prop('disabled')).toBe(undefined);
});

test('when "true" a disabled checkbox is rendered', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('components/Switch', () => {
describe('disabled', () => {
test('when "false" a default checkbox is rendered', () => {
const component = shallow(<Switch />);
expect(component.find(checkboxSelector).prop('disabled')).toBe(false);
expect(component.find(checkboxSelector).prop('disabled')).toBe(undefined);
});

test('when "true" a disabled checkbox is rendered', () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/react-native-web/src/exports/Text/TextPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@

import StyleSheetPropType from '../../modules/StyleSheetPropType';
import TextStylePropTypes from './TextStylePropTypes';
import { any, bool, func, number, oneOf, string } from 'prop-types';
import { any, bool, func, number, object, oneOf, string } from 'prop-types';

const TextPropTypes = {
accessibilityLabel: string,
accessibilityLiveRegion: oneOf(['assertive', 'none', 'polite']),
accessibilityRelationship: object,
accessibilityRole: oneOf([
'button',
'header',
Expand All @@ -26,6 +27,7 @@ const TextPropTypes = {
'text'
]),
accessible: bool,
accessibilityState: object,
children: any,
importantForAccessibility: oneOf(['auto', 'no', 'no-hide-descendants', 'yes']),
maxFontSizeMultiplier: number,
Expand Down
41 changes: 26 additions & 15 deletions packages/react-native-web/src/exports/View/ViewPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import EdgeInsetsPropType, { type EdgeInsetsProp } from '../EdgeInsetsPropType';
import StyleSheetPropType from '../../modules/StyleSheetPropType';
import ViewStylePropTypes from './ViewStylePropTypes';
import { any, arrayOf, bool, func, object, oneOf, string } from 'prop-types';
import { any, bool, func, object, oneOf, string } from 'prop-types';
import { type StyleObj } from '../StyleSheet/StyleSheetTypes';

const stylePropType = StyleSheetPropType(ViewStylePropTypes);
Expand All @@ -33,8 +33,30 @@ export type ViewProps = {
accessibilityComponentType?: string,
accessibilityLabel?: string,
accessibilityLiveRegion?: 'none' | 'polite' | 'assertive',
accessibilityRelationship?: {
activedescendant?: ?string,
controls?: ?string,
describedby?: ?string,
details?: ?string,
haspopup?: ?string,
labelledby?: ?string,
owns?: ?string
},
accessibilityRole?: string,
accessibilityStates?: Array<string>,
accessibilityState?: {
busy?: ?boolean,
checked?: ?boolean | 'mixed',
disabled?: ?boolean,
expanded?: ?boolean,
grabbed?: ?boolean,
hidden?: ?boolean,
invalid?: ?boolean,
modal?: ?boolean,
pressed?: ?boolean,
readonly?: ?boolean,
required?: ?boolean,
selected?: ?boolean
},
accessible?: boolean,
children?: any,
className?: string,
Expand Down Expand Up @@ -90,20 +112,9 @@ const ViewPropTypes = {
accessibilityComponentType: string,
accessibilityLabel: string,
accessibilityLiveRegion: oneOf(['assertive', 'none', 'polite']),
accessibilityRelationship: object,
accessibilityRole: string,
accessibilityStates: arrayOf(
oneOf([
'disabled',
'selected',
/* web-only */
'busy',
'checked',
'expanded',
'grabbed',
'invalid',
'pressed'
])
),
accessibilityState: object,
accessible: bool,
children: any,
hitSlop: EdgeInsetsPropType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,53 @@ exports[`modules/createDOMProps includes base reset style for browser-styled ele
exports[`modules/createDOMProps includes cursor style for pressable roles 1`] = `"css-cursor-18t94o4"`;

exports[`modules/createDOMProps includes cursor style for pressable roles 2`] = `"css-cursor-18t94o4"`;

exports[`modules/createDOMProps prop "accessibilityRelationship" values are "id" string 1`] = `
Object {
"aria-activedescendant": "id",
"aria-controls": "id",
"aria-describedby": "id",
"aria-details": "id",
"aria-haspopup": "id",
"aria-labelledby": "id",
"aria-owns": "id",
}
`;

exports[`modules/createDOMProps prop "accessibilityRelationship" values are "undefined" 1`] = `Object {}`;

exports[`modules/createDOMProps prop "accessibilityState" values are "false" 1`] = `
Object {
"aria-busy": false,
"aria-checked": false,
"aria-expanded": false,
"aria-grabbed": false,
"aria-invalid": false,
"aria-modal": false,
"aria-pressed": false,
"aria-readonly": false,
"aria-required": false,
"aria-selected": false,
}
`;

exports[`modules/createDOMProps prop "accessibilityState" values are "true" 1`] = `
Object {
"aria-busy": true,
"aria-checked": true,
"aria-disabled": true,
"aria-expanded": true,
"aria-grabbed": true,
"aria-hidden": true,
"aria-invalid": true,
"aria-modal": true,
"aria-pressed": true,
"aria-readonly": true,
"aria-required": true,
"aria-selected": true,
"disabled": true,
"hidden": true,
}
`;

exports[`modules/createDOMProps prop "accessibilityState" values are "undefined" 1`] = `Object {}`;
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,67 @@ describe('modules/createDOMProps', () => {
expect(props.role).toEqual('button');
});

test('prop "accessibilityStates" becomes ARIA states', () => {
const accessibilityStates = ['disabled', 'selected'];
const props = createProps({ accessibilityStates });
expect(props['aria-disabled']).toEqual(true);
expect(props['aria-selected']).toEqual(true);
describe('prop "accessibilityState"', () => {
function createAccessibilityState(value) {
return {
busy: value,
checked: value,
disabled: value,
expanded: value,
grabbed: value,
hidden: value,
invalid: value,
modal: value,
pressed: value,
readonly: value,
required: value,
selected: value
};
}

test('values are "undefined"', () => {
const accessibilityState = createAccessibilityState(undefined);
const props = createProps({ accessibilityState });
expect(props).toMatchSnapshot();
});

test('values are "false"', () => {
const accessibilityState = createAccessibilityState(false);
const props = createProps({ accessibilityState });
expect(props).toMatchSnapshot();
});

test('values are "true"', () => {
const accessibilityState = createAccessibilityState(true);
const props = createProps({ accessibilityState });
expect(props).toMatchSnapshot();
});
});

describe('prop "accessibilityRelationship"', () => {
function createAccessibilityRelationship(value) {
return {
activedescendant: value,
controls: value,
describedby: value,
details: value,
haspopup: value,
labelledby: value,
owns: value
};
}

test('values are "undefined"', () => {
const accessibilityRelationship = createAccessibilityRelationship(undefined);
const props = createProps({ accessibilityRelationship });
expect(props).toMatchSnapshot();
});

test('values are "id" string', () => {
const accessibilityRelationship = createAccessibilityRelationship('id');
const props = createProps({ accessibilityRelationship });
expect(props).toMatchSnapshot();
});
});

test('prop "className" is preserved', () => {
Expand Down
61 changes: 45 additions & 16 deletions packages/react-native-web/src/modules/createDOMProps/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@ const createDOMProps = (component, props, styleResolver) => {
const {
accessibilityLabel,
accessibilityLiveRegion,
accessibilityStates,
accessibilityRelationship,
accessibilityState,
classList,
className: deprecatedClassName,
disabled: providedDisabled,
importantForAccessibility,
nativeID,
placeholderTextColor,
Expand All @@ -79,32 +81,59 @@ const createDOMProps = (component, props, styleResolver) => {
...domProps
} = props;

const disabled = AccessibilityUtil.isDisabled(props);
const disabled =
(accessibilityState != null && accessibilityState.disabled === true) || providedDisabled;
const role = AccessibilityUtil.propsToAriaRole(props);

// GENERAL ACCESSIBILITY
if (importantForAccessibility === 'no-hide-descendants') {
domProps['aria-hidden'] = true;
}
if (accessibilityLabel && accessibilityLabel.constructor === String) {
// accessibilityLabel
if (accessibilityLabel != null) {
domProps['aria-label'] = accessibilityLabel;
}
if (accessibilityLiveRegion && accessibilityLiveRegion.constructor === String) {

// accessibilityLiveRegion
if (accessibilityLiveRegion != null) {
domProps['aria-live'] = accessibilityLiveRegion === 'none' ? 'off' : accessibilityLiveRegion;
}
if (Array.isArray(accessibilityStates)) {
for (let i = 0; i < accessibilityStates.length; i += 1) {
domProps[`aria-${accessibilityStates[i]}`] = true;

// accessibilityRelationship
if (accessibilityRelationship != null) {
for (const prop in accessibilityRelationship) {
const value = accessibilityRelationship[prop];
if (value != null) {
domProps[`aria-${prop}`] = value;
}
}
}
if (role && role.constructor === String) {

// accessibilityRole
if (role != null) {
domProps.role = role;
}

// DISABLED
if (disabled) {
domProps['aria-disabled'] = disabled;
domProps.disabled = disabled;
// accessibilityState
if (accessibilityState != null) {
for (const prop in accessibilityState) {
const value = accessibilityState[prop];
if (value != null) {
if (prop === 'disabled' || prop === 'hidden') {
if (value === true) {
domProps[`aria-${prop}`] = value;
// also set prop directly to pick up host component behaviour
domProps[prop] = value;
}
} else {
domProps[`aria-${prop}`] = value;
}
}
}
}
// legacy fallbacks
if (importantForAccessibility === 'no-hide-descendants') {
domProps['aria-hidden'] = true;
}
if (disabled === true) {
domProps['aria-disabled'] = true;
domProps.disabled = true;
}

// FOCUS
Expand Down

0 comments on commit b2dbb57

Please sign in to comment.