Skip to content

API Overview

Core API (Most Users)

SymbolPurposeExecution modeCommon gotcha
createKeymap()Create shortcut manager from an ordered binding arraySyncdispose() is terminal
findShortcutConflicts()Find duplicate and prefix pathsSyncInvalid non-empty input throws
formatShortcut()Format shortcut labelsSyncInvalid input returns ''
tap()Observe chord lifecycle, matches, and disposalSyncObserver failures are swallowed
BindingPer-binding config type (id, shortcut, handler, trigger, when, preventDefault, stopPropagation)Reusing an id replaces its binding

Parser Subpath (@vielzeug/keymap/parse)

Use the parser subpath if you're building keyboard-aware config validators, custom UI, or framework integrations.

SymbolPurposeExecution modeCommon gotcha
parseShortcut()Strictly parse full shortcutSyncEmpty input throws
parseStep()Parse one step without throwingSyncInvalid input returns null
canonicalizeShortcut()Create stable shortcut keySyncInput must already be parsed
matchStep()Test event against parsed stepSyncExtra modifiers prevent a match
detectModKey()Resolve platform primary modifierSyncReturns ctrl without navigator

Errors

SymbolPurposeExecution modeCommon gotcha
KeymapErrorBase Keymap errorSyncIncludes parse and lifecycle errors
KeymapParseErrorStrict parser errorSyncparseStep() never throws it

Package Entry Points

ImportPurpose
@vielzeug/keymapRoot entry point for createKeymap, formatShortcut, findShortcutConflicts, errors, and public types.
@vielzeug/keymap/parseParser internals subpath for custom tooling: parseShortcut, parseStep, matchStep, canonicalizeShortcut, detectModKey.

Core Manager

createKeymap()

ts
function createKeymap(
  bindings?: readonly Binding[],
  options?: KeymapOptions,
): Keymap;

Creates shortcut manager with independent chord state for each mounted target.

ParameterTypeDescription
bindingsreadonly Binding[]Ordered array of bindings. Each binding has an explicit id, shortcut, and handler.
optionsKeymapOptionsChord, modifier, and global-guard configuration.

Returns: Keymap.

ts
import { createKeymap } from '@vielzeug/keymap';

const map = createKeymap([
  { id: 'save', shortcut: 'ctrl+s', handler: () => console.log('save') },
]);
const unmount = map.mount(document);

unmount();
map.dispose();
Keymap memberReturnContract
bind(binding)() => voidAdds or replaces a binding with the same id. Returned callback removes that binding while active.
mount(target)() => voidAdds target listener. Repeat mounts of same target are reference-counted.
unbind(id)voidRemoves binding by id. Warns in development when unknown.
listBindings()readonly BindingEntry[]Returns a detached binding snapshot.
tap(handler, options?)() => voidObserves chord lifecycle, matches, and disposal. Supports signal-owned teardown.
dispose()voidRemoves all listeners, aborts signal, and permanently disposes map. Idempotent.
disposedbooleantrue after first dispose().
disposalSignalAbortSignalAborts when map is disposed.
[Symbol.dispose]()voidCalls dispose().

After disposal, bind(), unbind(), and mount() throw KeymapError.

Runtime Observation

tap()

ts
function tap(
  handler: (event: KeymapEvent) => void,
  options?: { signal?: AbortSignal },
): () => void;

Observes chord progress, cancellation, completed matches, timeouts, and disposal without affecting shortcut behavior.

ParameterTypeDescription
handler(event: KeymapEvent) => voidReceives each runtime event synchronously. Errors are swallowed.
options.signalAbortSignalDetaches the handler when aborted.

Returns: An idempotent detach function.

ts
const stop = map.tap((event) => {
  if (event.type === 'chord-start') showChordHint(event.step);
  if (event.type === 'chord-cancel' || event.type === 'chord-timeout' || event.type === 'match') hideChordHint();
});

Conflict Analysis

findShortcutConflicts()

ts
function findShortcutConflicts(
  shortcut: string,
  entries: readonly BindingEntry[],
  options?: ConflictOptions,
): BindingEntry[];

Returns entries with same-trigger exact or prefix-conflicting shortcut paths.

ParameterTypeDescription
shortcutstringProposed shortcut. Empty or whitespace-only input returns no conflicts.
entriesreadonly BindingEntry[]Bindings to compare, commonly map.listBindings().
optionsConflictOptionsOptional modifier resolution and trigger filter.

Returns: Matching entries. Returns [] when no conflict exists.

ts
import { createKeymap, findShortcutConflicts } from '@vielzeug/keymap';

const map = createKeymap([{ id: 'top', shortcut: 'g', handler: () => console.log('top') }]);
const conflicts = findShortcutConflicts('g g', map.listBindings());

console.log(conflicts.length); // 1

Formatting

formatShortcut()

ts
function formatShortcut(shortcut: string, modKey?: 'ctrl' | 'meta'): string;

Formats parsed shortcut into Mac symbols for meta or word labels for ctrl.

ParameterTypeDescription
shortcutstringShortcut string to format.
modKey'ctrl' | 'meta'Platform primary modifier. Defaults to detectModKey().

Returns: Display label, or '' for invalid input.

ts
import { formatShortcut } from '@vielzeug/keymap';

formatShortcut('mod+shift+p', 'meta'); // ⇧⌘P
formatShortcut('mod+shift+p', 'ctrl'); // Ctrl+Shift+P

Parser Subpath (@vielzeug/keymap/parse)

parseShortcut()

ts
function parseShortcut(raw: string, modKey?: 'ctrl' | 'meta'): Shortcut;

Strictly parses one or more space-separated shortcut steps.

ParameterTypeDescription
rawstringFull shortcut string.
modKey'ctrl' | 'meta'Platform primary modifier. Defaults to detectModKey().

Returns: Parsed Shortcut.

ts
import { parseShortcut } from '@vielzeug/keymap/parse';

const shortcut = parseShortcut('ctrl+k ctrl+s', 'ctrl');
console.log(shortcut.length); // 2

Throws KeymapParseError for empty, modifier-only, or ambiguous steps.


parseStep()

ts
function parseStep(raw: string, modKey?: 'ctrl' | 'meta'): ShortcutStep | null;

Parses one shortcut step without throwing.

ParameterTypeDescription
rawstringOne shortcut step.
modKey'ctrl' | 'meta'Platform primary modifier. Defaults to detectModKey().

Returns: Parsed ShortcutStep, or null for empty, modifier-only, or ambiguous input.

ts
import { parseStep } from '@vielzeug/keymap/parse';

parseStep('ctrl+k', 'ctrl'); // { key: 'k', modifiers: Set(['ctrl']) }
parseStep('ctrl+k+j', 'ctrl'); // null

canonicalizeShortcut()

ts
function canonicalizeShortcut(steps: readonly ShortcutStep[]): string;

Converts parsed steps into stable canonical string with sorted modifier order.

ParameterTypeDescription
stepsreadonly ShortcutStep[]Parsed shortcut steps.

Returns: Canonical shortcut string.

ts
import { canonicalizeShortcut, parseShortcut } from '@vielzeug/keymap/parse';

canonicalizeShortcut(parseShortcut('shift+ctrl+k', 'ctrl')); // ctrl+shift+k

matchStep()

ts
function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean;

Tests exact key and modifier equality for one parsed step.

ParameterTypeDescription
eventKeyboardEventEvent to match. Missing runtime key returns false.
stepShortcutStepParsed step.

Returns: true only when key and all modifier states match.

ts
import { matchStep, parseStep } from '@vielzeug/keymap/parse';

const step = parseStep('ctrl+k', 'ctrl')!;
matchStep(new KeyboardEvent('keydown', { ctrlKey: true, key: 'k' }), step); // true

detectModKey()

ts
function detectModKey(): 'ctrl' | 'meta';

Detects Mac platform from navigator and otherwise returns ctrl.

Returns: 'meta' on Mac platforms; 'ctrl' elsewhere or without navigator.

ts
import { detectModKey } from '@vielzeug/keymap/parse';

const modKey = detectModKey();

Types

Keymap

Stateful shortcut manager returned by createKeymap().

ts
interface Keymap {
  [Symbol.dispose](): void;
  bind(binding: Binding): () => void;
  dispose(): void;
  readonly disposalSignal: AbortSignal;
  readonly disposed: boolean;
  listBindings(): readonly BindingEntry[];
  mount(target: EventTarget): () => void;
  tap(handler: (event: KeymapEvent) => void, options?: { signal?: AbortSignal }): () => void;
  unbind(id: string): void;
}

KeymapOptions

Options applied to every binding owned by one manager.

ts
interface KeymapOptions {
  chordTimeout?: number;
  modKey?: 'ctrl' | 'meta';
  when?: When;
}
  • when: Guard function called for all bindings. When combined with per-binding when guards, both must return true for the handler to fire (AND composition). Global guard is checked first.

KeymapEvent

Runtime event observed through tap().

ts
type KeymapEvent =
  | { type: 'chord-cancel'; target: EventTarget; trigger: 'keydown' | 'keyup' }
  | { type: 'chord-start'; step: ShortcutStep; target: EventTarget; trigger: 'keydown' | 'keyup' }
  | { type: 'chord-progress'; steps: readonly ShortcutStep[]; target: EventTarget; trigger: 'keydown' | 'keyup' }
  | { type: 'chord-timeout'; target: EventTarget; trigger: 'keydown' | 'keyup' }
  | { type: 'match'; binding: BindingEntry; target: EventTarget; trigger: 'keydown' | 'keyup' }
  | { type: 'dispose' };

step, steps, and binding are detached snapshots. Mutating them does not affect matching.

Binding

Per-binding configuration. Each binding has an explicit id so duplicate shortcuts can coexist. Reusing an ID replaces its binding; among duplicate shortcuts, the first binding whose guard passes wins.

ts
interface Binding {
  id: string;
  shortcut: string;
  handler: Handler;
  trigger?: 'keydown' | 'keyup';
  when?: When;
  preventDefault?: boolean;
  stopPropagation?: boolean;
}
  • trigger: Defaults to 'keydown'.
  • preventDefault: Defaults to true. Set to false for shortcuts that must retain browser behavior.
  • stopPropagation: Defaults to false.

Handler and When

ts
type Handler = (event: KeyboardEvent) => void;
type When = (event: KeyboardEvent) => boolean;

BindingEntry

Detached binding metadata returned by listBindings().

ts
type BindingEntry = {
  readonly id: string;
  readonly shortcut: readonly ShortcutStep[];
  readonly trigger: 'keydown' | 'keyup';
  readonly preventDefault: boolean;
  readonly stopPropagation: boolean;
};

ModifierKey, Shortcut, and ShortcutStep

Parser types used by parseShortcut(), parseStep(), matchStep(), and canonicalizeShortcut(). Available from @vielzeug/keymap/parse.

ts
type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';

type ShortcutStep = {
  key: string;
  modifiers: Set<ModifierKey>;
};

type Shortcut = ShortcutStep[];

ConflictOptions

Comparison options for findShortcutConflicts().

ts
interface ConflictOptions {
  modKey?: 'ctrl' | 'meta';
  trigger?: 'keydown' | 'keyup';
}

Errors

ErrorTriggerNotable properties
KeymapErrorLifecycle operation after disposalUse instanceof KeymapError to narrow Keymap errors.
KeymapParseErrorStrict shortcut parser receives invalid inputExtends KeymapError.