From 47990695d52de518b70289892899b1be3790836d Mon Sep 17 00:00:00 2001 From: Gabriel Donadel Dall'Agnol Date: Thu, 13 Oct 2022 14:28:44 -0300 Subject: [PATCH] feat: Add role prop to Text component --- Libraries/Components/View/View.js | 69 +--------------- .../Components/View/ViewAccessibility.d.ts | 71 +++++++++++++++++ Libraries/Text/Text.js | 9 +++ Libraries/Text/TextProps.js | 6 ++ Libraries/Utilities/AcessibilityMapping.js | 79 +++++++++++++++++++ .../Accessibility/AccessibilityExample.js | 4 + 6 files changed, 170 insertions(+), 68 deletions(-) create mode 100644 Libraries/Utilities/AcessibilityMapping.js diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 129f50d36e2d95..d69a559ae916a6 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -12,6 +12,7 @@ import type {ViewProps} from './ViewPropTypes'; import flattenStyle from '../../StyleSheet/flattenStyle'; import TextAncestor from '../../Text/TextAncestor'; +import {roleToAccessibilityRoleMapping} from '../../Utilities/AcessibilityMapping'; import ViewNativeComponent from './ViewNativeComponent'; import * as React from 'react'; @@ -80,74 +81,6 @@ const View: React.AbstractComponent< text: ariaValueText ?? accessibilityValue?.text, }; - // Map role values to AccessibilityRole values - const roleToAccessibilityRoleMapping = { - alert: 'alert', - alertdialog: undefined, - application: undefined, - article: undefined, - banner: undefined, - button: 'button', - cell: undefined, - checkbox: 'checkbox', - columnheader: undefined, - combobox: 'combobox', - complementary: undefined, - contentinfo: undefined, - definition: undefined, - dialog: undefined, - directory: undefined, - document: undefined, - feed: undefined, - figure: undefined, - form: undefined, - grid: 'grid', - group: undefined, - heading: 'header', - img: 'image', - link: 'link', - list: 'list', - listitem: undefined, - log: undefined, - main: undefined, - marquee: undefined, - math: undefined, - menu: 'menu', - menubar: 'menubar', - menuitem: 'menuitem', - meter: undefined, - navigation: undefined, - none: 'none', - note: undefined, - presentation: 'none', - progressbar: 'progressbar', - radio: 'radio', - radiogroup: 'radiogroup', - region: undefined, - row: undefined, - rowgroup: undefined, - rowheader: undefined, - scrollbar: 'scrollbar', - searchbox: 'search', - separator: undefined, - slider: 'adjustable', - spinbutton: 'spinbutton', - status: undefined, - summary: 'summary', - switch: 'switch', - tab: 'tab', - table: undefined, - tablist: 'tablist', - tabpanel: undefined, - term: undefined, - timer: 'timer', - toolbar: 'toolbar', - tooltip: undefined, - tree: undefined, - treegrid: undefined, - treeitem: undefined, - }; - const flattenedStyle = flattenStyle(style); const newPointerEvents = flattenedStyle?.pointerEvents || pointerEvents; diff --git a/Libraries/Components/View/ViewAccessibility.d.ts b/Libraries/Components/View/ViewAccessibility.d.ts index a9ff8baa374686..8f530a78c19848 100644 --- a/Libraries/Components/View/ViewAccessibility.d.ts +++ b/Libraries/Components/View/ViewAccessibility.d.ts @@ -101,6 +101,11 @@ export interface AccessibilityProps 'aria-live'?: ('polite' | 'assertive' | 'off') | undefined; 'aria-modal'?: boolean | undefined; + + /** + * Indicates to accessibility services to treat UI component like a specific role. + */ + role?: Role; } export type AccessibilityActionInfo = Readonly<{ @@ -286,3 +291,69 @@ export interface AccessibilityPropsIOS { */ accessibilityIgnoresInvertColors?: boolean | undefined; } + +export type Role = + | 'alert' + | 'alertdialog' + | 'application' + | 'article' + | 'banner' + | 'button' + | 'cell' + | 'checkbox' + | 'columnheader' + | 'combobox' + | 'complementary' + | 'contentinfo' + | 'definition' + | 'dialog' + | 'directory' + | 'document' + | 'feed' + | 'figure' + | 'form' + | 'grid' + | 'group' + | 'heading' + | 'img' + | 'link' + | 'list' + | 'listitem' + | 'log' + | 'main' + | 'marquee' + | 'math' + | 'menu' + | 'menubar' + | 'menuitem' + | 'meter' + | 'navigation' + | 'none' + | 'note' + | 'presentation' + | 'progressbar' + | 'radio' + | 'radiogroup' + | 'region' + | 'row' + | 'rowgroup' + | 'rowheader' + | 'scrollbar' + | 'searchbox' + | 'separator' + | 'slider' + | 'spinbutton' + | 'status' + | 'summary' + | 'switch' + | 'tab' + | 'table' + | 'tablist' + | 'tabpanel' + | 'term' + | 'timer' + | 'toolbar' + | 'tooltip' + | 'tree' + | 'treegrid' + | 'treeitem'; diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index 0f9eb500bb919a..037fef2a324064 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -21,6 +21,7 @@ import {NativeText, NativeVirtualText} from './TextNativeComponent'; import {type TextProps} from './TextProps'; import * as React from 'react'; import {useContext, useMemo, useState} from 'react'; +import {roleToAccessibilityRoleMapping} from '../Utilities/AcessibilityMapping'; /** * Text is the fundamental component for displaying text. @@ -34,6 +35,7 @@ const Text: React.AbstractComponent< const { accessible, accessibilityLabel, + accessibilityRole, allowFontScaling, 'aria-busy': ariaBusy, 'aria-checked': ariaChecked, @@ -55,6 +57,7 @@ const Text: React.AbstractComponent< onResponderTerminationRequest, onStartShouldSetResponder, pressRetentionOffset, + role, suppressHighlighting, ...restProps } = props; @@ -225,6 +228,9 @@ const Text: React.AbstractComponent< accessibilityState={_accessibilityState} {...eventHandlersForText} accessibilityLabel={ariaLabel ?? accessibilityLabel} + accessibilityRole={ + role ? roleToAccessibilityRoleMapping[role] : accessibilityRole + } isHighlighted={isHighlighted} isPressable={isPressable} selectable={_selectable} @@ -248,6 +254,9 @@ const Text: React.AbstractComponent< } accessibilityLabel={ariaLabel ?? accessibilityLabel} accessibilityState={nativeTextAccessibilityState} + accessibilityRole={ + role ? roleToAccessibilityRoleMapping[role] : accessibilityRole + } allowFontScaling={allowFontScaling !== false} ellipsizeMode={ellipsizeMode ?? 'tail'} isHighlighted={isHighlighted} diff --git a/Libraries/Text/TextProps.js b/Libraries/Text/TextProps.js index 16e4f87b25cc03..5948b111f34d01 100644 --- a/Libraries/Text/TextProps.js +++ b/Libraries/Text/TextProps.js @@ -15,6 +15,7 @@ import type { AccessibilityActionInfo, AccessibilityRole, AccessibilityState, + Role, } from '../Components/View/ViewAccessibility'; import type {TextStyleProp} from '../StyleSheet/StyleSheet'; import type { @@ -176,6 +177,11 @@ export type TextProps = $ReadOnly<{| */ pressRetentionOffset?: ?PressRetentionOffset, + /** + * Indicates to accessibility services to treat UI component like a specific role. + */ + role?: ?Role, + /** * Lets the user select text. * diff --git a/Libraries/Utilities/AcessibilityMapping.js b/Libraries/Utilities/AcessibilityMapping.js new file mode 100644 index 00000000000000..5f7e7ed0a1f4d6 --- /dev/null +++ b/Libraries/Utilities/AcessibilityMapping.js @@ -0,0 +1,79 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +// Map role values to AccessibilityRole values +export const roleToAccessibilityRoleMapping = { + alert: 'alert', + alertdialog: undefined, + application: undefined, + article: undefined, + banner: undefined, + button: 'button', + cell: undefined, + checkbox: 'checkbox', + columnheader: undefined, + combobox: 'combobox', + complementary: undefined, + contentinfo: undefined, + definition: undefined, + dialog: undefined, + directory: undefined, + document: undefined, + feed: undefined, + figure: undefined, + form: undefined, + grid: 'grid', + group: undefined, + heading: 'header', + img: 'image', + link: 'link', + list: 'list', + listitem: undefined, + log: undefined, + main: undefined, + marquee: undefined, + math: undefined, + menu: 'menu', + menubar: 'menubar', + menuitem: 'menuitem', + meter: undefined, + navigation: undefined, + none: 'none', + note: undefined, + presentation: 'none', + progressbar: 'progressbar', + radio: 'radio', + radiogroup: 'radiogroup', + region: undefined, + row: undefined, + rowgroup: undefined, + rowheader: undefined, + scrollbar: 'scrollbar', + searchbox: 'search', + separator: undefined, + slider: 'adjustable', + spinbutton: 'spinbutton', + status: undefined, + summary: 'summary', + switch: 'switch', + tab: 'tab', + table: undefined, + tablist: 'tablist', + tabpanel: undefined, + term: undefined, + timer: 'timer', + toolbar: 'toolbar', + tooltip: undefined, + tree: undefined, + treegrid: undefined, + treeitem: undefined, +}; diff --git a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js index 9efdbfdb2da011..c552f75699656a 100644 --- a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js +++ b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js @@ -157,6 +157,10 @@ class AccessibilityExample extends React.Component<{}> { This is a title. + + This is a title. + + Alert.alert('Link has been clicked!')}