-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Making links independently focusable by Talkback #9
Comments
The nested link becomes the next focusable element after the parent element that contains it.Talkback moves focus on a Related facebook/react-native#31757 facebook/react-native@b352e2d Expected Result:
CLICK TO OPEN SOURCECODE
CLICK TO OPEN VIDEO TESTS - pr branch
linksOnFabriAndBranch.mp4CLICK TO OPEN VIDEO TESTS - main branch
linksOnMainDontWork.mp4 |
User Interacts with links through TalkBack default accessibility menu
Functionality built with commit facebook/react-native@b352e2d Expected Result: CLICK TO OPEN SOURCECODE
CLICK TO OPEN VIDEO TESTS - pr branch
linksOnFabriAndBranch.mp4CLICK TO OPEN VIDEO TESTS - main branch - link accessible through menu
2022-02-28.16-14-51.mp4 |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as outdated.
This comment was marked as outdated.
AccessibilityClickableSpan
/**
* {@link ClickableSpan} cannot be parceled, but accessibility services need to be able to cause
* their callback handlers to be called. This class serves as a parcelable placeholder for the
* real spans.
*
* This span is also passed back to an app's process when an accessibility service tries to click
* it. It contains enough information to track down the original clickable span so it can be
* called.
*
* @hide
*/
public class AccessibilityClickableSpan extends ClickableSpan /**
* Container for a message (data and object references) that can
* be sent through an IBinder. A Parcel can contain both flattened data
* that will be unflattened on the other side of the IPC (using the various
* methods here for writing specific types, or the general
* {@link Parcelable} interface), and references to live {@link IBinder}
* objects that will result in the other side receiving a proxy IBinder
* connected with the original IBinder in the Parcel.
* <h3>Parcelables</h3>
*
* <p>The {@link Parcelable} protocol provides an extremely efficient (but
* low-level) protocol for objects to write and read themselves from Parcels.
* You can use the direct methods {@link #writeParcelable(Parcelable, int)}
* and {@link #readParcelable(ClassLoader)} or
* {@link #writeParcelableArray} and
* {@link #readParcelableArray(ClassLoader)} to write or read. https://en.wikipedia.org/wiki/Inter-process_communication
|
getVirtualViewAt
getVirtualViewAt.mp4onPopulateBoundsForVirtualView
onPopulateBoundsForVirtualView.mp4 |
Done
Low Priority
Deleted
|
The TalkBack focus (green rectangle) does not span on multiple lines
CLICK TO OPEN SOURCECODE
<Text
accessibilityRole="link"
onPress={() => {
alert('pressed long link');
}}>
link that spans multiple lines because the text is so long.
</Text> CLICK TO OPEN VIDEO TESTS
linkThatSpansMultipleLines.mp4 |
isVirtual
|
CustomClickableSpan is clickable without talkback
Related #9 (comment) CLICK TO OPEN SOURCECODE
MainActivity.java public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.linearlayout);
TextView textView = new TextView(this);
textView.setMovementMethod(LinkMovementMethod.getInstance());
SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!");
spannable.setSpan(
new CustomClickableSpan(Color.RED),
2, // start
12, // end
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
);
textView.setText(spannable);
linearLayout.addView(textView);
}
} CustomClickabeSpan.java public class CustomClickableSpan extends ClickableSpan {
private int color;
public CustomClickableSpan(int spanColor) {
super();
color = spanColor;
}
@Override
public void onClick(@NonNull View widget) {
Log.w("TESTING::", "onClick called on view: " + widget);
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(color);
}
} CLICK TO OPEN VIDEO TESTS
clickableSpan.mp4 |
Nested Links do not announce disabled or other accessibility properties
Expected Result:
Actual Result:
CLICK TO OPEN SOURCECODE
<Text accessible={true} accessibilityState={{disabled: true}}>
This is a{' '}
<Text
style={{color: 'green'}}
accessibilityState={{disabled: true}}
accessibilityRole="link"
onPress={() => {
alert('pressed test');
}}>
test
</Text>{' '}
of{' '}
<Text
style={{color: 'green'}}
accessibilityRole="link"
onPress={() => {
alert('pressed Inline Links');
}}>
Inline Links
</Text>
in React Native. Here's{' '}
<Text
accessibilityRole="link"
onPress={() => {
alert('pressed another link');
}}>
another link
</Text>
. Here is a{' '}
<Text
accessibilityRole="link"
accessibilityState={{disabled: true}}
onPress={() => {
alert('pressed long link');
}}>
link that spans multiple lines because the text is so long.
</Text>
I wonder how this works?
</Text>
<Text>Normal Text</Text>
<Button
accessibilityRole="button"
title="Testing"
accessibilityState={{disabled: true}}
/>
</> CLICK TO OPEN NOTES
Here the accessibility links are created The accessibility node information are set here CLICK TO OPEN VIDEO TESTS
2022-02-21.17-47-24.mp4 |
possible regression fabOnReact/react-native-notes#9 (comment) need to check on the original branch if caused by removing setState
TalkBack announces no next link when trying to move focus to next element.
When reaching the last page and trying to further scroll down in the ScrollView. Expected Result: Actual Result: CLICK TO OPEN SOURCECODE
<>
<RNTesterBlock title="Disabled TouchableOpacity">
<TouchableOpacity
onPress={() => Alert.alert('Disabled Button has been pressed!')}
accessibilityLabel={'You are pressing Disabled TouchableOpacity'}
accessibilityState={{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
focusable={true}
accessibilityLabel="Accessibility label."
style={{height: 200, backgroundColor: 'red'}}
accessible={true}>
<Text>This view is selected and disabled.</Text>
</View>
</RNTesterBlock>
</> CLICK TO OPEN VIDEO TESTS - BRANCH
2022-02-22.14-48-57.mp4CLICK TO OPEN VIDEO TESTS - MAIN
2022-02-22.15-02-24.mp4 |
Done
|
Testing accessibility examples in main branchCLICK TO OPEN SOURCECODE
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import type {PressEvent} from 'react-native/Libraries/Types/CoreEventTypes';
const React = require('react');
const {
AccessibilityInfo,
TextInput,
Button,
Image,
Text,
View,
TouchableOpacity,
TouchableWithoutFeedback,
Alert,
StyleSheet,
Slider,
Platform,
} = require('react-native');
import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
const RNTesterBlock = require('../../components/RNTesterBlock');
const checkImageSource = require('./check.png');
const uncheckImageSource = require('./uncheck.png');
const mixedCheckboxImageSource = require('./mixed.png');
const {createRef} = require('react');
const styles = StyleSheet.create({
default: {
borderWidth: StyleSheet.hairlineWidth,
borderColor: '#0f0f0f',
flex: 1,
fontSize: 13,
padding: 4,
},
touchable: {
backgroundColor: 'blue',
borderColor: 'red',
borderWidth: 1,
borderRadius: 10,
padding: 10,
borderStyle: 'solid',
},
image: {
width: 20,
height: 20,
resizeMode: 'contain',
marginRight: 10,
},
disabledImage: {
width: 120,
height: 120,
},
containerAlignCenter: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
},
});
class AccessibilityExample extends React.Component<{}> {
render(): React.Node {
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"
accessibilityState={{disabled: true}}
disabled={true}>
<View>
<Text>
I am disabled. Clicking me will not trigger any action.
</Text>
</View>
</TouchableOpacity>
</RNTesterBlock>
<RNTesterBlock title="Disabled TouchableOpacity">
<TouchableOpacity
onPress={() => Alert.alert('Disabled Button has been pressed!')}
accessibilityLabel={'You are pressing Disabled TouchableOpacity'}
accessibilityState={{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}
accessibilityState={{selected: true, disabled: true}}>
<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"
accessibilityState={{selected: true}}
accessibilityHint="Accessibility hint.">
<Text>Accessible view with label, hint, role, and state</Text>
</View>
</RNTesterBlock>
<RNTesterBlock title="TextInput with accessibilityLabelledBy attribute">
<View>
<Text nativeID="formLabel1">Mail Address</Text>
<TextInput
accessibilityLabel="input test1"
accessibilityLabelledBy="formLabel1"
style={styles.default}
/>
<Text nativeID="formLabel2">First Name</Text>
<TextInput
accessibilityLabel="input test2"
accessibilityLabelledBy={['formLabel2', 'formLabel3']}
style={styles.default}
value="Foo"
/>
</View>
</RNTesterBlock>
</View>
);
}
}
class CheckboxExample extends React.Component<
{},
{
checkboxState: boolean | 'mixed',
},
> {
state = {
checkboxState: true,
};
_onCheckboxPress = () => {
let checkboxState = false;
if (this.state.checkboxState === false) {
checkboxState = 'mixed';
} else if (this.state.checkboxState === 'mixed') {
checkboxState = true;
} else {
checkboxState = false;
}
this.setState({
checkboxState: checkboxState,
});
};
render() {
return (
<TouchableOpacity
onPress={this._onCheckboxPress}
accessibilityLabel="element 2"
accessibilityRole="checkbox"
accessibilityState={{checked: this.state.checkboxState}}
accessibilityHint="click me to change state">
<Text>Checkbox example</Text>
</TouchableOpacity>
);
}
}
class SwitchExample extends React.Component<
{},
{
switchState: boolean,
},
> {
state = {
switchState: true,
};
_onSwitchToggle = () => {
const switchState = !this.state.switchState;
this.setState({
switchState: switchState,
});
};
render() {
return (
<TouchableOpacity
onPress={this._onSwitchToggle}
accessibilityLabel="element 12"
accessibilityRole="switch"
accessibilityState={{checked: this.state.switchState}}
accessible={true}>
<Text>Switch example</Text>
</TouchableOpacity>
);
}
}
class SelectionExample extends React.Component<
{},
{
isSelected: boolean,
isEnabled: boolean,
},
> {
constructor(props: {}) {
super(props);
this.selectableElement = createRef();
}
selectableElement: {
current: React.ElementRef<typeof TouchableOpacity> | null,
};
state = {
isSelected: true,
isEnabled: false,
};
render(): React.Node {
const {isSelected, isEnabled} = this.state;
let accessibilityHint = 'click me to select';
if (isSelected) {
accessibilityHint = 'click me to unselect';
}
if (!isEnabled) {
accessibilityHint = 'use the button on the right to enable selection';
}
let buttonTitle = isEnabled ? 'Disable selection' : 'Enable selection';
const touchableHint = ` (touching the TouchableOpacity will ${
isSelected ? 'disable' : 'enable'
} accessibilityState.selected)`;
return (
<View style={styles.containerAlignCenter}>
<TouchableOpacity
ref={this.selectableElement}
accessible={true}
onPress={() => {
if (isEnabled) {
this.setState({
isSelected: !isSelected,
});
} else {
console.warn('selection is disabled, please enable selection.');
}
}}
accessibilityLabel="element 19"
accessibilityState={{
selected: isSelected,
disabled: !isEnabled,
}}
style={styles.touchable}
accessibilityHint={accessibilityHint}>
<Text style={{color: 'white'}}>
{`Selectable TouchableOpacity Example ${touchableHint}`}
</Text>
</TouchableOpacity>
<TextInput
accessibilityLabel="element 20"
accessibilityState={{
selected: isSelected,
}}
multiline={true}
placeholder={`TextInput Example - ${
isSelected ? 'enabled' : 'disabled'
} selection`}
/>
<Button
onPress={() => {
this.setState({
isEnabled: !this.state.isEnabled,
});
}}
title={buttonTitle}
/>
</View>
);
}
}
class ExpandableElementExample extends React.Component<
{},
{
expandState: boolean,
},
> {
state = {
expandState: false,
};
_onElementPress = () => {
const expandState = !this.state.expandState;
this.setState({
expandState: expandState,
});
};
render() {
return (
<TouchableOpacity
onPress={this._onElementPress}
accessibilityLabel="element 18"
accessibilityState={{expanded: this.state.expandState}}
accessibilityHint="click me to change state">
<Text>Expandable element example</Text>
</TouchableOpacity>
);
}
}
class NestedCheckBox extends React.Component<
{},
{
checkbox1: boolean | 'mixed',
checkbox2: boolean | 'mixed',
checkbox3: boolean | 'mixed',
},
> {
state = {
checkbox1: false,
checkbox2: false,
checkbox3: false,
};
_onPress1 = () => {
let checkbox1 = false;
if (this.state.checkbox1 === false) {
checkbox1 = true;
} else if (this.state.checkbox1 === 'mixed') {
checkbox1 = false;
} else {
checkbox1 = false;
}
setTimeout(() => {
this.setState({
checkbox1: checkbox1,
checkbox2: checkbox1,
checkbox3: checkbox1,
});
}, 2000);
};
_onPress2 = () => {
const checkbox2 = !this.state.checkbox2;
this.setState({
checkbox2: checkbox2,
checkbox1:
checkbox2 && this.state.checkbox3
? true
: checkbox2 || this.state.checkbox3
? 'mixed'
: false,
});
};
_onPress3 = () => {
const checkbox3 = !this.state.checkbox3;
this.setState({
checkbox3: checkbox3,
checkbox1:
this.state.checkbox2 && checkbox3
? true
: this.state.checkbox2 || checkbox3
? 'mixed'
: false,
});
};
render() {
return (
<View>
<TouchableOpacity
style={{flex: 1, flexDirection: 'row'}}
onPress={this._onPress1}
accessibilityLabel="Meat"
accessibilityHint="State changes in 2 seconds after clicking."
accessibilityRole="checkbox"
accessibilityState={{checked: this.state.checkbox1}}>
<Image
style={styles.image}
source={
this.state.checkbox1 === 'mixed'
? mixedCheckboxImageSource
: this.state.checkbox1
? checkImageSource
: uncheckImageSource
}
/>
<Text>Meat</Text>
</TouchableOpacity>
<TouchableOpacity
style={{flex: 1, flexDirection: 'row'}}
onPress={this._onPress2}
accessibilityLabel="Beef"
accessibilityRole="checkbox"
accessibilityState={{checked: this.state.checkbox2}}>
<Image
style={styles.image}
source={
this.state.checkbox2 ? checkImageSource : uncheckImageSource
}
/>
<Text>Beef</Text>
</TouchableOpacity>
<TouchableOpacity
style={{flex: 1, flexDirection: 'row'}}
onPress={this._onPress3}
accessibilityLabel="Bacon"
accessibilityRole="checkbox"
accessibilityState={{checked: this.state.checkbox3}}>
<Image
style={styles.image}
source={
this.state.checkbox3 ? checkImageSource : uncheckImageSource
}
/>
<Text>Bacon</Text>
</TouchableOpacity>
</View>
);
}
}
class AccessibilityRoleAndStateExample extends React.Component<{}> {
render(): React.Node {
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"
accessibilityState={{busy: true}}
accessible={true}>
<Text>State busy example</Text>
</View>
<ExpandableElementExample />
<SelectionExample />
<RNTesterBlock title="Nested checkbox with delayed state change">
<NestedCheckBox />
</RNTesterBlock>
</View>
);
}
}
class AccessibilityActionsExample extends React.Component<{}> {
render(): React.Node {
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>
<RNTesterBlock title="TouchableWithoutFeedback with custom accessibility actions">
<TouchableWithoutFeedback
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;
}
}}
onPress={() => Alert.alert('Button has been pressed!')}
accessibilityRole="button">
<View>
<Text>Click me</Text>
</View>
</TouchableWithoutFeedback>
</RNTesterBlock>
<RNTesterBlock title="Button with accessibility actions">
<Button
accessible={true}
accessibilityActions={[
{name: 'activate', label: 'activate label'},
{name: 'copy', label: 'copy label'},
]}
onAccessibilityAction={event => {
switch (event.nativeEvent.actionName) {
case 'activate':
Alert.alert('Alert', 'Activate accessiblity action');
break;
case 'copy':
Alert.alert('Alert', 'copy action success');
break;
}
}}
onPress={() => Alert.alert('Button has been pressed!')}
title="Button with accessiblity action"
/>
</RNTesterBlock>
<RNTesterBlock title="Text with custom accessibility actions">
<Text
accessible={true}
accessibilityActions={[
{name: 'activate', label: 'activate label'},
{name: 'copy', label: 'copy label'},
]}
onAccessibilityAction={event => {
switch (event.nativeEvent.actionName) {
case 'activate':
Alert.alert('Alert', 'Activate accessiblity action');
break;
case 'copy':
Alert.alert('Alert', 'copy action success');
break;
}
}}>
Text
</Text>
</RNTesterBlock>
</View>
);
}
}
function SliderAccessibilityExample(): React.Node {
return (
<View>
<RNTesterBlock
title="Disabled Slider via disabled"
description="Verify with TalkBack/VoiceOver announces Slider as disabled">
<Slider value={25} maximumValue={100} minimumValue={0} disabled />
</RNTesterBlock>
<RNTesterBlock
title="Disabled Slider via accessibiltyState"
description="Verify with TalkBack/VoiceOver announces Slider as disabled">
<Slider
value={75}
maximumValue={100}
minimumValue={0}
accessibilityState={{disabled: true}}
/>
</RNTesterBlock>
<RNTesterBlock
title="Selected Slider"
description="Verify with TalkBack/VoiceOver announces Slider as selected">
<Slider
value={75}
maximumValue={100}
minimumValue={0}
accessibilityState={{selected: true}}
/>
</RNTesterBlock>
</View>
);
}
type FakeSliderExampleState = {
current: number,
textualValue: 'center' | 'left' | 'right',
};
class FakeSliderExample extends React.Component<{}, FakeSliderExampleState> {
state: FakeSliderExampleState = {
current: 50,
textualValue: 'center',
};
increment: () => void = () => {
let newValue = this.state.current + 2;
if (newValue > 100) {
newValue = 100;
}
this.setState({
current: newValue,
});
};
decrement: () => void = () => {
let newValue = this.state.current - 2;
if (newValue < 0) {
newValue = 0;
}
this.setState({
current: newValue,
});
};
render(): React.Node {
return (
<View>
<View
accessible={true}
accessibilityLabel="Fake Slider"
accessibilityRole="adjustable"
accessibilityActions={[{name: 'increment'}, {name: 'decrement'}]}
onAccessibilityAction={event => {
switch (event.nativeEvent.actionName) {
case 'increment':
this.increment();
break;
case 'decrement':
this.decrement();
break;
}
}}
accessibilityValue={{
min: 0,
now: this.state.current,
max: 100,
}}>
<Text>Fake Slider</Text>
</View>
<TouchableWithoutFeedback
accessible={true}
accessibilityLabel="Equalizer"
accessibilityRole="adjustable"
accessibilityActions={[{name: 'increment'}, {name: 'decrement'}]}
onAccessibilityAction={event => {
switch (event.nativeEvent.actionName) {
case 'increment':
if (this.state.textualValue === 'center') {
this.setState({textualValue: 'right'});
} else if (this.state.textualValue === 'left') {
this.setState({textualValue: 'center'});
}
break;
case 'decrement':
if (this.state.textualValue === 'center') {
this.setState({textualValue: 'left'});
} else if (this.state.textualValue === 'right') {
this.setState({textualValue: 'center'});
}
break;
}
}}
accessibilityValue={{text: this.state.textualValue}}>
<View>
<Text>Equalizer</Text>
</View>
</TouchableWithoutFeedback>
</View>
);
}
}
class AnnounceForAccessibility extends React.Component<{}> {
_handleOnPress = () =>
setTimeout(
() => AccessibilityInfo.announceForAccessibility('Announcement Test'),
1000,
);
_handleOnPressQueued = () =>
setTimeout(
() =>
AccessibilityInfo.announceForAccessibilityWithOptions(
'Queued Announcement Test',
{queue: true},
),
1000,
);
_handleOnPressQueueMultiple = () => {
setTimeout(
() =>
AccessibilityInfo.announceForAccessibilityWithOptions(
'First Queued Announcement Test',
{queue: true},
),
1000,
);
setTimeout(
() =>
AccessibilityInfo.announceForAccessibilityWithOptions(
'Second Queued Announcement Test',
{queue: true},
),
1100,
);
setTimeout(
() =>
AccessibilityInfo.announceForAccessibilityWithOptions(
'Third Queued Announcement Test',
{queue: true},
),
1200,
);
};
render(): React.Node {
return Platform.OS === 'ios' ? (
<View>
<Button
onPress={this._handleOnPress}
title="Announce for Accessibility Immediately"
/>
<Button
onPress={this._handleOnPressQueued}
title="Announce for Accessibility Queued"
/>
<Button
onPress={this._handleOnPressQueueMultiple}
title="Announce for Accessibility Queue Multiple"
/>
</View>
) : (
<View>
<Button
onPress={this._handleOnPress}
title="Announce for Accessibility"
/>
</View>
);
}
}
class SetAccessibilityFocusExample extends React.Component<{}> {
render(): React.Node {
const myRef: {current: React.ElementRef<any> | null} = createRef();
const onClose = () => {
if (myRef && myRef.current) {
AccessibilityInfo.sendAccessibilityEvent_unstable(
myRef.current,
'focus',
);
}
};
return (
<View>
<Text>SetAccessibilityFocus on native element</Text>
<Button
ref={myRef}
title={'Click'}
onPress={() => {
Alert.alert(
'Set Accessibility Focus',
'Press okay to proceed',
[{text: 'Okay', onPress: onClose}],
{cancelable: true},
);
}}
/>
</View>
);
}
}
class EnabledExamples extends React.Component<{}> {
render(): React.Node {
return (
<View>
{Platform.OS === 'ios' ? (
<>
<RNTesterBlock title="isBoldTextEnabled()">
<EnabledExample
test="bold text"
eventListener="boldTextChanged"
/>
</RNTesterBlock>
<RNTesterBlock title="isGrayScaleEnabled()">
<EnabledExample
test="gray scale"
eventListener="grayscaleChanged"
/>
</RNTesterBlock>
<RNTesterBlock title="isInvertColorsEnabled()">
<EnabledExample
test="invert colors"
eventListener="invertColorsChanged"
/>
</RNTesterBlock>
<RNTesterBlock title="isReduceTransparencyEnabled()">
<EnabledExample
test="reduce transparency"
eventListener="reduceTransparencyChanged"
/>
</RNTesterBlock>
</>
) : null}
{Platform.OS === 'android' ? (
<RNTesterBlock
title="isAccessibilityServiceEnabled()"
description={
'Event emitted whenever an accessibility service is enabled. This includes TalkBack as well as assistive technologies such as "Select to Speak".'
}>
<EnabledExample
test="any accessibility service"
eventListener="accessibilityServiceChanged"
/>
</RNTesterBlock>
) : null}
<RNTesterBlock title="isReduceMotionEnabled()">
<EnabledExample
test="reduce motion"
eventListener="reduceMotionChanged"
/>
</RNTesterBlock>
<RNTesterBlock title="isScreenReaderEnabled()">
<EnabledExample
test="screen reader"
eventListener="screenReaderChanged"
/>
</RNTesterBlock>
</View>
);
}
}
class EnabledExample extends React.Component<
{
eventListener:
| 'reduceMotionChanged'
| 'boldTextChanged'
| 'grayscaleChanged'
| 'invertColorsChanged'
| 'reduceTransparencyChanged'
| 'reduceMotionChanged'
| 'screenReaderChanged'
| 'accessibilityServiceChanged',
test: string,
},
{
isEnabled: boolean,
},
> {
state = {
isEnabled: false,
};
_subscription: EventSubscription;
componentDidMount() {
this._subscription = AccessibilityInfo.addEventListener(
this.props.eventListener,
this._handleToggled,
);
switch (this.props.eventListener) {
case 'reduceMotionChanged':
return AccessibilityInfo.isReduceMotionEnabled().then(state => {
this.setState({isEnabled: state});
});
case 'accessibilityServiceChanged':
return AccessibilityInfo.isAccessibilityServiceEnabled().then(state => {
this.setState({isEnabled: state});
});
default:
return null;
}
}
componentWillUnmount() {
this._subscription?.remove();
}
_handleToggled = (isEnabled: void | PressEvent | boolean) => {
if (!this.state.isEnabled) {
this.setState({isEnabled: true});
} else {
this.setState({isEnabled: false});
}
};
render(): React.Node {
return (
<View>
<Text>
The {this.props.test} is{' '}
{this.state.isEnabled ? 'enabled' : 'disabled'}
</Text>
<Button
title={this.state.isEnabled ? 'disable' : 'enable'}
onPress={this._handleToggled}
/>
</View>
);
}
}
class DisplayOptionsStatusExample extends React.Component<{}> {
render(): React.Node {
const isAndroid = Platform.OS === 'android';
return (
<View>
<DisplayOptionStatusExample
optionName={'Reduce Motion'}
optionChecker={AccessibilityInfo.isReduceMotionEnabled}
notification={'reduceMotionChanged'}
/>
<DisplayOptionStatusExample
optionName={'Screen Reader'}
optionChecker={AccessibilityInfo.isScreenReaderEnabled}
notification={'screenReaderChanged'}
/>
{isAndroid ? null : (
<>
<DisplayOptionStatusExample
optionName={'Bold Text'}
optionChecker={AccessibilityInfo.isBoldTextEnabled}
notification={'boldTextChanged'}
/>
<DisplayOptionStatusExample
optionName={'Grayscale'}
optionChecker={AccessibilityInfo.isGrayscaleEnabled}
notification={'grayscaleChanged'}
/>
<DisplayOptionStatusExample
optionName={'Invert Colors'}
optionChecker={AccessibilityInfo.isInvertColorsEnabled}
notification={'invertColorsChanged'}
/>
<DisplayOptionStatusExample
optionName={'Reduce Transparency'}
optionChecker={AccessibilityInfo.isReduceTransparencyEnabled}
notification={'reduceTransparencyChanged'}
/>
</>
)}
</View>
);
}
}
function DisplayOptionStatusExample({optionName, optionChecker, notification}) {
const [statusEnabled, setStatusEnabled] = React.useState(false);
React.useEffect(() => {
AccessibilityInfo.addEventListener(notification, setStatusEnabled);
optionChecker().then(isEnabled => {
setStatusEnabled(isEnabled);
});
return function cleanup() {
AccessibilityInfo.removeEventListener(notification, setStatusEnabled);
};
}, [optionChecker, notification]);
return (
<View>
<Text>
{optionName}
{' is '}
{statusEnabled ? 'enabled' : 'disabled'}.
</Text>
</View>
);
}
exports.title = 'Accessibility';
exports.documentationURL = 'https://reactnative.dev/docs/accessibilityinfo';
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 AccessibilityRoleAndStateExample> {
return <AccessibilityRoleAndStateExample />;
},
},
{
title: 'Accessibility action examples',
render(): React.Element<typeof AccessibilityActionsExample> {
return <AccessibilityActionsExample />;
},
},
{
title: 'Slider Accessibility Examples',
render(): React.Element<typeof SliderAccessibilityExample> {
return <SliderAccessibilityExample />;
},
},
{
title: 'Fake Slider Example',
render(): React.Element<typeof FakeSliderExample> {
return <FakeSliderExample />;
},
},
{
title: 'Check if the display options are enabled',
render(): React.Element<typeof DisplayOptionsStatusExample> {
return <DisplayOptionsStatusExample />;
},
},
{
title: 'Check if the screen reader announces',
render(): React.Element<typeof AnnounceForAccessibility> {
return <AnnounceForAccessibility />;
},
},
{
title: 'Check if accessibility is focused',
render(): React.Element<typeof SetAccessibilityFocusExample> {
return <SetAccessibilityFocusExample />;
},
},
{
title: 'Check if these properties are enabled',
render(): React.Element<typeof EnabledExamples> {
return <EnabledExamples />;
},
},
{
title:
'Check if accessibilityState disabled is announced when the screenreader focus moves on the image',
render(): React.Element<typeof Image> {
return (
<Image
accessible={true}
accessibilityLabel="plain local image"
accessibilityState={{disabled: true}}
source={require('../../assets/like.png')}
style={styles.disabledImage}
/>
);
},
},
]; 2022-03-01.09-20-28.mp4 |
Testing accessibility android examples in main branchCLICK TO OPEN SOURCECODE
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
'use strict';
const React = require('react');
import RNTesterBlock from '../../components/RNTesterBlock';
import RNTesterPage from '../../components/RNTesterPage';
import {StyleSheet, Text, View, TouchableWithoutFeedback} from 'react-native';
const importantForAccessibilityValues = [
'auto',
'yes',
'no',
'no-hide-descendants',
];
type AccessibilityAndroidExampleState = {
count: number,
backgroundImportantForAcc: number,
forgroundImportantForAcc: number,
};
class AccessibilityAndroidExample extends React.Component<
{},
AccessibilityAndroidExampleState,
> {
state: AccessibilityAndroidExampleState = {
count: 0,
backgroundImportantForAcc: 0,
forgroundImportantForAcc: 0,
};
_addOne = () => {
this.setState({
count: ++this.state.count,
});
};
_changeBackgroundImportantForAcc = () => {
this.setState({
backgroundImportantForAcc: (this.state.backgroundImportantForAcc + 1) % 4,
});
};
_changeForgroundImportantForAcc = () => {
this.setState({
forgroundImportantForAcc: (this.state.forgroundImportantForAcc + 1) % 4,
});
};
render(): React.Node {
return (
<RNTesterPage title={'Accessibility Android APIs'}>
<RNTesterBlock title="LiveRegion">
<TouchableWithoutFeedback onPress={this._addOne}>
<View style={styles.embedded}>
<Text>Click me</Text>
</View>
</TouchableWithoutFeedback>
<View accessibilityLiveRegion="polite">
<Text>Clicked {this.state.count} times</Text>
</View>
</RNTesterBlock>
<RNTesterBlock title="Overlapping views and importantForAccessibility property">
<View style={styles.container}>
<TouchableWithoutFeedback
accessible={true}
accessibilityLabel="First layout"
importantForAccessibility={
importantForAccessibilityValues[
this.state.backgroundImportantForAcc
]
}>
<View accessible={true} style={styles.touchableContainer}>
<Text style={{fontSize: 25}}>Hello</Text>
</View>
</TouchableWithoutFeedback>
<View
style={{
position: 'absolute',
left: 10,
top: 25,
right: 10,
height: 110,
backgroundColor: 'yellow',
opacity: 0.5,
}}
accessible={true}
accessibilityLabel="Second layout"
importantForAccessibility={
importantForAccessibilityValues[
this.state.forgroundImportantForAcc
]
}>
<View accessible={true}>
<Text style={{fontSize: 20}}>world</Text>
</View>
</View>
</View>
<TouchableWithoutFeedback
onPress={this._changeBackgroundImportantForAcc}>
<View style={styles.embedded}>
<Text>
Change importantForAccessibility for background layout.
</Text>
</View>
</TouchableWithoutFeedback>
<View accessible={true}>
<Text>Background layout importantForAccessibility</Text>
<Text>
{
importantForAccessibilityValues[
this.state.backgroundImportantForAcc
]
}
</Text>
</View>
<TouchableWithoutFeedback
onPress={this._changeForgroundImportantForAcc}>
<View style={styles.embedded}>
<Text>
Change importantForAccessibility for forground layout.
</Text>
</View>
</TouchableWithoutFeedback>
<View accessible={true}>
<Text>Forground layout importantForAccessibility</Text>
<Text>
{
importantForAccessibilityValues[
this.state.forgroundImportantForAcc
]
}
</Text>
</View>
</RNTesterBlock>
</RNTesterPage>
);
}
}
const styles = StyleSheet.create({
touchableContainer: {
position: 'absolute',
left: 10,
top: 10,
right: 10,
height: 100,
backgroundColor: 'green',
},
embedded: {
backgroundColor: 'yellow',
padding: 10,
},
container: {
flex: 1,
backgroundColor: 'white',
padding: 10,
height: 150,
},
});
exports.title = 'AccessibilityAndroid';
exports.description = 'Android specific Accessibility APIs.';
exports.examples = [
{
title: 'Accessibility elements',
render(): React.Element<typeof AccessibilityAndroidExample> {
return <AccessibilityAndroidExample />;
},
},
]; accessibilityAndroidMain.mp4 |
Testing accessibility examples in pr branchCLICK TO OPEN SOURCECODE
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import type {PressEvent} from 'react-native/Libraries/Types/CoreEventTypes';
const React = require('react');
const {
AccessibilityInfo,
TextInput,
Button,
Image,
Text,
View,
TouchableOpacity,
TouchableWithoutFeedback,
Alert,
StyleSheet,
Slider,
Platform,
} = require('react-native');
import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
const RNTesterBlock = require('../../components/RNTesterBlock');
const checkImageSource = require('./check.png');
const uncheckImageSource = require('./uncheck.png');
const mixedCheckboxImageSource = require('./mixed.png');
const {createRef} = require('react');
const styles = StyleSheet.create({
default: {
borderWidth: StyleSheet.hairlineWidth,
borderColor: '#0f0f0f',
flex: 1,
fontSize: 13,
padding: 4,
},
touchable: {
backgroundColor: 'blue',
borderColor: 'red',
borderWidth: 1,
borderRadius: 10,
padding: 10,
borderStyle: 'solid',
},
image: {
width: 20,
height: 20,
resizeMode: 'contain',
marginRight: 10,
},
disabledImage: {
width: 120,
height: 120,
},
containerAlignCenter: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
},
});
class AccessibilityExample extends React.Component<{}> {
render(): React.Node {
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"
accessibilityState={{disabled: true}}
disabled={true}>
<View>
<Text>
I am disabled. Clicking me will not trigger any action.
</Text>
</View>
</TouchableOpacity>
</RNTesterBlock>
<RNTesterBlock title="Disabled TouchableOpacity">
<TouchableOpacity
onPress={() => Alert.alert('Disabled Button has been pressed!')}
accessibilityLabel={'You are pressing Disabled TouchableOpacity'}
accessibilityState={{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}
accessibilityState={{selected: true, disabled: true}}>
<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"
accessibilityState={{selected: true}}
accessibilityHint="Accessibility hint.">
<Text>Accessible view with label, hint, role, and state</Text>
</View>
</RNTesterBlock>
<RNTesterBlock title="TextInput with accessibilityLabelledBy attribute">
<View>
<Text nativeID="formLabel1">Mail Address</Text>
<TextInput
accessibilityLabel="input test1"
accessibilityLabelledBy="formLabel1"
style={styles.default}
/>
<Text nativeID="formLabel2">First Name</Text>
<TextInput
accessibilityLabel="input test2"
accessibilityLabelledBy={['formLabel2', 'formLabel3']}
style={styles.default}
value="Foo"
/>
</View>
</RNTesterBlock>
</View>
);
}
}
class CheckboxExample extends React.Component<
{},
{
checkboxState: boolean | 'mixed',
},
> {
state = {
checkboxState: true,
};
_onCheckboxPress = () => {
let checkboxState = false;
if (this.state.checkboxState === false) {
checkboxState = 'mixed';
} else if (this.state.checkboxState === 'mixed') {
checkboxState = true;
} else {
checkboxState = false;
}
this.setState({
checkboxState: checkboxState,
});
};
render() {
return (
<TouchableOpacity
onPress={this._onCheckboxPress}
accessibilityLabel="element 2"
accessibilityRole="checkbox"
accessibilityState={{checked: this.state.checkboxState}}
accessibilityHint="click me to change state">
<Text>Checkbox example</Text>
</TouchableOpacity>
);
}
}
class SwitchExample extends React.Component<
{},
{
switchState: boolean,
},
> {
state = {
switchState: true,
};
_onSwitchToggle = () => {
const switchState = !this.state.switchState;
this.setState({
switchState: switchState,
});
};
render() {
return (
<TouchableOpacity
onPress={this._onSwitchToggle}
accessibilityLabel="element 12"
accessibilityRole="switch"
accessibilityState={{checked: this.state.switchState}}
accessible={true}>
<Text>Switch example</Text>
</TouchableOpacity>
);
}
}
class SelectionExample extends React.Component<
{},
{
isSelected: boolean,
isEnabled: boolean,
},
> {
constructor(props: {}) {
super(props);
this.selectableElement = createRef();
}
selectableElement: {
current: React.ElementRef<typeof TouchableOpacity> | null,
};
state = {
isSelected: true,
isEnabled: false,
};
render(): React.Node {
const {isSelected, isEnabled} = this.state;
let accessibilityHint = 'click me to select';
if (isSelected) {
accessibilityHint = 'click me to unselect';
}
if (!isEnabled) {
accessibilityHint = 'use the button on the right to enable selection';
}
let buttonTitle = isEnabled ? 'Disable selection' : 'Enable selection';
const touchableHint = ` (touching the TouchableOpacity will ${
isSelected ? 'disable' : 'enable'
} accessibilityState.selected)`;
return (
<View style={styles.containerAlignCenter}>
<TouchableOpacity
ref={this.selectableElement}
accessible={true}
onPress={() => {
if (isEnabled) {
this.setState({
isSelected: !isSelected,
});
} else {
console.warn('selection is disabled, please enable selection.');
}
}}
accessibilityLabel="element 19"
accessibilityState={{
selected: isSelected,
disabled: !isEnabled,
}}
style={styles.touchable}
accessibilityHint={accessibilityHint}>
<Text style={{color: 'white'}}>
{`Selectable TouchableOpacity Example ${touchableHint}`}
</Text>
</TouchableOpacity>
<TextInput
accessibilityLabel="element 20"
accessibilityState={{
selected: isSelected,
}}
multiline={true}
placeholder={`TextInput Example - ${
isSelected ? 'enabled' : 'disabled'
} selection`}
/>
<Button
onPress={() => {
this.setState({
isEnabled: !this.state.isEnabled,
});
}}
title={buttonTitle}
/>
</View>
);
}
}
class ExpandableElementExample extends React.Component<
{},
{
expandState: boolean,
},
> {
state = {
expandState: false,
};
_onElementPress = () => {
const expandState = !this.state.expandState;
this.setState({
expandState: expandState,
});
};
render() {
return (
<TouchableOpacity
onPress={this._onElementPress}
accessibilityLabel="element 18"
accessibilityState={{expanded: this.state.expandState}}
accessibilityHint="click me to change state">
<Text>Expandable element example</Text>
</TouchableOpacity>
);
}
}
class NestedCheckBox extends React.Component<
{},
{
checkbox1: boolean | 'mixed',
checkbox2: boolean | 'mixed',
checkbox3: boolean | 'mixed',
},
> {
state = {
checkbox1: false,
checkbox2: false,
checkbox3: false,
};
_onPress1 = () => {
let checkbox1 = false;
if (this.state.checkbox1 === false) {
checkbox1 = true;
} else if (this.state.checkbox1 === 'mixed') {
checkbox1 = false;
} else {
checkbox1 = false;
}
setTimeout(() => {
this.setState({
checkbox1: checkbox1,
checkbox2: checkbox1,
checkbox3: checkbox1,
});
}, 2000);
};
_onPress2 = () => {
const checkbox2 = !this.state.checkbox2;
this.setState({
checkbox2: checkbox2,
checkbox1:
checkbox2 && this.state.checkbox3
? true
: checkbox2 || this.state.checkbox3
? 'mixed'
: false,
});
};
_onPress3 = () => {
const checkbox3 = !this.state.checkbox3;
this.setState({
checkbox3: checkbox3,
checkbox1:
this.state.checkbox2 && checkbox3
? true
: this.state.checkbox2 || checkbox3
? 'mixed'
: false,
});
};
render() {
return (
<View>
<TouchableOpacity
style={{flex: 1, flexDirection: 'row'}}
onPress={this._onPress1}
accessibilityLabel="Meat"
accessibilityHint="State changes in 2 seconds after clicking."
accessibilityRole="checkbox"
accessibilityState={{checked: this.state.checkbox1}}>
<Image
style={styles.image}
source={
this.state.checkbox1 === 'mixed'
? mixedCheckboxImageSource
: this.state.checkbox1
? checkImageSource
: uncheckImageSource
}
/>
<Text>Meat</Text>
</TouchableOpacity>
<TouchableOpacity
style={{flex: 1, flexDirection: 'row'}}
onPress={this._onPress2}
accessibilityLabel="Beef"
accessibilityRole="checkbox"
accessibilityState={{checked: this.state.checkbox2}}>
<Image
style={styles.image}
source={
this.state.checkbox2 ? checkImageSource : uncheckImageSource
}
/>
<Text>Beef</Text>
</TouchableOpacity>
<TouchableOpacity
style={{flex: 1, flexDirection: 'row'}}
onPress={this._onPress3}
accessibilityLabel="Bacon"
accessibilityRole="checkbox"
accessibilityState={{checked: this.state.checkbox3}}>
<Image
style={styles.image}
source={
this.state.checkbox3 ? checkImageSource : uncheckImageSource
}
/>
<Text>Bacon</Text>
</TouchableOpacity>
</View>
);
}
}
class AccessibilityRoleAndStateExample extends React.Component<{}> {
render(): React.Node {
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"
accessibilityState={{busy: true}}
accessible={true}>
<Text>State busy example</Text>
</View>
<ExpandableElementExample />
<SelectionExample />
<RNTesterBlock title="Nested checkbox with delayed state change">
<NestedCheckBox />
</RNTesterBlock>
</View>
);
}
}
class AccessibilityActionsExample extends React.Component<{}> {
render(): React.Node {
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>
<RNTesterBlock title="TouchableWithoutFeedback with custom accessibility actions">
<TouchableWithoutFeedback
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;
}
}}
onPress={() => Alert.alert('Button has been pressed!')}
accessibilityRole="button">
<View>
<Text>Click me</Text>
</View>
</TouchableWithoutFeedback>
</RNTesterBlock>
<RNTesterBlock title="Button with accessibility actions">
<Button
accessible={true}
accessibilityActions={[
{name: 'activate', label: 'activate label'},
{name: 'copy', label: 'copy label'},
]}
onAccessibilityAction={event => {
switch (event.nativeEvent.actionName) {
case 'activate':
Alert.alert('Alert', 'Activate accessiblity action');
break;
case 'copy':
Alert.alert('Alert', 'copy action success');
break;
}
}}
onPress={() => Alert.alert('Button has been pressed!')}
title="Button with accessiblity action"
/>
</RNTesterBlock>
<RNTesterBlock title="Text with custom accessibility actions">
<Text
accessible={true}
accessibilityActions={[
{name: 'activate', label: 'activate label'},
{name: 'copy', label: 'copy label'},
]}
onAccessibilityAction={event => {
switch (event.nativeEvent.actionName) {
case 'activate':
Alert.alert('Alert', 'Activate accessiblity action');
break;
case 'copy':
Alert.alert('Alert', 'copy action success');
break;
}
}}>
Text
</Text>
</RNTesterBlock>
</View>
);
}
}
function SliderAccessibilityExample(): React.Node {
return (
<View>
<RNTesterBlock
title="Disabled Slider via disabled"
description="Verify with TalkBack/VoiceOver announces Slider as disabled">
<Slider value={25} maximumValue={100} minimumValue={0} disabled />
</RNTesterBlock>
<RNTesterBlock
title="Disabled Slider via accessibiltyState"
description="Verify with TalkBack/VoiceOver announces Slider as disabled">
<Slider
value={75}
maximumValue={100}
minimumValue={0}
accessibilityState={{disabled: true}}
/>
</RNTesterBlock>
<RNTesterBlock
title="Selected Slider"
description="Verify with TalkBack/VoiceOver announces Slider as selected">
<Slider
value={75}
maximumValue={100}
minimumValue={0}
accessibilityState={{selected: true}}
/>
</RNTesterBlock>
</View>
);
}
type FakeSliderExampleState = {
current: number,
textualValue: 'center' | 'left' | 'right',
};
class FakeSliderExample extends React.Component<{}, FakeSliderExampleState> {
state: FakeSliderExampleState = {
current: 50,
textualValue: 'center',
};
increment: () => void = () => {
let newValue = this.state.current + 2;
if (newValue > 100) {
newValue = 100;
}
this.setState({
current: newValue,
});
};
decrement: () => void = () => {
let newValue = this.state.current - 2;
if (newValue < 0) {
newValue = 0;
}
this.setState({
current: newValue,
});
};
render(): React.Node {
return (
<View>
<View
accessible={true}
accessibilityLabel="Fake Slider"
accessibilityRole="adjustable"
accessibilityActions={[{name: 'increment'}, {name: 'decrement'}]}
onAccessibilityAction={event => {
switch (event.nativeEvent.actionName) {
case 'increment':
this.increment();
break;
case 'decrement':
this.decrement();
break;
}
}}
accessibilityValue={{
min: 0,
now: this.state.current,
max: 100,
}}>
<Text>Fake Slider</Text>
</View>
<TouchableWithoutFeedback
accessible={true}
accessibilityLabel="Equalizer"
accessibilityRole="adjustable"
accessibilityActions={[{name: 'increment'}, {name: 'decrement'}]}
onAccessibilityAction={event => {
switch (event.nativeEvent.actionName) {
case 'increment':
if (this.state.textualValue === 'center') {
this.setState({textualValue: 'right'});
} else if (this.state.textualValue === 'left') {
this.setState({textualValue: 'center'});
}
break;
case 'decrement':
if (this.state.textualValue === 'center') {
this.setState({textualValue: 'left'});
} else if (this.state.textualValue === 'right') {
this.setState({textualValue: 'center'});
}
break;
}
}}
accessibilityValue={{text: this.state.textualValue}}>
<View>
<Text>Equalizer</Text>
</View>
</TouchableWithoutFeedback>
</View>
);
}
}
class AnnounceForAccessibility extends React.Component<{}> {
_handleOnPress = () =>
setTimeout(
() => AccessibilityInfo.announceForAccessibility('Announcement Test'),
1000,
);
_handleOnPressQueued = () =>
setTimeout(
() =>
AccessibilityInfo.announceForAccessibilityWithOptions(
'Queued Announcement Test',
{queue: true},
),
1000,
);
_handleOnPressQueueMultiple = () => {
setTimeout(
() =>
AccessibilityInfo.announceForAccessibilityWithOptions(
'First Queued Announcement Test',
{queue: true},
),
1000,
);
setTimeout(
() =>
AccessibilityInfo.announceForAccessibilityWithOptions(
'Second Queued Announcement Test',
{queue: true},
),
1100,
);
setTimeout(
() =>
AccessibilityInfo.announceForAccessibilityWithOptions(
'Third Queued Announcement Test',
{queue: true},
),
1200,
);
};
render(): React.Node {
return Platform.OS === 'ios' ? (
<View>
<Button
onPress={this._handleOnPress}
title="Announce for Accessibility Immediately"
/>
<Button
onPress={this._handleOnPressQueued}
title="Announce for Accessibility Queued"
/>
<Button
onPress={this._handleOnPressQueueMultiple}
title="Announce for Accessibility Queue Multiple"
/>
</View>
) : (
<View>
<Button
onPress={this._handleOnPress}
title="Announce for Accessibility"
/>
</View>
);
}
}
class SetAccessibilityFocusExample extends React.Component<{}> {
render(): React.Node {
const myRef: {current: React.ElementRef<any> | null} = createRef();
const onClose = () => {
if (myRef && myRef.current) {
AccessibilityInfo.sendAccessibilityEvent_unstable(
myRef.current,
'focus',
);
}
};
return (
<View>
<Text>SetAccessibilityFocus on native element</Text>
<Button
ref={myRef}
title={'Click'}
onPress={() => {
Alert.alert(
'Set Accessibility Focus',
'Press okay to proceed',
[{text: 'Okay', onPress: onClose}],
{cancelable: true},
);
}}
/>
</View>
);
}
}
class EnabledExamples extends React.Component<{}> {
render(): React.Node {
return (
<View>
{Platform.OS === 'ios' ? (
<>
<RNTesterBlock title="isBoldTextEnabled()">
<EnabledExample
test="bold text"
eventListener="boldTextChanged"
/>
</RNTesterBlock>
<RNTesterBlock title="isGrayScaleEnabled()">
<EnabledExample
test="gray scale"
eventListener="grayscaleChanged"
/>
</RNTesterBlock>
<RNTesterBlock title="isInvertColorsEnabled()">
<EnabledExample
test="invert colors"
eventListener="invertColorsChanged"
/>
</RNTesterBlock>
<RNTesterBlock title="isReduceTransparencyEnabled()">
<EnabledExample
test="reduce transparency"
eventListener="reduceTransparencyChanged"
/>
</RNTesterBlock>
</>
) : null}
{Platform.OS === 'android' ? (
<RNTesterBlock
title="isAccessibilityServiceEnabled()"
description={
'Event emitted whenever an accessibility service is enabled. This includes TalkBack as well as assistive technologies such as "Select to Speak".'
}>
<EnabledExample
test="any accessibility service"
eventListener="accessibilityServiceChanged"
/>
</RNTesterBlock>
) : null}
<RNTesterBlock title="isReduceMotionEnabled()">
<EnabledExample
test="reduce motion"
eventListener="reduceMotionChanged"
/>
</RNTesterBlock>
<RNTesterBlock title="isScreenReaderEnabled()">
<EnabledExample
test="screen reader"
eventListener="screenReaderChanged"
/>
</RNTesterBlock>
</View>
);
}
}
class EnabledExample extends React.Component<
{
eventListener:
| 'reduceMotionChanged'
| 'boldTextChanged'
| 'grayscaleChanged'
| 'invertColorsChanged'
| 'reduceTransparencyChanged'
| 'reduceMotionChanged'
| 'screenReaderChanged'
| 'accessibilityServiceChanged',
test: string,
},
{
isEnabled: boolean,
},
> {
state = {
isEnabled: false,
};
_subscription: EventSubscription;
componentDidMount() {
this._subscription = AccessibilityInfo.addEventListener(
this.props.eventListener,
this._handleToggled,
);
switch (this.props.eventListener) {
case 'reduceMotionChanged':
return AccessibilityInfo.isReduceMotionEnabled().then(state => {
this.setState({isEnabled: state});
});
case 'accessibilityServiceChanged':
return AccessibilityInfo.isAccessibilityServiceEnabled().then(state => {
this.setState({isEnabled: state});
});
default:
return null;
}
}
componentWillUnmount() {
this._subscription?.remove();
}
_handleToggled = (isEnabled: void | PressEvent | boolean) => {
if (!this.state.isEnabled) {
this.setState({isEnabled: true});
} else {
this.setState({isEnabled: false});
}
};
render(): React.Node {
return (
<View>
<Text>
The {this.props.test} is{' '}
{this.state.isEnabled ? 'enabled' : 'disabled'}
</Text>
<Button
title={this.state.isEnabled ? 'disable' : 'enable'}
onPress={this._handleToggled}
/>
</View>
);
}
}
class DisplayOptionsStatusExample extends React.Component<{}> {
render(): React.Node {
const isAndroid = Platform.OS === 'android';
return (
<View>
<DisplayOptionStatusExample
optionName={'Reduce Motion'}
optionChecker={AccessibilityInfo.isReduceMotionEnabled}
notification={'reduceMotionChanged'}
/>
<DisplayOptionStatusExample
optionName={'Screen Reader'}
optionChecker={AccessibilityInfo.isScreenReaderEnabled}
notification={'screenReaderChanged'}
/>
{isAndroid ? null : (
<>
<DisplayOptionStatusExample
optionName={'Bold Text'}
optionChecker={AccessibilityInfo.isBoldTextEnabled}
notification={'boldTextChanged'}
/>
<DisplayOptionStatusExample
optionName={'Grayscale'}
optionChecker={AccessibilityInfo.isGrayscaleEnabled}
notification={'grayscaleChanged'}
/>
<DisplayOptionStatusExample
optionName={'Invert Colors'}
optionChecker={AccessibilityInfo.isInvertColorsEnabled}
notification={'invertColorsChanged'}
/>
<DisplayOptionStatusExample
optionName={'Reduce Transparency'}
optionChecker={AccessibilityInfo.isReduceTransparencyEnabled}
notification={'reduceTransparencyChanged'}
/>
</>
)}
</View>
);
}
}
function DisplayOptionStatusExample({optionName, optionChecker, notification}) {
const [statusEnabled, setStatusEnabled] = React.useState(false);
React.useEffect(() => {
AccessibilityInfo.addEventListener(notification, setStatusEnabled);
optionChecker().then(isEnabled => {
setStatusEnabled(isEnabled);
});
return function cleanup() {
AccessibilityInfo.removeEventListener(notification, setStatusEnabled);
};
}, [optionChecker, notification]);
return (
<View>
<Text>
{optionName}
{' is '}
{statusEnabled ? 'enabled' : 'disabled'}.
</Text>
</View>
);
}
exports.title = 'Accessibility';
exports.documentationURL = 'https://reactnative.dev/docs/accessibilityinfo';
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 AccessibilityRoleAndStateExample> {
return <AccessibilityRoleAndStateExample />;
},
},
{
title: 'Accessibility action examples',
render(): React.Element<typeof AccessibilityActionsExample> {
return <AccessibilityActionsExample />;
},
},
{
title: 'Slider Accessibility Examples',
render(): React.Element<typeof SliderAccessibilityExample> {
return <SliderAccessibilityExample />;
},
},
{
title: 'Fake Slider Example',
render(): React.Element<typeof FakeSliderExample> {
return <FakeSliderExample />;
},
},
{
title: 'Check if the display options are enabled',
render(): React.Element<typeof DisplayOptionsStatusExample> {
return <DisplayOptionsStatusExample />;
},
},
{
title: 'Check if the screen reader announces',
render(): React.Element<typeof AnnounceForAccessibility> {
return <AnnounceForAccessibility />;
},
},
{
title: 'Check if accessibility is focused',
render(): React.Element<typeof SetAccessibilityFocusExample> {
return <SetAccessibilityFocusExample />;
},
},
{
title: 'Check if these properties are enabled',
render(): React.Element<typeof EnabledExamples> {
return <EnabledExamples />;
},
},
{
title:
'Check if accessibilityState disabled is announced when the screenreader focus moves on the image',
render(): React.Element<typeof Image> {
return (
<Image
accessible={true}
accessibilityLabel="plain local image"
accessibilityState={{disabled: true}}
source={require('../../assets/like.png')}
style={styles.disabledImage}
/>
);
},
},
]; accessibilityBranchLinks.mp4 |
Testing accessibility android examples in pr branchCLICK TO OPEN SOURCECODE
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
'use strict';
const React = require('react');
import RNTesterBlock from '../../components/RNTesterBlock';
import RNTesterPage from '../../components/RNTesterPage';
import {StyleSheet, Text, View, TouchableWithoutFeedback} from 'react-native';
const importantForAccessibilityValues = [
'auto',
'yes',
'no',
'no-hide-descendants',
];
type AccessibilityAndroidExampleState = {
count: number,
backgroundImportantForAcc: number,
forgroundImportantForAcc: number,
};
class AccessibilityAndroidExample extends React.Component<
{},
AccessibilityAndroidExampleState,
> {
state: AccessibilityAndroidExampleState = {
count: 0,
backgroundImportantForAcc: 0,
forgroundImportantForAcc: 0,
};
_addOne = () => {
this.setState({
count: ++this.state.count,
});
};
_changeBackgroundImportantForAcc = () => {
this.setState({
backgroundImportantForAcc: (this.state.backgroundImportantForAcc + 1) % 4,
});
};
_changeForgroundImportantForAcc = () => {
this.setState({
forgroundImportantForAcc: (this.state.forgroundImportantForAcc + 1) % 4,
});
};
render(): React.Node {
return (
<RNTesterPage title={'Accessibility Android APIs'}>
<RNTesterBlock title="LiveRegion">
<TouchableWithoutFeedback onPress={this._addOne}>
<View style={styles.embedded}>
<Text>Click me</Text>
</View>
</TouchableWithoutFeedback>
<View accessibilityLiveRegion="polite">
<Text>Clicked {this.state.count} times</Text>
</View>
</RNTesterBlock>
<RNTesterBlock title="Overlapping views and importantForAccessibility property">
<View style={styles.container}>
<TouchableWithoutFeedback
accessible={true}
accessibilityLabel="First layout"
importantForAccessibility={
importantForAccessibilityValues[
this.state.backgroundImportantForAcc
]
}>
<View accessible={true} style={styles.touchableContainer}>
<Text style={{fontSize: 25}}>Hello</Text>
</View>
</TouchableWithoutFeedback>
<View
style={{
position: 'absolute',
left: 10,
top: 25,
right: 10,
height: 110,
backgroundColor: 'yellow',
opacity: 0.5,
}}
accessible={true}
accessibilityLabel="Second layout"
importantForAccessibility={
importantForAccessibilityValues[
this.state.forgroundImportantForAcc
]
}>
<View accessible={true}>
<Text style={{fontSize: 20}}>world</Text>
</View>
</View>
</View>
<TouchableWithoutFeedback
onPress={this._changeBackgroundImportantForAcc}>
<View style={styles.embedded}>
<Text>
Change importantForAccessibility for background layout.
</Text>
</View>
</TouchableWithoutFeedback>
<View accessible={true}>
<Text>Background layout importantForAccessibility</Text>
<Text>
{
importantForAccessibilityValues[
this.state.backgroundImportantForAcc
]
}
</Text>
</View>
<TouchableWithoutFeedback
onPress={this._changeForgroundImportantForAcc}>
<View style={styles.embedded}>
<Text>
Change importantForAccessibility for forground layout.
</Text>
</View>
</TouchableWithoutFeedback>
<View accessible={true}>
<Text>Forground layout importantForAccessibility</Text>
<Text>
{
importantForAccessibilityValues[
this.state.forgroundImportantForAcc
]
}
</Text>
</View>
</RNTesterBlock>
</RNTesterPage>
);
}
}
const styles = StyleSheet.create({
touchableContainer: {
position: 'absolute',
left: 10,
top: 10,
right: 10,
height: 100,
backgroundColor: 'green',
},
embedded: {
backgroundColor: 'yellow',
padding: 10,
},
container: {
flex: 1,
backgroundColor: 'white',
padding: 10,
height: 150,
},
});
exports.title = 'AccessibilityAndroid';
exports.description = 'Android specific Accessibility APIs.';
exports.examples = [
{
title: 'Accessibility elements',
render(): React.Element<typeof AccessibilityAndroidExample> {
return <AccessibilityAndroidExample />;
},
},
]; CLICK TO OPEN VIDEO TESTS PART 1
accessibilityAndroidPrBranch1.mp4CLICK TO OPEN VIDEO TESTS PART 2 - LINKS
2022-03-04.12-40-56.mp4CLICK TO OPEN VIDEO TESTS PART 3 - LINKS
2022-03-04.12-47-55.mp4 |
TextView onInitializeAccessibilityNodeInfoInternal
|
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as resolved.
This comment was marked as resolved.
retrieve SPANS_START_KEY and SPANS_END_KEY from nodeInfo
Seems that spans variables area already saved after calling setText
List<Integer> starts = extrasIntList(SPANS_START_KEY); starts.get(i), ends.get(i), flags.get(i)); seems to be similar logic implemented in AccessibilityLinks |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
TalkBack focus moves through links IN THE CORRECT ORDER from top to bottom (PR Branch with link.id)Testing with the link.id in AccessibilityLink (discussion) // ID is the reverse of what is expected, since the ClickableSpans are returned in reverse
// order due to being added in reverse order. If we don't do this, focus will move to the
// last link first and move backwards.
//
// If this approach becomes unreliable, we should instead look at their start position and
// order them manually.
link.id = spans.length - 1 - i;
links.add(link); Expected Result:
Links are displayed in the above order in the TalkBack menu Actual Results: RESULT 1 (SUCCESS) - Swiping moves TalkBack focus in the correct order
2022-03-07.09-11-19.mp4RESULT 2 (FAIL) - Links are NOT displayed in the correct order in the TalkBack menu
2022-03-07.09-13-23.mp4 |
TalkBack focus does NOT move through links in the correct order from top to bottom (PR Branch without link.id)Testing without the link.id in AccessibilityLink (discussion) // ID is the reverse of what is expected, since the ClickableSpans are returned in reverse
// order due to being added in reverse order. If we don't do this, focus will move to the
// last link first and move backwards.
//
// If this approach becomes unreliable, we should instead look at their start position and
// order them manually.
// link.id = spans.length - 1 - i;
links.add(link); Expected Result:
Links are displayed in the above order in the TalkBack menu Actual Results: RESULT 1 (FAIL) - Swiping moves TalkBack focus in this wrong order
2022-03-07.08-07-55.mp4RESULT 2 (FAIL) - Links are NOT displayed in the correct order in the TalkBack menu
2022-03-07.08-17-20.mp4 |
Summary: This issue fixes [32004][23]. The Pull Request was previously published by [blavalla][10] with [31757][24]. >This is a follow-up on [D23553222 (https://github.com/facebook/react-native/commit/b352e2da8137452f66717cf1cecb2e72abd727d7)][18], which made links functional by using [Talkback's Links menu][1]. We don't often use this as the sole access point for links due to it being more difficult for users to navigate to and easy for users to miss if they don't listen to the full description, including the hint text that announces that links are available. The Implementation of the functionality consists of: Retrieving the accessibility links and triggering the TalkBack Focus over the Text 1. nested Text components with accessibilityRole link are saved as [ReactClickableSpan][17] instances in Android native [TextView][20] ([more info][19]) 1. If the TextView contains any [ClickableSpans][15] (which are [nested Text][14] components with role link), set a view tag and reset the accessibility delegate. 3. Obtain each link description, start, end, and position relative to the parent Text (id) from the Span as an [AccessibilityLink][16] 4. Use the [AccessibilityLink][16] to display TalkBack focus over the link with the `getVirtualViewAt` method (more [info][13]) Implementing ExploreByTouchHelper to detect touches over links and to display TalkBack rectangle around them. 1. ReactAccessibilityDelegate inherits from [ExploreByTouchHelper][12] 2. If the [ReactTextView][21] has an accessibility delegate, trigger ExploreByTouchHelper method [dispatchHoverEvent][22] 3. Implements the methods `getVirtualViewAt` and `onPopulateBoundsForVirtualView`. The two methods implements the following functionalities (more [info][13]): * detecting the TalkBack onPress/focus on nested Text with accessibilityRole="link" * displaying TalkBack rectangle around nested Text with accessibilityRole="link" ## Changelog [Android] [Added] - Make links independently focusable by Talkback Pull Request resolved: #33215 Test Plan: [1]. User Interacts with links through TalkBack default accessibility menu ([link][1]) [2]. The nested link becomes the next focusable element after the parent element that contains it. ([link][2]) [3]. Testing accessibility examples in pr branch ([link][3]) [4]. Testing accessibility android examples in pr branch ([link][4]) [7]. TalkBack focus moves through links in the correct order from top to bottom (PR Branch with [link.id][25]) ([link to video test][7]) ([discussion][26]) [8]. TalkBack focus does not move through links in the correct order from top to bottom (PR Branch without [link.id][25]) ([link to video test][8]) ([discussion][26]) Test on main branch [5]. Testing accessibility examples in main branch ([link][5]) [6]. Testing accessibility android examples in main branch ([link][6]) [1]: fabOnReact/react-native-notes#9 (comment) [2]: fabOnReact/react-native-notes#9 (comment) [3]: fabOnReact/react-native-notes#9 (comment) [4]: fabOnReact/react-native-notes#9 (comment) [5]: fabOnReact/react-native-notes#9 (comment) [6]: fabOnReact/react-native-notes#9 (comment) [7]: fabOnReact/react-native-notes#9 (comment) [8]: fabOnReact/react-native-notes#9 (comment) [10]: https://github.com/blavalla "blavalla github profile" [12]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/com/android/internal/widget/ExploreByTouchHelper.java#L48 "com/android/internal/widget/ExploreByTouchHelper.java#L48" [13]: fabOnReact/react-native-notes#9 (comment) "explanation of getVirtualViewAt and onPopulateBoundsForVirtualView" [14]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/android/text/Spannable.java#L3 "core/java/android/text/Spannable.java#L3" [15]: https://github.com/fabriziobertoglio1987/react-native/blob/561266fc180b96d6337d6c6c5c3323522d66cc44/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java#L70-L71 "react/views/text/ReactTextViewManager.java#L70-L71" [16]: https://github.com/fabriziobertoglio1987/react-native/blob/561266fc180b96d6337d6c6c5c3323522d66cc44/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java#L680-L685 "react/uimanager/ReactAccessibilityDelegate.java#L680-L685" [17]: https://github.com/facebook/react-native/blob/561266fc180b96d6337d6c6c5c3323522d66cc44/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java#L126-L129 "react/views/text/TextLayoutManager.java#L126-L129" [18]: b352e2d [19]: #30375 (comment) "explanation on how nested Text are converted to Android Spans" [20]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/android/widget/TextView.java#L214-L220 "core/java/android/widget/TextView.java#L214-L220" [21]: https://github.com/facebook/react-native/blob/485cf6118b0ab0b59e078b96701b69ae64c4dfb7/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java#L577 "dispatchHoverEvent in ReactTextView" [22]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/com/android/internal/widget/ExploreByTouchHelper.java#L120-L138 "dispatchHoverEvent in ExploreByTouchHelper" [23]: #32004 [24]: #31757 [25]: https://github.com/fabriziobertoglio1987/react-native/blob/485cf6118b0ab0b59e078b96701b69ae64c4dfb7/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java#L648 "setting link.id in the AccessibilityLink constructor" [26]: https://github.com/facebook/react-native/pull/33215/files/485cf6118b0ab0b59e078b96701b69ae64c4dfb7#r820014411 "comment on role of link.id" Reviewed By: blavalla Differential Revision: D34687371 Pulled By: philIip fbshipit-source-id: 8e63c70e9318ad8d27317bd68497705e595dea0f
Summary: This issue fixes [32004][23]. The Pull Request was previously published by [blavalla][10] with [31757][24]. >This is a follow-up on [D23553222 (https://github.com/facebook/react-native/commit/b352e2da8137452f66717cf1cecb2e72abd727d7)][18], which made links functional by using [Talkback's Links menu][1]. We don't often use this as the sole access point for links due to it being more difficult for users to navigate to and easy for users to miss if they don't listen to the full description, including the hint text that announces that links are available. The Implementation of the functionality consists of: Retrieving the accessibility links and triggering the TalkBack Focus over the Text 1. nested Text components with accessibilityRole link are saved as [ReactClickableSpan][17] instances in Android native [TextView][20] ([more info][19]) 1. If the TextView contains any [ClickableSpans][15] (which are [nested Text][14] components with role link), set a view tag and reset the accessibility delegate. 3. Obtain each link description, start, end, and position relative to the parent Text (id) from the Span as an [AccessibilityLink][16] 4. Use the [AccessibilityLink][16] to display TalkBack focus over the link with the `getVirtualViewAt` method (more [info][13]) Implementing ExploreByTouchHelper to detect touches over links and to display TalkBack rectangle around them. 1. ReactAccessibilityDelegate inherits from [ExploreByTouchHelper][12] 2. If the [ReactTextView][21] has an accessibility delegate, trigger ExploreByTouchHelper method [dispatchHoverEvent][22] 3. Implements the methods `getVirtualViewAt` and `onPopulateBoundsForVirtualView`. The two methods implements the following functionalities (more [info][13]): * detecting the TalkBack onPress/focus on nested Text with accessibilityRole="link" * displaying TalkBack rectangle around nested Text with accessibilityRole="link" ## Changelog [Android] [Added] - Make links independently focusable by Talkback Pull Request resolved: facebook#33215 Test Plan: [1]. User Interacts with links through TalkBack default accessibility menu ([link][1]) [2]. The nested link becomes the next focusable element after the parent element that contains it. ([link][2]) [3]. Testing accessibility examples in pr branch ([link][3]) [4]. Testing accessibility android examples in pr branch ([link][4]) [7]. TalkBack focus moves through links in the correct order from top to bottom (PR Branch with [link.id][25]) ([link to video test][7]) ([discussion][26]) [8]. TalkBack focus does not move through links in the correct order from top to bottom (PR Branch without [link.id][25]) ([link to video test][8]) ([discussion][26]) Test on main branch [5]. Testing accessibility examples in main branch ([link][5]) [6]. Testing accessibility android examples in main branch ([link][6]) [1]: fabOnReact/react-native-notes#9 (comment) [2]: fabOnReact/react-native-notes#9 (comment) [3]: fabOnReact/react-native-notes#9 (comment) [4]: fabOnReact/react-native-notes#9 (comment) [5]: fabOnReact/react-native-notes#9 (comment) [6]: fabOnReact/react-native-notes#9 (comment) [7]: fabOnReact/react-native-notes#9 (comment) [8]: fabOnReact/react-native-notes#9 (comment) [10]: https://github.com/blavalla "blavalla github profile" [12]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/com/android/internal/widget/ExploreByTouchHelper.java#L48 "com/android/internal/widget/ExploreByTouchHelper.java#L48" [13]: fabOnReact/react-native-notes#9 (comment) "explanation of getVirtualViewAt and onPopulateBoundsForVirtualView" [14]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/android/text/Spannable.java#L3 "core/java/android/text/Spannable.java#L3" [15]: https://github.com/fabriziobertoglio1987/react-native/blob/561266fc180b96d6337d6c6c5c3323522d66cc44/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java#L70-L71 "react/views/text/ReactTextViewManager.java#L70-L71" [16]: https://github.com/fabriziobertoglio1987/react-native/blob/561266fc180b96d6337d6c6c5c3323522d66cc44/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java#L680-L685 "react/uimanager/ReactAccessibilityDelegate.java#L680-L685" [17]: https://github.com/facebook/react-native/blob/561266fc180b96d6337d6c6c5c3323522d66cc44/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java#L126-L129 "react/views/text/TextLayoutManager.java#L126-L129" [18]: facebook@b352e2d [19]: facebook#30375 (comment) "explanation on how nested Text are converted to Android Spans" [20]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/android/widget/TextView.java#L214-L220 "core/java/android/widget/TextView.java#L214-L220" [21]: https://github.com/facebook/react-native/blob/485cf6118b0ab0b59e078b96701b69ae64c4dfb7/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java#L577 "dispatchHoverEvent in ReactTextView" [22]: https://github.com/aosp-mirror/platform_frameworks_base/blob/1ac46f932ef88a8f96d652580d8105e361ffc842/core/java/com/android/internal/widget/ExploreByTouchHelper.java#L120-L138 "dispatchHoverEvent in ExploreByTouchHelper" [23]: facebook#32004 [24]: facebook#31757 [25]: https://github.com/fabriziobertoglio1987/react-native/blob/485cf6118b0ab0b59e078b96701b69ae64c4dfb7/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java#L648 "setting link.id in the AccessibilityLink constructor" [26]: https://github.com/facebook/react-native/pull/33215/files/485cf6118b0ab0b59e078b96701b69ae64c4dfb7#r820014411 "comment on role of link.id" Reviewed By: blavalla Differential Revision: D34687371 Pulled By: philIip fbshipit-source-id: 8e63c70e9318ad8d27317bd68497705e595dea0f
new branch https://github.com/fabriziobertoglio1987/react-native/tree/independent-links-rebased
old branch https://github.com/fabriziobertoglio1987/react-native/tree/independent-links
facebook/react-native@b352e2d
facebook/react-native#31757
The PR allows TalkBack to move the focus directly on the nested Text link (
accessibilityRole="link"
).The nested link becomes the next focusable element after the that contains it.
If there are multiple links within a single text element, they will each be focusable in order after the main element.
Related facebook/react-native#30375 https://developer.android.com/reference/android/text/style/ClickableSpan https://developer.android.com/reference/androidx/core/view/ViewCompat#enableAccessibleClickableSpanSupport(android.view.View) https://stackoverflow.com/a/62222068/7295772 facebook/react-native#32004
The text was updated successfully, but these errors were encountered: