Native ARIA Roles: Android Paper + Fabric (#37306)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/37306

### Stack

ARIA roles in React Native are implemented on top of accessibilityRole. This is lossy because there are many more ARIA roles than accessibilityRole. This is especially true for RN on desktop where accessibilityRole was designed around accessibility APIs only available on mobile.

This series of changes aims to change this implementation to instead pass the ARIA role to native, alongside any existing accessibilityRole. This gives the platform more control in exactly how to map an ARIA role to native behavior.

As an example, this would allow mapping any ARIA role to AutomationControlType on Windows without needing to fork to add new options to accessibilityRole.

It also allows greater implementation flexibility for other platforms down the line, but for now, iOS and Android behave the same as before (though with their implementation living in native).

### Diff

This replicates the roles to Java. When using MapBuffer we pass the role by ordinal, assuming they keep in sync. We otherwise still pass by string matching the JS side.

Previous implementation kept `accessibilityRole` on a view tag of the native view. This adds `role` as a view tag as well.

For now, to reuse the existing code, we then expose a single function to query `AccessibilityRole` from the combined view tags. This will do the same mapping previously done in JS, so that any code previously reading for an `AccessibilityRole` will now get one derived from the role view tag if present.

Changelog: [Internal]

Reviewed By: sammy-SC

Differential Revision: D45431381

fbshipit-source-id: a72c7880d41b5cf2c4e1c1f3ebfa6832ce8b4250
This commit is contained in:
Nick Gerleman
2023-05-13 13:12:42 -07:00
committed by Facebook GitHub Bot
parent 9db5fa215e
commit 02ec60bd3e
18 changed files with 274 additions and 26 deletions
@@ -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<T extends View, C extends LayoutShadowNode
@ReactProp(name = ViewProps.ACCESSIBILITY_ROLE)
public void setAccessibilityRole(@NonNull T view, @Nullable String accessibilityRole) {
if (accessibilityRole == null) {
return;
view.setTag(R.id.accessibility_role, null);
} else {
view.setTag(R.id.accessibility_role, AccessibilityRole.fromValue(accessibilityRole));
}
view.setTag(R.id.accessibility_role, AccessibilityRole.fromValue(accessibilityRole));
}
@Override
@@ -380,6 +382,16 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
}
}
@Override
@ReactProp(name = ViewProps.ROLE)
public void setRole(@NonNull T view, @Nullable String role) {
if (role == null) {
view.setTag(R.id.role, null);
} else {
view.setTag(R.id.role, Role.fromValue(role));
}
}
@Override
@Deprecated
@ReactProp(name = ViewProps.ROTATION)
@@ -70,6 +70,9 @@ public abstract class BaseViewManagerAdapter<T extends View>
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) {}
@@ -89,6 +89,9 @@ public abstract class BaseViewManagerDelegate<T extends View, U extends BaseView
case ViewProps.IMPORTANT_FOR_ACCESSIBILITY:
mViewManager.setImportantForAccessibility(view, (String) value);
break;
case ViewProps.ROLE:
mViewManager.setRole(view, (String) value);
break;
case ViewProps.NATIVE_ID:
mViewManager.setNativeId(view, (String) value);
break;
@@ -52,6 +52,8 @@ public interface BaseViewManagerInterface<T extends View> {
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);
@@ -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<Integer, String> 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));
@@ -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";
@@ -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));
}
@@ -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());
@@ -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();
}
}
@@ -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) {
@@ -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) {
@@ -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) {
@@ -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) {
@@ -44,4 +44,7 @@
<!-- tag is used store bitset of pointer events observed -->
<item type="id" name="pointer_events"/>
<!-- tag is used to store role tag-->
<item type="id" name="role"/>
</resources>
@@ -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);
}
}
@@ -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<int32_t>(*textAttributes.role));
}
return builder.build();
}
@@ -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<int32_t>(newProps.role));
}
}
#endif
@@ -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;