Skip to content

Commit ded70a4

Browse files
committed
fix: restore isTrackpadDetector.ts after rebase
1 parent 292bd00 commit ded70a4

File tree

1 file changed

+119
-0
lines changed

1 file changed

+119
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Time in milliseconds to keep trackpad detection state
2+
const TRACKPAD_DETECTION_STATE_TIMEOUT = 60_000; // 1 minute
3+
4+
/**
5+
* Creates a trackpad detection function that distinguishes between trackpad and mouse wheel events.
6+
*
7+
* This factory function returns a detector that analyzes WheelEvent characteristics to determine
8+
* the input device type. The detection is based on several behavioral patterns:
9+
*
10+
* - **Pinch-to-zoom gestures**: Trackpads generate wheel events with modifier keys (Ctrl/Meta)
11+
* and continuous (non-integer) delta values
12+
* - **Horizontal scrolling**: Trackpads naturally produce horizontal scroll events (deltaX),
13+
* while mice typically only scroll vertically
14+
* - **Continuous scrolling**: Trackpad scroll deltas are usually fractional values, while mouse
15+
* wheels produce discrete integer values
16+
*
17+
* The detector maintains state across events to provide consistent results during a scroll session.
18+
* Once a trackpad is detected, the state persists for 60 seconds before resetting.
19+
*
20+
* @returns A detection function that accepts WheelEvent and optional devicePixelRatio
21+
*
22+
* @example
23+
* ```typescript
24+
* const isTrackpad = isTrackpadDetector();
25+
*
26+
* element.addEventListener('wheel', (e) => {
27+
* if (isTrackpad(e)) {
28+
* console.log('Trackpad scroll detected');
29+
* } else {
30+
* console.log('Mouse wheel detected');
31+
* }
32+
* });
33+
* ```
34+
*/
35+
function isTrackpadDetector() {
36+
let isTrackpadDetected = false;
37+
let cleanStateTimer: number | null = null;
38+
39+
/**
40+
* Marks the current input device as trackpad and resets the state timeout.
41+
* This ensures consistent detection during continuous scroll operations.
42+
*/
43+
const markAsTrackpad = (): void => {
44+
isTrackpadDetected = true;
45+
clearTimeout(cleanStateTimer);
46+
cleanStateTimer = setTimeout(() => {
47+
isTrackpadDetected = false;
48+
}, TRACKPAD_DETECTION_STATE_TIMEOUT) as unknown as number;
49+
};
50+
51+
/**
52+
* Analyzes a wheel event to determine if it originated from a trackpad.
53+
*
54+
* @param e - The WheelEvent to analyze
55+
* @param dpr - Device pixel ratio for normalizing delta values. Defaults to window.devicePixelRatio.
56+
* This normalization accounts for browser zoom levels to improve detection accuracy.
57+
* @returns `true` if the event is from a trackpad, `false` if from a mouse wheel
58+
*/
59+
return (e: WheelEvent, dpr: number = globalThis.devicePixelRatio || 1) => {
60+
const normalizedDeltaY = e.deltaY * dpr;
61+
const normalizedDeltaX = e.deltaX * dpr;
62+
const hasFractionalDelta = normalizedDeltaY && !Number.isInteger(normalizedDeltaY);
63+
64+
// Detection 1: Pinch-to-zoom gesture
65+
// Trackpad pinch-to-zoom generates wheel events with ctrlKey or metaKey.
66+
// Combined with non-integer deltaY, this is a strong indicator of trackpad.
67+
const isPinchToZoomGesture = (e.ctrlKey || e.metaKey) && hasFractionalDelta;
68+
if (isPinchToZoomGesture) {
69+
markAsTrackpad();
70+
return true;
71+
}
72+
73+
// Detection 2: Horizontal scroll (deltaX)
74+
// Trackpad naturally produces horizontal scroll events.
75+
// Note: When Shift is pressed, browser swaps deltaX and deltaY for mouse wheel,
76+
// so we skip this check to avoid false positives.
77+
const hasHorizontalScroll = normalizedDeltaX !== 0;
78+
const isShiftPressed = e.shiftKey;
79+
if (hasHorizontalScroll && !isShiftPressed) {
80+
markAsTrackpad();
81+
return true;
82+
}
83+
84+
// Detection 3: Fractional deltaY (mouse produces integer values)
85+
// If we have non-integer deltaY without ctrl/meta keys, it's likely NOT trackpad
86+
// (could be browser zoom or other factors), so we explicitly return false.
87+
if (hasFractionalDelta) {
88+
return false;
89+
}
90+
91+
// Fallback: Return previously detected state
92+
// This helps maintain consistency across rapid scroll events.
93+
return isTrackpadDetected;
94+
};
95+
}
96+
97+
/**
98+
* Global trackpad detector instance for wheel events.
99+
*
100+
* Use this pre-configured detector to check if a wheel event originated from a trackpad
101+
* rather than a traditional mouse wheel. The detector maintains state across calls to provide
102+
* consistent results throughout a scroll session.
103+
*
104+
* @example
105+
* ```typescript
106+
* import { isTrackpadWheelEvent } from './utils/functions';
107+
*
108+
* canvas.addEventListener('wheel', (event) => {
109+
* if (isTrackpadWheelEvent(event)) {
110+
* // Handle smooth trackpad scrolling
111+
* applyMomentumScrolling(event);
112+
* } else {
113+
* // Handle discrete mouse wheel steps
114+
* applySteppedScrolling(event);
115+
* }
116+
* });
117+
* ```
118+
*/
119+
export const isTrackpadWheelEvent = isTrackpadDetector();

0 commit comments

Comments
 (0)