Skip to content

Commit 01fe04b

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

File tree

10 files changed

+150
-22
lines changed

10 files changed

+150
-22
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: 15 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,28 +474,31 @@ 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
]}
480+
accessibilityState={{expanded: this.state.expanded}}
481+
accessibilityPosInSet={1}
482+
accessibilitySetSize={1}
483+
accessibilityLiveRegion='polite'
480484
testID="accessibility"
481485
accessible
482486
focusable
483487
onAccessibilityAction={event => {
484488
switch (event.nativeEvent.actionName) {
485-
case 'cut':
486-
Alert.alert('Alert', 'cut action success');
487-
break;
488-
case 'copy':
489-
Alert.alert('Alert', 'copy action success');
490-
break;
491-
case 'paste':
492-
Alert.alert('Alert', 'paste action success');
489+
case 'expand':
490+
this.setState({expanded: true})
493491
break;
492+
case 'collapse':
493+
this.setState({expanded: false})
494494
}
495495
}}
496496
onAccessibilityTap={() => {
497497
this.setState({tap: this.state.tap + 1});
498+
}}
499+
onPress={()=>{
500+
this.setState({expanded: !this.state.expanded});
501+
console.log('Pressed');
498502
}}>
499503
<Text>A View with accessibility values.</Text>
500504
<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
"LocalizedControlType": "button",

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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83759,27 +83759,34 @@ exports[`snapshotAllPages View 20`] = `
8375983759
accessibilityActions={
8376083760
[
8376183761
{
83762-
"label": "cut",
83763-
"name": "cut",
83762+
"label": "expand",
83763+
"name": "expand",
8376483764
},
8376583765
{
83766-
"label": "copy",
83767-
"name": "copy",
83768-
},
83769-
{
83770-
"label": "paste",
83771-
"name": "paste",
83766+
"label": "collapse",
83767+
"name": "collapse",
8377283768
},
8377383769
]
8377483770
}
8377583771
accessibilityHint="Accessibility Hint"
8377683772
accessibilityLabel="A View with accessibility values"
8377783773
accessibilityRole="button"
83778-
accessibilityValue={0}
83774+
accessibilitySetSize={1}
83775+
accessibilityState={
83776+
{
83777+
"expanded": true,
83778+
}
83779+
}
83780+
accessibilityValue={
83781+
{
83782+
"now": 0,
83783+
}
83784+
}
8377983785
accessible={true}
8378083786
focusable={true}
8378183787
onAccessibilityAction={[Function]}
8378283788
onAccessibilityTap={[Function]}
83789+
onPress={[Function]}
8378383790
testID="accessibility"
8378483791
>
8378583792
<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
@@ -308,6 +308,23 @@ void InsertToggleStateValueIfNotDefault(
308308
}
309309
}
310310

311+
void InsertExpandCollapseStateValueIfNotDefault(
312+
const winrt::Windows::Data::Json::JsonObject &obj,
313+
winrt::hstring name,
314+
ExpandCollapseState value,
315+
ExpandCollapseState defaultValue = ExpandCollapseState::ExpandCollapseState_Collapsed) {
316+
if (value != defaultValue) {
317+
switch (value) {
318+
case 0:
319+
obj.Insert(name, winrt::Windows::Data::Json::JsonValue::CreateStringValue(L"Collapsed"));
320+
break;
321+
case 1:
322+
obj.Insert(name, winrt::Windows::Data::Json::JsonValue::CreateStringValue(L"Expanded"));
323+
break;
324+
}
325+
}
326+
}
327+
311328
winrt::Windows::Data::Json::JsonObject ListErrors(winrt::Windows::Data::Json::JsonValue payload) {
312329
winrt::Windows::Data::Json::JsonObject result;
313330
winrt::Windows::Data::Json::JsonArray jsonErrors;
@@ -333,6 +350,7 @@ void DumpUIAPatternInfo(IUIAutomationElement *pTarget, const winrt::Windows::Dat
333350
BOOL isReadOnly;
334351
ToggleState toggleState;
335352
IValueProvider *valuePattern;
353+
ExpandCollapseState expandCollapseState;
336354
HRESULT hr;
337355

338356
// Dump IValueProvider Information
@@ -359,6 +377,18 @@ void DumpUIAPatternInfo(IUIAutomationElement *pTarget, const winrt::Windows::Dat
359377
}
360378
togglePattern->Release();
361379
}
380+
381+
// Dump IExpandCollapseProvider Information
382+
IExpandCollapseProvider *expandCollapsePattern;
383+
hr = pTarget->GetCurrentPattern(UIA_ExpandCollapsePatternId, reinterpret_cast<IUnknown **>(&expandCollapsePattern));
384+
if (SUCCEEDED(hr) && expandCollapsePattern) {
385+
hr = expandCollapsePattern->get_ExpandCollapseState(&expandCollapseState);
386+
if (SUCCEEDED(hr)) {
387+
InsertExpandCollapseStateValueIfNotDefault(
388+
result, L"ExpandCollapsePattern.ExpandCollapseState", expandCollapseState);
389+
}
390+
expandCollapsePattern->Release();
391+
}
362392
}
363393

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

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ exports[`ViewTests Views can have a custom nativeID 1`] = `
363363
exports[`ViewTests Views can have accessibility customization 1`] = `
364364
{
365365
"AccessibilityRole": "Button",
366+
"AccessibilityStateExpanded": true,
366367
"AutomationId": "accessibility",
367368
"Background": null,
368369
"BorderBrush": null,

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

@@ -464,4 +489,42 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::Toggle() {
464489
return S_OK;
465490
}
466491

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