Skip to content

Commit 9db5fa2

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
Native ARIA Roles: Shared code (#37305)
Summary: Pull Request resolved: #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
1 parent 1b0e8b1 commit 9db5fa2

File tree

14 files changed

+682
-189
lines changed

14 files changed

+682
-189
lines changed

packages/react-native/Libraries/Components/View/ReactNativeViewAttributes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const UIView = {
3535
collapsable: true,
3636
needsOffscreenAlphaCompositing: true,
3737
style: ReactNativeStyleAttributes,
38+
role: true,
3839
};
3940

4041
const RCTView = {

packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ const validAttributesForNonEventProps = {
179179
accessibilityActions: true,
180180
accessibilityValue: true,
181181
importantForAccessibility: true,
182+
role: true,
182183
rotation: true,
183184
scaleX: true,
184185
scaleY: true,

packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ const validAttributesForNonEventProps = {
198198
nativeID: true,
199199
pointerEvents: true,
200200
removeClippedSubviews: true,
201+
role: true,
201202
borderRadius: true,
202203
borderColor: {process: require('../StyleSheet/processColor').default},
203204
borderCurve: true,

packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ target_link_libraries(react_render_attributedstring
2626
glog
2727
glog_init
2828
react_debug
29+
rrc_view
2930
react_render_core
3031
react_render_debug
3132
react_render_graphics

packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ void TextAttributes::apply(TextAttributes textAttributes) {
101101
accessibilityRole = textAttributes.accessibilityRole.has_value()
102102
? textAttributes.accessibilityRole
103103
: accessibilityRole;
104+
role = textAttributes.role.has_value() ? textAttributes.role : role;
104105
}
105106

106107
#pragma mark - Operators
@@ -126,6 +127,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
126127
isHighlighted,
127128
layoutDirection,
128129
accessibilityRole,
130+
role,
129131
textTransform) ==
130132
std::tie(
131133
rhs.foregroundColor,
@@ -147,6 +149,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
147149
rhs.isHighlighted,
148150
rhs.layoutDirection,
149151
rhs.accessibilityRole,
152+
rhs.role,
150153
rhs.textTransform) &&
151154
floatEquality(opacity, rhs.opacity) &&
152155
floatEquality(fontSize, rhs.fontSize) &&
@@ -215,6 +218,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
215218
debugStringConvertibleItem("isHighlighted", isHighlighted),
216219
debugStringConvertibleItem("layoutDirection", layoutDirection),
217220
debugStringConvertibleItem("accessibilityRole", accessibilityRole),
221+
debugStringConvertibleItem("role", role),
218222
};
219223
}
220224
#endif

packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include <folly/Hash.h>
1515
#include <react/renderer/attributedstring/primitives.h>
16+
#include <react/renderer/components/view/AccessibilityPrimitives.h>
1617
#include <react/renderer/core/LayoutPrimitives.h>
1718
#include <react/renderer/core/ReactPrimitives.h>
1819
#include <react/renderer/debug/DebugStringConvertible.h>
@@ -80,6 +81,7 @@ class TextAttributes : public DebugStringConvertible {
8081
// construction.
8182
std::optional<LayoutDirection> layoutDirection{};
8283
std::optional<AccessibilityRole> accessibilityRole{};
84+
std::optional<Role> role{};
8385

8486
#pragma mark - Operations
8587

@@ -131,7 +133,8 @@ struct hash<facebook::react::TextAttributes> {
131133
textAttributes.textShadowColor,
132134
textAttributes.isHighlighted,
133135
textAttributes.layoutDirection,
134-
textAttributes.accessibilityRole);
136+
textAttributes.accessibilityRole,
137+
textAttributes.role);
135138
}
136139
};
137140
} // namespace std

packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h

Lines changed: 1 addition & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <react/renderer/attributedstring/TextAttributes.h>
1616
#include <react/renderer/attributedstring/conversions.h>
1717
#include <react/renderer/attributedstring/primitives.h>
18+
#include <react/renderer/components/view/accessibilityPropsConversions.h>
1819
#include <react/renderer/core/LayoutableShadowNode.h>
1920
#include <react/renderer/core/PropsParserContext.h>
2021
#include <react/renderer/core/ShadowNode.h>
@@ -641,150 +642,6 @@ inline std::string toString(const TextDecorationStyle &textDecorationStyle) {
641642
return "solid";
642643
}
643644

644-
inline std::string toString(const AccessibilityRole &accessibilityRole) {
645-
switch (accessibilityRole) {
646-
case AccessibilityRole::None:
647-
return "none";
648-
case AccessibilityRole::Button:
649-
return "button";
650-
case AccessibilityRole::Link:
651-
return "link";
652-
case AccessibilityRole::Search:
653-
return "search";
654-
case AccessibilityRole::Image:
655-
return "image";
656-
case AccessibilityRole::Imagebutton:
657-
return "imagebutton";
658-
case AccessibilityRole::Keyboardkey:
659-
return "keyboardkey";
660-
case AccessibilityRole::Text:
661-
return "text";
662-
case AccessibilityRole::Adjustable:
663-
return "adjustable";
664-
case AccessibilityRole::Summary:
665-
return "summary";
666-
case AccessibilityRole::Header:
667-
return "header";
668-
case AccessibilityRole::Alert:
669-
return "alert";
670-
case AccessibilityRole::Checkbox:
671-
return "checkbox";
672-
case AccessibilityRole::Combobox:
673-
return "combobox";
674-
case AccessibilityRole::Menu:
675-
return "menu";
676-
case AccessibilityRole::Menubar:
677-
return "menubar";
678-
case AccessibilityRole::Menuitem:
679-
return "menuitem";
680-
case AccessibilityRole::Progressbar:
681-
return "progressbar";
682-
case AccessibilityRole::Radio:
683-
return "radio";
684-
case AccessibilityRole::Radiogroup:
685-
return "radiogroup";
686-
case AccessibilityRole::Scrollbar:
687-
return "scrollbar";
688-
case AccessibilityRole::Spinbutton:
689-
return "spinbutton";
690-
case AccessibilityRole::Switch:
691-
return "switch";
692-
case AccessibilityRole::Tab:
693-
return "tab";
694-
case AccessibilityRole::TabBar:
695-
return "tabbar";
696-
case AccessibilityRole::Tablist:
697-
return "tablist";
698-
case AccessibilityRole::Timer:
699-
return "timer";
700-
case AccessibilityRole::Toolbar:
701-
return "toolbar";
702-
}
703-
704-
LOG(ERROR) << "Unsupported AccessibilityRole value";
705-
react_native_expect(false);
706-
// sane default for prod
707-
return "none";
708-
}
709-
710-
inline void fromRawValue(
711-
const PropsParserContext &context,
712-
const RawValue &value,
713-
AccessibilityRole &result) {
714-
react_native_expect(value.hasType<std::string>());
715-
if (value.hasType<std::string>()) {
716-
auto string = (std::string)value;
717-
if (string == "none") {
718-
result = AccessibilityRole::None;
719-
} else if (string == "button" || string == "togglebutton") {
720-
result = AccessibilityRole::Button;
721-
} else if (string == "link") {
722-
result = AccessibilityRole::Link;
723-
} else if (string == "search") {
724-
result = AccessibilityRole::Search;
725-
} else if (string == "image") {
726-
result = AccessibilityRole::Image;
727-
} else if (string == "imagebutton") {
728-
result = AccessibilityRole::Imagebutton;
729-
} else if (string == "keyboardkey") {
730-
result = AccessibilityRole::Keyboardkey;
731-
} else if (string == "text") {
732-
result = AccessibilityRole::Text;
733-
} else if (string == "adjustable") {
734-
result = AccessibilityRole::Adjustable;
735-
} else if (string == "summary") {
736-
result = AccessibilityRole::Summary;
737-
} else if (string == "header") {
738-
result = AccessibilityRole::Header;
739-
} else if (string == "alert") {
740-
result = AccessibilityRole::Alert;
741-
} else if (string == "checkbox") {
742-
result = AccessibilityRole::Checkbox;
743-
} else if (string == "combobox") {
744-
result = AccessibilityRole::Combobox;
745-
} else if (string == "menu") {
746-
result = AccessibilityRole::Menu;
747-
} else if (string == "menubar") {
748-
result = AccessibilityRole::Menubar;
749-
} else if (string == "menuitem") {
750-
result = AccessibilityRole::Menuitem;
751-
} else if (string == "progressbar") {
752-
result = AccessibilityRole::Progressbar;
753-
} else if (string == "radio") {
754-
result = AccessibilityRole::Radio;
755-
} else if (string == "radiogroup") {
756-
result = AccessibilityRole::Radiogroup;
757-
} else if (string == "scrollbar") {
758-
result = AccessibilityRole::Scrollbar;
759-
} else if (string == "spinbutton") {
760-
result = AccessibilityRole::Spinbutton;
761-
} else if (string == "switch") {
762-
result = AccessibilityRole::Switch;
763-
} else if (string == "tab") {
764-
result = AccessibilityRole::Tab;
765-
} else if (string == "tabbar") {
766-
result = AccessibilityRole::TabBar;
767-
} else if (string == "tablist") {
768-
result = AccessibilityRole::Tablist;
769-
} else if (string == "timer") {
770-
result = AccessibilityRole::Timer;
771-
} else if (string == "toolbar") {
772-
result = AccessibilityRole::Toolbar;
773-
} else {
774-
LOG(ERROR) << "Unsupported AccessibilityRole value: " << string;
775-
react_native_expect(false);
776-
// sane default for prod
777-
result = AccessibilityRole::None;
778-
}
779-
return;
780-
}
781-
782-
LOG(ERROR) << "Unsupported AccessibilityRole type";
783-
react_native_expect(false);
784-
// sane default for prod
785-
result = AccessibilityRole::None;
786-
}
787-
788645
inline std::string toString(const HyphenationFrequency &hyphenationFrequency) {
789646
switch (hyphenationFrequency) {
790647
case HyphenationFrequency::None:

packages/react-native/ReactCommon/react/renderer/attributedstring/primitives.h

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -105,37 +105,6 @@ enum class TextDecorationLineType {
105105

106106
enum class TextDecorationStyle { Solid, Double, Dotted, Dashed };
107107

108-
enum class AccessibilityRole {
109-
None,
110-
Button,
111-
Link,
112-
Search,
113-
Image,
114-
Imagebutton,
115-
Keyboardkey,
116-
Text,
117-
Adjustable,
118-
Summary,
119-
Header,
120-
Alert,
121-
Checkbox,
122-
Combobox,
123-
Menu,
124-
Menubar,
125-
Menuitem,
126-
Progressbar,
127-
Radio,
128-
Radiogroup,
129-
Scrollbar,
130-
Spinbutton,
131-
Switch,
132-
Tab,
133-
TabBar,
134-
Tablist,
135-
Timer,
136-
Toolbar,
137-
};
138-
139108
enum class TextTransform {
140109
None,
141110
Uppercase,
@@ -223,13 +192,6 @@ struct hash<facebook::react::TextBreakStrategy> {
223192
}
224193
};
225194

226-
template <>
227-
struct hash<facebook::react::AccessibilityRole> {
228-
size_t operator()(const facebook::react::AccessibilityRole &v) const {
229-
return hash<int>()(static_cast<int>(v));
230-
}
231-
};
232-
233195
template <>
234196
struct hash<facebook::react::TextTransform> {
235197
size_t operator()(const facebook::react::TextTransform &v) const {

packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@ static TextAttributes convertRawProp(
183183
sourceTextAttributes.accessibilityRole,
184184
defaultTextAttributes.accessibilityRole);
185185

186+
textAttributes.role = convertRawProp(
187+
context,
188+
rawProps,
189+
"role",
190+
sourceTextAttributes.role,
191+
defaultTextAttributes.role);
192+
186193
// Color (accessed in this order by ViewProps)
187194
textAttributes.opacity = convertRawProp(
188195
context,
@@ -293,6 +300,7 @@ void BaseTextProps::setProp(
293300
textAttributes,
294301
accessibilityRole,
295302
"accessibilityRole");
303+
REBUILD_FIELD_SWITCH_CASE(defaults, value, textAttributes, role, "role");
296304
REBUILD_FIELD_SWITCH_CASE(
297305
defaults, value, textAttributes, opacity, "opacity");
298306
REBUILD_FIELD_SWITCH_CASE(

0 commit comments

Comments
 (0)