@domecs/input
v2.0.0
Published
DOMECS input collector: keyboard, pointer, focus, gamepad → per-tick InputSnapshot (SPEC §6).
Readme
@domecs/input
Browser input collector plugin for DOMECS.
@domecs/input listens to keyboard, pointer, wheel, focus, and gamepad state and
publishes a per-tick InputSnapshot to world.input.
Status: v2.x — API-stable (semver honored; product contract still hardening).
Install
npm install @domecs/core @domecs/inputQuick start
import { createWorld } from '@domecs/core'
import { createInputPlugin } from '@domecs/input'
const world = createWorld()
const stage = document.querySelector<HTMLElement>('#stage')!
world.use(createInputPlugin({
keyTarget: document,
pointerTarget: stage,
wheelTarget: stage,
preventDefaultKeys: true,
}))
world.system('controls', { schedule: 'tick' }, ({ input }) => {
if (input.keyDelta.pressed.has('Space')) {
console.log('jump')
}
if (input.keys.has('ArrowLeft')) {
console.log('move left')
}
if (input.pointer.buttons !== 0) {
console.log('pointer down at', input.pointer.x, input.pointer.y)
}
})
world.startLoop()Snapshot shape
The plugin writes an InputSnapshot at tick start:
keys— currently held keyboardKeyboardEvent.codevalues.keyDelta.pressed— keys pressed since the previous snapshot.keyDelta.released— keys released since the previous snapshot.
keyDeltais render-tick-scoped (O-35). Edges live for exactly one collector publish (one render tick understartLoop). Afixedsystem at 4 Hz will miss most presses, and when one frame runs severalfixedsub-steps, each of them sees the same edges — one press fires once per sub-step (O-59). Put edge-triggered input on aticksystem (or read heldkeyswhen you only need “is down”). Astep(0)heartbeat publishes no edges or deltas and keeps them for the next full tick (O-57).
mods—ctrl,alt,shift,metamodifier state.pointer— pointer position, buttons, movement delta, wheel delta, and reservedenteredentity ids. The collector currently leavesenteredempty.gamepads— current gamepad axes/buttons when available and enabled.focus— active element tag and whether it consumes keyboard input.
Pointer coordinates are raw browser client coordinates (clientX/clientY),
even with an element pointerTarget; subtract its getBoundingClientRect()
for local coordinates. The first move after install sets the position without
a delta; under pointer lock, deltas come from movementX/movementY. The
default textInputSelector (TEXT_INPUT_SELECTOR) counts text-like inputs,
textarea, select and contenteditable elements as consuming keys, and
not checkboxes, radios, ranges or buttons.
Options
createInputPlugin({
keyTarget?: Document | HTMLElement
pointerTarget?: Document | HTMLElement
wheelTarget?: Document | HTMLElement
clearOnBlur?: boolean
textInputSelector?: string
pollGamepads?: boolean
preventDefaultKeys?: boolean
})Defaults:
keyTarget:documentpointerTarget:documentwheelTarget:pointerTargetclearOnBlur:truetextInputSelector:input,textarea,[contenteditable="true"]pollGamepads:truewhennavigator.getGamepadsexistspreventDefaultKeys:false
Key presses made while an element matching textInputSelector has focus pass
through to it; releases are always processed, so a key pressed in the game and
released in a text field does not stay held. With clearOnBlur (the default),
held keys and pointer buttons are released when the window loses focus, and —
with an element keyTarget — when focus leaves that element. pointercancel
and lostpointercapture clear pointer buttons. Teardown resets world.input
to an empty snapshot.
When no browser globals exist, the default DOM targets are absent. The plugin is still safe to import and install; it registers no event listeners, does not poll gamepads, and publishes empty snapshots on each tick.
Headless tests
For deterministic tests without browser events, you can bypass this plugin and set input directly:
world.setInput(snapshot)
world.stepOnce()Related packages
@domecs/core— core ECS runtime.@domecs/dom— retained-mode DOM renderer.
License
MIT
