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'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
}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
}interface KeyRecorderOptions {
onRecord?: (binding: string) => void
onStart?: () => void
onStop?: () => void
}KeyStateSnapshot
interface KeyStateSnapshot {
pressed: ReadonlySet<string>
hasModifier: boolean
}interface KeyStateSnapshot {
pressed: ReadonlySet<string>
hasModifier: boolean
}KeyRecorder
constructor(options?)
new KeyRecorder(options?: KeyRecorderOptions)new KeyRecorder(options?: KeyRecorderOptions)The callbacks are optional. onRecord fires every time the user presses a non-modifier key while holding modifiers.
start(target?)
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): voidrecorder.start(target?: HTMLElement | Document): voidWhile 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(): voidrecorder.stop(): voidgetState()
Get the current recorder state.
recorder.getState(): KeyRecorderStaterecorder.getState(): KeyRecorderStatereset()
Clear the recorded binding without stopping.
recorder.reset(): voidrecorder.reset(): voiddestroy()
Stop recording and clear all state. Call this on cleanup.
recorder.destroy(): voidrecorder.destroy(): voidHow 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'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): voidtracker.attach(target?: HTMLElement | Document): voiddetach()
tracker.detach(): voidtracker.detach(): voidgetSnapshot()
tracker.getSnapshot(): KeyStateSnapshottracker.getSnapshot(): KeyStateSnapshotisKeyPressed(key)
tracker.isKeyPressed(key: string): booleantracker.isKeyPressed(key: string): booleandestroy()
tracker.destroy(): voidtracker.destroy(): voidExample:
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)
}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.
React hook: useKeyRecorder
See the React API for the useKeyRecorder hook that wraps KeyRecorder with React state management.