From 9536fcf174df518e76fbd51717311f4a06b5b8b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Fri, 6 Mar 2026 12:59:53 +0100 Subject: [PATCH 01/15] poc --- .../react/RNGestureHandlerModule.kt | 11 +++++++++ .../src/RNGestureHandlerModule.web.ts | 3 +++ .../components/Pressable/stateDefinitions.ts | 24 +++++++++++++++++++ .../src/specs/NativeRNGestureHandlerModule.ts | 1 + .../src/v3/components/Pressable.tsx | 6 ++++- 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt index 7b2ab98640..cb8fc1627b 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt @@ -1,5 +1,7 @@ package com.swmansion.gesturehandler.react +import android.content.Context +import android.view.accessibility.AccessibilityManager import com.facebook.jni.HybridData import com.facebook.proguard.annotations.DoNotStrip import com.facebook.react.bridge.JSApplicationIllegalArgumentException @@ -129,6 +131,15 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) : isReanimatedAvailable = isAvailable } + @ReactMethod + override fun isAccessibilityEnabled(): Boolean { + val accessibilityManager = reactApplicationContext.getSystemService( + Context.ACCESSIBILITY_SERVICE, + ) as AccessibilityManager? + + return accessibilityManager?.isEnabled ?: false + } + @DoNotStrip @Suppress("unused") fun setGestureHandlerState(handlerTag: Int, newState: Int) { diff --git a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts index c30bfd7729..726474e5b8 100644 --- a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts +++ b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts @@ -92,4 +92,7 @@ export default { setReanimatedAvailable(_isAvailable: boolean) { // No-op on web }, + isAccessibilityEnabled() { + // TODO + }, }; diff --git a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts index 55279211cd..315ffe7068 100644 --- a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts +++ b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts @@ -1,6 +1,7 @@ import { Platform } from 'react-native'; import { PressableEvent } from './PressableProps'; import { StateDefinition } from './StateMachine'; +import RNGestureHandlerModule from '../../RNGestureHandlerModule'; export enum StateMachineEvent { NATIVE_BEGIN = 'nativeBegin', @@ -29,6 +30,25 @@ function getAndroidStatesConfig( ]; } +function getAndroidAccessibilityStatesConfig( + handlePressIn: (event: PressableEvent) => void, + handlePressOut: (event: PressableEvent) => void +) { + return [ + { + eventName: StateMachineEvent.LONG_PRESS_TOUCHES_DOWN, + callback: handlePressIn, + }, + { + eventName: StateMachineEvent.NATIVE_BEGIN, + }, + { + eventName: StateMachineEvent.FINALIZE, + callback: handlePressOut, + }, + ]; +} + function getIosStatesConfig( handlePressIn: (event: PressableEvent) => void, handlePressOut: (event: PressableEvent) => void @@ -112,6 +132,10 @@ export function getStatesConfig( handlePressOut: (event: PressableEvent) => void ): StateDefinition[] { if (Platform.OS === 'android') { + if (RNGestureHandlerModule.isAccessibilityEnabled()) { + console.log('accessible!!!'); + return getAndroidAccessibilityStatesConfig(handlePressIn, handlePressOut); + } return getAndroidStatesConfig(handlePressIn, handlePressOut); } else if (Platform.OS === 'ios') { return getIosStatesConfig(handlePressIn, handlePressOut); diff --git a/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts b/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts index c40798482b..3a19c43be5 100644 --- a/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts +++ b/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts @@ -25,6 +25,7 @@ export interface Spec extends TurboModule { dropGestureHandler: (handlerTag: Double) => void; flushOperations: () => void; setReanimatedAvailable: (isAvailable: boolean) => void; + isAccessibilityEnabled: () => boolean; } export default TurboModuleRegistry.getEnforcing('RNGestureHandlerModule'); diff --git a/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx b/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx index 50d3ce15e5..bbfed425c7 100644 --- a/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx +++ b/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx @@ -40,6 +40,7 @@ import { PureNativeButton } from './GestureButtons'; import { PressabilityDebugView } from '../../handlers/PressabilityDebugView'; import { INT32_MAX, isTestEnv } from '../../utils'; +import RNGestureHandlerModule from '../../RNGestureHandlerModule'; const DEFAULT_LONG_PRESS_DURATION = 500; const IS_TEST_ENV = isTestEnv(); @@ -257,7 +258,10 @@ const Pressable = (props: PressableProps) => { ); }, onTouchesUp: () => { - if (Platform.OS === 'android') { + if ( + Platform.OS === 'android' && + !RNGestureHandlerModule.isAccessibilityEnabled() + ) { // Prevents potential soft-locks stateMachine.reset(); handleFinalize(); From 35814ae6e4042f5afe9a5341305adccc7c48e4c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 9 Mar 2026 16:23:01 +0100 Subject: [PATCH 02/15] module function on ios --- .../apple/RNGestureHandlerModule.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm index 8be6f2a08d..870081e1e0 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm @@ -248,6 +248,11 @@ - (void)setGestureStateSync:(int)state forHandler:(int)handlerTag } } +- (NSNumber *)isAccessibilityEnabled +{ + return @(UIAccessibilityIsVoiceOverRunning()); +} + #pragma mark-- Batch handling - (void)addOperationBlock:(GestureHandlerOperation)operation From bca566a8b6103965da63d7242a6564aa8f46297f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 9 Mar 2026 16:24:14 +0100 Subject: [PATCH 03/15] remove leftover console log --- .../src/components/Pressable/stateDefinitions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts index 315ffe7068..21ba62bb42 100644 --- a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts +++ b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts @@ -133,7 +133,6 @@ export function getStatesConfig( ): StateDefinition[] { if (Platform.OS === 'android') { if (RNGestureHandlerModule.isAccessibilityEnabled()) { - console.log('accessible!!!'); return getAndroidAccessibilityStatesConfig(handlePressIn, handlePressOut); } return getAndroidStatesConfig(handlePressIn, handlePressOut); From ddc78744a90cd9f729a9ea28a7439f8019f146ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 9 Mar 2026 16:25:33 +0100 Subject: [PATCH 04/15] web and windows --- .../src/RNGestureHandlerModule.web.ts | 2 +- .../src/RNGestureHandlerModule.windows.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts index 726474e5b8..5a5a4a91de 100644 --- a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts +++ b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts @@ -93,6 +93,6 @@ export default { // No-op on web }, isAccessibilityEnabled() { - // TODO + return false; }, }; diff --git a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts index cf71b49397..eaa50d5694 100644 --- a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts +++ b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts @@ -56,4 +56,7 @@ export default { flushOperations() { // NO-OP }, + isAccessibilityEnabled() { + // NO-OP + }, }; From 61bee1fd00508a8c150a4357d5e42268f71f65a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 9 Mar 2026 16:38:24 +0100 Subject: [PATCH 05/15] only screen reader android --- .../swmansion/gesturehandler/react/RNGestureHandlerModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt index cb8fc1627b..f4a511c6f2 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt @@ -137,7 +137,7 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) : Context.ACCESSIBILITY_SERVICE, ) as AccessibilityManager? - return accessibilityManager?.isEnabled ?: false + return accessibilityManager?.isTouchExplorationEnabled ?: false } @DoNotStrip From feb74992831fe462bcc49eda5c71dbabfe4855b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 9 Mar 2026 16:50:54 +0100 Subject: [PATCH 06/15] rename --- .../swmansion/gesturehandler/react/RNGestureHandlerModule.kt | 2 +- .../apple/RNGestureHandlerModule.mm | 2 +- .../src/RNGestureHandlerModule.web.ts | 2 +- .../src/RNGestureHandlerModule.windows.ts | 2 +- .../src/components/Pressable/stateDefinitions.ts | 2 +- .../src/specs/NativeRNGestureHandlerModule.ts | 2 +- .../src/v3/components/Pressable.tsx | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt index f4a511c6f2..f9b8de3ed7 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt @@ -132,7 +132,7 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) : } @ReactMethod - override fun isAccessibilityEnabled(): Boolean { + override fun isScreenReaderEnabled(): Boolean { val accessibilityManager = reactApplicationContext.getSystemService( Context.ACCESSIBILITY_SERVICE, ) as AccessibilityManager? diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm index 870081e1e0..0ec7ed8162 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm @@ -248,7 +248,7 @@ - (void)setGestureStateSync:(int)state forHandler:(int)handlerTag } } -- (NSNumber *)isAccessibilityEnabled +- (NSNumber *)isScreenReaderEnabled { return @(UIAccessibilityIsVoiceOverRunning()); } diff --git a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts index 5a5a4a91de..698b8ade22 100644 --- a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts +++ b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts @@ -92,7 +92,7 @@ export default { setReanimatedAvailable(_isAvailable: boolean) { // No-op on web }, - isAccessibilityEnabled() { + isScreenReaderEnabled() { return false; }, }; diff --git a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts index eaa50d5694..87d5dd33fc 100644 --- a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts +++ b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts @@ -56,7 +56,7 @@ export default { flushOperations() { // NO-OP }, - isAccessibilityEnabled() { + isScreenReaderEnabled() { // NO-OP }, }; diff --git a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts index 21ba62bb42..f0b29dd35b 100644 --- a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts +++ b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts @@ -132,7 +132,7 @@ export function getStatesConfig( handlePressOut: (event: PressableEvent) => void ): StateDefinition[] { if (Platform.OS === 'android') { - if (RNGestureHandlerModule.isAccessibilityEnabled()) { + if (RNGestureHandlerModule.isScreenReaderEnabled()) { return getAndroidAccessibilityStatesConfig(handlePressIn, handlePressOut); } return getAndroidStatesConfig(handlePressIn, handlePressOut); diff --git a/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts b/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts index 3a19c43be5..83c22db57f 100644 --- a/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts +++ b/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts @@ -25,7 +25,7 @@ export interface Spec extends TurboModule { dropGestureHandler: (handlerTag: Double) => void; flushOperations: () => void; setReanimatedAvailable: (isAvailable: boolean) => void; - isAccessibilityEnabled: () => boolean; + isScreenReaderEnabled: () => boolean; } export default TurboModuleRegistry.getEnforcing('RNGestureHandlerModule'); diff --git a/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx b/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx index bbfed425c7..604e2cacf3 100644 --- a/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx +++ b/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx @@ -260,7 +260,7 @@ const Pressable = (props: PressableProps) => { onTouchesUp: () => { if ( Platform.OS === 'android' && - !RNGestureHandlerModule.isAccessibilityEnabled() + !RNGestureHandlerModule.isScreenReaderEnabled() ) { // Prevents potential soft-locks stateMachine.reset(); From 03d6451ae746dffbfe6fd0b0216e730ee3048f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 9 Mar 2026 17:04:56 +0100 Subject: [PATCH 07/15] cache --- .../src/components/Pressable/stateDefinitions.ts | 4 ++-- .../react-native-gesture-handler/src/utils.ts | 16 ++++++++++++++++ .../src/v3/components/Pressable.tsx | 8 ++------ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts index f0b29dd35b..fb1d7b162f 100644 --- a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts +++ b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts @@ -1,7 +1,7 @@ import { Platform } from 'react-native'; import { PressableEvent } from './PressableProps'; import { StateDefinition } from './StateMachine'; -import RNGestureHandlerModule from '../../RNGestureHandlerModule'; +import { isScreenReaderEnabled } from '../../utils'; export enum StateMachineEvent { NATIVE_BEGIN = 'nativeBegin', @@ -132,7 +132,7 @@ export function getStatesConfig( handlePressOut: (event: PressableEvent) => void ): StateDefinition[] { if (Platform.OS === 'android') { - if (RNGestureHandlerModule.isScreenReaderEnabled()) { + if (isScreenReaderEnabled()) { return getAndroidAccessibilityStatesConfig(handlePressIn, handlePressOut); } return getAndroidStatesConfig(handlePressIn, handlePressOut); diff --git a/packages/react-native-gesture-handler/src/utils.ts b/packages/react-native-gesture-handler/src/utils.ts index 6d715361c0..95c98acfb1 100644 --- a/packages/react-native-gesture-handler/src/utils.ts +++ b/packages/react-native-gesture-handler/src/utils.ts @@ -1,3 +1,6 @@ +import { AccessibilityInfo } from 'react-native'; +import RNGestureHandlerModule from './RNGestureHandlerModule'; + export function toArray(object: T | T[]): T[] { if (!Array.isArray(object)) { return [object]; @@ -93,3 +96,16 @@ export function deepEqual(obj1: any, obj2: any) { } export const INT32_MAX = 2 ** 31 - 1; + +let isScreenReaderEnabledCache: boolean | null = null; + +AccessibilityInfo.addEventListener('screenReaderChanged', (enabled) => { + isScreenReaderEnabledCache = enabled; +}); + +export function isScreenReaderEnabled(): boolean { + if (isScreenReaderEnabledCache === null) { + isScreenReaderEnabledCache = RNGestureHandlerModule.isScreenReaderEnabled(); + } + return isScreenReaderEnabledCache; +} diff --git a/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx b/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx index 604e2cacf3..e55b819d02 100644 --- a/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx +++ b/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx @@ -39,8 +39,7 @@ import { GestureDetector } from '../detectors'; import { PureNativeButton } from './GestureButtons'; import { PressabilityDebugView } from '../../handlers/PressabilityDebugView'; -import { INT32_MAX, isTestEnv } from '../../utils'; -import RNGestureHandlerModule from '../../RNGestureHandlerModule'; +import { INT32_MAX, isScreenReaderEnabled, isTestEnv } from '../../utils'; const DEFAULT_LONG_PRESS_DURATION = 500; const IS_TEST_ENV = isTestEnv(); @@ -258,10 +257,7 @@ const Pressable = (props: PressableProps) => { ); }, onTouchesUp: () => { - if ( - Platform.OS === 'android' && - !RNGestureHandlerModule.isScreenReaderEnabled() - ) { + if (Platform.OS === 'android' && !isScreenReaderEnabled()) { // Prevents potential soft-locks stateMachine.reset(); handleFinalize(); From ef091ac4217cf3f1dd25e1b99abfa97692fd3909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 9 Mar 2026 17:06:32 +0100 Subject: [PATCH 08/15] legacy --- .../src/components/Pressable/Pressable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx b/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx index f1abc78a78..ac44c46c13 100644 --- a/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx +++ b/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx @@ -28,7 +28,7 @@ import { isTouchWithinInset, } from './utils'; import { PressabilityDebugView } from '../../handlers/PressabilityDebugView'; -import { INT32_MAX, isTestEnv } from '../../utils'; +import { INT32_MAX, isScreenReaderEnabled, isTestEnv } from '../../utils'; import { applyRelationProp, RelationPropName, @@ -259,7 +259,7 @@ const LegacyPressable = (props: LegacyPressableProps) => { ); }) .onTouchesUp(() => { - if (Platform.OS === 'android') { + if (Platform.OS === 'android' && !isScreenReaderEnabled()) { // Prevents potential soft-locks stateMachine.reset(); handleFinalize(); From f5fb8411b94381979cf7b057e16c5d74dbd8ece5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 9 Mar 2026 18:07:57 +0100 Subject: [PATCH 09/15] mock --- packages/react-native-gesture-handler/src/mocks/module.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-native-gesture-handler/src/mocks/module.tsx b/packages/react-native-gesture-handler/src/mocks/module.tsx index c6b957e1d0..bc26151885 100644 --- a/packages/react-native-gesture-handler/src/mocks/module.tsx +++ b/packages/react-native-gesture-handler/src/mocks/module.tsx @@ -11,6 +11,7 @@ const flushOperations = NOOP; const configureRelations = NOOP; const setReanimatedAvailable = NOOP; const install = NOOP; +const isScreenReaderEnabled = NOOP; export default { attachGestureHandler, @@ -22,4 +23,5 @@ export default { setReanimatedAvailable, flushOperations, install, + isScreenReaderEnabled, } as const; From 11f7445c39277064fca1272c232ad6f409b7c961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Mon, 9 Mar 2026 18:10:41 +0100 Subject: [PATCH 10/15] update --- packages/react-native-gesture-handler/src/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/src/utils.ts b/packages/react-native-gesture-handler/src/utils.ts index 95c98acfb1..24e3a2c61b 100644 --- a/packages/react-native-gesture-handler/src/utils.ts +++ b/packages/react-native-gesture-handler/src/utils.ts @@ -99,8 +99,8 @@ export const INT32_MAX = 2 ** 31 - 1; let isScreenReaderEnabledCache: boolean | null = null; -AccessibilityInfo.addEventListener('screenReaderChanged', (enabled) => { - isScreenReaderEnabledCache = enabled; +AccessibilityInfo.addEventListener('screenReaderChanged', () => { + isScreenReaderEnabledCache = RNGestureHandlerModule.isScreenReaderEnabled(); }); export function isScreenReaderEnabled(): boolean { From 733dd7468c2bcc4edfb0a3561eecd1b59aff4f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Tue, 17 Mar 2026 12:24:50 +0100 Subject: [PATCH 11/15] hook based solution --- .../src/components/Pressable/Pressable.tsx | 13 ++++-- .../components/Pressable/stateDefinitions.ts | 6 +-- .../react-native-gesture-handler/src/utils.ts | 40 +++++++++++++------ .../src/v3/components/Pressable.tsx | 13 ++++-- 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx b/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx index 5212eb9551..80ca5d1b53 100644 --- a/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx +++ b/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx @@ -28,7 +28,7 @@ import { isTouchWithinInset, } from './utils'; import { PressabilityDebugView } from '../../handlers/PressabilityDebugView'; -import { INT32_MAX, isScreenReaderEnabled, isTestEnv } from '../../utils'; +import { INT32_MAX, isTestEnv, useIsScreenReaderEnabled } from '../../utils'; import { applyRelationProp, RelationPropName, @@ -202,11 +202,16 @@ const LegacyPressable = (props: LegacyPressableProps) => { ); const stateMachine = useMemo(() => new PressableStateMachine(), []); + const isScreenReaderEnabled = useIsScreenReaderEnabled(); useEffect(() => { - const configuration = getStatesConfig(handlePressIn, handlePressOut); + const configuration = getStatesConfig( + handlePressIn, + handlePressOut, + isScreenReaderEnabled + ); stateMachine.setStates(configuration); - }, [handlePressIn, handlePressOut, stateMachine]); + }, [handlePressIn, handlePressOut, stateMachine, isScreenReaderEnabled]); const hoverInTimeout = useRef(null); const hoverOutTimeout = useRef(null); @@ -259,7 +264,7 @@ const LegacyPressable = (props: LegacyPressableProps) => { ); }) .onTouchesUp(() => { - if (Platform.OS === 'android' && !isScreenReaderEnabled()) { + if (Platform.OS === 'android' && !isScreenReaderEnabled) { // Prevents potential soft-locks stateMachine.reset(); handleFinalize(); diff --git a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts index fb1d7b162f..b43c4cbc33 100644 --- a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts +++ b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts @@ -1,7 +1,6 @@ import { Platform } from 'react-native'; import { PressableEvent } from './PressableProps'; import { StateDefinition } from './StateMachine'; -import { isScreenReaderEnabled } from '../../utils'; export enum StateMachineEvent { NATIVE_BEGIN = 'nativeBegin', @@ -129,10 +128,11 @@ function getUniversalStatesConfig( export function getStatesConfig( handlePressIn: (event: PressableEvent) => void, - handlePressOut: (event: PressableEvent) => void + handlePressOut: (event: PressableEvent) => void, + screenReaderActive: boolean ): StateDefinition[] { if (Platform.OS === 'android') { - if (isScreenReaderEnabled()) { + if (screenReaderActive) { return getAndroidAccessibilityStatesConfig(handlePressIn, handlePressOut); } return getAndroidStatesConfig(handlePressIn, handlePressOut); diff --git a/packages/react-native-gesture-handler/src/utils.ts b/packages/react-native-gesture-handler/src/utils.ts index 24e3a2c61b..61b3222dc0 100644 --- a/packages/react-native-gesture-handler/src/utils.ts +++ b/packages/react-native-gesture-handler/src/utils.ts @@ -1,5 +1,5 @@ import { AccessibilityInfo } from 'react-native'; -import RNGestureHandlerModule from './RNGestureHandlerModule'; +import { useEffect, useState } from 'react'; export function toArray(object: T | T[]): T[] { if (!Array.isArray(object)) { @@ -97,15 +97,31 @@ export function deepEqual(obj1: any, obj2: any) { export const INT32_MAX = 2 ** 31 - 1; -let isScreenReaderEnabledCache: boolean | null = null; - -AccessibilityInfo.addEventListener('screenReaderChanged', () => { - isScreenReaderEnabledCache = RNGestureHandlerModule.isScreenReaderEnabled(); -}); - -export function isScreenReaderEnabled(): boolean { - if (isScreenReaderEnabledCache === null) { - isScreenReaderEnabledCache = RNGestureHandlerModule.isScreenReaderEnabled(); - } - return isScreenReaderEnabledCache; +export function useIsScreenReaderEnabled() { + const [isEnabled, setIsEnabled] = useState(false); + + useEffect(() => { + const checkStatus = async () => { + try { + const res = await AccessibilityInfo.isScreenReaderEnabled(); + setIsEnabled(res); + } catch (error) { + console.warn('Could not read accessibility info: defaulting to false'); + } + }; + + checkStatus(); + + const listener = AccessibilityInfo.addEventListener( + 'screenReaderChanged', + (enabled) => { + setIsEnabled(enabled); + } + ); + + return () => { + listener.remove(); + }; + }, []); + return isEnabled; } diff --git a/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx b/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx index e55b819d02..921b934fe1 100644 --- a/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx +++ b/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx @@ -39,7 +39,7 @@ import { GestureDetector } from '../detectors'; import { PureNativeButton } from './GestureButtons'; import { PressabilityDebugView } from '../../handlers/PressabilityDebugView'; -import { INT32_MAX, isScreenReaderEnabled, isTestEnv } from '../../utils'; +import { INT32_MAX, isTestEnv, useIsScreenReaderEnabled } from '../../utils'; const DEFAULT_LONG_PRESS_DURATION = 500; const IS_TEST_ENV = isTestEnv(); @@ -199,11 +199,16 @@ const Pressable = (props: PressableProps) => { ); const stateMachine = useMemo(() => new PressableStateMachine(), []); + const isScreenReaderEnabled = useIsScreenReaderEnabled(); useEffect(() => { - const configuration = getStatesConfig(handlePressIn, handlePressOut); + const configuration = getStatesConfig( + handlePressIn, + handlePressOut, + isScreenReaderEnabled + ); stateMachine.setStates(configuration); - }, [handlePressIn, handlePressOut, stateMachine]); + }, [handlePressIn, handlePressOut, stateMachine, isScreenReaderEnabled]); const hoverInTimeout = useRef(null); const hoverOutTimeout = useRef(null); @@ -257,7 +262,7 @@ const Pressable = (props: PressableProps) => { ); }, onTouchesUp: () => { - if (Platform.OS === 'android' && !isScreenReaderEnabled()) { + if (Platform.OS === 'android' && !isScreenReaderEnabled) { // Prevents potential soft-locks stateMachine.reset(); handleFinalize(); From 67fe0ebab693c01e21b945f8bac05aacfd32c2bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Tue, 17 Mar 2026 14:27:04 +0100 Subject: [PATCH 12/15] cleanup --- .../gesturehandler/react/RNGestureHandlerModule.kt | 11 ----------- .../apple/RNGestureHandlerModule.mm | 5 ----- .../src/RNGestureHandlerModule.web.ts | 3 --- .../src/RNGestureHandlerModule.windows.ts | 3 --- .../react-native-gesture-handler/src/mocks/module.tsx | 2 -- .../src/specs/NativeRNGestureHandlerModule.ts | 1 - 6 files changed, 25 deletions(-) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt index f9b8de3ed7..7b2ab98640 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt @@ -1,7 +1,5 @@ package com.swmansion.gesturehandler.react -import android.content.Context -import android.view.accessibility.AccessibilityManager import com.facebook.jni.HybridData import com.facebook.proguard.annotations.DoNotStrip import com.facebook.react.bridge.JSApplicationIllegalArgumentException @@ -131,15 +129,6 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) : isReanimatedAvailable = isAvailable } - @ReactMethod - override fun isScreenReaderEnabled(): Boolean { - val accessibilityManager = reactApplicationContext.getSystemService( - Context.ACCESSIBILITY_SERVICE, - ) as AccessibilityManager? - - return accessibilityManager?.isTouchExplorationEnabled ?: false - } - @DoNotStrip @Suppress("unused") fun setGestureHandlerState(handlerTag: Int, newState: Int) { diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm index 0ec7ed8162..8be6f2a08d 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm @@ -248,11 +248,6 @@ - (void)setGestureStateSync:(int)state forHandler:(int)handlerTag } } -- (NSNumber *)isScreenReaderEnabled -{ - return @(UIAccessibilityIsVoiceOverRunning()); -} - #pragma mark-- Batch handling - (void)addOperationBlock:(GestureHandlerOperation)operation diff --git a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts index 698b8ade22..c30bfd7729 100644 --- a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts +++ b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts @@ -92,7 +92,4 @@ export default { setReanimatedAvailable(_isAvailable: boolean) { // No-op on web }, - isScreenReaderEnabled() { - return false; - }, }; diff --git a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts index 87d5dd33fc..cf71b49397 100644 --- a/packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts +++ b/packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts @@ -56,7 +56,4 @@ export default { flushOperations() { // NO-OP }, - isScreenReaderEnabled() { - // NO-OP - }, }; diff --git a/packages/react-native-gesture-handler/src/mocks/module.tsx b/packages/react-native-gesture-handler/src/mocks/module.tsx index bc26151885..c6b957e1d0 100644 --- a/packages/react-native-gesture-handler/src/mocks/module.tsx +++ b/packages/react-native-gesture-handler/src/mocks/module.tsx @@ -11,7 +11,6 @@ const flushOperations = NOOP; const configureRelations = NOOP; const setReanimatedAvailable = NOOP; const install = NOOP; -const isScreenReaderEnabled = NOOP; export default { attachGestureHandler, @@ -23,5 +22,4 @@ export default { setReanimatedAvailable, flushOperations, install, - isScreenReaderEnabled, } as const; diff --git a/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts b/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts index 83c22db57f..c40798482b 100644 --- a/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts +++ b/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts @@ -25,7 +25,6 @@ export interface Spec extends TurboModule { dropGestureHandler: (handlerTag: Double) => void; flushOperations: () => void; setReanimatedAvailable: (isAvailable: boolean) => void; - isScreenReaderEnabled: () => boolean; } export default TurboModuleRegistry.getEnforcing('RNGestureHandlerModule'); From f843e4734250be35fb6843ad3c4b82840231ab9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Tue, 17 Mar 2026 14:42:41 +0100 Subject: [PATCH 13/15] ternary --- .../src/components/Pressable/stateDefinitions.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts index b43c4cbc33..af4fb93057 100644 --- a/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts +++ b/packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts @@ -132,10 +132,9 @@ export function getStatesConfig( screenReaderActive: boolean ): StateDefinition[] { if (Platform.OS === 'android') { - if (screenReaderActive) { - return getAndroidAccessibilityStatesConfig(handlePressIn, handlePressOut); - } - return getAndroidStatesConfig(handlePressIn, handlePressOut); + return screenReaderActive + ? getAndroidAccessibilityStatesConfig(handlePressIn, handlePressOut) + : getAndroidStatesConfig(handlePressIn, handlePressOut); } else if (Platform.OS === 'ios') { return getIosStatesConfig(handlePressIn, handlePressOut); } else if (Platform.OS === 'web') { From 7521519fe7135afdb78821bddc1a594cd4736a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Fri, 20 Mar 2026 09:25:10 +0100 Subject: [PATCH 14/15] seperate hook into its own file --- .../src/components/Pressable/Pressable.tsx | 3 +- .../src/useIsScreenReaderEnabled.ts | 31 ++++++++++++++++++ .../react-native-gesture-handler/src/utils.ts | 32 ------------------- .../src/v3/components/Pressable.tsx | 3 +- 4 files changed, 35 insertions(+), 34 deletions(-) create mode 100644 packages/react-native-gesture-handler/src/useIsScreenReaderEnabled.ts diff --git a/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx b/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx index 80ca5d1b53..b2a76ee961 100644 --- a/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx +++ b/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx @@ -28,7 +28,7 @@ import { isTouchWithinInset, } from './utils'; import { PressabilityDebugView } from '../../handlers/PressabilityDebugView'; -import { INT32_MAX, isTestEnv, useIsScreenReaderEnabled } from '../../utils'; +import { INT32_MAX, isTestEnv } from '../../utils'; import { applyRelationProp, RelationPropName, @@ -36,6 +36,7 @@ import { } from '../utils'; import { getStatesConfig, StateMachineEvent } from './stateDefinitions'; import { PressableStateMachine } from './StateMachine'; +import { useIsScreenReaderEnabled } from '../../useIsScreenReaderEnabled'; const DEFAULT_LONG_PRESS_DURATION = 500; const IS_TEST_ENV = isTestEnv(); diff --git a/packages/react-native-gesture-handler/src/useIsScreenReaderEnabled.ts b/packages/react-native-gesture-handler/src/useIsScreenReaderEnabled.ts new file mode 100644 index 0000000000..4d7dba945b --- /dev/null +++ b/packages/react-native-gesture-handler/src/useIsScreenReaderEnabled.ts @@ -0,0 +1,31 @@ +import { useEffect, useState } from 'react'; +import { AccessibilityInfo } from 'react-native'; + +export function useIsScreenReaderEnabled() { + const [isEnabled, setIsEnabled] = useState(false); + + useEffect(() => { + const checkStatus = async () => { + try { + const res = await AccessibilityInfo.isScreenReaderEnabled(); + setIsEnabled(res); + } catch (error) { + console.warn('Could not read accessibility info: defaulting to false'); + } + }; + + checkStatus(); + + const listener = AccessibilityInfo.addEventListener( + 'screenReaderChanged', + (enabled) => { + setIsEnabled(enabled); + } + ); + + return () => { + listener.remove(); + }; + }, []); + return isEnabled; +} diff --git a/packages/react-native-gesture-handler/src/utils.ts b/packages/react-native-gesture-handler/src/utils.ts index 61b3222dc0..6d715361c0 100644 --- a/packages/react-native-gesture-handler/src/utils.ts +++ b/packages/react-native-gesture-handler/src/utils.ts @@ -1,6 +1,3 @@ -import { AccessibilityInfo } from 'react-native'; -import { useEffect, useState } from 'react'; - export function toArray(object: T | T[]): T[] { if (!Array.isArray(object)) { return [object]; @@ -96,32 +93,3 @@ export function deepEqual(obj1: any, obj2: any) { } export const INT32_MAX = 2 ** 31 - 1; - -export function useIsScreenReaderEnabled() { - const [isEnabled, setIsEnabled] = useState(false); - - useEffect(() => { - const checkStatus = async () => { - try { - const res = await AccessibilityInfo.isScreenReaderEnabled(); - setIsEnabled(res); - } catch (error) { - console.warn('Could not read accessibility info: defaulting to false'); - } - }; - - checkStatus(); - - const listener = AccessibilityInfo.addEventListener( - 'screenReaderChanged', - (enabled) => { - setIsEnabled(enabled); - } - ); - - return () => { - listener.remove(); - }; - }, []); - return isEnabled; -} diff --git a/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx b/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx index 921b934fe1..245c250657 100644 --- a/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx +++ b/packages/react-native-gesture-handler/src/v3/components/Pressable.tsx @@ -39,7 +39,8 @@ import { GestureDetector } from '../detectors'; import { PureNativeButton } from './GestureButtons'; import { PressabilityDebugView } from '../../handlers/PressabilityDebugView'; -import { INT32_MAX, isTestEnv, useIsScreenReaderEnabled } from '../../utils'; +import { INT32_MAX, isTestEnv } from '../../utils'; +import { useIsScreenReaderEnabled } from '../../useIsScreenReaderEnabled'; const DEFAULT_LONG_PRESS_DURATION = 500; const IS_TEST_ENV = isTestEnv(); From b21d5f96cb616b0f0c5cdebbe3ef29d85ffa87b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Fri, 20 Mar 2026 09:27:42 +0100 Subject: [PATCH 15/15] add isScreenReaderEnabled to dependency array --- .../src/components/Pressable/Pressable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx b/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx index b2a76ee961..350fb5449f 100644 --- a/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx +++ b/packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx @@ -286,7 +286,7 @@ const LegacyPressable = (props: LegacyPressableProps) => { handleFinalize(); } }), - [stateMachine, handleFinalize, handlePressOut] + [stateMachine, handleFinalize, handlePressOut, isScreenReaderEnabled] ); // RNButton is placed inside ButtonGesture to enable Android's ripple and to capture non-propagating events