From 80e6c9635de044e7441666919e7c7d43fddbc69c Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Mon, 4 Mar 2024 11:42:53 -0500 Subject: [PATCH 1/5] Adds ARIA role support to Paper UIManager In react-native v0.73, the `role` prop was switched from setting its value to `accessibilityRole` to a standalone native prop. This wires up the native prop behavior in the Paper UIManager for react-native-windows. Here is the table of ARIA role to UIA role in this commit: |**ARIA role**|**UIA role**| |alert|Text| |alertdialog|Window| |application|Group| |article|Document| |banner|Text| |button|Button| |cell|DataItem| |checkbox|CheckBox| |columnheader|HeaderItem| |combobox|ComboBox| |complementary|Text| |contentinfo|Text| |definition|Text| |dialog|Window| |directory|| |document|Document| |feed|| |figure|Image| |form|| |grid|DataGrid| |group|Group |heading|Text| |img|Image| |link|Hyperlink| |list|List| |listitem|ListItem| |log|| |main|| |marquee|| |math|| |menu|Menu| |menubar|MenuBar| |menuitem|MenuItem| |meter|| |navigation|| |none|Group| |note|Text| |option|| |presentation|| |progressbar|PrgressBar| |radio|RadioButton| |radiogroup|Group| |region|Group| |row|DataGrid| |rowgroup|Group| |rowheader|HeaderItem| |scrollbar|ScrollBar| |searchbox|Edit| |separator|Separator| |slider|Slider| |spinbutton|Spinner| |status|Text| |summary|Text| |switch|Group| |tab|TabItem| |table|Table| |tablist|Tab| |tabpanel|Group| |term|Text| |timer|Group| |toolbar|ToolBar| |tooltip|ToolTip| |tree|Tree| |treegrid|Tree| |treeitem|TreeItem| --- .../Views/DynamicAutomationPeer.cpp | 120 +++++++++++++++ .../Views/DynamicAutomationPeer.h | 5 + .../Views/DynamicAutomationProperties.cpp | 20 +++ .../Views/DynamicAutomationProperties.h | 4 + .../Views/FrameworkElementViewManager.cpp | 137 ++++++++++++++++++ .../Views/cppwinrt/DynamicAutomationPeer.idl | 79 +++++++++- 6 files changed, 363 insertions(+), 2 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp b/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp index 96de3361c8e..5872766639e 100644 --- a/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp +++ b/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp @@ -44,6 +44,117 @@ winrt::hstring DynamicAutomationPeer::GetNameCore() const { } winrt::AutomationControlType DynamicAutomationPeer::GetAutomationControlTypeCore() const { + const auto automationControlType = GetAutomationControlTypeFromAriaRole(); + return automationControlType ? automationControlType.value() : GetAutomationControlTypeFromAccessibilityRole(); +} + +std::optional DynamicAutomationPeer::GetAutomationControlTypeFromAriaRole() const { + const auto ariaRole = GetAriaRole(); + switch (ariaRole) { + case winrt::Microsoft::ReactNative::AriaRole::Button: + return winrt::AutomationControlType::Button; + case winrt::Microsoft::ReactNative::AriaRole::CheckBox: + return winrt::AutomationControlType::CheckBox; + case winrt::Microsoft::ReactNative::AriaRole::ComboBox: + return winrt::AutomationControlType::ComboBox; + case winrt::Microsoft::ReactNative::AriaRole::Cell: + return winrt::AutomationControlType::DataItem; + case winrt::Microsoft::ReactNative::AriaRole::Grid: + case winrt::Microsoft::ReactNative::AriaRole::Row: + return winrt::AutomationControlType::DataGrid; + case winrt::Microsoft::ReactNative::AriaRole::Article: + case winrt::Microsoft::ReactNative::AriaRole::Document: + return winrt::AutomationControlType::Document; + case winrt::Microsoft::ReactNative::AriaRole::SearchBox: + return winrt::AutomationControlType::Edit; + case winrt::Microsoft::ReactNative::AriaRole::Application: + case winrt::Microsoft::ReactNative::AriaRole::Group: + case winrt::Microsoft::ReactNative::AriaRole::None: + case winrt::Microsoft::ReactNative::AriaRole::RadioGroup: + case winrt::Microsoft::ReactNative::AriaRole::Region: + case winrt::Microsoft::ReactNative::AriaRole::RowGroup: + case winrt::Microsoft::ReactNative::AriaRole::Switch: + case winrt::Microsoft::ReactNative::AriaRole::TabPanel: + case winrt::Microsoft::ReactNative::AriaRole::Timer: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::ColumnHeader: + case winrt::Microsoft::ReactNative::AriaRole::RowHeader: + return winrt::AutomationControlType::HeaderItem; + case winrt::Microsoft::ReactNative::AriaRole::Link: + return winrt::AutomationControlType::Hyperlink; + case winrt::Microsoft::ReactNative::AriaRole::Figure: + case winrt::Microsoft::ReactNative::AriaRole::Img: + return winrt::AutomationControlType::Image; + case winrt::Microsoft::ReactNative::AriaRole::List: + return winrt::AutomationControlType::List; + case winrt::Microsoft::ReactNative::AriaRole::ListItem: + return winrt::AutomationControlType::ListItem; + case winrt::Microsoft::ReactNative::AriaRole::Menu: + return winrt::AutomationControlType::Menu; + case winrt::Microsoft::ReactNative::AriaRole::MenuBar: + return winrt::AutomationControlType::MenuBar; + case winrt::Microsoft::ReactNative::AriaRole::MenuItem: + return winrt::AutomationControlType::MenuItem; + case winrt::Microsoft::ReactNative::AriaRole::ProgressBar: + return winrt::AutomationControlType::ProgressBar; + case winrt::Microsoft::ReactNative::AriaRole::Radio: + return winrt::AutomationControlType::RadioButton; + case winrt::Microsoft::ReactNative::AriaRole::ScrollBar: + return winrt::AutomationControlType::ScrollBar; + case winrt::Microsoft::ReactNative::AriaRole::Separator: + return winrt::AutomationControlType::Separator; + case winrt::Microsoft::ReactNative::AriaRole::Slider: + return winrt::AutomationControlType::Slider; + case winrt::Microsoft::ReactNative::AriaRole::SpinButton: + return winrt::AutomationControlType::Spinner; + case winrt::Microsoft::ReactNative::AriaRole::TabList: + return winrt::AutomationControlType::Tab; + case winrt::Microsoft::ReactNative::AriaRole::Tab: + return winrt::AutomationControlType::TabItem; + case winrt::Microsoft::ReactNative::AriaRole::Table: + return winrt::AutomationControlType::Table; + case winrt::Microsoft::ReactNative::AriaRole::Alert: + case winrt::Microsoft::ReactNative::AriaRole::Banner: + case winrt::Microsoft::ReactNative::AriaRole::Complementary: + case winrt::Microsoft::ReactNative::AriaRole::ContentInfo: + case winrt::Microsoft::ReactNative::AriaRole::Definition: + case winrt::Microsoft::ReactNative::AriaRole::Heading: + case winrt::Microsoft::ReactNative::AriaRole::Note: + case winrt::Microsoft::ReactNative::AriaRole::Status: + case winrt::Microsoft::ReactNative::AriaRole::Summary: + return winrt::AutomationControlType::Text; + case winrt::Microsoft::ReactNative::AriaRole::ToolBar: + return winrt::AutomationControlType::ToolBar; + case winrt::Microsoft::ReactNative::AriaRole::ToolTip: + return winrt::AutomationControlType::ToolTip; + case winrt::Microsoft::ReactNative::AriaRole::Tree: + case winrt::Microsoft::ReactNative::AriaRole::TreeGrid: + return winrt::AutomationControlType::Tree; + case winrt::Microsoft::ReactNative::AriaRole::TreeItem: + return winrt::AutomationControlType::TreeItem; + case winrt::Microsoft::ReactNative::AriaRole::AlertDialog: + case winrt::Microsoft::ReactNative::AriaRole::Dialog: + return winrt::AutomationControlType::Window; + case winrt::Microsoft::ReactNative::AriaRole::Directory: + case winrt::Microsoft::ReactNative::AriaRole::Feed: + case winrt::Microsoft::ReactNative::AriaRole::Form: + case winrt::Microsoft::ReactNative::AriaRole::Log: + case winrt::Microsoft::ReactNative::AriaRole::Main: + case winrt::Microsoft::ReactNative::AriaRole::Marquee: + case winrt::Microsoft::ReactNative::AriaRole::Math: + case winrt::Microsoft::ReactNative::AriaRole::Meter: + case winrt::Microsoft::ReactNative::AriaRole::Navigation: + case winrt::Microsoft::ReactNative::AriaRole::Option: + case winrt::Microsoft::ReactNative::AriaRole::Presentation: + case winrt::Microsoft::ReactNative::AriaRole::Term: + case winrt::Microsoft::ReactNative::AriaRole::Unknown: + break; + } + + return std::nullopt; +} + +winrt::AutomationControlType DynamicAutomationPeer::GetAutomationControlTypeFromAccessibilityRole() const { auto accessibilityRole = GetAccessibilityRole(); switch (accessibilityRole) { @@ -342,6 +453,15 @@ winrt::Microsoft::ReactNative::AccessibilityRoles DynamicAutomationPeer::GetAcce return winrt::Microsoft::ReactNative::AccessibilityRoles::None; } +winrt::Microsoft::ReactNative::AriaRole DynamicAutomationPeer::GetAriaRole() const { + try { + return DynamicAutomationProperties::GetAriaRole(Owner()); + } catch (...) { + } + + return winrt::Microsoft::ReactNative::AriaRole::Unknown; +} + bool DynamicAutomationPeer::HasAccessibilityState(winrt::Microsoft::ReactNative::AccessibilityStates state) const { try { auto const &owner = Owner(); diff --git a/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.h b/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.h index bda38b51e15..30f92190e79 100644 --- a/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.h +++ b/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.h @@ -72,6 +72,11 @@ struct DynamicAutomationPeer : DynamicAutomationPeerT { private: winrt::hstring GetContentName() const; winrt::Microsoft::ReactNative::AccessibilityRoles GetAccessibilityRole() const; + winrt::Microsoft::ReactNative::AriaRole GetAriaRole() const; + + std::optional GetAutomationControlTypeFromAriaRole() const; + xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeFromAccessibilityRole() const; + bool HasAccessibilityState(winrt::Microsoft::ReactNative::AccessibilityStates state) const; bool HasAccessibilityValue(winrt::Microsoft::ReactNative::AccessibilityValue value) const; double GetAccessibilityValueRange(winrt::Microsoft::ReactNative::AccessibilityValue value) const; diff --git a/vnext/Microsoft.ReactNative/Views/DynamicAutomationProperties.cpp b/vnext/Microsoft.ReactNative/Views/DynamicAutomationProperties.cpp index 19f78d3fcb0..7ca514b341d 100644 --- a/vnext/Microsoft.ReactNative/Views/DynamicAutomationProperties.cpp +++ b/vnext/Microsoft.ReactNative/Views/DynamicAutomationProperties.cpp @@ -48,6 +48,26 @@ winrt::Microsoft::ReactNative::AccessibilityRoles DynamicAutomationProperties::G element.GetValue(AccessibilityRoleProperty())); } +xaml::DependencyProperty DynamicAutomationProperties::AriaRoleProperty() { + static xaml::DependencyProperty s_ariaRoleProperty = xaml::DependencyProperty::RegisterAttached( + L"AriaRole", + winrt::xaml_typename(), + dynamicAutomationTypeName, + winrt::PropertyMetadata(winrt::box_value(winrt::Microsoft::ReactNative::AriaRole::Unknown))); + + return s_ariaRoleProperty; +} + +void DynamicAutomationProperties::SetAriaRole( + xaml::UIElement const &element, + winrt::Microsoft::ReactNative::AriaRole const &value) { + element.SetValue(AriaRoleProperty(), winrt::box_value(value)); +} + +winrt::Microsoft::ReactNative::AriaRole DynamicAutomationProperties::GetAriaRole(xaml::UIElement const &element) { + return winrt::unbox_value(element.GetValue(AriaRoleProperty())); +} + xaml::DependencyProperty DynamicAutomationProperties::AccessibilityStateSelectedProperty() { static xaml::DependencyProperty s_AccessibilityStateSelectedProperty = xaml::DependencyProperty::RegisterAttached( L"AccessibilityStateSelected", diff --git a/vnext/Microsoft.ReactNative/Views/DynamicAutomationProperties.h b/vnext/Microsoft.ReactNative/Views/DynamicAutomationProperties.h index 864c464aa95..0b9c7f25add 100644 --- a/vnext/Microsoft.ReactNative/Views/DynamicAutomationProperties.h +++ b/vnext/Microsoft.ReactNative/Views/DynamicAutomationProperties.h @@ -26,6 +26,10 @@ struct DynamicAutomationProperties : DynamicAutomationPropertiesT Date: Mon, 4 Mar 2024 11:51:15 -0500 Subject: [PATCH 2/5] Change files --- ...ative-windows-b20ce41b-73cf-4c53-b96a-5bf1118b6135.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-b20ce41b-73cf-4c53-b96a-5bf1118b6135.json diff --git a/change/react-native-windows-b20ce41b-73cf-4c53-b96a-5bf1118b6135.json b/change/react-native-windows-b20ce41b-73cf-4c53-b96a-5bf1118b6135.json new file mode 100644 index 00000000000..1e3d5e6b5ed --- /dev/null +++ b/change/react-native-windows-b20ce41b-73cf-4c53-b96a-5bf1118b6135.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Adds ARIA role support to Paper UIManager", + "packageName": "react-native-windows", + "email": "erozell@outlook.com", + "dependentChangeType": "patch" +} From a762efebef81273c5333f4ee1b2c47a29fa78473 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Mon, 4 Mar 2024 21:07:30 -0500 Subject: [PATCH 3/5] Improves ARIA role to UIA role mappings Switches from ad hoc mappings to mappings defined here: https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-ariaspecification#w3c-aria-role-mapped-to-microsoft-active-accessibility-and-ui-automation Comments left inline for how the missing mappings were interpolated. --- .../Views/DynamicAutomationPeer.cpp | 155 ++++++++++++------ 1 file changed, 101 insertions(+), 54 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp b/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp index 5872766639e..4a1b129f0a8 100644 --- a/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp +++ b/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp @@ -50,105 +50,152 @@ winrt::AutomationControlType DynamicAutomationPeer::GetAutomationControlTypeCore std::optional DynamicAutomationPeer::GetAutomationControlTypeFromAriaRole() const { const auto ariaRole = GetAriaRole(); + // Unless otherwise specified, mappings sourced from: + // https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-ariaspecification#w3c-aria-role-mapped-to-microsoft-active-accessibility-and-ui-automation + // Remaining mappings are: + // "cell": DataItem (based on "gridcell" mapping) + // "feed": List (based on "directory" mapping) + // "figure": Image (based on "img" mapping) + // "math": Group (based on "definition" mapping) + // "meter": Pane (based on "timer" mapping) + // "none": Group (based on "presentation") + // "rowgroup": Group (based on "group" mapping) + // "searchbox": Group (based on "group" mapping) + // "summary": Unknown (based on missing ARIA documentation) + // "switch": CheckBox (based on "checkbox" mapping) + // "table": Grid (based on "grid" mapping) + // "term": Group (based on "definition" mapping) switch (ariaRole) { + case winrt::Microsoft::ReactNative::AriaRole::Alert: + return winrt::AutomationControlType::Text; + case winrt::Microsoft::ReactNative::AriaRole::AlertDialog: + return winrt::AutomationControlType::Pane; + case winrt::Microsoft::ReactNative::AriaRole::Application: + return winrt::AutomationControlType::Pane; + case winrt::Microsoft::ReactNative::AriaRole::Article: + return winrt::AutomationControlType::Document; + case winrt::Microsoft::ReactNative::AriaRole::Banner: + return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::Button: return winrt::AutomationControlType::Button; + case winrt::Microsoft::ReactNative::AriaRole::Cell: + return winrt::AutomationControlType::DataItem; case winrt::Microsoft::ReactNative::AriaRole::CheckBox: return winrt::AutomationControlType::CheckBox; + case winrt::Microsoft::ReactNative::AriaRole::ColumnHeader: + return winrt::AutomationControlType::DataItem; case winrt::Microsoft::ReactNative::AriaRole::ComboBox: return winrt::AutomationControlType::ComboBox; - case winrt::Microsoft::ReactNative::AriaRole::Cell: - return winrt::AutomationControlType::DataItem; - case winrt::Microsoft::ReactNative::AriaRole::Grid: - case winrt::Microsoft::ReactNative::AriaRole::Row: - return winrt::AutomationControlType::DataGrid; - case winrt::Microsoft::ReactNative::AriaRole::Article: + case winrt::Microsoft::ReactNative::AriaRole::Complementary: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::ContentInfo: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::Definition: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::Dialog: + return winrt::AutomationControlType::Pane; + case winrt::Microsoft::ReactNative::AriaRole::Directory: + return winrt::AutomationControlType::List; case winrt::Microsoft::ReactNative::AriaRole::Document: return winrt::AutomationControlType::Document; - case winrt::Microsoft::ReactNative::AriaRole::SearchBox: - return winrt::AutomationControlType::Edit; - case winrt::Microsoft::ReactNative::AriaRole::Application: + case winrt::Microsoft::ReactNative::AriaRole::Feed: + return winrt::AutomationControlType::List; + case winrt::Microsoft::ReactNative::AriaRole::Figure: + return winrt::AutomationControlType::Image; + case winrt::Microsoft::ReactNative::AriaRole::Form: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::Grid: + return winrt::AutomationControlType::DataGrid; case winrt::Microsoft::ReactNative::AriaRole::Group: - case winrt::Microsoft::ReactNative::AriaRole::None: - case winrt::Microsoft::ReactNative::AriaRole::RadioGroup: - case winrt::Microsoft::ReactNative::AriaRole::Region: - case winrt::Microsoft::ReactNative::AriaRole::RowGroup: - case winrt::Microsoft::ReactNative::AriaRole::Switch: - case winrt::Microsoft::ReactNative::AriaRole::TabPanel: - case winrt::Microsoft::ReactNative::AriaRole::Timer: return winrt::AutomationControlType::Group; - case winrt::Microsoft::ReactNative::AriaRole::ColumnHeader: - case winrt::Microsoft::ReactNative::AriaRole::RowHeader: - return winrt::AutomationControlType::HeaderItem; - case winrt::Microsoft::ReactNative::AriaRole::Link: - return winrt::AutomationControlType::Hyperlink; - case winrt::Microsoft::ReactNative::AriaRole::Figure: + case winrt::Microsoft::ReactNative::AriaRole::Heading: + return winrt::AutomationControlType::Text; case winrt::Microsoft::ReactNative::AriaRole::Img: return winrt::AutomationControlType::Image; + case winrt::Microsoft::ReactNative::AriaRole::Link: + return winrt::AutomationControlType::Hyperlink; case winrt::Microsoft::ReactNative::AriaRole::List: return winrt::AutomationControlType::List; case winrt::Microsoft::ReactNative::AriaRole::ListItem: return winrt::AutomationControlType::ListItem; + case winrt::Microsoft::ReactNative::AriaRole::Log: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::Main: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::Marquee: + return winrt::AutomationControlType::Text; + case winrt::Microsoft::ReactNative::AriaRole::Math: + return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::Menu: return winrt::AutomationControlType::Menu; case winrt::Microsoft::ReactNative::AriaRole::MenuBar: return winrt::AutomationControlType::MenuBar; case winrt::Microsoft::ReactNative::AriaRole::MenuItem: return winrt::AutomationControlType::MenuItem; + case winrt::Microsoft::ReactNative::AriaRole::Meter: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::Navigation: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::None: + return winrt::AutomationControlType::Pane; + case winrt::Microsoft::ReactNative::AriaRole::Note: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::Option: + return winrt::AutomationControlType::ListItem; + case winrt::Microsoft::ReactNative::AriaRole::Presentation: + return winrt::AutomationControlType::Pane; case winrt::Microsoft::ReactNative::AriaRole::ProgressBar: return winrt::AutomationControlType::ProgressBar; case winrt::Microsoft::ReactNative::AriaRole::Radio: return winrt::AutomationControlType::RadioButton; + case winrt::Microsoft::ReactNative::AriaRole::RadioGroup: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::Region: + return winrt::AutomationControlType::Pane; + case winrt::Microsoft::ReactNative::AriaRole::Row: + return winrt::AutomationControlType::DataItem; + case winrt::Microsoft::ReactNative::AriaRole::RowGroup: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::RowHeader: + return winrt::AutomationControlType::DataItem; case winrt::Microsoft::ReactNative::AriaRole::ScrollBar: return winrt::AutomationControlType::ScrollBar; + case winrt::Microsoft::ReactNative::AriaRole::SearchBox: + return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::Separator: return winrt::AutomationControlType::Separator; case winrt::Microsoft::ReactNative::AriaRole::Slider: return winrt::AutomationControlType::Slider; case winrt::Microsoft::ReactNative::AriaRole::SpinButton: return winrt::AutomationControlType::Spinner; - case winrt::Microsoft::ReactNative::AriaRole::TabList: - return winrt::AutomationControlType::Tab; + case winrt::Microsoft::ReactNative::AriaRole::Status: + return winrt::AutomationControlType::StatusBar; + case winrt::Microsoft::ReactNative::AriaRole::Summary: + return winrt::AutomationControlType::Unknown; + case winrt::Microsoft::ReactNative::AriaRole::Switch: + return winrt::AutomationControlType::CheckBox; case winrt::Microsoft::ReactNative::AriaRole::Tab: return winrt::AutomationControlType::TabItem; case winrt::Microsoft::ReactNative::AriaRole::Table: - return winrt::AutomationControlType::Table; - case winrt::Microsoft::ReactNative::AriaRole::Alert: - case winrt::Microsoft::ReactNative::AriaRole::Banner: - case winrt::Microsoft::ReactNative::AriaRole::Complementary: - case winrt::Microsoft::ReactNative::AriaRole::ContentInfo: - case winrt::Microsoft::ReactNative::AriaRole::Definition: - case winrt::Microsoft::ReactNative::AriaRole::Heading: - case winrt::Microsoft::ReactNative::AriaRole::Note: - case winrt::Microsoft::ReactNative::AriaRole::Status: - case winrt::Microsoft::ReactNative::AriaRole::Summary: - return winrt::AutomationControlType::Text; + return winrt::AutomationControlType::Grid; + case winrt::Microsoft::ReactNative::AriaRole::TabList: + return winrt::AutomationControlType::Tab; + case winrt::Microsoft::ReactNative::AriaRole::TabPanel: + return winrt::AutomationControlType::Pane; + case winrt::Microsoft::ReactNative::AriaRole::Term: + return winrt::AutomationControlType::Group; + case winrt::Microsoft::ReactNative::AriaRole::Timer: + return winrt::AutomationControlType::Pane; case winrt::Microsoft::ReactNative::AriaRole::ToolBar: return winrt::AutomationControlType::ToolBar; case winrt::Microsoft::ReactNative::AriaRole::ToolTip: return winrt::AutomationControlType::ToolTip; case winrt::Microsoft::ReactNative::AriaRole::Tree: - case winrt::Microsoft::ReactNative::AriaRole::TreeGrid: return winrt::AutomationControlType::Tree; + case winrt::Microsoft::ReactNative::AriaRole::TreeGrid: + return winrt::AutomationControlType::DataGrid; case winrt::Microsoft::ReactNative::AriaRole::TreeItem: return winrt::AutomationControlType::TreeItem; - case winrt::Microsoft::ReactNative::AriaRole::AlertDialog: - case winrt::Microsoft::ReactNative::AriaRole::Dialog: - return winrt::AutomationControlType::Window; - case winrt::Microsoft::ReactNative::AriaRole::Directory: - case winrt::Microsoft::ReactNative::AriaRole::Feed: - case winrt::Microsoft::ReactNative::AriaRole::Form: - case winrt::Microsoft::ReactNative::AriaRole::Log: - case winrt::Microsoft::ReactNative::AriaRole::Main: - case winrt::Microsoft::ReactNative::AriaRole::Marquee: - case winrt::Microsoft::ReactNative::AriaRole::Math: - case winrt::Microsoft::ReactNative::AriaRole::Meter: - case winrt::Microsoft::ReactNative::AriaRole::Navigation: - case winrt::Microsoft::ReactNative::AriaRole::Option: - case winrt::Microsoft::ReactNative::AriaRole::Presentation: - case winrt::Microsoft::ReactNative::AriaRole::Term: - case winrt::Microsoft::ReactNative::AriaRole::Unknown: - break; } return std::nullopt; @@ -450,7 +497,7 @@ winrt::Microsoft::ReactNative::AccessibilityRoles DynamicAutomationPeer::GetAcce } catch (...) { } - return winrt::Microsoft::ReactNative::AccessibilityRoles::None; + return winrt::Microsoft::ReactNative::AccessibilityRoles::Unknown; } winrt::Microsoft::ReactNative::AriaRole DynamicAutomationPeer::GetAriaRole() const { From 47ed2540c01712f29da5e3513beb446dfbb52579 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Mon, 4 Mar 2024 21:52:22 -0500 Subject: [PATCH 4/5] Fixes a few issues with previous mappings --- .../Views/DynamicAutomationPeer.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp b/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp index 4a1b129f0a8..7c97d33e0ad 100644 --- a/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp +++ b/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp @@ -61,7 +61,7 @@ std::optional DynamicAutomationPeer::GetAutomation // "none": Group (based on "presentation") // "rowgroup": Group (based on "group" mapping) // "searchbox": Group (based on "group" mapping) - // "summary": Unknown (based on missing ARIA documentation) + // "summary": N/A (based on missing ARIA documentation) // "switch": CheckBox (based on "checkbox" mapping) // "table": Grid (based on "grid" mapping) // "term": Group (based on "definition" mapping) @@ -170,14 +170,12 @@ std::optional DynamicAutomationPeer::GetAutomation return winrt::AutomationControlType::Spinner; case winrt::Microsoft::ReactNative::AriaRole::Status: return winrt::AutomationControlType::StatusBar; - case winrt::Microsoft::ReactNative::AriaRole::Summary: - return winrt::AutomationControlType::Unknown; case winrt::Microsoft::ReactNative::AriaRole::Switch: return winrt::AutomationControlType::CheckBox; case winrt::Microsoft::ReactNative::AriaRole::Tab: return winrt::AutomationControlType::TabItem; case winrt::Microsoft::ReactNative::AriaRole::Table: - return winrt::AutomationControlType::Grid; + return winrt::AutomationControlType::DataGrid; case winrt::Microsoft::ReactNative::AriaRole::TabList: return winrt::AutomationControlType::Tab; case winrt::Microsoft::ReactNative::AriaRole::TabPanel: @@ -196,9 +194,11 @@ std::optional DynamicAutomationPeer::GetAutomation return winrt::AutomationControlType::DataGrid; case winrt::Microsoft::ReactNative::AriaRole::TreeItem: return winrt::AutomationControlType::TreeItem; + case winrt::Microsoft::ReactNative::AriaRole::Summary: + case winrt::Microsoft::ReactNative::AriaRole::Unknown: + default: + return std::nullopt; } - - return std::nullopt; } winrt::AutomationControlType DynamicAutomationPeer::GetAutomationControlTypeFromAccessibilityRole() const { @@ -497,7 +497,7 @@ winrt::Microsoft::ReactNative::AccessibilityRoles DynamicAutomationPeer::GetAcce } catch (...) { } - return winrt::Microsoft::ReactNative::AccessibilityRoles::Unknown; + return winrt::Microsoft::ReactNative::AccessibilityRoles::None; } winrt::Microsoft::ReactNative::AriaRole DynamicAutomationPeer::GetAriaRole() const { From e8226ae967f14c3ba610b07a8ceb1e8ff3aaa271 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Wed, 6 Mar 2024 15:17:03 -0500 Subject: [PATCH 5/5] Re-map ARIA -> UIA roles based on https://www.w3.org/TR/core-aam-1.2 --- .../Views/DynamicAutomationPeer.cpp | 58 ++++++++----------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp b/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp index 7c97d33e0ad..11170b3e78b 100644 --- a/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp +++ b/vnext/Microsoft.ReactNative/Views/DynamicAutomationPeer.cpp @@ -50,30 +50,19 @@ winrt::AutomationControlType DynamicAutomationPeer::GetAutomationControlTypeCore std::optional DynamicAutomationPeer::GetAutomationControlTypeFromAriaRole() const { const auto ariaRole = GetAriaRole(); - // Unless otherwise specified, mappings sourced from: - // https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-ariaspecification#w3c-aria-role-mapped-to-microsoft-active-accessibility-and-ui-automation - // Remaining mappings are: - // "cell": DataItem (based on "gridcell" mapping) - // "feed": List (based on "directory" mapping) - // "figure": Image (based on "img" mapping) - // "math": Group (based on "definition" mapping) - // "meter": Pane (based on "timer" mapping) - // "none": Group (based on "presentation") - // "rowgroup": Group (based on "group" mapping) - // "searchbox": Group (based on "group" mapping) - // "summary": N/A (based on missing ARIA documentation) - // "switch": CheckBox (based on "checkbox" mapping) - // "table": Grid (based on "grid" mapping) - // "term": Group (based on "definition" mapping) + // Sourced from: https://www.w3.org/TR/core-aam-1.2 + // Remaining unmapped roles are: + // - "none" + // - "presentation" switch (ariaRole) { case winrt::Microsoft::ReactNative::AriaRole::Alert: - return winrt::AutomationControlType::Text; + return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::AlertDialog: return winrt::AutomationControlType::Pane; case winrt::Microsoft::ReactNative::AriaRole::Application: return winrt::AutomationControlType::Pane; case winrt::Microsoft::ReactNative::AriaRole::Article: - return winrt::AutomationControlType::Document; + return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::Banner: return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::Button: @@ -99,9 +88,9 @@ std::optional DynamicAutomationPeer::GetAutomation case winrt::Microsoft::ReactNative::AriaRole::Document: return winrt::AutomationControlType::Document; case winrt::Microsoft::ReactNative::AriaRole::Feed: - return winrt::AutomationControlType::List; + return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::Figure: - return winrt::AutomationControlType::Image; + return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::Form: return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::Grid: @@ -133,45 +122,46 @@ std::optional DynamicAutomationPeer::GetAutomation case winrt::Microsoft::ReactNative::AriaRole::MenuItem: return winrt::AutomationControlType::MenuItem; case winrt::Microsoft::ReactNative::AriaRole::Meter: - return winrt::AutomationControlType::Group; + return winrt::AutomationControlType::ProgressBar; case winrt::Microsoft::ReactNative::AriaRole::Navigation: return winrt::AutomationControlType::Group; - case winrt::Microsoft::ReactNative::AriaRole::None: - return winrt::AutomationControlType::Pane; case winrt::Microsoft::ReactNative::AriaRole::Note: return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::Option: return winrt::AutomationControlType::ListItem; - case winrt::Microsoft::ReactNative::AriaRole::Presentation: - return winrt::AutomationControlType::Pane; case winrt::Microsoft::ReactNative::AriaRole::ProgressBar: return winrt::AutomationControlType::ProgressBar; case winrt::Microsoft::ReactNative::AriaRole::Radio: return winrt::AutomationControlType::RadioButton; case winrt::Microsoft::ReactNative::AriaRole::RadioGroup: - return winrt::AutomationControlType::Group; + return winrt::AutomationControlType::List; case winrt::Microsoft::ReactNative::AriaRole::Region: - return winrt::AutomationControlType::Pane; + return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::Row: return winrt::AutomationControlType::DataItem; case winrt::Microsoft::ReactNative::AriaRole::RowGroup: return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::RowHeader: - return winrt::AutomationControlType::DataItem; + return winrt::AutomationControlType::HeaderItem; case winrt::Microsoft::ReactNative::AriaRole::ScrollBar: return winrt::AutomationControlType::ScrollBar; case winrt::Microsoft::ReactNative::AriaRole::SearchBox: - return winrt::AutomationControlType::Group; + return winrt::AutomationControlType::Edit; case winrt::Microsoft::ReactNative::AriaRole::Separator: + if (auto const &viewControl = Owner().try_as()) { + if (viewControl.IsTabStop()) { + return winrt::AutomationControlType::Thumb; + } + } return winrt::AutomationControlType::Separator; case winrt::Microsoft::ReactNative::AriaRole::Slider: return winrt::AutomationControlType::Slider; case winrt::Microsoft::ReactNative::AriaRole::SpinButton: return winrt::AutomationControlType::Spinner; case winrt::Microsoft::ReactNative::AriaRole::Status: - return winrt::AutomationControlType::StatusBar; + return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::Switch: - return winrt::AutomationControlType::CheckBox; + return winrt::AutomationControlType::Button; case winrt::Microsoft::ReactNative::AriaRole::Tab: return winrt::AutomationControlType::TabItem; case winrt::Microsoft::ReactNative::AriaRole::Table: @@ -181,9 +171,9 @@ std::optional DynamicAutomationPeer::GetAutomation case winrt::Microsoft::ReactNative::AriaRole::TabPanel: return winrt::AutomationControlType::Pane; case winrt::Microsoft::ReactNative::AriaRole::Term: - return winrt::AutomationControlType::Group; + return winrt::AutomationControlType::Text; case winrt::Microsoft::ReactNative::AriaRole::Timer: - return winrt::AutomationControlType::Pane; + return winrt::AutomationControlType::Group; case winrt::Microsoft::ReactNative::AriaRole::ToolBar: return winrt::AutomationControlType::ToolBar; case winrt::Microsoft::ReactNative::AriaRole::ToolTip: @@ -194,8 +184,8 @@ std::optional DynamicAutomationPeer::GetAutomation return winrt::AutomationControlType::DataGrid; case winrt::Microsoft::ReactNative::AriaRole::TreeItem: return winrt::AutomationControlType::TreeItem; - case winrt::Microsoft::ReactNative::AriaRole::Summary: - case winrt::Microsoft::ReactNative::AriaRole::Unknown: + case winrt::Microsoft::ReactNative::AriaRole::None: + case winrt::Microsoft::ReactNative::AriaRole::Presentation: default: return std::nullopt; }