From 36037fa81bbdcc460057e7e7cf608cd364ca48a6 Mon Sep 17 00:00:00 2001 From: grgr-dkrk <40130327+grgr-dkrk@users.noreply.github.com> Date: Tue, 11 Jan 2022 06:50:12 -0800 Subject: [PATCH] feat: add `accessibilityLabelledBy` props (#32470) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: related: https://github.com/facebook/react-native/issues/30846, https://github.com/facebook/react-native/issues/26739 Added `accessibilityLabelledBy` props to find the nativeID of the associated label, it mainly for` `. The reason for implementing it as `labelledBy` instead of `labelFor` is as follows. - It was difficult to find a component with `labelFor` because the `` component does not add the `labelFor` received from her Props to the View's tag. - The use case looks like the HTML `aria-labelledby`, which is intuitive for web developers. It also seems easy to convert to a web platform. ## Changelog [Android] [Added] - add `accessibilityLabelledBy` props Pull Request resolved: https://github.com/facebook/react-native/pull/32470 Test Plan: I checked it with RNTester using an Android11. https://user-images.githubusercontent.com/40130327/138666856-891d9f4d-52cf-4181-a81f-13b033037db4.mp4 Reviewed By: lunaleaps, kacieb Differential Revision: D31897112 Pulled By: ShikaSD fbshipit-source-id: 66361735679560c01834b3a4483adf264098b3e3 --- Libraries/Components/View/ViewPropTypes.js | 7 +++++++ .../react/uimanager/BaseViewManager.java | 15 +++++++++++++++ .../uimanager/BaseViewManagerAdapter.java | 4 ++++ .../uimanager/ReactAccessibilityDelegate.java | 12 ++++++++++++ .../interfaces/BaseViewManagerDelegate.java | 4 ++++ .../interfaces/BaseViewManagerInterface.java | 3 +++ .../react/uimanager/interfaces/ViewProps.java | 1 + .../main/res/views/uimanager/values/ids.xml | 3 +++ .../Accessibility/AccessibilityExample.js | 18 ++++++++++++++++++ 9 files changed, 67 insertions(+) diff --git a/Libraries/Components/View/ViewPropTypes.js b/Libraries/Components/View/ViewPropTypes.js index eedde2ba49e1b5..0e6f739470b3f9 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 1edee99465d043..3c9c1eae3bb3ad 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 db344b1b7711ad..6a19c5a7cd2030 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 0e3e76a738c620..79693c11522e50 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 3ba80cf841ac16..6bc0b1bc0449d5 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 89f4d2dba3571e..cfdc791caf5f79 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 0ebcaf73a970af..661c3466dde96f 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 8fe4be952af418..5c6c6d354d517f 100644 --- a/ReactAndroid/src/main/res/views/uimanager/values/ids.xml +++ b/ReactAndroid/src/main/res/views/uimanager/values/ids.xml @@ -27,6 +27,9 @@ + + + diff --git a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js index dd29faeda83c3c..5c2c5d995ed373 100644 --- a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js +++ b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js @@ -194,6 +194,24 @@ class AccessibilityExample extends React.Component<{}> { Accessible view with label, hint, role, and state + + + + Mail Address + + First Name + + + ); }