From ba8a3f99cc66b9f841c0c4ba6cd1da8d10f4a32e Mon Sep 17 00:00:00 2001 From: Vineeth K <kvineeth@microsoft.com> Date: Mon, 21 Apr 2025 15:14:27 +0530 Subject: [PATCH 1/6] Implement accessibilityLevel for Fabric --- .../windows/RNTesterApp-Fabric/RNTesterApp-Fabric.cpp | 3 +++ .../Composition/CompositionDynamicAutomationProvider.cpp | 5 +++++ .../Fabric/Composition/CompositionViewComponentView.cpp | 3 +++ .../react/renderer/components/view/HostPlatformViewProps.cpp | 5 +++++ .../react/renderer/components/view/HostPlatformViewProps.h | 1 + 5 files changed, 17 insertions(+) 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 7cff87dadb2..f0a935e1260 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 @@ -494,6 +494,7 @@ winrt::Windows::Data::Json::JsonObject DumpUIATreeRecurse( BSTR name; int positionInSet = 0; int sizeOfSet = 0; + int level = 0; LiveSetting liveSetting = LiveSetting::Off; BSTR itemStatus; @@ -511,6 +512,7 @@ winrt::Windows::Data::Json::JsonObject DumpUIATreeRecurse( pTarget4->get_CurrentPositionInSet(&positionInSet); pTarget4->get_CurrentSizeOfSet(&sizeOfSet); pTarget4->get_CurrentLiveSetting(&liveSetting); + pTarget4->get_CurrentLevel(&level); pTarget4->Release(); } result.Insert(L"AutomationId", winrt::Windows::Data::Json::JsonValue::CreateStringValue(automationId)); @@ -523,6 +525,7 @@ winrt::Windows::Data::Json::JsonObject DumpUIATreeRecurse( InsertStringValueIfNotEmpty(result, L"Name", name); InsertIntValueIfNotDefault(result, L"PositionInSet", positionInSet); InsertIntValueIfNotDefault(result, L"SizeofSet", sizeOfSet); + InsertIntValueIfNotDefault(result, L"Level", level); InsertLiveSettingValueIfNotDefault(result, L"LiveSetting", liveSetting); InsertStringValueIfNotEmpty(result, L"ItemStatus", itemStatus); DumpUIAPatternInfo(pTarget, result); diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp index b229f740ab5..e0ab968867c 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp @@ -563,6 +563,11 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPropertyValue(PROPERT : SysAllocString(L""); break; } + case UIA_LevelPropertyId: { + pRetVal->vt = VT_I4; + pRetVal->lVal = props->accessibilityLevel; + break; + } } return hr; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp index 38af8995408..c23df8a9d06 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp @@ -804,6 +804,9 @@ void ComponentView::updateAccessibilityProps( oldViewProps.accessibilityLiveRegion, newViewProps.accessibilityLiveRegion); + winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( + EnsureUiaProvider(), UIA_LevelPropertyId, oldViewProps.accessibilityLevel, newViewProps.accessibilityLevel); + if ((oldViewProps.accessibilityState.has_value() && oldViewProps.accessibilityState->selected.has_value()) != ((newViewProps.accessibilityState.has_value() && newViewProps.accessibilityState->selected.has_value()))) { auto compProvider = diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.cpp b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.cpp index 3b8666ab7b9..4c3aeb8e37a 100644 --- a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.cpp @@ -40,6 +40,10 @@ HostPlatformViewProps::HostPlatformViewProps( ReactNativeFeatureFlags::enableCppPropsIteratorSetter() ? sourceProps.accessibilitySetSize : convertRawProp(context, rawProps, "accessibilitySetSize", sourceProps.accessibilitySetSize, 0)), + accessibilityLevel( + ReactNativeFeatureFlags::enableCppPropsIteratorSetter() + ? sourceProps.accessibilityLevel + : convertRawProp(context, rawProps, "accessibilityLevel", sourceProps.accessibilityLevel, 0)), accessibilityLiveRegion( ReactNativeFeatureFlags::enableCppPropsIteratorSetter() ? sourceProps.accessibilityLiveRegion : convertRawProp( @@ -84,6 +88,7 @@ void HostPlatformViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(focusable); RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityPosInSet); RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilitySetSize); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLevel); RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLiveRegion); RAW_SET_PROP_SWITCH_CASE_BASIC(keyDownEvents); RAW_SET_PROP_SWITCH_CASE_BASIC(keyUpEvents); diff --git a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.h b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.h index 885dd8cbf18..0a48bc1e787 100644 --- a/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.h +++ b/vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewProps.h @@ -27,6 +27,7 @@ class HostPlatformViewProps : public BaseViewProps { int accessibilityPosInSet{0}; int accessibilitySetSize{0}; std::string accessibilityLiveRegion{"none"}; + int accessibilityLevel{0}; // std::optional<std::string> overflowAnchor{}; std::optional<std::string> tooltip{}; From aaf2ab35a419dd8e1fdd3b4941a19188e34a884e Mon Sep 17 00:00:00 2001 From: Vineeth K <kvineeth@microsoft.com> Date: Mon, 21 Apr 2025 15:22:14 +0530 Subject: [PATCH 2/6] Change files --- ...ative-windows-5aa167ea-3ec8-4b11-87ed-14f728f2eec6.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-5aa167ea-3ec8-4b11-87ed-14f728f2eec6.json diff --git a/change/react-native-windows-5aa167ea-3ec8-4b11-87ed-14f728f2eec6.json b/change/react-native-windows-5aa167ea-3ec8-4b11-87ed-14f728f2eec6.json new file mode 100644 index 00000000000..baa9fd7b9bb --- /dev/null +++ b/change/react-native-windows-5aa167ea-3ec8-4b11-87ed-14f728f2eec6.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Implement accessibilityLevel for Fabric", + "packageName": "react-native-windows", + "email": "kvineeth@microsoft.com", + "dependentChangeType": "patch" +} From 5f216377f237fa0bda92d6c0cbe864b87bf9f853 Mon Sep 17 00:00:00 2001 From: Vineeth K <kvineeth@microsoft.com> Date: Tue, 22 Apr 2025 13:24:19 +0530 Subject: [PATCH 3/6] Adding Test Cases --- .../Accessibility/AccessibilityExampleWindows.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx b/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx index 2c3ffef60bb..f601da1c72d 100644 --- a/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx +++ b/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx @@ -19,19 +19,23 @@ class AccessibilityBaseExample extends React.Component { public render() { return ( <View> - <Text>The following has accessibilityLabel and accessibilityHint:</Text> + <Text accessibilityLevel={1}> + The following has accessibilityLabel and accessibilityHint: + </Text> <View style={{width: 50, height: 50, backgroundColor: 'blue'}} accessibilityLabel="A blue box" accessibilityHint="A hint for the blue box." + accessibilityLevel={2} /> <Text>The following has accessible and accessibilityLabel:</Text> <View style={{width: 50, height: 50, backgroundColor: 'red'}} accessible={true} accessibilityLabel="A hint for the red box." + accessibilityLevel={3} /> - <Text> + <Text accessibilityLevel={4}> The following has accessibilitySetSize, accessibilityPosInSet and accessibilityLabel: </Text> @@ -43,7 +47,8 @@ class AccessibilityBaseExample extends React.Component { aria-label="Aria label takes precedence" accessibilitySetSize={5} accessibilityPosInSet={2} - accessibilityLevel={4} + aria-level={9} //aria-level taked precedence over accessibilityLevel + accessibilityLevel={5} /> </View> ); From bb51a80a5f5ce2f947a4b4d3650980a14d1c2003 Mon Sep 17 00:00:00 2001 From: Vineeth K <kvineeth@microsoft.com> Date: Tue, 22 Apr 2025 14:40:32 +0530 Subject: [PATCH 4/6] Update SnapShots --- .../Accessibility/AccessibilityExampleWindows.tsx | 2 +- .../test/__snapshots__/snapshotPages.test.js.snap | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx b/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx index f601da1c72d..9a7f8acf3cb 100644 --- a/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx +++ b/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx @@ -47,7 +47,7 @@ class AccessibilityBaseExample extends React.Component { aria-label="Aria label takes precedence" accessibilitySetSize={5} accessibilityPosInSet={2} - aria-level={9} //aria-level taked precedence over accessibilityLevel + aria-level={9} //aria-level takes precedence over accessibilityLevel accessibilityLevel={5} /> </View> 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 9fa3e1e0bae..fe19594f59f 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 @@ -4,12 +4,15 @@ exports[`Component Control 1`] = `<View />`; exports[`snapshotAllPages Accessibility Windows 1`] = ` <View> - <Text> + <Text + accessibilityLevel={1} + > The following has accessibilityLabel and accessibilityHint: </Text> <View accessibilityHint="A hint for the blue box." accessibilityLabel="A blue box" + accessibilityLevel={2} style={ { "backgroundColor": "blue", @@ -23,6 +26,7 @@ exports[`snapshotAllPages Accessibility Windows 1`] = ` </Text> <View accessibilityLabel="A hint for the red box." + accessibilityLevel={3} accessible={true} style={ { @@ -32,17 +36,20 @@ exports[`snapshotAllPages Accessibility Windows 1`] = ` } } /> - <Text> + <Text + accessibilityLevel={4} + > The following has accessibilitySetSize, accessibilityPosInSet and accessibilityLabel: </Text> <View accessibilityLabel="This label should not be used" - accessibilityLevel={4} + accessibilityLevel={5} accessibilityPosInSet={2} accessibilityRole="listitem" accessibilitySetSize={5} accessible={true} aria-label="Aria label takes precedence" + aria-level={9} style={ { "backgroundColor": "red", From 9f34321fb6cd953fe4bdccd328e0e70383945134 Mon Sep 17 00:00:00 2001 From: Vineeth K <kvineeth@microsoft.com> Date: Wed, 23 Apr 2025 23:11:44 +0530 Subject: [PATCH 5/6] Added Test Cases for the accessibilty Label,Level etc --- .../AccessibilityExampleWindows.tsx | 14 +++++----- .../test/AccessibilityTest.test.ts | 27 +++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx b/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx index 9a7f8acf3cb..7ef5860479c 100644 --- a/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx +++ b/packages/@react-native-windows/tester/src/js/examples-win/Accessibility/AccessibilityExampleWindows.tsx @@ -19,28 +19,30 @@ class AccessibilityBaseExample extends React.Component { public render() { return ( <View> - <Text accessibilityLevel={1}> - The following has accessibilityLabel and accessibilityHint: - </Text> + <Text>The following has accessibilityLabel and accessibilityHint:</Text> <View style={{width: 50, height: 50, backgroundColor: 'blue'}} + accessible={true} accessibilityLabel="A blue box" accessibilityHint="A hint for the blue box." - accessibilityLevel={2} + accessibilityLevel={1} + testID="accessibility-base-view-1" /> <Text>The following has accessible and accessibilityLabel:</Text> <View style={{width: 50, height: 50, backgroundColor: 'red'}} accessible={true} accessibilityLabel="A hint for the red box." - accessibilityLevel={3} + accessibilityLevel={2} + testID="accessibility-base-view-2" /> - <Text accessibilityLevel={4}> + <Text> The following has accessibilitySetSize, accessibilityPosInSet and accessibilityLabel: </Text> <View style={{width: 50, height: 50, backgroundColor: 'red'}} + testID="accessibility-base-view-3" accessible={true} accessibilityRole="listitem" accessibilityLabel="This label should not be used" diff --git a/packages/e2e-test-app-fabric/test/AccessibilityTest.test.ts b/packages/e2e-test-app-fabric/test/AccessibilityTest.test.ts index 68e4df380c1..d27af11844f 100644 --- a/packages/e2e-test-app-fabric/test/AccessibilityTest.test.ts +++ b/packages/e2e-test-app-fabric/test/AccessibilityTest.test.ts @@ -77,4 +77,31 @@ describe('Accessibility Tests', () => { const dump = await dumpVisualTree('accessibilityValue-text'); expect(dump).toMatchSnapshot(); }); + test('Accessibility data for Label,Level and Hint', async () => { + await searchBox('Lab'); + const componentsTab = await app.findElementByTestID( + 'accessibility-base-view-1', + ); + await componentsTab.waitForDisplayed({timeout: 5000}); + const dump = await dumpVisualTree('accessibility-base-view-1'); + expect(dump).toMatchSnapshot(); + }); + test('Accessibility data for Label and Level', async () => { + await searchBox('Lab'); + const componentsTab = await app.findElementByTestID( + 'accessibility-base-view-2', + ); + await componentsTab.waitForDisplayed({timeout: 5000}); + const dump = await dumpVisualTree('accessibility-base-view-2'); + expect(dump).toMatchSnapshot(); + }); + test('Accessibility data for Role, Setsize etc.', async () => { + await searchBox('Lab'); + const componentsTab = await app.findElementByTestID( + 'accessibility-base-view-3', + ); + await componentsTab.waitForDisplayed({timeout: 5000}); + const dump = await dumpVisualTree('accessibility-base-view-3'); + expect(dump).toMatchSnapshot(); + }); }); From ef1e64bd6556695e9f02616f4d72f9a8f083c01f Mon Sep 17 00:00:00 2001 From: Vineeth K <kvineeth@microsoft.com> Date: Thu, 24 Apr 2025 00:14:16 +0530 Subject: [PATCH 6/6] Updating snapshots --- .../AccessibilityTest.test.ts.snap | 90 +++++++++++++++++++ .../__snapshots__/snapshotPages.test.js.snap | 16 ++-- 2 files changed, 98 insertions(+), 8 deletions(-) diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/AccessibilityTest.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/AccessibilityTest.test.ts.snap index cf7f392168f..1662d978eda 100644 --- a/packages/e2e-test-app-fabric/test/__snapshots__/AccessibilityTest.test.ts.snap +++ b/packages/e2e-test-app-fabric/test/__snapshots__/AccessibilityTest.test.ts.snap @@ -1,5 +1,95 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Accessibility Tests Accessibility data for Label and Level 1`] = ` +{ + "Automation Tree": { + "AutomationId": "accessibility-base-view-2", + "ControlType": 50026, + "Level": 2, + "LocalizedControlType": "group", + "Name": "A hint for the red box.", + }, + "Component Tree": { + "Type": "Microsoft.ReactNative.Composition.ViewComponentView", + "_Props": { + "AccessibilityLabel": "A hint for the red box.", + "TestId": "accessibility-base-view-2", + }, + }, + "Visual Tree": { + "Brush": { + "Brush Type": "ColorBrush", + "Color": "rgba(255, 0, 0, 255)", + }, + "Comment": "accessibility-base-view-2", + "Offset": "0, 0, 0", + "Size": "50, 50", + "Visual Type": "SpriteVisual", + }, +} +`; + +exports[`Accessibility Tests Accessibility data for Label,Level and Hint 1`] = ` +{ + "Automation Tree": { + "AutomationId": "accessibility-base-view-1", + "ControlType": 50026, + "HelpText": "A hint for the blue box.", + "Level": 1, + "LocalizedControlType": "group", + "Name": "A blue box", + }, + "Component Tree": { + "Type": "Microsoft.ReactNative.Composition.ViewComponentView", + "_Props": { + "AccessibilityLabel": "A blue box", + "TestId": "accessibility-base-view-1", + }, + }, + "Visual Tree": { + "Brush": { + "Brush Type": "ColorBrush", + "Color": "rgba(0, 0, 255, 255)", + }, + "Comment": "accessibility-base-view-1", + "Offset": "0, 0, 0", + "Size": "50, 50", + "Visual Type": "SpriteVisual", + }, +} +`; + +exports[`Accessibility Tests Accessibility data for Role, Setsize etc. 1`] = ` +{ + "Automation Tree": { + "AutomationId": "accessibility-base-view-3", + "ControlType": 50007, + "Level": 9, + "LocalizedControlType": "list item", + "Name": "Aria label takes precedence", + "PositionInSet": 2, + "SizeofSet": 5, + }, + "Component Tree": { + "Type": "Microsoft.ReactNative.Composition.ViewComponentView", + "_Props": { + "AccessibilityLabel": "Aria label takes precedence", + "TestId": "accessibility-base-view-3", + }, + }, + "Visual Tree": { + "Brush": { + "Brush Type": "ColorBrush", + "Color": "rgba(255, 0, 0, 255)", + }, + "Comment": "accessibility-base-view-3", + "Offset": "0, 0, 0", + "Size": "50, 50", + "Visual Type": "SpriteVisual", + }, +} +`; + exports[`Accessibility Tests Components can store range data by setting the min, max, and now of accessibilityValue 1`] = ` { "Automation Tree": { 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 3e8567b2cf5..fdbd40728a8 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 @@ -4,15 +4,14 @@ exports[`Component Control 1`] = `<View />`; exports[`snapshotAllPages Accessibility Windows 1`] = ` <View> - <Text - accessibilityLevel={1} - > + <Text> The following has accessibilityLabel and accessibilityHint: </Text> <View accessibilityHint="A hint for the blue box." accessibilityLabel="A blue box" - accessibilityLevel={2} + accessibilityLevel={1} + accessible={true} style={ { "backgroundColor": "blue", @@ -20,13 +19,14 @@ exports[`snapshotAllPages Accessibility Windows 1`] = ` "width": 50, } } + testID="accessibility-base-view-1" /> <Text> The following has accessible and accessibilityLabel: </Text> <View accessibilityLabel="A hint for the red box." - accessibilityLevel={3} + accessibilityLevel={2} accessible={true} style={ { @@ -35,10 +35,9 @@ exports[`snapshotAllPages Accessibility Windows 1`] = ` "width": 50, } } + testID="accessibility-base-view-2" /> - <Text - accessibilityLevel={4} - > + <Text> The following has accessibilitySetSize, accessibilityPosInSet and accessibilityLabel: </Text> <View @@ -57,6 +56,7 @@ exports[`snapshotAllPages Accessibility Windows 1`] = ` "width": 50, } } + testID="accessibility-base-view-3" /> </View> `;