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
This commit is contained in:
Jorge Cabiedes Acosta
2025-04-28 12:11:04 -07:00
committed by Facebook GitHub Bot
parent 99c3aa3861
commit 4ce093154d
11 changed files with 43 additions and 1 deletions
@@ -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 {
@@ -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<{
@@ -205,6 +205,7 @@ const validAttributesForNonEventProps = {
accessibilityValue: true,
experimental_accessibilityOrder: true,
importantForAccessibility: true,
screenReaderFocusable: true,
role: true,
rotation: true,
scaleX: true,
@@ -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,
@@ -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;
@@ -513,6 +513,13 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
}
}
@ReactProp(name = ViewProps.SCREEN_READER_FOCUSABLE)
public void setScreenReaderFocusable(@NonNull T view, boolean screenReaderFocusable) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
view.setScreenReaderFocusable(screenReaderFocusable);
}
}
@ReactProp(name = ViewProps.ROLE)
public void setRole(@NonNull T view, @Nullable String role) {
if (role == null) {
@@ -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 -> {
@@ -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"
@@ -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);
}
}
@@ -47,6 +47,7 @@ class HostPlatformViewProps : public BaseViewProps {
bool hasTVPreferredFocus{false};
bool needsOffscreenAlphaCompositing{false};
bool renderToHardwareTextureAndroid{false};
bool screenReaderFocusable{false};
#pragma mark - Convenience Methods
@@ -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) {