Skip to content

Commit aa48a7a

Browse files
[Fabric] Implementation of accessibiltyAnnotation and adding support for IAnnotationProvider (#14626)
* Implement accessibilityAnnotation for RNW Fabric * AccessibilityAnnotation Prop implementation final changes * Playground Changes * IAnnotationProvider Implementation * Adding CompositionAnnotationProvider to the project * Adding Test Cases * Change files * Lint issue Fixes * Lint fix * Updating SnapShots * Review Changes * Updating snapshot
1 parent f66ee05 commit aa48a7a

File tree

19 files changed

+400
-4
lines changed

19 files changed

+400
-4
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": "[Fabric] Implementation of accessibiltyAnnotation and adding support for IAnnotationProvider",
4+
"packageName": "react-native-windows",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ class AccessibilityBaseExample extends React.Component {
2626
accessibilityLabel="A blue box"
2727
accessibilityHint="A hint for the blue box."
2828
accessibilityLevel={1}
29+
accessibilityAnnotation={{
30+
typeID: 'Comment',
31+
typeName: 'Check Comment',
32+
author: 'Clint Westwood',
33+
dateTime: '3/19/2025 1:03 PM',
34+
}}
2935
testID="accessibility-base-view-1"
3036
/>
3137
<Text>The following has accessible and accessibilityLabel:</Text>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ exports[`Accessibility Tests Accessibility data for Label and Level 1`] = `
3232
exports[`Accessibility Tests Accessibility data for Label,Level and Hint 1`] = `
3333
{
3434
"Automation Tree": {
35+
"AnnotationPattern.Author": "Clint Westwood",
36+
"AnnotationPattern.DateTime": "3/19/2025 1:03 PM",
37+
"AnnotationPattern.TypeId": 60003,
38+
"AnnotationPattern.TypeName": "Check Comment",
3539
"AutomationId": "accessibility-base-view-1",
3640
"ControlType": 50026,
3741
"HelpText": "A hint for the blue box.",

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ exports[`snapshotAllPages Accessibility Windows 1`] = `
88
The following has accessibilityLabel and accessibilityHint:
99
</Text>
1010
<View
11+
accessibilityAnnotation={
12+
{
13+
"author": "Clint Westwood",
14+
"dateTime": "3/19/2025 1:03 PM",
15+
"typeID": "Comment",
16+
"typeName": "Check Comment",
17+
}
18+
}
1119
accessibilityHint="A hint for the blue box."
1220
accessibilityLabel="A blue box"
1321
accessibilityLevel={1}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@ void DumpUIAPatternInfo(IUIAutomationElement *pTarget, const winrt::Windows::Dat
365365
BOOL selectionRequired;
366366
BSTR text = nullptr;
367367
BOOL horizontallyScrollable;
368+
BSTR annotationAuthor = nullptr;
369+
BSTR annotationTypeName = nullptr;
370+
BSTR annotationDateTime = nullptr;
371+
int annotationTypeID = 0;
368372

369373
// Dump IValueProvider Information
370374
IValueProvider *valuePattern;
@@ -467,6 +471,28 @@ void DumpUIAPatternInfo(IUIAutomationElement *pTarget, const winrt::Windows::Dat
467471
}
468472
}
469473

474+
// Dump IAnnotationProvider Information
475+
winrt::com_ptr<IAnnotationProvider> annotationProvider;
476+
hr = pTarget->GetCurrentPattern(UIA_AnnotationPatternId, reinterpret_cast<IUnknown **>(annotationProvider.put()));
477+
if (SUCCEEDED(hr) && annotationProvider) {
478+
hr = annotationProvider->get_AnnotationTypeId(&annotationTypeID);
479+
if (SUCCEEDED(hr)) {
480+
InsertIntValueIfNotDefault(result, L"AnnotationPattern.TypeId", annotationTypeID, 0);
481+
}
482+
hr = annotationProvider->get_AnnotationTypeName(&annotationTypeName);
483+
if (SUCCEEDED(hr)) {
484+
InsertStringValueIfNotEmpty(result, L"AnnotationPattern.TypeName", annotationTypeName);
485+
}
486+
hr = annotationProvider->get_Author(&annotationAuthor);
487+
if (SUCCEEDED(hr)) {
488+
InsertStringValueIfNotEmpty(result, L"AnnotationPattern.Author", annotationAuthor);
489+
}
490+
hr = annotationProvider->get_DateTime(&annotationDateTime);
491+
if (SUCCEEDED(hr)) {
492+
InsertStringValueIfNotEmpty(result, L"AnnotationPattern.DateTime", annotationDateTime);
493+
}
494+
}
495+
470496
// Dump IScrollProvider Information
471497
winrt::com_ptr<IScrollProvider> scrollPattern;
472498
hr = pTarget->GetCurrentPattern(UIA_ScrollPatternId, reinterpret_cast<IUnknown **>(scrollPattern.put()));

packages/playground/Samples/text.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
*/
66

77
import React from 'react';
8-
import {AppRegistry, StyleSheet, View} from 'react-native';
9-
import {Text} from 'react-native-windows';
8+
import {AppRegistry, StyleSheet} from 'react-native';
9+
import {Text, View} from 'react-native-windows';
1010

1111
export default class Bootstrap extends React.Component {
1212
render() {
@@ -20,7 +20,16 @@ export default class Bootstrap extends React.Component {
2020
selectable={true}>
2121
Click here : This is a text with a tooltip.
2222
</Text>
23-
<View style={styles.container2}>
23+
<View
24+
style={styles.container2}
25+
accessible={true}
26+
accessibilityLabel="Annotation Checkc"
27+
accessibilityAnnotation={{
28+
typeID: 'Comment',
29+
typeName: 'Check Comment',
30+
author: 'Christopher tarantino',
31+
dateTime: '3/19/2025 1:03 PM',
32+
}}>
2433
<Text
2534
adjustsFontSizeToFit
2635
style={{maxHeight: 80, fontSize: 72}}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#include "pch.h"
2+
#include "CompositionAnnotationProvider.h"
3+
#include <Fabric/ComponentView.h>
4+
#include <Unicode.h>
5+
#include "RootComponentView.h"
6+
#include "UiaHelpers.h"
7+
8+
namespace winrt::Microsoft::ReactNative::implementation {
9+
10+
CompositionAnnotationProvider::CompositionAnnotationProvider(
11+
const winrt::Microsoft::ReactNative::Composition::ComponentView &componentView,
12+
CompositionDynamicAutomationProvider *parentProvider) noexcept
13+
: m_view{componentView} {
14+
m_parentProvider.copy_from(parentProvider);
15+
}
16+
HRESULT __stdcall CompositionAnnotationProvider::get_AnnotationTypeId(int *retVal) {
17+
if (retVal == nullptr)
18+
return E_POINTER;
19+
auto strongView = m_view.view();
20+
21+
if (!strongView)
22+
return UIA_E_ELEMENTNOTAVAILABLE;
23+
24+
auto props = std::static_pointer_cast<const facebook::react::ViewProps>(
25+
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(strongView)->props());
26+
if (props == nullptr)
27+
return UIA_E_ELEMENTNOTAVAILABLE;
28+
29+
if (props->accessibilityAnnotation.value().typeID.empty()) {
30+
return E_FAIL;
31+
}
32+
*retVal = static_cast<int>(GetAnnotationTypeId(props->accessibilityAnnotation.value().typeID));
33+
return S_OK;
34+
}
35+
HRESULT __stdcall CompositionAnnotationProvider::get_AnnotationTypeName(BSTR *retVal) {
36+
if (retVal == nullptr)
37+
return E_POINTER;
38+
auto strongView = m_view.view();
39+
40+
if (!strongView)
41+
return UIA_E_ELEMENTNOTAVAILABLE;
42+
43+
auto props = std::static_pointer_cast<const facebook::react::ViewProps>(
44+
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(strongView)->props());
45+
if (props == nullptr)
46+
return UIA_E_ELEMENTNOTAVAILABLE;
47+
48+
if (props->accessibilityAnnotation.value().typeName.empty()) {
49+
return E_FAIL;
50+
}
51+
auto typeName = ::Microsoft::Common::Unicode::Utf8ToUtf16(props->accessibilityAnnotation.value().typeName);
52+
*retVal = SysAllocString(typeName.c_str());
53+
return S_OK;
54+
}
55+
HRESULT __stdcall CompositionAnnotationProvider::get_Author(BSTR *retVal) {
56+
if (retVal == nullptr)
57+
return E_POINTER;
58+
auto strongView = m_view.view();
59+
60+
if (!strongView)
61+
return UIA_E_ELEMENTNOTAVAILABLE;
62+
63+
auto props = std::static_pointer_cast<const facebook::react::ViewProps>(
64+
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(strongView)->props());
65+
if (props == nullptr)
66+
return UIA_E_ELEMENTNOTAVAILABLE;
67+
68+
if (props->accessibilityAnnotation.value().author.empty()) {
69+
return E_FAIL;
70+
}
71+
auto author = ::Microsoft::Common::Unicode::Utf8ToUtf16(props->accessibilityAnnotation.value().author);
72+
*retVal = SysAllocString(author.c_str());
73+
return S_OK;
74+
}
75+
HRESULT __stdcall CompositionAnnotationProvider::get_DateTime(BSTR *retVal) {
76+
if (retVal == nullptr)
77+
return E_POINTER;
78+
auto strongView = m_view.view();
79+
80+
if (!strongView)
81+
return UIA_E_ELEMENTNOTAVAILABLE;
82+
83+
auto props = std::static_pointer_cast<const facebook::react::ViewProps>(
84+
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(strongView)->props());
85+
if (props == nullptr)
86+
return UIA_E_ELEMENTNOTAVAILABLE;
87+
88+
if (props->accessibilityAnnotation.value().dateTime.empty()) {
89+
return E_FAIL;
90+
}
91+
auto dateTime = ::Microsoft::Common::Unicode::Utf8ToUtf16(props->accessibilityAnnotation.value().dateTime);
92+
*retVal = SysAllocString(dateTime.c_str());
93+
return S_OK;
94+
}
95+
96+
HRESULT __stdcall CompositionAnnotationProvider::get_Target(IRawElementProviderSimple **retVal) {
97+
// no-opt
98+
return E_NOTIMPL;
99+
}
100+
} // namespace winrt::Microsoft::ReactNative::implementation
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#pragma once
2+
3+
#include <Fabric/Composition/CompositionDynamicAutomationProvider.h>
4+
#include <Fabric/Composition/CompositionViewComponentView.h>
5+
#include <Fabric/ReactTaggedView.h>
6+
#include <UIAutomation.h>
7+
#include <inspectable.h>
8+
#include <uiautomationcore.h>
9+
10+
namespace winrt::Microsoft::ReactNative::implementation {
11+
12+
class CompositionAnnotationProvider : public winrt::implements<CompositionAnnotationProvider, IAnnotationProvider> {
13+
public:
14+
CompositionAnnotationProvider(
15+
const winrt::Microsoft::ReactNative::Composition::ComponentView &componentView,
16+
CompositionDynamicAutomationProvider *parentProvider) noexcept;
17+
18+
// inherited via IAnnotationProvider
19+
virtual HRESULT __stdcall get_AnnotationTypeId(int *retVal) override;
20+
virtual HRESULT __stdcall get_AnnotationTypeName(BSTR *retVal) override;
21+
virtual HRESULT __stdcall get_Author(BSTR *retVal) override;
22+
virtual HRESULT __stdcall get_DateTime(BSTR *retVal) override;
23+
virtual HRESULT __stdcall get_Target(IRawElementProviderSimple **retVal) override;
24+
25+
private:
26+
::Microsoft::ReactNative::ReactTaggedView m_view;
27+
winrt::com_ptr<IAnnotationProvider> m_annotationProvider;
28+
winrt::com_ptr<CompositionDynamicAutomationProvider> m_parentProvider;
29+
};
30+
31+
} // namespace winrt::Microsoft::ReactNative::implementation

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "pch.h"
22
#include "CompositionDynamicAutomationProvider.h"
33
#include <Fabric/ComponentView.h>
4+
#include <Fabric/Composition/CompositionAnnotationProvider.h>
45
#include <Fabric/Composition/CompositionTextRangeProvider.h>
56
#include <Fabric/Composition/ParagraphComponentView.h>
67
#include <Fabric/Composition/ScrollViewComponentView.h>
@@ -36,6 +37,12 @@ CompositionDynamicAutomationProvider::CompositionDynamicAutomationProvider(
3637
strongView.as<winrt::Microsoft::ReactNative::Composition::ComponentView>(), this)
3738
.try_as<ITextProvider2>();
3839
}
40+
41+
if (strongView.try_as<winrt::Microsoft::ReactNative::Composition::implementation::ViewComponentView>()) {
42+
m_annotationProvider = winrt::make<CompositionAnnotationProvider>(
43+
strongView.as<winrt::Microsoft::ReactNative::Composition::ComponentView>(), this)
44+
.try_as<IAnnotationProvider>();
45+
}
3946
}
4047

4148
CompositionDynamicAutomationProvider::CompositionDynamicAutomationProvider(
@@ -282,6 +289,11 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPatternProvider(PATTE
282289
strongView.try_as<winrt::Microsoft::ReactNative::Composition::implementation::WindowsTextInputComponentView>()) {
283290
m_textProvider.as<IUnknown>().copy_to(pRetVal);
284291
}
292+
if (patternId == UIA_AnnotationPatternId &&
293+
strongView.try_as<winrt::Microsoft::ReactNative::Composition::implementation::ViewComponentView>() &&
294+
accessibilityAnnotationHasValue(props->accessibilityAnnotation)) {
295+
m_annotationProvider.as<IUnknown>().copy_to(pRetVal);
296+
}
285297

286298
return S_OK;
287299
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class CompositionDynamicAutomationProvider : public winrt::implements<
101101
private:
102102
::Microsoft::ReactNative::ReactTaggedView m_view;
103103
winrt::com_ptr<ITextProvider2> m_textProvider;
104+
winrt::com_ptr<IAnnotationProvider> m_annotationProvider;
104105
std::vector<winrt::com_ptr<IRawElementProviderSimple>> m_selectionItems;
105106
// Non-null when this UIA node is the peer of a ContentIslandComponentView.
106107
winrt::Microsoft::UI::Content::ChildSiteLink m_childSiteLink{nullptr};

0 commit comments

Comments
 (0)