diff --git a/Libraries/Components/View/ViewPropTypes.js b/Libraries/Components/View/ViewPropTypes.js index 497d94cff18b..58a2dad2230f 100644 --- a/Libraries/Components/View/ViewPropTypes.js +++ b/Libraries/Components/View/ViewPropTypes.js @@ -432,6 +432,13 @@ export type ViewProps = $ReadOnly<{| */ accessibilityActions?: ?$ReadOnlyArray, + /** + * Specifies the nativeID of the associated label text. When the assistive technology focuses on the component with this props, the text is read aloud. + * + * @platform android + */ + accessibilityLabelledBy?: ?string | ?Array, + /** * Views that are only used to layout their children or otherwise don't draw * anything may be automatically removed from the native hierarchy as an diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index 25579c4c292f..0f6aa8029d6c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -137,6 +137,21 @@ public void setNativeId(@NonNull T view, @Nullable String nativeId) { ReactFindViewUtil.notifyViewRendered(view); } + @Override + @ReactProp(name = ViewProps.ACCESSIBILITY_LABELLED_BY) + public void setAccessibilityLabelledBy(@NonNull T view, @Nullable Dynamic nativeId) { + if (nativeId.isNull()) { + return; + } + if (nativeId.getType() == ReadableType.String) { + view.setTag(R.id.labelled_by, nativeId.asString()); + } else if (nativeId.getType() == ReadableType.Array) { + // On Android, this takes a single View as labeledBy. If an array is specified, set the first + // element in the tag. + view.setTag(R.id.labelled_by, nativeId.asArray().getString(0)); + } + } + @Override @ReactProp(name = ViewProps.ACCESSIBILITY_LABEL) public void setAccessibilityLabel(@NonNull T view, @Nullable String accessibilityLabel) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerAdapter.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerAdapter.java index c0e21dece66f..de9423e6f8bd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerAdapter.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerAdapter.java @@ -10,6 +10,7 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; @@ -64,6 +65,9 @@ public void setImportantForAccessibility( @Override public void setNativeId(@NonNull T view, String nativeId) {} + @Override + public void setAccessibilityLabelledBy(@NonNull T view, Dynamic nativeId) {} + @Override public void setOpacity(@NonNull T view, float opacity) {} 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 9436df80d3f0..c8befc9ad26c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java @@ -35,6 +35,7 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.events.Event; import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.react.uimanager.util.ReactFindViewUtil; import java.util.HashMap; /** @@ -191,6 +192,8 @@ public void handleMessage(Message msg) { }; } + @Nullable View mAccessibilityLabelledBy; + @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { super.onInitializeAccessibilityNodeInfo(host, info); @@ -200,6 +203,15 @@ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCo setRole(info, accessibilityRole, host.getContext()); } + final Object accessibilityLabelledBy = host.getTag(R.id.labelled_by); + if (accessibilityLabelledBy != null) { + mAccessibilityLabelledBy = + ReactFindViewUtil.findView(host.getRootView(), (String) accessibilityLabelledBy); + if (mAccessibilityLabelledBy != null) { + info.setLabeledBy(mAccessibilityLabelledBy); + } + } + // state is changeable. final ReadableMap accessibilityState = (ReadableMap) host.getTag(R.id.accessibility_state); if (accessibilityState != null) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/interfaces/BaseViewManagerDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/interfaces/BaseViewManagerDelegate.java index 1598b578877b..8d1ac8584d5e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/interfaces/BaseViewManagerDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/interfaces/BaseViewManagerDelegate.java @@ -10,6 +10,7 @@ import android.view.View; import androidx.annotation.Nullable; import com.facebook.react.bridge.ColorPropConverter; +import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.yoga.YogaConstants; @@ -84,6 +85,9 @@ public void setProperty(T view, String propName, @Nullable Object value) { case ViewProps.NATIVE_ID: mViewManager.setNativeId(view, (String) value); break; + case ViewProps.ACCESSIBILITY_LABELLED_BY: + mViewManager.setAccessibilityLabelledBy(view, (Dynamic) value); + break; case ViewProps.OPACITY: mViewManager.setOpacity(view, value == null ? 1.0f : ((Double) value).floatValue()); break; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/interfaces/BaseViewManagerInterface.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/interfaces/BaseViewManagerInterface.java index 5423eeedd91a..629cae7e8ebb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/interfaces/BaseViewManagerInterface.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/interfaces/BaseViewManagerInterface.java @@ -9,6 +9,7 @@ import android.view.View; import androidx.annotation.Nullable; +import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; @@ -49,6 +50,8 @@ public interface BaseViewManagerInterface { void setNativeId(T view, @Nullable String nativeId); + void setAccessibilityLabelledBy(T view, @Nullable Dynamic nativeId); + void setOpacity(T view, float opacity); void setRenderToHardwareTexture(T view, boolean useHWTexture); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/interfaces/ViewProps.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/interfaces/ViewProps.java index 61151919cc0d..c7087b2baa5b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/interfaces/ViewProps.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/interfaces/ViewProps.java @@ -152,6 +152,7 @@ public class ViewProps { public static final String ACCESSIBILITY_STATE = "accessibilityState"; public static final String ACCESSIBILITY_ACTIONS = "accessibilityActions"; public static final String ACCESSIBILITY_VALUE = "accessibilityValue"; + public static final String ACCESSIBILITY_LABELLED_BY = "accessibilityLabelledBy"; public static final String IMPORTANT_FOR_ACCESSIBILITY = "importantForAccessibility"; // DEPRECATED diff --git a/ReactAndroid/src/main/res/views/uimanager/values/ids.xml b/ReactAndroid/src/main/res/views/uimanager/values/ids.xml index 6886defd4692..b552bd5eaa7f 100644 --- a/ReactAndroid/src/main/res/views/uimanager/values/ids.xml +++ b/ReactAndroid/src/main/res/views/uimanager/values/ids.xml @@ -27,4 +27,7 @@ + + + diff --git a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js index 6133080a4adf..12e7bd929862 100644 --- a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js +++ b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js @@ -192,6 +192,24 @@ class AccessibilityExample extends React.Component<{}> { Accessible view with label, hint, role, and state + + + + Mail Address + + First Name + + + ); }