Skip to content

Commit

Permalink
[Fabric] Implement IExpandCollapseProvider (microsoft#13892)
Browse files Browse the repository at this point in the history
* Implement IExpandCollapseProvider

* Change files

* Adjust Example

* Format + Update Snapshots
  • Loading branch information
chiaramooney authored and acoates-ms committed Oct 16, 2024
1 parent 12997d1 commit fbd63ae
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Implement IExpandCollapseProvider",
"packageName": "react-native-windows",
"email": "34109996+chiaramooney@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ class AccessibilityExample extends React.Component<
> {
state: {tap: number} = {
tap: 0,
expanded: true,
};

render(): React.Node {
Expand All @@ -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}
Expand All @@ -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');
}}>
<Text>A View with accessibility values.</Text>
<Text>Current Number of Accessibility Taps: {this.state.tap}</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
]
}
Expand All @@ -67845,6 +67841,7 @@ exports[`snapshotAllPages View 21`] = `
focusable={true}
onAccessibilityAction={[Function]}
onAccessibilityTap={[Function]}
onPress={[Function]}
testID="accessibility"
>
<Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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<IUnknown **>(&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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<IExpandCollapseProvider *>(this);
AddRef();
}

return S_OK;
}

Expand Down Expand Up @@ -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<const facebook::react::ViewProps>(
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
};
Expand Down
8 changes: 8 additions & 0 deletions vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit fbd63ae

Please sign in to comment.