Skip to content

Commit d386d7a

Browse files
author
Marc Mulcahy
committed
Add accessibilityValueDescription support.
1 parent 08daad4 commit d386d7a

File tree

14 files changed

+262
-3
lines changed

14 files changed

+262
-3
lines changed

Libraries/Components/View/ReactNativeViewAttributes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const UIView = {
2121
accessibilityRole: true,
2222
accessibilityStates: true,
2323
accessibilityState: true,
24+
accessibilityValueDescription: true,
2425
accessibilityHint: true,
2526
importantForAccessibility: true,
2627
nativeID: true,

Libraries/Components/View/ReactNativeViewViewConfig.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ const ReactNativeViewConfig = {
123123
accessibilityRole: true,
124124
accessibilityStates: true,
125125
accessibilityState: true,
126+
accessibilityValueDescription: true,
126127
accessibilityViewIsModal: true,
127128
accessible: true,
128129
alignContent: true,

Libraries/Components/View/ViewAccessibility.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,25 @@ export type AccessibilityState = {
7474
busy?: boolean,
7575
expanded?: boolean,
7676
};
77+
78+
export type AccessibilityValueDescription = {
79+
/**
80+
* The minimum value of this component's range. (should be an integer)
81+
*/
82+
minimum?: number,
83+
84+
/**
85+
* The current value of this component's range. (should be an integer)
86+
*/
87+
current?: number,
88+
89+
/**
90+
* The maximum value of this component's range. (should be an integer)
91+
*/
92+
maximum?: number,
93+
94+
/**
95+
* A textual description of this component's value. (will override minimum, current, and maximum if set)
96+
*/
97+
text?: string,
98+
};

Libraries/Components/View/ViewPropTypes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
AccessibilityRole,
2020
AccessibilityStates,
2121
AccessibilityState,
22+
AccessibilityValueDescription,
2223
AccessibilityActionEvent,
2324
AccessibilityActionInfo,
2425
} from './ViewAccessibility';
@@ -415,6 +416,7 @@ export type ViewProps = $ReadOnly<{|
415416
*/
416417
accessibilityStates?: ?AccessibilityStates,
417418
accessibilityState?: ?AccessibilityState,
419+
accessibilityValueDescription?: ?AccessibilityValueDescription,
418420

419421
/**
420422
* Provides an array of custom actions available for accessibility.

Libraries/DeprecatedPropTypes/DeprecatedViewPropTypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ module.exports = {
122122
>,
123123
>),
124124
accessibilityState: PropTypes.object,
125+
accessibilityValueDescription: PropTypes.object,
125126
/**
126127
* Indicates to accessibility services whether the user should be notified
127128
* when this view changes. Works for Android API >= 19 only.

RNTester/js/examples/Accessibility/AccessibilityExample.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,89 @@ class AccessibilityActionsExample extends React.Component {
512512
);
513513
}
514514
}
515+
516+
class FakeSliderExample extends React.Component {
517+
state = {
518+
current: 50,
519+
textualValue: 'center',
520+
};
521+
522+
increment = () => {
523+
let newValue = this.state.current + 2;
524+
if (newValue > 100) {
525+
newValue = 100;
526+
}
527+
this.setState({
528+
current: newValue,
529+
});
530+
};
531+
532+
decrement = () => {
533+
let newValue = this.state.current - 2;
534+
if (newValue < 0) {
535+
newValue = 0;
536+
}
537+
this.setState({
538+
current: newValue,
539+
});
540+
};
541+
542+
render() {
543+
return (
544+
<View>
545+
<View
546+
accessible={true}
547+
accessibilityLabel="Fake Slider"
548+
accessibilityRole="adjustable"
549+
accessibilityActions={[{name: 'increment'}, {name: 'decrement'}]}
550+
onAccessibilityAction={event => {
551+
switch (event.nativeEvent.actionName) {
552+
case 'increment':
553+
this.increment();
554+
break;
555+
case 'decrement':
556+
this.decrement();
557+
break;
558+
}
559+
}}
560+
accessibilityValueDescription={{
561+
minimum: 0,
562+
current: this.state.current,
563+
maximum: 100,
564+
}}>
565+
<Text>Fake Slider</Text>
566+
</View>
567+
<View
568+
accessible={true}
569+
accessibilityLabel="Equalizer"
570+
accessibilityRole="adjustable"
571+
accessibilityActions={[{name: 'increment'}, {name: 'decrement'}]}
572+
onAccessibilityAction={event => {
573+
switch (event.nativeEvent.actionName) {
574+
case 'increment':
575+
if (this.state.textualValue === 'center') {
576+
this.setState({textualValue: 'right'});
577+
} else if (this.state.textualValue === 'left') {
578+
this.setState({textualValue: 'center'});
579+
}
580+
break;
581+
case 'decrement':
582+
if (this.state.textualValue === 'center') {
583+
this.setState({textualValue: 'left'});
584+
} else if (this.state.textualValue === 'right') {
585+
this.setState({textualValue: 'center'});
586+
}
587+
break;
588+
}
589+
}}
590+
accessibilityValueDescription={{text: this.state.textualValue}}>
591+
<Text>Equalizer</Text>
592+
</View>
593+
</View>
594+
);
595+
}
596+
}
597+
515598
class ScreenReaderStatusExample extends React.Component<{}> {
516599
state = {
517600
screenReaderEnabled: false,
@@ -591,6 +674,12 @@ exports.examples = [
591674
return <AccessibilityActionsExample />;
592675
},
593676
},
677+
{
678+
title: 'Fake Slider Example',
679+
render(): React.Element<typeof FakeSliderExample> {
680+
return <FakeSliderExample />;
681+
},
682+
},
594683
{
595684
title: 'Check if the screen reader is enabled',
596685
render(): React.Element<typeof ScreenReaderStatusExample> {

React/Views/RCTView.m

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,26 @@ - (NSString *)accessibilityValue
284284
[valueComponents addObject:stateDescriptions[@"busy"]];
285285
}
286286
}
287+
288+
// handle accessibilityValueDescription
289+
290+
if (self.accessibilityValueDescription) {
291+
id minimum = self.accessibilityValueDescription[@"minimum"];
292+
id current = self.accessibilityValueDescription[@"current"];
293+
id maximum = self.accessibilityValueDescription[@"maximum"];
294+
id text = self.accessibilityValueDescription[@"text"];
295+
if (text && [text isKindOfClass:[NSString class]]) {
296+
[valueComponents addObject:text];
297+
} else if ([minimum isKindOfClass:[NSNumber class]] &&
298+
[current isKindOfClass:[NSNumber class]] &&
299+
[maximum isKindOfClass:[NSNumber class]] &&
300+
([minimum intValue] < [maximum intValue]) &&
301+
([minimum intValue] <= [current intValue] && [current intValue] <= [maximum intValue])) {
302+
int val = ([current intValue]*100)/([maximum intValue]-[minimum intValue]);
303+
[valueComponents addObject:[NSString stringWithFormat:@"%d percent", val]];
304+
}
305+
}
306+
287307
if (valueComponents.count > 0) {
288308
return [valueComponents componentsJoinedByString:@", "];
289309
}

React/Views/RCTViewManager.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ - (RCTShadowView *)shadowView
126126
RCT_REMAP_VIEW_PROPERTY(accessibilityActions, reactAccessibilityElement.accessibilityActions, NSDictionaryArray)
127127
RCT_REMAP_VIEW_PROPERTY(accessibilityLabel, reactAccessibilityElement.accessibilityLabel, NSString)
128128
RCT_REMAP_VIEW_PROPERTY(accessibilityHint, reactAccessibilityElement.accessibilityHint, NSString)
129+
RCT_REMAP_VIEW_PROPERTY(accessibilityValueDescription, reactAccessibilityElement.accessibilityValueDescription, NSDictionary)
129130
RCT_REMAP_VIEW_PROPERTY(accessibilityViewIsModal, reactAccessibilityElement.accessibilityViewIsModal, BOOL)
130131
RCT_REMAP_VIEW_PROPERTY(accessibilityElementsHidden, reactAccessibilityElement.accessibilityElementsHidden, BOOL)
131132
RCT_REMAP_VIEW_PROPERTY(accessibilityIgnoresInvertColors, reactAccessibilityElement.shouldAccessibilityIgnoresInvertColors, BOOL)

React/Views/UIView+React.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
@property (nonatomic, copy) NSArray <NSString *> *accessibilityStates;
121121
@property (nonatomic, copy) NSDictionary<NSString *, id> *accessibilityState;
122122
@property (nonatomic, copy) NSArray <NSDictionary *> *accessibilityActions;
123+
@property (nonatomic, copy) NSDictionary *accessibilityValueDescription;
123124

124125
/**
125126
* Used in debugging to get a description of the view hierarchy rooted at

React/Views/UIView+React.m

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,17 @@ - (void)setAccessibilityState:(NSDictionary<NSString *, id> *)accessibilityState
337337
objc_setAssociatedObject(self, @selector(accessibilityState), accessibilityState, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
338338
}
339339

340-
#pragma mark - Debug
340+
- (NSDictionary<NSString *, id> *)accessibilityValueDescription
341+
{
342+
return objc_getAssociatedObject(self, _cmd);
343+
}
341344

345+
- (void)setAccessibilityValueDescription:(NSDictionary<NSString *, id> *)accessibilityValueDescription
346+
{
347+
objc_setAssociatedObject(self, @selector(accessibilityValueDescription), accessibilityValueDescription, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
348+
}
349+
350+
#pragma mark - Debug
342351
- (void)react_addRecursiveDescriptionToString:(NSMutableString *)string atLevel:(NSUInteger)level
343352
{
344353
for (NSUInteger i = 0; i < level; i++) {

ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ private void updateViewContentDescription(@NonNull T view) {
194194
final ReadableMap accessibilityState = (ReadableMap) view.getTag(R.id.accessibility_state);
195195
final String accessibilityHint = (String) view.getTag(R.id.accessibility_hint);
196196
final List<String> contentDescription = new ArrayList<>();
197+
final ReadableMap accessibilityValueDescription = (ReadableMap) view.getTag(R.id.accessibility_value_description);
197198
if (accessibilityLabel != null) {
198199
contentDescription.add(accessibilityLabel);
199200
}
@@ -228,6 +229,12 @@ private void updateViewContentDescription(@NonNull T view) {
228229
}
229230
}
230231
}
232+
if (accessibilityValueDescription != null && accessibilityValueDescription.hasKey("text")) {
233+
final Dynamic text = accessibilityValueDescription.getDynamic("text");
234+
if (text != null && text.getType() == ReadableType.String) {
235+
contentDescription.add(text.asString());
236+
}
237+
}
231238
if (accessibilityHint != null) {
232239
contentDescription.add(accessibilityHint);
233240
}
@@ -245,6 +252,18 @@ public void setAccessibilityActions(T view, ReadableArray accessibilityActions)
245252
view.setTag(R.id.accessibility_actions, accessibilityActions);
246253
}
247254

255+
@ReactProp(name = ViewProps.ACCESSIBILITY_VALUE_DESCRIPTION)
256+
public void setAccessibilityValueDescription(T view, ReadableMap accessibilityValueDescription) {
257+
if (accessibilityValueDescription == null) {
258+
return;
259+
}
260+
261+
view.setTag(R.id.accessibility_value_description, accessibilityValueDescription);
262+
if (accessibilityValueDescription.hasKey("text")) {
263+
updateViewContentDescription(view);
264+
}
265+
}
266+
248267
@ReactProp(name = ViewProps.IMPORTANT_FOR_ACCESSIBILITY)
249268
public void setImportantForAccessibility(
250269
@NonNull T view, @Nullable String importantForAccessibility) {

0 commit comments

Comments
 (0)