Skip to content

Commit fa50c08

Browse files
author
williamdeng
committed
7df3eea|Facebook Github Bot|Add accessibilityValueDescription support. (facebook#26169)
1 parent 7593b0a commit fa50c08

File tree

14 files changed

+313
-26
lines changed

14 files changed

+313
-26
lines changed

Libraries/Components/View/ReactNativeViewAttributes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ ReactNativeViewAttributes.UIView = {
2323
accessibilityLiveRegion: true,
2424
accessibilityRole: true,
2525
accessibilityState: true,
26+
accessibilityValue: true,
2627
importantForAccessibility: true,
2728
nativeID: true,
2829
testID: true,

Libraries/Components/View/ViewAccessibility.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,25 @@ export type AccessibilityState = {
6363
busy?: boolean,
6464
expanded?: boolean,
6565
};
66+
67+
export type AccessibilityValue = $ReadOnly<{|
68+
/**
69+
* The minimum value of this component's range. (should be an integer)
70+
*/
71+
min?: number,
72+
73+
/**
74+
* The maximum value of this component's range. (should be an integer)
75+
*/
76+
max?: number,
77+
78+
/**
79+
* The current value of this component's range. (should be an integer)
80+
*/
81+
now?: number,
82+
83+
/**
84+
* A textual description of this component's value. (will override minimum, current, and maximum if set)
85+
*/
86+
text?: string,
87+
|}>;

Libraries/Components/View/ViewPropTypes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type {TVViewProps} from 'TVViewPropTypes';
2121
import type {
2222
AccessibilityRole,
2323
AccessibilityState,
24+
AccessibilityValue,
2425
AccessibilityActionEvent,
2526
AccessibilityActionInfo,
2627
} from './ViewAccessibility';
@@ -418,6 +419,7 @@ export type ViewProps = $ReadOnly<{|
418419
* Indicates to accessibility services that UI Component is in a specific State.
419420
*/
420421
accessibilityState?: ?AccessibilityState,
422+
accessibilityValue?: ?AccessibilityValue,
421423

422424
/**
423425
* 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
@@ -60,6 +60,7 @@ module.exports = {
6060
accessibilityRole: PropTypes.oneOf(DeprecatedAccessibilityRoles),
6161

6262
accessibilityState: PropTypes.object,
63+
accessibilityValue: PropTypes.object,
6364
/**
6465
* Indicates to accessibility services whether the user should be notified
6566
* when this view changes. Works for Android API >= 19 only.

RNTester/js/AccessibilityExample.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,88 @@ class AccessibilityActionsExample extends React.Component {
513513
}
514514
}
515515

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+
accessibilityValue={{
561+
min: 0,
562+
now: this.state.current,
563+
max: 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+
accessibilityValue={{text: this.state.textualValue}}>
591+
<Text>Equalizer</Text>
592+
</View>
593+
</View>
594+
);
595+
}
596+
}
597+
516598
class ScreenReaderStatusExample extends React.Component<{}> {
517599
state = {
518600
screenReaderEnabled: false,
@@ -592,6 +674,12 @@ exports.examples = [
592674
return <AccessibilityActionsExample />;
593675
},
594676
},
677+
{
678+
title: 'Fake Slider Example',
679+
render(): React.Element<typeof FakeSliderExample> {
680+
return <FakeSliderExample />;
681+
},
682+
},
595683
{
596684
title: 'Check if the screen reader is enabled',
597685
render(): React.Element<typeof ScreenReaderStatusExample> {

React/Views/RCTView.m

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,26 @@ - (NSString *)accessibilityValue
273273
[valueComponents addObject:stateDescriptions[@"busy"]];
274274
}
275275
}
276+
277+
// handle accessibilityValue
278+
279+
if (self.accessibilityValueInternal) {
280+
id min = self.accessibilityValueInternal[@"min"];
281+
id now = self.accessibilityValueInternal[@"now"];
282+
id max = self.accessibilityValueInternal[@"max"];
283+
id text = self.accessibilityValueInternal[@"text"];
284+
if (text && [text isKindOfClass:[NSString class]]) {
285+
[valueComponents addObject:text];
286+
} else if ([min isKindOfClass:[NSNumber class]] &&
287+
[now isKindOfClass:[NSNumber class]] &&
288+
[max isKindOfClass:[NSNumber class]] &&
289+
([min intValue] < [max intValue]) &&
290+
([min intValue] <= [now intValue] && [now intValue] <= [max intValue])) {
291+
int val = ([now intValue]*100)/([max intValue]-[min intValue]);
292+
[valueComponents addObject:[NSString stringWithFormat:@"%d percent", val]];
293+
}
294+
}
295+
276296
if (valueComponents.count > 0) {
277297
return [valueComponents componentsJoinedByString:@", "];
278298
}

React/Views/RCTViewManager.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio
139139
RCT_REMAP_VIEW_PROPERTY(accessible, reactAccessibilityElement.isAccessibilityElement, BOOL)
140140
RCT_REMAP_VIEW_PROPERTY(accessibilityActions, reactAccessibilityElement.accessibilityActions, NSDictionaryArray)
141141
RCT_REMAP_VIEW_PROPERTY(accessibilityLabel, reactAccessibilityElement.accessibilityLabel, NSString)
142+
RCT_REMAP_VIEW_PROPERTY(accessibilityValue, reactAccessibilityElement.accessibilityValueInternal, NSDictionary)
142143
RCT_REMAP_VIEW_PROPERTY(accessibilityViewIsModal, reactAccessibilityElement.accessibilityViewIsModal, BOOL)
143144
RCT_REMAP_VIEW_PROPERTY(accessibilityIgnoresInvertColors, reactAccessibilityElement.shouldAccessibilityIgnoresInvertColors, BOOL)
144145
RCT_REMAP_VIEW_PROPERTY(onAccessibilityAction, reactAccessibilityElement.onAccessibilityAction, RCTDirectEventBlock)

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) NSString *accessibilityRole;
121121
@property (nonatomic, copy) NSDictionary<NSString *, id> *accessibilityState;
122122
@property (nonatomic, copy) NSArray <NSDictionary *> *accessibilityActions;
123+
@property (nonatomic, copy) NSDictionary *accessibilityValueInternal;
123124

124125
#if RCT_DEV
125126

React/Views/UIView+React.m

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,4 +344,13 @@ - (void)setAccessibilityState:(NSDictionary<NSString *, id> *)accessibilityState
344344
objc_setAssociatedObject(self, @selector(accessibilityState), accessibilityState, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
345345
}
346346

347+
- (NSDictionary<NSString *, id> *)accessibilityValueInternal
348+
{
349+
return objc_getAssociatedObject(self, _cmd);
350+
}
351+
- (void)setAccessibilityValueInternal:(NSDictionary<NSString *, id> *)accessibilityValue
352+
{
353+
objc_setAssociatedObject(self, @selector(accessibilityValueInternal), accessibilityValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
354+
}
355+
347356
@end

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

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77
import android.os.Build;
88
import android.view.View;
99
import android.view.ViewParent;
10-
11-
import java.util.ArrayList;
12-
import java.util.HashMap;
10+
import android.support.annotation.NonNull;
11+
import android.support.annotation.Nullable;
1312

1413
import com.facebook.react.R;
1514
import com.facebook.react.bridge.Dynamic;
@@ -24,9 +23,10 @@
2423
import com.facebook.react.uimanager.util.ReactFindViewUtil;
2524
import java.util.Locale;
2625

27-
import javax.annotation.Nonnull;
26+
import java.util.ArrayList;
27+
import java.util.HashMap;
28+
import java.util.List;
2829
import java.util.Map;
29-
import javax.annotation.Nullable;
3030

3131
/**
3232
* Base class that should be suitable for the majority of subclasses of {@link ViewManager}.
@@ -66,7 +66,7 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
6666
new MatrixMathHelper.MatrixDecompositionContext();
6767
private static double[] sTransformDecompositionArray = new double[16];
6868

69-
public static final HashMap<String, Integer> sStateDescription = new HashMap<String, Integer>();
69+
public static final Map<String, Integer> sStateDescription = new HashMap<>();
7070

7171
static {
7272
sStateDescription.put("busy", R.string.state_busy_description);
@@ -115,7 +115,7 @@ public void setZIndex(T view, float zIndex) {
115115
int integerZIndex = Math.round(zIndex);
116116
ViewGroupManager.setViewZIndex(view, integerZIndex);
117117
ViewParent parent = view.getParent();
118-
if (parent != null && parent instanceof ReactZIndexedViewGroup) {
118+
if (parent instanceof ReactZIndexedViewGroup) {
119119
((ReactZIndexedViewGroup) parent).updateDrawingOrder();
120120
}
121121
}
@@ -180,7 +180,8 @@ public void setViewState(@Nonnull T view, @Nullable ReadableMap accessibilitySta
180180
private void updateViewContentDescription(@Nonnull T view) {
181181
final String accessibilityLabel = (String) view.getTag(R.id.accessibility_label);
182182
final ReadableMap accessibilityState = (ReadableMap) view.getTag(R.id.accessibility_state);
183-
final ArrayList<String> contentDescription = new ArrayList<String>();
183+
final List<String> contentDescription = new ArrayList<>();
184+
final ReadableMap accessibilityValue = (ReadableMap) view.getTag(R.id.accessibility_value);
184185
if (accessibilityLabel != null) {
185186
contentDescription.add(accessibilityLabel);
186187
}
@@ -198,6 +199,12 @@ private void updateViewContentDescription(@Nonnull T view) {
198199
}
199200
}
200201
}
202+
if (accessibilityValue != null && accessibilityValue.hasKey("text")) {
203+
final Dynamic text = accessibilityValue.getDynamic("text");
204+
if (text != null && text.getType() == ReadableType.String) {
205+
contentDescription.add(text.asString());
206+
}
207+
}
201208
if (contentDescription.size() > 0) {
202209
view.setContentDescription(TextUtils.join(", ", contentDescription));
203210
}
@@ -212,6 +219,18 @@ public void setAccessibilityActions(T view, ReadableArray accessibilityActions)
212219
view.setTag(R.id.accessibility_actions, accessibilityActions);
213220
}
214221

222+
@ReactProp(name = ViewProps.ACCESSIBILITY_VALUE)
223+
public void setAccessibilityValue(T view, ReadableMap accessibilityValue) {
224+
if (accessibilityValue == null) {
225+
return;
226+
}
227+
228+
view.setTag(R.id.accessibility_value, accessibilityValue);
229+
if (accessibilityValue.hasKey("text")) {
230+
updateViewContentDescription(view);
231+
}
232+
}
233+
215234
@ReactProp(name = PROP_IMPORTANT_FOR_ACCESSIBILITY)
216235
public void setImportantForAccessibility(
217236
@Nonnull T view, @Nullable String importantForAccessibility) {

0 commit comments

Comments
 (0)