Native ARIA Roles: iOS Paper + Fabric (#37309)

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

### 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 enables the `role` prop in iOS. ARIA role strings become convertible to `UIAccessibilityTrait`, which is the sink of most of the roles. We keep a list of traits derived from `role` vs `accessibilityRole` on the view, then inval and apply the result any time the prop for either is set.

Changelog: [Internal]

Reviewed By: sammy-SC

Differential Revision: D45432530

fbshipit-source-id: ced5be06e319c0b967d7511293e14e05147e5759
This commit is contained in:
Nick Gerleman
2023-06-04 03:48:45 -07:00
committed by Facebook GitHub Bot
parent d54f486fe6
commit 2d07d5f160
9 changed files with 178 additions and 181 deletions
@@ -17,6 +17,7 @@
#import <react/renderer/components/view/ViewComponentDescriptor.h>
#import <react/renderer/components/view/ViewEventEmitter.h>
#import <react/renderer/components/view/ViewProps.h>
#import <react/renderer/components/view/accessibilityPropsConversions.h>
#ifdef RCT_DYNAMIC_FRAMEWORKS
#import <React/RCTComponentViewFactory.h>
@@ -722,7 +723,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
}
NSMutableArray *valueComponents = [NSMutableArray new];
NSString *roleString = [NSString stringWithUTF8String:props.accessibilityRole.c_str()];
NSString *roleString = (props.role != Role::None) ? [NSString stringWithUTF8String:toString(props.role).c_str()]
: [NSString stringWithUTF8String:props.accessibilityRole.c_str()];
// In iOS, checkbox and radio buttons aren't recognized as traits. However,
// because our apps use checkbox and radio buttons often, we should announce
+5 -1
View File
@@ -332,7 +332,11 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
}
}
NSMutableArray *valueComponents = [NSMutableArray new];
NSString *roleDescription = self.accessibilityRole ? rolesAndStatesDescription[self.accessibilityRole] : nil;
// TODO: This logic makes VoiceOver describe some AccessibilityRole which do not have a backing UIAccessibilityTrait.
// It does not run on Fabric.
NSString *role = self.role ?: self.accessibilityRole;
NSString *roleDescription = role ? rolesAndStatesDescription[role] : nil;
if (roleDescription) {
[valueComponents addObject:roleDescription];
}
@@ -26,54 +26,97 @@
RCT_MULTI_ENUM_CONVERTER(
UIAccessibilityTraits,
(@{
@"none" : @(UIAccessibilityTraitNone),
@"adjustable" : @(UIAccessibilityTraitAdjustable),
@"alert" : @(UIAccessibilityTraitNone),
@"alertdialog" : @(UIAccessibilityTraitNone),
@"allowsDirectInteraction" : @(UIAccessibilityTraitAllowsDirectInteraction),
@"application" : @(UIAccessibilityTraitNone),
@"article" : @(UIAccessibilityTraitNone),
@"banner" : @(UIAccessibilityTraitNone),
@"button" : @(UIAccessibilityTraitButton),
@"cell" : @(UIAccessibilityTraitNone),
@"checkbox" : @(UIAccessibilityTraitNone),
@"columnheader" : @(UIAccessibilityTraitNone),
@"combobox" : @(UIAccessibilityTraitNone),
@"complementary" : @(UIAccessibilityTraitNone),
@"contentinfo" : @(UIAccessibilityTraitNone),
@"definition" : @(UIAccessibilityTraitNone),
@"dialog" : @(UIAccessibilityTraitNone),
@"directory" : @(UIAccessibilityTraitNone),
@"disabled" : @(UIAccessibilityTraitNotEnabled),
@"document" : @(UIAccessibilityTraitNone),
@"drawerlayout" : @(UIAccessibilityTraitNone),
@"dropdownlist" : @(UIAccessibilityTraitNone),
@"togglebutton" : @(UIAccessibilityTraitButton),
@"link" : @(UIAccessibilityTraitLink),
@"feed" : @(UIAccessibilityTraitNone),
@"figure" : @(UIAccessibilityTraitNone),
@"form" : @(UIAccessibilityTraitNone),
@"frequentUpdates" : @(UIAccessibilityTraitUpdatesFrequently),
@"grid" : @(UIAccessibilityTraitNone),
@"group" : @(UIAccessibilityTraitNone),
@"header" : @(UIAccessibilityTraitHeader),
@"search" : @(UIAccessibilityTraitSearchField),
@"heading" : @(UIAccessibilityTraitHeader),
@"horizontalscrollview" : @(UIAccessibilityTraitNone),
@"iconmenu" : @(UIAccessibilityTraitNone),
@"image" : @(UIAccessibilityTraitImage),
@"imagebutton" : @(UIAccessibilityTraitImage | UIAccessibilityTraitButton),
@"selected" : @(UIAccessibilityTraitSelected),
@"plays" : @(UIAccessibilityTraitPlaysSound),
@"img" : @(UIAccessibilityTraitImage),
@"key" : @(UIAccessibilityTraitKeyboardKey),
@"keyboardkey" : @(UIAccessibilityTraitKeyboardKey),
@"text" : @(UIAccessibilityTraitStaticText),
@"summary" : @(UIAccessibilityTraitSummaryElement),
@"disabled" : @(UIAccessibilityTraitNotEnabled),
@"frequentUpdates" : @(UIAccessibilityTraitUpdatesFrequently),
@"startsMedia" : @(UIAccessibilityTraitStartsMediaSession),
@"adjustable" : @(UIAccessibilityTraitAdjustable),
@"allowsDirectInteraction" : @(UIAccessibilityTraitAllowsDirectInteraction),
@"pageTurn" : @(UIAccessibilityTraitCausesPageTurn),
@"alert" : @(UIAccessibilityTraitNone),
@"checkbox" : @(UIAccessibilityTraitNone),
@"combobox" : @(UIAccessibilityTraitNone),
@"link" : @(UIAccessibilityTraitLink),
@"list" : @(UIAccessibilityTraitNone),
@"listitem" : @(UIAccessibilityTraitNone),
@"log" : @(UIAccessibilityTraitNone),
@"main" : @(UIAccessibilityTraitNone),
@"marquee" : @(UIAccessibilityTraitNone),
@"math" : @(UIAccessibilityTraitNone),
@"menu" : @(UIAccessibilityTraitNone),
@"menubar" : @(UIAccessibilityTraitNone),
@"menuitem" : @(UIAccessibilityTraitNone),
@"meter" : @(UIAccessibilityTraitNone),
@"navigation" : @(UIAccessibilityTraitNone),
@"none" : @(UIAccessibilityTraitNone),
@"note" : @(UIAccessibilityTraitNone),
@"option" : @(UIAccessibilityTraitNone),
@"pager" : @(UIAccessibilityTraitNone),
@"pageTurn" : @(UIAccessibilityTraitCausesPageTurn),
@"plays" : @(UIAccessibilityTraitPlaysSound),
@"presentation" : @(UIAccessibilityTraitNone),
@"progressbar" : @(UIAccessibilityTraitUpdatesFrequently),
@"radio" : @(UIAccessibilityTraitNone),
@"radiogroup" : @(UIAccessibilityTraitNone),
@"region" : @(UIAccessibilityTraitNone),
@"row" : @(UIAccessibilityTraitNone),
@"rowgroup" : @(UIAccessibilityTraitNone),
@"rowheader" : @(UIAccessibilityTraitNone),
@"scrollbar" : @(UIAccessibilityTraitNone),
@"scrollview" : @(UIAccessibilityTraitNone),
@"search" : @(UIAccessibilityTraitSearchField),
@"searchbox" : @(UIAccessibilityTraitNone),
@"selected" : @(UIAccessibilityTraitSelected),
@"separator" : @(UIAccessibilityTraitNone),
@"slider" : @(UIAccessibilityTraitNone),
@"slidingdrawer" : @(UIAccessibilityTraitNone),
@"spinbutton" : @(UIAccessibilityTraitNone),
@"startsMedia" : @(UIAccessibilityTraitStartsMediaSession),
@"status" : @(UIAccessibilityTraitNone),
@"summary" : @(UIAccessibilityTraitSummaryElement),
@"switch" : @(SwitchAccessibilityTrait),
@"tab" : @(UIAccessibilityTraitNone),
@"tabbar" : @(UIAccessibilityTraitTabBar),
@"table" : @(UIAccessibilityTraitNone),
@"tablist" : @(UIAccessibilityTraitNone),
@"tabpanel" : @(UIAccessibilityTraitNone),
@"term" : @(UIAccessibilityTraitNone),
@"text" : @(UIAccessibilityTraitStaticText),
@"timer" : @(UIAccessibilityTraitNone),
@"togglebutton" : @(UIAccessibilityTraitButton),
@"toolbar" : @(UIAccessibilityTraitNone),
@"pager" : @(UIAccessibilityTraitNone),
@"scrollview" : @(UIAccessibilityTraitNone),
@"horizontalscrollview" : @(UIAccessibilityTraitNone),
@"tooltip" : @(UIAccessibilityTraitNone),
@"tree" : @(UIAccessibilityTraitNone),
@"treegrid" : @(UIAccessibilityTraitNone),
@"treeitem" : @(UIAccessibilityTraitNone),
@"viewgroup" : @(UIAccessibilityTraitNone),
@"webview" : @(UIAccessibilityTraitNone),
@"drawerlayout" : @(UIAccessibilityTraitNone),
@"slidingdrawer" : @(UIAccessibilityTraitNone),
@"iconmenu" : @(UIAccessibilityTraitNone),
@"list" : @(UIAccessibilityTraitNone),
@"grid" : @(UIAccessibilityTraitNone),
}),
UIAccessibilityTraitNone,
unsignedLongLongValue)
@@ -182,22 +225,41 @@ RCT_CUSTOM_VIEW_PROPERTY(transform, CATransform3D, RCTView)
}
RCT_CUSTOM_VIEW_PROPERTY(accessibilityRole, UIAccessibilityTraits, RCTView)
{
UIAccessibilityTraits accessibilityRoleTraits =
json ? [RCTConvert UIAccessibilityTraits:json] : UIAccessibilityTraitNone;
if (view.reactAccessibilityElement.accessibilityRoleTraits != accessibilityRoleTraits) {
view.accessibilityRoleTraits = accessibilityRoleTraits;
view.reactAccessibilityElement.accessibilityRole = json ? [RCTConvert NSString:json] : nil;
[self updateAccessibilityTraitsForRole:view withDefaultView:defaultView];
}
}
RCT_CUSTOM_VIEW_PROPERTY(role, UIAccessibilityTraits, RCTView)
{
UIAccessibilityTraits roleTraits = json ? [RCTConvert UIAccessibilityTraits:json] : UIAccessibilityTraitNone;
if (view.reactAccessibilityElement.roleTraits != roleTraits) {
view.roleTraits = roleTraits;
view.reactAccessibilityElement.role = json ? [RCTConvert NSString:json] : nil;
[self updateAccessibilityTraitsForRole:view withDefaultView:defaultView];
}
}
- (void)updateAccessibilityTraitsForRole:(RCTView *)view withDefaultView:(RCTView *)defaultView
{
const UIAccessibilityTraits AccessibilityRolesMask = UIAccessibilityTraitNone | UIAccessibilityTraitButton |
UIAccessibilityTraitLink | UIAccessibilityTraitSearchField | UIAccessibilityTraitImage |
UIAccessibilityTraitKeyboardKey | UIAccessibilityTraitStaticText | UIAccessibilityTraitAdjustable |
UIAccessibilityTraitHeader | UIAccessibilityTraitSummaryElement | UIAccessibilityTraitTabBar |
UIAccessibilityTraitUpdatesFrequently | SwitchAccessibilityTrait;
view.reactAccessibilityElement.accessibilityTraits =
view.reactAccessibilityElement.accessibilityTraits & ~AccessibilityRolesMask;
UIAccessibilityTraits newTraits = json ? [RCTConvert UIAccessibilityTraits:json] : defaultView.accessibilityTraits;
if (newTraits != UIAccessibilityTraitNone) {
UIAccessibilityTraits maskedTraits = newTraits & AccessibilityRolesMask;
view.reactAccessibilityElement.accessibilityTraits |= maskedTraits;
} else {
NSString *role = json ? [RCTConvert NSString:json] : @"";
view.reactAccessibilityElement.accessibilityRole = role;
}
// Clear any existing traits set for AccessibilityRole
view.reactAccessibilityElement.accessibilityTraits &= ~(AccessibilityRolesMask);
view.reactAccessibilityElement.accessibilityTraits |= view.reactAccessibilityElement.role
? view.reactAccessibilityElement.roleTraits
: view.reactAccessibilityElement.accessibilityRole ? view.reactAccessibilityElement.accessibilityRoleTraits
: (defaultView.accessibilityTraits & AccessibilityRolesMask);
}
RCT_CUSTOM_VIEW_PROPERTY(accessibilityState, NSDictionary, RCTView)
@@ -117,10 +117,13 @@
* Accessibility properties
*/
@property (nonatomic, copy) NSString *accessibilityRole;
@property (nonatomic, copy) NSString *role;
@property (nonatomic, copy) NSDictionary<NSString *, id> *accessibilityState;
@property (nonatomic, copy) NSArray<NSDictionary *> *accessibilityActions;
@property (nonatomic, copy) NSDictionary *accessibilityValueInternal;
@property (nonatomic, copy) NSString *accessibilityLanguage;
@property (nonatomic) UIAccessibilityTraits accessibilityRoleTraits;
@property (nonatomic) UIAccessibilityTraits roleTraits;
/**
* Used in debugging to get a description of the view hierarchy rooted at
@@ -336,6 +336,16 @@
objc_setAssociatedObject(self, @selector(accessibilityRole), accessibilityRole, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)role
{
return objc_getAssociatedObject(self, _cmd);
}
- (void)setRole:(NSString *)role
{
objc_setAssociatedObject(self, @selector(role), role, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSDictionary<NSString *, id> *)accessibilityState
{
return objc_getAssociatedObject(self, _cmd);
@@ -356,6 +366,33 @@
self, @selector(accessibilityValueInternal), accessibilityValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIAccessibilityTraits)accessibilityRoleTraits
{
NSNumber *traitsAsNumber = objc_getAssociatedObject(self, _cmd);
return traitsAsNumber ? [traitsAsNumber unsignedLongLongValue] : UIAccessibilityTraitNone;
}
- (void)setAccessibilityRoleTraits:(UIAccessibilityTraits)accessibilityRoleTraits
{
objc_setAssociatedObject(
self,
@selector(accessibilityRoleTraits),
[NSNumber numberWithUnsignedLongLong:accessibilityRoleTraits],
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIAccessibilityTraits)roleTraits
{
NSNumber *traitsAsNumber = objc_getAssociatedObject(self, _cmd);
return traitsAsNumber ? [traitsAsNumber unsignedLongLongValue] : UIAccessibilityTraitNone;
}
- (void)setRoleTraits:(UIAccessibilityTraits)roleTraits
{
objc_setAssociatedObject(
self, @selector(roleTraits), [NSNumber numberWithUnsignedLongLong:roleTraits], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark - Debug
- (void)react_addRecursiveDescriptionToString:(NSMutableString *)string atLevel:(NSUInteger)level
{
@@ -171,14 +171,6 @@ AccessibilityProps::AccessibilityProps(
"importantForAccessibility",
sourceProps.importantForAccessibility,
ImportantForAccessibility::Auto)),
role(
CoreFeatures::enablePropIteratorSetter ? sourceProps.role
: convertRawProp(
context,
rawProps,
"role",
sourceProps.role,
{})),
testId(
CoreFeatures::enablePropIteratorSetter ? sourceProps.testId
: convertRawProp(
@@ -195,19 +187,31 @@ AccessibilityProps::AccessibilityProps(
// to work around here, and (2) would require very careful work to address
// this case and not regress the more common cases.
if (!CoreFeatures::enablePropIteratorSetter) {
const auto *rawPropValue =
auto *accessibilityRoleValue =
rawProps.at("accessibilityRole", nullptr, nullptr);
AccessibilityTraits traits;
std::string roleString;
if (rawPropValue == nullptr || !rawPropValue->hasValue()) {
traits = sourceProps.accessibilityTraits;
roleString = sourceProps.accessibilityRole;
auto *roleValue = rawProps.at("role", nullptr, nullptr);
auto *precedentRoleValue =
roleValue != nullptr ? roleValue : accessibilityRoleValue;
if (accessibilityRoleValue == nullptr ||
!accessibilityRoleValue->hasValue()) {
accessibilityRole = sourceProps.accessibilityRole;
} else {
fromRawValue(context, *rawPropValue, traits);
fromRawValue(context, *rawPropValue, roleString);
fromRawValue(context, *accessibilityRoleValue, accessibilityRole);
}
if (roleValue == nullptr || !roleValue->hasValue()) {
role = sourceProps.role;
} else {
fromRawValue(context, *roleValue, role);
}
if (precedentRoleValue == nullptr || !precedentRoleValue->hasValue()) {
accessibilityTraits = sourceProps.accessibilityTraits;
} else {
fromRawValue(context, *precedentRoleValue, accessibilityTraits);
}
accessibilityTraits = traits;
accessibilityRole = roleString;
}
}
@@ -30,7 +30,7 @@ inline void fromString(const std::string &string, AccessibilityTraits &result) {
result = AccessibilityTraits::Link;
return;
}
if (string == "image") {
if (string == "image" || string == "img") {
result = AccessibilityTraits::Image;
return;
}
@@ -78,7 +78,7 @@ inline void fromString(const std::string &string, AccessibilityTraits &result) {
result = AccessibilityTraits::CausesPageTurn;
return;
}
if (string == "header") {
if (string == "header" || string == "heading") {
result = AccessibilityTraits::Header;
return;
}
@@ -15,6 +15,8 @@ NS_ASSUME_NONNULL_BEGIN
NSString *const RCTAttributedStringIsHighlightedAttributeName = @"IsHighlighted";
NSString *const RCTAttributedStringEventEmitterKey = @"EventEmitter";
// String representation of either `role` or `accessibilityRole`
NSString *const RCTTextAttributesAccessibilityRoleAttributeName = @"AccessibilityRole";
/*
@@ -7,6 +7,7 @@
#import "RCTAttributedTextUtils.h"
#include <react/renderer/components/view/accessibilityPropsConversions.h>
#include <react/renderer/core/LayoutableShadowNode.h>
#include <react/renderer/textlayoutmanager/RCTFontProperties.h>
#include <react/renderer/textlayoutmanager/RCTFontUtils.h>
@@ -288,130 +289,12 @@ NSDictionary<NSAttributedStringKey, id> *RCTNSTextAttributesFromTextAttributes(T
attributes[RCTAttributedStringIsHighlightedAttributeName] = @YES;
}
if (textAttributes.accessibilityRole.has_value()) {
auto accessibilityRole = textAttributes.accessibilityRole.value();
switch (accessibilityRole) {
case AccessibilityRole::None:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("none");
break;
case AccessibilityRole::Button:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("button");
break;
case AccessibilityRole::Dropdownlist:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("dropdownlist");
break;
case AccessibilityRole::Togglebutton:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("togglebutton");
break;
case AccessibilityRole::Link:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("link");
break;
case AccessibilityRole::Search:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("search");
break;
case AccessibilityRole::Image:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("image");
break;
case AccessibilityRole::Keyboardkey:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("keyboardkey");
break;
case AccessibilityRole::Text:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("text");
break;
case AccessibilityRole::Adjustable:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("adjustable");
break;
case AccessibilityRole::Imagebutton:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("imagebutton");
break;
case AccessibilityRole::Header:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("header");
break;
case AccessibilityRole::Summary:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("summary");
break;
case AccessibilityRole::Alert:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("alert");
break;
case AccessibilityRole::Checkbox:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("checkbox");
break;
case AccessibilityRole::Combobox:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("combobox");
break;
case AccessibilityRole::Menu:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("menu");
break;
case AccessibilityRole::Menubar:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("menubar");
break;
case AccessibilityRole::Menuitem:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("menuitem");
break;
case AccessibilityRole::Progressbar:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("progressbar");
break;
case AccessibilityRole::Radio:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("radio");
break;
case AccessibilityRole::Radiogroup:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("radiogroup");
break;
case AccessibilityRole::Scrollbar:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("scrollbar");
break;
case AccessibilityRole::Spinbutton:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("spinbutton");
break;
case AccessibilityRole::Switch:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("switch");
break;
case AccessibilityRole::Tab:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("tab");
break;
case AccessibilityRole::Tabbar:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("tabbar");
break;
case AccessibilityRole::Tablist:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("tablist");
break;
case AccessibilityRole::Timer:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("timer");
break;
case AccessibilityRole::List:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("list");
break;
case AccessibilityRole::Toolbar:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("toolbar");
break;
case AccessibilityRole::Grid:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("grid");
break;
case AccessibilityRole::Pager:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("pager");
break;
case AccessibilityRole::Scrollview:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("scrollview");
break;
case AccessibilityRole::Horizontalscrollview:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("horizontalscrollview");
break;
case AccessibilityRole::Viewgroup:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("viewgroup");
break;
case AccessibilityRole::Webview:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("webview");
break;
case AccessibilityRole::Drawerlayout:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("drawerlayout");
break;
case AccessibilityRole::Slidingdrawer:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("slidingdrawer");
break;
case AccessibilityRole::Iconmenu:
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("iconmenu");
break;
};
if (textAttributes.role.has_value()) {
std::string roleStr = toString(textAttributes.role.value());
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = [NSString stringWithCString:roleStr.c_str()];
} else if (textAttributes.accessibilityRole.has_value()) {
std::string roleStr = toString(textAttributes.accessibilityRole.value());
attributes[RCTTextAttributesAccessibilityRoleAttributeName] = [NSString stringWithCString:roleStr.c_str()];
}
return [attributes copy];