@msobiecki/react-marauders-path
v1.32.0
Published
A lightweight, type-safe React library for handling keyboard and pointer events in a unified way.
Downloads
3,394
Maintainers
Readme
react-marauders-path
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.

Features
- 🎹 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
Installation
npm install @msobiecki/react-marauders-pathQuick Start
Key Event Hook
Single Key Pattern
import { useKey } from '@msobiecki/react-marauders-path';
function MyComponent() {
useKey('a', (event, key) => {
console.log(`Pressed ${key}`);
});
return <div>Press 'a'</div>;
}Multiple Single Key Patterns
useKey(["a", "b", "c"], (event, key) => {
console.log(`Pressed ${key}`);
});Key Combination Pattern
useKey("a+b", (event, key) => {
console.log(`Pressed ${key}`);
});Multiple Key Combination Patterns
useKey(["a+b", "c+d"], (event, key) => {
console.log(`Pressed ${key}`);
});Key Sequence Pattern
useKey("ArrowUp ArrowUp ArrowDown ArrowDown", (event, key) => {
console.log(`Pressed ${key}`);
});Multiple Key Sequence Patterns
useKey(
["ArrowUp ArrowUp ArrowDown ArrowDown", "ArrowLeft ArrowRight"],
(event, key) => {
console.log(`Pressed ${key}`);
},
);Pointer Event Hook
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>;
}Mouse Event Hook
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>;
}Mouse Wheel Event Hook
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>;
}Gesture Event Hook
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>;
}Tap Event Hook
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>;
}Double Tap Event Hook
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>;
}Press Event Hook
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>;
}Swipe Event Hook
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>;
}Drag Event Hook
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>;
}Pinch Event Hook
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>;
}API
useKey(keyEvent, callback, options?)
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
}usePointer(callback, options?)
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
}useMouse(callback, options?)
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
}useWheel(callback, options?)
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
}useGesture(gesture, callback, options?)
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,
},
);useTap(callback, options?)
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
}useDoubleTap(callback, options?)
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
}usePress(callback, options?)
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
}useSwipe(swipe, callback, options?)
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
}useDrag(callback, options?)
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
}usePinch(callback, options?)
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)
}Advanced Examples
Using Options for Event Type and Propagation Control
useKey(
"Enter",
(event, key) => {
handleSubmit();
},
{
eventType: "keydown",
eventStopImmediatePropagation: true,
container: inputRef,
},
);Listening for Key Repeat
// Allow repeated key presses to trigger callback (useful for games)
useKey(
"ArrowUp",
(event, key) => {
moveUp();
},
{
eventType: "keydown",
eventRepeat: true,
},
);Custom Thresholds for Sequences and Combinations
// 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
},
);Examples
Game Controls
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
Development
Build
npm run buildWatch Mode
npm run devLint
npm run lintLicense
See LICENSE file for details.
