Skip to content

Implement accessibilityLevel for Fabric #14593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 24, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Implement accessibilityLevel for Fabric",
"packageName": "react-native-windows",
"email": "kvineeth@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -22,28 +22,35 @@ class AccessibilityBaseExample extends React.Component {
<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={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={2}
testID="accessibility-base-view-2"
/>
<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"
aria-label="Aria label takes precedence"
accessibilitySetSize={5}
accessibilityPosInSet={2}
accessibilityLevel={4}
aria-level={9} //aria-level takes precedence over accessibilityLevel
accessibilityLevel={5}
/>
</View>
);
27 changes: 27 additions & 0 deletions packages/e2e-test-app-fabric/test/AccessibilityTest.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
Original file line number Diff line number Diff line change
@@ -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": {
Original file line number Diff line number Diff line change
@@ -10,19 +10,23 @@ exports[`snapshotAllPages Accessibility Windows 1`] = `
<View
accessibilityHint="A hint for the blue box."
accessibilityLabel="A blue box"
accessibilityLevel={1}
accessible={true}
style={
{
"backgroundColor": "blue",
"height": 50,
"width": 50,
}
}
testID="accessibility-base-view-1"
/>
<Text>
The following has accessible and accessibilityLabel:
</Text>
<View
accessibilityLabel="A hint for the red box."
accessibilityLevel={2}
accessible={true}
style={
{
@@ -31,25 +35,28 @@ exports[`snapshotAllPages Accessibility Windows 1`] = `
"width": 50,
}
}
testID="accessibility-base-view-2"
/>
<Text>
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",
"height": 50,
"width": 50,
}
}
testID="accessibility-base-view-3"
/>
</View>
`;
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -559,6 +559,11 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPropertyValue(PROPERT
: SysAllocString(L"");
break;
}
case UIA_LevelPropertyId: {
pRetVal->vt = VT_I4;
pRetVal->lVal = props->accessibilityLevel;
break;
}
}

return hr;
Original file line number Diff line number Diff line change
@@ -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 =
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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{};