mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: This is a reconstitution of #24190. It extends accessibility actions to include both a name and user facing label. These extensions support both standard and custom actions. We've also added actions support on Android, and added examples to RNTester showing how both standard and custom accessibility actions are used. ## Changelog [general] [changed] - Enhanced accessibility actions support Pull Request resolved: https://github.com/facebook/react-native/pull/24695 Differential Revision: D15391408 Pulled By: cpojer fbshipit-source-id: 5ed48004d46d9887da53baea7fdcd0e7e15c5739
571 lines
16 KiB
JavaScript
571 lines
16 KiB
JavaScript
/**
|
|
* 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.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const React = require('react');
|
|
const {
|
|
AccessibilityInfo,
|
|
Button,
|
|
Text,
|
|
View,
|
|
TouchableOpacity,
|
|
Alert,
|
|
UIManager,
|
|
findNodeHandle,
|
|
Platform,
|
|
} = require('react-native');
|
|
|
|
const RNTesterBlock = require('./RNTesterBlock');
|
|
|
|
class AccessibilityExample extends React.Component {
|
|
render() {
|
|
return (
|
|
<View>
|
|
<RNTesterBlock title="TextView without label">
|
|
<Text>
|
|
Text's accessibilityLabel is the raw text itself unless it is set
|
|
explicitly.
|
|
</Text>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="TextView with label">
|
|
<Text accessibilityLabel="I have label, so I read it instead of embedded text.">
|
|
This text component's accessibilityLabel is set explicitly.
|
|
</Text>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="Nonaccessible view with TextViews">
|
|
<View>
|
|
<Text style={{color: 'green'}}>This is text one.</Text>
|
|
<Text style={{color: 'blue'}}>This is text two.</Text>
|
|
</View>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="Accessible view with TextViews wihout label">
|
|
<View accessible={true}>
|
|
<Text style={{color: 'green'}}>This is text one.</Text>
|
|
<Text style={{color: 'blue'}}>This is text two.</Text>
|
|
</View>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="Accessible view with TextViews with label">
|
|
<View
|
|
accessible={true}
|
|
accessibilityLabel="I have label, so I read it instead of embedded text.">
|
|
<Text style={{color: 'green'}}>This is text one.</Text>
|
|
<Text style={{color: 'blue'}}>This is text two.</Text>
|
|
</View>
|
|
</RNTesterBlock>
|
|
|
|
{/* Android screen readers will say the accessibility hint instead of the text
|
|
since the view doesn't have a label. */}
|
|
<RNTesterBlock title="Accessible view with TextViews with hint">
|
|
<View accessibilityHint="Accessibility hint." accessible={true}>
|
|
<Text style={{color: 'green'}}>This is text one.</Text>
|
|
<Text style={{color: 'blue'}}>This is text two.</Text>
|
|
</View>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="Accessible view TextViews with label and hint">
|
|
<View
|
|
accessibilityLabel="Accessibility label."
|
|
accessibilityHint="Accessibility hint."
|
|
accessible={true}>
|
|
<Text style={{color: 'green'}}>This is text one.</Text>
|
|
<Text style={{color: 'blue'}}>This is text two.</Text>
|
|
</View>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="Text with accessibilityRole = header">
|
|
<Text accessibilityRole="header">This is a title.</Text>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="Touchable with accessibilityRole = link">
|
|
<TouchableOpacity
|
|
onPress={() => Alert.alert('Link has been clicked!')}
|
|
accessibilityRole="link">
|
|
<View>
|
|
<Text>Click me</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="Touchable with accessibilityRole = button">
|
|
<TouchableOpacity
|
|
onPress={() => Alert.alert('Button has been pressed!')}
|
|
accessibilityRole="button">
|
|
<Text>Click me</Text>
|
|
</TouchableOpacity>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="Disabled Touchable with role">
|
|
<TouchableOpacity
|
|
onPress={() => Alert.alert('Button has been pressed!')}
|
|
accessibilityRole="button"
|
|
accessibilityStates={['disabled']}
|
|
disabled={true}>
|
|
<View>
|
|
<Text>
|
|
I am disabled. Clicking me will not trigger any action.
|
|
</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="View with multiple states">
|
|
<View
|
|
accessible={true}
|
|
accessibilityStates={['selected', 'disabled']}>
|
|
<Text>This view is selected and disabled.</Text>
|
|
</View>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="View with label, hint, role, and state">
|
|
<View
|
|
accessible={true}
|
|
accessibilityLabel="Accessibility label."
|
|
accessibilityRole="button"
|
|
accessibilityStates={['selected']}
|
|
accessibilityHint="Accessibility hint.">
|
|
<Text>Accessible view with label, hint, role, and state</Text>
|
|
</View>
|
|
</RNTesterBlock>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
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 AccessibilityActionsExample extends React.Component {
|
|
render() {
|
|
return (
|
|
<View>
|
|
<RNTesterBlock title="Non-touchable with activate action">
|
|
<View
|
|
accessible={true}
|
|
accessibilityActions={[{name: 'activate'}]}
|
|
onAccessibilityAction={event => {
|
|
switch (event.nativeEvent.actionName) {
|
|
case 'activate':
|
|
Alert.alert('Alert', 'View is clicked');
|
|
break;
|
|
}
|
|
}}>
|
|
<Text>Click me</Text>
|
|
</View>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="View with multiple actions">
|
|
<View
|
|
accessible={true}
|
|
accessibilityActions={[
|
|
{name: 'cut', label: 'cut label'},
|
|
{name: 'copy', label: 'copy label'},
|
|
{name: 'paste', label: 'paste label'},
|
|
]}
|
|
onAccessibilityAction={event => {
|
|
switch (event.nativeEvent.actionName) {
|
|
case 'cut':
|
|
Alert.alert('Alert', 'cut action success');
|
|
break;
|
|
case 'copy':
|
|
Alert.alert('Alert', 'copy action success');
|
|
break;
|
|
case 'paste':
|
|
Alert.alert('Alert', 'paste action success');
|
|
break;
|
|
}
|
|
}}>
|
|
<Text>This view supports many actions.</Text>
|
|
</View>
|
|
</RNTesterBlock>
|
|
|
|
<RNTesterBlock title="Adjustable with increment/decrement actions">
|
|
<View
|
|
accessible={true}
|
|
accessibilityRole="adjustable"
|
|
accessibilityActions={[{name: 'increment'}, {name: 'decrement'}]}
|
|
onAccessibilityAction={event => {
|
|
switch (event.nativeEvent.actionName) {
|
|
case 'increment':
|
|
Alert.alert('Alert', 'increment action success');
|
|
break;
|
|
case 'decrement':
|
|
Alert.alert('Alert', 'decrement action success');
|
|
break;
|
|
}
|
|
}}>
|
|
<Text>Slider</Text>
|
|
</View>
|
|
</RNTesterBlock>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
class ScreenReaderStatusExample extends React.Component<{}> {
|
|
state = {
|
|
screenReaderEnabled: false,
|
|
};
|
|
|
|
componentDidMount() {
|
|
AccessibilityInfo.addEventListener(
|
|
'change',
|
|
this._handleScreenReaderToggled,
|
|
);
|
|
AccessibilityInfo.fetch().done(isEnabled => {
|
|
this.setState({
|
|
screenReaderEnabled: isEnabled,
|
|
});
|
|
});
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
AccessibilityInfo.removeEventListener(
|
|
'change',
|
|
this._handleScreenReaderToggled,
|
|
);
|
|
}
|
|
|
|
_handleScreenReaderToggled = isEnabled => {
|
|
this.setState({
|
|
screenReaderEnabled: isEnabled,
|
|
});
|
|
};
|
|
|
|
render() {
|
|
return (
|
|
<View>
|
|
<Text>
|
|
The screen reader is{' '}
|
|
{this.state.screenReaderEnabled ? 'enabled' : 'disabled'}.
|
|
</Text>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
class AnnounceForAccessibility extends React.Component<{}> {
|
|
_handleOnPress = () =>
|
|
AccessibilityInfo.announceForAccessibility('Announcement Test');
|
|
|
|
render() {
|
|
return (
|
|
<View>
|
|
<Button
|
|
onPress={this._handleOnPress}
|
|
title="Announce for Accessibility"
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
exports.title = 'Accessibility';
|
|
exports.description = 'Examples of using Accessibility APIs.';
|
|
exports.examples = [
|
|
{
|
|
title: 'Accessibility elements',
|
|
render(): React.Element<typeof AccessibilityExample> {
|
|
return <AccessibilityExample />;
|
|
},
|
|
},
|
|
{
|
|
title: 'New accessibility roles and states',
|
|
render(): React.Element<typeof AccessibilityRoleAndStateExamples> {
|
|
return <AccessibilityRoleAndStateExample />;
|
|
},
|
|
},
|
|
{
|
|
title: 'Accessibility action examples',
|
|
render(): React.Element<typeof AccessibilityActionsExample> {
|
|
return <AccessibilityActionsExample />;
|
|
},
|
|
},
|
|
{
|
|
title: 'Check if the screen reader is enabled',
|
|
render(): React.Element<typeof ScreenReaderStatusExample> {
|
|
return <ScreenReaderStatusExample />;
|
|
},
|
|
},
|
|
{
|
|
title: 'Check if the screen reader announces',
|
|
render(): React.Element<typeof AnnounceForAccessibility> {
|
|
return <AnnounceForAccessibility />;
|
|
},
|
|
},
|
|
];
|