diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/LongPressGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/LongPressGestureHandler.kt index cc53c76d1c..a38b1dfbc3 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/LongPressGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/LongPressGestureHandler.kt @@ -29,13 +29,14 @@ class LongPressGestureHandler(context: Context) : GestureHandler() { val systemDefaultMaxDist = DEFAULT_MAX_DIST_DP * context.resources.displayMetrics.density defaultMaxDist = systemDefaultMaxDist maxDist = defaultMaxDist - numberOfPointersRequired = 1 + numberOfPointersRequired = DEFAULT_NUMBER_OF_POINTERS_REQUIRED } override fun resetConfig() { super.resetConfig() minDurationMs = DEFAULT_MIN_DURATION_MS maxDist = defaultMaxDist + numberOfPointersRequired = DEFAULT_NUMBER_OF_POINTERS_REQUIRED shouldCancelWhenOutside = DEFAULT_SHOULD_CANCEL_WHEN_OUTSIDE } @@ -211,5 +212,6 @@ class LongPressGestureHandler(context: Context) : GestureHandler() { private const val DEFAULT_SHOULD_CANCEL_WHEN_OUTSIDE = true private const val DEFAULT_MIN_DURATION_MS: Long = 500 private const val DEFAULT_MAX_DIST_DP = 10f + private const val DEFAULT_NUMBER_OF_POINTERS_REQUIRED = 1 } } diff --git a/packages/react-native-gesture-handler/apple/Handlers/RNLongPressHandler.m b/packages/react-native-gesture-handler/apple/Handlers/RNLongPressHandler.m index 00cdf0421e..b6e3a50b92 100644 --- a/packages/react-native-gesture-handler/apple/Handlers/RNLongPressHandler.m +++ b/packages/react-native-gesture-handler/apple/Handlers/RNLongPressHandler.m @@ -236,6 +236,10 @@ - (void)resetConfig recognizer.minimumPressDuration = 0.5; recognizer.allowableMovement = 10; + +#if !TARGET_OS_TV + recognizer.numberOfTouchesRequired = 1; +#endif } - (void)updateConfig:(NSDictionary *)config diff --git a/packages/react-native-gesture-handler/src/web/handlers/LongPressGestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/LongPressGestureHandler.ts index 4a5221cfd4..1a53e7885b 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/LongPressGestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/LongPressGestureHandler.ts @@ -13,6 +13,7 @@ import type IGestureHandler from './IGestureHandler'; const DEFAULT_MIN_DURATION_MS = 500; const DEFAULT_MAX_DIST_DP = 10; +const DEFAULT_NUMBER_OF_POINTERS = 1; const SCALING_FACTOR = 10; export default class LongPressGestureHandler extends GestureHandler { @@ -20,7 +21,7 @@ export default class LongPressGestureHandler extends GestureHandler { private defaultMaxDistSq = DEFAULT_MAX_DIST_DP * SCALING_FACTOR; private maxDistSq = this.defaultMaxDistSq; - private numberOfPointers = 1; + private numberOfPointers = DEFAULT_NUMBER_OF_POINTERS; private startX = 0; private startY = 0; @@ -77,6 +78,7 @@ export default class LongPressGestureHandler extends GestureHandler { super.resetConfig(); this.minDurationMs = DEFAULT_MIN_DURATION_MS; this.maxDistSq = this.defaultMaxDistSq; + this.numberOfPointers = DEFAULT_NUMBER_OF_POINTERS; } protected override onStateChange(_newState: State, _oldState: State): void { diff --git a/packages/react-native-gesture-handler/src/web/handlers/__tests__/LongPressGestureHandler.test.ts b/packages/react-native-gesture-handler/src/web/handlers/__tests__/LongPressGestureHandler.test.ts new file mode 100644 index 0000000000..db86455b97 --- /dev/null +++ b/packages/react-native-gesture-handler/src/web/handlers/__tests__/LongPressGestureHandler.test.ts @@ -0,0 +1,90 @@ +import { ActionType } from '../../../ActionType'; +import { PointerType } from '../../../PointerType'; +import { State } from '../../../State'; +import type { AdaptedEvent } from '../../interfaces'; +import { EventTypes } from '../../interfaces'; +import type { GestureHandlerDelegate } from '../../tools/GestureHandlerDelegate'; +import GestureHandlerOrchestrator from '../../tools/GestureHandlerOrchestrator'; +import type IGestureHandler from '../IGestureHandler'; +import LongPressGestureHandler from '../LongPressGestureHandler'; + +class TestLongPressGestureHandler extends LongPressGestureHandler { + public pointerDown(event: AdaptedEvent): void { + this.onPointerDown(event); + } +} + +function touchEvent(pointerId: number, x: number, y: number): AdaptedEvent { + return { + x, + y, + offsetX: x, + offsetY: y, + pointerId, + eventType: EventTypes.DOWN, + pointerType: PointerType.TOUCH, + time: 0, + }; +} + +function createHandler() { + const delegate = { + init: jest.fn(), + detach: jest.fn(), + reset: jest.fn(), + onActivate: jest.fn(), + onFail: jest.fn(), + onCancel: jest.fn(), + onEnd: jest.fn(), + onEnabledChange: jest.fn(), + updateDOM: jest.fn(), + } as unknown as GestureHandlerDelegate; + + const handler = new TestLongPressGestureHandler(delegate); + handler.init(1, { current: {} } as never, ActionType.JS_FUNCTION_OLD_API); + + // The full event pipeline is not under test, silence event emission. + handler.sendEvent = jest.fn(); + + return handler; +} + +describe('LongPressGestureHandler config reset', () => { + afterEach(() => { + // The orchestrator is a singleton, drop handlers recorded by the test. + ( + GestureHandlerOrchestrator.instance as unknown as { + gestureHandlers: IGestureHandler[]; + } + ).gestureHandlers = []; + }); + + test('a config without numberOfPointers restores the single pointer default', () => { + const handler = createHandler(); + + handler.setGestureConfig({ + enabled: true, + minDurationMs: 0, + numberOfPointers: 2, + }); + handler.setGestureConfig({ enabled: true, minDurationMs: 0 }); + + handler.pointerDown(touchEvent(0, 100, 100)); + + expect(handler.state).toBe(State.ACTIVE); + }); + + test('numberOfPointers still applies while it stays in the config', () => { + const handler = createHandler(); + + handler.setGestureConfig({ + enabled: true, + minDurationMs: 0, + numberOfPointers: 2, + }); + + handler.pointerDown(touchEvent(0, 100, 100)); + + expect(handler.state).toBe(State.BEGAN); + }); +});