An open API service indexing awesome lists of open source software.

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

Awesome Lists containing this project

README

          

# react-marauders-path

[![License](https://img.shields.io/badge/license-%20%20GNU%20GPLv3%20-green.svg)](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.

![react-marauders-path](./docs/images/logotype.png)

## 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.