Skip to content

@gameable/input ​

Interfaces ​

ActionMap ​

A resolved action map. Every method reads the bound state, or the state passed explicitly as the last argument, and takes either an action name or the handle ActionMap.handle returns.

Example ​

ts
import { createActionMap, createInputState } from 'gameable/input';

const actions = createActionMap({ jump: ['Space'] }, createInputState());
const jump = actions.handle('jump');
console.log(actions.down('jump'), actions.down(jump)); // false false

Properties ​

names ​
ts
readonly names: readonly string[];

Action names, in declaration order.

Methods ​

axis2() ​
ts
axis2(
   action, 
   state?, 
   out?
): [number, number];

Read a two-axis action into a reusable tuple.

Parameters ​
ParameterType
actionActionRef
state?InputState
out?[number, number]
Returns ​

[number, number]

bind() ​
ts
bind(state): void;

Point the map at a different state, e.g. a replayed one.

Parameters ​
ParameterType
stateInputState
Returns ​

void

down() ​
ts
down(action, state?): boolean;

Is any binding of this action held?

Parameters ​
ParameterType
actionActionRef
state?InputState
Returns ​

boolean

handle() ​
ts
handle(name): number;

The stable handle for an action: its 1-based position in names.

Resolve it once, at startup, and pass it to the readers instead of the name. Unknown names throw here rather than sixty times a second.

Parameters ​
ParameterTypeDescription
namestringThe declared action name.
Returns ​

number

A small integer, 1..names.length.

pressed() ​
ts
pressed(action, state?): boolean;

Did any binding of this action go down this frame?

Parameters ​
ParameterType
actionActionRef
state?InputState
Returns ​

boolean

released() ​
ts
released(action, state?): boolean;

Did any binding of this action come up this frame?

Parameters ​
ParameterType
actionActionRef
state?InputState
Returns ​

boolean


Axis2Spec ​

A two-axis action: four buttons, and optionally a gamepad stick.

Example ​

ts
import { type Axis2Spec } from 'gameable/input';

const move: Axis2Spec = { axis2: ['A', 'D', 'S', 'W'], gamepadAxes: [0, 1] };
console.log(move.axis2[3]); // 'W'

Properties ​

axis2 ​
ts
axis2: readonly [string, string, string, string];

Buttons driving [-X, +X, -Y, +Y], e.g. ['A', 'D', 'S', 'W'].

deadzone? ​
ts
optional deadzone?: number;

Radial deadzone applied to the stick, 0..1. Default 0.15.

gamepadAxes? ​
ts
optional gamepadAxes?: readonly [number, number];

Gamepad axis indexes for [x, y], e.g. [0, 1] for the left stick.

invertGamepadY? ​
ts
optional invertGamepadY?: boolean;

Negate the gamepad Y axis so pushing the stick up matches W. Sticks report +1 when pushed down, so this defaults to true.


GamepadState ​

One gamepad slot. Slots are preallocated and reused; read connected before trusting buttons or axes.

Example ​

ts
import { createInputState } from 'gameable/input';

const pad = createInputState().gamepads[0];
console.log(pad.index, pad.connected, pad.axes.length); // 0 false 6

Properties ​

axes ​
ts
axes: Float32Array;

Standard mapping: lx, ly, rx, ry, left trigger, right trigger.

buttons ​
ts
buttons: number;

Standard-mapping buttons held, one bit per button index.

connected ​
ts
connected: boolean;

A pad is present in this slot.

index ​
ts
index: number;

navigator.getGamepads() slot this state mirrors.

pressed ​
ts
pressed: number;

Buttons that went down this frame.

released ​
ts
released: number;

Buttons that came up this frame.


InputCapture ​

A live DOM capture. The engine loop drives beginFrame / endFrame once per rendered frame and consume once per fixed step; game code reads InputCapture.state.

Example ​

ts
import { createInputCapture, isDown, keyIndex } from 'gameable/input';

const capture = createInputCapture(window);
capture.beginFrame();
capture.consume(); // one fixed step
console.log(isDown(capture.state, keyIndex('W'))); // false
capture.endFrame();
capture.dispose();

Properties ​

lockDenied ​
ts
readonly lockDenied: boolean;

True when the last pointer-lock request was refused — no user gesture, a sandboxed frame, or the user pressing escape too recently. Cleared by the next InputCapture.requestPointerLock and by a successful lock.

state ​
ts
readonly state: InputState;

The packed state, mutated in place every frame.

Methods ​

beginFrame() ​
ts
beginFrame(): void;

Fold accumulated DOM events into the pending edges and publish the level state — keys held, modifiers, pointer position, gamepad axes. Pending edges and mouse deltas are not published here; they wait for InputCapture.consume.

Returns ​

void

consume() ​
ts
consume(): void;

Take one fixed step's worth of input: publish the pending key, mouse and gamepad edges plus the accumulated mouse deltas into state, then clear them so the next step starts empty.

Call once per fixed simulation step, before the step reads state. The input module does this from its fixedUpdate.

Returns ​

void

dispose() ​
ts
dispose(): void;

Remove every listener and return the state to neutral.

Returns ​

void

endFrame() ​
ts
endFrame(): void;

Clear the published edges and deltas. Unconsumed input is untouched and is still waiting for the next InputCapture.consume.

Returns ​

void

exitPointerLock() ​
ts
exitPointerLock(): void;

Release pointer lock if it is held.

Returns ​

void

requestPointerLock() ​
ts
requestPointerLock(): void;

Ask the browser for pointer lock. Needs a user gesture to succeed.

Returns ​

void


InputCaptureOptions ​

Options for createInputCapture.

Example ​

ts
import { createInputCapture, type InputCaptureOptions } from 'gameable/input';

const options: InputCaptureOptions = { pointerLock: true };
const capture = createInputCapture(window, options);
capture.dispose();

Extended by ​

Properties ​

focused? ​
ts
optional focused?: boolean;

Whether the target starts focused, before any focus/blur event.

Defaults to document.hasFocus() where that exists and to true where it does not. Pass it explicitly in a headless environment: jsdom reports hasFocus() === false for a document nobody clicked, which would leave preventDefault disabled for keys a test dispatches straight at the window.

gamepads? ​
ts
optional gamepads?: boolean;

Poll navigator.getGamepads() each frame. Default true.

gamepadSlots? ​
ts
optional gamepadSlots?: number;

How many gamepad slots to preallocate. Default 4.

onLockDenied? ​
ts
optional onLockDenied?: () => void;

Called when the browser refuses pointer lock — a pointerlockerror, or a rejected requestPointerLock(). InputCapture.lockDenied carries the same answer for code that would rather poll than subscribe.

Returns ​

void

pointerLock? ​
ts
optional pointerLock?: boolean;

Enable pointer lock. A mousedown on the target then requests the lock, and mouse deltas switch to movementX/movementY. Default false.

preventDefault? ​
ts
optional preventDefault?: boolean;

Call preventDefault() on game keys while focused: the scroll keys always, and everything except escape and the F-keys while pointer-locked. Never fires for a browser shortcut (ctrl/meta/alt) or for a key typed into an editable element. Default true.

touch? ​
ts
optional touch?: false | TouchOptions;

Fingers on the screen (touch.ts): on by default with no thumb sticks (every finger is the pointer, a tap is a click, two fingers pinch). Pass the sticks and the thresholds, or false for a page that handles touch itself. The last gamepad slot is the touch pad's while it is on.


InputModule ​

The input module, with the service exposed directly for hosts that do not go through the registry.

Example ​

ts
import { input, type InputModule } from 'gameable/input';

const module: InputModule = input();
console.log(module.id, module.order); // 'input' -100

Extends ​

Properties ​

id ​
ts
readonly id: string;

Unique id. Also the service id when init returns a service.

Inherited from ​

EngineModule.id

order? ​
ts
readonly optional order?: number;

Sort key. Lower runs first; equal keys keep registration order.

Rough convention: input -100, gameplay -50, physics 0, rendering helpers 200. Gameplay runs before physics so that the commands a guest tick emits are simulated by the step that follows rather than the next one.

Inherited from ​

EngineModule.order

service ​
ts
readonly service: InputService | null;

The published service, or null before init and after dispose.

Methods ​

beginFrame() ​
ts
beginFrame(): void;

Snapshot the frame. Safe before init and after dispose.

Returns ​

void

Overrides ​

EngineModule.beginFrame

dispose() ​
ts
dispose(): void;

Release everything this module took.

Returns ​

void

Inherited from ​

EngineModule.dispose

endFrame() ​
ts
endFrame(): void;

Clear this frame's published edges. Safe before init and after dispose.

Returns ​

void

Overrides ​

EngineModule.endFrame

fixedUpdate() ​
ts
fixedUpdate(dt): void;

Hand this fixed step the edges and deltas that have accumulated since the previous one. Safe before init and after dispose.

Parameters ​
ParameterTypeDescription
dtnumberAlways ctx.config.fixedDt; unused.
Returns ​

void

Overrides ​

EngineModule.fixedUpdate

init() ​
ts
init(ctx): InputService;

Create the capture and return the service, which the engine publishes under input. Synchronous: input needs no asynchronous setup.

Parameters ​
ParameterTypeDescription
ctxHostContextThe host context. It has no renderer by design, so the capture falls back to window when none is present.
Returns ​

InputService

The service, also available as InputModule.service.

Overrides ​

EngineModule.init

update()? ​
ts
optional update(dt, alpha): void;

Run once per frame, after the fixed steps and before rendering.

Parameters ​
ParameterTypeDescription
dtnumberClamped wall-clock seconds since the previous frame, scaled by time.timeScale.
alphanumberInterpolation factor in [0, 1) for smoothing transforms.
Returns ​

void

Inherited from ​

EngineModule.update


InputOptions ​

Options for input.

Example ​

ts
import { input, type InputOptions } from 'gameable/input';

const options: InputOptions = { pointerLock: true, actions: { fire: ['LMB'] } };
console.log(input(options).id); // 'input'

Extends ​

Properties ​

actions? ​
ts
optional actions?: Readonly<Record<string, ActionSpec>>;

Action bindings, compiled once during init.

focused? ​
ts
optional focused?: boolean;

Whether the target starts focused, before any focus/blur event.

Defaults to document.hasFocus() where that exists and to true where it does not. Pass it explicitly in a headless environment: jsdom reports hasFocus() === false for a document nobody clicked, which would leave preventDefault disabled for keys a test dispatches straight at the window.

Inherited from ​

InputCaptureOptions.focused

gamepads? ​
ts
optional gamepads?: boolean;

Poll navigator.getGamepads() each frame. Default true.

Inherited from ​

InputCaptureOptions.gamepads

gamepadSlots? ​
ts
optional gamepadSlots?: number;

How many gamepad slots to preallocate. Default 4.

Inherited from ​

InputCaptureOptions.gamepadSlots

onLockDenied? ​
ts
optional onLockDenied?: () => void;

Called when the browser refuses pointer lock — a pointerlockerror, or a rejected requestPointerLock(). InputCapture.lockDenied carries the same answer for code that would rather poll than subscribe.

Returns ​

void

Inherited from ​

InputCaptureOptions.onLockDenied

overlay? ​
ts
optional overlay?: boolean | StickOverlayOptions;

Draw the thumb sticks on the page (stickOverlay.ts) while a thumb is on one: true for the defaults, or the overlay's options. Off by default; a page without sticks (touch.sticks unset) has nothing to draw.

pointerLock? ​
ts
optional pointerLock?: boolean;

Enable pointer lock. A mousedown on the target then requests the lock, and mouse deltas switch to movementX/movementY. Default false.

Inherited from ​

InputCaptureOptions.pointerLock

preventDefault? ​
ts
optional preventDefault?: boolean;

Call preventDefault() on game keys while focused: the scroll keys always, and everything except escape and the F-keys while pointer-locked. Never fires for a browser shortcut (ctrl/meta/alt) or for a key typed into an editable element. Default true.

Inherited from ​

InputCaptureOptions.preventDefault

target? ​
ts
optional target?: HTMLElement | Window;

Element to capture from. Defaults to the renderer's canvas (ctx.renderer.domElement), then to the global window.

touch? ​
ts
optional touch?: false | TouchOptions;

Fingers on the screen (touch.ts): on by default with no thumb sticks (every finger is the pointer, a tap is a click, two fingers pinch). Pass the sticks and the thresholds, or false for a page that handles touch itself. The last gamepad slot is the touch pad's while it is on.

Inherited from ​

InputCaptureOptions.touch


InputService ​

What the module publishes as the input service. init returns it, so the engine registers it under the module id and engine.get('input') resolves to exactly this object.

Example ​

ts
import { input, type InputService } from 'gameable/input';

const module = input({ actions: { jump: ['Space'] } });
const service: InputService | null = module.service;
console.log(service); // null until the engine calls init()

Properties ​

actions ​
ts
readonly actions: ActionMap;

The action map built from options.actions.

lockDenied ​
ts
readonly lockDenied: boolean;

True when the browser refused the last pointer-lock request. Show a "click to play" prompt rather than assuming the mouse is captured.

state ​
ts
readonly state: InputState;

The packed state for the current frame.

Methods ​

consume() ​
ts
consume(): void;

Publish one fixed step's worth of edges and mouse deltas into state.

The module already does this from its own fixedUpdate, which runs at order -100 and therefore before any gameplay module. It is exposed for a host that drives the loop itself and does not register the module.

Returns ​

void

exitPointerLock() ​
ts
exitPointerLock(): void;

Release pointer lock if it is held.

Returns ​

void

requestPointerLock() ​
ts
requestPointerLock(): void;

Ask the browser for pointer lock; needs a user gesture.

Returns ​

void


InputState ​

The whole input block for one frame: the packed form of WIT input-state.

Example ​

ts
import { createInputState, isDown, keyIndex } from 'gameable/input';

const state = createInputState();
console.log(state.keysDown.length, isDown(state, keyIndex('W'))); // 8 false

Properties ​

focused ​
ts
focused: boolean;

The capture target has focus; treat input as neutral when false.

gamepads ​
ts
gamepads: GamepadState[];

Gamepad slots, preallocated and stable. The last one is the touch pad's while a finger is down.

keysDown ​
ts
keysDown: Uint32Array;

Keys held this frame, 256 bits in 8 words.

keysPressed ​
ts
keysPressed: Uint32Array;

Keys that went down this frame.

keysReleased ​
ts
keysReleased: Uint32Array;

Keys that came up this frame.

mods ​
ts
mods: number;

Modifier flags, as Mods bits.

mouse ​
ts
mouse: MouseState;

Pointer state.

touch ​
ts
touch: TouchState;

Fingers on the screen, beside what they mirror into the keys, the pointer block and the touch pad (touch.ts). Host-side only: the guest reads the mirrors.


MouseState ​

Pointer state for one frame. dx/dy are the frame's accumulated movement: movementX/movementY while pointer lock is held, the client-space difference otherwise.

Example ​

ts
import { createInputState } from 'gameable/input';

const { mouse } = createInputState();
console.log(mouse.x, mouse.dx, mouse.locked); // 0 0 false

Properties ​

buttons ​
ts
buttons: number;

Buttons held, as MouseButtons bits.

dx ​
ts
dx: number;

Accumulated X movement for the frame.

dy ​
ts
dy: number;

Accumulated Y movement for the frame.

locked ​
ts
locked: boolean;

Pointer lock is currently held by the capture target.

pressed ​
ts
pressed: number;

Buttons that went down this frame.

released ​
ts
released: number;

Buttons that came up this frame.

wheel ​
ts
wheel: number;

Accumulated wheel delta for the frame, in lines; positive is down.

x ​
ts
x: number;

Client-space X in CSS pixels.

y ​
ts
y: number;

Client-space Y in CSS pixels.


StickOverlay ​

A stick overlay.

Properties ​

element ​
ts
readonly element: HTMLElement;

The two rings' root.

Methods ​

dispose() ​
ts
dispose(): void;

Take the rings off the page.

Returns ​

void

update() ​
ts
update(touch): void;

Move the rings to where the sticks are; hide the ones at rest. Once a frame.

Parameters ​
ParameterType
touchTouchState
Returns ​

void


StickOverlayOptions ​

Options for createStickOverlay.

Properties ​

knob? ​
ts
optional knob?: number;

The knob's diameter, CSS pixels. Default 44.

parent? ​
ts
optional parent?: HTMLElement;

The element the rings go in. Defaults to the document's body.

reach? ​
ts
optional reach?: number;

The ring's radius, CSS pixels; the same as the touch layer's reach. Default 56.

zIndex? ​
ts
optional zIndex?: number;

Stacking order. Default 20.


TouchArea ​

The target's box in client space, as getBoundingClientRect gives it.

Properties ​

height ​
ts
height: number;
left ​
ts
left: number;
top ​
ts
top: number;
width ​
ts
width: number;

TouchLayer ​

The touch layer, driven by the capture.

Properties ​

pad ​
ts
readonly pad: GamepadState;

The pad slot the layer drives (its index is TOUCH_PAD_INDEX while connected).

state ​
ts
readonly state: TouchState;

The state the capture publishes as state.touch.

Methods ​

consume() ​
ts
consume(): void;

Publish this step's deltas (the pointer's, the pinch's, the turn's) and clear them.

Returns ​

void

down() ​
ts
down(
   event, 
   area, 
   now
): boolean;

A finger landed. Returns false for a pointer the layer ignores (a mouse).

Parameters ​
ParameterTypeDescription
eventTouchPointerEventThe pointer event.
areaTouchAreaThe target's box in client space (its halves place the sticks, its top strip the band).
nownumberMilliseconds, the same clock as tick.
Returns ​

boolean

endFrame() ​
ts
endFrame(): void;

Clear the published deltas at the end of a frame.

Returns ​

void

mouseDown() ​
ts
mouseDown(
   x, 
   y, 
   now
): void;

The mouse's left button went down: a click, a hold and a drag are the same gestures a finger makes, so a game binds Tap once. The pointer block is the mouse handlers' own; this only names the gesture.

Parameters ​
ParameterTypeDescription
xnumberClient-space x.
ynumberClient-space y.
nownumberMilliseconds.
Returns ​

void

mouseMove() ​
ts
mouseMove(x, y): void;

The mouse moved with the left button down.

Parameters ​
ParameterType
xnumber
ynumber
Returns ​

void

mouseUp() ​
ts
mouseUp(
   x, 
   y, 
   now
): void;

The mouse's left button came up.

Parameters ​
ParameterType
xnumber
ynumber
nownumber
Returns ​

void

move() ​
ts
move(event): boolean;

A finger moved.

Parameters ​
ParameterType
eventTouchPointerEvent
Returns ​

boolean

releaseAll() ​
ts
releaseAll(): void;

Every finger let go: focus lost, the page hidden, the capture disposed.

Returns ​

void

tick() ​
ts
tick(now): boolean;

Time passes: holds mature. Returns true when something changed.

Parameters ​
ParameterType
nownumber
Returns ​

boolean

up() ​
ts
up(
   event, 
   now, 
   cancelled
): boolean;

A finger lifted (cancelled: the browser took it, so never a tap).

Parameters ​
ParameterType
eventTouchPointerEvent
nownumber
cancelledboolean
Returns ​

boolean


TouchOptions ​

Options for the touch layer, given as touch to createInputCapture or input().

Example ​

ts
import { input, type TouchOptions } from 'gameable/input';

const touch: TouchOptions = { sticks: 'both', reach: 56 };
console.log(input({ touch }).id); // 'input'

Properties ​

deadzone? ​
ts
optional deadzone?: number;

Radial deadzone of a stick, as a share of the reach. Default 0.15.

doubleTapMs? ​
ts
optional doubleTapMs?: number;

A second tap within this many milliseconds and doubleTapSlop pixels of the first is a double tap. Default 300.

doubleTapSlop? ​
ts
optional doubleTapSlop?: number;

See doubleTapMs. Default 40.

holdMs? ​
ts
optional holdMs?: number;

A finger held still this long (milliseconds) is a hold. Default 500.

reach? ​
ts
optional reach?: number;

The stick's reach from its centre to the rim, in CSS pixels. Default 56.

sticks? ​
ts
optional sticks?: "none" | "left" | "both";

Which thumb sticks the screen has: 'none' (every finger is the pointer; the default), 'left' (a finger landing on the left half moves a stick, the right half is the pointer) or 'both' (a left stick for movement and a right one for the camera, the right thumb also being the pointer).

tapMs? ​
ts
optional tapMs?: number;

A touch shorter than this (milliseconds) that never moved is a tap. Default 350.

tapSlop? ​
ts
optional tapSlop?: number;

How far a finger may wander and still be a tap, in CSS pixels. Default 12.

topBand? ​
ts
optional topBand?: number;

A strip at the top of the target, as a share of its height, where a finger landing is nobody's (a page's own bar sits there). Default 0.


TouchPointerEvent ​

A pointer event as the layer reads it: the four fields, so a test can hand over a plain object.

Properties ​

clientX ​
ts
clientX: number;
clientY ​
ts
clientY: number;
pointerId ​
ts
pointerId: number;
pointerType ​
ts
pointerType: string;

TouchSink ​

How the touch layer writes into the capture. The capture binds these to its own live mirrors; a test binds them to fakes.

Methods ​

pointerDown() ​
ts
pointerDown(x, y): void;

The pointer finger went down (the left button, at this position).

Parameters ​
ParameterType
xnumber
ynumber
Returns ​

void

pointerMove() ​
ts
pointerMove(x, y): void;

The pointer finger moved; the capture accumulates the delta itself.

Parameters ​
ParameterType
xnumber
ynumber
Returns ​

void

pointerUp() ​
ts
pointerUp(): void;

The pointer finger came up, or stopped being the pointer.

Returns ​

void

pulseKey() ​
ts
pulseKey(index): void;

A one-step pulse of a pseudo-key: pressed and released in the same step.

Parameters ​
ParameterType
indexnumber
Returns ​

void

setKey() ​
ts
setKey(index, down): void;

Set or clear a level pseudo-key.

Parameters ​
ParameterType
indexnumber
downboolean
Returns ​

void

wheel() ​
ts
wheel(lines): void;

Wheel lines from a pinch; negative zooms in.

Parameters ​
ParameterType
linesnumber
Returns ​

void


TouchState ​

Everything the touch layer knows this step, beside what it mirrors into the keys, the pointer block and the touch pad.

Example ​

ts
import { createInputState } from 'gameable/input';

const { touch } = createInputState();
console.log(touch.fingers, touch.pinch, touch.left.active); // 0 1 false

Properties ​

centreX ​
ts
centreX: number;
centreY ​
ts
centreY: number;
dx ​
ts
dx: number;

The pointer finger's movement this step.

dy ​
ts
dy: number;
fingers ​
ts
fingers: number;

Fingers on the target right now, the sticks' included.

left ​
ts
left: TouchStick;

The thumb sticks.

pinch ​
ts
pinch: number;

Two fingers: the scale this step (1 = no change), the turn this step in radians (counter-clockwise on screen positive), and their centre.

ts
right: TouchStick;
tapX ​
ts
tapX: number;

Where the last tap (or double tap) landed.

tapY ​
ts
tapY: number;
turn ​
ts
turn: number;
x ​
ts
x: number;

The pointer finger's position, client-space CSS pixels (the last known when none).

y ​
ts
y: number;

TouchStick ​

One thumb stick, as the game and a stick overlay read it.

Example ​

ts
import { createInputState } from 'gameable/input';

const { left } = createInputState().touch;
console.log(left.active, left.x, left.y); // false 0 0

Properties ​

active ​
ts
active: boolean;

A thumb is on this stick.

originX ​
ts
originX: number;

Where the stick's centre is, client-space CSS pixels.

originY ​
ts
originY: number;
touchX ​
ts
touchX: number;

Where the thumb is, client-space CSS pixels.

touchY ​
ts
touchY: number;
x ​
ts
x: number;

The stick's push, -1..1, right positive.

y ​
ts
y: number;

The stick's push, -1..1, DOWN positive (a real stick's convention).

Type Aliases ​

ActionBindings ​

ts
type ActionBindings = Readonly<Record<string, ActionSpec>>;

The whole action map, as game code declares it.

Example ​

ts
import { type ActionBindings } from 'gameable/input';

const bindings: ActionBindings = {
  fire: ['LMB', 'GamepadRT'],
  move: { axis2: ['A', 'D', 'S', 'W'], gamepadAxes: [0, 1] },
};
console.log(Object.keys(bindings).length); // 2

ActionRef ​

ts
type ActionRef = string | number;

How a reader names the action to read: the declared name, or the small integer ActionMap.handle minted for it.

Resolving a name costs a Map lookup and a branch; resolving a handle is an array index. Hoist the handle out of the frame body and the read becomes a handful of bit tests with no string work at all.

Example ​

ts
import { createActionMap, createInputState, type ActionRef } from 'gameable/input';

const actions = createActionMap({ jump: ['Space'] }, createInputState());
const jump: ActionRef = actions.handle('jump');
console.log(actions.down(jump)); // false

ActionSpec ​

ts
type ActionSpec = readonly string[] | Axis2Spec;

One action's bindings: a button list, or a two-axis spec.

Variables ​

DEFAULT_GAMEPAD_SLOTS ​

ts
const DEFAULT_GAMEPAD_SLOTS: 4 = 4;

Default number of preallocated gamepad slots.


GAMEPAD_AXES ​

ts
const GAMEPAD_AXES: 6 = 6;

Number of axes a GamepadState carries: lx, ly, rx, ry, lt, rt.


GAMEPAD_BUTTON_INDEX ​

ts
const GAMEPAD_BUTTON_INDEX: Readonly<Record<string, number>>;

Standard-mapping gamepad button indexes, by friendly name. The names follow the Xbox layout because that is what the W3C standard mapping describes; GamepadCross and friends alias the PlayStation spelling.

Example ​

ts
import { GAMEPAD_BUTTON_INDEX } from 'gameable/input';

console.log(GAMEPAD_BUTTON_INDEX.GamepadA); // 0
console.log(GAMEPAD_BUTTON_INDEX.GamepadRT); // 7

KEY_COUNT ​

ts
const KEY_COUNT: 256 = 256;

Number of bits — and therefore key slots — in a packed key bitset.


KEY_INDEX ​

ts
const KEY_INDEX: Readonly<Record<string, number>>;

Index of every known key, by KeyboardEvent.code.

Lookup is exact and case-sensitive; use keyIndex to accept friendly aliases such as 'W' or 'LMB'.

Example ​

ts
import { KEY_INDEX } from 'gameable/input';

console.log(KEY_INDEX.KeyA); // 0
console.log(KEY_INDEX.Space); // 64

KEY_NAMES ​

ts
const KEY_NAMES: readonly string[];

The inverse of KEY_INDEX: 256 slots, '' where nothing is assigned.

Example ​

ts
import { KEY_NAMES } from 'gameable/input';

console.log(KEY_NAMES[0]); // 'KeyA'
console.log(KEY_NAMES.length); // 256

KEY_WORDS ​

ts
const KEY_WORDS: number;

Number of u32 words a packed key bitset occupies.


Mods ​

ts
const Mods: Readonly<{
  Alt: number;
  CapsLock: number;
  Ctrl: number;
  Meta: number;
  NumLock: number;
  Shift: number;
}>;

Keyboard modifier bits, in input-mods flag order.

Example ​

ts
import { createInputState, Mods } from 'gameable/input';

const state = createInputState();
state.mods = Mods.Shift | Mods.Ctrl;
console.log((state.mods & Mods.Shift) !== 0); // true

MouseButtons ​

ts
const MouseButtons: Readonly<{
  Back: number;
  Forward: number;
  Left: number;
  Middle: number;
  Right: number;
}>;

Mouse button bits, matching mouse-state.buttons in the WIT contract and the DOM MouseEvent.button numbering.

Example ​

ts
import { createInputState, MouseButtons } from 'gameable/input';

const state = createInputState();
state.mouse.buttons = MouseButtons.Left;
console.log((state.mouse.buttons & MouseButtons.Left) !== 0); // true

PACKAGE ​

ts
const PACKAGE: "@gameable/input";

Package identity marker for gameable/input.

Example ​

ts
import { PACKAGE } from 'gameable/input';

console.log(PACKAGE); // 'gameable/input'

POINTER_BASE ​

ts
const POINTER_BASE: 248 = 248;

First index of the reserved pointer block. Indexes below this are real KeyboardEvent.code values; POINTER_BASE + n is mouse button n.


POINTER_BUTTON_MAX ​

ts
const POINTER_BUTTON_MAX: 4 = 4;

Highest mouse button the pointer block can represent (0..4).


TOUCH_KEYS ​

ts
const TOUCH_KEYS: Readonly<{
  doubleTap: "DoubleTap";
  drag: "Drag";
  hold: "Hold";
  pinch: "Pinch";
  stickLeft: "StickLeft";
  stickRight: "StickRight";
  tap: "Tap";
}>;

The gesture pseudo-keys, by gesture.

Example ​

ts
import { keyIndex, TOUCH_KEYS } from 'gameable/input';

console.log(TOUCH_KEYS.tap, keyIndex(TOUCH_KEYS.tap)); // 'Tap' 128

TOUCH_PAD_AXIS_PINCH ​

ts
const TOUCH_PAD_AXIS_PINCH: 4 = 4;

This step's pinch as log2 of the scale, and turn in radians.


TOUCH_PAD_AXIS_TURN ​

ts
const TOUCH_PAD_AXIS_TURN: 5 = 5;

TOUCH_PAD_INDEX ​

ts
const TOUCH_PAD_INDEX: 255 = 255;

The index of the touch pad in input-state.gamepads: the virtual gamepad the host's touch layer connects while a finger is on the screen. Its axes are the two thumb sticks (lx, ly, rx, ry, the standard mapping's order, +y pushed down), then this step's pinch (log2 of the scale) and turn (radians). A real gamepad never reports this index.

Functions ​

axis2() ​

ts
function axis2(
   state, 
   negX, 
   posX, 
   negY, 
   posY, 
   out?
): [number, number];

Read four keys as one clamped 2D axis: x = posX - negX, y = posY - negY.

The result is written into a reusable tuple. Consume it before the next call, or pass your own out — this keeps the per-frame allocation count at zero, which hard rule 2 requires of anything on the update path.

Parameters ​

ParameterTypeDefault valueDescription
stateInputStateundefinedThe state to read.
negXnumberundefinedKey index driving -X, e.g. keyIndex('A').
posXnumberundefinedKey index driving +X, e.g. keyIndex('D').
negYnumberundefinedKey index driving -Y, e.g. keyIndex('S').
posYnumberundefinedKey index driving +Y, e.g. keyIndex('W').
out[number, number]AXIS2_SCRATCHTuple to write into; defaults to a shared scratch tuple.

Returns ​

[number, number]

out, holding the axis values, each in -1..1.

Example ​

ts
import { axis2, createInputState, keyIndex, setBit } from 'gameable/input';

const state = createInputState();
setBit(state.keysDown, keyIndex('W'), true);
const [x, y] = axis2(state, keyIndex('A'), keyIndex('D'), keyIndex('S'), keyIndex('W'));
console.log(x, y); // 0 1

clearEdges() ​

ts
function clearEdges(state): void;

Clear every single-frame edge: key press/release bitsets, mouse edges and deltas, gamepad edges. The loop calls this from endFrame.

Parameters ​

ParameterTypeDescription
stateInputStateThe state to clear, in place.

Returns ​

void

Example ​

ts
import { clearEdges, createInputState } from 'gameable/input';

const state = createInputState();
state.mouse.dx = 12;
clearEdges(state);
console.log(state.mouse.dx); // 0

createActionMap() ​

ts
function createActionMap(bindings, state?): ActionMap;

Compile a binding declaration into an ActionMap.

Unknown binding spellings throw here, at startup, rather than silently doing nothing sixty times a second.

Parameters ​

ParameterTypeDescription
bindingsActionBindingsThe action declaration.
state?InputStateState the map reads by default; may be supplied later with ActionMap.bind or per call.

Returns ​

ActionMap

The compiled map.

Example ​

ts
import { createActionMap, createInputState, keyIndex, setBit } from 'gameable/input';

const state = createInputState();
const actions = createActionMap(
  {
    fire: ['LMB', 'GamepadRT'],
    jump: ['Space', 'GamepadA'],
    move: { axis2: ['A', 'D', 'S', 'W'], gamepadAxes: [0, 1] },
  },
  state,
);
const move = actions.handle('move');

setBit(state.keysDown, keyIndex('D'), true);
console.log(actions.axis2(move)); // [1, 0]

createInputCapture() ​

ts
function createInputCapture(target, options?): InputCapture;

Attach DOM listeners to target and collect input into a packed state.

Keyboard, focus and gamepad-connection events are attached to the owning window (a canvas only receives key events when it is focusable); mouse, wheel and context-menu events are attached to target itself; pointerlockchange is attached to the owning document.

Parameters ​

ParameterTypeDescription
targetHTMLElement | WindowThe element that owns the pointer — usually the canvas — or a window.
optionsInputCaptureOptionsSee InputCaptureOptions.

Returns ​

InputCapture

A live capture; call dispose() when the engine shuts down.

Example ​

ts
import { createInputCapture, keyIndex, wasPressed } from 'gameable/input';

const capture = createInputCapture(window, { pointerLock: true });
// Resolve the index once, never inside the frame body.
const SPACE = keyIndex('Space');

function frame(): void {
  capture.beginFrame();
  capture.consume(); // one fixed step's worth of edges
  if (wasPressed(capture.state, SPACE)) console.log('jump');
  capture.endFrame();
}

frame();
capture.dispose();

createInputState() ​

ts
function createInputState(gamepadSlots?): InputState;

Allocate a zeroed InputState. This is the only allocation the input pipeline performs; everything afterwards mutates it in place.

Parameters ​

ParameterTypeDefault valueDescription
gamepadSlotsnumberDEFAULT_GAMEPAD_SLOTSHow many gamepad slots to preallocate.

Returns ​

InputState

A fresh, neutral input state.

Example ​

ts
import { createInputState } from 'gameable/input';

const state = createInputState();
console.log(state.gamepads.length, state.focused); // 4 false

createStickOverlay() ​

ts
function createStickOverlay(options?): StickOverlay;

Draw the thumb sticks on the page.

Parameters ​

ParameterTypeDescription
optionsStickOverlayOptionsSee StickOverlayOptions.

Returns ​

StickOverlay

The overlay; call update(state.touch) once a frame.

Example ​

ts
import { createInputState, createStickOverlay } from 'gameable/input';

const overlay = createStickOverlay({ reach: 56 });
overlay.update(createInputState().touch);
overlay.dispose();

createTouchLayer() ​

ts
function createTouchLayer(
   sink, 
   pad, 
   touch, 
   options?
): TouchLayer;

Create the touch layer.

Parameters ​

ParameterTypeDescription
sinkTouchSinkWhere the layer writes keys, the pointer block and the wheel.
padGamepadStateThe gamepad slot the layer drives (the capture's last one).
touchTouchStateThe state to fill (the capture's state.touch).
optionsTouchOptionsSee TouchOptions.

Returns ​

TouchLayer

The layer.

Example ​

ts
import { createInputState, createTouchLayer, type TouchSink } from 'gameable/input';

const state = createInputState();
const sink: TouchSink = {
  setKey: () => undefined,
  pulseKey: () => undefined,
  pointerDown: () => undefined,
  pointerMove: () => undefined,
  pointerUp: () => undefined,
  wheel: () => undefined,
};
const layer = createTouchLayer(sink, state.gamepads[3], state.touch, { sticks: 'both' });
const area = { left: 0, top: 0, width: 800, height: 600 };
layer.down({ pointerId: 1, pointerType: 'touch', clientX: 100, clientY: 400 }, area, 0);
console.log(state.touch.left.active); // true

createTouchState() ​

ts
function createTouchState(): TouchState;

A neutral touch state. createInputState calls this, so every state has one.

Returns ​

TouchState

The state, all fingers up.

Example ​

ts
import { createTouchState } from 'gameable/input';

console.log(createTouchState().fingers); // 0

gamepadButtonIndex() ​

ts
function gamepadButtonIndex(name): number;

Resolve a gamepad binding name to a standard-mapping button index.

Parameters ​

ParameterTypeDescription
namestringA Gamepad* name from GAMEPAD_BUTTON_INDEX.

Returns ​

number

The button index, or -1 when the name is not a gamepad button.

Example ​

ts
import { gamepadButtonIndex } from 'gameable/input';

gamepadButtonIndex('GamepadA'); // 0
gamepadButtonIndex('KeyW'); // -1

input() ​

ts
function input(options?): InputModule;

Create the input EngineModule.

beginFrame folds the frame's DOM events into the capture's pending edges and publishes the level state; fixedUpdate hands one simulation step the edges and mouse deltas that have accumulated since the previous step; endFrame clears the published edges. All three delegate to the capture and allocate nothing.

Parameters ​

ParameterTypeDescription
optionsInputOptionsSee InputOptions.

Returns ​

InputModule

The module; register it with the engine.

Example ​

ts
import { input } from 'gameable/input';

const module = input({
  pointerLock: true,
  actions: { fire: ['LMB', 'GamepadRT'], move: { axis2: ['A', 'D', 'S', 'W'] } },
});

console.log(module.id); // 'input'

isDown() ​

ts
function isDown(state, index): boolean;

Is the key at index held this frame?

A negative index (what keyIndex returns for an unknown name) is always false, so an unresolvable binding is dead rather than fatal.

Parameters ​

ParameterTypeDescription
stateInputStateThe state to read.
indexnumberA frozen key index.

Returns ​

boolean

True when the key is down.

Example ​

ts
import { createInputState, isDown, keyIndex } from 'gameable/input';

const state = createInputState();
state.keysDown[keyIndex('W') >> 5] |= 1 << (keyIndex('W') & 31);
console.log(isDown(state, keyIndex('W'))); // true

isFingerPointer() ​

ts
function isFingerPointer(pointerType): boolean;

Is this pointer a finger or a pen? A mouse keeps its own events.

Parameters ​

ParameterTypeDescription
pointerTypestringThe event's pointerType.

Returns ​

boolean

True for a touch or a pen.


keyIndex() ​

ts
function keyIndex(nameOrCode): number;

Resolve a KeyboardEvent.code, a friendly alias or a mouse-button name to its frozen bit index.

Accepted spellings, in order: the exact code ('KeyW', 'Space'), a single letter or digit ('W', '7'), a short name ('Esc', 'Ctrl', 'Up'), a mouse button ('LMB', 'RMB', 'Mouse3'). Gamepad bindings are not keys — see createActionMap for those.

Parameters ​

ParameterTypeDescription
nameOrCodestringThe code or alias to resolve.

Returns ​

number

The bit index, or -1 when nothing matches.

Example ​

ts
import { keyIndex } from 'gameable/input';

keyIndex('KeyW'); // 22
keyIndex('W'); // 22, the same key
keyIndex('LMB'); // 248, the left mouse button
keyIndex('nonsense'); // -1

keyIndex2() ​

ts
function keyIndex2(nameOrCode): number;

The second index of an alias that names a left/right pair, so a binding of 'Shift' covers both shift keys.

Parameters ​

ParameterTypeDescription
nameOrCodestringThe code or alias to resolve.

Returns ​

number

The second bit index, or -1 when the name maps to one key only.

Example ​

ts
import { keyIndex, keyIndex2, KEY_NAMES } from 'gameable/input';

console.log(KEY_NAMES[keyIndex('Shift')]); // 'ShiftLeft'
console.log(KEY_NAMES[keyIndex2('Shift')]); // 'ShiftRight'
console.log(keyIndex2('KeyW')); // -1

pointerKeyIndex() ​

ts
function pointerKeyIndex(button): number;

The key index that mirrors a DOM MouseEvent.button number.

Parameters ​

ParameterTypeDescription
buttonnumberMouseEvent.button: 0 left, 1 middle, 2 right, 3 back, 4 forward.

Returns ​

number

The mirrored key index, or -1 for a button outside the block.

Example ​

ts
import { pointerKeyIndex } from 'gameable/input';

pointerKeyIndex(0); // 248
pointerKeyIndex(9); // -1

resetInputState() ​

ts
function resetInputState(state): void;

Return the state to neutral: nothing held, no edges, no deltas. Capture calls this on blur so a key held when the window loses focus does not stick down forever.

Parameters ​

ParameterTypeDescription
stateInputStateThe state to reset, in place.

Returns ​

void

Example ​

ts
import { createInputState, isDown, keyIndex, resetInputState, setBit } from 'gameable/input';

const state = createInputState();
setBit(state.keysDown, keyIndex('W'), true);
resetInputState(state);
console.log(isDown(state, keyIndex('W'))); // false

resetTouchState() ​

ts
function resetTouchState(touch): void;

Put a touch state back to neutral, in place.

Parameters ​

ParameterTypeDescription
touchTouchStateThe state to reset.

Returns ​

void

Example ​

ts
import { createTouchState, resetTouchState } from 'gameable/input';

const touch = createTouchState();
touch.fingers = 2;
resetTouchState(touch);
console.log(touch.fingers); // 0

setBit() ​

ts
function setBit(
   words, 
   index, 
   value
): void;

Set or clear one bit in a packed key bitset. Exported for capture and for tests that hand-build a state; game code reads, it does not write.

Parameters ​

ParameterTypeDescription
wordsUint32ArrayThe eight-word bitset.
indexnumberBit index; out-of-range indexes are ignored.
valuebooleanTrue to set the bit, false to clear it.

Returns ​

void

Example ​

ts
import { createInputState, isDown, keyIndex, setBit } from 'gameable/input';

const state = createInputState();
setBit(state.keysDown, keyIndex('KeyA'), true);
console.log(isDown(state, keyIndex('KeyA'))); // true

wasPressed() ​

ts
function wasPressed(state, index): boolean;

Did the key at index go down this frame?

Parameters ​

ParameterTypeDescription
stateInputStateThe state to read.
indexnumberA frozen key index.

Returns ​

boolean

True on the single frame the key went down.

Example ​

ts
import { createInputState, keyIndex, wasPressed } from 'gameable/input';

const state = createInputState();
console.log(wasPressed(state, keyIndex('Space'))); // false

wasReleased() ​

ts
function wasReleased(state, index): boolean;

Did the key at index come up this frame?

Parameters ​

ParameterTypeDescription
stateInputStateThe state to read.
indexnumberA frozen key index.

Returns ​

boolean

True on the single frame the key came up.

Example ​

ts
import { createInputState, keyIndex, wasReleased } from 'gameable/input';

const state = createInputState();
console.log(wasReleased(state, keyIndex('Space'))); // false