Skip to content

Commit fbd63ae

Browse files
chiaramooneyacoates-ms
authored andcommitted
[Fabric] Implement IExpandCollapseProvider (microsoft#13892)
* Implement IExpandCollapseProvider * Change files * Adjust Example * Format + Update Snapshots
1 parent 12997d1 commit fbd63ae

File tree

9 files changed

+134
-21
lines changed

9 files changed

+134
-21
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Implement IExpandCollapseProvider",
4+
"packageName": "react-native-windows",
5+
"email": "34109996+chiaramooney@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

packages/@react-native-windows/tester/src/js/examples/View/ViewExample.windows.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ class AccessibilityExample extends React.Component<
463463
> {
464464
state: {tap: number} = {
465465
tap: 0,
466+
expanded: true,
466467
};
467468

468469
render(): React.Node {
@@ -473,9 +474,8 @@ class AccessibilityExample extends React.Component<
473474
accessibilityRole="button"
474475
accessibilityValue={0}
475476
accessibilityActions={[
476-
{name: 'cut', label: 'cut'},
477-
{name: 'copy', label: 'copy'},
478-
{name: 'paste', label: 'paste'},
477+
{name: 'expand', label: 'expand'},
478+
{name: 'collapse', label: 'collapse'},
479479
]}
480480
accessibilityState={{expanded: this.state.expanded, busy: true}}
481481
accessibilityPosInSet={1}
@@ -486,19 +486,19 @@ class AccessibilityExample extends React.Component<
486486
focusable
487487
onAccessibilityAction={event => {
488488
switch (event.nativeEvent.actionName) {
489-
case 'cut':
490-
Alert.alert('Alert', 'cut action success');
491-
break;
492-
case 'copy':
493-
Alert.alert('Alert', 'copy action success');
494-
break;
495-
case 'paste':
496-
Alert.alert('Alert', 'paste action success');
489+
case 'expand':
490+
this.setState({expanded: true})
497491
break;
492+
case 'collapse':
493+
this.setState({expanded: false})
498494
}
499495
}}
500496
onAccessibilityTap={() => {
501497
this.setState({tap: this.state.tap + 1});
498+
}}
499+
onPress={()=>{
500+
this.setState({expanded: !this.state.expanded});
501+
console.log('Pressed');
502502
}}>
503503
<Text>A View with accessibility values.</Text>
504504
<Text>Current Number of Accessibility Taps: {this.state.tap}</Text>

packages/e2e-test-app-fabric/test/__snapshots__/ViewComponentTest.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,7 @@ exports[`View Tests Views can have customized accessibility 1`] = `
10941094
"Automation Tree": {
10951095
"AutomationId": "accessibility",
10961096
"ControlType": 50000,
1097+
"ExpandCollapsePattern.ExpandCollapseState": "Expanded",
10971098
"HelpText": "Accessibility Hint",
10981099
"IsKeyboardFocusable": true,
10991100
"ItemStatus": "Busy",

packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67813,16 +67813,12 @@ exports[`snapshotAllPages View 21`] = `
6781367813
accessibilityActions={
6781467814
[
6781567815
{
67816-
"label": "cut",
67817-
"name": "cut",
67816+
"label": "expand",
67817+
"name": "expand",
6781867818
},
6781967819
{
67820-
"label": "copy",
67821-
"name": "copy",
67822-
},
67823-
{
67824-
"label": "paste",
67825-
"name": "paste",
67820+
"label": "collapse",
67821+
"name": "collapse",
6782667822
},
6782767823
]
6782867824
}
@@ -67845,6 +67841,7 @@ exports[`snapshotAllPages View 21`] = `
6784567841
focusable={true}
6784667842
onAccessibilityAction={[Function]}
6784767843
onAccessibilityTap={[Function]}
67844+
onPress={[Function]}
6784867845
testID="accessibility"
6784967846
>
6785067847
<Text>

packages/e2e-test-app-fabric/windows/RNTesterApp-Fabric/RNTesterApp-Fabric.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,23 @@ void InsertToggleStateValueIfNotDefault(
312312
}
313313
}
314314

315+
void InsertExpandCollapseStateValueIfNotDefault(
316+
const winrt::Windows::Data::Json::JsonObject &obj,
317+
winrt::hstring name,
318+
ExpandCollapseState value,
319+
ExpandCollapseState defaultValue = ExpandCollapseState::ExpandCollapseState_Collapsed) {
320+
if (value != defaultValue) {
321+
switch (value) {
322+
case 0:
323+
obj.Insert(name, winrt::Windows::Data::Json::JsonValue::CreateStringValue(L"Collapsed"));
324+
break;
325+
case 1:
326+
obj.Insert(name, winrt::Windows::Data::Json::JsonValue::CreateStringValue(L"Expanded"));
327+
break;
328+
}
329+
}
330+
}
331+
315332
winrt::Windows::Data::Json::JsonObject ListErrors(winrt::Windows::Data::Json::JsonValue payload) {
316333
winrt::Windows::Data::Json::JsonObject result;
317334
winrt::Windows::Data::Json::JsonArray jsonErrors;
@@ -337,6 +354,7 @@ void DumpUIAPatternInfo(IUIAutomationElement *pTarget, const winrt::Windows::Dat
337354
BOOL isReadOnly;
338355
ToggleState toggleState;
339356
IValueProvider *valuePattern;
357+
ExpandCollapseState expandCollapseState;
340358
HRESULT hr;
341359

342360
// Dump IValueProvider Information
@@ -363,6 +381,18 @@ void DumpUIAPatternInfo(IUIAutomationElement *pTarget, const winrt::Windows::Dat
363381
}
364382
togglePattern->Release();
365383
}
384+
385+
// Dump IExpandCollapseProvider Information
386+
IExpandCollapseProvider *expandCollapsePattern;
387+
hr = pTarget->GetCurrentPattern(UIA_ExpandCollapsePatternId, reinterpret_cast<IUnknown **>(&expandCollapsePattern));
388+
if (SUCCEEDED(hr) && expandCollapsePattern) {
389+
hr = expandCollapsePattern->get_ExpandCollapseState(&expandCollapseState);
390+
if (SUCCEEDED(hr)) {
391+
InsertExpandCollapseStateValueIfNotDefault(
392+
result, L"ExpandCollapsePattern.ExpandCollapseState", expandCollapseState);
393+
}
394+
expandCollapsePattern->Release();
395+
}
366396
}
367397

368398
winrt::Windows::Data::Json::JsonObject DumpUIATreeRecurse(

vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,22 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::get_ProviderOptions(Prov
125125
return S_OK;
126126
}
127127

128+
bool accessibilityValueHasValue(const facebook::react::AccessibilityValue &value) {
129+
return (value.min.has_value() && value.max.has_value()) || value.now.has_value() || value.text.has_value();
130+
}
131+
132+
bool expandableControl(const facebook::react::SharedViewProps props) {
133+
if (props->accessibilityState.has_value() && props->accessibilityState->expanded.has_value())
134+
return true;
135+
auto accessibilityActions = props->accessibilityActions;
136+
for (size_t i = 0; i < accessibilityActions.size(); i++) {
137+
if (accessibilityActions[i].name == "expand" || accessibilityActions[i].name == "collapse") {
138+
return true;
139+
}
140+
}
141+
return false;
142+
}
143+
128144
HRESULT __stdcall CompositionDynamicAutomationProvider::GetPatternProvider(PATTERNID patternId, IUnknown **pRetVal) {
129145
if (pRetVal == nullptr)
130146
return E_POINTER;
@@ -165,6 +181,15 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPatternProvider(PATTE
165181
AddRef();
166182
}
167183

184+
if (patternId == UIA_ExpandCollapsePatternId &&
185+
(accessibilityRole == "combobox" || accessibilityRole == "splitbutton" || accessibilityRole == "treeitem" ||
186+
(expandableControl(props) &&
187+
(accessibilityRole == "toolbar" || accessibilityRole == "menuitem" || accessibilityRole == "menubar" ||
188+
accessibilityRole == "listitem" || accessibilityRole == "group" || accessibilityRole == "button")))) {
189+
*pRetVal = static_cast<IExpandCollapseProvider *>(this);
190+
AddRef();
191+
}
192+
168193
return S_OK;
169194
}
170195

@@ -471,4 +496,42 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::Toggle() {
471496
return S_OK;
472497
}
473498

499+
HRESULT __stdcall CompositionDynamicAutomationProvider::get_ExpandCollapseState(ExpandCollapseState *pRetVal) {
500+
if (pRetVal == nullptr)
501+
return E_POINTER;
502+
auto strongView = m_view.view();
503+
504+
if (!strongView)
505+
return UIA_E_ELEMENTNOTAVAILABLE;
506+
507+
auto props = std::static_pointer_cast<const facebook::react::ViewProps>(
508+
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(strongView)->props());
509+
510+
if (props == nullptr)
511+
return UIA_E_ELEMENTNOTAVAILABLE;
512+
513+
*pRetVal = props->accessibilityState->expanded.has_value()
514+
? GetExpandCollapseState(props->accessibilityState->expanded.value())
515+
: ExpandCollapseState_Collapsed;
516+
return S_OK;
517+
}
518+
519+
HRESULT __stdcall CompositionDynamicAutomationProvider::Expand() {
520+
auto strongView = m_view.view();
521+
522+
if (!strongView)
523+
return UIA_E_ELEMENTNOTAVAILABLE;
524+
DispatchAccessibilityAction(m_view, "expand");
525+
return S_OK;
526+
}
527+
528+
HRESULT __stdcall CompositionDynamicAutomationProvider::Collapse() {
529+
auto strongView = m_view.view();
530+
531+
if (!strongView)
532+
return UIA_E_ELEMENTNOTAVAILABLE;
533+
DispatchAccessibilityAction(m_view, "collapse");
534+
return S_OK;
535+
}
536+
474537
} // namespace winrt::Microsoft::ReactNative::implementation

vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ class CompositionDynamicAutomationProvider : public winrt::implements<
1616
IInvokeProvider,
1717
IScrollItemProvider,
1818
IValueProvider,
19-
IToggleProvider> {
19+
IToggleProvider,
20+
IExpandCollapseProvider> {
2021
public:
2122
CompositionDynamicAutomationProvider(
2223
const winrt::Microsoft::ReactNative::Composition::ComponentView &componentView) noexcept;
@@ -47,10 +48,15 @@ class CompositionDynamicAutomationProvider : public winrt::implements<
4748
virtual HRESULT __stdcall get_Value(BSTR *pRetVal) override;
4849
virtual HRESULT __stdcall get_IsReadOnly(BOOL *pRetVal) override;
4950

50-
// inherited via IToggleProivder
51+
// inherited via IToggleProvider
5152
virtual HRESULT __stdcall get_ToggleState(ToggleState *pRetVal) override;
5253
virtual HRESULT __stdcall Toggle() override;
5354

55+
// inherited via IExpandCollapseProvider
56+
virtual HRESULT __stdcall get_ExpandCollapseState(ExpandCollapseState *pRetVal) override;
57+
virtual HRESULT __stdcall Expand() override;
58+
virtual HRESULT __stdcall Collapse() override;
59+
5460
private:
5561
::Microsoft::ReactNative::ReactTaggedView m_view;
5662
};

vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,12 @@ void DispatchAccessibilityAction(::Microsoft::ReactNative::ReactTaggedView &view
206206
}
207207
}
208208

209+
ExpandCollapseState GetExpandCollapseState(const bool &expanded) noexcept {
210+
if (expanded) {
211+
return ExpandCollapseState_Expanded;
212+
} else {
213+
return ExpandCollapseState_Collapsed;
214+
}
215+
}
216+
209217
} // namespace winrt::Microsoft::ReactNative::implementation

vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ std::string extractAccessibilityValue(const facebook::react::AccessibilityValue
3535

3636
void DispatchAccessibilityAction(::Microsoft::ReactNative::ReactTaggedView &view, const std::string &action) noexcept;
3737

38+
ExpandCollapseState GetExpandCollapseState(const bool &expanded) noexcept;
3839
} // namespace winrt::Microsoft::ReactNative::implementation

0 commit comments

Comments
 (0)