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 = 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): voidrecorder.start(target: HTMLElement | Document = 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.