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 b15a2984f20..b8f10af4da9 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 @@ -26,6 +26,7 @@ import com.facebook.react.bridge.ReadableType; import com.facebook.react.common.MapBuilder; import com.facebook.react.common.ReactConstants; import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole; +import com.facebook.react.uimanager.ReactAccessibilityDelegate.Role; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.events.PointerEventHelper; import com.facebook.react.uimanager.util.ReactFindViewUtil; @@ -234,9 +235,10 @@ public abstract class BaseViewManager public void setImportantForAccessibility( @NonNull T view, @Nullable String importantForAccessibility) {} + @Override + public void setRole(@NonNull T view, @Nullable String role) {} + @Override public void setNativeId(@NonNull T view, String nativeId) {} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java index bb809381494..e59a1c4da3b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java @@ -89,6 +89,9 @@ public abstract class BaseViewManagerDelegate { void setImportantForAccessibility(T view, @Nullable String importantForAccessibility); + void setRole(T view, @Nullable String role); + void setNativeId(T view, @Nullable String nativeId); void setAccessibilityLabelledBy(T view, @Nullable Dynamic nativeId); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java index 626232ed0f3..531e4591ba6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java @@ -96,6 +96,87 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper { mHandler.sendMessageDelayed(msg, TIMEOUT_SEND_ACCESSIBILITY_EVENT); } + /** + * An ARIA Role representable by View's `role` prop. Ordinals should be kept in sync with + * `facebook::react::Role`. + */ + public enum Role { + ALERT, + ALERTDIALOG, + APPLICATION, + ARTICLE, + BANNER, + BUTTON, + CELL, + CHECKBOX, + COLUMNHEADER, + COMBOBOX, + COMPLEMENTARY, + CONTENTINFO, + DEFINITION, + DIALOG, + DIRECTORY, + DOCUMENT, + FEED, + FIGURE, + FORM, + GRID, + GROUP, + HEADING, + IMG, + LINK, + LIST, + LISTITEM, + LOG, + MAIN, + MARQUEE, + MATH, + MENU, + MENUBAR, + MENUITEM, + METER, + NAVIGATION, + NONE, + NOTE, + OPTION, + PRESENTATION, + PROGRESSBAR, + RADIO, + RADIOGROUP, + REGION, + ROW, + ROWGROUP, + ROWHEADER, + SCROLLBAR, + SEARCHBOX, + SEPARATOR, + SLIDER, + SPINBUTTON, + STATUS, + SUMMARY, + SWITCH, + TAB, + TABLE, + TABLIST, + TABPANEL, + TERM, + TIMER, + TOOLBAR, + TOOLTIP, + TREE, + TREEGRID, + TREEITEM; + + public static @Nullable Role fromValue(@Nullable String value) { + for (Role role : Role.values()) { + if (role.name().equalsIgnoreCase(value)) { + return role; + } + } + return null; + } + } + /** * These roles are defined by Google's TalkBack screen reader, and this list should be kept up to * date with their implementation. Details can be seen in their source code here: @@ -221,6 +302,75 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper { } throw new IllegalArgumentException("Invalid accessibility role value: " + value); } + + public static @Nullable AccessibilityRole fromRole(Role role) { + switch (role) { + case ALERT: + return AccessibilityRole.ALERT; + case BUTTON: + return AccessibilityRole.BUTTON; + case CHECKBOX: + return AccessibilityRole.CHECKBOX; + case COMBOBOX: + return AccessibilityRole.COMBOBOX; + case GRID: + return AccessibilityRole.GRID; + case HEADING: + return AccessibilityRole.HEADER; + case IMG: + return AccessibilityRole.IMAGE; + case LINK: + return AccessibilityRole.LINK; + case LIST: + return AccessibilityRole.LIST; + case MENU: + return AccessibilityRole.MENU; + case MENUBAR: + return AccessibilityRole.MENUBAR; + case MENUITEM: + return AccessibilityRole.MENUITEM; + case NONE: + return AccessibilityRole.NONE; + case PROGRESSBAR: + return AccessibilityRole.PROGRESSBAR; + case RADIO: + return AccessibilityRole.RADIO; + case RADIOGROUP: + return AccessibilityRole.RADIOGROUP; + case SCROLLBAR: + return AccessibilityRole.SCROLLBAR; + case SEARCHBOX: + return AccessibilityRole.SEARCH; + case SLIDER: + return AccessibilityRole.ADJUSTABLE; + case SPINBUTTON: + return AccessibilityRole.SPINBUTTON; + case SUMMARY: + return AccessibilityRole.SUMMARY; + case SWITCH: + return AccessibilityRole.SWITCH; + case TAB: + return AccessibilityRole.TAB; + case TABLIST: + return AccessibilityRole.TABLIST; + case TIMER: + return AccessibilityRole.TIMER; + case TOOLBAR: + return AccessibilityRole.TOOLBAR; + default: + // No mapping from ARIA role to AccessibilityRole + return null; + } + } + + public static @Nullable AccessibilityRole fromViewTag(View view) { + Role role = (Role) view.getTag(R.id.role); + if (role != null) { + return AccessibilityRole.fromRole(role); + } else { + return (AccessibilityRole) view.getTag(R.id.accessibility_role); + } + } } private final HashMap mAccessibilityActionsMap; @@ -267,8 +417,7 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper { ? AccessibilityNodeInfoCompat.ACTION_COLLAPSE : AccessibilityNodeInfoCompat.ACTION_EXPAND); } - final AccessibilityRole accessibilityRole = - (AccessibilityRole) host.getTag(R.id.accessibility_role); + final AccessibilityRole accessibilityRole = AccessibilityRole.fromViewTag(host); final String accessibilityHint = (String) host.getTag(R.id.accessibility_hint); if (accessibilityRole != null) { setRole(info, accessibilityRole, host.getContext()); @@ -551,7 +700,8 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper { || view.getTag(R.id.accessibility_actions) != null || view.getTag(R.id.react_test_id) != null || view.getTag(R.id.accessibility_collection_item) != null - || view.getTag(R.id.accessibility_links) != null)) { + || view.getTag(R.id.accessibility_links) != null + || view.getTag(R.id.role) != null)) { ViewCompat.setAccessibilityDelegate( view, new ReactAccessibilityDelegate(view, originalFocus, originalImportantForAccessibility)); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java index 3f76fa7dd3d..cc9f7178e65 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java @@ -166,6 +166,7 @@ public class ViewProps { public static final String ACCESSIBILITY_VALUE = "accessibilityValue"; public static final String ACCESSIBILITY_LABELLED_BY = "accessibilityLabelledBy"; public static final String IMPORTANT_FOR_ACCESSIBILITY = "importantForAccessibility"; + public static final String ROLE = "role"; // DEPRECATED public static final String ROTATION = "rotation"; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayout.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayout.java index 40392887a1c..d5d3931b00b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayout.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayout.java @@ -42,8 +42,8 @@ import com.facebook.react.uimanager.events.NativeGestureUtil; public void onInitializeAccessibilityNodeInfo( View host, AccessibilityNodeInfoCompat info) { super.onInitializeAccessibilityNodeInfo(host, info); - final AccessibilityRole accessibilityRole = - (AccessibilityRole) host.getTag(R.id.accessibility_role); + + final AccessibilityRole accessibilityRole = AccessibilityRole.fromViewTag(host); if (accessibilityRole != null) { info.setClassName(AccessibilityRole.getValue(accessibilityRole)); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewAccessibilityDelegate.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewAccessibilityDelegate.java index bc122f2ee3f..adbb78611c6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewAccessibilityDelegate.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewAccessibilityDelegate.java @@ -17,6 +17,7 @@ import com.facebook.react.bridge.AssertionException; import com.facebook.react.bridge.ReactSoftExceptionLogger; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.ReactAccessibilityDelegate; +import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole; public class ReactScrollViewAccessibilityDelegate extends AccessibilityDelegateCompat { private final String TAG = ReactScrollViewAccessibilityDelegate.class.getSimpleName(); @@ -122,8 +123,8 @@ public class ReactScrollViewAccessibilityDelegate extends AccessibilityDelegateC private void onInitializeAccessibilityNodeInfoInternal( View view, AccessibilityNodeInfoCompat info) { - final ReactAccessibilityDelegate.AccessibilityRole accessibilityRole = - (ReactAccessibilityDelegate.AccessibilityRole) view.getTag(R.id.accessibility_role); + + final AccessibilityRole accessibilityRole = AccessibilityRole.fromViewTag(view); if (accessibilityRole != null) { ReactAccessibilityDelegate.setRole(info, accessibilityRole, view.getContext()); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java index 12c1c5e0572..4fd3a8165f4 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java @@ -26,6 +26,8 @@ import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.NativeViewHierarchyOptimizer; import com.facebook.react.uimanager.PixelUtil; +import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole; +import com.facebook.react.uimanager.ReactAccessibilityDelegate.Role; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; @@ -36,7 +38,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; /** * {@link ReactShadowNode} abstract class for spannable text nodes. @@ -180,7 +181,11 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode { new SetSpanOperation( start, end, new ReactBackgroundColorSpan(textShadowNode.mBackgroundColor))); } - if (textShadowNode.mIsAccessibilityLink) { + boolean roleIsLink = + textShadowNode.mRole != null + ? textShadowNode.mRole == Role.LINK + : textShadowNode.mAccessibilityRole == AccessibilityRole.LINK; + if (roleIsLink) { ops.add( new SetSpanOperation(start, end, new ReactClickableSpan(textShadowNode.getReactTag()))); } @@ -325,7 +330,9 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode { protected int mColor; protected boolean mIsBackgroundColorSet = false; protected int mBackgroundColor; - protected boolean mIsAccessibilityLink = false; + + protected @Nullable AccessibilityRole mAccessibilityRole = null; + protected @Nullable Role mRole = null; protected int mNumberOfLines = UNSET; protected int mTextAlign = Gravity.NO_GRAVITY; @@ -499,9 +506,17 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode { } @ReactProp(name = ViewProps.ACCESSIBILITY_ROLE) - public void setIsAccessibilityLink(@Nullable String accessibilityRole) { + public void setAccessibilityRole(@Nullable String accessibilityRole) { if (isVirtual()) { - mIsAccessibilityLink = Objects.equals(accessibilityRole, "link"); + mAccessibilityRole = AccessibilityRole.fromValue(accessibilityRole); + markUpdated(); + } + } + + @ReactProp(name = ViewProps.ROLE) + public void setRole(@Nullable String role) { + if (isVirtual()) { + mRole = Role.fromValue(role); markUpdated(); } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java index bc53523c815..eb03a50743f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java @@ -19,7 +19,8 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.mapbuffer.MapBuffer; import com.facebook.react.uimanager.PixelUtil; -import com.facebook.react.uimanager.ReactAccessibilityDelegate; +import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole; +import com.facebook.react.uimanager.ReactAccessibilityDelegate.Role; import com.facebook.react.uimanager.ReactStylesDiffMap; import com.facebook.react.uimanager.ViewProps; import java.util.ArrayList; @@ -56,6 +57,8 @@ public class TextAttributeProps { public static final short TA_KEY_IS_HIGHLIGHTED = 22; public static final short TA_KEY_LAYOUT_DIRECTION = 23; public static final short TA_KEY_ACCESSIBILITY_ROLE = 24; + public static final short TA_KEY_LINE_BREAK_STRATEGY = 25; + public static final short TA_KEY_ROLE = 26; public static final int UNSET = -1; @@ -103,9 +106,8 @@ public class TextAttributeProps { protected boolean mIsLineThroughTextDecorationSet = false; protected boolean mIncludeFontPadding = true; - protected @Nullable ReactAccessibilityDelegate.AccessibilityRole mAccessibilityRole = null; - protected boolean mIsAccessibilityRoleSet = false; - protected boolean mIsAccessibilityLink = false; + protected @Nullable AccessibilityRole mAccessibilityRole = null; + protected @Nullable Role mRole = null; protected int mFontStyle = UNSET; protected int mFontWeight = UNSET; @@ -214,6 +216,9 @@ public class TextAttributeProps { case TA_KEY_ACCESSIBILITY_ROLE: result.setAccessibilityRole(entry.getStringValue()); break; + case TA_KEY_ROLE: + result.setRole(Role.values()[entry.getIntValue()]); + break; } } @@ -254,6 +259,7 @@ public class TextAttributeProps { result.setTextTransform(getStringProp(props, PROP_TEXT_TRANSFORM)); result.setLayoutDirection(getStringProp(props, ViewProps.LAYOUT_DIRECTION)); result.setAccessibilityRole(getStringProp(props, ViewProps.ACCESSIBILITY_ROLE)); + result.setRole(getStringProp(props, ViewProps.ROLE)); return result; } @@ -618,15 +624,25 @@ public class TextAttributeProps { } private void setAccessibilityRole(@Nullable String accessibilityRole) { - if (accessibilityRole != null) { - mIsAccessibilityRoleSet = true; - mAccessibilityRole = - ReactAccessibilityDelegate.AccessibilityRole.fromValue(accessibilityRole); - mIsAccessibilityLink = - mAccessibilityRole.equals(ReactAccessibilityDelegate.AccessibilityRole.LINK); + if (accessibilityRole == null) { + mAccessibilityRole = null; + } else { + mAccessibilityRole = AccessibilityRole.fromValue(accessibilityRole); } } + private void setRole(@Nullable String role) { + if (role == null) { + mRole = null; + } else { + mRole = Role.fromValue(role); + } + } + + private void setRole(Role role) { + mRole = role; + } + public static int getTextBreakStrategy(@Nullable String textBreakStrategy) { int androidTextBreakStrategy = DEFAULT_BREAK_STRATEGY; if (textBreakStrategy != null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java index 44195221646..040171ed008 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java @@ -33,6 +33,8 @@ import com.facebook.react.bridge.ReadableNativeMap; import com.facebook.react.bridge.WritableArray; import com.facebook.react.common.build.ReactBuildConfig; import com.facebook.react.uimanager.PixelUtil; +import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole; +import com.facebook.react.uimanager.ReactAccessibilityDelegate.Role; import com.facebook.react.uimanager.ReactStylesDiffMap; import com.facebook.react.uimanager.ViewProps; import com.facebook.yoga.YogaConstants; @@ -126,7 +128,11 @@ public class TextLayoutManager { sb.length(), new TextInlineViewPlaceholderSpan(reactTag, (int) width, (int) height))); } else if (end >= start) { - if (textAttributes.mIsAccessibilityLink) { + boolean roleIsLink = + textAttributes.mRole != null + ? textAttributes.mRole == Role.LINK + : textAttributes.mAccessibilityRole == AccessibilityRole.LINK; + if (roleIsLink) { ops.add(new SetSpanOperation(start, end, new ReactClickableSpan(reactTag))); } if (textAttributes.mIsColorSet) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java index bc0a56384b9..92167031088 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java @@ -33,6 +33,8 @@ import com.facebook.react.common.build.ReactBuildConfig; import com.facebook.react.common.mapbuffer.MapBuffer; import com.facebook.react.common.mapbuffer.ReadableMapBuffer; import com.facebook.react.uimanager.PixelUtil; +import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole; +import com.facebook.react.uimanager.ReactAccessibilityDelegate.Role; import com.facebook.yoga.YogaConstants; import com.facebook.yoga.YogaMeasureMode; import com.facebook.yoga.YogaMeasureOutput; @@ -146,7 +148,11 @@ public class TextLayoutManagerMapBuffer { sb.length(), new TextInlineViewPlaceholderSpan(reactTag, (int) width, (int) height))); } else if (end >= start) { - if (textAttributes.mIsAccessibilityLink) { + boolean roleIsLink = + textAttributes.mRole != null + ? textAttributes.mRole == Role.LINK + : textAttributes.mAccessibilityRole == AccessibilityRole.LINK; + if (roleIsLink) { ops.add(new SetSpanOperation(start, end, new ReactClickableSpan(reactTag))); } if (textAttributes.mIsColorSet) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactMapBufferPropSetter.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactMapBufferPropSetter.kt index a7effcdab17..7447e45313a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactMapBufferPropSetter.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactMapBufferPropSetter.kt @@ -10,6 +10,7 @@ package com.facebook.react.views.view import android.graphics.Color import android.graphics.Rect import androidx.core.view.ViewCompat +import com.facebook.react.R import com.facebook.react.bridge.DynamicFromObject import com.facebook.react.bridge.JavaOnlyArray import com.facebook.react.bridge.JavaOnlyMap @@ -17,6 +18,7 @@ import com.facebook.react.bridge.ReadableMap import com.facebook.react.common.mapbuffer.MapBuffer import com.facebook.react.uimanager.PixelUtil import com.facebook.react.uimanager.PointerEvents +import com.facebook.react.uimanager.ReactAccessibilityDelegate.Role object ReactMapBufferPropSetter { // ViewProps values @@ -64,6 +66,7 @@ object ReactMapBufferPropSetter { private const val VP_POINTER_OVER_CAPTURE = 44 private const val VP_BORDER_CURVES = 45 // iOS only private const val VP_FG_COLOR = 46 // iOS only? + private const val VP_ROLE = 47 // Yoga values private const val YG_BORDER_WIDTH = 100 @@ -180,6 +183,9 @@ object ReactMapBufferPropSetter { VP_IMPORTANT_FOR_ACCESSIBILITY -> { view.importantForAccessibility(entry.intValue) } + VP_ROLE -> { + view.role(entry.intValue) + } VP_NATIVE_BACKGROUND -> { viewManager.nativeBackground(view, entry.mapBufferValue) } @@ -422,6 +428,10 @@ object ReactMapBufferPropSetter { ViewCompat.setImportantForAccessibility(this, mode) } + private fun ReactViewGroup.role(value: Int) { + setTag(R.id.role, Role.values()[value]) + } + private fun ReactViewGroup.pointerEvents(value: Int) { val pointerEvents = when (value) { diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml index 6324b85af44..d2928f810df 100644 --- a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml @@ -44,4 +44,7 @@ + + + diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.java index 95642f5edd9..83e32f7287a 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.java +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.java @@ -14,6 +14,7 @@ import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.JavaOnlyMap; import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole; +import com.facebook.react.uimanager.ReactAccessibilityDelegate.Role; import com.facebook.react.views.view.ReactViewGroup; import com.facebook.react.views.view.ReactViewManager; import java.util.Locale; @@ -78,4 +79,10 @@ public class BaseViewManagerTest { assertThat(mView.getTag(R.id.accessibility_state)).isEqualTo(accessibilityState); assertThat(mView.isSelected()).isEqualTo(true); } + + @Test + public void testRoleList() { + mViewManager.setRole(mView, "list"); + assertThat(mView.getTag(R.id.role)).isEqualTo(Role.LIST); + } } diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h index b2ae66a5b95..0d54c7e7749 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h @@ -968,6 +968,7 @@ constexpr static MapBuffer::Key TA_KEY_IS_HIGHLIGHTED = 22; constexpr static MapBuffer::Key TA_KEY_LAYOUT_DIRECTION = 23; constexpr static MapBuffer::Key TA_KEY_ACCESSIBILITY_ROLE = 24; constexpr static MapBuffer::Key TA_KEY_LINE_BREAK_STRATEGY = 25; +constexpr static MapBuffer::Key TA_KEY_ROLE = 26; // constants for ParagraphAttributes serialization constexpr static MapBuffer::Key PA_KEY_MAX_NUMBER_OF_LINES = 0; @@ -1120,6 +1121,9 @@ inline MapBuffer toMapBuffer(const TextAttributes &textAttributes) { builder.putString( TA_KEY_ACCESSIBILITY_ROLE, toString(*textAttributes.accessibilityRole)); } + if (textAttributes.role.has_value()) { + builder.putInt(TA_KEY_ROLE, static_cast(*textAttributes.role)); + } return builder.build(); } diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPropsMapBuffer.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPropsMapBuffer.cpp index c866cb23a46..a9fa5e60e2c 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPropsMapBuffer.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPropsMapBuffer.cpp @@ -158,6 +158,10 @@ void AccessibilityProps::propsDiffMapBuffer( } builder.putInt(AP_IMPORTANT_FOR_ACCESSIBILITY, value); } + + if (oldProps.role != newProps.role) { + builder.putInt(AP_ROLE, static_cast(newProps.role)); + } } #endif diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPropsMapBuffer.h b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPropsMapBuffer.h index 37dd41eeec9..51d96ee40a0 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPropsMapBuffer.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPropsMapBuffer.h @@ -14,6 +14,9 @@ namespace facebook::react { +// TODO: "AP" (Accessibility Props) are interleaved with "VP" (View Props). +// Ordinals must be unique between them. + constexpr MapBuffer::Key AP_ACCESSIBILITY_ACTIONS = 0; constexpr MapBuffer::Key AP_ACCESSIBILITY_HINT = 1; constexpr MapBuffer::Key AP_ACCESSIBILITY_LABEL = 2; @@ -25,6 +28,8 @@ constexpr MapBuffer::Key AP_ACCESSIBILITY_VALUE = 7; constexpr MapBuffer::Key AP_ACCESSIBLE = 8; constexpr MapBuffer::Key AP_IMPORTANT_FOR_ACCESSIBILITY = 19; +constexpr MapBuffer::Key AP_ROLE = 47; + // AccessibilityAction values constexpr MapBuffer::Key ACCESSIBILITY_ACTION_NAME = 0; constexpr MapBuffer::Key ACCESSIBILITY_ACTION_LABEL = 1;