Permalink
Browse files

added accessibilityRole Prop, added functionality support for role on…

… android

Summary:
Added a new property to View for Accessibility called `accessibilityRole`. This property merges functionality of existing properties: `accessibilityTraits` (iOS) and `accessibilityComponentType` (android).

Currently, nine values are supported with equivalent behavior as `accessibilityTraits` (iOS) when `accessibilityRole` is set on iOS Voiceover and Android TalkBack

```
  | 'none'
  | 'button'
  | 'link'
  | 'search'
  | 'image'
  | 'keyboardkey'
  | 'text'
  | 'adjustable'
  | 'tabbar'
```
They currently support similar behavior on talkback on Android and voice over on iOS
Does not break functionality of existing properties, but have not tested for behavior of setting both this one and the old one.

* iOS - I added a property accessibilityRoles, and basically remapped it to the same thing as accessibilityTraits. I also added in enum mappings for keyboardkey and tabbar.
* Android - Also added a property accessibilityRoles, from the Android side. For the underlying native functionality, I built a helper class that is based off of AccessibilityRolesUtil.java from the accessibility team. Biggest changes made are that I defined my own enums if needed, and also set some properties to match the functionality of iOS Accessibility Traits. I also handled the logic for switch/case statements of setting roles for the android side on this file. Also, I currently haven't localized strings for setRoleDescription, but plan to.
* Javascript - I added a view property accessibilityRoles in ViewPropTypes.

Reviewed By: blavalla

Differential Revision: D8756225

fbshipit-source-id: e03eec40cce86042551764f433e1defe7ee41b35
  • Loading branch information...
ziqichen6 authored and facebook-github-bot committed Jul 10, 2018
1 parent ef3d8b2 commit c27b495a89e71ff13959eb4c34605a527514fa1e
@@ -39,6 +39,14 @@ export type AccessibilityComponentType =
| 'radiobutton_checked'
| 'radiobutton_unchecked';
export type AccessibilityRole =
| 'none'
| 'button'
| 'image'
| 'keyboardkey'
| 'text'
| 'tabbar';
module.exports = {
AccessibilityTraits: [
'none',
@@ -65,4 +73,12 @@ module.exports = {
'radiobutton_checked',
'radiobutton_unchecked',
],
AccessibilityRoles: [
'none',
'button',
'image',
'keyboardkey',
'text',
'tabbar',
],
};
@@ -20,11 +20,13 @@ const ViewStylePropTypes = require('ViewStylePropTypes');
const {
AccessibilityComponentTypes,
AccessibilityTraits,
AccessibilityRoles,
} = require('ViewAccessibility');
import type {
AccessibilityComponentType,
AccessibilityTrait,
AccessibilityRole,
} from 'ViewAccessibility';
import type {EdgeInsetsProp} from 'EdgeInsetsPropType';
import type {TVViewProps} from 'TVViewPropTypes';
@@ -89,6 +91,7 @@ export type ViewProps = $ReadOnly<{|
importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants',
accessibilityIgnoresInvertColors?: boolean,
accessibilityTraits?: AccessibilityTrait | Array<AccessibilityTrait>,
accessibilityRole?: AccessibilityRole,
accessibilityViewIsModal?: boolean,
accessibilityElementsHidden?: boolean,
children?: ?React.Node,
@@ -139,6 +142,12 @@ module.exports = {
*/
accessibilityComponentType: PropTypes.oneOf(AccessibilityComponentTypes),
/**
* Indicates to accessibility services to treat UI component like a
* native one. Merging accessibilityComponentType and accessibilityTraits.
*/
accessibilityRole: PropTypes.oneOf(AccessibilityRoles),
/**
* Indicates to accessibility services whether the user should be notified
* when this view changes. Works for Android API >= 19 only.
@@ -33,9 +33,11 @@ @implementation RCTConvert(UIAccessibilityTraits)
@"header": @(UIAccessibilityTraitHeader),
@"search": @(UIAccessibilityTraitSearchField),
@"image": @(UIAccessibilityTraitImage),
@"tabbar": @(UIAccessibilityTraitTabBar),
@"selected": @(UIAccessibilityTraitSelected),
@"plays": @(UIAccessibilityTraitPlaysSound),
@"key": @(UIAccessibilityTraitKeyboardKey),
@"keyboardkey": @(UIAccessibilityTraitKeyboardKey),
@"text": @(UIAccessibilityTraitStaticText),
@"summary": @(UIAccessibilityTraitSummaryElement),
@"disabled": @(UIAccessibilityTraitNotEnabled),
@@ -110,6 +112,7 @@ - (RCTShadowView *)shadowView
RCT_REMAP_VIEW_PROPERTY(accessibilityActions, reactAccessibilityElement.accessibilityActions, NSString)
RCT_REMAP_VIEW_PROPERTY(accessibilityLabel, reactAccessibilityElement.accessibilityLabel, NSString)
RCT_REMAP_VIEW_PROPERTY(accessibilityTraits, reactAccessibilityElement.accessibilityTraits, UIAccessibilityTraits)
RCT_REMAP_VIEW_PROPERTY(accessibilityRole, reactAccessibilityElement.accessibilityTraits, UIAccessibilityTraits)
RCT_REMAP_VIEW_PROPERTY(accessibilityViewIsModal, reactAccessibilityElement.accessibilityViewIsModal, BOOL)
RCT_REMAP_VIEW_PROPERTY(accessibilityElementsHidden, reactAccessibilityElement.accessibilityElementsHidden, BOOL)
RCT_REMAP_VIEW_PROPERTY(accessibilityIgnoresInvertColors, reactAccessibilityElement.shouldAccessibilityIgnoresInvertColors, BOOL)
@@ -0,0 +1,121 @@
// Copyright (c) 2004-present, Facebook, Inc.
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
package com.facebook.react.uimanager;
import android.annotation.TargetApi;
import android.os.Build;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import javax.annotation.Nullable;
/**
* Utility class that handles the addition of a "role" for accessibility to either a View or
* AccessibilityNodeInfo.
*/
public class AccessibilityRoleUtil {
/**
* These roles are defined by Google's TalkBack screen reader, and this list should be kept up to
* date with their implementation. Details can be seen in their source code here:
*
* <p>https://github.com/google/talkback/blob/master/utils/src/main/java/Role.java
*/
public enum AccessibilityRole {
NONE(null),
BUTTON("android.widget.Button"),
IMAGE("android.widget.ImageView"),
KEYBOARD_KEY("android.inputmethodservice.Keyboard$Key"),
TEXT("android.widget.ViewGroup"),
TAB_BAR("android.widget.TabWidget");
@Nullable private final String mValue;
AccessibilityRole(String type) {
mValue = type;
}
@Nullable
public String getValue() {
return mValue;
}
public static AccessibilityRole fromValue(String value) {
for (AccessibilityRole role : AccessibilityRole.values()) {
if (role.getValue() != null && role.getValue().equals(value)) {
return role;
}
}
return AccessibilityRole.NONE;
}
}
private AccessibilityRoleUtil() {
// No instances
}
public static void setRole(View view, final AccessibilityRole role) {
// if a view already has an accessibility delegate, replacing it could cause problems,
// so leave it alone.
if (!ViewCompat.hasAccessibilityDelegate(view)) {
ViewCompat.setAccessibilityDelegate(
view,
new AccessibilityDelegateCompat() {
@Override
public void onInitializeAccessibilityNodeInfo(
View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
setRole(info, role);
}
});
}
}
public static void setRole(AccessibilityNodeInfoCompat nodeInfo, final AccessibilityRole role) {
nodeInfo.setClassName(role.getValue());
}
/**
* Variables and methods for setting accessibilityRole on view properties.
*/
private static final String NONE = "none";
private static final String BUTTON = "button";
private static final String IMAGE = "image";
private static final String KEYBOARDKEY = "keyboardkey";
private static final String TEXT = "text";
private static final String TABBAR = "tabbar";
public static void updateAccessibilityRole(View view, String role) {
if (role == null) {
view.setAccessibilityDelegate(null);
}
switch (role) {
case NONE:
break;
case BUTTON:
setRole(view, AccessibilityRoleUtil.AccessibilityRole.BUTTON);
break;
case IMAGE:
setRole(view, AccessibilityRoleUtil.AccessibilityRole.IMAGE);
break;
case KEYBOARDKEY:
setRole(view, AccessibilityRoleUtil.AccessibilityRole.KEYBOARD_KEY);
break;
case TEXT:
setRole(view, AccessibilityRoleUtil.AccessibilityRole.TEXT);
break;
case TABBAR:
setRole(view, AccessibilityRoleUtil.AccessibilityRole.TAB_BAR);
break;
default:
view.setAccessibilityDelegate(null);
}
}
}
@@ -29,6 +29,7 @@
private static final String PROP_ACCESSIBILITY_LABEL = "accessibilityLabel";
private static final String PROP_ACCESSIBILITY_COMPONENT_TYPE = "accessibilityComponentType";
private static final String PROP_ACCESSIBILITY_LIVE_REGION = "accessibilityLiveRegion";
private static final String PROP_ACCESSIBILITY_ROLE = "accessibilityRole";
private static final String PROP_IMPORTANT_FOR_ACCESSIBILITY = "importantForAccessibility";
// DEPRECATED
@@ -117,6 +118,11 @@ public void setAccessibilityComponentType(T view, String accessibilityComponentT
AccessibilityHelper.updateAccessibilityComponentType(view, accessibilityComponentType);
}
@ReactProp(name = PROP_ACCESSIBILITY_ROLE)
public void setAccessibilityRole(T view, String accessibilityRole) {
AccessibilityRoleUtil.updateAccessibilityRole(view, accessibilityRole);
}
@ReactProp(name = PROP_IMPORTANT_FOR_ACCESSIBILITY)
public void setImportantForAccessibility(T view, String importantForAccessibility) {
if (importantForAccessibility == null || importantForAccessibility.equals("auto")) {

0 comments on commit c27b495

Please sign in to comment.