Skip to content

Commit

Permalink
added accessibilityRole Prop, added functionality support for role on…
Browse files Browse the repository at this point in the history
… 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
Ziqi Chen authored and facebook-github-bot committed Jul 10, 2018
1 parent ef3d8b2 commit c27b495
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Libraries/Components/View/ViewAccessibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ export type AccessibilityComponentType =
| 'radiobutton_checked'
| 'radiobutton_unchecked';

export type AccessibilityRole =
| 'none'
| 'button'
| 'image'
| 'keyboardkey'
| 'text'
| 'tabbar';

module.exports = {
AccessibilityTraits: [
'none',
Expand All @@ -65,4 +73,12 @@ module.exports = {
'radiobutton_checked',
'radiobutton_unchecked',
],
AccessibilityRoles: [
'none',
'button',
'image',
'keyboardkey',
'text',
'tabbar',
],
};
9 changes: 9 additions & 0 deletions Libraries/Components/View/ViewPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions React/Views/RCTViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
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
Expand Down Expand Up @@ -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")) {
Expand Down

0 comments on commit c27b495

Please sign in to comment.