From 9db5fa215ed2be49cc28846385494cd1a2e44e30 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Sat, 13 May 2023 11:03:19 -0700 Subject: [PATCH] Native ARIA Roles: Shared code (#37305) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37305 ### 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`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.automation.peers.automationcontroltype?view=windowsdesktop-7.0&viewFallbackFrom=dotnet-uwp-10.0) 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 syncs the Fabric representations of Roles to the current state of the world in JS, and adds usage to the view configs. 1. `Role` enum for the View `role` prop (ARIA role) 2. Sync enums and conversions to JS `AccessibilityRole` 1. This parsing is done for `TextAttributes` only. `View` uses the string directly 2. Add ARIA roles, and parse those to their enum form. 3. Move enums from attributedstring primitves to accessibility primitives 3. Add to viewconfig to allow it to be passed Changelog: [Internal] Reviewed By: sammy-SC Differential Revision: D45431372 fbshipit-source-id: 0150538345bbb6cb4d9426c4eebd0f67c2a33f3d --- .../View/ReactNativeViewAttributes.js | 1 + .../NativeComponent/BaseViewConfig.android.js | 1 + .../NativeComponent/BaseViewConfig.ios.js | 1 + .../renderer/attributedstring/CMakeLists.txt | 1 + .../attributedstring/TextAttributes.cpp | 4 + .../attributedstring/TextAttributes.h | 5 +- .../renderer/attributedstring/conversions.h | 145 +----- .../renderer/attributedstring/primitives.h | 38 -- .../components/text/BaseTextProps.cpp | 8 + .../components/view/AccessibilityPrimitives.h | 127 +++++ .../components/view/AccessibilityProps.cpp | 9 + .../components/view/AccessibilityProps.h | 1 + .../view/accessibilityPropsConversions.h | 482 ++++++++++++++++++ .../RCTAttributedTextUtils.mm | 48 +- 14 files changed, 682 insertions(+), 189 deletions(-) diff --git a/packages/react-native/Libraries/Components/View/ReactNativeViewAttributes.js b/packages/react-native/Libraries/Components/View/ReactNativeViewAttributes.js index f4ea0d2c1f5..e27df43205f 100644 --- a/packages/react-native/Libraries/Components/View/ReactNativeViewAttributes.js +++ b/packages/react-native/Libraries/Components/View/ReactNativeViewAttributes.js @@ -35,6 +35,7 @@ const UIView = { collapsable: true, needsOffscreenAlphaCompositing: true, style: ReactNativeStyleAttributes, + role: true, }; const RCTView = { diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js index 5cd505439ea..fef57a1aea9 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js @@ -179,6 +179,7 @@ const validAttributesForNonEventProps = { accessibilityActions: true, accessibilityValue: true, importantForAccessibility: true, + role: true, rotation: true, scaleX: true, scaleY: true, diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js index 9c1e72fa1a1..bb13443bf44 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js @@ -198,6 +198,7 @@ const validAttributesForNonEventProps = { nativeID: true, pointerEvents: true, removeClippedSubviews: true, + role: true, borderRadius: true, borderColor: {process: require('../StyleSheet/processColor').default}, borderCurve: true, diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt index b5399e44e48..cc34a3fe959 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt @@ -26,6 +26,7 @@ target_link_libraries(react_render_attributedstring glog glog_init react_debug + rrc_view react_render_core react_render_debug react_render_graphics diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp b/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp index 2112a70a3d2..dfeaafef0d2 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp @@ -101,6 +101,7 @@ void TextAttributes::apply(TextAttributes textAttributes) { accessibilityRole = textAttributes.accessibilityRole.has_value() ? textAttributes.accessibilityRole : accessibilityRole; + role = textAttributes.role.has_value() ? textAttributes.role : role; } #pragma mark - Operators @@ -126,6 +127,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const { isHighlighted, layoutDirection, accessibilityRole, + role, textTransform) == std::tie( rhs.foregroundColor, @@ -147,6 +149,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const { rhs.isHighlighted, rhs.layoutDirection, rhs.accessibilityRole, + rhs.role, rhs.textTransform) && floatEquality(opacity, rhs.opacity) && floatEquality(fontSize, rhs.fontSize) && @@ -215,6 +218,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const { debugStringConvertibleItem("isHighlighted", isHighlighted), debugStringConvertibleItem("layoutDirection", layoutDirection), debugStringConvertibleItem("accessibilityRole", accessibilityRole), + debugStringConvertibleItem("role", role), }; } #endif diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h index fd552ca7817..a681e9b4837 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -80,6 +81,7 @@ class TextAttributes : public DebugStringConvertible { // construction. std::optional layoutDirection{}; std::optional accessibilityRole{}; + std::optional role{}; #pragma mark - Operations @@ -131,7 +133,8 @@ struct hash { textAttributes.textShadowColor, textAttributes.isHighlighted, textAttributes.layoutDirection, - textAttributes.accessibilityRole); + textAttributes.accessibilityRole, + textAttributes.role); } }; } // namespace std diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h index 20383dd113b..b2ae66a5b95 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -641,150 +642,6 @@ inline std::string toString(const TextDecorationStyle &textDecorationStyle) { return "solid"; } -inline std::string toString(const AccessibilityRole &accessibilityRole) { - switch (accessibilityRole) { - case AccessibilityRole::None: - return "none"; - case AccessibilityRole::Button: - return "button"; - case AccessibilityRole::Link: - return "link"; - case AccessibilityRole::Search: - return "search"; - case AccessibilityRole::Image: - return "image"; - case AccessibilityRole::Imagebutton: - return "imagebutton"; - case AccessibilityRole::Keyboardkey: - return "keyboardkey"; - case AccessibilityRole::Text: - return "text"; - case AccessibilityRole::Adjustable: - return "adjustable"; - case AccessibilityRole::Summary: - return "summary"; - case AccessibilityRole::Header: - return "header"; - case AccessibilityRole::Alert: - return "alert"; - case AccessibilityRole::Checkbox: - return "checkbox"; - case AccessibilityRole::Combobox: - return "combobox"; - case AccessibilityRole::Menu: - return "menu"; - case AccessibilityRole::Menubar: - return "menubar"; - case AccessibilityRole::Menuitem: - return "menuitem"; - case AccessibilityRole::Progressbar: - return "progressbar"; - case AccessibilityRole::Radio: - return "radio"; - case AccessibilityRole::Radiogroup: - return "radiogroup"; - case AccessibilityRole::Scrollbar: - return "scrollbar"; - case AccessibilityRole::Spinbutton: - return "spinbutton"; - case AccessibilityRole::Switch: - return "switch"; - case AccessibilityRole::Tab: - return "tab"; - case AccessibilityRole::TabBar: - return "tabbar"; - case AccessibilityRole::Tablist: - return "tablist"; - case AccessibilityRole::Timer: - return "timer"; - case AccessibilityRole::Toolbar: - return "toolbar"; - } - - LOG(ERROR) << "Unsupported AccessibilityRole value"; - react_native_expect(false); - // sane default for prod - return "none"; -} - -inline void fromRawValue( - const PropsParserContext &context, - const RawValue &value, - AccessibilityRole &result) { - react_native_expect(value.hasType()); - if (value.hasType()) { - auto string = (std::string)value; - if (string == "none") { - result = AccessibilityRole::None; - } else if (string == "button" || string == "togglebutton") { - result = AccessibilityRole::Button; - } else if (string == "link") { - result = AccessibilityRole::Link; - } else if (string == "search") { - result = AccessibilityRole::Search; - } else if (string == "image") { - result = AccessibilityRole::Image; - } else if (string == "imagebutton") { - result = AccessibilityRole::Imagebutton; - } else if (string == "keyboardkey") { - result = AccessibilityRole::Keyboardkey; - } else if (string == "text") { - result = AccessibilityRole::Text; - } else if (string == "adjustable") { - result = AccessibilityRole::Adjustable; - } else if (string == "summary") { - result = AccessibilityRole::Summary; - } else if (string == "header") { - result = AccessibilityRole::Header; - } else if (string == "alert") { - result = AccessibilityRole::Alert; - } else if (string == "checkbox") { - result = AccessibilityRole::Checkbox; - } else if (string == "combobox") { - result = AccessibilityRole::Combobox; - } else if (string == "menu") { - result = AccessibilityRole::Menu; - } else if (string == "menubar") { - result = AccessibilityRole::Menubar; - } else if (string == "menuitem") { - result = AccessibilityRole::Menuitem; - } else if (string == "progressbar") { - result = AccessibilityRole::Progressbar; - } else if (string == "radio") { - result = AccessibilityRole::Radio; - } else if (string == "radiogroup") { - result = AccessibilityRole::Radiogroup; - } else if (string == "scrollbar") { - result = AccessibilityRole::Scrollbar; - } else if (string == "spinbutton") { - result = AccessibilityRole::Spinbutton; - } else if (string == "switch") { - result = AccessibilityRole::Switch; - } else if (string == "tab") { - result = AccessibilityRole::Tab; - } else if (string == "tabbar") { - result = AccessibilityRole::TabBar; - } else if (string == "tablist") { - result = AccessibilityRole::Tablist; - } else if (string == "timer") { - result = AccessibilityRole::Timer; - } else if (string == "toolbar") { - result = AccessibilityRole::Toolbar; - } else { - LOG(ERROR) << "Unsupported AccessibilityRole value: " << string; - react_native_expect(false); - // sane default for prod - result = AccessibilityRole::None; - } - return; - } - - LOG(ERROR) << "Unsupported AccessibilityRole type"; - react_native_expect(false); - // sane default for prod - result = AccessibilityRole::None; -} - inline std::string toString(const HyphenationFrequency &hyphenationFrequency) { switch (hyphenationFrequency) { case HyphenationFrequency::None: diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/primitives.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/primitives.h index 75c5ce77f0d..30a6e002daf 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/primitives.h @@ -105,37 +105,6 @@ enum class TextDecorationLineType { enum class TextDecorationStyle { Solid, Double, Dotted, Dashed }; -enum class AccessibilityRole { - None, - Button, - Link, - Search, - Image, - Imagebutton, - Keyboardkey, - Text, - Adjustable, - Summary, - Header, - Alert, - Checkbox, - Combobox, - Menu, - Menubar, - Menuitem, - Progressbar, - Radio, - Radiogroup, - Scrollbar, - Spinbutton, - Switch, - Tab, - TabBar, - Tablist, - Timer, - Toolbar, -}; - enum class TextTransform { None, Uppercase, @@ -223,13 +192,6 @@ struct hash { } }; -template <> -struct hash { - size_t operator()(const facebook::react::AccessibilityRole &v) const { - return hash()(static_cast(v)); - } -}; - template <> struct hash { size_t operator()(const facebook::react::TextTransform &v) const { diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp index d83b9f73188..1d54aaf6829 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp @@ -183,6 +183,13 @@ static TextAttributes convertRawProp( sourceTextAttributes.accessibilityRole, defaultTextAttributes.accessibilityRole); + textAttributes.role = convertRawProp( + context, + rawProps, + "role", + sourceTextAttributes.role, + defaultTextAttributes.role); + // Color (accessed in this order by ViewProps) textAttributes.opacity = convertRawProp( context, @@ -293,6 +300,7 @@ void BaseTextProps::setProp( textAttributes, accessibilityRole, "accessibilityRole"); + REBUILD_FIELD_SWITCH_CASE(defaults, value, textAttributes, role, "role"); REBUILD_FIELD_SWITCH_CASE( defaults, value, textAttributes, opacity, "opacity"); REBUILD_FIELD_SWITCH_CASE( diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h index 6e934df39f5..108216ae652 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h @@ -136,4 +136,131 @@ enum class AccessibilityLiveRegion : uint8_t { Assertive, }; +enum class AccessibilityRole { + None, + Button, + Dropdownlist, + Togglebutton, + Link, + Search, + Image, + Keyboardkey, + Text, + Adjustable, + Imagebutton, + Header, + Summary, + Alert, + Checkbox, + Combobox, + Menu, + Menubar, + Menuitem, + Progressbar, + Radio, + Radiogroup, + Scrollbar, + Spinbutton, + Switch, + Tab, + Tabbar, + Tablist, + Timer, + List, + Toolbar, + Grid, + Pager, + Scrollview, + Horizontalscrollview, + Viewgroup, + Webview, + Drawerlayout, + Slidingdrawer, + Iconmenu, +}; + +enum class 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, +}; + } // namespace facebook::react + +namespace std { +template <> +struct hash { + size_t operator()(const facebook::react::AccessibilityRole &v) const { + return hash()(static_cast(v)); + } +}; + +template <> +struct hash { + size_t operator()(const facebook::react::Role &v) const { + return hash()(static_cast(v)); + } +}; +} // namespace std 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 8861f69c95a..ad6cf1ce83d 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp @@ -171,6 +171,14 @@ AccessibilityProps::AccessibilityProps( "importantForAccessibility", sourceProps.importantForAccessibility, ImportantForAccessibility::Auto)), + role( + CoreFeatures::enablePropIteratorSetter ? sourceProps.role + : convertRawProp( + context, + rawProps, + "role", + sourceProps.role, + {})), testId( CoreFeatures::enablePropIteratorSetter ? sourceProps.testId : convertRawProp( @@ -227,6 +235,7 @@ void AccessibilityProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityEscape); RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityAction); RAW_SET_PROP_SWITCH_CASE_BASIC(importantForAccessibility); + RAW_SET_PROP_SWITCH_CASE_BASIC(role); RAW_SET_PROP_SWITCH_CASE(testId, "testID"); case CONSTEXPR_RAW_PROPS_KEY_HASH("accessibilityRole"): { AccessibilityTraits traits = AccessibilityTraits::None; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.h index 87f60e7b14c..4b6cc6cf6be 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.h @@ -57,6 +57,7 @@ class AccessibilityProps { bool onAccessibilityAction{}; ImportantForAccessibility importantForAccessibility{ ImportantForAccessibility::Auto}; + Role role{Role::None}; std::string testId{""}; #pragma mark - DebugStringConvertible 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 cc918cb9e2c..9ee16674181 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/accessibilityPropsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/accessibilityPropsConversions.h @@ -298,4 +298,486 @@ inline void fromRawValue( } } +inline std::string toString(const AccessibilityRole &accessibilityRole) { + switch (accessibilityRole) { + case AccessibilityRole::None: + return "none"; + case AccessibilityRole::Button: + return "button"; + case AccessibilityRole::Dropdownlist: + return "dropdownlist"; + case AccessibilityRole::Togglebutton: + return "togglebutton"; + case AccessibilityRole::Link: + return "link"; + case AccessibilityRole::Search: + return "search"; + case AccessibilityRole::Image: + return "image"; + case AccessibilityRole::Keyboardkey: + return "keyboardkey"; + case AccessibilityRole::Text: + return "text"; + case AccessibilityRole::Adjustable: + return "adjustable"; + case AccessibilityRole::Imagebutton: + return "imagebutton"; + case AccessibilityRole::Header: + return "header"; + case AccessibilityRole::Summary: + return "summary"; + case AccessibilityRole::Alert: + return "alert"; + case AccessibilityRole::Checkbox: + return "checkbox"; + case AccessibilityRole::Combobox: + return "combobox"; + case AccessibilityRole::Menu: + return "menu"; + case AccessibilityRole::Menubar: + return "menubar"; + case AccessibilityRole::Menuitem: + return "menuitem"; + case AccessibilityRole::Progressbar: + return "progressbar"; + case AccessibilityRole::Radio: + return "radio"; + case AccessibilityRole::Radiogroup: + return "radiogroup"; + case AccessibilityRole::Scrollbar: + return "scrollbar"; + case AccessibilityRole::Spinbutton: + return "spinbutton"; + case AccessibilityRole::Switch: + return "switch"; + case AccessibilityRole::Tab: + return "tab"; + case AccessibilityRole::Tabbar: + return "tabbar"; + case AccessibilityRole::Tablist: + return "tablist"; + case AccessibilityRole::Timer: + return "timer"; + case AccessibilityRole::List: + return "timer"; + case AccessibilityRole::Toolbar: + return "toolbar"; + case AccessibilityRole::Grid: + return "grid"; + case AccessibilityRole::Pager: + return "pager"; + case AccessibilityRole::Scrollview: + return "scrollview"; + case AccessibilityRole::Horizontalscrollview: + return "horizontalscrollview"; + case AccessibilityRole::Viewgroup: + return "viewgroup"; + case AccessibilityRole::Webview: + return "webview"; + case AccessibilityRole::Drawerlayout: + return "drawerlayout"; + case AccessibilityRole::Slidingdrawer: + return "slidingdrawer"; + case AccessibilityRole::Iconmenu: + return "iconmenu"; + } + + LOG(ERROR) << "Unsupported AccessibilityRole value"; + react_native_expect(false); + // sane default for prod + return "none"; +} + +inline void fromRawValue( + const PropsParserContext &context, + const RawValue &value, + AccessibilityRole &result) { + react_native_expect(value.hasType()); + if (value.hasType()) { + auto string = (std::string)value; + if (string == "none") { + result = AccessibilityRole::None; + } else if (string == "button") { + result = AccessibilityRole::Button; + } else if (string == "dropdownlist") { + result = AccessibilityRole::Dropdownlist; + } else if (string == "togglebutton") { + result = AccessibilityRole::Togglebutton; + } else if (string == "link") { + result = AccessibilityRole::Link; + } else if (string == "search") { + result = AccessibilityRole::Search; + } else if (string == "image") { + result = AccessibilityRole::Image; + } else if (string == "keyboardkey") { + result = AccessibilityRole::Keyboardkey; + } else if (string == "text") { + result = AccessibilityRole::Text; + } else if (string == "adjustable") { + result = AccessibilityRole::Adjustable; + } else if (string == "imagebutton") { + result = AccessibilityRole::Imagebutton; + } else if (string == "header") { + result = AccessibilityRole::Header; + } else if (string == "summary") { + result = AccessibilityRole::Summary; + } else if (string == "alert") { + result = AccessibilityRole::Alert; + } else if (string == "checkbox") { + result = AccessibilityRole::Checkbox; + } else if (string == "combobox") { + result = AccessibilityRole::Combobox; + } else if (string == "menu") { + result = AccessibilityRole::Menu; + } else if (string == "menubar") { + result = AccessibilityRole::Menubar; + } else if (string == "menuitem") { + result = AccessibilityRole::Menuitem; + } else if (string == "progressbar") { + result = AccessibilityRole::Progressbar; + } else if (string == "radio") { + result = AccessibilityRole::Radio; + } else if (string == "radiogroup") { + result = AccessibilityRole::Radiogroup; + } else if (string == "scrollbar") { + result = AccessibilityRole::Scrollbar; + } else if (string == "spinbutton") { + result = AccessibilityRole::Spinbutton; + } else if (string == "switch") { + result = AccessibilityRole::Switch; + } else if (string == "tab") { + result = AccessibilityRole::Tab; + } else if (string == "tabbar") { + result = AccessibilityRole::Tabbar; + } else if (string == "tablist") { + result = AccessibilityRole::Tablist; + } else if (string == "timer") { + result = AccessibilityRole::Timer; + } else if (string == "toolbar") { + result = AccessibilityRole::Toolbar; + } else if (string == "grid") { + result = AccessibilityRole::Grid; + } else if (string == "pager") { + result = AccessibilityRole::Pager; + } else if (string == "scrollview") { + result = AccessibilityRole::Scrollview; + } else if (string == "horizontalscrollview") { + result = AccessibilityRole::Horizontalscrollview; + } else if (string == "viewgroup") { + result = AccessibilityRole::Viewgroup; + } else if (string == "webview") { + result = AccessibilityRole::Webview; + } else if (string == "drawerlayout") { + result = AccessibilityRole::Drawerlayout; + } else if (string == "slidingdrawer") { + result = AccessibilityRole::Slidingdrawer; + } else if (string == "iconmenu") { + result = AccessibilityRole::Iconmenu; + } else { + LOG(ERROR) << "Unsupported AccessibilityRole value: " << string; + react_native_expect(false); + // sane default for prod + result = AccessibilityRole::None; + } + return; + } + + LOG(ERROR) << "Unsupported AccessibilityRole type"; + react_native_expect(false); + // sane default for prod + result = AccessibilityRole::None; +} + +inline std::string toString(const Role &role) { + switch (role) { + case Role::Alert: + return "alert"; + case Role::Alertdialog: + return "alertdialog"; + case Role::Application: + return "application"; + case Role::Article: + return "article"; + case Role::Banner: + return "banner"; + case Role::Button: + return "button"; + case Role::Cell: + return "cell"; + case Role::Checkbox: + return "checkbox"; + case Role::Columnheader: + return "columnheader"; + case Role::Combobox: + return "combobox"; + case Role::Complementary: + return "complementary"; + case Role::Contentinfo: + return "contentinfo"; + case Role::Definition: + return "definition"; + case Role::Dialog: + return "dialog"; + case Role::Directory: + return "directory"; + case Role::Document: + return "document"; + case Role::Feed: + return "feed"; + case Role::Figure: + return "figure"; + case Role::Form: + return "form"; + case Role::Grid: + return "grid"; + case Role::Group: + return "group"; + case Role::Heading: + return "heading"; + case Role::Img: + return "img"; + case Role::Link: + return "link"; + case Role::List: + return "list"; + case Role::Listitem: + return "listitem"; + case Role::Log: + return "log"; + case Role::Main: + return "main"; + case Role::Marquee: + return "marquee"; + case Role::Math: + return "math"; + case Role::Menu: + return "menu"; + case Role::Menubar: + return "menubar"; + case Role::Menuitem: + return "menuitem"; + case Role::Meter: + return "meter"; + case Role::Navigation: + return "navigation"; + case Role::None: + return "none"; + case Role::Note: + return "note"; + case Role::Option: + return "option"; + case Role::Presentation: + return "presentation"; + case Role::Progressbar: + return "progressbar"; + case Role::Radio: + return "radio"; + case Role::Radiogroup: + return "radiogroup"; + case Role::Region: + return "region"; + case Role::Row: + return "row"; + case Role::Rowgroup: + return "rowgroup"; + case Role::Rowheader: + return "rowheader"; + case Role::Scrollbar: + return "scrollbar"; + case Role::Searchbox: + return "searchbox"; + case Role::Separator: + return "separator"; + case Role::Slider: + return "slider"; + case Role::Spinbutton: + return "spinbutton"; + case Role::Status: + return "status"; + case Role::Summary: + return "summary"; + case Role::Switch: + return "switch"; + case Role::Tab: + return "tab"; + case Role::Table: + return "table"; + case Role::Tablist: + return "tablist"; + case Role::Tabpanel: + return "tabpanel"; + case Role::Term: + return "term"; + case Role::Timer: + return "timer"; + case Role::Toolbar: + return "toolbar"; + case Role::Tooltip: + return "tooltip"; + case Role::Tree: + return "tree"; + case Role::Treegrid: + return "treegrid"; + case Role::Treeitem: + return "treeitem"; + } + + LOG(ERROR) << "Unsupported Role value"; + react_native_expect(false); + // sane default for prod + return "none"; +} + +inline void fromRawValue( + const PropsParserContext &context, + const RawValue &value, + Role &result) { + react_native_expect(value.hasType()); + if (value.hasType()) { + auto string = (std::string)value; + if (string == "alert") { + result = Role::Alert; + } else if (string == "alertdialog") { + result = Role::Alertdialog; + } else if (string == "application") { + result = Role::Application; + } else if (string == "article") { + result = Role::Article; + } else if (string == "banner") { + result = Role::Banner; + } else if (string == "button") { + result = Role::Button; + } else if (string == "cell") { + result = Role::Cell; + } else if (string == "checkbox") { + result = Role::Checkbox; + } else if (string == "columnheader") { + result = Role::Columnheader; + } else if (string == "combobox") { + result = Role::Combobox; + } else if (string == "complementary") { + result = Role::Complementary; + } else if (string == "contentinfo") { + result = Role::Contentinfo; + } else if (string == "definition") { + result = Role::Definition; + } else if (string == "dialog") { + result = Role::Dialog; + } else if (string == "directory") { + result = Role::Directory; + } else if (string == "document") { + result = Role::Document; + } else if (string == "feed") { + result = Role::Feed; + } else if (string == "figure") { + result = Role::Figure; + } else if (string == "form") { + result = Role::Form; + } else if (string == "grid") { + result = Role::Grid; + } else if (string == "group") { + result = Role::Group; + } else if (string == "heading") { + result = Role::Heading; + } else if (string == "img") { + result = Role::Img; + } else if (string == "link") { + result = Role::Link; + } else if (string == "list") { + result = Role::List; + } else if (string == "listitem") { + result = Role::Listitem; + } else if (string == "log") { + result = Role::Log; + } else if (string == "main") { + result = Role::Main; + } else if (string == "marquee") { + result = Role::Marquee; + } else if (string == "math") { + result = Role::Math; + } else if (string == "menu") { + result = Role::Menu; + } else if (string == "menubar") { + result = Role::Menubar; + } else if (string == "menuitem") { + result = Role::Menuitem; + } else if (string == "meter") { + result = Role::Meter; + } else if (string == "navigation") { + result = Role::Navigation; + } else if (string == "none") { + result = Role::None; + } else if (string == "note") { + result = Role::Note; + } else if (string == "option") { + result = Role::Option; + } else if (string == "presentation") { + result = Role::Presentation; + } else if (string == "progressbar") { + result = Role::Progressbar; + } else if (string == "radio") { + result = Role::Radio; + } else if (string == "radiogroup") { + result = Role::Radiogroup; + } else if (string == "region") { + result = Role::Region; + } else if (string == "row") { + result = Role::Row; + } else if (string == "rowgroup") { + result = Role::Rowgroup; + } else if (string == "rowheader") { + result = Role::Rowheader; + } else if (string == "scrollbar") { + result = Role::Scrollbar; + } else if (string == "searchbox") { + result = Role::Searchbox; + } else if (string == "separator") { + result = Role::Separator; + } else if (string == "slider") { + result = Role::Slider; + } else if (string == "spinbutton") { + result = Role::Spinbutton; + } else if (string == "status") { + result = Role::Status; + } else if (string == "summary") { + result = Role::Summary; + } else if (string == "switch") { + result = Role::Switch; + } else if (string == "tab") { + result = Role::Tab; + } else if (string == "table") { + result = Role::Table; + } else if (string == "tablist") { + result = Role::Tablist; + } else if (string == "tabpanel") { + result = Role::Tabpanel; + } else if (string == "term") { + result = Role::Term; + } else if (string == "timer") { + result = Role::Timer; + } else if (string == "toolbar") { + result = Role::Toolbar; + } else if (string == "tooltip") { + result = Role::Tooltip; + } else if (string == "tree") { + result = Role::Tree; + } else if (string == "treegrid") { + result = Role::Treegrid; + } else if (string == "treeitem") { + result = Role::Treeitem; + } else { + LOG(ERROR) << "Unsupported Role value: " << string; + react_native_expect(false); + // sane default for prod + result = Role::None; + } + return; + } + + LOG(ERROR) << "Unsupported Role type"; + react_native_expect(false); + // sane default for prod + result = Role::None; +} + } // namespace facebook::react 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 dca3ab892c8..68388d1c839 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 @@ -297,6 +297,12 @@ NSDictionary *RCTNSTextAttributesFromTextAttributes(T 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; @@ -306,9 +312,6 @@ NSDictionary *RCTNSTextAttributesFromTextAttributes(T case AccessibilityRole::Image: attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("image"); break; - case AccessibilityRole::Imagebutton: - attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("imagebutton"); - break; case AccessibilityRole::Keyboardkey: attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("keyboardkey"); break; @@ -318,12 +321,15 @@ NSDictionary *RCTNSTextAttributesFromTextAttributes(T case AccessibilityRole::Adjustable: attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("adjustable"); break; - case AccessibilityRole::Summary: - attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("summary"); + 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; @@ -363,7 +369,7 @@ NSDictionary *RCTNSTextAttributesFromTextAttributes(T case AccessibilityRole::Tab: attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("tab"); break; - case AccessibilityRole::TabBar: + case AccessibilityRole::Tabbar: attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("tabbar"); break; case AccessibilityRole::Tablist: @@ -372,9 +378,39 @@ NSDictionary *RCTNSTextAttributesFromTextAttributes(T 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; }; }