From ac32efa2ab8caee97ddff5bf2681a2904b8060f0 Mon Sep 17 00:00:00 2001 From: Bao Nguyen Date: Fri, 4 Sep 2026 14:31:09 +0700 Subject: [PATCH] Reset `enableTrackpadTwoFingerGesture` in web `PanGestureHandler` `updateGestureConfig` writes `enableTrackpadTwoFingerGesture`, but `resetConfig` never restores it, so once a pan gesture has been configured with it, dropping the prop from a later config leaves two-finger trackpad panning enabled. iOS already clears the equivalent `allowedScrollTypesMask` in `RNPanHandler`'s `resetConfig`, so web was the odd one out. Adds a web test covering both the reset and the still-configured case. --- .../src/web/handlers/PanGestureHandler.ts | 1 + .../__tests__/PanGestureHandler.test.ts | 99 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 packages/react-native-gesture-handler/src/web/handlers/__tests__/PanGestureHandler.test.ts diff --git a/packages/react-native-gesture-handler/src/web/handlers/PanGestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/PanGestureHandler.ts index cec7a7bee4..3f21333011 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/PanGestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/PanGestureHandler.ts @@ -170,6 +170,7 @@ export default class PanGestureHandler extends GestureHandler { this.maxPointers = DEFAULT_MAX_POINTERS; this.activateAfterLongPress = 0; + this.enableTrackpadTwoFingerGesture = false; this.hasCustomActivationCriteria = false; } diff --git a/packages/react-native-gesture-handler/src/web/handlers/__tests__/PanGestureHandler.test.ts b/packages/react-native-gesture-handler/src/web/handlers/__tests__/PanGestureHandler.test.ts new file mode 100644 index 0000000000..d2da183f8a --- /dev/null +++ b/packages/react-native-gesture-handler/src/web/handlers/__tests__/PanGestureHandler.test.ts @@ -0,0 +1,99 @@ +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 PanGestureHandler from '../PanGestureHandler'; + +class TestPanGestureHandler extends PanGestureHandler { + public wheel(event: AdaptedEvent): void { + this.onWheel(event); + } +} + +function wheelEvent(): AdaptedEvent { + return { + x: 100, + y: 100, + offsetX: 100, + offsetY: 100, + pointerId: 0, + eventType: EventTypes.MOVE, + pointerType: PointerType.OTHER, + time: 0, + // Not a multiple of 120, so the wheel is recognized as a touchpad. + wheelDeltaY: 13, + }; +} + +function createHandler() { + const delegate = { + init: jest.fn(), + detach: jest.fn(), + reset: jest.fn(), + onBegin: jest.fn(), + onActivate: jest.fn(), + onFail: jest.fn(), + onCancel: jest.fn(), + onEnd: jest.fn(), + onEnabledChange: jest.fn(), + updateDOM: jest.fn(), + isPointerInBounds: jest.fn().mockReturnValue(true), + measureView: jest.fn().mockReturnValue({ + pageX: 0, + pageY: 0, + width: 100, + height: 100, + }), + absoluteToLocal: jest.fn((x: number, y: number) => ({ x, y })), + } as unknown as GestureHandlerDelegate; + + const handler = new TestPanGestureHandler(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('PanGestureHandler 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 enableTrackpadTwoFingerGesture restores the disabled default', () => { + const handler = createHandler(); + + handler.setGestureConfig({ + enabled: true, + enableTrackpadTwoFingerGesture: true, + }); + handler.setGestureConfig({ enabled: true }); + + handler.wheel(wheelEvent()); + + expect(handler.state).toBe(State.UNDETERMINED); + }); + + test('enableTrackpadTwoFingerGesture still applies while it stays in the config', () => { + const handler = createHandler(); + + handler.setGestureConfig({ + enabled: true, + enableTrackpadTwoFingerGesture: true, + }); + + handler.wheel(wheelEvent()); + + expect(handler.state).toBe(State.ACTIVE); + }); +});