A lightweight, type-safe React library for handling keyboard, pointer, mouse, wheel, and gesture events including tap, double-tap, press, swipe, drag, and pinch interactions.
- ๐น Keyboard Event Handling - Detect single keys, key combinations, and sequences with configurable timing thresholds
- ๐ฑ๏ธ Pointer Event Handling - Detect pointer interactions events with pointer type filtering
- ๐ฑ๏ธ Mouse Event Handling - Detect mouse interactions through a pointer-powered mouse events with button filtering
- ๐ฑ๏ธ Mouse Wheel Event Handling - Detect mouse wheel delta values with optional
requestAnimationFramebatching for smoother updates - ๐ Gesture Event Handling - Detect tap, double-tap, press, swipe, drag, and pinch gestures
- ๐ Tap Gesture Handling - Detect single taps or clicks with configurable movement and duration thresholds
- ๐๐ Double-Tap Gesture Handling - Detect consecutive taps or clicks with configurable timing and position thresholds
- โ Press Gesture Handling - Detect press-and-hold interactions with configurable delay and movement thresholds
- ๐๏ธ Swipe Gesture Handling - Detect directional swipes with configurable distance, velocity, and pointer type filtering
- โ Drag Gesture Handling - Detect movement, deltas, duration, and start/end positions with pointer type filtering and optional
requestAnimationFramebatching - ๐ค Pinch Gesture Handling - Detect two-finger distance, delta, and scale with pointer type filtering and optional
requestAnimationFramebatching
npm install @msobiecki/react-marauders-pathimport { useKey } from '@msobiecki/react-marauders-path';
function MyComponent() {
useKey('a', (event, key) => {
console.log(`Pressed ${key}`);
});
return <div>Press 'a'</div>;
}useKey(["a", "b", "c"], (event, key) => {
console.log(`Pressed ${key}`);
});useKey("a+b", (event, key) => {
console.log(`Pressed ${key}`);
});useKey(["a+b", "c+d"], (event, key) => {
console.log(`Pressed ${key}`);
});useKey("ArrowUp ArrowUp ArrowDown ArrowDown", (event, key) => {
console.log(`Pressed ${key}`);
});useKey(
["ArrowUp ArrowUp ArrowDown ArrowDown", "ArrowLeft ArrowRight"],
(event, key) => {
console.log(`Pressed ${key}`);
},
);import { usePointer, PointerEventTypes } from '@msobiecki/react-marauders-path';
function MyComponent() {
usePointer((event, type, data) => {
console.log(`Pointer ${type} at X: ${data.x}, Y: ${data.y}`);
}, {
eventType: [PointerEventTypes.Down, PointerEventTypes.Move, PointerEventTypes.Up],
});
return <div>Use pointer input</div>;
}import { useMouse, MouseEventTypes, MouseButtons } from '@msobiecki/react-marauders-path';
function MyComponent() {
useMouse((event, type, button, data) => {
console.log(`Mouse ${type} button ${button} at X: ${data.x}, Y: ${data.y}`);
}, {
eventType: [MouseEventTypes.Move, MouseEventTypes.Click, MouseEventTypes.DoubleClick],
eventButtons: [MouseButtons.Left],
});
return <div>Use mouse input</div>;
}import { useWheel } from '@msobiecki/react-marauders-path';
function MyComponent() {
useWheel((event, data) => {
console.log(`Scrolled - X: ${data.deltaX}, Y: ${data.deltaY}`);
});
return <div>Scroll to interact</div>;
}import { useGesture } from '@msobiecki/react-marauders-path';
function MyComponent() {
useGesture('tap', (event, data) => {
console.log(`Tapped at X: ${data.x}, Y: ${data.y}`);
}, {
threshold: 8,
});
return <div>Tap to interact</div>;
}import { useTap } from '@msobiecki/react-marauders-path';
function MyComponent() {
useTap((event, data) => {
console.log(`Tapped at X: ${data.x}, Y: ${data.y}`);
});
return <div>Tap to interact</div>;
}import { useDoubleTap } from '@msobiecki/react-marauders-path';
function MyComponent() {
useDoubleTap((event, data) => {
console.log(`Double tapped at X: ${data.x}, Y: ${data.y}`);
});
return <div>Double tap to interact</div>;
}import { usePress } from '@msobiecki/react-marauders-path';
function MyComponent() {
usePress((event, data) => {
console.log(`Pressed at X: ${data.x}, Y: ${data.y}`);
});
return <div>Press and hold to interact</div>;
}import { useSwipe } from '@msobiecki/react-marauders-path';
function MyComponent() {
useSwipe('left', (event, direction, data) => {
console.log(`Swiped ${direction} with velocity ${data.velocity}`);
});
return <div>Swipe left</div>;
}import { useDrag } from '@msobiecki/react-marauders-path';
function MyComponent() {
useDrag((event, data) => {
console.log(`Dragged by X: ${data.deltaX}, Y: ${data.deltaY}`);
});
return <div>Drag to interact</div>;
}import { usePinch } from '@msobiecki/react-marauders-path';
function MyComponent() {
usePinch((event, data) => {
console.log(`Pinch scale: ${data.scale}, delta: ${data.delta}`);
});
return <div>Pinch to zoom</div>;
}Hook for keyboard event handling with support for single keys, combinations, and sequences.
Parameters:
keyEvent: string | string[]- Single key, key combination, or key sequence to listen forcallback: (event: KeyboardEvent, key: string) => void | boolean- Called when a key event occursoptions?: UseKeyOptions- Optional configuration
Options:
interface UseKeyOptions {
eventType?: "keyup" | "keydown"; // Default: 'keyup'
eventRepeat?: boolean; // Default: false
eventCapture?: boolean; // Default: false
eventOnce?: boolean; // Default: false
eventStopImmediatePropagation?: boolean; // Default: false
sequenceThreshold?: number; // Default: 1000 (ms) - Timeout between sequence keys
combinationThreshold?: number; // Default: 200 (ms) - Timeout between combination keys
container?: RefObject<HTMLElement>; // Default: window
}Hook for handling pointer events with configurable event types, pointer types, and listener options.
Parameters:
callback: (event: PointerEvent, type: PointerEventType, data: PointerData) => void | boolean- Called when a pointer event occursoptions?: UsePointerOptions- Optional configuration
Options:
interface UsePointerOptions {
eventType?: PointerEventType[]; // Default: ["pointermove", "pointerup", "pointerdown"]
eventPointerTypes?: PointerEventPointerType[]; // Default: ["touch", "mouse", "pen"]
eventCapture?: boolean; // Default: false
eventOnce?: boolean; // Default: false
eventStopImmediatePropagation?: boolean; // Default: false
container?: RefObject<HTMLElement>; // Default: window
}Pointer Data:
interface PointerData {
x: number; // Pointer X position
y: number; // Pointer Y position
}Hook for handling mouse-like events through pointer events with button filtering and synthesized click/double-click support.
Parameters:
callback: (event: MouseEvent, type: MouseEventType, button: MouseButton, data: MouseData) => void | boolean- Called when a mouse event occursoptions?: UseMouseOptions- Optional configuration
Options:
interface UseMouseOptions {
eventType?: MouseEventType[]; // Default: ["mousemove", "mousedown", "mouseup", "click", "dblclick"]
eventButtons?: MouseButton[]; // Default: [0, 1, 2]
eventCapture?: boolean; // Default: false
eventOnce?: boolean; // Default: false
eventStopImmediatePropagation?: boolean; // Default: false
container?: RefObject<HTMLElement>; // Default: window
}Mouse Data:
interface MouseData {
x: number; // Mouse X position
y: number; // Mouse Y position
button: 0 | 1 | 2 | 3 | 4; // Active mouse button
}Hook for handling mouse wheel events with support for different delta modes and options.
Parameters:
callback: (event: WheelEvent, data: WheelData) => void | boolean- Called when a wheel event occursoptions?: UseWheelOptions- Optional configuration
Options:
interface UseWheelOptions {
eventPassive?: boolean; // Default: true
eventCapture?: boolean; // Default: false
eventOnce?: boolean; // Default: false
eventStopImmediatePropagation?: boolean; // Default: false
container?: RefObject<HTMLElement>; // Default: window
raf?: boolean; // Default: false - Use requestAnimationFrame for batching
}Wheel Data:
interface WheelData {
deltaX: number; // Delta X value
deltaY: number; // Delta Y value
deltaZ: number; // Delta Z value
deltaMode: number; // Delta mode value
}Hook for gesture event handling that delegates to one of the low-level gesture hooks.
Parameters:
gesture: "tap" | "doubletap" | "press" | "swipe" | "drag" | "pinch"- Gesture to bind (must stay the same between renders)callback- Callback type is inferred fromgestureoptions?- Options type is inferred fromgesture
Swipe-only option:
direction?: SwipeDirection- Optional direction foruseGesture("swipe", ...); defaults to"both"
Example:
useGesture(
"swipe",
(event, direction, data) => {
console.log(direction, data.velocity);
},
{
direction: "horizontal",
threshold: 40,
},
);Hook for handling single tap/click interactions.
Parameters:
callback: (event: PointerEvent, data: TapData) => void | boolean- Called when a tap gesture is recognizedoptions?: UseTapOptions- Optional configuration
Options:
interface UseTapOptions {
eventPointerTypes?: TapEventPointerType[]; // Default: ["touch", "mouse", "pen"]
eventCapture?: boolean; // Default: false
eventOnce?: boolean; // Default: false
eventStopImmediatePropagation?: boolean; // Default: false
threshold?: number; // Default: 8 (px) - Maximum movement allowed between pointerdown and pointerup
maxDuration?: number; // Default: 250 (ms) - Maximum tap duration
container?: RefObject<HTMLElement>; // Default: window
}Tap Data:
interface TapData {
x: number; // Tap pointerup X
y: number; // Tap pointerup Y
}Hook for handling double-tap / double-click interactions.
Parameters:
callback: (event: PointerEvent, data: DoubleTapData) => void | boolean- Called when a double tap is recognizedoptions?: UseDoubleTapOptions- Optional configuration
Options:
interface UseDoubleTapOptions {
eventPointerTypes?: DoubleTapEventPointerType[]; // Default: ["touch", "mouse", "pen"]
eventCapture?: boolean; // Default: false
eventOnce?: boolean; // Default: false
eventStopImmediatePropagation?: boolean; // Default: false
delay?: number; // Default: 300 (ms) - Maximum interval between taps
threshold?: number; // Default: 8 (px) - Maximum distance between two tap positions
container?: RefObject<HTMLElement>; // Default: window
}Double Tap Data:
interface DoubleTapData {
x: number; // Tap pointerup X
y: number; // Tap pointerup Y
}Hook for handling press-and-hold interactions.
Parameters:
callback: (event: PointerEvent, data: PressData) => void | boolean- Called when a press delay completesoptions?: UsePressOptions- Optional configuration
Options:
interface UsePressOptions {
eventPointerTypes?: PressEventPointerType[]; // Default: ["touch", "mouse", "pen"]
eventCapture?: boolean; // Default: false
eventOnce?: boolean; // Default: false
eventStopImmediatePropagation?: boolean; // Default: false
delay?: number; // Default: 500 (ms) - Press-and-hold duration required
threshold?: number; // Default: 8 (px) - Maximum movement allowed while holding
container?: RefObject<HTMLElement>; // Default: window
}Press Data:
interface PressData {
x: number; // Pointerdown X at press start
y: number; // Pointerdown Y at press start
}Hook for handling touch swipe gestures with configurable distance and velocity thresholds.
Parameters:
swipe: "left" | "right" | "up" | "down" | "horizontal" | "vertical" | "both"- Allowed directions to listencallback: (event: PointerEvent, direction: SwipeDirection, data: SwipeData) => void | boolean- Called when a swipe event occursoptions?: UseSwipeOptions- Optional configuration
Options:
interface UseSwipeOptions {
eventPointerTypes?: SwipeEventPointerType[]; // Default: ["touch", "mouse", "pen"]
eventCapture?: boolean; // Default: false
eventOnce?: boolean; // Default: false
eventStopImmediatePropagation?: boolean; // Default: false
threshold?: number; // Default: 50 (px) - Minimum travel distance
velocity?: number; // Default: 0.3 (px/ms) - Minimum average speed
container?: RefObject<HTMLElement>; // Default: window
}Swipe Data:
interface SwipeData {
deltaX: number; // Horizontal travel
deltaY: number; // Vertical travel
velocity: number; // Average speed (distance / duration)
duration: number; // Swipe duration in ms
}Hook for handling pointer drag gestures with configurable threshold and pointer types.
Parameters:
callback: (event: PointerEvent, data: DragData) => void | boolean- Called when a drag event occursoptions?: UseDragOptions- Optional configuration
Options:
interface UseDragOptions {
eventPointerTypes?: DragEventPointerType[]; // Default: ["touch", "mouse", "pen"]
eventCapture?: boolean; // Default: false
eventOnce?: boolean; // Default: false
eventStopImmediatePropagation?: boolean; // Default: false
threshold?: number; // Default: 0 (px) - Minimum drag distance
container?: RefObject<HTMLElement>; // Default: window
raf?: boolean; // Default: false - Use requestAnimationFrame for batching
}Drag Data:
interface DragData {
deltaX: number; // Horizontal movement from drag start
deltaY: number; // Vertical movement from drag start
movementX: number; // Horizontal movement from previous drag event
movementY: number; // Vertical movement from previous drag event
duration: number; // Drag duration in ms
startX: number; // Drag start X
startY: number; // Drag start Y
endX: number; // Drag end X
endY: number; // Drag end Y
}Hook for handling two-pointer pinch gestures with distance and scale tracking.
Parameters:
callback: (event: PointerEvent, data: PinchData) => void | boolean- Called when a pinch event occursoptions?: UsePinchOptions- Optional configuration
Options:
interface UsePinchOptions {
eventPointerTypes?: PinchEventPointerType[]; // Default: ["touch"]
eventCapture?: boolean; // Default: false
eventOnce?: boolean; // Default: false
eventStopImmediatePropagation?: boolean; // Default: false
threshold?: number; // Default: 0 (px) - Minimum pinch distance change
container?: RefObject<HTMLElement>; // Default: window
raf?: boolean; // Default: false - Use requestAnimationFrame for batching
}Pinch Data:
interface PinchData {
distance: number; // Current distance between active pointers
delta: number; // Distance change since previous pinch update
totalDelta: number; // Distance change since pinch start
scale: number; // Current scale ratio (distance / startDistance)
}useKey(
"Enter",
(event, key) => {
handleSubmit();
},
{
eventType: "keydown",
eventStopImmediatePropagation: true,
container: inputRef,
},
);// Allow repeated key presses to trigger callback (useful for games)
useKey(
"ArrowUp",
(event, key) => {
moveUp();
},
{
eventType: "keydown",
eventRepeat: true,
},
);// Increase threshold for slower typists
useKey(
"a b c",
(event, key) => {
console.log(`Sequence: ${key}`);
},
{
sequenceThreshold: 2000, // 2 seconds between keys
},
);
// Increase threshold for combination keys
useKey(
"a+b",
(event, key) => {
console.log(`Combination: ${key}`);
},
{
combinationThreshold: 1000, // 1 second window for simultaneous press
},
);See the Cube Game Example for a full implementation:
cd examples/cube-the-game
npm install
npm run devThis example demonstrates:
- Combined mouse, touch, and keyboard input
npm run buildnpm run devnpm run lintSee LICENSE file for details.
