From fbd63aebfd8cd93450c9c0eff97bb6d31fe3da4b Mon Sep 17 00:00:00 2001 From: Chiara Mooney <34109996+chiaramooney@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:30:32 -0700 Subject: [PATCH] [Fabric] Implement IExpandCollapseProvider (#13892) * Implement IExpandCollapseProvider * Change files * Adjust Example * Format + Update Snapshots --- ...-9696c96d-9a44-4ffc-84ea-dd5451818206.json | 7 +++ .../js/examples/View/ViewExample.windows.js | 22 +++---- .../ViewComponentTest.test.ts.snap | 1 + .../__snapshots__/snapshotPages.test.js.snap | 13 ++-- .../RNTesterApp-Fabric/RNTesterApp-Fabric.cpp | 30 +++++++++ .../CompositionDynamicAutomationProvider.cpp | 63 +++++++++++++++++++ .../CompositionDynamicAutomationProvider.h | 10 ++- .../Fabric/Composition/UiaHelpers.cpp | 8 +++ .../Fabric/Composition/UiaHelpers.h | 1 + 9 files changed, 134 insertions(+), 21 deletions(-) create mode 100644 change/react-native-windows-9696c96d-9a44-4ffc-84ea-dd5451818206.json diff --git a/change/react-native-windows-9696c96d-9a44-4ffc-84ea-dd5451818206.json b/change/react-native-windows-9696c96d-9a44-4ffc-84ea-dd5451818206.json new file mode 100644 index 00000000000..38c2fdeb383 --- /dev/null +++ b/change/react-native-windows-9696c96d-9a44-4ffc-84ea-dd5451818206.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Implement IExpandCollapseProvider", + "packageName": "react-native-windows", + "email": "34109996+chiaramooney@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/@react-native-windows/tester/src/js/examples/View/ViewExample.windows.js b/packages/@react-native-windows/tester/src/js/examples/View/ViewExample.windows.js index f0a1bc4f216..f37544652c3 100644 --- a/packages/@react-native-windows/tester/src/js/examples/View/ViewExample.windows.js +++ b/packages/@react-native-windows/tester/src/js/examples/View/ViewExample.windows.js @@ -463,6 +463,7 @@ class AccessibilityExample extends React.Component< > { state: {tap: number} = { tap: 0, + expanded: true, }; render(): React.Node { @@ -473,9 +474,8 @@ class AccessibilityExample extends React.Component< accessibilityRole="button" accessibilityValue={0} accessibilityActions={[ - {name: 'cut', label: 'cut'}, - {name: 'copy', label: 'copy'}, - {name: 'paste', label: 'paste'}, + {name: 'expand', label: 'expand'}, + {name: 'collapse', label: 'collapse'}, ]} accessibilityState={{expanded: this.state.expanded, busy: true}} accessibilityPosInSet={1} @@ -486,19 +486,19 @@ class AccessibilityExample extends React.Component< focusable onAccessibilityAction={event => { switch (event.nativeEvent.actionName) { - case 'cut': - Alert.alert('Alert', 'cut action success'); - break; - case 'copy': - Alert.alert('Alert', 'copy action success'); - break; - case 'paste': - Alert.alert('Alert', 'paste action success'); + case 'expand': + this.setState({expanded: true}) break; + case 'collapse': + this.setState({expanded: false}) } }} onAccessibilityTap={() => { this.setState({tap: this.state.tap + 1}); + }} + onPress={()=>{ + this.setState({expanded: !this.state.expanded}); + console.log('Pressed'); }}> A View with accessibility values. Current Number of Accessibility Taps: {this.state.tap} diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/ViewComponentTest.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/ViewComponentTest.test.ts.snap index 6d8b50aeaef..8142b782ae1 100644 --- a/packages/e2e-test-app-fabric/test/__snapshots__/ViewComponentTest.test.ts.snap +++ b/packages/e2e-test-app-fabric/test/__snapshots__/ViewComponentTest.test.ts.snap @@ -1094,6 +1094,7 @@ exports[`View Tests Views can have customized accessibility 1`] = ` "Automation Tree": { "AutomationId": "accessibility", "ControlType": 50000, + "ExpandCollapsePattern.ExpandCollapseState": "Expanded", "HelpText": "Accessibility Hint", "IsKeyboardFocusable": true, "ItemStatus": "Busy", diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap b/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap index 581c1c62a1c..70beb59d921 100644 --- a/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap +++ b/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap @@ -67813,16 +67813,12 @@ exports[`snapshotAllPages View 21`] = ` accessibilityActions={ [ { - "label": "cut", - "name": "cut", + "label": "expand", + "name": "expand", }, { - "label": "copy", - "name": "copy", - }, - { - "label": "paste", - "name": "paste", + "label": "collapse", + "name": "collapse", }, ] } @@ -67845,6 +67841,7 @@ exports[`snapshotAllPages View 21`] = ` focusable={true} onAccessibilityAction={[Function]} onAccessibilityTap={[Function]} + onPress={[Function]} testID="accessibility" > diff --git a/packages/e2e-test-app-fabric/windows/RNTesterApp-Fabric/RNTesterApp-Fabric.cpp b/packages/e2e-test-app-fabric/windows/RNTesterApp-Fabric/RNTesterApp-Fabric.cpp index 6f9ebbfe49f..6dfab8e456d 100644 --- a/packages/e2e-test-app-fabric/windows/RNTesterApp-Fabric/RNTesterApp-Fabric.cpp +++ b/packages/e2e-test-app-fabric/windows/RNTesterApp-Fabric/RNTesterApp-Fabric.cpp @@ -312,6 +312,23 @@ void InsertToggleStateValueIfNotDefault( } } +void InsertExpandCollapseStateValueIfNotDefault( + const winrt::Windows::Data::Json::JsonObject &obj, + winrt::hstring name, + ExpandCollapseState value, + ExpandCollapseState defaultValue = ExpandCollapseState::ExpandCollapseState_Collapsed) { + if (value != defaultValue) { + switch (value) { + case 0: + obj.Insert(name, winrt::Windows::Data::Json::JsonValue::CreateStringValue(L"Collapsed")); + break; + case 1: + obj.Insert(name, winrt::Windows::Data::Json::JsonValue::CreateStringValue(L"Expanded")); + break; + } + } +} + winrt::Windows::Data::Json::JsonObject ListErrors(winrt::Windows::Data::Json::JsonValue payload) { winrt::Windows::Data::Json::JsonObject result; winrt::Windows::Data::Json::JsonArray jsonErrors; @@ -337,6 +354,7 @@ void DumpUIAPatternInfo(IUIAutomationElement *pTarget, const winrt::Windows::Dat BOOL isReadOnly; ToggleState toggleState; IValueProvider *valuePattern; + ExpandCollapseState expandCollapseState; HRESULT hr; // Dump IValueProvider Information @@ -363,6 +381,18 @@ void DumpUIAPatternInfo(IUIAutomationElement *pTarget, const winrt::Windows::Dat } togglePattern->Release(); } + + // Dump IExpandCollapseProvider Information + IExpandCollapseProvider *expandCollapsePattern; + hr = pTarget->GetCurrentPattern(UIA_ExpandCollapsePatternId, reinterpret_cast(&expandCollapsePattern)); + if (SUCCEEDED(hr) && expandCollapsePattern) { + hr = expandCollapsePattern->get_ExpandCollapseState(&expandCollapseState); + if (SUCCEEDED(hr)) { + InsertExpandCollapseStateValueIfNotDefault( + result, L"ExpandCollapsePattern.ExpandCollapseState", expandCollapseState); + } + expandCollapsePattern->Release(); + } } winrt::Windows::Data::Json::JsonObject DumpUIATreeRecurse( diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp index a4040fa9bf9..66cfa867890 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp @@ -125,6 +125,22 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::get_ProviderOptions(Prov return S_OK; } +bool accessibilityValueHasValue(const facebook::react::AccessibilityValue &value) { + return (value.min.has_value() && value.max.has_value()) || value.now.has_value() || value.text.has_value(); +} + +bool expandableControl(const facebook::react::SharedViewProps props) { + if (props->accessibilityState.has_value() && props->accessibilityState->expanded.has_value()) + return true; + auto accessibilityActions = props->accessibilityActions; + for (size_t i = 0; i < accessibilityActions.size(); i++) { + if (accessibilityActions[i].name == "expand" || accessibilityActions[i].name == "collapse") { + return true; + } + } + return false; +} + HRESULT __stdcall CompositionDynamicAutomationProvider::GetPatternProvider(PATTERNID patternId, IUnknown **pRetVal) { if (pRetVal == nullptr) return E_POINTER; @@ -165,6 +181,15 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPatternProvider(PATTE AddRef(); } + if (patternId == UIA_ExpandCollapsePatternId && + (accessibilityRole == "combobox" || accessibilityRole == "splitbutton" || accessibilityRole == "treeitem" || + (expandableControl(props) && + (accessibilityRole == "toolbar" || accessibilityRole == "menuitem" || accessibilityRole == "menubar" || + accessibilityRole == "listitem" || accessibilityRole == "group" || accessibilityRole == "button")))) { + *pRetVal = static_cast(this); + AddRef(); + } + return S_OK; } @@ -471,4 +496,42 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::Toggle() { return S_OK; } +HRESULT __stdcall CompositionDynamicAutomationProvider::get_ExpandCollapseState(ExpandCollapseState *pRetVal) { + if (pRetVal == nullptr) + return E_POINTER; + auto strongView = m_view.view(); + + if (!strongView) + return UIA_E_ELEMENTNOTAVAILABLE; + + auto props = std::static_pointer_cast( + winrt::get_self(strongView)->props()); + + if (props == nullptr) + return UIA_E_ELEMENTNOTAVAILABLE; + + *pRetVal = props->accessibilityState->expanded.has_value() + ? GetExpandCollapseState(props->accessibilityState->expanded.value()) + : ExpandCollapseState_Collapsed; + return S_OK; +} + +HRESULT __stdcall CompositionDynamicAutomationProvider::Expand() { + auto strongView = m_view.view(); + + if (!strongView) + return UIA_E_ELEMENTNOTAVAILABLE; + DispatchAccessibilityAction(m_view, "expand"); + return S_OK; +} + +HRESULT __stdcall CompositionDynamicAutomationProvider::Collapse() { + auto strongView = m_view.view(); + + if (!strongView) + return UIA_E_ELEMENTNOTAVAILABLE; + DispatchAccessibilityAction(m_view, "collapse"); + return S_OK; +} + } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.h b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.h index b089efd7e84..fd37624b580 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.h @@ -16,7 +16,8 @@ class CompositionDynamicAutomationProvider : public winrt::implements< IInvokeProvider, IScrollItemProvider, IValueProvider, - IToggleProvider> { + IToggleProvider, + IExpandCollapseProvider> { public: CompositionDynamicAutomationProvider( const winrt::Microsoft::ReactNative::Composition::ComponentView &componentView) noexcept; @@ -47,10 +48,15 @@ class CompositionDynamicAutomationProvider : public winrt::implements< virtual HRESULT __stdcall get_Value(BSTR *pRetVal) override; virtual HRESULT __stdcall get_IsReadOnly(BOOL *pRetVal) override; - // inherited via IToggleProivder + // inherited via IToggleProvider virtual HRESULT __stdcall get_ToggleState(ToggleState *pRetVal) override; virtual HRESULT __stdcall Toggle() override; + // inherited via IExpandCollapseProvider + virtual HRESULT __stdcall get_ExpandCollapseState(ExpandCollapseState *pRetVal) override; + virtual HRESULT __stdcall Expand() override; + virtual HRESULT __stdcall Collapse() override; + private: ::Microsoft::ReactNative::ReactTaggedView m_view; }; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp index b678219c06c..9689b046f60 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp @@ -206,4 +206,12 @@ void DispatchAccessibilityAction(::Microsoft::ReactNative::ReactTaggedView &view } } +ExpandCollapseState GetExpandCollapseState(const bool &expanded) noexcept { + if (expanded) { + return ExpandCollapseState_Expanded; + } else { + return ExpandCollapseState_Collapsed; + } +} + } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h index 28a6fcbebcb..42a31aa131e 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h @@ -35,4 +35,5 @@ std::string extractAccessibilityValue(const facebook::react::AccessibilityValue void DispatchAccessibilityAction(::Microsoft::ReactNative::ReactTaggedView &view, const std::string &action) noexcept; +ExpandCollapseState GetExpandCollapseState(const bool &expanded) noexcept; } // namespace winrt::Microsoft::ReactNative::implementation