@vielzeug/keymap 1.1.1 → 1.1.3

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.
package/dist/_dev.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=!globalThis.__KEYMAP_PROD__;function t(t){e&&console.warn(`[@vielzeug/keymap] ${t}`)}exports.warn=t;
1
+ function e(e){}exports.warn=e;
2
2
  //# sourceMappingURL=_dev.cjs.map
package/dist/_dev.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["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"],"mappings":"AAAA,IAAM,EAAQ,CAAE,WAA6C,gBAG7D,SAAgB,EAAK,EAAmB,CAClC,GAAO,QAAQ,KAAK,sBAAsB,GAAK,CACrD"}
1
+ {"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["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"],"mappings":"AAGA,SAAgB,EAAK,EAAmB,CAExC"}
package/dist/_dev.js CHANGED
@@ -1,9 +1,6 @@
1
1
  //#region src/_dev.ts
2
- var e = !globalThis.__KEYMAP_PROD__;
3
- function t(t) {
4
- e && console.warn(`[@vielzeug/keymap] ${t}`);
5
- }
2
+ function e(e) {}
6
3
  //#endregion
7
- export { t as warn };
4
+ export { e as warn };
8
5
 
9
6
  //# sourceMappingURL=_dev.js.map
package/dist/_dev.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["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"],"mappings":";AAAA,IAAM,IAAQ,CAAE,WAA6C;AAG7D,SAAgB,EAAK,GAAmB;CACtC,AAAI,KAAO,QAAQ,KAAK,sBAAsB,GAAK;AACrD"}
1
+ {"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["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"],"mappings":";AAGA,SAAgB,EAAK,GAAmB,CAExC"}
@@ -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,IAAI,OAAO,KACvB,OAAO,eAAe,KAAM,IAAI,OAAO,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 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 +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,IAAI,OAAO,MACvB,OAAO,eAAe,MAAM,IAAI,OAAO,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 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"}
package/dist/format.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./parser.cjs"),t=require("./_dev.cjs");var n={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},r={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},i={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},a=[`ctrl`,`alt`,`shift`,`meta`];function o(o,s=e.detectModKey()){if(!o.trim())return t.warn(`formatShortcut() received an empty shortcut string`),``;let c;try{c=e.parseShortcut(o,s)}catch{return t.warn(`formatShortcut() received an invalid shortcut: "${o}"`),``}let l=s===`meta`;return c.map(e=>{let t=a.filter(t=>e.modifiers.has(t)).map(e=>l?r[e]:i[e]),o=n[e.key]??e.key.toUpperCase();return l?`${t.join(``)}${o}`:[...t,o].join(`+`)}).join(` `)}exports.formatShortcut=o;
1
+ const e=require("./parser.cjs");require("./_dev.cjs");var t={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},n={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},r={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},i=[`ctrl`,`alt`,`shift`,`meta`];function a(a,o=e.detectModKey()){if(!a.trim())return``;let s;try{s=e.parseShortcut(a,o)}catch{return`${a}`,``}let c=o===`meta`;return s.map(e=>{let a=i.filter(t=>e.modifiers.has(t)).map(e=>c?n[e]:r[e]),o=t[e.key]??e.key.toUpperCase();return c?`${a.join(``)}${o}`:[...a,o].join(`+`)}).join(` `)}exports.formatShortcut=a;
2
2
  //# sourceMappingURL=format.cjs.map
@@ -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":"wDAGA,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,OAFA,EAAA,KAAK,oDAAoD,EAElD,GAGT,IAAI,EAEJ,GAAI,CACF,EAAQ,EAAA,cAAc,EAAU,CAAM,CACxC,MAAQ,CAGN,OAFA,EAAA,KAAK,mDAAmD,EAAS,EAAE,EAE5D,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;\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"}
package/dist/format.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { detectModKey as e, parseShortcut as t } from "./parser.js";
2
- import { warn as n } from "./_dev.js";
2
+ import "./_dev.js";
3
3
  //#region src/format.ts
4
- var r = {
4
+ var n = {
5
5
  " ": "Space",
6
6
  arrowdown: "↓",
7
7
  arrowleft: "←",
@@ -16,37 +16,37 @@ var r = {
16
16
  pagedown: "PgDn",
17
17
  pageup: "PgUp",
18
18
  tab: "⇥"
19
- }, i = {
19
+ }, r = {
20
20
  alt: "⌥",
21
21
  ctrl: "⌃",
22
22
  meta: "⌘",
23
23
  shift: "⇧"
24
- }, a = {
24
+ }, i = {
25
25
  alt: "Alt",
26
26
  ctrl: "Ctrl",
27
27
  meta: "Win",
28
28
  shift: "Shift"
29
- }, o = [
29
+ }, a = [
30
30
  "ctrl",
31
31
  "alt",
32
32
  "shift",
33
33
  "meta"
34
34
  ];
35
- function s(s, c = e()) {
36
- if (!s.trim()) return n("formatShortcut() received an empty shortcut string"), "";
37
- let l;
35
+ function o(o, s = e()) {
36
+ if (!o.trim()) return "";
37
+ let c;
38
38
  try {
39
- l = t(s, c);
39
+ c = t(o, s);
40
40
  } catch {
41
- return n(`formatShortcut() received an invalid shortcut: "${s}"`), "";
41
+ return `${o}`, "";
42
42
  }
43
- let u = c === "meta";
44
- return l.map((e) => {
45
- let t = o.filter((t) => e.modifiers.has(t)).map((e) => u ? i[e] : a[e]), n = r[e.key] ?? e.key.toUpperCase();
46
- return u ? `${t.join("")}${n}` : [...t, n].join("+");
43
+ let l = s === "meta";
44
+ return c.map((e) => {
45
+ let t = a.filter((t) => e.modifiers.has(t)).map((e) => l ? r[e] : i[e]), o = n[e.key] ?? e.key.toUpperCase();
46
+ return l ? `${t.join("")}${o}` : [...t, o].join("+");
47
47
  }).join(" ");
48
48
  }
49
49
  //#endregion
50
- export { s as formatShortcut };
50
+ export { o as formatShortcut };
51
51
 
52
52
  //# sourceMappingURL=format.js.map
@@ -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,OAFA,EAAK,oDAAoD,GAElD;CAGT,IAAI;CAEJ,IAAI;EACF,IAAQ,EAAc,GAAU,CAAM;CACxC,QAAQ;EAGN,OAFA,EAAK,mDAAmD,EAAS,EAAE,GAE5D;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;\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/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){return{...r,mod:e}}function o(e,t){let n=e.toLowerCase();return Object.hasOwn(t,n)?t[n]:null}function s(e){return Object.hasOwn(i,e)?i[e]:e}function c(e,r=n()){let i=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(i.length===0)return null;let c=a(r),l=new Set,u=[];for(let e of i){let t=o(e,c);t?l.add(t):u.push(e)}if(u.length===0)return null;if(u.length>1)throw new t(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);return{key:s(u[0]),modifiers:l}}function l(e,r=n()){return e.trim().split(/\s+/).filter(Boolean).map(n=>{let i=c(n,r);if(i===null)throw new t(`Invalid shortcut step: "${n}" in "${e}"`);return i})}function u(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function d(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 f(e,t){return e.length>t.length?!1:u(e)===u(t.slice(0,e.length))}function p(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n,a=l(e,r);return a.length===0?[]:t.filter(e=>e.trigger===i&&(f(a,e.shortcut)||f(e.shortcut,a)))}var m=!globalThis.__KEYMAP_PROD__;function h(e){m&&console.warn(`[@vielzeug/keymap] ${e}`)}var g={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},_={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},v={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},y=[`ctrl`,`alt`,`shift`,`meta`];function b(e,t=n()){if(!e.trim())return h(`formatShortcut() received an empty shortcut string`),``;let r;try{r=l(e,t)}catch{return h(`formatShortcut() received an invalid shortcut: "${e}"`),``}let i=t===`meta`;return r.map(e=>{let t=y.filter(t=>e.modifiers.has(t)).map(e=>i?_[e]:v[e]),n=g[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function x(e){if(typeof e==`function`)return{handler:e,priority:0,trigger:`keydown`};let t=e.priority??0;return Number.isFinite(t)||h(`binding priority must be a finite number; received ${t}. Using 0.`),{handler:e.handler,priority:Number.isFinite(t)?t:0,trigger:e.trigger??`keydown`,when:e.when}}function S(e,t){let n=0,r=[],i=null;function a(){i!==null&&(clearTimeout(i),i=null)}function o(){a(),n=0,r=[]}function s(c){let l=n===0?e():r,u=[];for(let e of l){let t=e.shortcut[n];t&&d(c,t)&&u.push(e)}if(u.length===0){let e=n!==0;return o(),e?s(c):null}a();let f=u.find(e=>e.shortcut.length===n+1);return f?(o(),f):(r=u,n++,i=setTimeout(o,t),null)}return{advance:s,reset:o}}function C(e={},t={}){let{chordTimeout:r=1e3,modKey:i=n(),preventDefault:a=!0,stopPropagation:o=!1,when:s}=t,c=Number.isFinite(r)&&r>0?r:1e3;c!==r&&h(`chordTimeout must be a positive finite number; received ${r}. Using default of 1000ms.`);let d=new Map,f=[],p=[];function m(){f=[],p=[];for(let e of d.values())e.trigger===`keydown`?f.push(e):p.push(e)}function g(e,t){let n=l(e,i),r=u(n);return d.set(r,{shortcut:n,...x(t)}),m(),r}function _(e){let t=d.delete(e);return t&&m(),t}function v(e){return _(u(l(e,i)))}for(let[t,n]of Object.entries(e)){let e=l(t,i),r=u(e);d.set(r,{shortcut:e,...x(n)})}m();let y=S(()=>f,c),b=S(()=>p,c),C=new Set,w=new Map;function T(e){return function(t){if(s&&!s())return;let n=e.advance(t);n&&(n.when&&!n.when()||(a&&t.preventDefault(),o&&t.stopPropagation(),n.handler(t)))}}let E=T(y),D=T(b),O=new AbortController,k=!1;return{bind(e,t){let n=g(e,t);return()=>{_(n)}},get disposalSignal(){return O.signal},dispose(){if(!k){k=!0,O.abort();for(let e of C)e();C.clear(),y.reset(),b.reset()}},get disposed(){return k},listBindings(){return[...d.values()].map(e=>({priority:e.priority,shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){let t=w.get(e)??0;t>0&&h(`mount() called for a target that is already mounted — this registers a duplicate listener`),w.set(e,t+1),e.addEventListener(`keydown`,E),e.addEventListener(`keyup`,D);let n=()=>{e.removeEventListener(`keydown`,E),e.removeEventListener(`keyup`,D),C.delete(n);let t=(w.get(e)??1)-1;t<=0?w.delete(e):w.set(e,t)};return C.add(n),n},[Symbol.dispose](){this.dispose()},unbind(e){v(e)||h(`unbind() called for unknown shortcut: "${e}"`)}}}function w(e,t={},n={}){let r=!0,i=C(t,{...n,when:()=>r&&(n.when?.()??!0)});return{activate(){r=!0},get active(){return r},bind(e,t){return i.bind(e,t)},deactivate(){r=!1},get disposalSignal(){return i.disposalSignal},dispose(){i.dispose()},get disposed(){return i.disposed},listBindings(){return i.listBindings()},mount(e){return i.mount(e)},get parent(){return e},[Symbol.dispose](){this.dispose()},unbind(e){i.unbind(e)}}}exports.KeymapError=e,exports.KeymapParseError=t,exports.canonicalizeShortcut=u,exports.createKeymap=C,exports.createKeymapLayer=w,exports.detectModKey=n,exports.findShortcutConflicts=p,exports.formatShortcut=b,exports.matchStep=d,exports.parseShortcut=l,exports.parseStep=c;
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){return{...r,mod:e}}function o(e,t){let n=e.toLowerCase();return Object.hasOwn(t,n)?t[n]:null}function s(e){return Object.hasOwn(i,e)?i[e]:e}function c(e,r=n()){let i=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(i.length===0)return null;let c=a(r),l=new Set,u=[];for(let e of i){let t=o(e,c);t?l.add(t):u.push(e)}if(u.length===0)return null;if(u.length>1)throw new t(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);return{key:s(u[0]),modifiers:l}}function l(e,r=n()){return e.trim().split(/\s+/).filter(Boolean).map(n=>{let i=c(n,r);if(i===null)throw new t(`Invalid shortcut step: "${n}" in "${e}"`);return i})}function u(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function d(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 f(e,t){return e.length>t.length?!1:u(e)===u(t.slice(0,e.length))}function p(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n,a=l(e,r);return a.length===0?[]:t.filter(e=>e.trigger===i&&(f(a,e.shortcut)||f(e.shortcut,a)))}var m={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},h={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},g={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},_=[`ctrl`,`alt`,`shift`,`meta`];function v(e,t=n()){if(!e.trim())return``;let r;try{r=l(e,t)}catch{return`${e}`,``}let i=t===`meta`;return r.map(e=>{let t=_.filter(t=>e.modifiers.has(t)).map(e=>i?h[e]:g[e]),n=m[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function y(e){if(typeof e==`function`)return{handler:e,priority:0,trigger:`keydown`};let t=e.priority??0;return Number.isFinite(t)||`${t}`,{handler:e.handler,priority:Number.isFinite(t)?t:0,trigger:e.trigger??`keydown`,when:e.when}}function b(e,t){let n=0,r=[],i=null;function a(){i!==null&&(clearTimeout(i),i=null)}function o(){a(),n=0,r=[]}function s(c){let l=n===0?e():r,u=[];for(let e of l){let t=e.shortcut[n];t&&d(c,t)&&u.push(e)}if(u.length===0){let e=n!==0;return o(),e?s(c):null}a();let f=u.find(e=>e.shortcut.length===n+1);return f?(o(),f):(r=u,n++,i=setTimeout(o,t),null)}return{advance:s,reset:o}}function x(e={},t={}){let{chordTimeout:r=1e3,modKey:i=n(),preventDefault:a=!0,stopPropagation:o=!1,when:s}=t,c=Number.isFinite(r)&&r>0?r:1e3;c!==r&&`${r}`;let d=new Map,f=[],p=[];function m(){f=[],p=[];for(let e of d.values())e.trigger===`keydown`?f.push(e):p.push(e)}function h(e,t){let n=l(e,i),r=u(n);return d.set(r,{shortcut:n,...y(t)}),m(),r}function g(e){let t=d.delete(e);return t&&m(),t}function _(e){return g(u(l(e,i)))}for(let[t,n]of Object.entries(e)){let e=l(t,i),r=u(e);d.set(r,{shortcut:e,...y(n)})}m();let v=b(()=>f,c),x=b(()=>p,c),S=new Set,C=new Map;function w(e){return function(t){if(s&&!s())return;let n=e.advance(t);n&&(n.when&&!n.when()||(a&&t.preventDefault(),o&&t.stopPropagation(),n.handler(t)))}}let T=w(v),E=w(x),D=new AbortController,O=!1;return{bind(e,t){let n=h(e,t);return()=>{g(n)}},get disposalSignal(){return D.signal},dispose(){if(!O){O=!0,D.abort();for(let e of S)e();S.clear(),v.reset(),x.reset()}},get disposed(){return O},listBindings(){return[...d.values()].map(e=>({priority:e.priority,shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){let t=C.get(e)??0;C.set(e,t+1),e.addEventListener(`keydown`,T),e.addEventListener(`keyup`,E);let n=()=>{e.removeEventListener(`keydown`,T),e.removeEventListener(`keyup`,E),S.delete(n);let t=(C.get(e)??1)-1;t<=0?C.delete(e):C.set(e,t)};return S.add(n),n},[Symbol.dispose](){this.dispose()},unbind(e){_(e)||`${e}`}}}function S(e,t={},n={}){let r=!0,i=x(t,{...n,when:()=>r&&(n.when?.()??!0)});return{activate(){r=!0},get active(){return r},bind(e,t){return i.bind(e,t)},deactivate(){r=!1},get disposalSignal(){return i.disposalSignal},dispose(){i.dispose()},get disposed(){return i.disposed},listBindings(){return i.listBindings()},mount(e){return i.mount(e)},get parent(){return e},[Symbol.dispose](){this.dispose()},unbind(e){i.unbind(e)}}}exports.KeymapError=e,exports.KeymapParseError=t,exports.canonicalizeShortcut=u,exports.createKeymap=x,exports.createKeymapLayer=S,exports.detectModKey=n,exports.findShortcutConflicts=p,exports.formatShortcut=v,exports.matchStep=d,exports.parseShortcut=l,exports.parseStep=c;
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","../src/layer.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 buildModifierAliases(modKey: 'ctrl' | 'meta'): Record<string, ModifierKey> {\n return { ...MODIFIER_ALIASES_BASE, mod: modKey };\n}\n\nfunction normalizeModifier(token: string, aliases: Record<string, ModifierKey>): ModifierKey | null {\n const lower = token.toLowerCase();\n\n return Object.hasOwn(aliases, lower) ? aliases[lower]! : null;\n}\n\nfunction normalizeKey(raw: string): string {\n return Object.hasOwn(SPECIAL_KEY_ALIASES, raw) ? SPECIAL_KEY_ALIASES[raw]! : raw;\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((p) => p.toLowerCase());\n\n if (parts.length === 0) return null;\n\n const aliases = buildModifierAliases(modKey);\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const mod = normalizeModifier(part, aliases);\n\n if (mod) {\n modifiers.add(mod);\n } else {\n keyParts.push(part);\n }\n }\n\n if (keyParts.length === 0) return null;\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const key = normalizeKey(keyParts[0]);\n\n return { key, modifiers };\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n return raw\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((step) => {\n const result = parseStep(step, modKey);\n\n if (result === null) {\n throw new KeymapParseError(`Invalid shortcut step: \"${step}\" in \"${raw}\"`);\n }\n\n return result;\n });\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((s) => {\n const mods = [...s.modifiers].sort().join('+');\n\n return mods ? `${mods}+${s.key}` : s.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 if (event.altKey !== modifiers.has('alt')) return false;\n\n if (event.ctrlKey !== modifiers.has('ctrl')) return false;\n\n if (event.metaKey !== modifiers.has('meta')) return false;\n\n if (event.shiftKey !== modifiers.has('shift')) return false;\n\n return true;\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 const steps = parseShortcut(shortcut, modKey);\n\n // An empty/whitespace-only shortcut parses to zero steps, which would otherwise be treated as\n // a trivial prefix of every entry — never report that as a conflict.\n if (steps.length === 0) return [];\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, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions } from './types';\n\nimport { warn } from './_dev';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\n\ntype ParsedBinding = {\n handler: Handler;\n priority: number;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: () => boolean;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') {\n return { handler: value, priority: 0, trigger: 'keydown' };\n }\n\n const priority = value.priority ?? 0;\n\n if (!Number.isFinite(priority)) {\n warn(`binding priority must be a finite number; received ${priority}. Using 0.`);\n }\n\n return {\n handler: value.handler,\n priority: Number.isFinite(priority) ? priority : 0,\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> | null = null;\n\n function clearTimer(): void {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n function reset(): void {\n clearTimer();\n pendingIndex = 0;\n candidates = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | null {\n // At the root of a chord, re-scan every binding. Mid-chord, narrow only within bindings that\n // already matched every prior step — never re-admit a binding whose earlier step didn't match.\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched: ParsedBinding[] = [];\n\n for (const binding of pool) {\n const step = binding.shortcut[pendingIndex];\n\n if (step && matchStep(event, step)) {\n matched.push(binding);\n }\n }\n\n if (matched.length === 0) {\n const wasPartial = pendingIndex !== 0;\n\n reset();\n\n if (wasPartial) {\n return advance(event);\n }\n\n return null;\n }\n\n clearTimer();\n\n // At most one binding can complete here — the bindings map is keyed by canonical shortcut,\n // so two live bindings can never share an identical step sequence. `priority` therefore never\n // has a real tie to resolve; the shortest completing match always fires immediately.\n const completed = matched.find((b) => b.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n candidates = matched;\n pendingIndex++;\n timer = setTimeout(reset, chordTimeout);\n\n return null;\n }\n\n return { advance, reset };\n}\n\n/**\n * Creates a headless keyboard shortcut manager.\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 * `priority`, 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: () => isPanelOpen() },\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\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 let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const b of bindings.values()) {\n if (b.trigger === 'keydown') bindingsDown.push(b);\n else bindingsUp.push(b);\n }\n }\n\n function addBinding(shortcutStr: string, value: BindingValue): string {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n bindings.set(key, { shortcut, ...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 removeBinding(shortcutStr: string): boolean {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n return removeByKey(key);\n }\n\n for (const [shortcutStr, value] of Object.entries(initialBindings)) {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n bindings.set(key, { shortcut, ...resolveBinding(value) });\n }\n\n rebuildTriggerCaches();\n\n const chordDown = createChordTracker(() => bindingsDown, chordTimeout);\n const chordUp = createChordTracker(() => bindingsUp, chordTimeout);\n const unmounts = new Set<() => void>();\n const mountCounts = new Map<EventTarget, number>();\n\n function makeHandler(chord: ReturnType<typeof createChordTracker>) {\n return function handleEvent(event: KeyboardEvent): void {\n if (globalWhen && !globalWhen()) return;\n\n const binding = chord.advance(event);\n\n if (!binding) return;\n\n if (binding.when && !binding.when()) return;\n\n if (preventDefault) event.preventDefault();\n\n if (stopPropagation) event.stopPropagation();\n\n binding.handler(event);\n };\n }\n\n const handleKeydown = makeHandler(chordDown);\n const handleKeyup = makeHandler(chordUp);\n const ac = new AbortController();\n let isDisposed = false;\n\n return {\n bind(shortcutStr: string, value: BindingValue): () => void {\n const key = addBinding(shortcutStr, value);\n\n return () => {\n removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return ac.signal;\n },\n\n dispose(): void {\n if (isDisposed) return;\n\n isDisposed = true;\n ac.abort();\n for (const unmount of unmounts) unmount();\n unmounts.clear();\n chordDown.reset();\n chordUp.reset();\n },\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((b) => ({\n priority: b.priority,\n shortcut: b.shortcut.map((s) => ({ key: s.key, modifiers: new Set(s.modifiers) })),\n trigger: b.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n const priorCount = mountCounts.get(target) ?? 0;\n\n if (priorCount > 0) {\n warn('mount() called for a target that is already mounted — this registers a duplicate listener');\n }\n\n mountCounts.set(target, priorCount + 1);\n\n target.addEventListener('keydown', handleKeydown as EventListener);\n target.addEventListener('keyup', handleKeyup as EventListener);\n\n const unmount = (): void => {\n target.removeEventListener('keydown', handleKeydown as EventListener);\n target.removeEventListener('keyup', handleKeyup as EventListener);\n unmounts.delete(unmount);\n\n const remaining = (mountCounts.get(target) ?? 1) - 1;\n\n if (remaining <= 0) mountCounts.delete(target);\n else mountCounts.set(target, remaining);\n };\n\n unmounts.add(unmount);\n\n return unmount;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcutStr: string): void {\n const existed = removeBinding(shortcutStr);\n\n if (!existed) {\n warn(`unbind() called for unknown shortcut: \"${shortcutStr}\"`);\n }\n },\n };\n}\n\nexport type { BindingOptions };\n","import type { BindingValue, Keymap, KeymapOptions } from './types';\n\nimport { createKeymap } from './keymap';\n\n/**\n * Creates a scoped keymap layer that sits on top of a parent keymap.\n *\n * The **caller** is responsible for mounting and disposing the parent independently.\n * `layer.mount(target)` only attaches the layer's own shortcuts — it does not touch\n * the parent. `layer.dispose()` only tears down the layer — it does not dispose the parent.\n *\n * While the layer is active, matching shortcuts are handled by the layer. Call\n * `layer.deactivate()` to suspend the layer and `layer.activate()` to re-enable it.\n *\n * @example\n * const base = createKeymap({ 'ctrl+z': undo });\n * const modal = createKeymapLayer(base, {\n * esc: { handler: closeModal, when: () => isModalOpen() },\n * });\n * // Mount both independently — the layer does NOT auto-mount the parent.\n * const unmountBase = base.mount(document);\n * const unmountModal = modal.mount(document);\n *\n * modal.deactivate(); // base handles everything; layer is suspended\n * modal.activate(); // layer is active again\n */\nexport interface KeymapLayer extends Keymap {\n activate(): void;\n deactivate(): void;\n readonly active: boolean;\n readonly parent: Keymap;\n}\n\nexport function createKeymapLayer(\n parent: Keymap,\n initialBindings: Record<string, BindingValue> = {},\n options: KeymapOptions = {},\n): KeymapLayer {\n let isActive = true;\n\n const layer = createKeymap(initialBindings, {\n ...options,\n when: () => isActive && (options.when?.() ?? true),\n });\n\n return {\n activate(): void {\n isActive = true;\n },\n\n get active() {\n return isActive;\n },\n\n bind(shortcut: string, value: BindingValue): () => void {\n return layer.bind(shortcut, value);\n },\n\n deactivate(): void {\n isActive = false;\n },\n\n get disposalSignal(): AbortSignal {\n return layer.disposalSignal;\n },\n\n dispose(): void {\n layer.dispose();\n },\n\n get disposed(): boolean {\n return layer.disposed;\n },\n\n listBindings() {\n return layer.listBindings();\n },\n\n mount(target: EventTarget): () => void {\n return layer.mount(target);\n },\n\n get parent() {\n return parent;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n layer.unbind(shortcut);\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","../src/layer.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 buildModifierAliases(modKey: 'ctrl' | 'meta'): Record<string, ModifierKey> {\n return { ...MODIFIER_ALIASES_BASE, mod: modKey };\n}\n\nfunction normalizeModifier(token: string, aliases: Record<string, ModifierKey>): ModifierKey | null {\n const lower = token.toLowerCase();\n\n return Object.hasOwn(aliases, lower) ? aliases[lower]! : null;\n}\n\nfunction normalizeKey(raw: string): string {\n return Object.hasOwn(SPECIAL_KEY_ALIASES, raw) ? SPECIAL_KEY_ALIASES[raw]! : raw;\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((p) => p.toLowerCase());\n\n if (parts.length === 0) return null;\n\n const aliases = buildModifierAliases(modKey);\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const mod = normalizeModifier(part, aliases);\n\n if (mod) {\n modifiers.add(mod);\n } else {\n keyParts.push(part);\n }\n }\n\n if (keyParts.length === 0) return null;\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const key = normalizeKey(keyParts[0]);\n\n return { key, modifiers };\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n return raw\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((step) => {\n const result = parseStep(step, modKey);\n\n if (result === null) {\n throw new KeymapParseError(`Invalid shortcut step: \"${step}\" in \"${raw}\"`);\n }\n\n return result;\n });\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((s) => {\n const mods = [...s.modifiers].sort().join('+');\n\n return mods ? `${mods}+${s.key}` : s.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 if (event.altKey !== modifiers.has('alt')) return false;\n\n if (event.ctrlKey !== modifiers.has('ctrl')) return false;\n\n if (event.metaKey !== modifiers.has('meta')) return false;\n\n if (event.shiftKey !== modifiers.has('shift')) return false;\n\n return true;\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 const steps = parseShortcut(shortcut, modKey);\n\n // An empty/whitespace-only shortcut parses to zero steps, which would otherwise be treated as\n // a trivial prefix of every entry — never report that as a conflict.\n if (steps.length === 0) return [];\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, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions } from './types';\n\nimport { warn } from './_dev';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\n\ntype ParsedBinding = {\n handler: Handler;\n priority: number;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: () => boolean;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') {\n return { handler: value, priority: 0, trigger: 'keydown' };\n }\n\n const priority = value.priority ?? 0;\n\n if (!Number.isFinite(priority)) {\n warn(`binding priority must be a finite number; received ${priority}. Using 0.`);\n }\n\n return {\n handler: value.handler,\n priority: Number.isFinite(priority) ? priority : 0,\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> | null = null;\n\n function clearTimer(): void {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n function reset(): void {\n clearTimer();\n pendingIndex = 0;\n candidates = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | null {\n // At the root of a chord, re-scan every binding. Mid-chord, narrow only within bindings that\n // already matched every prior step — never re-admit a binding whose earlier step didn't match.\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched: ParsedBinding[] = [];\n\n for (const binding of pool) {\n const step = binding.shortcut[pendingIndex];\n\n if (step && matchStep(event, step)) {\n matched.push(binding);\n }\n }\n\n if (matched.length === 0) {\n const wasPartial = pendingIndex !== 0;\n\n reset();\n\n if (wasPartial) {\n return advance(event);\n }\n\n return null;\n }\n\n clearTimer();\n\n // At most one binding can complete here — the bindings map is keyed by canonical shortcut,\n // so two live bindings can never share an identical step sequence. `priority` therefore never\n // has a real tie to resolve; the shortest completing match always fires immediately.\n const completed = matched.find((b) => b.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n candidates = matched;\n pendingIndex++;\n timer = setTimeout(reset, chordTimeout);\n\n return null;\n }\n\n return { advance, reset };\n}\n\n/**\n * Creates a headless keyboard shortcut manager.\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 * `priority`, 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: () => isPanelOpen() },\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\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 let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const b of bindings.values()) {\n if (b.trigger === 'keydown') bindingsDown.push(b);\n else bindingsUp.push(b);\n }\n }\n\n function addBinding(shortcutStr: string, value: BindingValue): string {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n bindings.set(key, { shortcut, ...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 removeBinding(shortcutStr: string): boolean {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n return removeByKey(key);\n }\n\n for (const [shortcutStr, value] of Object.entries(initialBindings)) {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n bindings.set(key, { shortcut, ...resolveBinding(value) });\n }\n\n rebuildTriggerCaches();\n\n const chordDown = createChordTracker(() => bindingsDown, chordTimeout);\n const chordUp = createChordTracker(() => bindingsUp, chordTimeout);\n const unmounts = new Set<() => void>();\n const mountCounts = new Map<EventTarget, number>();\n\n function makeHandler(chord: ReturnType<typeof createChordTracker>) {\n return function handleEvent(event: KeyboardEvent): void {\n if (globalWhen && !globalWhen()) return;\n\n const binding = chord.advance(event);\n\n if (!binding) return;\n\n if (binding.when && !binding.when()) return;\n\n if (preventDefault) event.preventDefault();\n\n if (stopPropagation) event.stopPropagation();\n\n binding.handler(event);\n };\n }\n\n const handleKeydown = makeHandler(chordDown);\n const handleKeyup = makeHandler(chordUp);\n const ac = new AbortController();\n let isDisposed = false;\n\n return {\n bind(shortcutStr: string, value: BindingValue): () => void {\n const key = addBinding(shortcutStr, value);\n\n return () => {\n removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return ac.signal;\n },\n\n dispose(): void {\n if (isDisposed) return;\n\n isDisposed = true;\n ac.abort();\n for (const unmount of unmounts) unmount();\n unmounts.clear();\n chordDown.reset();\n chordUp.reset();\n },\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((b) => ({\n priority: b.priority,\n shortcut: b.shortcut.map((s) => ({ key: s.key, modifiers: new Set(s.modifiers) })),\n trigger: b.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n const priorCount = mountCounts.get(target) ?? 0;\n\n if (priorCount > 0) {\n warn('mount() called for a target that is already mounted — this registers a duplicate listener');\n }\n\n mountCounts.set(target, priorCount + 1);\n\n target.addEventListener('keydown', handleKeydown as EventListener);\n target.addEventListener('keyup', handleKeyup as EventListener);\n\n const unmount = (): void => {\n target.removeEventListener('keydown', handleKeydown as EventListener);\n target.removeEventListener('keyup', handleKeyup as EventListener);\n unmounts.delete(unmount);\n\n const remaining = (mountCounts.get(target) ?? 1) - 1;\n\n if (remaining <= 0) mountCounts.delete(target);\n else mountCounts.set(target, remaining);\n };\n\n unmounts.add(unmount);\n\n return unmount;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcutStr: string): void {\n const existed = removeBinding(shortcutStr);\n\n if (!existed) {\n warn(`unbind() called for unknown shortcut: \"${shortcutStr}\"`);\n }\n },\n };\n}\n\nexport type { BindingOptions };\n","import type { BindingValue, Keymap, KeymapOptions } from './types';\n\nimport { createKeymap } from './keymap';\n\n/**\n * Creates a scoped keymap layer that sits on top of a parent keymap.\n *\n * The **caller** is responsible for mounting and disposing the parent independently.\n * `layer.mount(target)` only attaches the layer's own shortcuts — it does not touch\n * the parent. `layer.dispose()` only tears down the layer — it does not dispose the parent.\n *\n * While the layer is active, matching shortcuts are handled by the layer. Call\n * `layer.deactivate()` to suspend the layer and `layer.activate()` to re-enable it.\n *\n * @example\n * const base = createKeymap({ 'ctrl+z': undo });\n * const modal = createKeymapLayer(base, {\n * esc: { handler: closeModal, when: () => isModalOpen() },\n * });\n * // Mount both independently — the layer does NOT auto-mount the parent.\n * const unmountBase = base.mount(document);\n * const unmountModal = modal.mount(document);\n *\n * modal.deactivate(); // base handles everything; layer is suspended\n * modal.activate(); // layer is active again\n */\nexport interface KeymapLayer extends Keymap {\n activate(): void;\n deactivate(): void;\n readonly active: boolean;\n readonly parent: Keymap;\n}\n\nexport function createKeymapLayer(\n parent: Keymap,\n initialBindings: Record<string, BindingValue> = {},\n options: KeymapOptions = {},\n): KeymapLayer {\n let isActive = true;\n\n const layer = createKeymap(initialBindings, {\n ...options,\n when: () => isActive && (options.when?.() ?? true),\n });\n\n return {\n activate(): void {\n isActive = true;\n },\n\n get active() {\n return isActive;\n },\n\n bind(shortcut: string, value: BindingValue): () => void {\n return layer.bind(shortcut, value);\n },\n\n deactivate(): void {\n isActive = false;\n },\n\n get disposalSignal(): AbortSignal {\n return layer.disposalSignal;\n },\n\n dispose(): void {\n layer.dispose();\n },\n\n get disposed(): boolean {\n return layer.disposed;\n },\n\n listBindings() {\n return layer.listBindings();\n },\n\n mount(target: EventTarget): () => void {\n return layer.mount(target);\n },\n\n get parent() {\n return parent;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n layer.unbind(shortcut);\n },\n };\n}\n"],"mappings":"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"}
@@ -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){return{...i,mod:e}}function s(e,t){let n=e.toLowerCase();return Object.hasOwn(t,n)?t[n]:null}function c(e){return Object.hasOwn(a,e)?a[e]:e}function l(e,t=r()){let i=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(i.length===0)return null;let a=o(t),l=new Set,u=[];for(let e of i){let t=s(e,a);t?l.add(t):u.push(e)}if(u.length===0)return null;if(u.length>1)throw new n(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);return{key:c(u[0]),modifiers:l}}function u(e,t=r()){return e.trim().split(/\s+/).filter(Boolean).map(r=>{let i=l(r,t);if(i===null)throw new n(`Invalid shortcut step: "${r}" in "${e}"`);return i})}function d(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function f(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 p(e,t){return e.length>t.length?!1:d(e)===d(t.slice(0,e.length))}function m(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n,a=u(e,r);return a.length===0?[]:t.filter(e=>e.trigger===i&&(p(a,e.shortcut)||p(e.shortcut,a)))}var h=!globalThis.__KEYMAP_PROD__;function g(e){h&&console.warn(`[@vielzeug/keymap] ${e}`)}var _={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},v={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},y={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},b=[`ctrl`,`alt`,`shift`,`meta`];function x(e,t=r()){if(!e.trim())return g(`formatShortcut() received an empty shortcut string`),``;let n;try{n=u(e,t)}catch{return g(`formatShortcut() received an invalid shortcut: "${e}"`),``}let i=t===`meta`;return n.map(e=>{let t=b.filter(t=>e.modifiers.has(t)).map(e=>i?v[e]:y[e]),n=_[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function S(e){if(typeof e==`function`)return{handler:e,priority:0,trigger:`keydown`};let t=e.priority??0;return Number.isFinite(t)||g(`binding priority must be a finite number; received ${t}. Using 0.`),{handler:e.handler,priority:Number.isFinite(t)?t:0,trigger:e.trigger??`keydown`,when:e.when}}function C(e,t){let n=0,r=[],i=null;function a(){i!==null&&(clearTimeout(i),i=null)}function o(){a(),n=0,r=[]}function s(c){let l=n===0?e():r,u=[];for(let e of l){let t=e.shortcut[n];t&&f(c,t)&&u.push(e)}if(u.length===0){let e=n!==0;return o(),e?s(c):null}a();let d=u.find(e=>e.shortcut.length===n+1);return d?(o(),d):(r=u,n++,i=setTimeout(o,t),null)}return{advance:s,reset:o}}function w(e={},t={}){let{chordTimeout:n=1e3,modKey:i=r(),preventDefault:a=!0,stopPropagation:o=!1,when:s}=t,c=Number.isFinite(n)&&n>0?n:1e3;c!==n&&g(`chordTimeout must be a positive finite number; received ${n}. Using default of 1000ms.`);let l=new Map,f=[],p=[];function m(){f=[],p=[];for(let e of l.values())e.trigger===`keydown`?f.push(e):p.push(e)}function h(e,t){let n=u(e,i),r=d(n);return l.set(r,{shortcut:n,...S(t)}),m(),r}function _(e){let t=l.delete(e);return t&&m(),t}function v(e){return _(d(u(e,i)))}for(let[t,n]of Object.entries(e)){let e=u(t,i),r=d(e);l.set(r,{shortcut:e,...S(n)})}m();let y=C(()=>f,c),b=C(()=>p,c),x=new Set,w=new Map;function T(e){return function(t){if(s&&!s())return;let n=e.advance(t);n&&(n.when&&!n.when()||(a&&t.preventDefault(),o&&t.stopPropagation(),n.handler(t)))}}let E=T(y),D=T(b),O=new AbortController,k=!1;return{bind(e,t){let n=h(e,t);return()=>{_(n)}},get disposalSignal(){return O.signal},dispose(){if(!k){k=!0,O.abort();for(let e of x)e();x.clear(),y.reset(),b.reset()}},get disposed(){return k},listBindings(){return[...l.values()].map(e=>({priority:e.priority,shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){let t=w.get(e)??0;t>0&&g(`mount() called for a target that is already mounted — this registers a duplicate listener`),w.set(e,t+1),e.addEventListener(`keydown`,E),e.addEventListener(`keyup`,D);let n=()=>{e.removeEventListener(`keydown`,E),e.removeEventListener(`keyup`,D),x.delete(n);let t=(w.get(e)??1)-1;t<=0?w.delete(e):w.set(e,t)};return x.add(n),n},[Symbol.dispose](){this.dispose()},unbind(e){v(e)||g(`unbind() called for unknown shortcut: "${e}"`)}}}function T(e,t={},n={}){let r=!0,i=w(t,{...n,when:()=>r&&(n.when?.()??!0)});return{activate(){r=!0},get active(){return r},bind(e,t){return i.bind(e,t)},deactivate(){r=!1},get disposalSignal(){return i.disposalSignal},dispose(){i.dispose()},get disposed(){return i.disposed},listBindings(){return i.listBindings()},mount(e){return i.mount(e)},get parent(){return e},[Symbol.dispose](){this.dispose()},unbind(e){i.unbind(e)}}}return e.KeymapError=t,e.KeymapParseError=n,e.canonicalizeShortcut=d,e.createKeymap=w,e.createKeymapLayer=T,e.detectModKey=r,e.findShortcutConflicts=m,e.formatShortcut=x,e.matchStep=f,e.parseShortcut=u,e.parseStep=l,e})({});
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){return{...i,mod:e}}function s(e,t){let n=e.toLowerCase();return Object.hasOwn(t,n)?t[n]:null}function c(e){return Object.hasOwn(a,e)?a[e]:e}function l(e,t=r()){let i=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(i.length===0)return null;let a=o(t),l=new Set,u=[];for(let e of i){let t=s(e,a);t?l.add(t):u.push(e)}if(u.length===0)return null;if(u.length>1)throw new n(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);return{key:c(u[0]),modifiers:l}}function u(e,t=r()){return e.trim().split(/\s+/).filter(Boolean).map(r=>{let i=l(r,t);if(i===null)throw new n(`Invalid shortcut step: "${r}" in "${e}"`);return i})}function d(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function f(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 p(e,t){return e.length>t.length?!1:d(e)===d(t.slice(0,e.length))}function m(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n,a=u(e,r);return a.length===0?[]:t.filter(e=>e.trigger===i&&(p(a,e.shortcut)||p(e.shortcut,a)))}var h={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},g={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},_={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},v=[`ctrl`,`alt`,`shift`,`meta`];function y(e,t=r()){if(!e.trim())return``;let n;try{n=u(e,t)}catch{return`${e}`,``}let i=t===`meta`;return n.map(e=>{let t=v.filter(t=>e.modifiers.has(t)).map(e=>i?g[e]:_[e]),n=h[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function b(e){if(typeof e==`function`)return{handler:e,priority:0,trigger:`keydown`};let t=e.priority??0;return Number.isFinite(t)||`${t}`,{handler:e.handler,priority:Number.isFinite(t)?t:0,trigger:e.trigger??`keydown`,when:e.when}}function x(e,t){let n=0,r=[],i=null;function a(){i!==null&&(clearTimeout(i),i=null)}function o(){a(),n=0,r=[]}function s(c){let l=n===0?e():r,u=[];for(let e of l){let t=e.shortcut[n];t&&f(c,t)&&u.push(e)}if(u.length===0){let e=n!==0;return o(),e?s(c):null}a();let d=u.find(e=>e.shortcut.length===n+1);return d?(o(),d):(r=u,n++,i=setTimeout(o,t),null)}return{advance:s,reset:o}}function S(e={},t={}){let{chordTimeout:n=1e3,modKey:i=r(),preventDefault:a=!0,stopPropagation:o=!1,when:s}=t,c=Number.isFinite(n)&&n>0?n:1e3;c!==n&&`${n}`;let l=new Map,f=[],p=[];function m(){f=[],p=[];for(let e of l.values())e.trigger===`keydown`?f.push(e):p.push(e)}function h(e,t){let n=u(e,i),r=d(n);return l.set(r,{shortcut:n,...b(t)}),m(),r}function g(e){let t=l.delete(e);return t&&m(),t}function _(e){return g(d(u(e,i)))}for(let[t,n]of Object.entries(e)){let e=u(t,i),r=d(e);l.set(r,{shortcut:e,...b(n)})}m();let v=x(()=>f,c),y=x(()=>p,c),S=new Set,C=new Map;function w(e){return function(t){if(s&&!s())return;let n=e.advance(t);n&&(n.when&&!n.when()||(a&&t.preventDefault(),o&&t.stopPropagation(),n.handler(t)))}}let T=w(v),E=w(y),D=new AbortController,O=!1;return{bind(e,t){let n=h(e,t);return()=>{g(n)}},get disposalSignal(){return D.signal},dispose(){if(!O){O=!0,D.abort();for(let e of S)e();S.clear(),v.reset(),y.reset()}},get disposed(){return O},listBindings(){return[...l.values()].map(e=>({priority:e.priority,shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){let t=C.get(e)??0;C.set(e,t+1),e.addEventListener(`keydown`,T),e.addEventListener(`keyup`,E);let n=()=>{e.removeEventListener(`keydown`,T),e.removeEventListener(`keyup`,E),S.delete(n);let t=(C.get(e)??1)-1;t<=0?C.delete(e):C.set(e,t)};return S.add(n),n},[Symbol.dispose](){this.dispose()},unbind(e){_(e)||`${e}`}}}function C(e,t={},n={}){let r=!0,i=S(t,{...n,when:()=>r&&(n.when?.()??!0)});return{activate(){r=!0},get active(){return r},bind(e,t){return i.bind(e,t)},deactivate(){r=!1},get disposalSignal(){return i.disposalSignal},dispose(){i.dispose()},get disposed(){return i.disposed},listBindings(){return i.listBindings()},mount(e){return i.mount(e)},get parent(){return e},[Symbol.dispose](){this.dispose()},unbind(e){i.unbind(e)}}}return e.KeymapError=t,e.KeymapParseError=n,e.canonicalizeShortcut=d,e.createKeymap=S,e.createKeymapLayer=C,e.detectModKey=r,e.findShortcutConflicts=m,e.formatShortcut=y,e.matchStep=f,e.parseShortcut=u,e.parseStep=l,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","../src/layer.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 buildModifierAliases(modKey: 'ctrl' | 'meta'): Record<string, ModifierKey> {\n return { ...MODIFIER_ALIASES_BASE, mod: modKey };\n}\n\nfunction normalizeModifier(token: string, aliases: Record<string, ModifierKey>): ModifierKey | null {\n const lower = token.toLowerCase();\n\n return Object.hasOwn(aliases, lower) ? aliases[lower]! : null;\n}\n\nfunction normalizeKey(raw: string): string {\n return Object.hasOwn(SPECIAL_KEY_ALIASES, raw) ? SPECIAL_KEY_ALIASES[raw]! : raw;\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((p) => p.toLowerCase());\n\n if (parts.length === 0) return null;\n\n const aliases = buildModifierAliases(modKey);\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const mod = normalizeModifier(part, aliases);\n\n if (mod) {\n modifiers.add(mod);\n } else {\n keyParts.push(part);\n }\n }\n\n if (keyParts.length === 0) return null;\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const key = normalizeKey(keyParts[0]);\n\n return { key, modifiers };\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n return raw\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((step) => {\n const result = parseStep(step, modKey);\n\n if (result === null) {\n throw new KeymapParseError(`Invalid shortcut step: \"${step}\" in \"${raw}\"`);\n }\n\n return result;\n });\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((s) => {\n const mods = [...s.modifiers].sort().join('+');\n\n return mods ? `${mods}+${s.key}` : s.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 if (event.altKey !== modifiers.has('alt')) return false;\n\n if (event.ctrlKey !== modifiers.has('ctrl')) return false;\n\n if (event.metaKey !== modifiers.has('meta')) return false;\n\n if (event.shiftKey !== modifiers.has('shift')) return false;\n\n return true;\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 const steps = parseShortcut(shortcut, modKey);\n\n // An empty/whitespace-only shortcut parses to zero steps, which would otherwise be treated as\n // a trivial prefix of every entry — never report that as a conflict.\n if (steps.length === 0) return [];\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, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions } from './types';\n\nimport { warn } from './_dev';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\n\ntype ParsedBinding = {\n handler: Handler;\n priority: number;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: () => boolean;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') {\n return { handler: value, priority: 0, trigger: 'keydown' };\n }\n\n const priority = value.priority ?? 0;\n\n if (!Number.isFinite(priority)) {\n warn(`binding priority must be a finite number; received ${priority}. Using 0.`);\n }\n\n return {\n handler: value.handler,\n priority: Number.isFinite(priority) ? priority : 0,\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> | null = null;\n\n function clearTimer(): void {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n function reset(): void {\n clearTimer();\n pendingIndex = 0;\n candidates = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | null {\n // At the root of a chord, re-scan every binding. Mid-chord, narrow only within bindings that\n // already matched every prior step — never re-admit a binding whose earlier step didn't match.\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched: ParsedBinding[] = [];\n\n for (const binding of pool) {\n const step = binding.shortcut[pendingIndex];\n\n if (step && matchStep(event, step)) {\n matched.push(binding);\n }\n }\n\n if (matched.length === 0) {\n const wasPartial = pendingIndex !== 0;\n\n reset();\n\n if (wasPartial) {\n return advance(event);\n }\n\n return null;\n }\n\n clearTimer();\n\n // At most one binding can complete here — the bindings map is keyed by canonical shortcut,\n // so two live bindings can never share an identical step sequence. `priority` therefore never\n // has a real tie to resolve; the shortest completing match always fires immediately.\n const completed = matched.find((b) => b.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n candidates = matched;\n pendingIndex++;\n timer = setTimeout(reset, chordTimeout);\n\n return null;\n }\n\n return { advance, reset };\n}\n\n/**\n * Creates a headless keyboard shortcut manager.\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 * `priority`, 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: () => isPanelOpen() },\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\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 let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const b of bindings.values()) {\n if (b.trigger === 'keydown') bindingsDown.push(b);\n else bindingsUp.push(b);\n }\n }\n\n function addBinding(shortcutStr: string, value: BindingValue): string {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n bindings.set(key, { shortcut, ...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 removeBinding(shortcutStr: string): boolean {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n return removeByKey(key);\n }\n\n for (const [shortcutStr, value] of Object.entries(initialBindings)) {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n bindings.set(key, { shortcut, ...resolveBinding(value) });\n }\n\n rebuildTriggerCaches();\n\n const chordDown = createChordTracker(() => bindingsDown, chordTimeout);\n const chordUp = createChordTracker(() => bindingsUp, chordTimeout);\n const unmounts = new Set<() => void>();\n const mountCounts = new Map<EventTarget, number>();\n\n function makeHandler(chord: ReturnType<typeof createChordTracker>) {\n return function handleEvent(event: KeyboardEvent): void {\n if (globalWhen && !globalWhen()) return;\n\n const binding = chord.advance(event);\n\n if (!binding) return;\n\n if (binding.when && !binding.when()) return;\n\n if (preventDefault) event.preventDefault();\n\n if (stopPropagation) event.stopPropagation();\n\n binding.handler(event);\n };\n }\n\n const handleKeydown = makeHandler(chordDown);\n const handleKeyup = makeHandler(chordUp);\n const ac = new AbortController();\n let isDisposed = false;\n\n return {\n bind(shortcutStr: string, value: BindingValue): () => void {\n const key = addBinding(shortcutStr, value);\n\n return () => {\n removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return ac.signal;\n },\n\n dispose(): void {\n if (isDisposed) return;\n\n isDisposed = true;\n ac.abort();\n for (const unmount of unmounts) unmount();\n unmounts.clear();\n chordDown.reset();\n chordUp.reset();\n },\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((b) => ({\n priority: b.priority,\n shortcut: b.shortcut.map((s) => ({ key: s.key, modifiers: new Set(s.modifiers) })),\n trigger: b.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n const priorCount = mountCounts.get(target) ?? 0;\n\n if (priorCount > 0) {\n warn('mount() called for a target that is already mounted — this registers a duplicate listener');\n }\n\n mountCounts.set(target, priorCount + 1);\n\n target.addEventListener('keydown', handleKeydown as EventListener);\n target.addEventListener('keyup', handleKeyup as EventListener);\n\n const unmount = (): void => {\n target.removeEventListener('keydown', handleKeydown as EventListener);\n target.removeEventListener('keyup', handleKeyup as EventListener);\n unmounts.delete(unmount);\n\n const remaining = (mountCounts.get(target) ?? 1) - 1;\n\n if (remaining <= 0) mountCounts.delete(target);\n else mountCounts.set(target, remaining);\n };\n\n unmounts.add(unmount);\n\n return unmount;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcutStr: string): void {\n const existed = removeBinding(shortcutStr);\n\n if (!existed) {\n warn(`unbind() called for unknown shortcut: \"${shortcutStr}\"`);\n }\n },\n };\n}\n\nexport type { BindingOptions };\n","import type { BindingValue, Keymap, KeymapOptions } from './types';\n\nimport { createKeymap } from './keymap';\n\n/**\n * Creates a scoped keymap layer that sits on top of a parent keymap.\n *\n * The **caller** is responsible for mounting and disposing the parent independently.\n * `layer.mount(target)` only attaches the layer's own shortcuts — it does not touch\n * the parent. `layer.dispose()` only tears down the layer — it does not dispose the parent.\n *\n * While the layer is active, matching shortcuts are handled by the layer. Call\n * `layer.deactivate()` to suspend the layer and `layer.activate()` to re-enable it.\n *\n * @example\n * const base = createKeymap({ 'ctrl+z': undo });\n * const modal = createKeymapLayer(base, {\n * esc: { handler: closeModal, when: () => isModalOpen() },\n * });\n * // Mount both independently — the layer does NOT auto-mount the parent.\n * const unmountBase = base.mount(document);\n * const unmountModal = modal.mount(document);\n *\n * modal.deactivate(); // base handles everything; layer is suspended\n * modal.activate(); // layer is active again\n */\nexport interface KeymapLayer extends Keymap {\n activate(): void;\n deactivate(): void;\n readonly active: boolean;\n readonly parent: Keymap;\n}\n\nexport function createKeymapLayer(\n parent: Keymap,\n initialBindings: Record<string, BindingValue> = {},\n options: KeymapOptions = {},\n): KeymapLayer {\n let isActive = true;\n\n const layer = createKeymap(initialBindings, {\n ...options,\n when: () => isActive && (options.when?.() ?? true),\n });\n\n return {\n activate(): void {\n isActive = true;\n },\n\n get active() {\n return isActive;\n },\n\n bind(shortcut: string, value: BindingValue): () => void {\n return layer.bind(shortcut, value);\n },\n\n deactivate(): void {\n isActive = false;\n },\n\n get disposalSignal(): AbortSignal {\n return layer.disposalSignal;\n },\n\n dispose(): void {\n layer.dispose();\n },\n\n get disposed(): boolean {\n return layer.disposed;\n },\n\n listBindings() {\n return layer.listBindings();\n },\n\n mount(target: EventTarget): () => void {\n return layer.mount(target);\n },\n\n get parent() {\n return parent;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n layer.unbind(shortcut);\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","../src/layer.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 buildModifierAliases(modKey: 'ctrl' | 'meta'): Record<string, ModifierKey> {\n return { ...MODIFIER_ALIASES_BASE, mod: modKey };\n}\n\nfunction normalizeModifier(token: string, aliases: Record<string, ModifierKey>): ModifierKey | null {\n const lower = token.toLowerCase();\n\n return Object.hasOwn(aliases, lower) ? aliases[lower]! : null;\n}\n\nfunction normalizeKey(raw: string): string {\n return Object.hasOwn(SPECIAL_KEY_ALIASES, raw) ? SPECIAL_KEY_ALIASES[raw]! : raw;\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((p) => p.toLowerCase());\n\n if (parts.length === 0) return null;\n\n const aliases = buildModifierAliases(modKey);\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const mod = normalizeModifier(part, aliases);\n\n if (mod) {\n modifiers.add(mod);\n } else {\n keyParts.push(part);\n }\n }\n\n if (keyParts.length === 0) return null;\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const key = normalizeKey(keyParts[0]);\n\n return { key, modifiers };\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n return raw\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((step) => {\n const result = parseStep(step, modKey);\n\n if (result === null) {\n throw new KeymapParseError(`Invalid shortcut step: \"${step}\" in \"${raw}\"`);\n }\n\n return result;\n });\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((s) => {\n const mods = [...s.modifiers].sort().join('+');\n\n return mods ? `${mods}+${s.key}` : s.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 if (event.altKey !== modifiers.has('alt')) return false;\n\n if (event.ctrlKey !== modifiers.has('ctrl')) return false;\n\n if (event.metaKey !== modifiers.has('meta')) return false;\n\n if (event.shiftKey !== modifiers.has('shift')) return false;\n\n return true;\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 const steps = parseShortcut(shortcut, modKey);\n\n // An empty/whitespace-only shortcut parses to zero steps, which would otherwise be treated as\n // a trivial prefix of every entry — never report that as a conflict.\n if (steps.length === 0) return [];\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, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions } from './types';\n\nimport { warn } from './_dev';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\n\ntype ParsedBinding = {\n handler: Handler;\n priority: number;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: () => boolean;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') {\n return { handler: value, priority: 0, trigger: 'keydown' };\n }\n\n const priority = value.priority ?? 0;\n\n if (!Number.isFinite(priority)) {\n warn(`binding priority must be a finite number; received ${priority}. Using 0.`);\n }\n\n return {\n handler: value.handler,\n priority: Number.isFinite(priority) ? priority : 0,\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> | null = null;\n\n function clearTimer(): void {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n function reset(): void {\n clearTimer();\n pendingIndex = 0;\n candidates = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | null {\n // At the root of a chord, re-scan every binding. Mid-chord, narrow only within bindings that\n // already matched every prior step — never re-admit a binding whose earlier step didn't match.\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched: ParsedBinding[] = [];\n\n for (const binding of pool) {\n const step = binding.shortcut[pendingIndex];\n\n if (step && matchStep(event, step)) {\n matched.push(binding);\n }\n }\n\n if (matched.length === 0) {\n const wasPartial = pendingIndex !== 0;\n\n reset();\n\n if (wasPartial) {\n return advance(event);\n }\n\n return null;\n }\n\n clearTimer();\n\n // At most one binding can complete here — the bindings map is keyed by canonical shortcut,\n // so two live bindings can never share an identical step sequence. `priority` therefore never\n // has a real tie to resolve; the shortest completing match always fires immediately.\n const completed = matched.find((b) => b.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n candidates = matched;\n pendingIndex++;\n timer = setTimeout(reset, chordTimeout);\n\n return null;\n }\n\n return { advance, reset };\n}\n\n/**\n * Creates a headless keyboard shortcut manager.\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 * `priority`, 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: () => isPanelOpen() },\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\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 let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const b of bindings.values()) {\n if (b.trigger === 'keydown') bindingsDown.push(b);\n else bindingsUp.push(b);\n }\n }\n\n function addBinding(shortcutStr: string, value: BindingValue): string {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n bindings.set(key, { shortcut, ...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 removeBinding(shortcutStr: string): boolean {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n return removeByKey(key);\n }\n\n for (const [shortcutStr, value] of Object.entries(initialBindings)) {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n bindings.set(key, { shortcut, ...resolveBinding(value) });\n }\n\n rebuildTriggerCaches();\n\n const chordDown = createChordTracker(() => bindingsDown, chordTimeout);\n const chordUp = createChordTracker(() => bindingsUp, chordTimeout);\n const unmounts = new Set<() => void>();\n const mountCounts = new Map<EventTarget, number>();\n\n function makeHandler(chord: ReturnType<typeof createChordTracker>) {\n return function handleEvent(event: KeyboardEvent): void {\n if (globalWhen && !globalWhen()) return;\n\n const binding = chord.advance(event);\n\n if (!binding) return;\n\n if (binding.when && !binding.when()) return;\n\n if (preventDefault) event.preventDefault();\n\n if (stopPropagation) event.stopPropagation();\n\n binding.handler(event);\n };\n }\n\n const handleKeydown = makeHandler(chordDown);\n const handleKeyup = makeHandler(chordUp);\n const ac = new AbortController();\n let isDisposed = false;\n\n return {\n bind(shortcutStr: string, value: BindingValue): () => void {\n const key = addBinding(shortcutStr, value);\n\n return () => {\n removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return ac.signal;\n },\n\n dispose(): void {\n if (isDisposed) return;\n\n isDisposed = true;\n ac.abort();\n for (const unmount of unmounts) unmount();\n unmounts.clear();\n chordDown.reset();\n chordUp.reset();\n },\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((b) => ({\n priority: b.priority,\n shortcut: b.shortcut.map((s) => ({ key: s.key, modifiers: new Set(s.modifiers) })),\n trigger: b.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n const priorCount = mountCounts.get(target) ?? 0;\n\n if (priorCount > 0) {\n warn('mount() called for a target that is already mounted — this registers a duplicate listener');\n }\n\n mountCounts.set(target, priorCount + 1);\n\n target.addEventListener('keydown', handleKeydown as EventListener);\n target.addEventListener('keyup', handleKeyup as EventListener);\n\n const unmount = (): void => {\n target.removeEventListener('keydown', handleKeydown as EventListener);\n target.removeEventListener('keyup', handleKeyup as EventListener);\n unmounts.delete(unmount);\n\n const remaining = (mountCounts.get(target) ?? 1) - 1;\n\n if (remaining <= 0) mountCounts.delete(target);\n else mountCounts.set(target, remaining);\n };\n\n unmounts.add(unmount);\n\n return unmount;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcutStr: string): void {\n const existed = removeBinding(shortcutStr);\n\n if (!existed) {\n warn(`unbind() called for unknown shortcut: \"${shortcutStr}\"`);\n }\n },\n };\n}\n\nexport type { BindingOptions };\n","import type { BindingValue, Keymap, KeymapOptions } from './types';\n\nimport { createKeymap } from './keymap';\n\n/**\n * Creates a scoped keymap layer that sits on top of a parent keymap.\n *\n * The **caller** is responsible for mounting and disposing the parent independently.\n * `layer.mount(target)` only attaches the layer's own shortcuts — it does not touch\n * the parent. `layer.dispose()` only tears down the layer — it does not dispose the parent.\n *\n * While the layer is active, matching shortcuts are handled by the layer. Call\n * `layer.deactivate()` to suspend the layer and `layer.activate()` to re-enable it.\n *\n * @example\n * const base = createKeymap({ 'ctrl+z': undo });\n * const modal = createKeymapLayer(base, {\n * esc: { handler: closeModal, when: () => isModalOpen() },\n * });\n * // Mount both independently — the layer does NOT auto-mount the parent.\n * const unmountBase = base.mount(document);\n * const unmountModal = modal.mount(document);\n *\n * modal.deactivate(); // base handles everything; layer is suspended\n * modal.activate(); // layer is active again\n */\nexport interface KeymapLayer extends Keymap {\n activate(): void;\n deactivate(): void;\n readonly active: boolean;\n readonly parent: Keymap;\n}\n\nexport function createKeymapLayer(\n parent: Keymap,\n initialBindings: Record<string, BindingValue> = {},\n options: KeymapOptions = {},\n): KeymapLayer {\n let isActive = true;\n\n const layer = createKeymap(initialBindings, {\n ...options,\n when: () => isActive && (options.when?.() ?? true),\n });\n\n return {\n activate(): void {\n isActive = true;\n },\n\n get active() {\n return isActive;\n },\n\n bind(shortcut: string, value: BindingValue): () => void {\n return layer.bind(shortcut, value);\n },\n\n deactivate(): void {\n isActive = false;\n },\n\n get disposalSignal(): AbortSignal {\n return layer.disposalSignal;\n },\n\n dispose(): void {\n layer.dispose();\n },\n\n get disposed(): boolean {\n return layer.disposed;\n },\n\n listBindings() {\n return layer.listBindings();\n },\n\n mount(target: EventTarget): () => void {\n return layer.mount(target);\n },\n\n get parent() {\n return parent;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n layer.unbind(shortcut);\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){return{...r,mod:e}}function o(e,t){let n=e.toLowerCase();return Object.hasOwn(t,n)?t[n]:null}function s(e){return Object.hasOwn(i,e)?i[e]:e}function c(e,r=n()){let i=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(i.length===0)return null;let c=a(r),l=new Set,u=[];for(let e of i){let t=o(e,c);t?l.add(t):u.push(e)}if(u.length===0)return null;if(u.length>1)throw new t(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);return{key:s(u[0]),modifiers:l}}function l(e,r=n()){return e.trim().split(/\s+/).filter(Boolean).map(n=>{let i=c(n,r);if(i===null)throw new t(`Invalid shortcut step: "${n}" in "${e}"`);return i})}function u(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function d(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 f(e,t){return e.length>t.length?!1:u(e)===u(t.slice(0,e.length))}function p(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n,a=l(e,r);return a.length===0?[]:t.filter(e=>e.trigger===i&&(f(a,e.shortcut)||f(e.shortcut,a)))}var m=!globalThis.__KEYMAP_PROD__;function h(e){m&&console.warn(`[@vielzeug/keymap] ${e}`)}var g={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},_={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},v={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},y=[`ctrl`,`alt`,`shift`,`meta`];function b(e,t=n()){if(!e.trim())return h(`formatShortcut() received an empty shortcut string`),``;let r;try{r=l(e,t)}catch{return h(`formatShortcut() received an invalid shortcut: "${e}"`),``}let i=t===`meta`;return r.map(e=>{let t=y.filter(t=>e.modifiers.has(t)).map(e=>i?_[e]:v[e]),n=g[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function x(e){if(typeof e==`function`)return{handler:e,priority:0,trigger:`keydown`};let t=e.priority??0;return Number.isFinite(t)||h(`binding priority must be a finite number; received ${t}. Using 0.`),{handler:e.handler,priority:Number.isFinite(t)?t:0,trigger:e.trigger??`keydown`,when:e.when}}function S(e,t){let n=0,r=[],i=null;function a(){i!==null&&(clearTimeout(i),i=null)}function o(){a(),n=0,r=[]}function s(c){let l=n===0?e():r,u=[];for(let e of l){let t=e.shortcut[n];t&&d(c,t)&&u.push(e)}if(u.length===0){let e=n!==0;return o(),e?s(c):null}a();let f=u.find(e=>e.shortcut.length===n+1);return f?(o(),f):(r=u,n++,i=setTimeout(o,t),null)}return{advance:s,reset:o}}function C(e={},t={}){let{chordTimeout:r=1e3,modKey:i=n(),preventDefault:a=!0,stopPropagation:o=!1,when:s}=t,c=Number.isFinite(r)&&r>0?r:1e3;c!==r&&h(`chordTimeout must be a positive finite number; received ${r}. Using default of 1000ms.`);let d=new Map,f=[],p=[];function m(){f=[],p=[];for(let e of d.values())e.trigger===`keydown`?f.push(e):p.push(e)}function g(e,t){let n=l(e,i),r=u(n);return d.set(r,{shortcut:n,...x(t)}),m(),r}function _(e){let t=d.delete(e);return t&&m(),t}function v(e){return _(u(l(e,i)))}for(let[t,n]of Object.entries(e)){let e=l(t,i),r=u(e);d.set(r,{shortcut:e,...x(n)})}m();let y=S(()=>f,c),b=S(()=>p,c),C=new Set,w=new Map;function T(e){return function(t){if(s&&!s())return;let n=e.advance(t);n&&(n.when&&!n.when()||(a&&t.preventDefault(),o&&t.stopPropagation(),n.handler(t)))}}let E=T(y),D=T(b),O=new AbortController,k=!1;return{bind(e,t){let n=g(e,t);return()=>{_(n)}},get disposalSignal(){return O.signal},dispose(){if(!k){k=!0,O.abort();for(let e of C)e();C.clear(),y.reset(),b.reset()}},get disposed(){return k},listBindings(){return[...d.values()].map(e=>({priority:e.priority,shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){let t=w.get(e)??0;t>0&&h(`mount() called for a target that is already mounted — this registers a duplicate listener`),w.set(e,t+1),e.addEventListener(`keydown`,E),e.addEventListener(`keyup`,D);let n=()=>{e.removeEventListener(`keydown`,E),e.removeEventListener(`keyup`,D),C.delete(n);let t=(w.get(e)??1)-1;t<=0?w.delete(e):w.set(e,t)};return C.add(n),n},[Symbol.dispose](){this.dispose()},unbind(e){v(e)||h(`unbind() called for unknown shortcut: "${e}"`)}}}function w(e,t={},n={}){let r=!0,i=C(t,{...n,when:()=>r&&(n.when?.()??!0)});return{activate(){r=!0},get active(){return r},bind(e,t){return i.bind(e,t)},deactivate(){r=!1},get disposalSignal(){return i.disposalSignal},dispose(){i.dispose()},get disposed(){return i.disposed},listBindings(){return i.listBindings()},mount(e){return i.mount(e)},get parent(){return e},[Symbol.dispose](){this.dispose()},unbind(e){i.unbind(e)}}}export{e as KeymapError,t as KeymapParseError,u as canonicalizeShortcut,C as createKeymap,w as createKeymapLayer,n as detectModKey,p as findShortcutConflicts,b as formatShortcut,d as matchStep,l as parseShortcut,c as parseStep};
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){return{...r,mod:e}}function o(e,t){let n=e.toLowerCase();return Object.hasOwn(t,n)?t[n]:null}function s(e){return Object.hasOwn(i,e)?i[e]:e}function c(e,r=n()){let i=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(i.length===0)return null;let c=a(r),l=new Set,u=[];for(let e of i){let t=o(e,c);t?l.add(t):u.push(e)}if(u.length===0)return null;if(u.length>1)throw new t(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);return{key:s(u[0]),modifiers:l}}function l(e,r=n()){return e.trim().split(/\s+/).filter(Boolean).map(n=>{let i=c(n,r);if(i===null)throw new t(`Invalid shortcut step: "${n}" in "${e}"`);return i})}function u(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function d(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 f(e,t){return e.length>t.length?!1:u(e)===u(t.slice(0,e.length))}function p(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n,a=l(e,r);return a.length===0?[]:t.filter(e=>e.trigger===i&&(f(a,e.shortcut)||f(e.shortcut,a)))}var m={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},h={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},g={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},_=[`ctrl`,`alt`,`shift`,`meta`];function v(e,t=n()){if(!e.trim())return``;let r;try{r=l(e,t)}catch{return`${e}`,``}let i=t===`meta`;return r.map(e=>{let t=_.filter(t=>e.modifiers.has(t)).map(e=>i?h[e]:g[e]),n=m[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function y(e){if(typeof e==`function`)return{handler:e,priority:0,trigger:`keydown`};let t=e.priority??0;return Number.isFinite(t)||`${t}`,{handler:e.handler,priority:Number.isFinite(t)?t:0,trigger:e.trigger??`keydown`,when:e.when}}function b(e,t){let n=0,r=[],i=null;function a(){i!==null&&(clearTimeout(i),i=null)}function o(){a(),n=0,r=[]}function s(c){let l=n===0?e():r,u=[];for(let e of l){let t=e.shortcut[n];t&&d(c,t)&&u.push(e)}if(u.length===0){let e=n!==0;return o(),e?s(c):null}a();let f=u.find(e=>e.shortcut.length===n+1);return f?(o(),f):(r=u,n++,i=setTimeout(o,t),null)}return{advance:s,reset:o}}function x(e={},t={}){let{chordTimeout:r=1e3,modKey:i=n(),preventDefault:a=!0,stopPropagation:o=!1,when:s}=t,c=Number.isFinite(r)&&r>0?r:1e3;c!==r&&`${r}`;let d=new Map,f=[],p=[];function m(){f=[],p=[];for(let e of d.values())e.trigger===`keydown`?f.push(e):p.push(e)}function h(e,t){let n=l(e,i),r=u(n);return d.set(r,{shortcut:n,...y(t)}),m(),r}function g(e){let t=d.delete(e);return t&&m(),t}function _(e){return g(u(l(e,i)))}for(let[t,n]of Object.entries(e)){let e=l(t,i),r=u(e);d.set(r,{shortcut:e,...y(n)})}m();let v=b(()=>f,c),x=b(()=>p,c),S=new Set,C=new Map;function w(e){return function(t){if(s&&!s())return;let n=e.advance(t);n&&(n.when&&!n.when()||(a&&t.preventDefault(),o&&t.stopPropagation(),n.handler(t)))}}let T=w(v),E=w(x),D=new AbortController,O=!1;return{bind(e,t){let n=h(e,t);return()=>{g(n)}},get disposalSignal(){return D.signal},dispose(){if(!O){O=!0,D.abort();for(let e of S)e();S.clear(),v.reset(),x.reset()}},get disposed(){return O},listBindings(){return[...d.values()].map(e=>({priority:e.priority,shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){let t=C.get(e)??0;C.set(e,t+1),e.addEventListener(`keydown`,T),e.addEventListener(`keyup`,E);let n=()=>{e.removeEventListener(`keydown`,T),e.removeEventListener(`keyup`,E),S.delete(n);let t=(C.get(e)??1)-1;t<=0?C.delete(e):C.set(e,t)};return S.add(n),n},[Symbol.dispose](){this.dispose()},unbind(e){_(e)||`${e}`}}}function S(e,t={},n={}){let r=!0,i=x(t,{...n,when:()=>r&&(n.when?.()??!0)});return{activate(){r=!0},get active(){return r},bind(e,t){return i.bind(e,t)},deactivate(){r=!1},get disposalSignal(){return i.disposalSignal},dispose(){i.dispose()},get disposed(){return i.disposed},listBindings(){return i.listBindings()},mount(e){return i.mount(e)},get parent(){return e},[Symbol.dispose](){this.dispose()},unbind(e){i.unbind(e)}}}export{e as KeymapError,t as KeymapParseError,u as canonicalizeShortcut,x as createKeymap,S as createKeymapLayer,n as detectModKey,p as findShortcutConflicts,v as formatShortcut,d as matchStep,l as parseShortcut,c 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","../src/layer.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 buildModifierAliases(modKey: 'ctrl' | 'meta'): Record<string, ModifierKey> {\n return { ...MODIFIER_ALIASES_BASE, mod: modKey };\n}\n\nfunction normalizeModifier(token: string, aliases: Record<string, ModifierKey>): ModifierKey | null {\n const lower = token.toLowerCase();\n\n return Object.hasOwn(aliases, lower) ? aliases[lower]! : null;\n}\n\nfunction normalizeKey(raw: string): string {\n return Object.hasOwn(SPECIAL_KEY_ALIASES, raw) ? SPECIAL_KEY_ALIASES[raw]! : raw;\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((p) => p.toLowerCase());\n\n if (parts.length === 0) return null;\n\n const aliases = buildModifierAliases(modKey);\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const mod = normalizeModifier(part, aliases);\n\n if (mod) {\n modifiers.add(mod);\n } else {\n keyParts.push(part);\n }\n }\n\n if (keyParts.length === 0) return null;\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const key = normalizeKey(keyParts[0]);\n\n return { key, modifiers };\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n return raw\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((step) => {\n const result = parseStep(step, modKey);\n\n if (result === null) {\n throw new KeymapParseError(`Invalid shortcut step: \"${step}\" in \"${raw}\"`);\n }\n\n return result;\n });\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((s) => {\n const mods = [...s.modifiers].sort().join('+');\n\n return mods ? `${mods}+${s.key}` : s.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 if (event.altKey !== modifiers.has('alt')) return false;\n\n if (event.ctrlKey !== modifiers.has('ctrl')) return false;\n\n if (event.metaKey !== modifiers.has('meta')) return false;\n\n if (event.shiftKey !== modifiers.has('shift')) return false;\n\n return true;\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 const steps = parseShortcut(shortcut, modKey);\n\n // An empty/whitespace-only shortcut parses to zero steps, which would otherwise be treated as\n // a trivial prefix of every entry — never report that as a conflict.\n if (steps.length === 0) return [];\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, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions } from './types';\n\nimport { warn } from './_dev';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\n\ntype ParsedBinding = {\n handler: Handler;\n priority: number;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: () => boolean;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') {\n return { handler: value, priority: 0, trigger: 'keydown' };\n }\n\n const priority = value.priority ?? 0;\n\n if (!Number.isFinite(priority)) {\n warn(`binding priority must be a finite number; received ${priority}. Using 0.`);\n }\n\n return {\n handler: value.handler,\n priority: Number.isFinite(priority) ? priority : 0,\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> | null = null;\n\n function clearTimer(): void {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n function reset(): void {\n clearTimer();\n pendingIndex = 0;\n candidates = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | null {\n // At the root of a chord, re-scan every binding. Mid-chord, narrow only within bindings that\n // already matched every prior step — never re-admit a binding whose earlier step didn't match.\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched: ParsedBinding[] = [];\n\n for (const binding of pool) {\n const step = binding.shortcut[pendingIndex];\n\n if (step && matchStep(event, step)) {\n matched.push(binding);\n }\n }\n\n if (matched.length === 0) {\n const wasPartial = pendingIndex !== 0;\n\n reset();\n\n if (wasPartial) {\n return advance(event);\n }\n\n return null;\n }\n\n clearTimer();\n\n // At most one binding can complete here — the bindings map is keyed by canonical shortcut,\n // so two live bindings can never share an identical step sequence. `priority` therefore never\n // has a real tie to resolve; the shortest completing match always fires immediately.\n const completed = matched.find((b) => b.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n candidates = matched;\n pendingIndex++;\n timer = setTimeout(reset, chordTimeout);\n\n return null;\n }\n\n return { advance, reset };\n}\n\n/**\n * Creates a headless keyboard shortcut manager.\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 * `priority`, 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: () => isPanelOpen() },\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\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 let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const b of bindings.values()) {\n if (b.trigger === 'keydown') bindingsDown.push(b);\n else bindingsUp.push(b);\n }\n }\n\n function addBinding(shortcutStr: string, value: BindingValue): string {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n bindings.set(key, { shortcut, ...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 removeBinding(shortcutStr: string): boolean {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n return removeByKey(key);\n }\n\n for (const [shortcutStr, value] of Object.entries(initialBindings)) {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n bindings.set(key, { shortcut, ...resolveBinding(value) });\n }\n\n rebuildTriggerCaches();\n\n const chordDown = createChordTracker(() => bindingsDown, chordTimeout);\n const chordUp = createChordTracker(() => bindingsUp, chordTimeout);\n const unmounts = new Set<() => void>();\n const mountCounts = new Map<EventTarget, number>();\n\n function makeHandler(chord: ReturnType<typeof createChordTracker>) {\n return function handleEvent(event: KeyboardEvent): void {\n if (globalWhen && !globalWhen()) return;\n\n const binding = chord.advance(event);\n\n if (!binding) return;\n\n if (binding.when && !binding.when()) return;\n\n if (preventDefault) event.preventDefault();\n\n if (stopPropagation) event.stopPropagation();\n\n binding.handler(event);\n };\n }\n\n const handleKeydown = makeHandler(chordDown);\n const handleKeyup = makeHandler(chordUp);\n const ac = new AbortController();\n let isDisposed = false;\n\n return {\n bind(shortcutStr: string, value: BindingValue): () => void {\n const key = addBinding(shortcutStr, value);\n\n return () => {\n removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return ac.signal;\n },\n\n dispose(): void {\n if (isDisposed) return;\n\n isDisposed = true;\n ac.abort();\n for (const unmount of unmounts) unmount();\n unmounts.clear();\n chordDown.reset();\n chordUp.reset();\n },\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((b) => ({\n priority: b.priority,\n shortcut: b.shortcut.map((s) => ({ key: s.key, modifiers: new Set(s.modifiers) })),\n trigger: b.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n const priorCount = mountCounts.get(target) ?? 0;\n\n if (priorCount > 0) {\n warn('mount() called for a target that is already mounted — this registers a duplicate listener');\n }\n\n mountCounts.set(target, priorCount + 1);\n\n target.addEventListener('keydown', handleKeydown as EventListener);\n target.addEventListener('keyup', handleKeyup as EventListener);\n\n const unmount = (): void => {\n target.removeEventListener('keydown', handleKeydown as EventListener);\n target.removeEventListener('keyup', handleKeyup as EventListener);\n unmounts.delete(unmount);\n\n const remaining = (mountCounts.get(target) ?? 1) - 1;\n\n if (remaining <= 0) mountCounts.delete(target);\n else mountCounts.set(target, remaining);\n };\n\n unmounts.add(unmount);\n\n return unmount;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcutStr: string): void {\n const existed = removeBinding(shortcutStr);\n\n if (!existed) {\n warn(`unbind() called for unknown shortcut: \"${shortcutStr}\"`);\n }\n },\n };\n}\n\nexport type { BindingOptions };\n","import type { BindingValue, Keymap, KeymapOptions } from './types';\n\nimport { createKeymap } from './keymap';\n\n/**\n * Creates a scoped keymap layer that sits on top of a parent keymap.\n *\n * The **caller** is responsible for mounting and disposing the parent independently.\n * `layer.mount(target)` only attaches the layer's own shortcuts — it does not touch\n * the parent. `layer.dispose()` only tears down the layer — it does not dispose the parent.\n *\n * While the layer is active, matching shortcuts are handled by the layer. Call\n * `layer.deactivate()` to suspend the layer and `layer.activate()` to re-enable it.\n *\n * @example\n * const base = createKeymap({ 'ctrl+z': undo });\n * const modal = createKeymapLayer(base, {\n * esc: { handler: closeModal, when: () => isModalOpen() },\n * });\n * // Mount both independently — the layer does NOT auto-mount the parent.\n * const unmountBase = base.mount(document);\n * const unmountModal = modal.mount(document);\n *\n * modal.deactivate(); // base handles everything; layer is suspended\n * modal.activate(); // layer is active again\n */\nexport interface KeymapLayer extends Keymap {\n activate(): void;\n deactivate(): void;\n readonly active: boolean;\n readonly parent: Keymap;\n}\n\nexport function createKeymapLayer(\n parent: Keymap,\n initialBindings: Record<string, BindingValue> = {},\n options: KeymapOptions = {},\n): KeymapLayer {\n let isActive = true;\n\n const layer = createKeymap(initialBindings, {\n ...options,\n when: () => isActive && (options.when?.() ?? true),\n });\n\n return {\n activate(): void {\n isActive = true;\n },\n\n get active() {\n return isActive;\n },\n\n bind(shortcut: string, value: BindingValue): () => void {\n return layer.bind(shortcut, value);\n },\n\n deactivate(): void {\n isActive = false;\n },\n\n get disposalSignal(): AbortSignal {\n return layer.disposalSignal;\n },\n\n dispose(): void {\n layer.dispose();\n },\n\n get disposed(): boolean {\n return layer.disposed;\n },\n\n listBindings() {\n return layer.listBindings();\n },\n\n mount(target: EventTarget): () => void {\n return layer.mount(target);\n },\n\n get parent() {\n return parent;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n layer.unbind(shortcut);\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","../src/layer.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 buildModifierAliases(modKey: 'ctrl' | 'meta'): Record<string, ModifierKey> {\n return { ...MODIFIER_ALIASES_BASE, mod: modKey };\n}\n\nfunction normalizeModifier(token: string, aliases: Record<string, ModifierKey>): ModifierKey | null {\n const lower = token.toLowerCase();\n\n return Object.hasOwn(aliases, lower) ? aliases[lower]! : null;\n}\n\nfunction normalizeKey(raw: string): string {\n return Object.hasOwn(SPECIAL_KEY_ALIASES, raw) ? SPECIAL_KEY_ALIASES[raw]! : raw;\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((p) => p.toLowerCase());\n\n if (parts.length === 0) return null;\n\n const aliases = buildModifierAliases(modKey);\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const mod = normalizeModifier(part, aliases);\n\n if (mod) {\n modifiers.add(mod);\n } else {\n keyParts.push(part);\n }\n }\n\n if (keyParts.length === 0) return null;\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const key = normalizeKey(keyParts[0]);\n\n return { key, modifiers };\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n return raw\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((step) => {\n const result = parseStep(step, modKey);\n\n if (result === null) {\n throw new KeymapParseError(`Invalid shortcut step: \"${step}\" in \"${raw}\"`);\n }\n\n return result;\n });\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((s) => {\n const mods = [...s.modifiers].sort().join('+');\n\n return mods ? `${mods}+${s.key}` : s.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 if (event.altKey !== modifiers.has('alt')) return false;\n\n if (event.ctrlKey !== modifiers.has('ctrl')) return false;\n\n if (event.metaKey !== modifiers.has('meta')) return false;\n\n if (event.shiftKey !== modifiers.has('shift')) return false;\n\n return true;\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 const steps = parseShortcut(shortcut, modKey);\n\n // An empty/whitespace-only shortcut parses to zero steps, which would otherwise be treated as\n // a trivial prefix of every entry — never report that as a conflict.\n if (steps.length === 0) return [];\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, BindingOptions, BindingValue, Handler, Keymap, KeymapOptions } from './types';\n\nimport { warn } from './_dev';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\n\ntype ParsedBinding = {\n handler: Handler;\n priority: number;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: () => boolean;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') {\n return { handler: value, priority: 0, trigger: 'keydown' };\n }\n\n const priority = value.priority ?? 0;\n\n if (!Number.isFinite(priority)) {\n warn(`binding priority must be a finite number; received ${priority}. Using 0.`);\n }\n\n return {\n handler: value.handler,\n priority: Number.isFinite(priority) ? priority : 0,\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> | null = null;\n\n function clearTimer(): void {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n function reset(): void {\n clearTimer();\n pendingIndex = 0;\n candidates = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | null {\n // At the root of a chord, re-scan every binding. Mid-chord, narrow only within bindings that\n // already matched every prior step — never re-admit a binding whose earlier step didn't match.\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched: ParsedBinding[] = [];\n\n for (const binding of pool) {\n const step = binding.shortcut[pendingIndex];\n\n if (step && matchStep(event, step)) {\n matched.push(binding);\n }\n }\n\n if (matched.length === 0) {\n const wasPartial = pendingIndex !== 0;\n\n reset();\n\n if (wasPartial) {\n return advance(event);\n }\n\n return null;\n }\n\n clearTimer();\n\n // At most one binding can complete here — the bindings map is keyed by canonical shortcut,\n // so two live bindings can never share an identical step sequence. `priority` therefore never\n // has a real tie to resolve; the shortest completing match always fires immediately.\n const completed = matched.find((b) => b.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n candidates = matched;\n pendingIndex++;\n timer = setTimeout(reset, chordTimeout);\n\n return null;\n }\n\n return { advance, reset };\n}\n\n/**\n * Creates a headless keyboard shortcut manager.\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 * `priority`, 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: () => isPanelOpen() },\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\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 let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const b of bindings.values()) {\n if (b.trigger === 'keydown') bindingsDown.push(b);\n else bindingsUp.push(b);\n }\n }\n\n function addBinding(shortcutStr: string, value: BindingValue): string {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n bindings.set(key, { shortcut, ...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 removeBinding(shortcutStr: string): boolean {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n return removeByKey(key);\n }\n\n for (const [shortcutStr, value] of Object.entries(initialBindings)) {\n const shortcut = parseShortcut(shortcutStr, modKey);\n const key = canonicalizeShortcut(shortcut);\n\n bindings.set(key, { shortcut, ...resolveBinding(value) });\n }\n\n rebuildTriggerCaches();\n\n const chordDown = createChordTracker(() => bindingsDown, chordTimeout);\n const chordUp = createChordTracker(() => bindingsUp, chordTimeout);\n const unmounts = new Set<() => void>();\n const mountCounts = new Map<EventTarget, number>();\n\n function makeHandler(chord: ReturnType<typeof createChordTracker>) {\n return function handleEvent(event: KeyboardEvent): void {\n if (globalWhen && !globalWhen()) return;\n\n const binding = chord.advance(event);\n\n if (!binding) return;\n\n if (binding.when && !binding.when()) return;\n\n if (preventDefault) event.preventDefault();\n\n if (stopPropagation) event.stopPropagation();\n\n binding.handler(event);\n };\n }\n\n const handleKeydown = makeHandler(chordDown);\n const handleKeyup = makeHandler(chordUp);\n const ac = new AbortController();\n let isDisposed = false;\n\n return {\n bind(shortcutStr: string, value: BindingValue): () => void {\n const key = addBinding(shortcutStr, value);\n\n return () => {\n removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return ac.signal;\n },\n\n dispose(): void {\n if (isDisposed) return;\n\n isDisposed = true;\n ac.abort();\n for (const unmount of unmounts) unmount();\n unmounts.clear();\n chordDown.reset();\n chordUp.reset();\n },\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((b) => ({\n priority: b.priority,\n shortcut: b.shortcut.map((s) => ({ key: s.key, modifiers: new Set(s.modifiers) })),\n trigger: b.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n const priorCount = mountCounts.get(target) ?? 0;\n\n if (priorCount > 0) {\n warn('mount() called for a target that is already mounted — this registers a duplicate listener');\n }\n\n mountCounts.set(target, priorCount + 1);\n\n target.addEventListener('keydown', handleKeydown as EventListener);\n target.addEventListener('keyup', handleKeyup as EventListener);\n\n const unmount = (): void => {\n target.removeEventListener('keydown', handleKeydown as EventListener);\n target.removeEventListener('keyup', handleKeyup as EventListener);\n unmounts.delete(unmount);\n\n const remaining = (mountCounts.get(target) ?? 1) - 1;\n\n if (remaining <= 0) mountCounts.delete(target);\n else mountCounts.set(target, remaining);\n };\n\n unmounts.add(unmount);\n\n return unmount;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcutStr: string): void {\n const existed = removeBinding(shortcutStr);\n\n if (!existed) {\n warn(`unbind() called for unknown shortcut: \"${shortcutStr}\"`);\n }\n },\n };\n}\n\nexport type { BindingOptions };\n","import type { BindingValue, Keymap, KeymapOptions } from './types';\n\nimport { createKeymap } from './keymap';\n\n/**\n * Creates a scoped keymap layer that sits on top of a parent keymap.\n *\n * The **caller** is responsible for mounting and disposing the parent independently.\n * `layer.mount(target)` only attaches the layer's own shortcuts — it does not touch\n * the parent. `layer.dispose()` only tears down the layer — it does not dispose the parent.\n *\n * While the layer is active, matching shortcuts are handled by the layer. Call\n * `layer.deactivate()` to suspend the layer and `layer.activate()` to re-enable it.\n *\n * @example\n * const base = createKeymap({ 'ctrl+z': undo });\n * const modal = createKeymapLayer(base, {\n * esc: { handler: closeModal, when: () => isModalOpen() },\n * });\n * // Mount both independently — the layer does NOT auto-mount the parent.\n * const unmountBase = base.mount(document);\n * const unmountModal = modal.mount(document);\n *\n * modal.deactivate(); // base handles everything; layer is suspended\n * modal.activate(); // layer is active again\n */\nexport interface KeymapLayer extends Keymap {\n activate(): void;\n deactivate(): void;\n readonly active: boolean;\n readonly parent: Keymap;\n}\n\nexport function createKeymapLayer(\n parent: Keymap,\n initialBindings: Record<string, BindingValue> = {},\n options: KeymapOptions = {},\n): KeymapLayer {\n let isActive = true;\n\n const layer = createKeymap(initialBindings, {\n ...options,\n when: () => isActive && (options.when?.() ?? true),\n });\n\n return {\n activate(): void {\n isActive = true;\n },\n\n get active() {\n return isActive;\n },\n\n bind(shortcut: string, value: BindingValue): () => void {\n return layer.bind(shortcut, value);\n },\n\n deactivate(): void {\n isActive = false;\n },\n\n get disposalSignal(): AbortSignal {\n return layer.disposalSignal;\n },\n\n dispose(): void {\n layer.dispose();\n },\n\n get disposed(): boolean {\n return layer.disposed;\n },\n\n listBindings() {\n return layer.listBindings();\n },\n\n mount(target: EventTarget): () => void {\n return layer.mount(target);\n },\n\n get parent() {\n return parent;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n layer.unbind(shortcut);\n },\n };\n}\n"],"mappings":"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"}
package/dist/parser.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs");function t(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var n={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},r={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function i(e){return{...n,mod:e}}function a(e,t){let n=e.toLowerCase();return Object.hasOwn(t,n)?t[n]:null}function o(e){return Object.hasOwn(r,e)?r[e]:e}function s(n,r=t()){let s=n.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(s.length===0)return null;let c=i(r),l=new Set,u=[];for(let e of s){let t=a(e,c);t?l.add(t):u.push(e)}if(u.length===0)return null;if(u.length>1)throw new e.KeymapParseError(`Ambiguous shortcut step: "${n}" — multiple non-modifier keys found`);return{key:o(u[0]),modifiers:l}}function c(n,r=t()){return n.trim().split(/\s+/).filter(Boolean).map(t=>{let i=s(t,r);if(i===null)throw new e.KeymapParseError(`Invalid shortcut step: "${t}" in "${n}"`);return i})}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`))}exports.canonicalizeShortcut=l,exports.detectModKey=t,exports.matchStep=u,exports.parseShortcut=c,exports.parseStep=s;
1
+ const e=require("./errors.cjs");function t(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var n={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},r={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function i(e){return{...n,mod:e}}function a(e,t){let n=e.toLowerCase();return Object.hasOwn(t,n)?t[n]:null}function o(e){return Object.hasOwn(r,e)?r[e]:e}function s(n,r=t()){let s=n.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(s.length===0)return null;let c=i(r),l=new Set,u=[];for(let e of s){let t=a(e,c);t?l.add(t):u.push(e)}if(u.length===0)return null;if(u.length>1)throw new e.KeymapParseError(`Ambiguous shortcut step: "${n}" — multiple non-modifier keys found`);return{key:o(u[0]),modifiers:l}}function c(n,r=t()){return n.trim().split(/\s+/).filter(Boolean).map(t=>{let i=s(t,r);if(i===null)throw new e.KeymapParseError(`Invalid shortcut step: "${t}" in "${n}"`);return i})}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`)}exports.canonicalizeShortcut=l,exports.detectModKey=t,exports.matchStep=u,exports.parseShortcut=c,exports.parseStep=s;
2
2
  //# sourceMappingURL=parser.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"parser.cjs","names":[],"sources":["../src/parser.ts"],"sourcesContent":["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 buildModifierAliases(modKey: 'ctrl' | 'meta'): Record<string, ModifierKey> {\n return { ...MODIFIER_ALIASES_BASE, mod: modKey };\n}\n\nfunction normalizeModifier(token: string, aliases: Record<string, ModifierKey>): ModifierKey | null {\n const lower = token.toLowerCase();\n\n return Object.hasOwn(aliases, lower) ? aliases[lower]! : null;\n}\n\nfunction normalizeKey(raw: string): string {\n return Object.hasOwn(SPECIAL_KEY_ALIASES, raw) ? SPECIAL_KEY_ALIASES[raw]! : raw;\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((p) => p.toLowerCase());\n\n if (parts.length === 0) return null;\n\n const aliases = buildModifierAliases(modKey);\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const mod = normalizeModifier(part, aliases);\n\n if (mod) {\n modifiers.add(mod);\n } else {\n keyParts.push(part);\n }\n }\n\n if (keyParts.length === 0) return null;\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const key = normalizeKey(keyParts[0]);\n\n return { key, modifiers };\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n return raw\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((step) => {\n const result = parseStep(step, modKey);\n\n if (result === null) {\n throw new KeymapParseError(`Invalid shortcut step: \"${step}\" in \"${raw}\"`);\n }\n\n return result;\n });\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((s) => {\n const mods = [...s.modifiers].sort().join('+');\n\n return mods ? `${mods}+${s.key}` : s.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 if (event.altKey !== modifiers.has('alt')) return false;\n\n if (event.ctrlKey !== modifiers.has('ctrl')) return false;\n\n if (event.metaKey !== modifiers.has('meta')) return false;\n\n if (event.shiftKey !== modifiers.has('shift')) return false;\n\n return true;\n}\n"],"mappings":"gCAWA,SAAgB,GAAgC,CAC9C,GAAI,OAAO,UAAc,IAAa,MAAO,OAE7C,IAAM,EACH,UAAoE,eAAe,UACpF,UAAU,UACV,GAEF,MAAO,OAAO,KAAK,CAAQ,EAAI,OAAS,MAC1C,CAEA,IAAM,EAAqD,CACzD,IAAK,MACL,IAAK,OACL,QAAS,OACT,QAAS,OACT,KAAM,OACN,KAAM,OACN,IAAK,MACL,OAAQ,MACR,MAAO,QACP,IAAK,MACP,EAEM,EAA8C,CAClD,IAAK,SACL,KAAM,YACN,IAAK,SACL,KAAM,YACN,MAAO,aACP,MAAO,IACP,SAAU,IACV,GAAI,SACN,EAEA,SAAS,EAAqB,EAAsD,CAClF,MAAO,CAAE,GAAG,EAAuB,IAAK,CAAO,CACjD,CAEA,SAAS,EAAkB,EAAe,EAA0D,CAClG,IAAM,EAAQ,EAAM,YAAY,EAEhC,OAAO,OAAO,OAAO,EAAS,CAAK,EAAI,EAAQ,GAAU,IAC3D,CAEA,SAAS,EAAa,EAAqB,CACzC,OAAO,OAAO,OAAO,EAAqB,CAAG,EAAI,EAAoB,GAAQ,CAC/E,CAEA,SAAgB,EAAU,EAAa,EAA0B,EAAa,EAAwB,CACpG,IAAM,EAAQ,EACX,KAAK,CAAC,CACN,MAAM,GAAG,CAAC,CACV,OAAO,OAAO,CAAC,CACf,IAAK,GAAM,EAAE,YAAY,CAAC,EAE7B,GAAI,EAAM,SAAW,EAAG,OAAO,KAE/B,IAAM,EAAU,EAAqB,CAAM,EACrC,EAAY,IAAI,IAChB,EAAqB,CAAC,EAE5B,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAM,EAAkB,EAAM,CAAO,EAEvC,EACF,EAAU,IAAI,CAAG,EAEjB,EAAS,KAAK,CAAI,CAEtB,CAEA,GAAI,EAAS,SAAW,EAAG,OAAO,KAElC,GAAI,EAAS,OAAS,EACpB,MAAM,IAAI,EAAA,iBAAiB,6BAA6B,EAAI,qCAAqC,EAKnG,MAAO,CAAE,IAFG,EAAa,EAAS,EAEzB,EAAK,WAAU,CAC1B,CAEA,SAAgB,EAAc,EAAa,EAA0B,EAAa,EAAa,CAC7F,OAAO,EACJ,KAAK,CAAC,CACN,MAAM,KAAK,CAAC,CACZ,OAAO,OAAO,CAAC,CACf,IAAK,GAAS,CACb,IAAM,EAAS,EAAU,EAAM,CAAM,EAErC,GAAI,IAAW,KACb,MAAM,IAAI,EAAA,iBAAiB,2BAA2B,EAAK,QAAQ,EAAI,EAAE,EAG3E,OAAO,CACT,CAAC,CACL,CAEA,SAAgB,EAAqB,EAAwC,CAC3E,OAAO,EACJ,IAAK,GAAM,CACV,IAAM,EAAO,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,EAE7C,OAAO,EAAO,GAAG,EAAK,GAAG,EAAE,MAAQ,EAAE,GACvC,CAAC,CAAC,CACD,KAAK,GAAG,CACb,CAEA,SAAgB,EAAU,EAAsB,EAA6B,CAM3E,GAFI,OAAO,EAAM,KAAQ,UAErB,EAAM,IAAI,YAAY,IAAM,EAAK,IAAK,MAAO,GAEjD,GAAM,CAAE,aAAc,EAUtB,MAFA,EANI,EAAM,SAAW,EAAU,IAAI,KAAK,GAEpC,EAAM,UAAY,EAAU,IAAI,MAAM,GAEtC,EAAM,UAAY,EAAU,IAAI,MAAM,GAEtC,EAAM,WAAa,EAAU,IAAI,OAAO,EAG9C"}
1
+ {"version":3,"file":"parser.cjs","names":[],"sources":["../src/parser.ts"],"sourcesContent":["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 buildModifierAliases(modKey: 'ctrl' | 'meta'): Record<string, ModifierKey> {\n return { ...MODIFIER_ALIASES_BASE, mod: modKey };\n}\n\nfunction normalizeModifier(token: string, aliases: Record<string, ModifierKey>): ModifierKey | null {\n const lower = token.toLowerCase();\n\n return Object.hasOwn(aliases, lower) ? aliases[lower]! : null;\n}\n\nfunction normalizeKey(raw: string): string {\n return Object.hasOwn(SPECIAL_KEY_ALIASES, raw) ? SPECIAL_KEY_ALIASES[raw]! : raw;\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((p) => p.toLowerCase());\n\n if (parts.length === 0) return null;\n\n const aliases = buildModifierAliases(modKey);\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const mod = normalizeModifier(part, aliases);\n\n if (mod) {\n modifiers.add(mod);\n } else {\n keyParts.push(part);\n }\n }\n\n if (keyParts.length === 0) return null;\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const key = normalizeKey(keyParts[0]);\n\n return { key, modifiers };\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n return raw\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((step) => {\n const result = parseStep(step, modKey);\n\n if (result === null) {\n throw new KeymapParseError(`Invalid shortcut step: \"${step}\" in \"${raw}\"`);\n }\n\n return result;\n });\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((s) => {\n const mods = [...s.modifiers].sort().join('+');\n\n return mods ? `${mods}+${s.key}` : s.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 if (event.altKey !== modifiers.has('alt')) return false;\n\n if (event.ctrlKey !== modifiers.has('ctrl')) return false;\n\n if (event.metaKey !== modifiers.has('meta')) return false;\n\n if (event.shiftKey !== modifiers.has('shift')) return false;\n\n return true;\n}\n"],"mappings":"gCAWA,SAAgB,GAAgC,CAC9C,GAAI,OAAO,UAAc,IAAa,MAAO,OAE7C,IAAM,EACH,UAAoE,eAAe,UACpF,UAAU,UACV,GAEF,MAAO,OAAO,KAAK,CAAQ,EAAI,OAAS,MAC1C,CAEA,IAAM,EAAqD,CACzD,IAAK,MACL,IAAK,OACL,QAAS,OACT,QAAS,OACT,KAAM,OACN,KAAM,OACN,IAAK,MACL,OAAQ,MACR,MAAO,QACP,IAAK,MACP,EAEM,EAA8C,CAClD,IAAK,SACL,KAAM,YACN,IAAK,SACL,KAAM,YACN,MAAO,aACP,MAAO,IACP,SAAU,IACV,GAAI,SACN,EAEA,SAAS,EAAqB,EAAsD,CAClF,MAAO,CAAE,GAAG,EAAuB,IAAK,CAAO,CACjD,CAEA,SAAS,EAAkB,EAAe,EAA0D,CAClG,IAAM,EAAQ,EAAM,YAAY,EAEhC,OAAO,OAAO,OAAO,EAAS,CAAK,EAAI,EAAQ,GAAU,IAC3D,CAEA,SAAS,EAAa,EAAqB,CACzC,OAAO,OAAO,OAAO,EAAqB,CAAG,EAAI,EAAoB,GAAQ,CAC/E,CAEA,SAAgB,EAAU,EAAa,EAA0B,EAAa,EAAwB,CACpG,IAAM,EAAQ,EACX,KAAK,CAAC,CACN,MAAM,GAAG,CAAC,CACV,OAAO,OAAO,CAAC,CACf,IAAK,GAAM,EAAE,YAAY,CAAC,EAE7B,GAAI,EAAM,SAAW,EAAG,OAAO,KAE/B,IAAM,EAAU,EAAqB,CAAM,EACrC,EAAY,IAAI,IAChB,EAAqB,CAAC,EAE5B,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAM,EAAkB,EAAM,CAAO,EAEvC,EACF,EAAU,IAAI,CAAG,EAEjB,EAAS,KAAK,CAAI,CAEtB,CAEA,GAAI,EAAS,SAAW,EAAG,OAAO,KAElC,GAAI,EAAS,OAAS,EACpB,MAAM,IAAI,EAAA,iBAAiB,6BAA6B,EAAI,qCAAqC,EAKnG,MAAO,CAAE,IAFG,EAAa,EAAS,EAEzB,EAAK,WAAU,CAC1B,CAEA,SAAgB,EAAc,EAAa,EAA0B,EAAa,EAAa,CAC7F,OAAO,EACJ,KAAK,CAAC,CACN,MAAM,KAAK,CAAC,CACZ,OAAO,OAAO,CAAC,CACf,IAAK,GAAS,CACb,IAAM,EAAS,EAAU,EAAM,CAAM,EAErC,GAAI,IAAW,KACb,MAAM,IAAI,EAAA,iBAAiB,2BAA2B,EAAK,QAAQ,EAAI,EAAE,EAG3E,OAAO,CACT,CAAC,CACL,CAEA,SAAgB,EAAqB,EAAwC,CAC3E,OAAO,EACJ,IAAK,GAAM,CACV,IAAM,EAAO,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,EAE7C,OAAO,EAAO,GAAG,EAAK,GAAG,EAAE,MAAQ,EAAE,GACvC,CAAC,CAAC,CACD,KAAK,GAAG,CACb,CAEA,SAAgB,EAAU,EAAsB,EAA6B,CAM3E,GAFI,OAAO,EAAM,KAAQ,UAErB,EAAM,IAAI,YAAY,IAAM,EAAK,IAAK,MAAO,GAEjD,GAAM,CAAE,aAAc,EAUtB,OARI,EAAM,SAAW,EAAU,IAAI,KAAK,GAEpC,EAAM,UAAY,EAAU,IAAI,MAAM,GAEtC,EAAM,UAAY,EAAU,IAAI,MAAM,GAEtC,EAAM,WAAa,EAAU,IAAI,OAAO,CAG9C"}
package/dist/parser.js CHANGED
@@ -70,7 +70,7 @@ function l(e) {
70
70
  function u(e, t) {
71
71
  if (typeof e.key != "string" || e.key.toLowerCase() !== t.key) return !1;
72
72
  let { modifiers: n } = t;
73
- return !(e.altKey !== n.has("alt") || e.ctrlKey !== n.has("ctrl") || e.metaKey !== n.has("meta") || e.shiftKey !== n.has("shift"));
73
+ return e.altKey === n.has("alt") && e.ctrlKey === n.has("ctrl") && e.metaKey === n.has("meta") && e.shiftKey === n.has("shift");
74
74
  }
75
75
  //#endregion
76
76
  export { l as canonicalizeShortcut, t as detectModKey, u as matchStep, c as parseShortcut, s as parseStep };
@@ -1 +1 @@
1
- {"version":3,"file":"parser.js","names":[],"sources":["../src/parser.ts"],"sourcesContent":["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 buildModifierAliases(modKey: 'ctrl' | 'meta'): Record<string, ModifierKey> {\n return { ...MODIFIER_ALIASES_BASE, mod: modKey };\n}\n\nfunction normalizeModifier(token: string, aliases: Record<string, ModifierKey>): ModifierKey | null {\n const lower = token.toLowerCase();\n\n return Object.hasOwn(aliases, lower) ? aliases[lower]! : null;\n}\n\nfunction normalizeKey(raw: string): string {\n return Object.hasOwn(SPECIAL_KEY_ALIASES, raw) ? SPECIAL_KEY_ALIASES[raw]! : raw;\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((p) => p.toLowerCase());\n\n if (parts.length === 0) return null;\n\n const aliases = buildModifierAliases(modKey);\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const mod = normalizeModifier(part, aliases);\n\n if (mod) {\n modifiers.add(mod);\n } else {\n keyParts.push(part);\n }\n }\n\n if (keyParts.length === 0) return null;\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const key = normalizeKey(keyParts[0]);\n\n return { key, modifiers };\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n return raw\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((step) => {\n const result = parseStep(step, modKey);\n\n if (result === null) {\n throw new KeymapParseError(`Invalid shortcut step: \"${step}\" in \"${raw}\"`);\n }\n\n return result;\n });\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((s) => {\n const mods = [...s.modifiers].sort().join('+');\n\n return mods ? `${mods}+${s.key}` : s.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 if (event.altKey !== modifiers.has('alt')) return false;\n\n if (event.ctrlKey !== modifiers.has('ctrl')) return false;\n\n if (event.metaKey !== modifiers.has('meta')) return false;\n\n if (event.shiftKey !== modifiers.has('shift')) return false;\n\n return true;\n}\n"],"mappings":";;AAWA,SAAgB,IAAgC;CAC9C,IAAI,OAAO,YAAc,KAAa,OAAO;CAE7C,IAAM,IACH,UAAoE,eAAe,YACpF,UAAU,YACV;CAEF,OAAO,OAAO,KAAK,CAAQ,IAAI,SAAS;AAC1C;AAEA,IAAM,IAAqD;CACzD,KAAK;CACL,KAAK;CACL,SAAS;CACT,SAAS;CACT,MAAM;CACN,MAAM;CACN,KAAK;CACL,QAAQ;CACR,OAAO;CACP,KAAK;AACP,GAEM,IAA8C;CAClD,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,OAAO;CACP,OAAO;CACP,UAAU;CACV,IAAI;AACN;AAEA,SAAS,EAAqB,GAAsD;CAClF,OAAO;EAAE,GAAG;EAAuB,KAAK;CAAO;AACjD;AAEA,SAAS,EAAkB,GAAe,GAA0D;CAClG,IAAM,IAAQ,EAAM,YAAY;CAEhC,OAAO,OAAO,OAAO,GAAS,CAAK,IAAI,EAAQ,KAAU;AAC3D;AAEA,SAAS,EAAa,GAAqB;CACzC,OAAO,OAAO,OAAO,GAAqB,CAAG,IAAI,EAAoB,KAAQ;AAC/E;AAEA,SAAgB,EAAU,GAAa,IAA0B,EAAa,GAAwB;CACpG,IAAM,IAAQ,EACX,KAAK,CAAC,CACN,MAAM,GAAG,CAAC,CACV,OAAO,OAAO,CAAC,CACf,KAAK,MAAM,EAAE,YAAY,CAAC;CAE7B,IAAI,EAAM,WAAW,GAAG,OAAO;CAE/B,IAAM,IAAU,EAAqB,CAAM,GACrC,oBAAY,IAAI,IAAiB,GACjC,IAAqB,CAAC;CAE5B,KAAK,IAAM,KAAQ,GAAO;EACxB,IAAM,IAAM,EAAkB,GAAM,CAAO;EAE3C,AAAI,IACF,EAAU,IAAI,CAAG,IAEjB,EAAS,KAAK,CAAI;CAEtB;CAEA,IAAI,EAAS,WAAW,GAAG,OAAO;CAElC,IAAI,EAAS,SAAS,GACpB,MAAM,IAAI,EAAiB,6BAA6B,EAAI,qCAAqC;CAKnG,OAAO;EAAE,KAFG,EAAa,EAAS,EAEzB;EAAK;CAAU;AAC1B;AAEA,SAAgB,EAAc,GAAa,IAA0B,EAAa,GAAa;CAC7F,OAAO,EACJ,KAAK,CAAC,CACN,MAAM,KAAK,CAAC,CACZ,OAAO,OAAO,CAAC,CACf,KAAK,MAAS;EACb,IAAM,IAAS,EAAU,GAAM,CAAM;EAErC,IAAI,MAAW,MACb,MAAM,IAAI,EAAiB,2BAA2B,EAAK,QAAQ,EAAI,EAAE;EAG3E,OAAO;CACT,CAAC;AACL;AAEA,SAAgB,EAAqB,GAAwC;CAC3E,OAAO,EACJ,KAAK,MAAM;EACV,IAAM,IAAO,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG;EAE7C,OAAO,IAAO,GAAG,EAAK,GAAG,EAAE,QAAQ,EAAE;CACvC,CAAC,CAAC,CACD,KAAK,GAAG;AACb;AAEA,SAAgB,EAAU,GAAsB,GAA6B;CAM3E,IAFI,OAAO,EAAM,OAAQ,YAErB,EAAM,IAAI,YAAY,MAAM,EAAK,KAAK,OAAO;CAEjD,IAAM,EAAE,iBAAc;CAUtB,OAFA,EANI,EAAM,WAAW,EAAU,IAAI,KAAK,KAEpC,EAAM,YAAY,EAAU,IAAI,MAAM,KAEtC,EAAM,YAAY,EAAU,IAAI,MAAM,KAEtC,EAAM,aAAa,EAAU,IAAI,OAAO;AAG9C"}
1
+ {"version":3,"file":"parser.js","names":[],"sources":["../src/parser.ts"],"sourcesContent":["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 buildModifierAliases(modKey: 'ctrl' | 'meta'): Record<string, ModifierKey> {\n return { ...MODIFIER_ALIASES_BASE, mod: modKey };\n}\n\nfunction normalizeModifier(token: string, aliases: Record<string, ModifierKey>): ModifierKey | null {\n const lower = token.toLowerCase();\n\n return Object.hasOwn(aliases, lower) ? aliases[lower]! : null;\n}\n\nfunction normalizeKey(raw: string): string {\n return Object.hasOwn(SPECIAL_KEY_ALIASES, raw) ? SPECIAL_KEY_ALIASES[raw]! : raw;\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((p) => p.toLowerCase());\n\n if (parts.length === 0) return null;\n\n const aliases = buildModifierAliases(modKey);\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const mod = normalizeModifier(part, aliases);\n\n if (mod) {\n modifiers.add(mod);\n } else {\n keyParts.push(part);\n }\n }\n\n if (keyParts.length === 0) return null;\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const key = normalizeKey(keyParts[0]);\n\n return { key, modifiers };\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n return raw\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((step) => {\n const result = parseStep(step, modKey);\n\n if (result === null) {\n throw new KeymapParseError(`Invalid shortcut step: \"${step}\" in \"${raw}\"`);\n }\n\n return result;\n });\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((s) => {\n const mods = [...s.modifiers].sort().join('+');\n\n return mods ? `${mods}+${s.key}` : s.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 if (event.altKey !== modifiers.has('alt')) return false;\n\n if (event.ctrlKey !== modifiers.has('ctrl')) return false;\n\n if (event.metaKey !== modifiers.has('meta')) return false;\n\n if (event.shiftKey !== modifiers.has('shift')) return false;\n\n return true;\n}\n"],"mappings":";;AAWA,SAAgB,IAAgC;CAC9C,IAAI,OAAO,YAAc,KAAa,OAAO;CAE7C,IAAM,IACH,UAAoE,eAAe,YACpF,UAAU,YACV;CAEF,OAAO,OAAO,KAAK,CAAQ,IAAI,SAAS;AAC1C;AAEA,IAAM,IAAqD;CACzD,KAAK;CACL,KAAK;CACL,SAAS;CACT,SAAS;CACT,MAAM;CACN,MAAM;CACN,KAAK;CACL,QAAQ;CACR,OAAO;CACP,KAAK;AACP,GAEM,IAA8C;CAClD,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,OAAO;CACP,OAAO;CACP,UAAU;CACV,IAAI;AACN;AAEA,SAAS,EAAqB,GAAsD;CAClF,OAAO;EAAE,GAAG;EAAuB,KAAK;CAAO;AACjD;AAEA,SAAS,EAAkB,GAAe,GAA0D;CAClG,IAAM,IAAQ,EAAM,YAAY;CAEhC,OAAO,OAAO,OAAO,GAAS,CAAK,IAAI,EAAQ,KAAU;AAC3D;AAEA,SAAS,EAAa,GAAqB;CACzC,OAAO,OAAO,OAAO,GAAqB,CAAG,IAAI,EAAoB,KAAQ;AAC/E;AAEA,SAAgB,EAAU,GAAa,IAA0B,EAAa,GAAwB;CACpG,IAAM,IAAQ,EACX,KAAK,CAAC,CACN,MAAM,GAAG,CAAC,CACV,OAAO,OAAO,CAAC,CACf,KAAK,MAAM,EAAE,YAAY,CAAC;CAE7B,IAAI,EAAM,WAAW,GAAG,OAAO;CAE/B,IAAM,IAAU,EAAqB,CAAM,GACrC,oBAAY,IAAI,IAAiB,GACjC,IAAqB,CAAC;CAE5B,KAAK,IAAM,KAAQ,GAAO;EACxB,IAAM,IAAM,EAAkB,GAAM,CAAO;EAE3C,AAAI,IACF,EAAU,IAAI,CAAG,IAEjB,EAAS,KAAK,CAAI;CAEtB;CAEA,IAAI,EAAS,WAAW,GAAG,OAAO;CAElC,IAAI,EAAS,SAAS,GACpB,MAAM,IAAI,EAAiB,6BAA6B,EAAI,qCAAqC;CAKnG,OAAO;EAAE,KAFG,EAAa,EAAS,EAEzB;EAAK;CAAU;AAC1B;AAEA,SAAgB,EAAc,GAAa,IAA0B,EAAa,GAAa;CAC7F,OAAO,EACJ,KAAK,CAAC,CACN,MAAM,KAAK,CAAC,CACZ,OAAO,OAAO,CAAC,CACf,KAAK,MAAS;EACb,IAAM,IAAS,EAAU,GAAM,CAAM;EAErC,IAAI,MAAW,MACb,MAAM,IAAI,EAAiB,2BAA2B,EAAK,QAAQ,EAAI,EAAE;EAG3E,OAAO;CACT,CAAC;AACL;AAEA,SAAgB,EAAqB,GAAwC;CAC3E,OAAO,EACJ,KAAK,MAAM;EACV,IAAM,IAAO,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG;EAE7C,OAAO,IAAO,GAAG,EAAK,GAAG,EAAE,QAAQ,EAAE;CACvC,CAAC,CAAC,CACD,KAAK,GAAG;AACb;AAEA,SAAgB,EAAU,GAAsB,GAA6B;CAM3E,IAFI,OAAO,EAAM,OAAQ,YAErB,EAAM,IAAI,YAAY,MAAM,EAAK,KAAK,OAAO;CAEjD,IAAM,EAAE,iBAAc;CAUtB,OARI,EAAM,WAAW,EAAU,IAAI,KAAK,KAEpC,EAAM,YAAY,EAAU,IAAI,MAAM,KAEtC,EAAM,YAAY,EAAU,IAAI,MAAM,KAEtC,EAAM,aAAa,EAAU,IAAI,OAAO;AAG9C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/keymap",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Headless keyboard shortcut manager with chord sequences, context guards, and disposable bindings",
5
5
  "type": "module",
6
6
  "files": [
@@ -11,7 +11,6 @@
11
11
  "types": "dist/index.d.ts",
12
12
  "exports": {
13
13
  ".": {
14
- "source": "./src/index.ts",
15
14
  "types": "./dist/index.d.ts",
16
15
  "import": "./dist/index.js",
17
16
  "require": "./dist/index.cjs"
@@ -31,12 +30,12 @@
31
30
  "registry": "https://registry.npmjs.org/"
32
31
  },
33
32
  "engines": {
34
- "node": ">=18"
33
+ "node": ">=22"
35
34
  },
36
35
  "devDependencies": {
37
- "@types/node": "^26.1.0",
38
- "typescript": "~6.0.3",
39
- "vite": "^8.1.3",
40
- "vitest": "^4.1.9"
36
+ "@types/node": "^26.1.2",
37
+ "typescript": "^6.0.3",
38
+ "vite": "^8.2.0",
39
+ "vitest": "^4.1.10"
41
40
  }
42
41
  }