From 91007ceedf07b44497e8b71e5e86a0dc55c4d1cb Mon Sep 17 00:00:00 2001 From: cgoldsby Date: Fri, 10 Jul 2026 16:03:22 -0400 Subject: [PATCH] fix(accessibility): Derive accessibilityTraits from role and state (#57515) --- .../View/RCTViewComponentView.mm | 12 ---- .../components/view/AccessibilityProps.cpp | 62 ++++++++++++------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 5571b5350bff..a658b111ea1d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -472,18 +472,6 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & RCTUIAccessibilityTraitsFromAccessibilityTraits(newViewProps.accessibilityTraits); } - // `accessibilityState` - if (oldViewProps.accessibilityState != newViewProps.accessibilityState) { - self.accessibilityTraits &= ~(UIAccessibilityTraitNotEnabled | UIAccessibilityTraitSelected); - const auto accessibilityState = newViewProps.accessibilityState.value_or(AccessibilityState{}); - if (accessibilityState.selected) { - self.accessibilityTraits |= UIAccessibilityTraitSelected; - } - if (accessibilityState.disabled) { - self.accessibilityTraits |= UIAccessibilityTraitNotEnabled; - } - } - // `accessibilityIgnoresInvertColors` if (oldViewProps.accessibilityIgnoresInvertColors != newViewProps.accessibilityIgnoresInvertColors) { self.accessibilityIgnoresInvertColors = newViewProps.accessibilityIgnoresInvertColors; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp index 474c0ad8f2ae..8bff3b8cc696 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp @@ -14,6 +14,31 @@ namespace facebook::react { +// Derive accessibilityTraits from the resolved role and accessibilityState +// members. This makes accessibilityTraits the single source of truth. +// See: github.com/facebook/react-native/issues/57515 +static AccessibilityTraits deriveAccessibilityTraits( + Role role, + const std::string& accessibilityRole, + const std::optional& accessibilityState) { + AccessibilityTraits traits = AccessibilityTraits::None; + if (role != Role::None) { + fromString(toString(role), traits); + } else if (!accessibilityRole.empty()) { + fromString(accessibilityRole, traits); + } + + const auto state = accessibilityState.value_or(AccessibilityState{}); + if (state.selected) { + traits = traits | AccessibilityTraits::Selected; + } + if (state.disabled) { + traits = traits | AccessibilityTraits::NotEnabled; + } + + return traits; +} + AccessibilityProps::AccessibilityProps( const PropsParserContext& context, const AccessibilityProps& sourceProps, @@ -146,19 +171,9 @@ AccessibilityProps::AccessibilityProps( ImportantForAccessibility::Auto)), testId( convertRawProp(context, rawProps, "testID", sourceProps.testId, "")) { - // It is a (severe!) perf deoptimization to request props out-of-order. - // Thus, since we need to request the same prop twice here - // (accessibilityRole) we "must" do them subsequently here to prevent - // a regression. It is reasonable to ask if the `at` function can be improved; - // it probably can, but this is a fairly rare edge-case that (1) is easy-ish - // to work around here, and (2) would require very careful work to address - // this case and not regress the more common cases. auto* accessibilityRoleValue = rawProps.at("accessibilityRole"); auto* roleValue = rawProps.at("role"); - auto* precedentRoleValue = - roleValue != nullptr ? roleValue : accessibilityRoleValue; - if (accessibilityRoleValue == nullptr || !accessibilityRoleValue->hasValue()) { accessibilityRole = sourceProps.accessibilityRole; @@ -172,11 +187,8 @@ AccessibilityProps::AccessibilityProps( fromRawValue(context, *roleValue, role); } - if (precedentRoleValue == nullptr || !precedentRoleValue->hasValue()) { - accessibilityTraits = sourceProps.accessibilityTraits; - } else { - fromRawValue(context, *precedentRoleValue, accessibilityTraits); - } + accessibilityTraits = + deriveAccessibilityTraits(role, accessibilityRole, accessibilityState); } void AccessibilityProps::setProp( @@ -188,7 +200,12 @@ void AccessibilityProps::setProp( switch (hash) { RAW_SET_PROP_SWITCH_CASE_BASIC(accessible); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityState); + case CONSTEXPR_RAW_PROPS_KEY_HASH("accessibilityState"): { + fromRawValue(context, value, accessibilityState, defaults.accessibilityState); + accessibilityTraits = + deriveAccessibilityTraits(role, accessibilityRole, accessibilityState); + return; + } RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLabel); RAW_SET_PROP_SWITCH_CASE( accessibilityOrder, "experimental_accessibilityOrder"); @@ -209,18 +226,21 @@ void AccessibilityProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityEscape); RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityAction); RAW_SET_PROP_SWITCH_CASE_BASIC(importantForAccessibility); - RAW_SET_PROP_SWITCH_CASE_BASIC(role); + case CONSTEXPR_RAW_PROPS_KEY_HASH("role"): { + fromRawValue(context, value, role, defaults.role); + accessibilityTraits = + deriveAccessibilityTraits(role, accessibilityRole, accessibilityState); + return; + } RAW_SET_PROP_SWITCH_CASE(testId, "testID"); case CONSTEXPR_RAW_PROPS_KEY_HASH("accessibilityRole"): { - AccessibilityTraits traits = AccessibilityTraits::None; std::string roleString; if (value.hasValue()) { - fromRawValue(context, value, traits); fromRawValue(context, value, roleString); } - - accessibilityTraits = traits; accessibilityRole = roleString; + accessibilityTraits = + deriveAccessibilityTraits(role, accessibilityRole, accessibilityState); return; } }