Skip to content

Commit

Permalink
Additional Accessibility Roles and States (#24095)
Browse files Browse the repository at this point in the history
Summary:
Assistive technologies use the accessibility role of a component to tell the disabled user what the component is, and provide hints about how to use it. Many important roles do not have analog AccessibilityTraits on iOS. This PR adds many critical roles, such as editabletext, checkbox, menu, and switch to name a few.

Accessibility states are used to convey the current state of a component. This PR adds several critical states such as checked, unchecked, on and off.

[general] [change] - Adds critical accessibility roles and states.
Pull Request resolved: #24095

Differential Revision: D15079245

Pulled By: cpojer

fbshipit-source-id: 941b30eb8f5d565597e5ea3a04687d9809cbe372
  • Loading branch information
Marc Mulcahy authored and facebook-github-bot committed Apr 25, 2019
1 parent 421ffb0 commit 1aeac1c
Show file tree
Hide file tree
Showing 10 changed files with 743 additions and 120 deletions.
29 changes: 27 additions & 2 deletions Libraries/Components/View/ViewAccessibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,32 @@ export type AccessibilityRole =
| 'adjustable'
| 'imagebutton'
| 'header'
| 'summary';
| 'summary'
| 'alert'
| 'checkbox'
| 'combobox'
| 'menu'
| 'menubar'
| 'menuitem'
| 'progressbar'
| 'radio'
| 'radiogroup'
| 'scrollbar'
| 'spinbutton'
| 'switch'
| 'tab'
| 'tablist'
| 'timer'
| 'toolbar';

// This must be kept in sync with the AccessibilityStatesMask in RCTViewManager.m
export type AccessibilityStates = $ReadOnlyArray<'disabled' | 'selected'>;
export type AccessibilityStates = $ReadOnlyArray<
| 'disabled'
| 'selected'
| 'checked'
| 'unchecked'
| 'busy'
| 'expanded'
| 'collapsed'
| 'hasPopup',
>;
27 changes: 26 additions & 1 deletion Libraries/DeprecatedPropTypes/DeprecatedViewAccessibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,32 @@ module.exports = {
'imagebutton',
'header',
'summary',
'alert',
'checkbox',
'combobox',
'menu',
'menubar',
'menuitem',
'progressbar',
'radio',
'radiogroup',
'scrollbar',
'spinbutton',
'switch',
'tab',
'tablist',
'timer',
'toolbar',
],
// This must be kept in sync with the AccessibilityStatesMask in RCTViewManager.m
DeprecatedAccessibilityStates: ['selected', 'disabled'],
DeprecatedAccessibilityStates: [
'selected',
'disabled',
'checked',
'unchecked',
'busy',
'expanded',
'collapsed',
'hasPopup',
],
};
278 changes: 278 additions & 0 deletions RNTester/js/AccessibilityExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@
const React = require('react');
const {
AccessibilityInfo,
Button,
Text,
View,
TouchableOpacity,
Alert,
UIManager,
findNodeHandle,
Platform,
} = require('react-native');

const RNTesterBlock = require('./RNTesterBlock');
Expand Down Expand Up @@ -138,6 +142,274 @@ class AccessibilityExample extends React.Component {
}
}

class CheckboxExample extends React.Component {
state = {
checkboxState: 'checked',
};

_onCheckboxPress = () => {
const checkboxState =
this.state.checkboxState === 'checked' ? 'unchecked' : 'checked';

this.setState({
checkboxState: checkboxState,
});

if (Platform.OS === 'android') {
UIManager.sendAccessibilityEvent(
findNodeHandle(this),
UIManager.AccessibilityEventTypes.typeViewClicked,
);
}
};

render() {
return (
<TouchableOpacity
onPress={this._onCheckboxPress}
accessibilityLabel="element 2"
accessibilityRole="checkbox"
accessibilityStates={[this.state.checkboxState]}
accessibilityHint="click me to change state">
<Text>Checkbox example</Text>
</TouchableOpacity>
);
}
}

class SwitchExample extends React.Component {
state = {
switchState: 'checked',
};

_onSwitchToggle = () => {
const switchState =
this.state.switchState === 'checked' ? 'unchecked' : 'checked';

this.setState({
switchState: switchState,
});

if (Platform.OS === 'android') {
UIManager.sendAccessibilityEvent(
findNodeHandle(this),
UIManager.AccessibilityEventTypes.typeViewClicked,
);
}
};

render() {
return (
<TouchableOpacity
onPress={this._onSwitchToggle}
accessibilityLabel="element 12"
accessibilityRole="switch"
accessibilityStates={[this.state.switchState]}
accessible={true}>
<Text>Switch example</Text>
</TouchableOpacity>
);
}
}

class SelectionExample extends React.Component {
constructor(props) {
super(props);
this.selectableElement = React.createRef();
}

state = {
isSelected: true,
isEnabled: false,
};

render() {
let accessibilityStates = [];
let accessibilityHint = 'click me to select';
if (this.state.isSelected) {
accessibilityStates.push('selected');
accessibilityHint = 'click me to unselect';
}
if (!this.state.isEnabled) {
accessibilityStates.push('disabled');
accessibilityHint = 'use the button on the right to enable selection';
}
let buttonTitle = this.state.isEnabled
? 'Disable selection'
: 'Enable selection';

return (
<View style={{flex: 1, flexDirection: 'row'}}>
<TouchableOpacity
ref={this.selectableElement}
accessible={true}
onPress={() => {
this.setState({
isSelected: !this.state.isSelected,
});

if (Platform.OS === 'android') {
UIManager.sendAccessibilityEvent(
findNodeHandle(this.selectableElement.current),
UIManager.AccessibilityEventTypes.typeViewClicked,
);
}
}}
accessibilityLabel="element 19"
accessibilityStates={accessibilityStates}
accessibilityHint={accessibilityHint}>
<Text>Selectable element example</Text>
</TouchableOpacity>
<Button
onPress={() => {
this.setState({
isEnabled: !this.state.isEnabled,
});
}}
title={buttonTitle}
/>
</View>
);
}
}

class ExpandableElementExample extends React.Component {
state = {
expandState: 'collapsed',
};

_onElementPress = () => {
const expandState =
this.state.expandState === 'collapsed' ? 'expanded' : 'collapsed';

this.setState({
expandState: expandState,
});

if (Platform.OS === 'android') {
UIManager.sendAccessibilityEvent(
findNodeHandle(this),
UIManager.AccessibilityEventTypes.typeViewClicked,
);
}
};

render() {
return (
<TouchableOpacity
onPress={this._onElementPress}
accessibilityLabel="element 18"
accessibilityStates={[this.state.expandState]}
accessibilityHint="click me to change state">
<Text>Expandable element example</Text>
</TouchableOpacity>
);
}
}

class AccessibilityRoleAndStateExample extends React.Component<{}> {
render() {
return (
<View>
<View
accessibilityLabel="element 1"
accessibilityRole="alert"
accessible={true}>
<Text>Alert example</Text>
</View>
<CheckboxExample />
<View
accessibilityLabel="element 3"
accessibilityRole="combobox"
accessible={true}>
<Text>Combobox example</Text>
</View>
<View
accessibilityLabel="element 4"
accessibilityRole="menu"
accessible={true}>
<Text>Menu example</Text>
</View>
<View
accessibilityLabel="element 5"
accessibilityRole="menubar"
accessible={true}>
<Text>Menu bar example</Text>
</View>
<View
accessibilityLabel="element 6"
accessibilityRole="menuitem"
accessible={true}>
<Text>Menu item example</Text>
</View>
<View
accessibilityLabel="element 7"
accessibilityRole="progressbar"
accessible={true}>
<Text>Progress bar example</Text>
</View>
<View
accessibilityLabel="element 8"
accessibilityRole="radio"
accessible={true}>
<Text>Radio button example</Text>
</View>
<View
accessibilityLabel="element 9"
accessibilityRole="radiogroup"
accessible={true}>
<Text>Radio group example</Text>
</View>
<View
accessibilityLabel="element 10"
accessibilityRole="scrollbar"
accessible={true}>
<Text>Scrollbar example</Text>
</View>
<View
accessibilityLabel="element 11"
accessibilityRole="spinbutton"
accessible={true}>
<Text>Spin button example</Text>
</View>
<SwitchExample />
<View
accessibilityLabel="element 13"
accessibilityRole="tab"
accessible={true}>
<Text>Tab example</Text>
</View>
<View
accessibilityLabel="element 14"
accessibilityRole="tablist"
accessible={true}>
<Text>Tab list example</Text>
</View>
<View
accessibilityLabel="element 15"
accessibilityRole="timer"
accessible={true}>
<Text>Timer example</Text>
</View>
<View
accessibilityLabel="element 16"
accessibilityRole="toolbar"
accessible={true}>
<Text>Toolbar example</Text>
</View>
<View
accessibilityLabel="element 17"
accessibilityStates={['busy']}
accessible={true}>
<Text>State busy example</Text>
</View>
<ExpandableElementExample />
<SelectionExample />
</View>
);
}
}

class ScreenReaderStatusExample extends React.Component<{}> {
state = {
screenReaderEnabled: false,
Expand Down Expand Up @@ -189,6 +461,12 @@ exports.examples = [
return <AccessibilityExample />;
},
},
{
title: 'New accessibility roles and states',
render(): React.Element<typeof AccessibilityRoleAndStateExamples> {
return <AccessibilityRoleAndStateExample />;
},
},
{
title: 'Check if the screen reader is enabled',
render(): React.Element<typeof ScreenReaderStatusExample> {
Expand Down
4 changes: 4 additions & 0 deletions React/Views/RCTView.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#import <React/RCTPointerEvents.h>
#import <React/RCTView.h>

extern const UIAccessibilityTraits SwitchAccessibilityTrait;

@protocol RCTAutoInsetsProtocol;

@class RCTView;
Expand All @@ -30,6 +32,8 @@
* Accessibility properties
*/
@property (nonatomic, copy) NSArray <NSString *> *accessibilityActions;
@property (nonatomic, copy) NSString *accessibilityRole;
@property (nonatomic, copy) NSArray <NSString *> *accessibilityStates;

/**
* Used to control how touch events are processed.
Expand Down
Loading

0 comments on commit 1aeac1c

Please sign in to comment.