Skip to content

Commit 1db1d89

Browse files
lazergmeta-codesync[bot]
authored andcommitted
Announce collapsed state for expanded: false on iOS (#58748)
Summary: On the new architecture, VoiceOver reads nothing for `aria-expanded={false}` / `accessibilityState={{expanded: false}}`, so a collapsed accordion header sounds like a plain button. `RCTViewComponentView` only adds "expanded" to the accessibility value when the state is true and has no branch for false. The old `RCTView` announces "collapsed" there, and so does TalkBack. This adds the missing branch, reusing the same localized "collapsed" string `RCTView.m` already has. An unset `expanded` still adds nothing. Fixes #58746 ## Changelog: [IOS] [FIXED] - VoiceOver announces "collapsed" for `aria-expanded={false}` on the new architecture Pull Request resolved: #58748 Test Plan: Added XCTest cases to `RCTViewComponentViewTests.mm` for `expanded` true, false and unset. Ran the repo's clang-format on both files. I couldn't run the iOS tests locally (no Xcode on this machine), so they rely on CI. Reviewed By: cipolleschi Differential Revision: D122875216 Pulled By: zeyap fbshipit-source-id: ecca5825fcb6443cbaca57a3998be1efac893c35
1 parent 425a284 commit 1db1d89

2 files changed

Lines changed: 42 additions & 3 deletions

File tree

‎packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,9 +1614,15 @@ - (NSString *)accessibilityValue
16141614
addObject:RCTLocalizedString(
16151615
"mixed", "a checkbox, radio button, or other widget which is both checked and unchecked")];
16161616
}
1617-
if (accessibilityState.expanded.value_or(false)) {
1618-
[valueComponents
1619-
addObject:RCTLocalizedString("expanded", "a menu, dialog, accordian panel, or other widget which is expanded")];
1617+
if (const auto expanded = accessibilityState.expanded; expanded.has_value()) {
1618+
if (expanded.value()) {
1619+
[valueComponents addObject:RCTLocalizedString(
1620+
"expanded", "a menu, dialog, accordian panel, or other widget which is expanded")];
1621+
} else {
1622+
[valueComponents
1623+
addObject:RCTLocalizedString(
1624+
"collapsed", "a menu, dialog, accordian panel, or other widget which is collapsed")];
1625+
}
16201626
}
16211627

16221628
if (accessibilityState.busy) {

‎packages/react-native/React/Tests/Mounting/RCTViewComponentViewTests.mm‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,39 @@ - (void)testViewWithoutRoleIsNotKeyboardFocusable
238238
XCTAssertFalse(view.canBecomeFocused);
239239
}
240240

241+
#pragma mark - accessibilityValue for expanded state
242+
243+
static RCTViewComponentView *makeViewWithExpandedState(std::optional<bool> expanded)
244+
{
245+
RCTViewComponentView *view = [RCTViewComponentView new];
246+
auto props = std::make_shared<ViewProps>();
247+
props->accessible = true;
248+
props->accessibilityRole = "button";
249+
AccessibilityState accessibilityState;
250+
accessibilityState.expanded = expanded;
251+
props->accessibilityState = accessibilityState;
252+
[view updateProps:props oldProps:ViewShadowNode::defaultSharedProps()];
253+
return view;
254+
}
255+
256+
- (void)testAccessibilityValueAnnouncesExpanded
257+
{
258+
RCTViewComponentView *view = makeViewWithExpandedState(true);
259+
XCTAssertEqualObjects(view.accessibilityValue, @"expanded");
260+
}
261+
262+
- (void)testAccessibilityValueAnnouncesCollapsed
263+
{
264+
RCTViewComponentView *view = makeViewWithExpandedState(false);
265+
XCTAssertEqualObjects(view.accessibilityValue, @"collapsed");
266+
}
267+
268+
- (void)testAccessibilityValueIsNilWithoutExpandedState
269+
{
270+
RCTViewComponentView *view = makeViewWithExpandedState(std::nullopt);
271+
XCTAssertNil(view.accessibilityValue);
272+
}
273+
241274
#pragma mark - outline style on square corners (#57841)
242275

243276
static RCTViewComponentView *makeViewWithOutlineStyle(OutlineStyle outlineStyle)

0 commit comments

Comments
 (0)