From 2d07d5f160efdd25f5b3dbfa65c13884df9f3117 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Sun, 4 Jun 2023 03:48:45 -0700 Subject: [PATCH] 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 --- .../View/RCTViewComponentView.mm | 4 +- packages/react-native/React/Views/RCTView.m | 6 +- .../react-native/React/Views/RCTViewManager.m | 132 +++++++++++++----- .../react-native/React/Views/UIView+React.h | 3 + .../react-native/React/Views/UIView+React.m | 37 +++++ .../components/view/AccessibilityProps.cpp | 40 +++--- .../view/accessibilityPropsConversions.h | 4 +- .../RCTAttributedTextUtils.h | 2 + .../RCTAttributedTextUtils.mm | 131 +---------------- 9 files changed, 178 insertions(+), 181 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index ecfe8bc408f..97feafc4c10 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -17,6 +17,7 @@ #import #import #import +#import #ifdef RCT_DYNAMIC_FRAMEWORKS #import @@ -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 diff --git a/packages/react-native/React/Views/RCTView.m b/packages/react-native/React/Views/RCTView.m index fc678c2577a..66c6c5978f8 100644 --- a/packages/react-native/React/Views/RCTView.m +++ b/packages/react-native/React/Views/RCTView.m @@ -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]; } diff --git a/packages/react-native/React/Views/RCTViewManager.m b/packages/react-native/React/Views/RCTViewManager.m index 2b8f2c0a33a..9a0af4e74ce 100644 --- a/packages/react-native/React/Views/RCTViewManager.m +++ b/packages/react-native/React/Views/RCTViewManager.m @@ -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) diff --git a/packages/react-native/React/Views/UIView+React.h b/packages/react-native/React/Views/UIView+React.h index 21a70337d7a..d378a8320ba 100644 --- a/packages/react-native/React/Views/UIView+React.h +++ b/packages/react-native/React/Views/UIView+React.h @@ -117,10 +117,13 @@ * Accessibility properties */ @property (nonatomic, copy) NSString *accessibilityRole; +@property (nonatomic, copy) NSString *role; @property (nonatomic, copy) NSDictionary *accessibilityState; @property (nonatomic, copy) NSArray *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 diff --git a/packages/react-native/React/Views/UIView+React.m b/packages/react-native/React/Views/UIView+React.m index 94ad951e717..7c6c71829ff 100644 --- a/packages/react-native/React/Views/UIView+React.m +++ b/packages/react-native/React/Views/UIView+React.m @@ -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 *)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 { diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp index ad6cf1ce83d..d9ebf62e9b5 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp @@ -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; } } diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/accessibilityPropsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/view/accessibilityPropsConversions.h index 9ee16674181..1690113e239 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/accessibilityPropsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/accessibilityPropsConversions.h @@ -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; } diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h index b09fd7115eb..fe0adae88f4 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h @@ -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"; /* diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm index 68388d1c839..7db4677a329 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm @@ -7,6 +7,7 @@ #import "RCTAttributedTextUtils.h" +#include #include #include #include @@ -288,130 +289,12 @@ NSDictionary *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];