@vielzeug/keymap 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"conflicts.cjs","names":[],"sources":["../src/conflicts.ts"],"sourcesContent":["import type { ShortcutStep } from './parser';\nimport type { BindingEntry } from './types';\n\nimport { canonicalizeShortcut, parseShortcut } from './parser';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n"],"mappings":"gCAUA,SAAS,EAAW,EAAkC,EAA0C,CAG9F,OAFI,EAAQ,OAAS,EAAO,OAAe,GAEpC,EAAA,qBAAqB,CAAO,IAAM,EAAA,qBAAqB,EAAO,MAAM,EAAG,EAAQ,MAAM,CAAC,CAC/F,CAYA,SAAgB,EACd,EACA,EACA,EAA2B,CAAC,EACZ,CAChB,GAAM,CAAE,SAAQ,UAAU,WAAc,EAIxC,GAAI,EAAS,KAAK,IAAM,GAAI,MAAO,CAAC,EAEpC,IAAM,EAAQ,EAAA,cAAc,EAAU,CAAM,EAE5C,OAAO,EAAQ,OACZ,GAAU,EAAM,UAAY,IAAY,EAAW,EAAO,EAAM,QAAQ,GAAK,EAAW,EAAM,SAAU,CAAK,EAChH,CACF"}
1
+ {"version":3,"file":"conflicts.cjs","names":[],"sources":["../src/conflicts.ts"],"sourcesContent":["import type { ShortcutStep } from './parser';\nimport { canonicalizeShortcut, parseShortcut } from './parser';\nimport type { BindingEntry } from './types';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n"],"mappings":"gCASA,SAAS,EAAW,EAAkC,EAA0C,CAG9F,OAFI,EAAQ,OAAS,EAAO,OAAe,GAEpC,EAAA,qBAAqB,CAAO,IAAM,EAAA,qBAAqB,EAAO,MAAM,EAAG,EAAQ,MAAM,CAAC,CAC/F,CAYA,SAAgB,EACd,EACA,EACA,EAA2B,CAAC,EACZ,CAChB,GAAM,CAAE,SAAQ,UAAU,WAAc,EAIxC,GAAI,EAAS,KAAK,IAAM,GAAI,MAAO,CAAC,EAEpC,IAAM,EAAQ,EAAA,cAAc,EAAU,CAAM,EAE5C,OAAO,EAAQ,OACZ,GAAU,EAAM,UAAY,IAAY,EAAW,EAAO,EAAM,QAAQ,GAAK,EAAW,EAAM,SAAU,CAAK,EAChH,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"conflicts.d.ts","sourceRoot":"","sources":["../src/conflicts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI5C,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;CAC/B;AAQD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,OAAO,GAAE,eAAoB,GAC5B,YAAY,EAAE,CAYhB"}
1
+ {"version":3,"file":"conflicts.d.ts","sourceRoot":"","sources":["../src/conflicts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;CAC/B;AAQD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,OAAO,GAAE,eAAoB,GAC5B,YAAY,EAAE,CAYhB"}
@@ -1 +1 @@
1
- {"version":3,"file":"conflicts.js","names":[],"sources":["../src/conflicts.ts"],"sourcesContent":["import type { ShortcutStep } from './parser';\nimport type { BindingEntry } from './types';\n\nimport { canonicalizeShortcut, parseShortcut } from './parser';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n"],"mappings":";;AAUA,SAAS,EAAW,GAAkC,GAA0C;CAG9F,OAFI,EAAQ,SAAS,EAAO,SAAe,KAEpC,EAAqB,CAAO,MAAM,EAAqB,EAAO,MAAM,GAAG,EAAQ,MAAM,CAAC;AAC/F;AAYA,SAAgB,EACd,GACA,GACA,IAA2B,CAAC,GACZ;CAChB,IAAM,EAAE,WAAQ,aAAU,cAAc;CAIxC,IAAI,EAAS,KAAK,MAAM,IAAI,OAAO,CAAC;CAEpC,IAAM,IAAQ,EAAc,GAAU,CAAM;CAE5C,OAAO,EAAQ,QACZ,MAAU,EAAM,YAAY,MAAY,EAAW,GAAO,EAAM,QAAQ,KAAK,EAAW,EAAM,UAAU,CAAK,EAChH;AACF"}
1
+ {"version":3,"file":"conflicts.js","names":[],"sources":["../src/conflicts.ts"],"sourcesContent":["import type { ShortcutStep } from './parser';\nimport { canonicalizeShortcut, parseShortcut } from './parser';\nimport type { BindingEntry } from './types';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n"],"mappings":";;AASA,SAAS,EAAW,GAAkC,GAA0C;CAG9F,OAFI,EAAQ,SAAS,EAAO,SAAe,KAEpC,EAAqB,CAAO,MAAM,EAAqB,EAAO,MAAM,GAAG,EAAQ,MAAM,CAAC;AAC/F;AAYA,SAAgB,EACd,GACA,GACA,IAA2B,CAAC,GACZ;CAChB,IAAM,EAAE,WAAQ,aAAU,cAAc;CAIxC,IAAI,EAAS,KAAK,MAAM,IAAI,OAAO,CAAC;CAEpC,IAAM,IAAQ,EAAc,GAAU,CAAM;CAE5C,OAAO,EAAQ,QACZ,MAAU,EAAM,YAAY,MAAY,EAAW,GAAO,EAAM,QAAQ,KAAK,EAAW,EAAM,UAAU,CAAK,EAChH;AACF"}
package/dist/errors.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{constructor(e){super(e)}};exports.KeymapError=e,exports.KeymapParseError=t;
1
+ var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{};exports.KeymapError=e,exports.KeymapParseError=t;
2
2
  //# sourceMappingURL=errors.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is KeymapError {\n return err instanceof KeymapError;\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {\n constructor(message: string) {\n super(message);\n }\n}\n"],"mappings":"AACA,IAAa,EAAb,MAAa,UAAoB,KAAM,CACrC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CAEA,OAAO,GAAG,EAAkC,CAC1C,OAAO,aAAe,CACxB,CACF,EAGa,EAAb,cAAsC,CAAY,CAChD,YAAY,EAAiB,CAC3B,MAAM,CAAO,CACf,CACF"}
1
+ {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n"],"mappings":"AACA,IAAa,EAAb,cAAiC,KAAM,CACrC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,EAAb,cAAsC,CAAY,CAAC"}
package/dist/errors.d.ts CHANGED
@@ -1,10 +1,8 @@
1
1
  /** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */
2
2
  export declare class KeymapError extends Error {
3
3
  constructor(message: string, opts?: ErrorOptions);
4
- static is(err: unknown): err is KeymapError;
5
4
  }
6
5
  /** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */
7
6
  export declare class KeymapParseError extends KeymapError {
8
- constructor(message: string);
9
7
  }
10
8
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAC3G,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;IAMhD,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,WAAW;CAG5C;AAED,sFAAsF;AACtF,qBAAa,gBAAiB,SAAQ,WAAW;gBACnC,OAAO,EAAE,MAAM;CAG5B"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAC3G,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAKjD;AAED,sFAAsF;AACtF,qBAAa,gBAAiB,SAAQ,WAAW;CAAG"}
package/dist/errors.js CHANGED
@@ -1,16 +1,9 @@
1
1
  //#region src/errors.ts
2
- var e = class e extends Error {
2
+ var e = class extends Error {
3
3
  constructor(e, t) {
4
4
  super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
5
5
  }
6
- static is(t) {
7
- return t instanceof e;
8
- }
9
- }, t = class extends e {
10
- constructor(e) {
11
- super(e);
12
- }
13
- };
6
+ }, t = class extends e {};
14
7
  //#endregion
15
8
  export { e as KeymapError, t as KeymapParseError };
16
9
 
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is KeymapError {\n return err instanceof KeymapError;\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {\n constructor(message: string) {\n super(message);\n }\n}\n"],"mappings":";AACA,IAAa,IAAb,MAAa,UAAoB,MAAM;CACrC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;CAEA,OAAO,GAAG,GAAkC;EAC1C,OAAO,aAAe;CACxB;AACF,GAGa,IAAb,cAAsC,EAAY;CAChD,YAAY,GAAiB;EAC3B,MAAM,CAAO;CACf;AACF"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n"],"mappings":";AACA,IAAa,IAAb,cAAiC,MAAM;CACrC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;AACF,GAGa,IAAb,cAAsC,EAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"format.cjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n"],"mappings":"sDAGA,IAAM,EAAsC,CAC1C,IAAK,QACL,UAAW,IACX,UAAW,IACX,WAAY,IACZ,QAAS,IACT,UAAW,IACX,OAAQ,IACR,IAAK,MACL,MAAO,IACP,OAAQ,MACR,KAAM,OACN,SAAU,OACV,OAAQ,OACR,IAAK,GACP,EAEM,EAA0C,CAC9C,IAAK,IACL,KAAM,IACN,KAAM,IACN,MAAO,GACT,EAEM,EAA2C,CAC/C,IAAK,MACL,KAAM,OACN,KAAM,MACN,MAAO,OACT,EAEM,EAAY,CAAC,OAAQ,MAAO,QAAS,MAAM,EAajD,SAAgB,EAAe,EAAkB,EAA0B,EAAA,aAAa,EAAW,CACjG,GAAI,CAAC,EAAS,KAAK,EAGjB,MAAO,GAGT,IAAI,EAEJ,GAAI,CACF,EAAQ,EAAA,cAAc,EAAU,CAAM,CACxC,MAAQ,CAGN,MAFK,GAAmD,EAAnD,EAEE,EACT,CAEA,IAAM,EAAQ,IAAW,OAEzB,OAAO,EACJ,IAAK,GAAS,CACb,IAAM,EAAW,EAAU,OAAQ,GAAM,EAAK,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,IAAK,GACnE,EAAQ,EAAgB,GAAK,EAAiB,EAChD,EAEM,EAAW,EAAY,EAAK,MAAQ,EAAK,IAAI,YAAY,EAE/D,OAAO,EAAQ,GAAG,EAAS,KAAK,EAAE,IAAI,IAAa,CAAC,GAAG,EAAU,CAAQ,CAAC,CAAC,KAAK,GAAG,CACrF,CAAC,CAAC,CACD,KAAK,GAAG,CACb"}
1
+ {"version":3,"file":"format.cjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps: ReturnType<typeof parseShortcut>;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n"],"mappings":"sDAGA,IAAM,EAAsC,CAC1C,IAAK,QACL,UAAW,IACX,UAAW,IACX,WAAY,IACZ,QAAS,IACT,UAAW,IACX,OAAQ,IACR,IAAK,MACL,MAAO,IACP,OAAQ,MACR,KAAM,OACN,SAAU,OACV,OAAQ,OACR,IAAK,GACP,EAEM,EAA0C,CAC9C,IAAK,IACL,KAAM,IACN,KAAM,IACN,MAAO,GACT,EAEM,EAA2C,CAC/C,IAAK,MACL,KAAM,OACN,KAAM,MACN,MAAO,OACT,EAEM,EAAY,CAAC,OAAQ,MAAO,QAAS,MAAM,EAajD,SAAgB,EAAe,EAAkB,EAA0B,EAAA,aAAa,EAAW,CACjG,GAAI,CAAC,EAAS,KAAK,EAGjB,MAAO,GAGT,IAAI,EAEJ,GAAI,CACF,EAAQ,EAAA,cAAc,EAAU,CAAM,CACxC,MAAQ,CAGN,MAFK,GAAmD,EAAnD,EAEE,EACT,CAEA,IAAM,EAAQ,IAAW,OAEzB,OAAO,EACJ,IAAK,GAAS,CACb,IAAM,EAAW,EAAU,OAAQ,GAAM,EAAK,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,IAAK,GACnE,EAAQ,EAAgB,GAAK,EAAiB,EAChD,EAEM,EAAW,EAAY,EAAK,MAAQ,EAAK,IAAI,YAAY,EAE/D,OAAO,EAAQ,GAAG,EAAS,KAAK,EAAE,IAAI,IAAa,CAAC,GAAG,EAAU,CAAQ,CAAC,CAAC,KAAK,GAAG,CACrF,CAAC,CAAC,CACD,KAAK,GAAG,CACb"}
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","names":[],"sources":["../src/format.ts"],"sourcesContent":["import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n"],"mappings":";;;AAGA,IAAM,IAAsC;CAC1C,KAAK;CACL,WAAW;CACX,WAAW;CACX,YAAY;CACZ,SAAS;CACT,WAAW;CACX,QAAQ;CACR,KAAK;CACL,OAAO;CACP,QAAQ;CACR,MAAM;CACN,UAAU;CACV,QAAQ;CACR,KAAK;AACP,GAEM,IAA0C;CAC9C,KAAK;CACL,MAAM;CACN,MAAM;CACN,OAAO;AACT,GAEM,IAA2C;CAC/C,KAAK;CACL,MAAM;CACN,MAAM;CACN,OAAO;AACT,GAEM,IAAY;CAAC;CAAQ;CAAO;CAAS;AAAM;AAajD,SAAgB,EAAe,GAAkB,IAA0B,EAAa,GAAW;CACjG,IAAI,CAAC,EAAS,KAAK,GAGjB,OAAO;CAGT,IAAI;CAEJ,IAAI;EACF,IAAQ,EAAc,GAAU,CAAM;CACxC,QAAQ;EAGN,OAFK,GAAmD,EAAnD,GAEE;CACT;CAEA,IAAM,IAAQ,MAAW;CAEzB,OAAO,EACJ,KAAK,MAAS;EACb,IAAM,IAAW,EAAU,QAAQ,MAAM,EAAK,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,MACnE,IAAQ,EAAgB,KAAK,EAAiB,EAChD,GAEM,IAAW,EAAY,EAAK,QAAQ,EAAK,IAAI,YAAY;EAE/D,OAAO,IAAQ,GAAG,EAAS,KAAK,EAAE,IAAI,MAAa,CAAC,GAAG,GAAU,CAAQ,CAAC,CAAC,KAAK,GAAG;CACrF,CAAC,CAAC,CACD,KAAK,GAAG;AACb"}
1
+ {"version":3,"file":"format.js","names":[],"sources":["../src/format.ts"],"sourcesContent":["import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps: ReturnType<typeof parseShortcut>;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n"],"mappings":";;;AAGA,IAAM,IAAsC;CAC1C,KAAK;CACL,WAAW;CACX,WAAW;CACX,YAAY;CACZ,SAAS;CACT,WAAW;CACX,QAAQ;CACR,KAAK;CACL,OAAO;CACP,QAAQ;CACR,MAAM;CACN,UAAU;CACV,QAAQ;CACR,KAAK;AACP,GAEM,IAA0C;CAC9C,KAAK;CACL,MAAM;CACN,MAAM;CACN,OAAO;AACT,GAEM,IAA2C;CAC/C,KAAK;CACL,MAAM;CACN,MAAM;CACN,OAAO;AACT,GAEM,IAAY;CAAC;CAAQ;CAAO;CAAS;AAAM;AAajD,SAAgB,EAAe,GAAkB,IAA0B,EAAa,GAAW;CACjG,IAAI,CAAC,EAAS,KAAK,GAGjB,OAAO;CAGT,IAAI;CAEJ,IAAI;EACF,IAAQ,EAAc,GAAU,CAAM;CACxC,QAAQ;EAGN,OAFK,GAAmD,EAAnD,GAEE;CACT;CAEA,IAAM,IAAQ,MAAW;CAEzB,OAAO,EACJ,KAAK,MAAS;EACb,IAAM,IAAW,EAAU,QAAQ,MAAM,EAAK,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,MACnE,IAAQ,EAAgB,KAAK,EAAiB,EAChD,GAEM,IAAW,EAAY,EAAK,QAAQ,EAAK,IAAI,YAAY;EAE/D,OAAO,IAAQ,GAAG,EAAS,KAAK,EAAE,IAAI,MAAa,CAAC,GAAG,GAAU,CAAQ,CAAC,CAAC,KAAK,GAAG;CACrF,CAAC,CAAC,CACD,KAAK,GAAG;AACb"}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
+ export type { ConflictOptions } from './conflicts';
1
2
  export { findShortcutConflicts } from './conflicts';
2
3
  export { KeymapError, KeymapParseError } from './errors';
3
4
  export { formatShortcut } from './format';
4
5
  export { createKeymap } from './keymap';
5
- export { canonicalizeShortcut, detectModKey, matchStep, parseShortcut, parseStep } from './parser';
6
- export type { ConflictOptions } from './conflicts';
7
- export type { BindingEntry, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';
8
6
  export type { ModifierKey, Shortcut, ShortcutStep } from './parser';
7
+ export { canonicalizeShortcut, detectModKey, matchStep, parseShortcut, parseStep } from './parser';
8
+ export type { BindingEntry, BindingOptions, BindingValue, ChordStateChange, Handler, Keymap, KeymapOptions, When, } from './types';
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACnG,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAChH,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACnG,YAAY,EACV,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,OAAO,EACP,MAAM,EACN,aAAa,EACb,IAAI,GACL,MAAM,SAAS,CAAC"}
package/dist/keymap.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{constructor(e){super(e)}};function n(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var r={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},i={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function a(e,n){let a=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(a.length===0)throw new t(`Invalid shortcut step: "${e}"`);let o={...r,mod:n},s=new Set,c=[];for(let e of a){let t=Object.hasOwn(o,e)?o[e]:void 0;t?s.add(t):c.push(e)}if(c.length===0)throw new t(`Invalid shortcut step: "${e}"`);if(c.length>1)throw new t(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);let l=c[0];return{key:Object.hasOwn(i,l)?i[l]:l,modifiers:s}}function o(e,t=n()){try{return a(e,t)}catch{return null}}function s(e,r=n()){let i=e.trim().split(/\s+/).filter(Boolean);if(i.length===0)throw new t(`Shortcut must contain at least one key step`);return i.map(e=>a(e,r))}function c(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function l(e,t){if(typeof e.key!=`string`||e.key.toLowerCase()!==t.key)return!1;let{modifiers:n}=t;return e.altKey===n.has(`alt`)&&e.ctrlKey===n.has(`ctrl`)&&e.metaKey===n.has(`meta`)&&e.shiftKey===n.has(`shift`)}function u(e,t){return e.length>t.length?!1:c(e)===c(t.slice(0,e.length))}function d(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n;if(e.trim()===``)return[];let a=s(e,r);return t.filter(e=>e.trigger===i&&(u(a,e.shortcut)||u(e.shortcut,a)))}var f={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},p={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},m={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},h=[`ctrl`,`alt`,`shift`,`meta`];function g(e,t=n()){if(!e.trim())return``;let r;try{r=s(e,t)}catch{return`${e}`,``}let i=t===`meta`;return r.map(e=>{let t=h.filter(t=>e.modifiers.has(t)).map(e=>i?p[e]:m[e]),n=f[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function _(e){return typeof e==`function`?{handler:e,trigger:`keydown`}:{handler:e.handler,trigger:e.trigger??`keydown`,when:e.when}}function v(e,t){let n=0,r=[],i;function a(){i!==void 0&&clearTimeout(i),i=void 0,n=0,r=[]}function o(s){let c=(n===0?e():r).filter(e=>{let t=e.shortcut[n];return t!==void 0&&l(s,t)});if(c.length===0){let e=n!==0;return a(),e?o(s):void 0}i!==void 0&&clearTimeout(i),i=void 0;let u=c.find(e=>e.shortcut.length===n+1);if(u)return a(),u;r=c,n+=1,i=setTimeout(a,t)}return{advance:o,reset:a}}function y(t={},r={}){let{chordTimeout:i=1e3,modKey:a=n(),preventDefault:o=!0,stopPropagation:l=!1,when:u}=r,d=Number.isFinite(i)&&i>0?i:1e3;d!==i&&`${i}`;let f=new Map,p=new Map,m=new AbortController,h=[],g=[],y=!1;function b(){if(y)throw new e(`Keymap is disposed`)}function x(){h=[],g=[];for(let e of f.values())e.trigger===`keydown`?h.push(e):g.push(e)}function S(e){return c(s(e,a))}function C(e,t){let n=s(e,a),r=c(n);return f.set(r,{shortcut:n,..._(t)}),x(),r}function w(e){let t=f.delete(e);return t&&x(),t}function T(e,t){return n=>{let r=(typeof n.composedPath==`function`?n.composedPath():[]).find(e=>p.has(e));if(r&&r!==e)return;let i=n;if(y||u&&!u(i))return;let a=t.advance(i);!a||a.when&&!a.when(i)||(o&&i.preventDefault(),l&&i.stopPropagation(),a.handler(i))}}for(let[e,n]of Object.entries(t))C(e,n);return{bind(e,t){b();let n=C(e,t);return()=>{y||w(n)}},get disposalSignal(){return m.signal},dispose(){if(!y){y=!0,m.abort();for(let[e,t]of p)e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset();p.clear(),f.clear(),h=[],g=[]}},get disposed(){return y},listBindings(){return[...f.values()].map(e=>({shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){b();let t=p.get(e);if(!t){let n=v(()=>h,d),r=v(()=>g,d),i=T(e,n),a=T(e,r);t={keydown:n,keyup:r,onKeydown:i,onKeyup:a,refs:0},p.set(e,t),e.addEventListener(`keydown`,i),e.addEventListener(`keyup`,a)}t.refs+=1;let n=!1;return()=>{n||(n=!0,--t.refs,!(t.refs>0)&&(e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset(),p.delete(e)))}},[Symbol.dispose](){this.dispose()},unbind(e){b(),w(S(e))||`${e}`}}}exports.KeymapError=e,exports.KeymapParseError=t,exports.canonicalizeShortcut=c,exports.createKeymap=y,exports.detectModKey=n,exports.findShortcutConflicts=d,exports.formatShortcut=g,exports.matchStep=l,exports.parseShortcut=s,exports.parseStep=o;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{};function n(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var r={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},i={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function a(e,n){let a=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(a.length===0)throw new t(`Invalid shortcut step: "${e}"`);let o={...r,mod:n},s=new Set,c=[];for(let e of a){let t=Object.hasOwn(o,e)?o[e]:void 0;t?s.add(t):c.push(e)}if(c.length===0)throw new t(`Invalid shortcut step: "${e}"`);if(c.length>1)throw new t(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);let l=c[0];return{key:Object.hasOwn(i,l)?i[l]:l,modifiers:s}}function o(e,t=n()){try{return a(e,t)}catch{return null}}function s(e,r=n()){let i=e.trim().split(/\s+/).filter(Boolean);if(i.length===0)throw new t(`Shortcut must contain at least one key step`);return i.map(e=>a(e,r))}function c(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function l(e,t){if(typeof e.key!=`string`||e.key.toLowerCase()!==t.key)return!1;let{modifiers:n}=t;return e.altKey===n.has(`alt`)&&e.ctrlKey===n.has(`ctrl`)&&e.metaKey===n.has(`meta`)&&e.shiftKey===n.has(`shift`)}function u(e,t){return e.length>t.length?!1:c(e)===c(t.slice(0,e.length))}function d(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n;if(e.trim()===``)return[];let a=s(e,r);return t.filter(e=>e.trigger===i&&(u(a,e.shortcut)||u(e.shortcut,a)))}var f={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},p={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},m={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},h=[`ctrl`,`alt`,`shift`,`meta`];function g(e,t=n()){if(!e.trim())return``;let r;try{r=s(e,t)}catch{return`${e}`,``}let i=t===`meta`;return r.map(e=>{let t=h.filter(t=>e.modifiers.has(t)).map(e=>i?p[e]:m[e]),n=f[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function _(e){return typeof e==`function`?{handler:e,trigger:`keydown`}:{handler:e.handler,trigger:e.trigger??`keydown`,when:e.when}}function v(e,t,n,r,i){let a=0,o=[],s,c=[];function u(e=!1){if(s!==void 0&&clearTimeout(s),e&&a>0&&i?.onChordTimeout)try{i.onChordTimeout(n,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}s=void 0,a=0,o=[],c=[]}function d(f){let p=(a===0?e():o).filter(e=>{let t=e.shortcut[a];return t!==void 0&&l(f,t)});if(p.length===0){let e=a!==0;return u(),e?d(f):void 0}s!==void 0&&clearTimeout(s),s=void 0;let m=p.find(e=>e.shortcut.length===a+1);if(m)return u(),m;let h=p[0].shortcut[a];if(c=p[0].shortcut.slice(0,a+1),a===0&&i?.onChordStart)try{i.onChordStart(n,h,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}else if(a>0&&i?.onChordProgress)try{i.onChordProgress(n,c,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}o=p,a+=1,s=setTimeout(()=>u(!0),t)}return{advance:d,reset:()=>u()}}function y(t={},r={}){let{chordTimeout:i=1e3,modKey:a=n(),preventDefault:o=!0,stopPropagation:l=!1,when:u,onChordState:d}=r,f=Number.isFinite(i)&&i>0?i:1e3;f!==i&&`${i}`;let p=new Map,m=new Map,h=new AbortController,g=[],y=[],b=!1;function x(){if(b)throw new e(`Keymap is disposed`)}function S(){g=[],y=[];for(let e of p.values())e.trigger===`keydown`?g.push(e):y.push(e)}function C(e){return c(s(e,a))}function w(e,t){let n=s(e,a),r=c(n);return p.set(r,{shortcut:n,..._(t)}),S(),r}function T(e){let t=p.delete(e);return t&&S(),t}function E(e){if(d)try{d(e)}catch(e){`${e instanceof Error?e.message:String(e)}`}}function D(e,t){return n=>{let r=(typeof n.composedPath==`function`?n.composedPath():[]).find(e=>m.has(e));if(r&&r!==e)return;let i=n;if(b)return;let a=t.advance(i);u&&!u(i)||!a||a.when&&!a.when(i)||(o&&i.preventDefault(),l&&i.stopPropagation(),a.handler(i))}}for(let[e,n]of Object.entries(t))w(e,n);return{bind(e,t){x();let n=w(e,t);return()=>{b||T(n)}},get disposalSignal(){return h.signal},dispose(){if(!b){b=!0,h.abort();for(let[e,t]of m)e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset();m.clear(),p.clear(),g=[],y=[]}},get disposed(){return b},listBindings(){return[...p.values()].map(e=>({shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){x();let t=m.get(e);if(!t){let n=v(()=>g,f,e,`keydown`,{onChordProgress:(e,t,n)=>E({steps:t,target:e,trigger:n,type:`progressed`}),onChordStart:(e,t,n)=>E({step:t,target:e,trigger:n,type:`started`}),onChordTimeout:(e,t)=>E({target:e,trigger:t,type:`timeout`})}),r=v(()=>y,f,e,`keyup`,{onChordProgress:(e,t,n)=>E({steps:t,target:e,trigger:n,type:`progressed`}),onChordStart:(e,t,n)=>E({step:t,target:e,trigger:n,type:`started`}),onChordTimeout:(e,t)=>E({target:e,trigger:t,type:`timeout`})}),i=D(e,n),a=D(e,r);t={keydown:n,keyup:r,onKeydown:i,onKeyup:a,refs:0},m.set(e,t),e.addEventListener(`keydown`,i),e.addEventListener(`keyup`,a)}t.refs+=1;let n=!1;return()=>{n||(n=!0,--t.refs,!(t?.refs>0)&&(e.removeEventListener(`keydown`,t?.onKeydown),e.removeEventListener(`keyup`,t?.onKeyup),t?.keydown.reset(),t?.keyup.reset(),m.delete(e)))}},[Symbol.dispose](){this.dispose()},unbind(e){x(),T(C(e))||`${e}`}}}exports.KeymapError=e,exports.KeymapParseError=t,exports.canonicalizeShortcut=c,exports.createKeymap=y,exports.detectModKey=n,exports.findShortcutConflicts=d,exports.formatShortcut=g,exports.matchStep=l,exports.parseShortcut=s,exports.parseStep=o;
2
2
  //# sourceMappingURL=keymap.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"keymap.cjs","names":[],"sources":["../src/errors.ts","../src/parser.ts","../src/conflicts.ts","../src/_dev.ts","../src/format.ts","../src/keymap.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is KeymapError {\n return err instanceof KeymapError;\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {\n constructor(message: string) {\n super(message);\n }\n}\n","import { KeymapParseError } from './errors';\n\nexport type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\nexport type ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\nexport type Shortcut = ShortcutStep[];\n\nexport function detectModKey(): 'ctrl' | 'meta' {\n if (typeof navigator === 'undefined') return 'ctrl';\n\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n\n return /mac/i.test(platform) ? 'meta' : 'ctrl';\n}\n\nconst MODIFIER_ALIASES_BASE: Record<string, ModifierKey> = {\n alt: 'alt',\n cmd: 'meta',\n command: 'meta',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n opt: 'alt',\n option: 'alt',\n shift: 'shift',\n win: 'meta',\n};\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n del: 'delete',\n down: 'arrowdown',\n esc: 'escape',\n left: 'arrowleft',\n right: 'arrowright',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n};\n\nfunction parseStepStrict(raw: string, modKey: 'ctrl' | 'meta'): ShortcutStep {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((part) => part.toLowerCase());\n\n if (parts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n const aliases: Record<string, ModifierKey> = { ...MODIFIER_ALIASES_BASE, mod: modKey };\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const modifier = Object.hasOwn(aliases, part) ? aliases[part]! : undefined;\n\n if (modifier) modifiers.add(modifier);\n else keyParts.push(part);\n }\n\n if (keyParts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const rawKey = keyParts[0]!;\n const key = Object.hasOwn(SPECIAL_KEY_ALIASES, rawKey) ? SPECIAL_KEY_ALIASES[rawKey]! : rawKey;\n\n return { key, modifiers };\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n try {\n return parseStepStrict(raw, modKey);\n } catch {\n return null;\n }\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n const steps = raw.trim().split(/\\s+/).filter(Boolean);\n\n if (steps.length === 0) throw new KeymapParseError('Shortcut must contain at least one key step');\n\n return steps.map((step) => parseStepStrict(step, modKey));\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((step) => {\n const modifiers = [...step.modifiers].sort().join('+');\n\n return modifiers ? `${modifiers}+${step.key}` : step.key;\n })\n .join(' ');\n}\n\nexport function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean {\n // Headless usage (SSR, non-DOM `EventTarget`s, hand-built test events) means `event` isn't\n // guaranteed to be a real `KeyboardEvent` at runtime even though the type says so — treat a\n // missing/non-string `key` as \"doesn't match\" rather than throwing inside the event listener.\n if (typeof event.key !== 'string') return false;\n\n if (event.key.toLowerCase() !== step.key) return false;\n\n const { modifiers } = step;\n\n return (\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift')\n );\n}\n","import type { ShortcutStep } from './parser';\nimport type { BindingEntry } from './types';\n\nimport { canonicalizeShortcut, parseShortcut } from './parser';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n","const isDev = !(globalThis as { __KEYMAP_PROD__?: boolean }).__KEYMAP_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/keymap] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n","import type { Shortcut } from './parser';\nimport type { BindingEntry, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';\n\nimport { warn } from './_dev';\nimport { KeymapError } from './errors';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\n\ntype ParsedBinding = {\n handler: Handler;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: When;\n};\n\ntype ChordTracker = ReturnType<typeof createChordTracker>;\ntype MountedTarget = {\n keydown: ChordTracker;\n keyup: ChordTracker;\n onKeydown: EventListener;\n onKeyup: EventListener;\n refs: number;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') return { handler: value, trigger: 'keydown' };\n\n return {\n handler: value.handler,\n trigger: value.trigger ?? 'keydown',\n when: value.when,\n };\n}\n\nfunction createChordTracker(getBindings: () => ParsedBinding[], chordTimeout: number) {\n let pendingIndex = 0;\n let candidates: ParsedBinding[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n function reset(): void {\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n pendingIndex = 0;\n candidates = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | undefined {\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched = pool.filter((binding) => {\n const step = binding.shortcut[pendingIndex];\n\n return step !== undefined && matchStep(event, step);\n });\n\n if (matched.length === 0) {\n const retryFromRoot = pendingIndex !== 0;\n\n reset();\n\n return retryFromRoot ? advance(event) : undefined;\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n\n const completed = matched.find((binding) => binding.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n candidates = matched;\n pendingIndex += 1;\n timer = setTimeout(reset, chordTimeout);\n\n return undefined;\n }\n\n return { advance, reset };\n}\n\n/**\n * Creates a headless keyboard shortcut manager with target-local chord state.\n *\n * Pass a bindings map of shortcut strings to handlers or `BindingOptions`, then call\n * `.mount(target)` to attach to any `EventTarget`. Supports chord sequences\n * (e.g. `\"ctrl+k ctrl+s\"`), per-binding `when` guards, `trigger` (keydown/keyup),\n * and dynamic `bind`/`unbind`.\n *\n * @example\n * const map = createKeymap({\n * 'mod+k mod+s': () => save(),\n * 'mod+shift+p': () => openPalette(),\n * 'g g': () => goToTop(),\n * esc: { handler: closePanel, when: (event) => !isEditableTarget(event.target) },\n * space: { handler: togglePlay, trigger: 'keyup' },\n * }, { modKey: 'ctrl' });\n * const unmount = map.mount(document);\n */\nexport function createKeymap(initialBindings: Record<string, BindingValue> = {}, options: KeymapOptions = {}): Keymap {\n const {\n chordTimeout: rawChordTimeout = 1000,\n modKey = detectModKey(),\n preventDefault = true,\n stopPropagation = false,\n when: globalWhen,\n } = options;\n const chordTimeout = Number.isFinite(rawChordTimeout) && rawChordTimeout > 0 ? rawChordTimeout : 1000;\n\n if (chordTimeout !== rawChordTimeout) {\n warn(`chordTimeout must be a positive finite number; received ${rawChordTimeout}. Using default of 1000ms.`);\n }\n\n const bindings = new Map<string, ParsedBinding>();\n const mounted = new Map<EventTarget, MountedTarget>();\n const disposalController = new AbortController();\n let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n let disposed = false;\n\n function assertActive(): void {\n if (disposed) throw new KeymapError('Keymap is disposed');\n }\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const binding of bindings.values()) {\n if (binding.trigger === 'keydown') bindingsDown.push(binding);\n else bindingsUp.push(binding);\n }\n }\n\n function bindingKey(shortcut: string): string {\n return canonicalizeShortcut(parseShortcut(shortcut, modKey));\n }\n\n function addBinding(shortcut: string, value: BindingValue): string {\n const parsed = parseShortcut(shortcut, modKey);\n const key = canonicalizeShortcut(parsed);\n\n bindings.set(key, { shortcut: parsed, ...resolveBinding(value) });\n rebuildTriggerCaches();\n\n return key;\n }\n\n function removeByKey(key: string): boolean {\n const existed = bindings.delete(key);\n\n if (existed) rebuildTriggerCaches();\n\n return existed;\n }\n\n function makeHandler(target: EventTarget, chord: ChordTracker): EventListener {\n return (event) => {\n const path = typeof event.composedPath === 'function' ? event.composedPath() : [];\n const nearestMountedTarget = path.find((pathTarget) => mounted.has(pathTarget));\n\n if (nearestMountedTarget && nearestMountedTarget !== target) return;\n\n const keyboardEvent = event as KeyboardEvent;\n\n if (disposed || (globalWhen && !globalWhen(keyboardEvent))) return;\n\n const binding = chord.advance(keyboardEvent);\n\n if (!binding || (binding.when && !binding.when(keyboardEvent))) return;\n\n if (preventDefault) keyboardEvent.preventDefault();\n\n if (stopPropagation) keyboardEvent.stopPropagation();\n\n binding.handler(keyboardEvent);\n };\n }\n\n for (const [shortcut, value] of Object.entries(initialBindings)) addBinding(shortcut, value);\n\n return {\n bind(shortcut: string, value: BindingValue): () => void {\n assertActive();\n\n const key = addBinding(shortcut, value);\n\n return () => {\n if (!disposed) removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n for (const [target, record] of mounted) {\n target.removeEventListener('keydown', record.onKeydown);\n target.removeEventListener('keyup', record.onKeyup);\n record.keydown.reset();\n record.keyup.reset();\n }\n\n mounted.clear();\n bindings.clear();\n bindingsDown = [];\n bindingsUp = [];\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((binding) => ({\n shortcut: binding.shortcut.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) })),\n trigger: binding.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n assertActive();\n\n let record = mounted.get(target);\n\n if (!record) {\n const keydown = createChordTracker(() => bindingsDown, chordTimeout);\n const keyup = createChordTracker(() => bindingsUp, chordTimeout);\n const onKeydown = makeHandler(target, keydown);\n const onKeyup = makeHandler(target, keyup);\n\n record = { keydown, keyup, onKeydown, onKeyup, refs: 0 };\n mounted.set(target, record);\n target.addEventListener('keydown', onKeydown);\n target.addEventListener('keyup', onKeyup);\n }\n\n record.refs += 1;\n\n let unmounted = false;\n\n return () => {\n if (unmounted) return;\n\n unmounted = true;\n record!.refs -= 1;\n\n if (record!.refs > 0) return;\n\n target.removeEventListener('keydown', record!.onKeydown);\n target.removeEventListener('keyup', record!.onKeyup);\n record!.keydown.reset();\n record!.keyup.reset();\n mounted.delete(target);\n };\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n assertActive();\n\n if (!removeByKey(bindingKey(shortcut))) {\n warn(`unbind() called for unknown shortcut: \"${shortcut}\"`);\n }\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"keymap.cjs","names":[],"sources":["../src/errors.ts","../src/parser.ts","../src/conflicts.ts","../src/_dev.ts","../src/format.ts","../src/keymap.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n","import { KeymapParseError } from './errors';\n\nexport type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\nexport type ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\nexport type Shortcut = ShortcutStep[];\n\nexport function detectModKey(): 'ctrl' | 'meta' {\n if (typeof navigator === 'undefined') return 'ctrl';\n\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n\n return /mac/i.test(platform) ? 'meta' : 'ctrl';\n}\n\nconst MODIFIER_ALIASES_BASE: Record<string, ModifierKey> = {\n alt: 'alt',\n cmd: 'meta',\n command: 'meta',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n opt: 'alt',\n option: 'alt',\n shift: 'shift',\n win: 'meta',\n};\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n del: 'delete',\n down: 'arrowdown',\n esc: 'escape',\n left: 'arrowleft',\n right: 'arrowright',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n};\n\nfunction parseStepStrict(raw: string, modKey: 'ctrl' | 'meta'): ShortcutStep {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((part) => part.toLowerCase());\n\n if (parts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n const aliases: Record<string, ModifierKey> = { ...MODIFIER_ALIASES_BASE, mod: modKey };\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const modifier = Object.hasOwn(aliases, part) ? aliases[part]! : undefined;\n\n if (modifier) modifiers.add(modifier);\n else keyParts.push(part);\n }\n\n if (keyParts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const rawKey = keyParts[0]!;\n const key = Object.hasOwn(SPECIAL_KEY_ALIASES, rawKey) ? SPECIAL_KEY_ALIASES[rawKey]! : rawKey;\n\n return { key, modifiers };\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n try {\n return parseStepStrict(raw, modKey);\n } catch {\n return null;\n }\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n const steps = raw.trim().split(/\\s+/).filter(Boolean);\n\n if (steps.length === 0) throw new KeymapParseError('Shortcut must contain at least one key step');\n\n return steps.map((step) => parseStepStrict(step, modKey));\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((step) => {\n const modifiers = [...step.modifiers].sort().join('+');\n\n return modifiers ? `${modifiers}+${step.key}` : step.key;\n })\n .join(' ');\n}\n\nexport function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean {\n // Headless usage (SSR, non-DOM `EventTarget`s, hand-built test events) means `event` isn't\n // guaranteed to be a real `KeyboardEvent` at runtime even though the type says so — treat a\n // missing/non-string `key` as \"doesn't match\" rather than throwing inside the event listener.\n if (typeof event.key !== 'string') return false;\n\n if (event.key.toLowerCase() !== step.key) return false;\n\n const { modifiers } = step;\n\n return (\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift')\n );\n}\n","import type { ShortcutStep } from './parser';\nimport { canonicalizeShortcut, parseShortcut } from './parser';\nimport type { BindingEntry } from './types';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n","const isDev = !(globalThis as { __KEYMAP_PROD__?: boolean }).__KEYMAP_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/keymap] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps: ReturnType<typeof parseShortcut>;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n","import { warn } from './_dev';\nimport { KeymapError } from './errors';\nimport type { Shortcut, ShortcutStep } from './parser';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\nimport type { BindingEntry, BindingValue, ChordStateChange, Handler, Keymap, KeymapOptions, When } from './types';\n\ntype ParsedBinding = {\n handler: Handler;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: When;\n};\n\ntype ChordTracker = ReturnType<typeof createChordTracker>;\ntype MountedTarget = {\n keydown: ChordTracker;\n keyup: ChordTracker;\n onKeydown: EventListener;\n onKeyup: EventListener;\n refs: number;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') return { handler: value, trigger: 'keydown' };\n\n return {\n handler: value.handler,\n trigger: value.trigger ?? 'keydown',\n when: value.when,\n };\n}\n\ntype ChordTrackerCallbacks = {\n onChordStart?: (target: EventTarget, step: ShortcutStep, trigger: 'keydown' | 'keyup') => void;\n onChordProgress?: (target: EventTarget, steps: readonly ShortcutStep[], trigger: 'keydown' | 'keyup') => void;\n onChordTimeout?: (target: EventTarget, trigger: 'keydown' | 'keyup') => void;\n};\n\nfunction createChordTracker(\n getBindings: () => ParsedBinding[],\n chordTimeout: number,\n target: EventTarget,\n trigger: 'keydown' | 'keyup',\n callbacks?: ChordTrackerCallbacks,\n) {\n let pendingIndex = 0;\n let candidates: ParsedBinding[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n let pendingSteps: ShortcutStep[] = [];\n\n function reset(emitTimeout = false): void {\n if (timer !== undefined) clearTimeout(timer);\n\n // Emit timeout only if explicitly requested (from timeout event)\n if (emitTimeout && pendingIndex > 0 && callbacks?.onChordTimeout) {\n try {\n callbacks.onChordTimeout(target, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n timer = undefined;\n pendingIndex = 0;\n candidates = [];\n pendingSteps = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | undefined {\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched = pool.filter((binding) => {\n const step = binding.shortcut[pendingIndex];\n\n return step !== undefined && matchStep(event, step);\n });\n\n if (matched.length === 0) {\n const retryFromRoot = pendingIndex !== 0;\n\n reset();\n\n return retryFromRoot ? advance(event) : undefined;\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n\n const completed = matched.find((binding) => binding.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n // New step in chord progression\n const currentStep = matched[0]!.shortcut[pendingIndex];\n pendingSteps = matched[0]!.shortcut.slice(0, pendingIndex + 1);\n\n // Emit started or progressed\n if (pendingIndex === 0 && callbacks?.onChordStart) {\n try {\n callbacks.onChordStart(target, currentStep, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n } else if (pendingIndex > 0 && callbacks?.onChordProgress) {\n try {\n callbacks.onChordProgress(target, pendingSteps, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n candidates = matched;\n pendingIndex += 1;\n timer = setTimeout(() => reset(true), chordTimeout);\n\n return undefined;\n }\n\n return { advance, reset: () => reset() };\n}\n\n/**\n * Creates a headless keyboard shortcut manager with target-local chord state.\n *\n * Pass a bindings map of shortcut strings to handlers or `BindingOptions`, then call\n * `.mount(target)` to attach to any `EventTarget`. Supports chord sequences\n * (e.g. `\"ctrl+k ctrl+s\"`), per-binding `when` guards, `trigger` (keydown/keyup),\n * and dynamic `bind`/`unbind`.\n *\n * @example\n * const map = createKeymap({\n * 'mod+k mod+s': () => save(),\n * 'mod+shift+p': () => openPalette(),\n * 'g g': () => goToTop(),\n * esc: { handler: closePanel, when: (event) => !isEditableTarget(event.target) },\n * space: { handler: togglePlay, trigger: 'keyup' },\n * }, { modKey: 'ctrl' });\n * const unmount = map.mount(document);\n */\nexport function createKeymap(initialBindings: Record<string, BindingValue> = {}, options: KeymapOptions = {}): Keymap {\n const {\n chordTimeout: rawChordTimeout = 1000,\n modKey = detectModKey(),\n preventDefault = true,\n stopPropagation = false,\n when: globalWhen,\n onChordState: userOnChordState,\n } = options;\n const chordTimeout = Number.isFinite(rawChordTimeout) && rawChordTimeout > 0 ? rawChordTimeout : 1000;\n\n if (chordTimeout !== rawChordTimeout) {\n warn(`chordTimeout must be a positive finite number; received ${rawChordTimeout}. Using default of 1000ms.`);\n }\n\n const bindings = new Map<string, ParsedBinding>();\n const mounted = new Map<EventTarget, MountedTarget>();\n const disposalController = new AbortController();\n let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n let disposed = false;\n\n function assertActive(): void {\n if (disposed) throw new KeymapError('Keymap is disposed');\n }\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const binding of bindings.values()) {\n if (binding.trigger === 'keydown') bindingsDown.push(binding);\n else bindingsUp.push(binding);\n }\n }\n\n function bindingKey(shortcut: string): string {\n return canonicalizeShortcut(parseShortcut(shortcut, modKey));\n }\n\n function addBinding(shortcut: string, value: BindingValue): string {\n const parsed = parseShortcut(shortcut, modKey);\n const key = canonicalizeShortcut(parsed);\n\n bindings.set(key, { shortcut: parsed, ...resolveBinding(value) });\n rebuildTriggerCaches();\n\n return key;\n }\n\n function removeByKey(key: string): boolean {\n const existed = bindings.delete(key);\n\n if (existed) rebuildTriggerCaches();\n\n return existed;\n }\n\n function emitChordState(change: ChordStateChange): void {\n if (!userOnChordState) return;\n\n try {\n userOnChordState(change);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n function makeHandler(target: EventTarget, chord: ChordTracker): EventListener {\n return (event) => {\n const path = typeof event.composedPath === 'function' ? event.composedPath() : [];\n const nearestMountedTarget = path.find((pathTarget) => mounted.has(pathTarget));\n\n if (nearestMountedTarget && nearestMountedTarget !== target) return;\n\n const keyboardEvent = event as KeyboardEvent;\n\n if (disposed) return;\n\n // Track chord progression (independent of guards)\n const binding = chord.advance(keyboardEvent);\n\n // Check guards AFTER tracking chord\n if (globalWhen && !globalWhen(keyboardEvent)) return;\n\n if (!binding || (binding.when && !binding.when(keyboardEvent))) return;\n\n if (preventDefault) keyboardEvent.preventDefault();\n\n if (stopPropagation) keyboardEvent.stopPropagation();\n\n binding.handler(keyboardEvent);\n };\n }\n\n for (const [shortcut, value] of Object.entries(initialBindings)) addBinding(shortcut, value);\n\n return {\n bind(shortcut: string, value: BindingValue): () => void {\n assertActive();\n\n const key = addBinding(shortcut, value);\n\n return () => {\n if (!disposed) removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n for (const [target, record] of mounted) {\n target.removeEventListener('keydown', record.onKeydown);\n target.removeEventListener('keyup', record.onKeyup);\n record.keydown.reset();\n record.keyup.reset();\n }\n\n mounted.clear();\n bindings.clear();\n bindingsDown = [];\n bindingsUp = [];\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((binding) => ({\n shortcut: binding.shortcut.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) })),\n trigger: binding.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n assertActive();\n\n let record = mounted.get(target);\n\n if (!record) {\n const keydown = createChordTracker(() => bindingsDown, chordTimeout, target, 'keydown', {\n onChordProgress: (t, steps, trigger) => emitChordState({ steps, target: t, trigger, type: 'progressed' }),\n onChordStart: (t, step, trigger) => emitChordState({ step, target: t, trigger, type: 'started' }),\n onChordTimeout: (t, trigger) => emitChordState({ target: t, trigger, type: 'timeout' }),\n });\n const keyup = createChordTracker(() => bindingsUp, chordTimeout, target, 'keyup', {\n onChordProgress: (t, steps, trigger) => emitChordState({ steps, target: t, trigger, type: 'progressed' }),\n onChordStart: (t, step, trigger) => emitChordState({ step, target: t, trigger, type: 'started' }),\n onChordTimeout: (t, trigger) => emitChordState({ target: t, trigger, type: 'timeout' }),\n });\n const onKeydown = makeHandler(target, keydown);\n const onKeyup = makeHandler(target, keyup);\n\n record = { keydown, keyup, onKeydown, onKeyup, refs: 0 };\n mounted.set(target, record);\n target.addEventListener('keydown', onKeydown);\n target.addEventListener('keyup', onKeyup);\n }\n\n record.refs += 1;\n\n let unmounted = false;\n\n return () => {\n if (unmounted) return;\n\n unmounted = true;\n record!.refs -= 1;\n\n if (record?.refs > 0) return;\n\n target.removeEventListener('keydown', record?.onKeydown);\n target.removeEventListener('keyup', record?.onKeyup);\n record?.keydown.reset();\n record?.keyup.reset();\n mounted.delete(target);\n };\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n assertActive();\n\n if (!removeByKey(bindingKey(shortcut))) {\n warn(`unbind() called for unknown shortcut: \"${shortcut}\"`);\n }\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"keymap.d.ts","sourceRoot":"","sources":["../src/keymap.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,YAAY,EAAW,MAAM,EAAE,aAAa,EAAQ,MAAM,SAAS,CAAC;AAmFhG;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAgLpH"}
1
+ {"version":3,"file":"keymap.d.ts","sourceRoot":"","sources":["../src/keymap.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAgB,YAAY,EAA6B,MAAM,EAAE,aAAa,EAAQ,MAAM,SAAS,CAAC;AAyHlH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAuMpH"}
@@ -1,2 +1,2 @@
1
- var Keymap=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},n=class extends t{constructor(e){super(e)}};function r(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var i={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},a={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function o(e,t){let r=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(r.length===0)throw new n(`Invalid shortcut step: "${e}"`);let o={...i,mod:t},s=new Set,c=[];for(let e of r){let t=Object.hasOwn(o,e)?o[e]:void 0;t?s.add(t):c.push(e)}if(c.length===0)throw new n(`Invalid shortcut step: "${e}"`);if(c.length>1)throw new n(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);let l=c[0];return{key:Object.hasOwn(a,l)?a[l]:l,modifiers:s}}function s(e,t=r()){try{return o(e,t)}catch{return null}}function c(e,t=r()){let i=e.trim().split(/\s+/).filter(Boolean);if(i.length===0)throw new n(`Shortcut must contain at least one key step`);return i.map(e=>o(e,t))}function l(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function u(e,t){if(typeof e.key!=`string`||e.key.toLowerCase()!==t.key)return!1;let{modifiers:n}=t;return e.altKey===n.has(`alt`)&&e.ctrlKey===n.has(`ctrl`)&&e.metaKey===n.has(`meta`)&&e.shiftKey===n.has(`shift`)}function d(e,t){return e.length>t.length?!1:l(e)===l(t.slice(0,e.length))}function f(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n;if(e.trim()===``)return[];let a=c(e,r);return t.filter(e=>e.trigger===i&&(d(a,e.shortcut)||d(e.shortcut,a)))}var p={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},m={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},h={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},g=[`ctrl`,`alt`,`shift`,`meta`];function _(e,t=r()){if(!e.trim())return``;let n;try{n=c(e,t)}catch{return`${e}`,``}let i=t===`meta`;return n.map(e=>{let t=g.filter(t=>e.modifiers.has(t)).map(e=>i?m[e]:h[e]),n=p[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function v(e){return typeof e==`function`?{handler:e,trigger:`keydown`}:{handler:e.handler,trigger:e.trigger??`keydown`,when:e.when}}function y(e,t){let n=0,r=[],i;function a(){i!==void 0&&clearTimeout(i),i=void 0,n=0,r=[]}function o(s){let c=(n===0?e():r).filter(e=>{let t=e.shortcut[n];return t!==void 0&&u(s,t)});if(c.length===0){let e=n!==0;return a(),e?o(s):void 0}i!==void 0&&clearTimeout(i),i=void 0;let l=c.find(e=>e.shortcut.length===n+1);if(l)return a(),l;r=c,n+=1,i=setTimeout(a,t)}return{advance:o,reset:a}}function b(e={},n={}){let{chordTimeout:i=1e3,modKey:a=r(),preventDefault:o=!0,stopPropagation:s=!1,when:u}=n,d=Number.isFinite(i)&&i>0?i:1e3;d!==i&&`${i}`;let f=new Map,p=new Map,m=new AbortController,h=[],g=[],_=!1;function b(){if(_)throw new t(`Keymap is disposed`)}function x(){h=[],g=[];for(let e of f.values())e.trigger===`keydown`?h.push(e):g.push(e)}function S(e){return l(c(e,a))}function C(e,t){let n=c(e,a),r=l(n);return f.set(r,{shortcut:n,...v(t)}),x(),r}function w(e){let t=f.delete(e);return t&&x(),t}function T(e,t){return n=>{let r=(typeof n.composedPath==`function`?n.composedPath():[]).find(e=>p.has(e));if(r&&r!==e)return;let i=n;if(_||u&&!u(i))return;let a=t.advance(i);!a||a.when&&!a.when(i)||(o&&i.preventDefault(),s&&i.stopPropagation(),a.handler(i))}}for(let[t,n]of Object.entries(e))C(t,n);return{bind(e,t){b();let n=C(e,t);return()=>{_||w(n)}},get disposalSignal(){return m.signal},dispose(){if(!_){_=!0,m.abort();for(let[e,t]of p)e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset();p.clear(),f.clear(),h=[],g=[]}},get disposed(){return _},listBindings(){return[...f.values()].map(e=>({shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){b();let t=p.get(e);if(!t){let n=y(()=>h,d),r=y(()=>g,d),i=T(e,n),a=T(e,r);t={keydown:n,keyup:r,onKeydown:i,onKeyup:a,refs:0},p.set(e,t),e.addEventListener(`keydown`,i),e.addEventListener(`keyup`,a)}t.refs+=1;let n=!1;return()=>{n||(n=!0,--t.refs,!(t.refs>0)&&(e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset(),p.delete(e)))}},[Symbol.dispose](){this.dispose()},unbind(e){b(),w(S(e))||`${e}`}}}return e.KeymapError=t,e.KeymapParseError=n,e.canonicalizeShortcut=l,e.createKeymap=b,e.detectModKey=r,e.findShortcutConflicts=f,e.formatShortcut=_,e.matchStep=u,e.parseShortcut=c,e.parseStep=s,e})({});
1
+ var Keymap=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},n=class extends t{};function r(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var i={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},a={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function o(e,t){let r=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(r.length===0)throw new n(`Invalid shortcut step: "${e}"`);let o={...i,mod:t},s=new Set,c=[];for(let e of r){let t=Object.hasOwn(o,e)?o[e]:void 0;t?s.add(t):c.push(e)}if(c.length===0)throw new n(`Invalid shortcut step: "${e}"`);if(c.length>1)throw new n(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);let l=c[0];return{key:Object.hasOwn(a,l)?a[l]:l,modifiers:s}}function s(e,t=r()){try{return o(e,t)}catch{return null}}function c(e,t=r()){let i=e.trim().split(/\s+/).filter(Boolean);if(i.length===0)throw new n(`Shortcut must contain at least one key step`);return i.map(e=>o(e,t))}function l(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function u(e,t){if(typeof e.key!=`string`||e.key.toLowerCase()!==t.key)return!1;let{modifiers:n}=t;return e.altKey===n.has(`alt`)&&e.ctrlKey===n.has(`ctrl`)&&e.metaKey===n.has(`meta`)&&e.shiftKey===n.has(`shift`)}function d(e,t){return e.length>t.length?!1:l(e)===l(t.slice(0,e.length))}function f(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n;if(e.trim()===``)return[];let a=c(e,r);return t.filter(e=>e.trigger===i&&(d(a,e.shortcut)||d(e.shortcut,a)))}var p={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},m={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},h={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},g=[`ctrl`,`alt`,`shift`,`meta`];function _(e,t=r()){if(!e.trim())return``;let n;try{n=c(e,t)}catch{return`${e}`,``}let i=t===`meta`;return n.map(e=>{let t=g.filter(t=>e.modifiers.has(t)).map(e=>i?m[e]:h[e]),n=p[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function v(e){return typeof e==`function`?{handler:e,trigger:`keydown`}:{handler:e.handler,trigger:e.trigger??`keydown`,when:e.when}}function y(e,t,n,r,i){let a=0,o=[],s,c=[];function l(e=!1){if(s!==void 0&&clearTimeout(s),e&&a>0&&i?.onChordTimeout)try{i.onChordTimeout(n,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}s=void 0,a=0,o=[],c=[]}function d(f){let p=(a===0?e():o).filter(e=>{let t=e.shortcut[a];return t!==void 0&&u(f,t)});if(p.length===0){let e=a!==0;return l(),e?d(f):void 0}s!==void 0&&clearTimeout(s),s=void 0;let m=p.find(e=>e.shortcut.length===a+1);if(m)return l(),m;let h=p[0].shortcut[a];if(c=p[0].shortcut.slice(0,a+1),a===0&&i?.onChordStart)try{i.onChordStart(n,h,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}else if(a>0&&i?.onChordProgress)try{i.onChordProgress(n,c,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}o=p,a+=1,s=setTimeout(()=>l(!0),t)}return{advance:d,reset:()=>l()}}function b(e={},n={}){let{chordTimeout:i=1e3,modKey:a=r(),preventDefault:o=!0,stopPropagation:s=!1,when:u,onChordState:d}=n,f=Number.isFinite(i)&&i>0?i:1e3;f!==i&&`${i}`;let p=new Map,m=new Map,h=new AbortController,g=[],_=[],b=!1;function x(){if(b)throw new t(`Keymap is disposed`)}function S(){g=[],_=[];for(let e of p.values())e.trigger===`keydown`?g.push(e):_.push(e)}function C(e){return l(c(e,a))}function w(e,t){let n=c(e,a),r=l(n);return p.set(r,{shortcut:n,...v(t)}),S(),r}function T(e){let t=p.delete(e);return t&&S(),t}function E(e){if(d)try{d(e)}catch(e){`${e instanceof Error?e.message:String(e)}`}}function D(e,t){return n=>{let r=(typeof n.composedPath==`function`?n.composedPath():[]).find(e=>m.has(e));if(r&&r!==e)return;let i=n;if(b)return;let a=t.advance(i);u&&!u(i)||!a||a.when&&!a.when(i)||(o&&i.preventDefault(),s&&i.stopPropagation(),a.handler(i))}}for(let[t,n]of Object.entries(e))w(t,n);return{bind(e,t){x();let n=w(e,t);return()=>{b||T(n)}},get disposalSignal(){return h.signal},dispose(){if(!b){b=!0,h.abort();for(let[e,t]of m)e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset();m.clear(),p.clear(),g=[],_=[]}},get disposed(){return b},listBindings(){return[...p.values()].map(e=>({shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){x();let t=m.get(e);if(!t){let n=y(()=>g,f,e,`keydown`,{onChordProgress:(e,t,n)=>E({steps:t,target:e,trigger:n,type:`progressed`}),onChordStart:(e,t,n)=>E({step:t,target:e,trigger:n,type:`started`}),onChordTimeout:(e,t)=>E({target:e,trigger:t,type:`timeout`})}),r=y(()=>_,f,e,`keyup`,{onChordProgress:(e,t,n)=>E({steps:t,target:e,trigger:n,type:`progressed`}),onChordStart:(e,t,n)=>E({step:t,target:e,trigger:n,type:`started`}),onChordTimeout:(e,t)=>E({target:e,trigger:t,type:`timeout`})}),i=D(e,n),a=D(e,r);t={keydown:n,keyup:r,onKeydown:i,onKeyup:a,refs:0},m.set(e,t),e.addEventListener(`keydown`,i),e.addEventListener(`keyup`,a)}t.refs+=1;let n=!1;return()=>{n||(n=!0,--t.refs,!(t?.refs>0)&&(e.removeEventListener(`keydown`,t?.onKeydown),e.removeEventListener(`keyup`,t?.onKeyup),t?.keydown.reset(),t?.keyup.reset(),m.delete(e)))}},[Symbol.dispose](){this.dispose()},unbind(e){x(),T(C(e))||`${e}`}}}return e.KeymapError=t,e.KeymapParseError=n,e.canonicalizeShortcut=l,e.createKeymap=b,e.detectModKey=r,e.findShortcutConflicts=f,e.formatShortcut=_,e.matchStep=u,e.parseShortcut=c,e.parseStep=s,e})({});
2
2
  //# sourceMappingURL=keymap.iife.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keymap.iife.js","names":[],"sources":["../src/errors.ts","../src/parser.ts","../src/conflicts.ts","../src/_dev.ts","../src/format.ts","../src/keymap.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is KeymapError {\n return err instanceof KeymapError;\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {\n constructor(message: string) {\n super(message);\n }\n}\n","import { KeymapParseError } from './errors';\n\nexport type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\nexport type ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\nexport type Shortcut = ShortcutStep[];\n\nexport function detectModKey(): 'ctrl' | 'meta' {\n if (typeof navigator === 'undefined') return 'ctrl';\n\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n\n return /mac/i.test(platform) ? 'meta' : 'ctrl';\n}\n\nconst MODIFIER_ALIASES_BASE: Record<string, ModifierKey> = {\n alt: 'alt',\n cmd: 'meta',\n command: 'meta',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n opt: 'alt',\n option: 'alt',\n shift: 'shift',\n win: 'meta',\n};\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n del: 'delete',\n down: 'arrowdown',\n esc: 'escape',\n left: 'arrowleft',\n right: 'arrowright',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n};\n\nfunction parseStepStrict(raw: string, modKey: 'ctrl' | 'meta'): ShortcutStep {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((part) => part.toLowerCase());\n\n if (parts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n const aliases: Record<string, ModifierKey> = { ...MODIFIER_ALIASES_BASE, mod: modKey };\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const modifier = Object.hasOwn(aliases, part) ? aliases[part]! : undefined;\n\n if (modifier) modifiers.add(modifier);\n else keyParts.push(part);\n }\n\n if (keyParts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const rawKey = keyParts[0]!;\n const key = Object.hasOwn(SPECIAL_KEY_ALIASES, rawKey) ? SPECIAL_KEY_ALIASES[rawKey]! : rawKey;\n\n return { key, modifiers };\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n try {\n return parseStepStrict(raw, modKey);\n } catch {\n return null;\n }\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n const steps = raw.trim().split(/\\s+/).filter(Boolean);\n\n if (steps.length === 0) throw new KeymapParseError('Shortcut must contain at least one key step');\n\n return steps.map((step) => parseStepStrict(step, modKey));\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((step) => {\n const modifiers = [...step.modifiers].sort().join('+');\n\n return modifiers ? `${modifiers}+${step.key}` : step.key;\n })\n .join(' ');\n}\n\nexport function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean {\n // Headless usage (SSR, non-DOM `EventTarget`s, hand-built test events) means `event` isn't\n // guaranteed to be a real `KeyboardEvent` at runtime even though the type says so — treat a\n // missing/non-string `key` as \"doesn't match\" rather than throwing inside the event listener.\n if (typeof event.key !== 'string') return false;\n\n if (event.key.toLowerCase() !== step.key) return false;\n\n const { modifiers } = step;\n\n return (\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift')\n );\n}\n","import type { ShortcutStep } from './parser';\nimport type { BindingEntry } from './types';\n\nimport { canonicalizeShortcut, parseShortcut } from './parser';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n","const isDev = !(globalThis as { __KEYMAP_PROD__?: boolean }).__KEYMAP_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/keymap] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n","import type { Shortcut } from './parser';\nimport type { BindingEntry, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';\n\nimport { warn } from './_dev';\nimport { KeymapError } from './errors';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\n\ntype ParsedBinding = {\n handler: Handler;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: When;\n};\n\ntype ChordTracker = ReturnType<typeof createChordTracker>;\ntype MountedTarget = {\n keydown: ChordTracker;\n keyup: ChordTracker;\n onKeydown: EventListener;\n onKeyup: EventListener;\n refs: number;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') return { handler: value, trigger: 'keydown' };\n\n return {\n handler: value.handler,\n trigger: value.trigger ?? 'keydown',\n when: value.when,\n };\n}\n\nfunction createChordTracker(getBindings: () => ParsedBinding[], chordTimeout: number) {\n let pendingIndex = 0;\n let candidates: ParsedBinding[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n function reset(): void {\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n pendingIndex = 0;\n candidates = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | undefined {\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched = pool.filter((binding) => {\n const step = binding.shortcut[pendingIndex];\n\n return step !== undefined && matchStep(event, step);\n });\n\n if (matched.length === 0) {\n const retryFromRoot = pendingIndex !== 0;\n\n reset();\n\n return retryFromRoot ? advance(event) : undefined;\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n\n const completed = matched.find((binding) => binding.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n candidates = matched;\n pendingIndex += 1;\n timer = setTimeout(reset, chordTimeout);\n\n return undefined;\n }\n\n return { advance, reset };\n}\n\n/**\n * Creates a headless keyboard shortcut manager with target-local chord state.\n *\n * Pass a bindings map of shortcut strings to handlers or `BindingOptions`, then call\n * `.mount(target)` to attach to any `EventTarget`. Supports chord sequences\n * (e.g. `\"ctrl+k ctrl+s\"`), per-binding `when` guards, `trigger` (keydown/keyup),\n * and dynamic `bind`/`unbind`.\n *\n * @example\n * const map = createKeymap({\n * 'mod+k mod+s': () => save(),\n * 'mod+shift+p': () => openPalette(),\n * 'g g': () => goToTop(),\n * esc: { handler: closePanel, when: (event) => !isEditableTarget(event.target) },\n * space: { handler: togglePlay, trigger: 'keyup' },\n * }, { modKey: 'ctrl' });\n * const unmount = map.mount(document);\n */\nexport function createKeymap(initialBindings: Record<string, BindingValue> = {}, options: KeymapOptions = {}): Keymap {\n const {\n chordTimeout: rawChordTimeout = 1000,\n modKey = detectModKey(),\n preventDefault = true,\n stopPropagation = false,\n when: globalWhen,\n } = options;\n const chordTimeout = Number.isFinite(rawChordTimeout) && rawChordTimeout > 0 ? rawChordTimeout : 1000;\n\n if (chordTimeout !== rawChordTimeout) {\n warn(`chordTimeout must be a positive finite number; received ${rawChordTimeout}. Using default of 1000ms.`);\n }\n\n const bindings = new Map<string, ParsedBinding>();\n const mounted = new Map<EventTarget, MountedTarget>();\n const disposalController = new AbortController();\n let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n let disposed = false;\n\n function assertActive(): void {\n if (disposed) throw new KeymapError('Keymap is disposed');\n }\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const binding of bindings.values()) {\n if (binding.trigger === 'keydown') bindingsDown.push(binding);\n else bindingsUp.push(binding);\n }\n }\n\n function bindingKey(shortcut: string): string {\n return canonicalizeShortcut(parseShortcut(shortcut, modKey));\n }\n\n function addBinding(shortcut: string, value: BindingValue): string {\n const parsed = parseShortcut(shortcut, modKey);\n const key = canonicalizeShortcut(parsed);\n\n bindings.set(key, { shortcut: parsed, ...resolveBinding(value) });\n rebuildTriggerCaches();\n\n return key;\n }\n\n function removeByKey(key: string): boolean {\n const existed = bindings.delete(key);\n\n if (existed) rebuildTriggerCaches();\n\n return existed;\n }\n\n function makeHandler(target: EventTarget, chord: ChordTracker): EventListener {\n return (event) => {\n const path = typeof event.composedPath === 'function' ? event.composedPath() : [];\n const nearestMountedTarget = path.find((pathTarget) => mounted.has(pathTarget));\n\n if (nearestMountedTarget && nearestMountedTarget !== target) return;\n\n const keyboardEvent = event as KeyboardEvent;\n\n if (disposed || (globalWhen && !globalWhen(keyboardEvent))) return;\n\n const binding = chord.advance(keyboardEvent);\n\n if (!binding || (binding.when && !binding.when(keyboardEvent))) return;\n\n if (preventDefault) keyboardEvent.preventDefault();\n\n if (stopPropagation) keyboardEvent.stopPropagation();\n\n binding.handler(keyboardEvent);\n };\n }\n\n for (const [shortcut, value] of Object.entries(initialBindings)) addBinding(shortcut, value);\n\n return {\n bind(shortcut: string, value: BindingValue): () => void {\n assertActive();\n\n const key = addBinding(shortcut, value);\n\n return () => {\n if (!disposed) removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n for (const [target, record] of mounted) {\n target.removeEventListener('keydown', record.onKeydown);\n target.removeEventListener('keyup', record.onKeyup);\n record.keydown.reset();\n record.keyup.reset();\n }\n\n mounted.clear();\n bindings.clear();\n bindingsDown = [];\n bindingsUp = [];\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((binding) => ({\n shortcut: binding.shortcut.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) })),\n trigger: binding.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n assertActive();\n\n let record = mounted.get(target);\n\n if (!record) {\n const keydown = createChordTracker(() => bindingsDown, chordTimeout);\n const keyup = createChordTracker(() => bindingsUp, chordTimeout);\n const onKeydown = makeHandler(target, keydown);\n const onKeyup = makeHandler(target, keyup);\n\n record = { keydown, keyup, onKeydown, onKeyup, refs: 0 };\n mounted.set(target, record);\n target.addEventListener('keydown', onKeydown);\n target.addEventListener('keyup', onKeyup);\n }\n\n record.refs += 1;\n\n let unmounted = false;\n\n return () => {\n if (unmounted) return;\n\n unmounted = true;\n record!.refs -= 1;\n\n if (record!.refs > 0) return;\n\n target.removeEventListener('keydown', record!.onKeydown);\n target.removeEventListener('keyup', record!.onKeyup);\n record!.keydown.reset();\n record!.keyup.reset();\n mounted.delete(target);\n };\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n assertActive();\n\n if (!removeByKey(bindingKey(shortcut))) {\n warn(`unbind() called for unknown shortcut: \"${shortcut}\"`);\n }\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"keymap.iife.js","names":[],"sources":["../src/errors.ts","../src/parser.ts","../src/conflicts.ts","../src/_dev.ts","../src/format.ts","../src/keymap.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n","import { KeymapParseError } from './errors';\n\nexport type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\nexport type ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\nexport type Shortcut = ShortcutStep[];\n\nexport function detectModKey(): 'ctrl' | 'meta' {\n if (typeof navigator === 'undefined') return 'ctrl';\n\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n\n return /mac/i.test(platform) ? 'meta' : 'ctrl';\n}\n\nconst MODIFIER_ALIASES_BASE: Record<string, ModifierKey> = {\n alt: 'alt',\n cmd: 'meta',\n command: 'meta',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n opt: 'alt',\n option: 'alt',\n shift: 'shift',\n win: 'meta',\n};\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n del: 'delete',\n down: 'arrowdown',\n esc: 'escape',\n left: 'arrowleft',\n right: 'arrowright',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n};\n\nfunction parseStepStrict(raw: string, modKey: 'ctrl' | 'meta'): ShortcutStep {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((part) => part.toLowerCase());\n\n if (parts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n const aliases: Record<string, ModifierKey> = { ...MODIFIER_ALIASES_BASE, mod: modKey };\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const modifier = Object.hasOwn(aliases, part) ? aliases[part]! : undefined;\n\n if (modifier) modifiers.add(modifier);\n else keyParts.push(part);\n }\n\n if (keyParts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const rawKey = keyParts[0]!;\n const key = Object.hasOwn(SPECIAL_KEY_ALIASES, rawKey) ? SPECIAL_KEY_ALIASES[rawKey]! : rawKey;\n\n return { key, modifiers };\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n try {\n return parseStepStrict(raw, modKey);\n } catch {\n return null;\n }\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n const steps = raw.trim().split(/\\s+/).filter(Boolean);\n\n if (steps.length === 0) throw new KeymapParseError('Shortcut must contain at least one key step');\n\n return steps.map((step) => parseStepStrict(step, modKey));\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((step) => {\n const modifiers = [...step.modifiers].sort().join('+');\n\n return modifiers ? `${modifiers}+${step.key}` : step.key;\n })\n .join(' ');\n}\n\nexport function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean {\n // Headless usage (SSR, non-DOM `EventTarget`s, hand-built test events) means `event` isn't\n // guaranteed to be a real `KeyboardEvent` at runtime even though the type says so — treat a\n // missing/non-string `key` as \"doesn't match\" rather than throwing inside the event listener.\n if (typeof event.key !== 'string') return false;\n\n if (event.key.toLowerCase() !== step.key) return false;\n\n const { modifiers } = step;\n\n return (\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift')\n );\n}\n","import type { ShortcutStep } from './parser';\nimport { canonicalizeShortcut, parseShortcut } from './parser';\nimport type { BindingEntry } from './types';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n","const isDev = !(globalThis as { __KEYMAP_PROD__?: boolean }).__KEYMAP_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/keymap] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps: ReturnType<typeof parseShortcut>;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n","import { warn } from './_dev';\nimport { KeymapError } from './errors';\nimport type { Shortcut, ShortcutStep } from './parser';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\nimport type { BindingEntry, BindingValue, ChordStateChange, Handler, Keymap, KeymapOptions, When } from './types';\n\ntype ParsedBinding = {\n handler: Handler;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: When;\n};\n\ntype ChordTracker = ReturnType<typeof createChordTracker>;\ntype MountedTarget = {\n keydown: ChordTracker;\n keyup: ChordTracker;\n onKeydown: EventListener;\n onKeyup: EventListener;\n refs: number;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') return { handler: value, trigger: 'keydown' };\n\n return {\n handler: value.handler,\n trigger: value.trigger ?? 'keydown',\n when: value.when,\n };\n}\n\ntype ChordTrackerCallbacks = {\n onChordStart?: (target: EventTarget, step: ShortcutStep, trigger: 'keydown' | 'keyup') => void;\n onChordProgress?: (target: EventTarget, steps: readonly ShortcutStep[], trigger: 'keydown' | 'keyup') => void;\n onChordTimeout?: (target: EventTarget, trigger: 'keydown' | 'keyup') => void;\n};\n\nfunction createChordTracker(\n getBindings: () => ParsedBinding[],\n chordTimeout: number,\n target: EventTarget,\n trigger: 'keydown' | 'keyup',\n callbacks?: ChordTrackerCallbacks,\n) {\n let pendingIndex = 0;\n let candidates: ParsedBinding[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n let pendingSteps: ShortcutStep[] = [];\n\n function reset(emitTimeout = false): void {\n if (timer !== undefined) clearTimeout(timer);\n\n // Emit timeout only if explicitly requested (from timeout event)\n if (emitTimeout && pendingIndex > 0 && callbacks?.onChordTimeout) {\n try {\n callbacks.onChordTimeout(target, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n timer = undefined;\n pendingIndex = 0;\n candidates = [];\n pendingSteps = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | undefined {\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched = pool.filter((binding) => {\n const step = binding.shortcut[pendingIndex];\n\n return step !== undefined && matchStep(event, step);\n });\n\n if (matched.length === 0) {\n const retryFromRoot = pendingIndex !== 0;\n\n reset();\n\n return retryFromRoot ? advance(event) : undefined;\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n\n const completed = matched.find((binding) => binding.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n // New step in chord progression\n const currentStep = matched[0]!.shortcut[pendingIndex];\n pendingSteps = matched[0]!.shortcut.slice(0, pendingIndex + 1);\n\n // Emit started or progressed\n if (pendingIndex === 0 && callbacks?.onChordStart) {\n try {\n callbacks.onChordStart(target, currentStep, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n } else if (pendingIndex > 0 && callbacks?.onChordProgress) {\n try {\n callbacks.onChordProgress(target, pendingSteps, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n candidates = matched;\n pendingIndex += 1;\n timer = setTimeout(() => reset(true), chordTimeout);\n\n return undefined;\n }\n\n return { advance, reset: () => reset() };\n}\n\n/**\n * Creates a headless keyboard shortcut manager with target-local chord state.\n *\n * Pass a bindings map of shortcut strings to handlers or `BindingOptions`, then call\n * `.mount(target)` to attach to any `EventTarget`. Supports chord sequences\n * (e.g. `\"ctrl+k ctrl+s\"`), per-binding `when` guards, `trigger` (keydown/keyup),\n * and dynamic `bind`/`unbind`.\n *\n * @example\n * const map = createKeymap({\n * 'mod+k mod+s': () => save(),\n * 'mod+shift+p': () => openPalette(),\n * 'g g': () => goToTop(),\n * esc: { handler: closePanel, when: (event) => !isEditableTarget(event.target) },\n * space: { handler: togglePlay, trigger: 'keyup' },\n * }, { modKey: 'ctrl' });\n * const unmount = map.mount(document);\n */\nexport function createKeymap(initialBindings: Record<string, BindingValue> = {}, options: KeymapOptions = {}): Keymap {\n const {\n chordTimeout: rawChordTimeout = 1000,\n modKey = detectModKey(),\n preventDefault = true,\n stopPropagation = false,\n when: globalWhen,\n onChordState: userOnChordState,\n } = options;\n const chordTimeout = Number.isFinite(rawChordTimeout) && rawChordTimeout > 0 ? rawChordTimeout : 1000;\n\n if (chordTimeout !== rawChordTimeout) {\n warn(`chordTimeout must be a positive finite number; received ${rawChordTimeout}. Using default of 1000ms.`);\n }\n\n const bindings = new Map<string, ParsedBinding>();\n const mounted = new Map<EventTarget, MountedTarget>();\n const disposalController = new AbortController();\n let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n let disposed = false;\n\n function assertActive(): void {\n if (disposed) throw new KeymapError('Keymap is disposed');\n }\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const binding of bindings.values()) {\n if (binding.trigger === 'keydown') bindingsDown.push(binding);\n else bindingsUp.push(binding);\n }\n }\n\n function bindingKey(shortcut: string): string {\n return canonicalizeShortcut(parseShortcut(shortcut, modKey));\n }\n\n function addBinding(shortcut: string, value: BindingValue): string {\n const parsed = parseShortcut(shortcut, modKey);\n const key = canonicalizeShortcut(parsed);\n\n bindings.set(key, { shortcut: parsed, ...resolveBinding(value) });\n rebuildTriggerCaches();\n\n return key;\n }\n\n function removeByKey(key: string): boolean {\n const existed = bindings.delete(key);\n\n if (existed) rebuildTriggerCaches();\n\n return existed;\n }\n\n function emitChordState(change: ChordStateChange): void {\n if (!userOnChordState) return;\n\n try {\n userOnChordState(change);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n function makeHandler(target: EventTarget, chord: ChordTracker): EventListener {\n return (event) => {\n const path = typeof event.composedPath === 'function' ? event.composedPath() : [];\n const nearestMountedTarget = path.find((pathTarget) => mounted.has(pathTarget));\n\n if (nearestMountedTarget && nearestMountedTarget !== target) return;\n\n const keyboardEvent = event as KeyboardEvent;\n\n if (disposed) return;\n\n // Track chord progression (independent of guards)\n const binding = chord.advance(keyboardEvent);\n\n // Check guards AFTER tracking chord\n if (globalWhen && !globalWhen(keyboardEvent)) return;\n\n if (!binding || (binding.when && !binding.when(keyboardEvent))) return;\n\n if (preventDefault) keyboardEvent.preventDefault();\n\n if (stopPropagation) keyboardEvent.stopPropagation();\n\n binding.handler(keyboardEvent);\n };\n }\n\n for (const [shortcut, value] of Object.entries(initialBindings)) addBinding(shortcut, value);\n\n return {\n bind(shortcut: string, value: BindingValue): () => void {\n assertActive();\n\n const key = addBinding(shortcut, value);\n\n return () => {\n if (!disposed) removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n for (const [target, record] of mounted) {\n target.removeEventListener('keydown', record.onKeydown);\n target.removeEventListener('keyup', record.onKeyup);\n record.keydown.reset();\n record.keyup.reset();\n }\n\n mounted.clear();\n bindings.clear();\n bindingsDown = [];\n bindingsUp = [];\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((binding) => ({\n shortcut: binding.shortcut.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) })),\n trigger: binding.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n assertActive();\n\n let record = mounted.get(target);\n\n if (!record) {\n const keydown = createChordTracker(() => bindingsDown, chordTimeout, target, 'keydown', {\n onChordProgress: (t, steps, trigger) => emitChordState({ steps, target: t, trigger, type: 'progressed' }),\n onChordStart: (t, step, trigger) => emitChordState({ step, target: t, trigger, type: 'started' }),\n onChordTimeout: (t, trigger) => emitChordState({ target: t, trigger, type: 'timeout' }),\n });\n const keyup = createChordTracker(() => bindingsUp, chordTimeout, target, 'keyup', {\n onChordProgress: (t, steps, trigger) => emitChordState({ steps, target: t, trigger, type: 'progressed' }),\n onChordStart: (t, step, trigger) => emitChordState({ step, target: t, trigger, type: 'started' }),\n onChordTimeout: (t, trigger) => emitChordState({ target: t, trigger, type: 'timeout' }),\n });\n const onKeydown = makeHandler(target, keydown);\n const onKeyup = makeHandler(target, keyup);\n\n record = { keydown, keyup, onKeydown, onKeyup, refs: 0 };\n mounted.set(target, record);\n target.addEventListener('keydown', onKeydown);\n target.addEventListener('keyup', onKeyup);\n }\n\n record.refs += 1;\n\n let unmounted = false;\n\n return () => {\n if (unmounted) return;\n\n unmounted = true;\n record!.refs -= 1;\n\n if (record?.refs > 0) return;\n\n target.removeEventListener('keydown', record?.onKeydown);\n target.removeEventListener('keyup', record?.onKeyup);\n record?.keydown.reset();\n record?.keyup.reset();\n mounted.delete(target);\n };\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n assertActive();\n\n if (!removeByKey(bindingKey(shortcut))) {\n warn(`unbind() called for unknown shortcut: \"${shortcut}\"`);\n }\n },\n };\n}\n"],"mappings":"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"}
package/dist/keymap.js CHANGED
@@ -1,2 +1,2 @@
1
- var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{constructor(e){super(e)}};function n(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var r={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},i={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function a(e,n){let a=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(a.length===0)throw new t(`Invalid shortcut step: "${e}"`);let o={...r,mod:n},s=new Set,c=[];for(let e of a){let t=Object.hasOwn(o,e)?o[e]:void 0;t?s.add(t):c.push(e)}if(c.length===0)throw new t(`Invalid shortcut step: "${e}"`);if(c.length>1)throw new t(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);let l=c[0];return{key:Object.hasOwn(i,l)?i[l]:l,modifiers:s}}function o(e,t=n()){try{return a(e,t)}catch{return null}}function s(e,r=n()){let i=e.trim().split(/\s+/).filter(Boolean);if(i.length===0)throw new t(`Shortcut must contain at least one key step`);return i.map(e=>a(e,r))}function c(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function l(e,t){if(typeof e.key!=`string`||e.key.toLowerCase()!==t.key)return!1;let{modifiers:n}=t;return e.altKey===n.has(`alt`)&&e.ctrlKey===n.has(`ctrl`)&&e.metaKey===n.has(`meta`)&&e.shiftKey===n.has(`shift`)}function u(e,t){return e.length>t.length?!1:c(e)===c(t.slice(0,e.length))}function d(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n;if(e.trim()===``)return[];let a=s(e,r);return t.filter(e=>e.trigger===i&&(u(a,e.shortcut)||u(e.shortcut,a)))}var f={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},p={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},m={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},h=[`ctrl`,`alt`,`shift`,`meta`];function g(e,t=n()){if(!e.trim())return``;let r;try{r=s(e,t)}catch{return`${e}`,``}let i=t===`meta`;return r.map(e=>{let t=h.filter(t=>e.modifiers.has(t)).map(e=>i?p[e]:m[e]),n=f[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function _(e){return typeof e==`function`?{handler:e,trigger:`keydown`}:{handler:e.handler,trigger:e.trigger??`keydown`,when:e.when}}function v(e,t){let n=0,r=[],i;function a(){i!==void 0&&clearTimeout(i),i=void 0,n=0,r=[]}function o(s){let c=(n===0?e():r).filter(e=>{let t=e.shortcut[n];return t!==void 0&&l(s,t)});if(c.length===0){let e=n!==0;return a(),e?o(s):void 0}i!==void 0&&clearTimeout(i),i=void 0;let u=c.find(e=>e.shortcut.length===n+1);if(u)return a(),u;r=c,n+=1,i=setTimeout(a,t)}return{advance:o,reset:a}}function y(t={},r={}){let{chordTimeout:i=1e3,modKey:a=n(),preventDefault:o=!0,stopPropagation:l=!1,when:u}=r,d=Number.isFinite(i)&&i>0?i:1e3;d!==i&&`${i}`;let f=new Map,p=new Map,m=new AbortController,h=[],g=[],y=!1;function b(){if(y)throw new e(`Keymap is disposed`)}function x(){h=[],g=[];for(let e of f.values())e.trigger===`keydown`?h.push(e):g.push(e)}function S(e){return c(s(e,a))}function C(e,t){let n=s(e,a),r=c(n);return f.set(r,{shortcut:n,..._(t)}),x(),r}function w(e){let t=f.delete(e);return t&&x(),t}function T(e,t){return n=>{let r=(typeof n.composedPath==`function`?n.composedPath():[]).find(e=>p.has(e));if(r&&r!==e)return;let i=n;if(y||u&&!u(i))return;let a=t.advance(i);!a||a.when&&!a.when(i)||(o&&i.preventDefault(),l&&i.stopPropagation(),a.handler(i))}}for(let[e,n]of Object.entries(t))C(e,n);return{bind(e,t){b();let n=C(e,t);return()=>{y||w(n)}},get disposalSignal(){return m.signal},dispose(){if(!y){y=!0,m.abort();for(let[e,t]of p)e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset();p.clear(),f.clear(),h=[],g=[]}},get disposed(){return y},listBindings(){return[...f.values()].map(e=>({shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){b();let t=p.get(e);if(!t){let n=v(()=>h,d),r=v(()=>g,d),i=T(e,n),a=T(e,r);t={keydown:n,keyup:r,onKeydown:i,onKeyup:a,refs:0},p.set(e,t),e.addEventListener(`keydown`,i),e.addEventListener(`keyup`,a)}t.refs+=1;let n=!1;return()=>{n||(n=!0,--t.refs,!(t.refs>0)&&(e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset(),p.delete(e)))}},[Symbol.dispose](){this.dispose()},unbind(e){b(),w(S(e))||`${e}`}}}export{e as KeymapError,t as KeymapParseError,c as canonicalizeShortcut,y as createKeymap,n as detectModKey,d as findShortcutConflicts,g as formatShortcut,l as matchStep,s as parseShortcut,o as parseStep};
1
+ var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{};function n(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var r={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},i={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function a(e,n){let a=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(a.length===0)throw new t(`Invalid shortcut step: "${e}"`);let o={...r,mod:n},s=new Set,c=[];for(let e of a){let t=Object.hasOwn(o,e)?o[e]:void 0;t?s.add(t):c.push(e)}if(c.length===0)throw new t(`Invalid shortcut step: "${e}"`);if(c.length>1)throw new t(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);let l=c[0];return{key:Object.hasOwn(i,l)?i[l]:l,modifiers:s}}function o(e,t=n()){try{return a(e,t)}catch{return null}}function s(e,r=n()){let i=e.trim().split(/\s+/).filter(Boolean);if(i.length===0)throw new t(`Shortcut must contain at least one key step`);return i.map(e=>a(e,r))}function c(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function l(e,t){if(typeof e.key!=`string`||e.key.toLowerCase()!==t.key)return!1;let{modifiers:n}=t;return e.altKey===n.has(`alt`)&&e.ctrlKey===n.has(`ctrl`)&&e.metaKey===n.has(`meta`)&&e.shiftKey===n.has(`shift`)}function u(e,t){return e.length>t.length?!1:c(e)===c(t.slice(0,e.length))}function d(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n;if(e.trim()===``)return[];let a=s(e,r);return t.filter(e=>e.trigger===i&&(u(a,e.shortcut)||u(e.shortcut,a)))}var f={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},p={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},m={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},h=[`ctrl`,`alt`,`shift`,`meta`];function g(e,t=n()){if(!e.trim())return``;let r;try{r=s(e,t)}catch{return`${e}`,``}let i=t===`meta`;return r.map(e=>{let t=h.filter(t=>e.modifiers.has(t)).map(e=>i?p[e]:m[e]),n=f[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function _(e){return typeof e==`function`?{handler:e,trigger:`keydown`}:{handler:e.handler,trigger:e.trigger??`keydown`,when:e.when}}function v(e,t,n,r,i){let a=0,o=[],s,c=[];function u(e=!1){if(s!==void 0&&clearTimeout(s),e&&a>0&&i?.onChordTimeout)try{i.onChordTimeout(n,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}s=void 0,a=0,o=[],c=[]}function d(f){let p=(a===0?e():o).filter(e=>{let t=e.shortcut[a];return t!==void 0&&l(f,t)});if(p.length===0){let e=a!==0;return u(),e?d(f):void 0}s!==void 0&&clearTimeout(s),s=void 0;let m=p.find(e=>e.shortcut.length===a+1);if(m)return u(),m;let h=p[0].shortcut[a];if(c=p[0].shortcut.slice(0,a+1),a===0&&i?.onChordStart)try{i.onChordStart(n,h,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}else if(a>0&&i?.onChordProgress)try{i.onChordProgress(n,c,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}o=p,a+=1,s=setTimeout(()=>u(!0),t)}return{advance:d,reset:()=>u()}}function y(t={},r={}){let{chordTimeout:i=1e3,modKey:a=n(),preventDefault:o=!0,stopPropagation:l=!1,when:u,onChordState:d}=r,f=Number.isFinite(i)&&i>0?i:1e3;f!==i&&`${i}`;let p=new Map,m=new Map,h=new AbortController,g=[],y=[],b=!1;function x(){if(b)throw new e(`Keymap is disposed`)}function S(){g=[],y=[];for(let e of p.values())e.trigger===`keydown`?g.push(e):y.push(e)}function C(e){return c(s(e,a))}function w(e,t){let n=s(e,a),r=c(n);return p.set(r,{shortcut:n,..._(t)}),S(),r}function T(e){let t=p.delete(e);return t&&S(),t}function E(e){if(d)try{d(e)}catch(e){`${e instanceof Error?e.message:String(e)}`}}function D(e,t){return n=>{let r=(typeof n.composedPath==`function`?n.composedPath():[]).find(e=>m.has(e));if(r&&r!==e)return;let i=n;if(b)return;let a=t.advance(i);u&&!u(i)||!a||a.when&&!a.when(i)||(o&&i.preventDefault(),l&&i.stopPropagation(),a.handler(i))}}for(let[e,n]of Object.entries(t))w(e,n);return{bind(e,t){x();let n=w(e,t);return()=>{b||T(n)}},get disposalSignal(){return h.signal},dispose(){if(!b){b=!0,h.abort();for(let[e,t]of m)e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset();m.clear(),p.clear(),g=[],y=[]}},get disposed(){return b},listBindings(){return[...p.values()].map(e=>({shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){x();let t=m.get(e);if(!t){let n=v(()=>g,f,e,`keydown`,{onChordProgress:(e,t,n)=>E({steps:t,target:e,trigger:n,type:`progressed`}),onChordStart:(e,t,n)=>E({step:t,target:e,trigger:n,type:`started`}),onChordTimeout:(e,t)=>E({target:e,trigger:t,type:`timeout`})}),r=v(()=>y,f,e,`keyup`,{onChordProgress:(e,t,n)=>E({steps:t,target:e,trigger:n,type:`progressed`}),onChordStart:(e,t,n)=>E({step:t,target:e,trigger:n,type:`started`}),onChordTimeout:(e,t)=>E({target:e,trigger:t,type:`timeout`})}),i=D(e,n),a=D(e,r);t={keydown:n,keyup:r,onKeydown:i,onKeyup:a,refs:0},m.set(e,t),e.addEventListener(`keydown`,i),e.addEventListener(`keyup`,a)}t.refs+=1;let n=!1;return()=>{n||(n=!0,--t.refs,!(t?.refs>0)&&(e.removeEventListener(`keydown`,t?.onKeydown),e.removeEventListener(`keyup`,t?.onKeyup),t?.keydown.reset(),t?.keyup.reset(),m.delete(e)))}},[Symbol.dispose](){this.dispose()},unbind(e){x(),T(C(e))||`${e}`}}}export{e as KeymapError,t as KeymapParseError,c as canonicalizeShortcut,y as createKeymap,n as detectModKey,d as findShortcutConflicts,g as formatShortcut,l as matchStep,s as parseShortcut,o as parseStep};
2
2
  //# sourceMappingURL=keymap.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keymap.js","names":[],"sources":["../src/errors.ts","../src/parser.ts","../src/conflicts.ts","../src/_dev.ts","../src/format.ts","../src/keymap.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is KeymapError {\n return err instanceof KeymapError;\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {\n constructor(message: string) {\n super(message);\n }\n}\n","import { KeymapParseError } from './errors';\n\nexport type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\nexport type ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\nexport type Shortcut = ShortcutStep[];\n\nexport function detectModKey(): 'ctrl' | 'meta' {\n if (typeof navigator === 'undefined') return 'ctrl';\n\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n\n return /mac/i.test(platform) ? 'meta' : 'ctrl';\n}\n\nconst MODIFIER_ALIASES_BASE: Record<string, ModifierKey> = {\n alt: 'alt',\n cmd: 'meta',\n command: 'meta',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n opt: 'alt',\n option: 'alt',\n shift: 'shift',\n win: 'meta',\n};\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n del: 'delete',\n down: 'arrowdown',\n esc: 'escape',\n left: 'arrowleft',\n right: 'arrowright',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n};\n\nfunction parseStepStrict(raw: string, modKey: 'ctrl' | 'meta'): ShortcutStep {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((part) => part.toLowerCase());\n\n if (parts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n const aliases: Record<string, ModifierKey> = { ...MODIFIER_ALIASES_BASE, mod: modKey };\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const modifier = Object.hasOwn(aliases, part) ? aliases[part]! : undefined;\n\n if (modifier) modifiers.add(modifier);\n else keyParts.push(part);\n }\n\n if (keyParts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const rawKey = keyParts[0]!;\n const key = Object.hasOwn(SPECIAL_KEY_ALIASES, rawKey) ? SPECIAL_KEY_ALIASES[rawKey]! : rawKey;\n\n return { key, modifiers };\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n try {\n return parseStepStrict(raw, modKey);\n } catch {\n return null;\n }\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n const steps = raw.trim().split(/\\s+/).filter(Boolean);\n\n if (steps.length === 0) throw new KeymapParseError('Shortcut must contain at least one key step');\n\n return steps.map((step) => parseStepStrict(step, modKey));\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((step) => {\n const modifiers = [...step.modifiers].sort().join('+');\n\n return modifiers ? `${modifiers}+${step.key}` : step.key;\n })\n .join(' ');\n}\n\nexport function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean {\n // Headless usage (SSR, non-DOM `EventTarget`s, hand-built test events) means `event` isn't\n // guaranteed to be a real `KeyboardEvent` at runtime even though the type says so — treat a\n // missing/non-string `key` as \"doesn't match\" rather than throwing inside the event listener.\n if (typeof event.key !== 'string') return false;\n\n if (event.key.toLowerCase() !== step.key) return false;\n\n const { modifiers } = step;\n\n return (\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift')\n );\n}\n","import type { ShortcutStep } from './parser';\nimport type { BindingEntry } from './types';\n\nimport { canonicalizeShortcut, parseShortcut } from './parser';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n","const isDev = !(globalThis as { __KEYMAP_PROD__?: boolean }).__KEYMAP_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/keymap] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n","import type { Shortcut } from './parser';\nimport type { BindingEntry, BindingValue, Handler, Keymap, KeymapOptions, When } from './types';\n\nimport { warn } from './_dev';\nimport { KeymapError } from './errors';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\n\ntype ParsedBinding = {\n handler: Handler;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: When;\n};\n\ntype ChordTracker = ReturnType<typeof createChordTracker>;\ntype MountedTarget = {\n keydown: ChordTracker;\n keyup: ChordTracker;\n onKeydown: EventListener;\n onKeyup: EventListener;\n refs: number;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') return { handler: value, trigger: 'keydown' };\n\n return {\n handler: value.handler,\n trigger: value.trigger ?? 'keydown',\n when: value.when,\n };\n}\n\nfunction createChordTracker(getBindings: () => ParsedBinding[], chordTimeout: number) {\n let pendingIndex = 0;\n let candidates: ParsedBinding[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n function reset(): void {\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n pendingIndex = 0;\n candidates = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | undefined {\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched = pool.filter((binding) => {\n const step = binding.shortcut[pendingIndex];\n\n return step !== undefined && matchStep(event, step);\n });\n\n if (matched.length === 0) {\n const retryFromRoot = pendingIndex !== 0;\n\n reset();\n\n return retryFromRoot ? advance(event) : undefined;\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n\n const completed = matched.find((binding) => binding.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n candidates = matched;\n pendingIndex += 1;\n timer = setTimeout(reset, chordTimeout);\n\n return undefined;\n }\n\n return { advance, reset };\n}\n\n/**\n * Creates a headless keyboard shortcut manager with target-local chord state.\n *\n * Pass a bindings map of shortcut strings to handlers or `BindingOptions`, then call\n * `.mount(target)` to attach to any `EventTarget`. Supports chord sequences\n * (e.g. `\"ctrl+k ctrl+s\"`), per-binding `when` guards, `trigger` (keydown/keyup),\n * and dynamic `bind`/`unbind`.\n *\n * @example\n * const map = createKeymap({\n * 'mod+k mod+s': () => save(),\n * 'mod+shift+p': () => openPalette(),\n * 'g g': () => goToTop(),\n * esc: { handler: closePanel, when: (event) => !isEditableTarget(event.target) },\n * space: { handler: togglePlay, trigger: 'keyup' },\n * }, { modKey: 'ctrl' });\n * const unmount = map.mount(document);\n */\nexport function createKeymap(initialBindings: Record<string, BindingValue> = {}, options: KeymapOptions = {}): Keymap {\n const {\n chordTimeout: rawChordTimeout = 1000,\n modKey = detectModKey(),\n preventDefault = true,\n stopPropagation = false,\n when: globalWhen,\n } = options;\n const chordTimeout = Number.isFinite(rawChordTimeout) && rawChordTimeout > 0 ? rawChordTimeout : 1000;\n\n if (chordTimeout !== rawChordTimeout) {\n warn(`chordTimeout must be a positive finite number; received ${rawChordTimeout}. Using default of 1000ms.`);\n }\n\n const bindings = new Map<string, ParsedBinding>();\n const mounted = new Map<EventTarget, MountedTarget>();\n const disposalController = new AbortController();\n let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n let disposed = false;\n\n function assertActive(): void {\n if (disposed) throw new KeymapError('Keymap is disposed');\n }\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const binding of bindings.values()) {\n if (binding.trigger === 'keydown') bindingsDown.push(binding);\n else bindingsUp.push(binding);\n }\n }\n\n function bindingKey(shortcut: string): string {\n return canonicalizeShortcut(parseShortcut(shortcut, modKey));\n }\n\n function addBinding(shortcut: string, value: BindingValue): string {\n const parsed = parseShortcut(shortcut, modKey);\n const key = canonicalizeShortcut(parsed);\n\n bindings.set(key, { shortcut: parsed, ...resolveBinding(value) });\n rebuildTriggerCaches();\n\n return key;\n }\n\n function removeByKey(key: string): boolean {\n const existed = bindings.delete(key);\n\n if (existed) rebuildTriggerCaches();\n\n return existed;\n }\n\n function makeHandler(target: EventTarget, chord: ChordTracker): EventListener {\n return (event) => {\n const path = typeof event.composedPath === 'function' ? event.composedPath() : [];\n const nearestMountedTarget = path.find((pathTarget) => mounted.has(pathTarget));\n\n if (nearestMountedTarget && nearestMountedTarget !== target) return;\n\n const keyboardEvent = event as KeyboardEvent;\n\n if (disposed || (globalWhen && !globalWhen(keyboardEvent))) return;\n\n const binding = chord.advance(keyboardEvent);\n\n if (!binding || (binding.when && !binding.when(keyboardEvent))) return;\n\n if (preventDefault) keyboardEvent.preventDefault();\n\n if (stopPropagation) keyboardEvent.stopPropagation();\n\n binding.handler(keyboardEvent);\n };\n }\n\n for (const [shortcut, value] of Object.entries(initialBindings)) addBinding(shortcut, value);\n\n return {\n bind(shortcut: string, value: BindingValue): () => void {\n assertActive();\n\n const key = addBinding(shortcut, value);\n\n return () => {\n if (!disposed) removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n for (const [target, record] of mounted) {\n target.removeEventListener('keydown', record.onKeydown);\n target.removeEventListener('keyup', record.onKeyup);\n record.keydown.reset();\n record.keyup.reset();\n }\n\n mounted.clear();\n bindings.clear();\n bindingsDown = [];\n bindingsUp = [];\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((binding) => ({\n shortcut: binding.shortcut.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) })),\n trigger: binding.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n assertActive();\n\n let record = mounted.get(target);\n\n if (!record) {\n const keydown = createChordTracker(() => bindingsDown, chordTimeout);\n const keyup = createChordTracker(() => bindingsUp, chordTimeout);\n const onKeydown = makeHandler(target, keydown);\n const onKeyup = makeHandler(target, keyup);\n\n record = { keydown, keyup, onKeydown, onKeyup, refs: 0 };\n mounted.set(target, record);\n target.addEventListener('keydown', onKeydown);\n target.addEventListener('keyup', onKeyup);\n }\n\n record.refs += 1;\n\n let unmounted = false;\n\n return () => {\n if (unmounted) return;\n\n unmounted = true;\n record!.refs -= 1;\n\n if (record!.refs > 0) return;\n\n target.removeEventListener('keydown', record!.onKeydown);\n target.removeEventListener('keyup', record!.onKeyup);\n record!.keydown.reset();\n record!.keyup.reset();\n mounted.delete(target);\n };\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n assertActive();\n\n if (!removeByKey(bindingKey(shortcut))) {\n warn(`unbind() called for unknown shortcut: \"${shortcut}\"`);\n }\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"keymap.js","names":[],"sources":["../src/errors.ts","../src/parser.ts","../src/conflicts.ts","../src/_dev.ts","../src/format.ts","../src/keymap.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n","import { KeymapParseError } from './errors';\n\nexport type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\nexport type ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\nexport type Shortcut = ShortcutStep[];\n\nexport function detectModKey(): 'ctrl' | 'meta' {\n if (typeof navigator === 'undefined') return 'ctrl';\n\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n\n return /mac/i.test(platform) ? 'meta' : 'ctrl';\n}\n\nconst MODIFIER_ALIASES_BASE: Record<string, ModifierKey> = {\n alt: 'alt',\n cmd: 'meta',\n command: 'meta',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n opt: 'alt',\n option: 'alt',\n shift: 'shift',\n win: 'meta',\n};\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n del: 'delete',\n down: 'arrowdown',\n esc: 'escape',\n left: 'arrowleft',\n right: 'arrowright',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n};\n\nfunction parseStepStrict(raw: string, modKey: 'ctrl' | 'meta'): ShortcutStep {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((part) => part.toLowerCase());\n\n if (parts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n const aliases: Record<string, ModifierKey> = { ...MODIFIER_ALIASES_BASE, mod: modKey };\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const modifier = Object.hasOwn(aliases, part) ? aliases[part]! : undefined;\n\n if (modifier) modifiers.add(modifier);\n else keyParts.push(part);\n }\n\n if (keyParts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const rawKey = keyParts[0]!;\n const key = Object.hasOwn(SPECIAL_KEY_ALIASES, rawKey) ? SPECIAL_KEY_ALIASES[rawKey]! : rawKey;\n\n return { key, modifiers };\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n try {\n return parseStepStrict(raw, modKey);\n } catch {\n return null;\n }\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n const steps = raw.trim().split(/\\s+/).filter(Boolean);\n\n if (steps.length === 0) throw new KeymapParseError('Shortcut must contain at least one key step');\n\n return steps.map((step) => parseStepStrict(step, modKey));\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((step) => {\n const modifiers = [...step.modifiers].sort().join('+');\n\n return modifiers ? `${modifiers}+${step.key}` : step.key;\n })\n .join(' ');\n}\n\nexport function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean {\n // Headless usage (SSR, non-DOM `EventTarget`s, hand-built test events) means `event` isn't\n // guaranteed to be a real `KeyboardEvent` at runtime even though the type says so — treat a\n // missing/non-string `key` as \"doesn't match\" rather than throwing inside the event listener.\n if (typeof event.key !== 'string') return false;\n\n if (event.key.toLowerCase() !== step.key) return false;\n\n const { modifiers } = step;\n\n return (\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift')\n );\n}\n","import type { ShortcutStep } from './parser';\nimport { canonicalizeShortcut, parseShortcut } from './parser';\nimport type { BindingEntry } from './types';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n","const isDev = !(globalThis as { __KEYMAP_PROD__?: boolean }).__KEYMAP_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/keymap] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps: ReturnType<typeof parseShortcut>;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n","import { warn } from './_dev';\nimport { KeymapError } from './errors';\nimport type { Shortcut, ShortcutStep } from './parser';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\nimport type { BindingEntry, BindingValue, ChordStateChange, Handler, Keymap, KeymapOptions, When } from './types';\n\ntype ParsedBinding = {\n handler: Handler;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: When;\n};\n\ntype ChordTracker = ReturnType<typeof createChordTracker>;\ntype MountedTarget = {\n keydown: ChordTracker;\n keyup: ChordTracker;\n onKeydown: EventListener;\n onKeyup: EventListener;\n refs: number;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') return { handler: value, trigger: 'keydown' };\n\n return {\n handler: value.handler,\n trigger: value.trigger ?? 'keydown',\n when: value.when,\n };\n}\n\ntype ChordTrackerCallbacks = {\n onChordStart?: (target: EventTarget, step: ShortcutStep, trigger: 'keydown' | 'keyup') => void;\n onChordProgress?: (target: EventTarget, steps: readonly ShortcutStep[], trigger: 'keydown' | 'keyup') => void;\n onChordTimeout?: (target: EventTarget, trigger: 'keydown' | 'keyup') => void;\n};\n\nfunction createChordTracker(\n getBindings: () => ParsedBinding[],\n chordTimeout: number,\n target: EventTarget,\n trigger: 'keydown' | 'keyup',\n callbacks?: ChordTrackerCallbacks,\n) {\n let pendingIndex = 0;\n let candidates: ParsedBinding[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n let pendingSteps: ShortcutStep[] = [];\n\n function reset(emitTimeout = false): void {\n if (timer !== undefined) clearTimeout(timer);\n\n // Emit timeout only if explicitly requested (from timeout event)\n if (emitTimeout && pendingIndex > 0 && callbacks?.onChordTimeout) {\n try {\n callbacks.onChordTimeout(target, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n timer = undefined;\n pendingIndex = 0;\n candidates = [];\n pendingSteps = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | undefined {\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched = pool.filter((binding) => {\n const step = binding.shortcut[pendingIndex];\n\n return step !== undefined && matchStep(event, step);\n });\n\n if (matched.length === 0) {\n const retryFromRoot = pendingIndex !== 0;\n\n reset();\n\n return retryFromRoot ? advance(event) : undefined;\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n\n const completed = matched.find((binding) => binding.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n // New step in chord progression\n const currentStep = matched[0]!.shortcut[pendingIndex];\n pendingSteps = matched[0]!.shortcut.slice(0, pendingIndex + 1);\n\n // Emit started or progressed\n if (pendingIndex === 0 && callbacks?.onChordStart) {\n try {\n callbacks.onChordStart(target, currentStep, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n } else if (pendingIndex > 0 && callbacks?.onChordProgress) {\n try {\n callbacks.onChordProgress(target, pendingSteps, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n candidates = matched;\n pendingIndex += 1;\n timer = setTimeout(() => reset(true), chordTimeout);\n\n return undefined;\n }\n\n return { advance, reset: () => reset() };\n}\n\n/**\n * Creates a headless keyboard shortcut manager with target-local chord state.\n *\n * Pass a bindings map of shortcut strings to handlers or `BindingOptions`, then call\n * `.mount(target)` to attach to any `EventTarget`. Supports chord sequences\n * (e.g. `\"ctrl+k ctrl+s\"`), per-binding `when` guards, `trigger` (keydown/keyup),\n * and dynamic `bind`/`unbind`.\n *\n * @example\n * const map = createKeymap({\n * 'mod+k mod+s': () => save(),\n * 'mod+shift+p': () => openPalette(),\n * 'g g': () => goToTop(),\n * esc: { handler: closePanel, when: (event) => !isEditableTarget(event.target) },\n * space: { handler: togglePlay, trigger: 'keyup' },\n * }, { modKey: 'ctrl' });\n * const unmount = map.mount(document);\n */\nexport function createKeymap(initialBindings: Record<string, BindingValue> = {}, options: KeymapOptions = {}): Keymap {\n const {\n chordTimeout: rawChordTimeout = 1000,\n modKey = detectModKey(),\n preventDefault = true,\n stopPropagation = false,\n when: globalWhen,\n onChordState: userOnChordState,\n } = options;\n const chordTimeout = Number.isFinite(rawChordTimeout) && rawChordTimeout > 0 ? rawChordTimeout : 1000;\n\n if (chordTimeout !== rawChordTimeout) {\n warn(`chordTimeout must be a positive finite number; received ${rawChordTimeout}. Using default of 1000ms.`);\n }\n\n const bindings = new Map<string, ParsedBinding>();\n const mounted = new Map<EventTarget, MountedTarget>();\n const disposalController = new AbortController();\n let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n let disposed = false;\n\n function assertActive(): void {\n if (disposed) throw new KeymapError('Keymap is disposed');\n }\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const binding of bindings.values()) {\n if (binding.trigger === 'keydown') bindingsDown.push(binding);\n else bindingsUp.push(binding);\n }\n }\n\n function bindingKey(shortcut: string): string {\n return canonicalizeShortcut(parseShortcut(shortcut, modKey));\n }\n\n function addBinding(shortcut: string, value: BindingValue): string {\n const parsed = parseShortcut(shortcut, modKey);\n const key = canonicalizeShortcut(parsed);\n\n bindings.set(key, { shortcut: parsed, ...resolveBinding(value) });\n rebuildTriggerCaches();\n\n return key;\n }\n\n function removeByKey(key: string): boolean {\n const existed = bindings.delete(key);\n\n if (existed) rebuildTriggerCaches();\n\n return existed;\n }\n\n function emitChordState(change: ChordStateChange): void {\n if (!userOnChordState) return;\n\n try {\n userOnChordState(change);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n function makeHandler(target: EventTarget, chord: ChordTracker): EventListener {\n return (event) => {\n const path = typeof event.composedPath === 'function' ? event.composedPath() : [];\n const nearestMountedTarget = path.find((pathTarget) => mounted.has(pathTarget));\n\n if (nearestMountedTarget && nearestMountedTarget !== target) return;\n\n const keyboardEvent = event as KeyboardEvent;\n\n if (disposed) return;\n\n // Track chord progression (independent of guards)\n const binding = chord.advance(keyboardEvent);\n\n // Check guards AFTER tracking chord\n if (globalWhen && !globalWhen(keyboardEvent)) return;\n\n if (!binding || (binding.when && !binding.when(keyboardEvent))) return;\n\n if (preventDefault) keyboardEvent.preventDefault();\n\n if (stopPropagation) keyboardEvent.stopPropagation();\n\n binding.handler(keyboardEvent);\n };\n }\n\n for (const [shortcut, value] of Object.entries(initialBindings)) addBinding(shortcut, value);\n\n return {\n bind(shortcut: string, value: BindingValue): () => void {\n assertActive();\n\n const key = addBinding(shortcut, value);\n\n return () => {\n if (!disposed) removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n for (const [target, record] of mounted) {\n target.removeEventListener('keydown', record.onKeydown);\n target.removeEventListener('keyup', record.onKeyup);\n record.keydown.reset();\n record.keyup.reset();\n }\n\n mounted.clear();\n bindings.clear();\n bindingsDown = [];\n bindingsUp = [];\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((binding) => ({\n shortcut: binding.shortcut.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) })),\n trigger: binding.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n assertActive();\n\n let record = mounted.get(target);\n\n if (!record) {\n const keydown = createChordTracker(() => bindingsDown, chordTimeout, target, 'keydown', {\n onChordProgress: (t, steps, trigger) => emitChordState({ steps, target: t, trigger, type: 'progressed' }),\n onChordStart: (t, step, trigger) => emitChordState({ step, target: t, trigger, type: 'started' }),\n onChordTimeout: (t, trigger) => emitChordState({ target: t, trigger, type: 'timeout' }),\n });\n const keyup = createChordTracker(() => bindingsUp, chordTimeout, target, 'keyup', {\n onChordProgress: (t, steps, trigger) => emitChordState({ steps, target: t, trigger, type: 'progressed' }),\n onChordStart: (t, step, trigger) => emitChordState({ step, target: t, trigger, type: 'started' }),\n onChordTimeout: (t, trigger) => emitChordState({ target: t, trigger, type: 'timeout' }),\n });\n const onKeydown = makeHandler(target, keydown);\n const onKeyup = makeHandler(target, keyup);\n\n record = { keydown, keyup, onKeydown, onKeyup, refs: 0 };\n mounted.set(target, record);\n target.addEventListener('keydown', onKeydown);\n target.addEventListener('keyup', onKeyup);\n }\n\n record.refs += 1;\n\n let unmounted = false;\n\n return () => {\n if (unmounted) return;\n\n unmounted = true;\n record!.refs -= 1;\n\n if (record?.refs > 0) return;\n\n target.removeEventListener('keydown', record?.onKeydown);\n target.removeEventListener('keyup', record?.onKeyup);\n record?.keydown.reset();\n record?.keyup.reset();\n mounted.delete(target);\n };\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n assertActive();\n\n if (!removeByKey(bindingKey(shortcut))) {\n warn(`unbind() called for unknown shortcut: \"${shortcut}\"`);\n }\n },\n };\n}\n"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -4,6 +4,26 @@ export type BindingEntry = {
4
4
  readonly trigger: 'keydown' | 'keyup';
5
5
  };
6
6
  export type Handler = (event: KeyboardEvent) => void;
7
+ /**
8
+ * Guard function for fine-grained binding context.
9
+ *
10
+ * When both a global `when` (from `KeymapOptions`) and a per-binding `when`
11
+ * are provided, both must return `true` for the handler to fire:
12
+ *
13
+ * 1. Global `when` is checked first; if it fails, all bindings are skipped.
14
+ * 2. Per-binding `when` is checked only after the global guard passes.
15
+ *
16
+ * Treat it as: _global guard AND per-binding guard_.
17
+ *
18
+ * @example
19
+ * // Global: don't fire shortcuts while modal is open
20
+ * { when: (e) => !isModalOpen() }
21
+ *
22
+ * // Per-binding: only fire when event target is the specific panel
23
+ * { 'escape': { handler: closePanel, when: (e) => e.target === panel } }
24
+ *
25
+ * Both conditions must be met for the handler to execute.
26
+ */
7
27
  export type When = (event: KeyboardEvent) => boolean;
8
28
  export type BindingOptions = {
9
29
  handler: Handler;
@@ -11,21 +31,67 @@ export type BindingOptions = {
11
31
  when?: When;
12
32
  };
13
33
  export type BindingValue = Handler | BindingOptions;
34
+ /**
35
+ * Chord state change event emitted when chord progression changes.
36
+ *
37
+ * Use `onChordState` callback in `KeymapOptions` to observe chord state
38
+ * for debugging, UI hints, or chord history. When a chord fully matches,
39
+ * the binding handler fires immediately; no separate 'completed' event.
40
+ */
41
+ export type ChordStateChange = {
42
+ type: 'started';
43
+ target: EventTarget;
44
+ step: ShortcutStep;
45
+ trigger: 'keydown' | 'keyup';
46
+ } | {
47
+ type: 'progressed';
48
+ target: EventTarget;
49
+ steps: readonly ShortcutStep[];
50
+ trigger: 'keydown' | 'keyup';
51
+ } | {
52
+ type: 'timeout';
53
+ target: EventTarget;
54
+ trigger: 'keydown' | 'keyup';
55
+ };
14
56
  export interface KeymapOptions {
15
57
  chordTimeout?: number;
16
58
  modKey?: 'ctrl' | 'meta';
59
+ /**
60
+ * Optional callback to observe chord state changes.
61
+ *
62
+ * Fires when a chord starts, progresses through steps, completes, or times out.
63
+ * Useful for debugging, logging, testing, or implementing chord UI hints.
64
+ *
65
+ * @example
66
+ * {
67
+ * onChordState: (change) => {
68
+ * if (change.type === 'started') console.log('Chord started:', change.step.key);
69
+ * if (change.type === 'timeout') console.log('Chord timed out');
70
+ * }
71
+ * }
72
+ */
73
+ onChordState?: (change: ChordStateChange) => void;
17
74
  preventDefault?: boolean;
18
75
  stopPropagation?: boolean;
76
+ /**
77
+ * Guard function for all bindings in this keymap.
78
+ *
79
+ * When both global and per-binding `when` guards are provided, both must
80
+ * return `true` for the handler to fire. Global guard is checked first.
81
+ *
82
+ * @example
83
+ * { when: (e) => !isModalOpen() }
84
+ */
19
85
  when?: When;
20
86
  }
21
87
  export interface Keymap {
22
- [Symbol.dispose](): void;
23
88
  bind(shortcut: string, value: BindingValue): () => void;
24
- dispose(): void;
25
89
  readonly disposalSignal: AbortSignal;
90
+ dispose(): void;
26
91
  readonly disposed: boolean;
27
92
  listBindings(): readonly BindingEntry[];
28
93
  mount(target: EventTarget): () => void;
29
94
  unbind(shortcut: string): void;
95
+ [Symbol.dispose](): void;
30
96
  }
31
97
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AACrD,MAAM,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC;AAErD,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9B,IAAI,CAAC,EAAE,IAAI,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,cAAc,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,MAAM,WAAW,MAAM;IACrB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACzB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,IAAI,CAAC;IACxD,OAAO,IAAI,IAAI,CAAC;IAChB,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,YAAY,IAAI,SAAS,YAAY,EAAE,CAAC;IACxC,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,IAAI,CAAC;IACvC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAErD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC;AAErD,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9B,IAAI,CAAC,EAAE,IAAI,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,cAAc,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAA;CAAE,GAC1F;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAA;CAAE,GACzG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAA;CAAE,CAAC;AAE3E,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAEzB;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,IAAI,CAAC;IACxD,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC;IACrC,OAAO,IAAI,IAAI,CAAC;IAChB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,YAAY,IAAI,SAAS,YAAY,EAAE,CAAC;IACxC,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,IAAI,CAAC;IACvC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B"}
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "@vielzeug/keymap",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Headless keyboard shortcut manager with chord sequences, context guards, and disposable bindings",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/helmuthdu/vielzeug",
8
+ "directory": "packages/keymap"
9
+ },
5
10
  "type": "module",
11
+ "engines": {
12
+ "node": ">=22"
13
+ },
6
14
  "files": [
7
15
  "dist"
8
16
  ],
@@ -11,31 +19,29 @@
11
19
  "types": "dist/index.d.ts",
12
20
  "exports": {
13
21
  ".": {
14
- "types": "./dist/index.d.ts",
15
22
  "import": "./dist/index.js",
16
- "require": "./dist/index.cjs"
23
+ "require": "./dist/index.cjs",
24
+ "types": "./dist/index.d.ts"
17
25
  }
18
26
  },
27
+ "sideEffects": false,
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "registry": "https://registry.npmjs.org/"
31
+ },
19
32
  "scripts": {
20
33
  "build": "vite build && pnpm run build:bundle && pnpm run build:types",
21
34
  "build:bundle": "vite build --config vite.bundle.config.ts",
22
35
  "build:types": "tsc -p tsconfig.declarations.json",
23
- "fix": "eslint --fix src",
24
- "lint": "eslint src",
36
+ "fix": "biome check --write src",
37
+ "lint": "biome ci src",
25
38
  "prepublishOnly": "pnpm run build",
26
39
  "test": "vitest"
27
40
  },
28
- "publishConfig": {
29
- "access": "public",
30
- "registry": "https://registry.npmjs.org/"
31
- },
32
- "engines": {
33
- "node": ">=22"
34
- },
35
41
  "devDependencies": {
36
- "@types/node": "^26.1.2",
42
+ "@types/node": "^26.2.0",
37
43
  "typescript": "^6.0.3",
38
- "vite": "^8.2.0",
44
+ "vite": "^8.2.1",
39
45
  "vitest": "^4.1.10"
40
46
  }
41
47
  }