Conversation
There was a problem hiding this comment.
Pull request overview
Fixes Android TalkBack interactions with Pressable by adapting the internal Pressable state machine to the different event ordering produced when accessibility services are active.
Changes:
- Added a new native module API (
isAccessibilityEnabled) to detect accessibility state from JS. - Adjusted Android Pressable state machine event ordering when accessibility is enabled.
- Prevented an Android-only state-machine reset on
onTouchesUpwhen accessibility is enabled (to avoid breaking long-press flows under TalkBack).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/react-native-gesture-handler/src/v3/components/Pressable.tsx | Skips the Android onTouchesUp reset path when accessibility is enabled. |
| packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts | Extends the TurboModule spec with isAccessibilityEnabled(): boolean. |
| packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts | Adds an Android accessibility-specific state config and selects it when accessibility is enabled. |
| packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts | Adds a web stub for isAccessibilityEnabled (currently unimplemented). |
| packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt | Implements isAccessibilityEnabled() on Android via AccessibilityManager. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Context.ACCESSIBILITY_SERVICE, | ||
| ) as AccessibilityManager? | ||
|
|
||
| return accessibilityManager?.isEnabled ?: false |
There was a problem hiding this comment.
AccessibilityManager.isEnabled is broader than TalkBack/screen reader and can be true for other accessibility features (which would switch Pressable into the altered event-order path unexpectedly). If the intent is specifically TalkBack, consider using accessibilityManager?.isTouchExplorationEnabled == true (optionally gated by isEnabled) instead.
| return accessibilityManager?.isEnabled ?: false | |
| return accessibilityManager?.let { it.isEnabled && it.isTouchExplorationEnabled } ?: false |
| if ( | ||
| Platform.OS === 'android' && | ||
| !RNGestureHandlerModule.isAccessibilityEnabled() | ||
| ) { |
There was a problem hiding this comment.
This calls the native isAccessibilityEnabled() synchronously on every onTouchesUp. Since this value typically changes rarely, consider reading it once (e.g., on mount) and caching it in a ref/state to avoid repeated sync native calls on a hot interaction path.
| ): StateDefinition[] { | ||
| if (Platform.OS === 'android') { | ||
| if (RNGestureHandlerModule.isAccessibilityEnabled()) { | ||
| console.log('accessible!!!'); |
There was a problem hiding this comment.
Remove the leftover debug console.log('accessible!!!'). Logging here will leak to production apps and can be noisy; the accessibility-specific config selection should be silent (or use the library's existing debug/log mechanism if one exists).
| console.log('accessible!!!'); |
| isAccessibilityEnabled() { | ||
| // TODO |
There was a problem hiding this comment.
isAccessibilityEnabled() is currently a stub with no return value. It should return a boolean (likely false on web) to keep behavior predictable and avoid returning undefined if this method gets used on web in the future.
| isAccessibilityEnabled() { | |
| // TODO | |
| isAccessibilityEnabled(): boolean { | |
| // Accessibility status is not wired on web; default to false. | |
| return false; |
| dropGestureHandler: (handlerTag: Double) => void; | ||
| flushOperations: () => void; | ||
| setReanimatedAvailable: (isAvailable: boolean) => void; | ||
| isAccessibilityEnabled: () => boolean; |
There was a problem hiding this comment.
Adding isAccessibilityEnabled to the TurboModule spec requires providing native implementations on all supported platforms that build this module (notably iOS, where RNGestureHandlerModule conforms to NativeRNGestureHandlerModuleSpec). Without adding the corresponding method on iOS, this change is likely to break native compilation.
| isAccessibilityEnabled: () => boolean; |
Description
When TalkBack is activated, pressable cannot be clicked on android. This PR fixes the issue.
The reason was that Pressable requires a specific order of events. This order of events is hardcoded and found empirically. However, with talback enabled the order of events is different, I added a module function which queries native side whether or not talback is enabled and adjusts expected event order accordingly.
Moreover with talback enabled longPress gets both onPointerUp and onPointerDown before native side receives its events, thus we must not reset the state machine state on onPointerUp.
Test plan
Tested on the following example:
Details