Skip to content

msobiecki/react-marauders-path

Repository files navigation

react-marauders-path

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

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

npm install @msobiecki/react-marauders-path

Quick 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 for
  • callback: (event: KeyboardEvent, key: string) => void | boolean - Called when a key event occurs
  • options?: 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 occurs
  • options?: 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 occurs
  • options?: 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 occurs
  • options?: 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 from gesture
  • options? - Options type is inferred from gesture

Swipe-only option:

  • direction?: SwipeDirection - Optional direction for useGesture("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 recognized
  • options?: 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 recognized
  • options?: 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 completes
  • options?: 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 listen
  • callback: (event: PointerEvent, direction: SwipeDirection, data: SwipeData) => void | boolean - Called when a swipe event occurs
  • options?: 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 occurs
  • options?: 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 occurs
  • options?: 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 dev

This example demonstrates:

  • Combined mouse, touch, and keyboard input

Development

Build

npm run build

Watch Mode

npm run dev

Lint

npm run lint

License

See LICENSE file for details.

About

๐ŸŽน A lightweight, type-safe React library for handling keyboard and pointer events in a unified way

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors