From 7e4979465204374ad2f4491f8deb4f8707484658 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 1 Dec 2023 04:50:54 +0100 Subject: [PATCH 1/3] [fabric][a11y] Enable accessibility property assignments for View Summary: Paper was rendering text as AXStaticText. This diff updates the `RCTParagraphComponentView` to propagate the same role in Fabric for text. This change will allow to select UI elements based on the text contents. Test Plan: Use the Accessibility Inspector in Zeratul with Fabric enabled. With the changes the text elements are presented in the a11y hierarchy with AXStaticText: {F1162808272} Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51736932 Tasks: T170938725 Tags: uikit-diff --- .../ComponentViews/View/RCTViewComponentView.h | 2 +- .../ComponentViews/View/RCTViewComponentView.mm | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h index 7a903401112fa8..3d7564de46e8b1 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h @@ -56,7 +56,7 @@ NS_ASSUME_NONNULL_BEGIN * transparent in favour of some subview. * Defaults to `self`. */ -@property (nonatomic, strong, nullable, readonly) NSObject *accessibilityElement; +@property (nonatomic, strong, nullable, readonly) RCTPlatformView *accessibilityElement; // [macOS] /** * Insets used when hit testing inside this view. 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 035bcc2245f317..85ab96aa125d3e 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 // [macOS] #import +#import // [macOS] #import #import #import @@ -373,10 +374,13 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & self.nativeId = RCTNSStringFromStringNilIfEmpty(newViewProps.nativeId); } -#if !TARGET_OS_OSX // [macOS] // `accessible` if (oldViewProps.accessible != newViewProps.accessible) { +#if !TARGET_OS_OSX // [macOS] self.accessibilityElement.isAccessibilityElement = newViewProps.accessible; +#else // [macOS + self.accessibilityElement.accessibilityElement = newViewProps.accessible; +#endif // macOS] } // `accessibilityLabel` @@ -392,9 +396,14 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & // `accessibilityHint` if (oldViewProps.accessibilityHint != newViewProps.accessibilityHint) { +#if !TARGET_OS_OSX // [macOS] self.accessibilityElement.accessibilityHint = RCTNSStringFromStringNilIfEmpty(newViewProps.accessibilityHint); +#else // [macOS + self.accessibilityElement.accessibilityHelp = RCTNSStringFromStringNilIfEmpty(newViewProps.accessibilityHint); +#endif // macOS] } +#if !TARGET_OS_OSX // [macOS] // `accessibilityViewIsModal` if (oldViewProps.accessibilityViewIsModal != newViewProps.accessibilityViewIsModal) { self.accessibilityElement.accessibilityViewIsModal = newViewProps.accessibilityViewIsModal; @@ -447,6 +456,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & if (oldViewProps.accessibilityIgnoresInvertColors != newViewProps.accessibilityIgnoresInvertColors) { self.accessibilityIgnoresInvertColors = newViewProps.accessibilityIgnoresInvertColors; } +#endif // [macOS] // `accessibilityValue` if (oldViewProps.accessibilityValue != newViewProps.accessibilityValue) { @@ -465,8 +475,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & self.accessibilityElement.accessibilityValue = nil; } } -#endif // [macOS] - + // `testId` if (oldViewProps.testId != newViewProps.testId) { SEL setAccessibilityIdentifierSelector = @selector(setAccessibilityIdentifier:); @@ -1236,7 +1245,7 @@ - (void)clearExistingGradientLayers #pragma mark - Accessibility -- (NSObject *)accessibilityElement +- (RCTPlatformView *)accessibilityElement { return self; } From 68f9bc860009f8a77396cc86f36545ddd359c9a8 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 1 Dec 2023 04:53:33 +0100 Subject: [PATCH 2/3] [fabric][a11y] Add role mapping for common traits Summary: Paper was rendering text as AXStaticText. This diff updates the `RCTParagraphComponentView` to propagate the same role in Fabric for text. This change will allow to select UI elements based on the text contents. Test Plan: Use the Accessibility Inspector in Zeratul with Fabric enabled. With the changes the text elements are presented in the a11y hierarchy with AXStaticText: {F1162808272} Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51736931 Tasks: T170938725 --- .../View/RCTViewComponentView.mm | 6 +++ .../React/Fabric/RCTConversions.h | 40 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) 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 85ab96aa125d3e..dbbe22384a84a3 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -413,6 +413,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & if (oldViewProps.accessibilityElementsHidden != newViewProps.accessibilityElementsHidden) { self.accessibilityElement.accessibilityElementsHidden = newViewProps.accessibilityElementsHidden; } +#endif // [macOS] // `accessibilityShowsLargeContentViewer` if (oldViewProps.accessibilityShowsLargeContentViewer != newViewProps.accessibilityShowsLargeContentViewer) { @@ -436,10 +437,15 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & // `accessibilityTraits` if (oldViewProps.accessibilityTraits != newViewProps.accessibilityTraits) { +#if !TARGET_OS_OSX // [macOS] self.accessibilityElement.accessibilityTraits = RCTUIAccessibilityTraitsFromAccessibilityTraits(newViewProps.accessibilityTraits); +#else // [macOS + self.accessibilityElement.accessibilityRole = RCTUIAccessibilityRoleFromAccessibilityTraits(newViewProps.accessibilityTraits); +#endif // macOS] } +#if !TARGET_OS_OSX // [macOS] // `accessibilityState` if (oldViewProps.accessibilityState != newViewProps.accessibilityState) { self.accessibilityTraits &= ~(UIAccessibilityTraitNotEnabled | UIAccessibilityTraitSelected); diff --git a/packages/react-native/React/Fabric/RCTConversions.h b/packages/react-native/React/Fabric/RCTConversions.h index b797136759f8c1..e56b0219a5a20d 100644 --- a/packages/react-native/React/Fabric/RCTConversions.h +++ b/packages/react-native/React/Fabric/RCTConversions.h @@ -124,7 +124,45 @@ inline UIAccessibilityTraits RCTUIAccessibilityTraitsFromAccessibilityTraits( } return result; }; -#endif // [macOS] +#else // [macOS +inline NSAccessibilityRole RCTUIAccessibilityRoleFromAccessibilityTraits( + facebook::react::AccessibilityTraits accessibilityTraits) +{ + using AccessibilityTraits = facebook::react::AccessibilityTraits; + if ((accessibilityTraits & AccessibilityTraits::Button) != AccessibilityTraits::None) { + return NSAccessibilityButtonRole; + } + if ((accessibilityTraits & AccessibilityTraits::Link) != AccessibilityTraits::None) { + return NSAccessibilityLinkRole; + } + if ((accessibilityTraits & AccessibilityTraits::Image) != AccessibilityTraits::None) { + return NSAccessibilityImageRole; + } + if ((accessibilityTraits & AccessibilityTraits::KeyboardKey) != AccessibilityTraits::None) { + return NSAccessibilityButtonRole; + } + if ((accessibilityTraits & AccessibilityTraits::StaticText) != AccessibilityTraits::None) { + return NSAccessibilityStaticTextRole; + } + if ((accessibilityTraits & AccessibilityTraits::SummaryElement) != AccessibilityTraits::None) { + return NSAccessibilityStaticTextRole; + } + if ((accessibilityTraits & AccessibilityTraits::SearchField) != AccessibilityTraits::None) { + return NSAccessibilityTextFieldRole; + } + if ((accessibilityTraits & AccessibilityTraits::Adjustable) != AccessibilityTraits::None) { + return NSAccessibilitySliderRole; + } + if ((accessibilityTraits & AccessibilityTraits::Header) != AccessibilityTraits::None) { + return NSAccessibilityStaticTextRole; + } + if ((accessibilityTraits & AccessibilityTraits::Switch) != AccessibilityTraits::None) { + return NSAccessibilityCheckBoxRole; + } + + return NSAccessibilityUnknownRole; +}; +#endif // macOS] inline CATransform3D RCTCATransform3DFromTransformMatrix(const facebook::react::Transform &transformMatrix) { From 3517bb14171fd6dee7ed23b788ad0e7da2ee72a2 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 1 Dec 2023 04:57:43 +0100 Subject: [PATCH 3/3] [fabric][a11y] Add role mapping for desktop specific traits Summary: Paper was rendering text as AXStaticText. This diff updates the `RCTParagraphComponentView` to propagate the same role in Fabric for text. This change will allow to select UI elements based on the text contents. Test Plan: Use the Accessibility Inspector in Zeratul with Fabric enabled. With the changes the text elements are presented in the a11y hierarchy with AXStaticText: {F1162808272} Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51736933 Tasks: T170938725 --- .../React/Fabric/RCTConversions.h | 56 ++++++++++++++++++- .../components/view/AccessibilityPrimitives.h | 15 +++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/RCTConversions.h b/packages/react-native/React/Fabric/RCTConversions.h index e56b0219a5a20d..a20b52b229972b 100644 --- a/packages/react-native/React/Fabric/RCTConversions.h +++ b/packages/react-native/React/Fabric/RCTConversions.h @@ -130,6 +130,15 @@ inline NSAccessibilityRole RCTUIAccessibilityRoleFromAccessibilityTraits( { using AccessibilityTraits = facebook::react::AccessibilityTraits; if ((accessibilityTraits & AccessibilityTraits::Button) != AccessibilityTraits::None) { + if ((accessibilityTraits & AccessibilityTraits::Bar) != AccessibilityTraits::None) { + return NSAccessibilityToolbarRole; + } + if ((accessibilityTraits & AccessibilityTraits::PopUp) != AccessibilityTraits::None) { + return NSAccessibilityPopUpButtonRole; + } + if ((accessibilityTraits & AccessibilityTraits::Menu) != AccessibilityTraits::None) { + return NSAccessibilityMenuButtonRole; + } return NSAccessibilityButtonRole; } if ((accessibilityTraits & AccessibilityTraits::Link) != AccessibilityTraits::None) { @@ -159,7 +168,52 @@ inline NSAccessibilityRole RCTUIAccessibilityRoleFromAccessibilityTraits( if ((accessibilityTraits & AccessibilityTraits::Switch) != AccessibilityTraits::None) { return NSAccessibilityCheckBoxRole; } - + if ((accessibilityTraits & AccessibilityTraits::UpdatesFrequently) != AccessibilityTraits::None) { + return NSAccessibilityProgressIndicatorRole; + } + if ((accessibilityTraits & AccessibilityTraits::ComboBox) != AccessibilityTraits::None) { + return NSAccessibilityComboBoxRole; + } + if ((accessibilityTraits & AccessibilityTraits::Menu) != AccessibilityTraits::None) { + if ((accessibilityTraits & AccessibilityTraits::Bar) != AccessibilityTraits::None) { + return NSAccessibilityMenuBarRole; + } + if ((accessibilityTraits & AccessibilityTraits::Item) != AccessibilityTraits::None) { + return NSAccessibilityMenuItemRole; + } + return NSAccessibilityMenuRole; + } + if ((accessibilityTraits & AccessibilityTraits::Radio) != AccessibilityTraits::None) { + if ((accessibilityTraits & AccessibilityTraits::Group) != AccessibilityTraits::None) { + return NSAccessibilityRadioGroupRole; + } + return NSAccessibilityRadioButtonRole; + } + if ((accessibilityTraits & AccessibilityTraits::ScrollBar) != AccessibilityTraits::None) { + return NSAccessibilityScrollBarRole; + } + if ((accessibilityTraits & AccessibilityTraits::SpinButton) != AccessibilityTraits::None) { + return NSAccessibilityIncrementorRole; + } + if ((accessibilityTraits & AccessibilityTraits::Tab) != AccessibilityTraits::None) { + if ((accessibilityTraits & AccessibilityTraits::List) != AccessibilityTraits::None) { + return NSAccessibilityTabGroupRole; + } + return NSAccessibilityButtonRole; + } + if ((accessibilityTraits & AccessibilityTraits::Disclosure) != AccessibilityTraits::None) { + return NSAccessibilityDisclosureTriangleRole; + } + if ((accessibilityTraits & AccessibilityTraits::Group) != AccessibilityTraits::None) { + return NSAccessibilityGroupRole; + } + if ((accessibilityTraits & AccessibilityTraits::List) != AccessibilityTraits::None) { + return NSAccessibilityListRole; + } + if ((accessibilityTraits & AccessibilityTraits::Table) != AccessibilityTraits::None) { + return NSAccessibilityTableRole; + } + return NSAccessibilityUnknownRole; }; #endif // macOS] 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 420a10852af33d..06e9fc9b94e11a 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h @@ -34,6 +34,21 @@ enum class AccessibilityTraits : uint32_t { Header = (1 << 15), Switch = (1 << 16), TabBar = (1 << 17), +// [macOS + ComboBox = (1 << 18), + Menu = (1 << 19), + PopUp = (1 << 20), + Bar = (1 << 21), + Item = (1 << 22), + Group = (1 << 23), + List = (1 << 24), + Tab = (1 << 25), + Table = (1 << 26), + Disclosure = (1 << 27), + Radio = (1 << 28), + ScrollBar = (1 << 29), + SpinButton = (1 << 30), +// macOS] }; constexpr enum AccessibilityTraits operator|(