https://github.com/msobiecki/react-marauders-path
🎹 A lightweight, type-safe React library for handling keyboard and pointer events in a unified way
https://github.com/msobiecki/react-marauders-path
events hotkeys keyboard mouse pointer react shortcuts touch
Last synced: 25 days ago
JSON representation
🎹 A lightweight, type-safe React library for handling keyboard and pointer events in a unified way
- Host: GitHub
- URL: https://github.com/msobiecki/react-marauders-path
- Owner: msobiecki
- License: gpl-3.0
- Created: 2024-04-16T14:22:41.000Z (about 2 years ago)
- Default Branch: master
- Last Pushed: 2026-04-06T23:47:50.000Z (about 1 month ago)
- Last Synced: 2026-04-07T01:22:06.163Z (about 1 month ago)
- Topics: events, hotkeys, keyboard, mouse, pointer, react, shortcuts, touch
- Language: TypeScript
- Homepage:
- Size: 7.26 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# react-marauders-path
[](https://github.com/msobiecki/react-marauders-path/blob/master/LICENSE)
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 `requestAnimationFrame` batching 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 `requestAnimationFrame` batching
- 🤏 **Pinch Gesture Handling** - Detect two-finger distance, delta, and scale with pointer type filtering and optional `requestAnimationFrame` batching
## Installation
```bash
npm install @msobiecki/react-marauders-path
```
## Quick Start
### Key Event Hook
#### Single Key Pattern
```typescript
import { useKey } from '@msobiecki/react-marauders-path';
function MyComponent() {
useKey('a', (event, key) => {
console.log(`Pressed ${key}`);
});
return
Press 'a';
}
```
#### Multiple Single Key Patterns
```typescript
useKey(["a", "b", "c"], (event, key) => {
console.log(`Pressed ${key}`);
});
```
#### Key Combination Pattern
```typescript
useKey("a+b", (event, key) => {
console.log(`Pressed ${key}`);
});
```
#### Multiple Key Combination Patterns
```typescript
useKey(["a+b", "c+d"], (event, key) => {
console.log(`Pressed ${key}`);
});
```
#### Key Sequence Pattern
```typescript
useKey("ArrowUp ArrowUp ArrowDown ArrowDown", (event, key) => {
console.log(`Pressed ${key}`);
});
```
#### Multiple Key Sequence Patterns
```typescript
useKey(
["ArrowUp ArrowUp ArrowDown ArrowDown", "ArrowLeft ArrowRight"],
(event, key) => {
console.log(`Pressed ${key}`);
},
);
```
### Pointer Event Hook
```typescript
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
Use pointer input;
}
```
### Mouse Event Hook
```typescript
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
Use mouse input;
}
```
### Mouse Wheel Event Hook
```typescript
import { useWheel } from '@msobiecki/react-marauders-path';
function MyComponent() {
useWheel((event, data) => {
console.log(`Scrolled - X: ${data.deltaX}, Y: ${data.deltaY}`);
});
return
Scroll to interact;
}
```
### Gesture Event Hook
```typescript
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
Tap to interact;
}
```
#### Tap Event Hook
```typescript
import { useTap } from '@msobiecki/react-marauders-path';
function MyComponent() {
useTap((event, data) => {
console.log(`Tapped at X: ${data.x}, Y: ${data.y}`);
});
return
Tap to interact;
}
```
#### Double Tap Event Hook
```typescript
import { useDoubleTap } from '@msobiecki/react-marauders-path';
function MyComponent() {
useDoubleTap((event, data) => {
console.log(`Double tapped at X: ${data.x}, Y: ${data.y}`);
});
return
Double tap to interact;
}
```
#### Press Event Hook
```typescript
import { usePress } from '@msobiecki/react-marauders-path';
function MyComponent() {
usePress((event, data) => {
console.log(`Pressed at X: ${data.x}, Y: ${data.y}`);
});
return
Press and hold to interact;
}
```
#### Swipe Event Hook
```typescript
import { useSwipe } from '@msobiecki/react-marauders-path';
function MyComponent() {
useSwipe('left', (event, direction, data) => {
console.log(`Swiped ${direction} with velocity ${data.velocity}`);
});
return
Swipe left;
}
```
#### Drag Event Hook
```typescript
import { useDrag } from '@msobiecki/react-marauders-path';
function MyComponent() {
useDrag((event, data) => {
console.log(`Dragged by X: ${data.deltaX}, Y: ${data.deltaY}`);
});
return
Drag to interact;
}
```
#### Pinch Event Hook
```typescript
import { usePinch } from '@msobiecki/react-marauders-path';
function MyComponent() {
usePinch((event, data) => {
console.log(`Pinch scale: ${data.scale}, delta: ${data.delta}`);
});
return
Pinch to zoom;
}
```
## 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 for
- `callback: (event: KeyboardEvent, key: string) => void | boolean` - Called when a key event occurs
- `options?: UseKeyOptions` - Optional configuration
**Options:**
```typescript
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; // 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 occurs
- `options?: UsePointerOptions` - Optional configuration
**Options:**
```typescript
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; // Default: window
}
```
**Pointer Data:**
```typescript
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 occurs
- `options?: UseMouseOptions` - Optional configuration
**Options:**
```typescript
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; // Default: window
}
```
**Mouse Data:**
```typescript
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 occurs
- `options?: UseWheelOptions` - Optional configuration
**Options:**
```typescript
interface UseWheelOptions {
eventPassive?: boolean; // Default: true
eventCapture?: boolean; // Default: false
eventOnce?: boolean; // Default: false
eventStopImmediatePropagation?: boolean; // Default: false
container?: RefObject; // Default: window
raf?: boolean; // Default: false - Use requestAnimationFrame for batching
}
```
**Wheel Data:**
```typescript
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 from `gesture`
- `options?` - Options type is inferred from `gesture`
**Swipe-only option:**
- `direction?: SwipeDirection` - Optional direction for `useGesture("swipe", ...)`; defaults to `"both"`
**Example:**
```typescript
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 recognized
- `options?: UseTapOptions` - Optional configuration
**Options:**
```typescript
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; // Default: window
}
```
**Tap Data:**
```typescript
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 recognized
- `options?: UseDoubleTapOptions` - Optional configuration
**Options:**
```typescript
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; // Default: window
}
```
**Double Tap Data:**
```typescript
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 completes
- `options?: UsePressOptions` - Optional configuration
**Options:**
```typescript
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; // Default: window
}
```
**Press Data:**
```typescript
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 listen
- `callback: (event: PointerEvent, direction: SwipeDirection, data: SwipeData) => void | boolean` - Called when a swipe event occurs
- `options?: UseSwipeOptions` - Optional configuration
**Options:**
```typescript
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; // Default: window
}
```
**Swipe Data:**
```typescript
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 occurs
- `options?: UseDragOptions` - Optional configuration
**Options:**
```typescript
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; // Default: window
raf?: boolean; // Default: false - Use requestAnimationFrame for batching
}
```
**Drag Data:**
```typescript
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 occurs
- `options?: UsePinchOptions` - Optional configuration
**Options:**
```typescript
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; // Default: window
raf?: boolean; // Default: false - Use requestAnimationFrame for batching
}
```
**Pinch Data:**
```typescript
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
```typescript
useKey(
"Enter",
(event, key) => {
handleSubmit();
},
{
eventType: "keydown",
eventStopImmediatePropagation: true,
container: inputRef,
},
);
```
### Listening for Key Repeat
```typescript
// 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
```typescript
// 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](./examples/cube-the-game/) for a full implementation:
```bash
cd examples/cube-the-game
npm install
npm run dev
```
This example demonstrates:
- Combined mouse, touch, and keyboard input
## Development
### Build
```bash
npm run build
```
### Watch Mode
```bash
npm run dev
```
### Lint
```bash
npm run lint
```
## License
See [LICENSE](./LICENSE) file for details.