Skip to content

New Accessibility states API. #24608

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

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Libraries/Components/TextInput/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,7 @@ const TextInput = createReactClass({
accessibilityLabel={props.accessibilityLabel}
accessibilityRole={props.accessibilityRole}
accessibilityStates={props.accessibilityStates}
accessibilityState={props.accessibilityState}
nativeID={this.props.nativeID}
testID={props.testID}>
{textContainer}
Expand Down Expand Up @@ -1156,6 +1157,7 @@ const TextInput = createReactClass({
accessibilityLabel={props.accessibilityLabel}
accessibilityRole={props.accessibilityRole}
accessibilityStates={props.accessibilityStates}
accessibilityState={props.accessibilityState}
nativeID={this.props.nativeID}
testID={props.testID}>
{textContainer}
Expand Down Expand Up @@ -1213,6 +1215,7 @@ const TextInput = createReactClass({
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole}
accessibilityStates={this.props.accessibilityStates}
accessibilityState={this.props.accessibilityState}
nativeID={this.props.nativeID}
testID={this.props.testID}>
{textContainer}
Expand Down
1 change: 1 addition & 0 deletions Libraries/Components/Touchable/TouchableBounce.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ const TouchableBounce = ((createReactClass({
accessibilityHint={this.props.accessibilityHint}
accessibilityRole={this.props.accessibilityRole}
accessibilityStates={this.props.accessibilityStates}
accessibilityState={this.props.accessibilityState}
nativeID={this.props.nativeID}
testID={this.props.testID}
hitSlop={this.props.hitSlop}
Expand Down
1 change: 1 addition & 0 deletions Libraries/Components/Touchable/TouchableHighlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ const TouchableHighlight = ((createReactClass({
accessibilityHint={this.props.accessibilityHint}
accessibilityRole={this.props.accessibilityRole}
accessibilityStates={this.props.accessibilityStates}
accessibilityState={this.props.accessibilityState}
style={StyleSheet.compose(
this.props.style,
this.state.extraUnderlayStyle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ const TouchableNativeFeedback = createReactClass({
accessibilityLabel: this.props.accessibilityLabel,
accessibilityRole: this.props.accessibilityRole,
accessibilityStates: this.props.accessibilityStates,
accessibilityState: this.props.accessibilityState,
children,
testID: this.props.testID,
onLayout: this.props.onLayout,
Expand Down
1 change: 1 addition & 0 deletions Libraries/Components/Touchable/TouchableOpacity.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ const TouchableOpacity = ((createReactClass({
accessibilityHint={this.props.accessibilityHint}
accessibilityRole={this.props.accessibilityRole}
accessibilityStates={this.props.accessibilityStates}
accessibilityState={this.props.accessibilityState}
style={[this.props.style, {opacity: this.state.anim}]}
nativeID={this.props.nativeID}
testID={this.props.testID}
Expand Down
9 changes: 5 additions & 4 deletions Libraries/Components/Touchable/TouchableWithoutFeedback.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const ensurePositiveDelayProps = require('./ensurePositiveDelayProps');

const {
DeprecatedAccessibilityRoles,
DeprecatedAccessibilityStates,
} = require('../../DeprecatedPropTypes/DeprecatedViewAccessibility');

import type {
Expand All @@ -33,6 +32,7 @@ import type {EdgeInsetsProp} from '../../StyleSheet/EdgeInsetsPropType';
import type {
AccessibilityRole,
AccessibilityStates,
AccessibilityState,
} from '../View/ViewAccessibility';

type TargetEvent = SyntheticEvent<
Expand All @@ -52,6 +52,7 @@ const OVERRIDE_PROPS = [
'accessibilityIgnoresInvertColors',
'accessibilityRole',
'accessibilityStates',
'accessibilityState',
'hitSlop',
'nativeID',
'onBlur',
Expand All @@ -67,6 +68,7 @@ export type Props = $ReadOnly<{|
accessibilityIgnoresInvertColors?: ?boolean,
accessibilityRole?: ?AccessibilityRole,
accessibilityStates?: ?AccessibilityStates,
accessibilityState?: ?AccessibilityState,
children?: ?React.Node,
delayLongPress?: ?number,
delayPressIn?: ?number,
Expand Down Expand Up @@ -104,9 +106,8 @@ const TouchableWithoutFeedback = ((createReactClass({
accessibilityHint: PropTypes.string,
accessibilityIgnoresInvertColors: PropTypes.bool,
accessibilityRole: PropTypes.oneOf(DeprecatedAccessibilityRoles),
accessibilityStates: PropTypes.arrayOf(
PropTypes.oneOf(DeprecatedAccessibilityStates),
),
accessibilityStates: PropTypes.array,
accessibilityState: PropTypes.object,
/**
* When `accessible` is true (which is the default) this may be called when
* the OS-specific concept of "focus" occurs. Some platforms may not have
Expand Down
1 change: 1 addition & 0 deletions Libraries/Components/View/ReactNativeViewAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ ReactNativeViewAttributes.UIView = {
accessibilityLiveRegion: true,
accessibilityRole: true,
accessibilityStates: true,
accessibilityState: true,
accessibilityHint: true,
importantForAccessibility: true,
nativeID: true,
Expand Down
1 change: 1 addition & 0 deletions Libraries/Components/View/ReactNativeViewViewConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ const ReactNativeViewConfig = {
accessibilityLiveRegion: true,
accessibilityRole: true,
accessibilityStates: true,
accessibilityState: true,
accessibilityViewIsModal: true,
accessible: true,
alignContent: true,
Expand Down
8 changes: 8 additions & 0 deletions Libraries/Components/View/ViewAccessibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,11 @@ export type AccessibilityActionEvent = SyntheticEvent<
actionName: string,
}>,
>;

export type AccessibilityState = {
disabled?: boolean,
selected?: boolean,
checked?: ?boolean | 'mixed',
busy?: boolean,
expanded?: boolean,
};
2 changes: 2 additions & 0 deletions Libraries/Components/View/ViewPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {TVViewProps} from '../AppleTV/TVViewPropTypes';
import type {
AccessibilityRole,
AccessibilityStates,
AccessibilityState,
AccessibilityActionEvent,
AccessibilityActionInfo,
} from './ViewAccessibility';
Expand Down Expand Up @@ -413,6 +414,7 @@ export type ViewProps = $ReadOnly<{|
* Indicates to accessibility services that UI Component is in a specific State.
*/
accessibilityStates?: ?AccessibilityStates,
accessibilityState?: ?AccessibilityState,

/**
* Provides an array of custom actions available for accessibility.
Expand Down
1 change: 1 addition & 0 deletions Libraries/DeprecatedPropTypes/DeprecatedViewPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ module.exports = {
accessibilityStates: PropTypes.arrayOf(
PropTypes.oneOf(DeprecatedAccessibilityStates),
),
accessibilityState: PropTypes.object,
/**
* Indicates to accessibility services whether the user should be notified
* when this view changes. Works for Android API >= 19 only.
Expand Down
2 changes: 2 additions & 0 deletions Libraries/Text/TextProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {TextStyleProp} from '../StyleSheet/StyleSheet';
import type {
AccessibilityRole,
AccessibilityStates,
AccessibilityState,
} from '../Components/View/ViewAccessibility';

export type PressRetentionOffset = $ReadOnly<{|
Expand All @@ -43,6 +44,7 @@ export type TextProps = $ReadOnly<{|
accessibilityLabel?: ?Stringish,
accessibilityRole?: ?AccessibilityRole,
accessibilityStates?: ?AccessibilityStates,
accessibilityState?: ?AccessibilityState,

/**
* Whether font should be scaled down automatically.
Expand Down
52 changes: 29 additions & 23 deletions RNTester/js/AccessibilityExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class AccessibilityExample extends React.Component {
<TouchableOpacity
onPress={() => Alert.alert('Button has been pressed!')}
accessibilityRole="button"
accessibilityStates={['disabled']}
accessibilityState={{disabled: true}}
disabled={true}>
<View>
<Text>
Expand All @@ -122,7 +122,7 @@ class AccessibilityExample extends React.Component {
<RNTesterBlock title="View with multiple states">
<View
accessible={true}
accessibilityStates={['selected', 'disabled']}>
accessibilityState={{selected: true, disabled: true}}>
<Text>This view is selected and disabled.</Text>
</View>
</RNTesterBlock>
Expand All @@ -132,7 +132,7 @@ class AccessibilityExample extends React.Component {
accessible={true}
accessibilityLabel="Accessibility label."
accessibilityRole="button"
accessibilityStates={['selected']}
accessibilityState={{selected: true}}
accessibilityHint="Accessibility hint.">
<Text>Accessible view with label, hint, role, and state</Text>
</View>
Expand All @@ -144,12 +144,18 @@ class AccessibilityExample extends React.Component {

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

_onCheckboxPress = () => {
const checkboxState =
this.state.checkboxState === 'checked' ? 'unchecked' : 'checked';
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,
Expand All @@ -169,7 +175,7 @@ class CheckboxExample extends React.Component {
onPress={this._onCheckboxPress}
accessibilityLabel="element 2"
accessibilityRole="checkbox"
accessibilityStates={[this.state.checkboxState]}
accessibilityState={{checked: this.state.checkboxState}}
accessibilityHint="click me to change state">
<Text>Checkbox example</Text>
</TouchableOpacity>
Expand All @@ -179,12 +185,11 @@ class CheckboxExample extends React.Component {

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

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

this.setState({
switchState: switchState,
Expand All @@ -204,7 +209,7 @@ class SwitchExample extends React.Component {
onPress={this._onSwitchToggle}
accessibilityLabel="element 12"
accessibilityRole="switch"
accessibilityStates={[this.state.switchState]}
accessibilityState={{checked: this.state.switchState}}
accessible={true}>
<Text>Switch example</Text>
</TouchableOpacity>
Expand All @@ -224,14 +229,11 @@ class SelectionExample extends React.Component {
};

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
Expand All @@ -244,9 +246,11 @@ class SelectionExample extends React.Component {
ref={this.selectableElement}
accessible={true}
onPress={() => {
this.setState({
isSelected: !this.state.isSelected,
});
if (this.state.isEnabled) {
this.setState({
isSelected: !this.state.isSelected,
});
}

if (Platform.OS === 'android') {
UIManager.sendAccessibilityEvent(
Expand All @@ -256,7 +260,10 @@ class SelectionExample extends React.Component {
}
}}
accessibilityLabel="element 19"
accessibilityStates={accessibilityStates}
accessibilityState={{
selected: this.state.isSelected,
disabled: !this.state.isEnabled,
}}
accessibilityHint={accessibilityHint}>
<Text>Selectable element example</Text>
</TouchableOpacity>
Expand All @@ -275,12 +282,11 @@ class SelectionExample extends React.Component {

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

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

this.setState({
expandState: expandState,
Expand All @@ -299,7 +305,7 @@ class ExpandableElementExample extends React.Component {
<TouchableOpacity
onPress={this._onElementPress}
accessibilityLabel="element 18"
accessibilityStates={[this.state.expandState]}
accessibilityState={{expanded: this.state.expandState}}
accessibilityHint="click me to change state">
<Text>Expandable element example</Text>
</TouchableOpacity>
Expand Down Expand Up @@ -399,7 +405,7 @@ class AccessibilityRoleAndStateExample extends React.Component<{}> {
</View>
<View
accessibilityLabel="element 17"
accessibilityStates={['busy']}
accessibilityState={{busy: true}}
accessible={true}>
<Text>State busy example</Text>
</View>
Expand Down
31 changes: 30 additions & 1 deletion React/Views/RCTView.m
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ - (NSString *)accessibilityValue
return @"0";
}
}
for (NSString *state in self.accessibilityState) {
id val = self.accessibilityState[state];
if (!val) {
continue;
}
if ([state isEqualToString:@"checked"] && [val isKindOfClass:[NSNumber class]]) {
return [val boolValue] ? @"1" : @"0";
}
}
}
NSMutableArray *valueComponents = [NSMutableArray new];
static NSDictionary<NSString *, NSString *> *roleDescriptions = nil;
Expand Down Expand Up @@ -255,6 +264,7 @@ - (NSString *)accessibilityValue
@"busy" : @"busy",
@"expanded" : @"expanded",
@"collapsed" : @"collapsed",
@"mixed": @"mixed",
};
});
NSString *roleDescription = self.accessibilityRole ? roleDescriptions[self.accessibilityRole]: nil;
Expand All @@ -267,8 +277,27 @@ - (NSString *)accessibilityValue
[valueComponents addObject:stateDescription];
}
}
for (NSString *state in self.accessibilityState) {
id val = self.accessibilityState[state];
if (!val) {
continue;
}
if ([state isEqualToString:@"checked"]) {
if ([val isKindOfClass:[NSNumber class]]) {
[valueComponents addObject:stateDescriptions[[val boolValue] ? @"checked" : @"unchecked"]];
} else if ([val isKindOfClass:[NSString class]] && [val isEqualToString:@"mixed"]) {
[valueComponents addObject:stateDescriptions[@"mixed"]];
}
}
if ([state isEqualToString:@"expanded"] && [val isKindOfClass:[NSNumber class]]) {
[valueComponents addObject:stateDescriptions[[val boolValue] ? @"expanded" : @"collapsed"]];
}
if ([state isEqualToString:@"busy"] && [val isKindOfClass:[NSNumber class]] && [val boolValue]) {
[valueComponents addObject:stateDescriptions[@"busy"]];
}
}
if (valueComponents.count > 0) {
return [valueComponents componentsJoinedByString:@", "];
return [valueComponents componentsJoinedByString:@", "];
}
return nil;
}
Expand Down
Loading