Skip to content

Commit 5eaf877

Browse files
authored
Reset enableTrackpadTwoFingerGesture in web PanGestureHandler (#4480)
## Description On web, `PanGestureHandler.updateGestureConfig` writes `enableTrackpadTwoFingerGesture`, but `resetConfig` never restores it. `setGestureConfig` is a full config replace (`resetConfig` then `updateGestureConfig`), and it is what `updateHandlers` / `useGesture` call whenever the gesture config changes. So once a pan gesture has been configured with `enableTrackpadTwoFingerGesture: true`, a later config that no longer carries the prop keeps two-finger trackpad panning enabled, and a wheel event from a trackpad still activates the gesture. Every other field the web `PanGestureHandler` reads from the config is restored in `resetConfig`; this one was missed. iOS is already correct: `RNPanHandler`'s `resetConfig` sets `recognizer.allowedScrollTypesMask = 0`, which is the same property. Android does not support the prop, so this is web only. ## Test plan Added `src/web/handlers/__tests__/PanGestureHandler.test.ts` with two cases: - configure the handler with `enableTrackpadTwoFingerGesture: true`, then apply a config without it, and feed a touchpad wheel event: the handler must stay `UNDETERMINED`. - configure it with `enableTrackpadTwoFingerGesture: true` and feed the same event: the handler must reach `ACTIVE`, so the flag itself keeps working. Without the one-line change in `resetConfig`, the first test fails with `Expected: 0 / Received: 4` (the gesture activates from stale config). The second one passes both ways. `yarn test`, `yarn lint-js` and `yarn ts-check` are green in `packages/react-native-gesture-handler`.
1 parent 2efd514 commit 5eaf877

2 files changed

Lines changed: 100 additions & 0 deletions

File tree

‎packages/react-native-gesture-handler/src/web/handlers/PanGestureHandler.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ export default class PanGestureHandler extends GestureHandler {
170170
this.maxPointers = DEFAULT_MAX_POINTERS;
171171

172172
this.activateAfterLongPress = 0;
173+
this.enableTrackpadTwoFingerGesture = false;
173174

174175
this.hasCustomActivationCriteria = false;
175176
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { ActionType } from '../../../ActionType';
2+
import { PointerType } from '../../../PointerType';
3+
import { State } from '../../../State';
4+
import type { AdaptedEvent } from '../../interfaces';
5+
import { EventTypes } from '../../interfaces';
6+
import type { GestureHandlerDelegate } from '../../tools/GestureHandlerDelegate';
7+
import GestureHandlerOrchestrator from '../../tools/GestureHandlerOrchestrator';
8+
import type IGestureHandler from '../IGestureHandler';
9+
import PanGestureHandler from '../PanGestureHandler';
10+
11+
class TestPanGestureHandler extends PanGestureHandler {
12+
public wheel(event: AdaptedEvent): void {
13+
this.onWheel(event);
14+
}
15+
}
16+
17+
function wheelEvent(): AdaptedEvent {
18+
return {
19+
x: 100,
20+
y: 100,
21+
offsetX: 100,
22+
offsetY: 100,
23+
pointerId: 0,
24+
eventType: EventTypes.MOVE,
25+
pointerType: PointerType.OTHER,
26+
time: 0,
27+
// Not a multiple of 120, so the wheel is recognized as a touchpad.
28+
wheelDeltaY: 13,
29+
};
30+
}
31+
32+
function createHandler() {
33+
const delegate = {
34+
init: jest.fn(),
35+
detach: jest.fn(),
36+
reset: jest.fn(),
37+
onBegin: jest.fn(),
38+
onActivate: jest.fn(),
39+
onFail: jest.fn(),
40+
onCancel: jest.fn(),
41+
onEnd: jest.fn(),
42+
onEnabledChange: jest.fn(),
43+
updateDOM: jest.fn(),
44+
isPointerInBounds: jest.fn().mockReturnValue(true),
45+
measureView: jest.fn().mockReturnValue({
46+
pageX: 0,
47+
pageY: 0,
48+
width: 100,
49+
height: 100,
50+
}),
51+
absoluteToLocal: jest.fn((x: number, y: number) => ({ x, y })),
52+
} as unknown as GestureHandlerDelegate<unknown, IGestureHandler>;
53+
54+
const handler = new TestPanGestureHandler(delegate);
55+
handler.init(1, { current: {} } as never, ActionType.JS_FUNCTION_OLD_API);
56+
57+
// The full event pipeline is not under test, silence event emission.
58+
handler.sendEvent = jest.fn();
59+
60+
return handler;
61+
}
62+
63+
describe('PanGestureHandler config reset', () => {
64+
afterEach(() => {
65+
// The orchestrator is a singleton, drop handlers recorded by the test.
66+
(
67+
GestureHandlerOrchestrator.instance as unknown as {
68+
gestureHandlers: IGestureHandler[];
69+
}
70+
).gestureHandlers = [];
71+
});
72+
73+
test('a config without enableTrackpadTwoFingerGesture restores the disabled default', () => {
74+
const handler = createHandler();
75+
76+
handler.setGestureConfig({
77+
enabled: true,
78+
enableTrackpadTwoFingerGesture: true,
79+
});
80+
handler.setGestureConfig({ enabled: true });
81+
82+
handler.wheel(wheelEvent());
83+
84+
expect(handler.state).toBe(State.UNDETERMINED);
85+
});
86+
87+
test('enableTrackpadTwoFingerGesture still applies while it stays in the config', () => {
88+
const handler = createHandler();
89+
90+
handler.setGestureConfig({
91+
enabled: true,
92+
enableTrackpadTwoFingerGesture: true,
93+
});
94+
95+
handler.wheel(wheelEvent());
96+
97+
expect(handler.state).toBe(State.ACTIVE);
98+
});
99+
});

0 commit comments

Comments
 (0)