npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hoard-engine/input

v0.1.0

Published

HOARD engine: framework-independent input bindings, action state, and gesture classification

Readme

@hoard-engine/input

Generic key binding, action dispatch, held-action and touch gesture mechanics for interactive applications. A dependency-free leaf of the HOARD engine — no game actions, framework or browser event wiring.

Pluggable, interoperable, standalone

HOARD engine packages share the same design goals:

  • Pluggable. Adopt a package on its own, or swap it out, without touching the rest of the engine — there is no central engine object to install and no package that owns the others.
  • Interoperable. Packages compose with each other and with consumer code through data: minimal shapes, callback seams and plain values — never through shared state or a common framework.
  • As standalone as possible. Each package is usable as-is in any project, HOARD-based or not; the leaf packages carry no dependencies and import no siblings.

This package meets all three by construction.

Install

npm install @hoard-engine/input

Requires Node 18+ (ESM only).

Usage

import {
  ActionDispatcher,
  HeldActionState,
  KeyBindings,
  classifySwipe,
  dispatchKeyInput,
} from '@hoard-engine/input';

type Action = 'move-up' | 'move-down';
declare const moveHero: (direction: 'north') => void; // injected by the game
const bindings = new KeyBindings<Action>({
  'move-up': ['ArrowUp', 'w'],
  'move-down': ['ArrowDown', 's'],
});

const dispatcher = new ActionDispatcher<Action>();
// The mapping from generic actions to game rules stays with the consumer.
dispatcher.on('move-up', () => moveHero('north')); // consumer policy
dispatchKeyInput({ key: 'ArrowUp' }, bindings, dispatcher);

const held = new HeldActionState<Action>();
const action = bindings.get('ArrowUp');
if (action) {
  held.press('ArrowUp', action);
  dispatcher.execute(action);
}

const direction = classifySwipe(
  { x: 0, y: 0, time: 0 },
  { x: 50, y: 0, time: 120 },
  { minSwipeDistance: 30, maxSwipeTime: 600, enableDiagonals: true },
);

The package never asks for world state, a framework or asset paths. Consumers inject action vocabulary, bindings, callbacks and adapt their browser events to the plain data contracts.

API

| Export | Description | | ---------------------------- | ------------------------------------------------------------------------- | | KeyBindings | Rebindable action-to-key registry with cloned defaults and snapshots | | ActionDispatcher | Registers, clears and executes action handlers; accepts an error callback | | ActionHandler | Handler callback shape for an action type | | ActionErrorHandler | Consumer callback shape for a handler failure | | HeldActionState | Tracks actions held by keys until the final bound key is released | | dispatchKeyInput | Resolves a plain key event, filters editable targets and dispatches action | | isEditableTarget | Checks plain target shapes for text fields with optional extra predicates | | EditableTargetOptions | Opt-in rules for select and content-editable targets | | classifySwipe | Classifies a coordinate/time pair using distance and angle thresholds | | GesturePosition | Plain x/y/time sample used for swipe classification | | SwipeConfig | Distance, time and diagonal-classification settings | | SwipeDirection | Cardinal or diagonal swipe result | | createTouchPanState | Creates the mutable state for a two-finger pan | | getTouchCentroid | Returns the midpoint of the first two touch points | | handleTwoFingerTouchStart | Anchors pan state when exactly two touches begin | | handleTwoFingerTouchMove | Computes pan offsets and movement threshold state | | handleTwoFingerTouchEnd | Ends or reanchors a two-finger pan | | TouchPanState | Plain mutable state for the two-finger pan lifecycle | | TouchPoint | Minimal client-coordinate shape consumed by touch-pan mechanics |

Design notes

  • Key-binding inputs, stored values and returned snapshots are cloned; callers cannot mutate the registry through an alias.
  • Action dispatch iterates a handler snapshot. Registrations changed during dispatch take effect on the next execution.
  • Handler errors are rethrown unless the consumer injects an error callback; logging and recovery policy stay with the consumer.
  • A held action remains active until every key mapped to it is released.
  • Swipe thresholds are inclusive at their minimum distance and maximum elapsed time; diagonal classification uses 45-degree octants.
  • Two-finger pan uses the centroid of the first two points and marks movement only after either axis moves more than three pixels.
  • The package uses plain coordinate and target shapes, not browser classes; DOM listeners and action mappings remain consumer-owned.

Development

npm test          # vitest
npm run check     # tsc --noEmit
npm run lint      # eslint over src
npm run prettier  # prettier --check (fix via prettier:format)
npm run build     # tsc declarations + esbuild ESM bundle to dist/
npm publish       # prepublishOnly runs check + build + test first