Recorder
Record key combinations for settings UIs where users customize their shortcuts.
Record key combinations for settings UIs where users customize their shortcuts. Capture what the user presses and output a canonical binding string like
ctrl+shift+k.import { KeyRecorder, KeyStateTracker } from '@gentleduck/vim/recorder'
import type { KeyRecorderState, KeyRecorderOptions, KeyStateSnapshot } from '@gentleduck/vim/recorder'
Overview
The recorder module provides two classes:
- KeyRecorder - captures a full key combination (modifiers + key) and outputs a canonical binding string. Designed for settings UIs where users press a shortcut to define it.
- KeyStateTracker - tracks which keys are currently held down. Simpler than KeyRecorder, no recording logic, just real-time state.
Types
KeyRecorderState
interface KeyRecorderState {
activeKeys: string[] // Currently held key descriptors
recorded: string | null // The final recorded binding, or null
isRecording: boolean // Whether the recorder is listening
}
KeyRecorderOptions
interface KeyRecorderOptions {
onRecord?: (binding: string) => void
onStart?: () => void
onStop?: () => void
}
KeyStateSnapshot
interface KeyStateSnapshot {
pressed: ReadonlySet<string>
hasModifier: boolean
}
KeyRecorder
constructor(options?)
new KeyRecorder(options?: KeyRecorderOptions)
The callbacks are optional. onRecord fires every time the user presses a non-modifier key while holding modifiers.
start(target = document)
Begin recording on the given target. Defaults to document. Calls event.preventDefault() and event.stopPropagation() on all key events while recording, so the user's keystrokes do not trigger other bindings.
recorder.start(target: HTMLElement | Document = document): void
While recording, all key events are captured and prevented from propagating. This means shortcuts like Ctrl+S will not trigger the browser's save dialog during recording.
stop()
Stop recording and clean up event listeners.
recorder.stop(): void
getState()
Get the current recorder state.
recorder.getState(): KeyRecorderState
reset()
Clear the recorded binding without stopping.
recorder.reset(): void
destroy()
Stop recording and clear all state. Call this on cleanup.
recorder.destroy(): void
How it works
When start() is called, the recorder listens for keydown and keyup on the target.
Modifier keys are tracked as they are pressed and released.
When a non-modifier key is pressed, the recorder builds a canonical binding string from the held modifiers + the key.
The onRecord callback fires with the binding string.
If the window loses focus, all held keys are cleared to prevent "stuck" modifiers.
Example:
const recorder = new KeyRecorder({
onRecord: (binding) => {
console.log('User pressed:', binding) // e.g. 'ctrl+shift+k'
recorder.stop()
},
})
recorder.start(document.body)
// User presses Ctrl+Shift+K
// onRecord fires with 'ctrl+shift+k'
KeyStateTracker
A simpler alternative that just tracks which keys are currently down.
attach(target?)
tracker.attach(target?: HTMLElement | Document): void
detach()
tracker.detach(): void
getSnapshot()
tracker.getSnapshot(): KeyStateSnapshot
isKeyPressed(key)
tracker.isKeyPressed(key: string): boolean
destroy()
tracker.destroy(): void
Example:
const tracker = new KeyStateTracker()
tracker.attach(document)
// In a game loop or animation frame:
function update() {
if (tracker.isKeyPressed('w')) moveForward()
if (tracker.isKeyPressed('shift')) sprint()
requestAnimationFrame(update)
}
KeyStateTracker is useful for real-time key state (e.g., game loops or drag interactions), not for shortcut binding. For shortcuts, use KeyRecorder or the Command module.