Files
react-native/scripts
Calvin Liu af4d8eb08a Preserve unset accessibilityState.selected in C++ props (#58599)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58599

`AccessibilityState::selected` was a plain `bool` defaulting to `false`, so the
native side could not tell a component that is selectable but currently
unselected (`accessibilityState={{selected: false}}`) from one that is not
selectable at all (`accessibilityState={{}}`). Both arrived as `false`. The JS
type is already `selected?: ?boolean`, so this is the bridge discarding a value
the public API accepts.

Make `selected` a `std::optional<bool>` defaulting to `std::nullopt`.

  JS accessibilityState      native selected  (before -> after)
  {}                         false -> undefined
  {selected: false}          false -> false
  {selected: true}           true  -> true

This aligns the representation with ARIA, which `accessibilityState` mirrors:
the bool / tri-state split now tracks which ARIA attributes admit an undefined
value.

  field      ARIA value type  admits undefined  representation
  disabled   boolean          no                bool
  busy       boolean          no                bool
  selected   boolean          yes               std::optional<bool>  (changed)
  expanded   boolean          yes               std::optional<bool>
  checked    tristate         yes               CheckedState (None = unset)

That is also why `disabled` and `busy` stay plain `bool`: ARIA gives them no
undefined value, so there is no unset state to preserve. `expanded` was made
optional for this same reason in
https://github.com/facebook/react-native/pull/40881 and `checked` has always
carried a `None`; `selected` was the outlier.

Nor is "unset" merely "absent" for this attribute. `testing-library/dom`
computes it as `boolean | undefined`, documented "false/true if (not)selected,
undefined if not selectable" -- the same shape, with the same meaning, that
this change introduces.

Host platforms need the distinction: on Windows a selectable component must
implement ISelectionItemProvider so UIA can report selection state, and with
the old representation every component carrying an accessibilityState looked
selectable.

iOS and Android rendering is unchanged. Trait derivation coalesces the optional
with `value_or(false)`, and the Android serializer omits the key when the value
is unset, which `BaseViewManager#setViewState` already handles by falling back
to `setSelected(false)`.

Reviewer note: `std::optional<bool>` is contextually convertible to `bool`, so a
bare `if (state.selected)` still compiles but tests engagement rather than
value, silently marking an explicitly unselected component as selected. There is
a regression test for that specific hazard.

Fixes https://github.com/facebook/react-native/issues/46988
Supersedes https://github.com/facebook/react-native/pull/47296, which went stale.

Changelog:
[General][Breaking] - `AccessibilityState::selected` is now `std::optional<bool>` in C++ props, preserving an unset `selected` instead of coercing it to `false`

Reviewed By: javache

Differential Revision: D120049025

fbshipit-source-id: 53ac440976f00daffb3f0599c8841ada2a1452a6
2026-09-18 15:42:35 -07:00
..