From 4ce093154d8fe130fca1f8da57067a666171db7f Mon Sep 17 00:00:00 2001 From: Jorge Cabiedes Acosta Date: Mon, 28 Apr 2025 12:11:04 -0700 Subject: [PATCH] Add `screenReaderFocusable` prop (#50850) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50850 This prop will be used to enable screen reader focusability without allowing keyboard focus. Mostly a quality of life prop for product engineers and maps 1:1 to Android Changelog: [Android][Added] - Expose Android's screenReaderFocusable prop Reviewed By: javache Differential Revision: D73382051 fbshipit-source-id: 8171b9d24a735dd42d54abe4537fb487bdd011b7 --- .../Libraries/Components/View/ViewAccessibility.d.ts | 7 +++++++ .../Libraries/Components/View/ViewAccessibility.js | 8 ++++++++ .../NativeComponent/BaseViewConfig.android.js | 1 + .../__tests__/__snapshots__/public-api-test.js.snap | 1 + .../react-native/ReactAndroid/api/ReactAndroid.api | 2 ++ .../com/facebook/react/uimanager/BaseViewManager.java | 7 +++++++ .../react/uimanager/BaseViewManagerDelegate.kt | 3 +++ .../java/com/facebook/react/uimanager/ViewProps.kt | 1 + .../renderer/components/view/HostPlatformViewProps.cpp | 10 ++++++++++ .../renderer/components/view/HostPlatformViewProps.h | 1 + .../view/HostPlatformViewTraitsInitializer.h | 3 ++- 11 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/View/ViewAccessibility.d.ts b/packages/react-native/Libraries/Components/View/ViewAccessibility.d.ts index 1d6d060b9ca..8c7ef1e9930 100644 --- a/packages/react-native/Libraries/Components/View/ViewAccessibility.d.ts +++ b/packages/react-native/Libraries/Components/View/ViewAccessibility.d.ts @@ -273,6 +273,13 @@ export interface AccessibilityPropsAndroid { | 'no' | 'no-hide-descendants' | undefined; + + /** + * Enables the view to be screen reader focusable, not keyboard focusable. + * + * @platform android + */ + screenReaderFocusable?: boolean | undefined; } export interface AccessibilityPropsIOS { diff --git a/packages/react-native/Libraries/Components/View/ViewAccessibility.js b/packages/react-native/Libraries/Components/View/ViewAccessibility.js index 2f58e94c19f..cccbd0cdcd1 100644 --- a/packages/react-native/Libraries/Components/View/ViewAccessibility.js +++ b/packages/react-native/Libraries/Components/View/ViewAccessibility.js @@ -258,6 +258,14 @@ export type AccessibilityPropsAndroid = $ReadOnly<{ * See https://reactnative.dev/docs/view#importantforaccessibility */ importantForAccessibility?: ?('auto' | 'yes' | 'no' | 'no-hide-descendants'), + + /** + * Enables the view to be screen reader focusable, not keyboard focusable. This has lower priority + * than focusable or accessible props. + * + * @platform android + */ + screenReaderFocusable?: boolean, }>; export type AccessibilityPropsIOS = $ReadOnly<{ diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js index 9178b4b22c8..bc35556e5af 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js @@ -205,6 +205,7 @@ const validAttributesForNonEventProps = { accessibilityValue: true, experimental_accessibilityOrder: true, importantForAccessibility: true, + screenReaderFocusable: true, role: true, rotation: true, scaleX: true, diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index 163b751f46f..b25752eb8e5 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -3643,6 +3643,7 @@ export type AccessibilityPropsAndroid = $ReadOnly<{ accessibilityLiveRegion?: ?(\\"none\\" | \\"polite\\" | \\"assertive\\"), \\"aria-live\\"?: ?(\\"polite\\" | \\"assertive\\" | \\"off\\"), importantForAccessibility?: ?(\\"auto\\" | \\"yes\\" | \\"no\\" | \\"no-hide-descendants\\"), + screenReaderFocusable?: boolean, }>; export type AccessibilityPropsIOS = $ReadOnly<{ accessibilityIgnoresInvertColors?: ?boolean, diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index b3cdd036b39..740e46d755d 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -3517,6 +3517,7 @@ public abstract class com/facebook/react/uimanager/BaseViewManager : com/faceboo public fun setRotation (Landroid/view/View;F)V public fun setScaleX (Landroid/view/View;F)V public fun setScaleY (Landroid/view/View;F)V + public fun setScreenReaderFocusable (Landroid/view/View;Z)V public fun setShadowColor (Landroid/view/View;I)V public fun setShouldBlockNativeResponder (Landroid/view/View;Z)V public fun setStartShouldSetResponder (Landroid/view/View;Z)V @@ -4890,6 +4891,7 @@ public final class com/facebook/react/uimanager/ViewProps { public static final field ROW_GAP Ljava/lang/String; public static final field SCALE_X Ljava/lang/String; public static final field SCALE_Y Ljava/lang/String; + public static final field SCREEN_READER_FOCUSABLE Ljava/lang/String; public static final field SCROLL Ljava/lang/String; public static final field SHADOW_COLOR Ljava/lang/String; public static final field START Ljava/lang/String; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index c3a0326a333..0cca01845c3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -513,6 +513,13 @@ public abstract class BaseViewManager= Build.VERSION_CODES.P) { + view.setScreenReaderFocusable(screenReaderFocusable); + } + } + @ReactProp(name = ViewProps.ROLE) public void setRole(@NonNull T view, @Nullable String role) { if (role == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.kt index b2b16a0b99c..a59da677094 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.kt @@ -82,6 +82,9 @@ public abstract class BaseViewManagerDelegate< ViewProps.IMPORTANT_FOR_ACCESSIBILITY -> mViewManager.setImportantForAccessibility(view, value as String?) + ViewProps.SCREEN_READER_FOCUSABLE -> + mViewManager.setScreenReaderFocusable(view, value as Boolean? ?: false) + ViewProps.ROLE -> mViewManager.setRole(view, value as String?) ViewProps.NATIVE_ID -> mViewManager.setNativeId(view, value as String?) ViewProps.ACCESSIBILITY_LABELLED_BY -> { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.kt index 660be0f831e..f3716ecdb47 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.kt @@ -159,6 +159,7 @@ public object ViewProps { public const val ACCESSIBILITY_LABELLED_BY: String = "accessibilityLabelledBy" public const val ACCESSIBILITY_ORDER: String = "experimental_accessibilityOrder" public const val IMPORTANT_FOR_ACCESSIBILITY: String = "importantForAccessibility" + public const val SCREEN_READER_FOCUSABLE: String = "screenReaderFocusable" public const val ROLE: String = "role" // DEPRECATED public const val ROTATION: String = "rotation" diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp index f5ee897794e..f51d962f330 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp @@ -85,6 +85,15 @@ HostPlatformViewProps::HostPlatformViewProps( rawProps, "renderToHardwareTextureAndroid", sourceProps.renderToHardwareTextureAndroid, + {})), + screenReaderFocusable( + ReactNativeFeatureFlags::enableCppPropsIteratorSetter() + ? sourceProps.screenReaderFocusable + : convertRawProp( + context, + rawProps, + "screenReaderFocusable", + sourceProps.screenReaderFocusable, {})) {} #define VIEW_EVENT_CASE(eventType) \ @@ -119,6 +128,7 @@ void HostPlatformViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(hasTVPreferredFocus); RAW_SET_PROP_SWITCH_CASE_BASIC(needsOffscreenAlphaCompositing); RAW_SET_PROP_SWITCH_CASE_BASIC(renderToHardwareTextureAndroid); + RAW_SET_PROP_SWITCH_CASE_BASIC(screenReaderFocusable); } } diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.h index d18d7e863bb..130b0169821 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.h @@ -47,6 +47,7 @@ class HostPlatformViewProps : public BaseViewProps { bool hasTVPreferredFocus{false}; bool needsOffscreenAlphaCompositing{false}; bool renderToHardwareTextureAndroid{false}; + bool screenReaderFocusable{false}; #pragma mark - Convenience Methods diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewTraitsInitializer.h b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewTraitsInitializer.h index 23297fec74b..8550d271637 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewTraitsInitializer.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewTraitsInitializer.h @@ -21,7 +21,8 @@ inline bool formsView(const ViewProps& viewProps) { viewProps.nativeForeground.has_value() || viewProps.focusable || viewProps.hasTVPreferredFocus || viewProps.needsOffscreenAlphaCompositing || - viewProps.renderToHardwareTextureAndroid; + viewProps.renderToHardwareTextureAndroid || + viewProps.screenReaderFocusable; } inline bool isKeyboardFocusable(const ViewProps& viewProps) {