Repository navigation
Add AccessibilityInfo.getPreferences() and a longer iOS timeout while Switch Control runs - #59318
Open
camilopires wants to merge 9 commits into
Open
camilopires wants to merge 9 commits into
camilopires wants to merge 9 commits into
Conversation
|
Warning JavaScript API change detected This PR commits an update to
This change was flagged as: |
This was referenced Oct 21, 2026
Contributor
|
there are several conflicts in this PR. Can you rebase on top of main please? |
camilopires
force-pushed
the
a11y/accessibility-preferences
branch
from
October 24, 2026 00:08
0d680aa to
55e57de
Compare
Contributor
Author
|
Rebased on main and updated the C++ API snapshots. |
Visual regressionNo visual differences at |
Add a preferences snapshot for accessibility settings that change how an app should draw its own controls, named by what the user needs rather than by each platform's setting: prefersSwitchLabels (iOS On/Off Labels) and prefersNonColorIndicators (iOS Differentiate Without Color). Android has neither setting, so it resolves both to false without a native call. preferencesChanged carries the whole new snapshot. AccessibilityPreferences is read-only (Readonly<> in Flow, readonly fields in TypeScript), and its docs say fields will be added over time, so apps should read the object rather than build one. The native spec keeps the fields inline, because a named type would add C++ structs to the API snapshots. Passing resolve as onSuccess lets Flow's exact object types check that the spec matches the public type. When NativeAccessibilityManagerIOS.getCurrentPreferences is missing (new JS on an older binary), getPreferences() rejects with an error naming the method, like isShowBordersEnabled, rather than resolving a snapshot that looks like "every setting off". Adds Jest tests, type tests, a typed jest-preset mock and the API snapshot. The iOS native side follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a row to Accessibility's display options that shows getPreferences() and the last preferencesChanged event. A rejected read shows "Unavailable: <message>" instead of staying on "loading", and a starting read that resolves after an event doesn't overwrite the newer value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
camilopires
force-pushed
the
a11y/accessibility-preferences
branch
from
October 24, 2026 13:44
55e57de to
9559cb6
Compare
RCTAccessibilityManager builds the getPreferences() snapshot from On/Off Labels (UIAccessibilityIsOnOffSwitchLabelsEnabled) and Differentiate Without Color (UIAccessibilityShouldDifferentiateWithoutColor), observes both settings' notifications, and sends preferencesChanged with the whole snapshot only when it changes. One normaliser builds every snapshot from the known keys, boxing each value from a BOOL, so the keys always match AccessibilityPreferences and the values are always CFBoolean. Tests can replace how one module instance reads the settings with RCTAccessibilityManagerSetPreferencesReader, in +Internal.h. Setting a reader re-reads the snapshot without sending an event, and nil reads the system again. A reader that returns nil falls back to the system with a warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add RCTAccessibilityManagerTests to RNTesterUnitTests. The tests install per-instance readers backed by mutable state, change the state, and post the real UIKit notifications, so they cover the observers, the deduplication and getCurrentPreferences rather than a helper. They check the system values at startup, that the setter sends no event, that each setting's change sends exactly one event, that two instances keep their own readers, that reader values reach the event as CFBoolean, and that a nil reader returns the system values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The iOS NativeAccessibilityManager spec gains getCurrentPreferences, so the generated spec method appears in the Apple snapshots. Generated with Doxygen 1.16.1 and Python 3.12, as in validate-cxx-api-snapshots.yml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The iOS module sent preferencesChanged on the shared device event emitter, where another native module could send the same name. The public JS event stays preferencesChanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test now starts from the opposite of the system values, so a stale cache can't pass, and asserts the module logs exactly one warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t test The test asserted on the Android mock, so it couldn't fail on iOS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Updated after a review pass:
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
Following the discussion in react-native-community/discussions-and-proposals#1026, this adds one snapshot for accessibility settings that change how an app should draw its own controls. It replaces the one-flag-per-setting PRs #59178, #59179 and #59180.
React Native apps mostly draw their own switches and buttons with
PressableandView, so the system can't adapt them to these settings as it doesUISwitchand system buttons. The app has to read the setting itself.prefersCrossFadeTransitionsalready does this. A platform with no such setting reportsfalse, so apps don't need platform checks. A future setting becomes a new field, not a new method and event.Readonly<>in Flow,readonlyfields in TypeScript), and its docs say fields will be added over time, so apps should read the object they're given rather than build one.preferencesChangedcarries the whole new snapshot, and fires only when a field changes. Natively, iOS sends it asaccessibilityPreferencesChanged, so it can't clash with apreferencesChangeddevice event from another module on the sharedRCTDeviceEventEmitter; the JS event name stayspreferencesChanged.getPreferences()resolves both fields tofalsewithout a native call, andpreferencesChangeddoesn't subscribe, like the other iOS-only events.NativeAccessibilityManagerIOS.getCurrentPreferencesis missing (new JS on an older binary),getPreferences()rejects with an error naming the method, asisShowBordersEnableddoes, rather than resolving a snapshot that looks like "every setting off".RCTAccessibilityManagerbuilds every snapshot from the two known keys, boxing each value from aBOOL, so the keys always matchAccessibilityPreferencesand the values are alwaysCFBoolean. The native spec keeps the fields inline, because a named type would add C++ structs to the API snapshots; passingresolveasonSuccesslets Flow's exact object types check that the spec matches the public type.Split: an earlier version of this PR also made
getRecommendedTimeoutMillis()return a longer timeout on iOS while Switch Control is running. That changes an existing API, so it's now a separate PR, #59453, and this one leavesgetRecommendedTimeoutMillis()as it is onmain.Scope: Show Borders (#59177) has already landed as
isShowBordersEnabled(), and the Android contrast level (#59181) is in review, so neither is folded into the snapshot here. #59176 (Bold Text on Android) and #59453 (the Switch Control timeout) are separate, and whichever lands second needs a small rebase:AccessibilityInfo.js, its__tests__, the d.ts, RNTester's Accessibility example andReactNativeApi.d.ts.__tests__/AccessibilityInfo-test.js,RCTAccessibilityManager+Internal.h,RCTAccessibilityManager.mm,__typetests__/index.tsxandAccessibilityExample.js.project.pbxprojand the C++ API snapshots merge cleanly.Changelog:
[IOS] [ADDED] -
AccessibilityInfo.getPreferences()andpreferencesChanged(On/Off Labels and Differentiate Without Color)Test Plan:
yarn jest packages/react-native/Libraries/Components/AccessibilityInfo: 49 tests pass. They covergetPreferenceson both platforms (including an older iOS binary without the native method), that iOSgetRecommendedTimeoutMillisstill resolvesoriginalTimeout(the iOS native module has no such method), and the event wiring: the nativeaccessibilityPreferencesChangedevent reachespreferencesChangedlisteners, and a barepreferencesChangeddevice event doesn't.yarn flow-check: 0 errorsyarn lint: passesyarn build-types --validate: API snapshot up to dateyarn test-generated-typescriptandyarn test-typescript-legacy: passyarn format-check-cpp, and Prettier on the changed JS and TS: passRCTAccessibilityManagerTestsin RNTesterUnitTests: 9 tests pass on an iPhone 18 Pro simulator (iOS 27.0, Xcode 27.1). Each module instance gets its own settings reader (RCTAccessibilityManagerSetPreferencesReader, in+Internal.h), backed by state the test changes before posting the real UIKit notification. They check the system values at startup (exactly the two keys,CFBooleanvalues), that installing a reader sends no event, that each setting's change sends exactly one event even when the notification is posted twice, that a notification without a change sends nothing, that two instances keep their own readers, that reader values such as@1reach the event asCFBoolean, that a reader returningnillogs exactly one warning and gives the system values (starting from the opposite values, so a stale cache can't pass), and that resetting the reader tonilgives the system values. The event tests assert the native nameaccessibilityPreferencesChanged. Removing the On/Off Labels observer makes 3 of them fail; sending the old event name and dropping the warning makes 4 fail.On an iPhone 18 Pro simulator running iOS 27.0, on the current branch, I switched each setting in the Settings app with a throwaway XCUITest (not part of this PR). Then I read RNTester's Accessibility → "display options" rows. RNTester stayed running, so the "Last preferencesChanged" row could only update through the event.
preferencesChangedgetPreferences()afterwardsAndroid (from an earlier run; the Android path, which resolves both fields to
falsewithout a native call, hasn't changed since): RNTester on an API 36 emulator shows "Preferences: switch labels off, non-color indicators off." and "Last preferencesChanged: none.", with no errors.Not verified: a SwiftPM build, because this PR doesn't change linking or
Package.swift.