diff --git a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js index 10e265c553cccb..4435044e57a102 100644 --- a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js +++ b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js @@ -21,6 +21,7 @@ import dismissKeyboard from '../../Utilities/dismissKeyboard'; import Platform from '../../Utilities/Platform'; import StatusBar from '../StatusBar/StatusBar'; import View from '../View/View'; +import type {AccessibilityRole} from '../../Components/View/ViewAccessibility'; import AndroidDrawerLayoutNativeComponent, { Commands, } from './AndroidDrawerLayoutNativeComponent'; @@ -36,6 +37,8 @@ type DrawerSlideEvent = $ReadOnly<{| |}>; type Props = $ReadOnly<{| + accessibilityRole?: ?AccessibilityRole, + /** * Determines whether the keyboard gets dismissed in response to a drag. * - 'none' (the default), drags do not dismiss the keyboard. diff --git a/Libraries/Components/View/ViewAccessibility.js b/Libraries/Components/View/ViewAccessibility.js index c62be022b046e7..18da75effcb1d7 100644 --- a/Libraries/Components/View/ViewAccessibility.js +++ b/Libraries/Components/View/ViewAccessibility.js @@ -16,6 +16,7 @@ import type {SyntheticEvent} from '../../Types/CoreEventTypes'; export type AccessibilityRole = | 'none' | 'button' + | 'dropdownlist' | 'togglebutton' | 'link' | 'search' @@ -44,7 +45,15 @@ export type AccessibilityRole = | 'timer' | 'list' | 'toolbar' - | 'grid'; + | 'grid' + | 'pager' + | 'scrollview' + | 'horizontalscrollview' + | 'viewgroup' + | 'webview' + | 'drawerlayout' + | 'slidingdrawer' + | 'iconmenu'; // Role types for web export type Role = diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index b034ff08ac244a..309f9a2fb8fc5c 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -28,6 +28,7 @@ @implementation RCTConvert (UIAccessibilityTraits) (@{ @"none" : @(UIAccessibilityTraitNone), @"button" : @(UIAccessibilityTraitButton), + @"dropdownlist" : @(UIAccessibilityTraitNone), @"togglebutton" : @(UIAccessibilityTraitButton), @"link" : @(UIAccessibilityTraitLink), @"header" : @(UIAccessibilityTraitHeader), @@ -63,6 +64,15 @@ @implementation RCTConvert (UIAccessibilityTraits) @"tablist" : @(UIAccessibilityTraitNone), @"timer" : @(UIAccessibilityTraitNone), @"toolbar" : @(UIAccessibilityTraitNone), + @"grid" : @(UIAccessibilityTraitNone), + @"pager" : @(UIAccessibilityTraitNone), + @"scrollview" : @(UIAccessibilityTraitNone), + @"horizontalscrollview" : @(UIAccessibilityTraitNone), + @"viewgroup" : @(UIAccessibilityTraitNone), + @"webview" : @(UIAccessibilityTraitNone), + @"drawerlayout" : @(UIAccessibilityTraitNone), + @"slidingdrawer" : @(UIAccessibilityTraitNone), + @"iconmenu" : @(UIAccessibilityTraitNone), @"list" : @(UIAccessibilityTraitNone), @"grid" : @(UIAccessibilityTraitNone), }), diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java index 0f5f1671c8ffd6..1ec4a23f73fb0e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java @@ -96,6 +96,7 @@ private void scheduleAccessibilityEventSender(View host) { public enum AccessibilityRole { NONE, BUTTON, + DROPDOWNLIST, TOGGLEBUTTON, LINK, SEARCH, @@ -123,12 +124,22 @@ public enum AccessibilityRole { TIMER, LIST, GRID, + PAGER, + SCROLLVIEW, + HORIZONTALSCROLLVIEW, + VIEWGROUP, + WEBVIEW, + DRAWERLAYOUT, + SLIDINGDRAWER, + ICONMENU, TOOLBAR; public static String getValue(AccessibilityRole role) { switch (role) { case BUTTON: return "android.widget.Button"; + case DROPDOWNLIST: + return "android.widget.Spinner"; case TOGGLEBUTTON: return "android.widget.ToggleButton"; case SEARCH: @@ -136,7 +147,7 @@ public static String getValue(AccessibilityRole role) { case IMAGE: return "android.widget.ImageView"; case IMAGEBUTTON: - return "android.widget.ImageButon"; + return "android.widget.ImageButton"; case KEYBOARDKEY: return "android.inputmethodservice.Keyboard$Key"; case TEXT: @@ -155,6 +166,22 @@ public static String getValue(AccessibilityRole role) { return "android.widget.AbsListView"; case GRID: return "android.widget.GridView"; + case SCROLLVIEW: + return "android.widget.ScrollView"; + case HORIZONTALSCROLLVIEW: + return "android.widget.HorizontalScrollView"; + case PAGER: + return "androidx.viewpager.widget.ViewPager"; + case DRAWERLAYOUT: + return "androidx.drawerlayout.widget.DrawerLayout"; + case SLIDINGDRAWER: + return "android.widget.SlidingDrawer"; + case ICONMENU: + return "com.android.internal.view.menu.IconMenuView"; + case VIEWGROUP: + return "android.view.ViewGroup"; + case WEBVIEW: + return "android.webkit.WebView"; case NONE: case LINK: case SUMMARY: diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK index 9677362369c1ca..0b70da4c4df2db 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK @@ -29,5 +29,6 @@ rn_android_library( react_native_target("java/com/facebook/react/uimanager/annotations:annotations"), react_native_target("java/com/facebook/react/views/scroll:scroll"), react_native_root_target(":generated_components_java-FBReactNativeComponentSpec"), + react_native_target("res:uimanager"), ], ) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayout.java b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayout.java index ab9bb7d30d3ea2..40392887a1c781 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayout.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayout.java @@ -10,10 +10,16 @@ import android.view.Gravity; import android.view.MotionEvent; import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import androidx.core.view.AccessibilityDelegateCompat; +import androidx.core.view.ViewCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.drawerlayout.widget.DrawerLayout; import com.facebook.common.logging.FLog; +import com.facebook.react.R; import com.facebook.react.bridge.ReactContext; import com.facebook.react.common.ReactConstants; +import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole; import com.facebook.react.uimanager.events.NativeGestureUtil; /** @@ -29,6 +35,30 @@ public ReactDrawerLayout(ReactContext reactContext) { super(reactContext); + ViewCompat.setAccessibilityDelegate( + this, + new AccessibilityDelegateCompat() { + @Override + public void onInitializeAccessibilityNodeInfo( + View host, AccessibilityNodeInfoCompat info) { + super.onInitializeAccessibilityNodeInfo(host, info); + final AccessibilityRole accessibilityRole = + (AccessibilityRole) host.getTag(R.id.accessibility_role); + if (accessibilityRole != null) { + info.setClassName(AccessibilityRole.getValue(accessibilityRole)); + } + } + + @Override + public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(host, event); + final AccessibilityRole accessibilityRole = + (AccessibilityRole) host.getTag(R.id.accessibility_role); + if (accessibilityRole != null) { + event.setClassName(AccessibilityRole.getValue(accessibilityRole)); + } + } + }); } @Override diff --git a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js index c552f75699656a..d5caca95b71530 100644 --- a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js +++ b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js @@ -28,6 +28,7 @@ const { Slider, Platform, Switch, + ScrollView, } = require('react-native'); import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter'; @@ -84,6 +85,9 @@ const styles = StyleSheet.create({ textAlign: 'center', backgroundColor: '#000000c0', }, + scrollView: { + height: 50, + }, }); class AccessibilityExample extends React.Component<{}> { @@ -571,106 +575,168 @@ class NestedCheckBox extends React.Component< class AccessibilityRoleAndStateExample extends React.Component<{}> { render(): React.Node { + const content = [ + This is some text, + This is some text, + This is some text, + This is some text, + This is some text, + This is some text, + This is some text, + ]; + return ( - - - Alert example - - - - Combobox example - - - Menu example - - - Menu bar example - - - Menu item example - - - Progress bar example - - - Radio button example - - - Radio group example - - - Scrollbar example - - - Spin button example - - - - Tab example - - - Tab list example - - - Timer example - - - Toolbar example - - - State busy example - - - - - + <> + + + {content} + - + + + {content} + + + + + {content} + + + + + + Alert example + + + + Combobox example + + + Menu example + + + Menu bar example + + + Menu item example + + + Progress bar example + + + Radio button example + + + Radio group example + + + Scrollbar example + + + Spin button example + + + + Tab example + + + Tab list example + + + Timer example + + + Toolbar example + + + State busy example + + + Drop Down List example + + + Pager example + + + Toggle Button example + + + Viewgroup example + + + Webview example + + + + + + + + + ); } } @@ -1369,18 +1435,18 @@ exports.title = 'Accessibility'; exports.documentationURL = 'https://reactnative.dev/docs/accessibilityinfo'; exports.description = 'Examples of using Accessibility APIs.'; exports.examples = [ - { - title: 'Accessibility elements', - render(): React.Element { - return ; - }, - }, { title: 'New accessibility roles and states', render(): React.Element { return ; }, }, + { + title: 'Accessibility elements', + render(): React.Element { + return ; + }, + }, { title: 'Accessibility action examples', render(): React.Element { diff --git a/packages/rn-tester/js/examples/DrawerLayoutAndroid/DrawerLayoutAndroidExample.js b/packages/rn-tester/js/examples/DrawerLayoutAndroid/DrawerLayoutAndroidExample.js new file mode 100644 index 00000000000000..2906099c49d27d --- /dev/null +++ b/packages/rn-tester/js/examples/DrawerLayoutAndroid/DrawerLayoutAndroidExample.js @@ -0,0 +1,100 @@ +/** + * 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. + * + * @format + * @flow + */ + +'use strict'; + +import React, {useRef, useState} from 'react'; +import type {Node} from 'react'; +import { + Button, + DrawerLayoutAndroid, + Text, + StyleSheet, + View, +} from 'react-native'; + +const Drawer = () => { + const drawer = useRef(null); + const [drawerPosition, setDrawerPosition] = useState('left'); + const changeDrawerPosition = () => { + if (drawerPosition === 'left') { + setDrawerPosition('right'); + } else { + setDrawerPosition('left'); + } + }; + + const navigationView = () => ( + + I'm in the Drawer! +