@vielzeug/keymap 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,39 +1,30 @@
1
1
  # @vielzeug/keymap
2
2
 
3
- Headless keyboard shortcut manager with target-local chord state, event-aware guards, conflict detection, and terminal disposal.
3
+ > Headless keyboard shortcut manager with chord sequences
4
4
 
5
- ## Features
6
-
7
- - `createKeymap()` manages bindings on one or more `EventTarget`s.
8
- - Chords support sequences such as `"g g"` and `"ctrl+k ctrl+s"`.
9
- - `when(event)` guards inspect event target and composed path.
10
- - `findShortcutConflicts()` detects duplicate and unreachable prefix paths.
11
- - `formatShortcut()` produces Mac symbols or platform labels.
12
- - `dispose()` releases all listeners and permanently ends manager lifecycle.
13
-
14
- ## Install
5
+ ## Installation
15
6
 
16
7
  ```sh
17
8
  pnpm add @vielzeug/keymap
9
+ npm install @vielzeug/keymap
10
+ yarn add @vielzeug/keymap
18
11
  ```
19
12
 
20
13
  ## Quick Start
21
14
 
22
- Create one map for an application scope, then mount and dispose it with that scope.
23
-
24
15
  ```ts
25
16
  import { createKeymap, formatShortcut } from '@vielzeug/keymap';
26
17
 
27
18
  const isEditableTarget = (target: EventTarget | null): boolean =>
28
19
  target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
29
20
 
30
- const map = createKeymap({
31
- 'mod+k mod+s': () => console.log('save'),
32
- 'mod+shift+p': () => console.log('open palette'),
33
- 'g g': () => window.scrollTo({ top: 0 }),
34
- escape: { handler: () => console.log('close panel'), when: (event) => !isEditableTarget(event.target) },
35
- space: { handler: () => console.log('toggle playback'), trigger: 'keyup' },
36
- });
21
+ const map = createKeymap([
22
+ { id: 'save', shortcut: 'mod+k mod+s', handler: () => console.log('save') },
23
+ { id: 'palette', shortcut: 'mod+shift+p', handler: () => console.log('open palette') },
24
+ { id: 'top', shortcut: 'g g', handler: () => window.scrollTo({ top: 0 }) },
25
+ { id: 'close', shortcut: 'escape', handler: () => console.log('close panel'), when: (event) => !isEditableTarget(event.target) },
26
+ { id: 'play', shortcut: 'space', handler: () => console.log('toggle playback'), trigger: 'keyup' },
27
+ ]);
37
28
 
38
29
  const unmount = map.mount(document);
39
30
  console.log(formatShortcut('mod+shift+p', 'meta')); // ⇧⌘P
@@ -42,6 +33,14 @@ unmount();
42
33
  map.dispose();
43
34
  ```
44
35
 
45
- Use separate maps with mutually exclusive `when(event)` guards for independent UI owners that share shortcuts. `dispose()` is terminal; temporary detachment uses mount callbacks.
36
+ ## Documentation
37
+
38
+ - [Overview](https://vielzeug.dev/keymap/)
39
+ - [Usage Guide](https://vielzeug.dev/keymap/usage)
40
+ - [API Reference](https://vielzeug.dev/keymap/api)
41
+ - [Examples](https://vielzeug.dev/keymap/examples)
42
+ - [Migration Guide](https://vielzeug.dev/keymap/migration)
43
+
44
+ ## License
46
45
 
47
- [Full documentation](https://vielzeug.dev/keymap/)
46
+ MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
package/dist/errors.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{};exports.KeymapError=e,exports.KeymapParseError=t;
1
+ var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{};exports.KeymapError=e,exports.KeymapParseError=t;
2
2
  //# sourceMappingURL=errors.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is KeymapError {\n return err instanceof KeymapError;\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n"],"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,CAAC"}
1
+ {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n"],"mappings":"AACA,IAAa,EAAb,cAAiC,KAAM,CACrC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,EAAb,cAAsC,CAAY,CAAC"}
package/dist/errors.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  /** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */
2
2
  export declare class KeymapError extends Error {
3
3
  constructor(message: string, opts?: ErrorOptions);
4
- static is(err: unknown): err is KeymapError;
5
4
  }
6
5
  /** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */
7
6
  export declare class KeymapParseError extends KeymapError {
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAC3G,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;IAMhD,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,WAAW;CAG5C;AAED,sFAAsF;AACtF,qBAAa,gBAAiB,SAAQ,WAAW;CAAG"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAC3G,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAKjD;AAED,sFAAsF;AACtF,qBAAa,gBAAiB,SAAQ,WAAW;CAAG"}
package/dist/errors.js CHANGED
@@ -1,11 +1,8 @@
1
1
  //#region src/errors.ts
2
- var e = class e extends Error {
2
+ var e = class extends Error {
3
3
  constructor(e, t) {
4
4
  super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
5
5
  }
6
- static is(t) {
7
- return t instanceof e;
8
- }
9
6
  }, t = class extends e {};
10
7
  //#endregion
11
8
  export { e as KeymapError, t as KeymapParseError };
@@ -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"],"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,CAAC"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n"],"mappings":";AACA,IAAa,IAAb,cAAiC,MAAM;CACrC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;AACF,GAGa,IAAb,cAAsC,EAAY,CAAC"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./parser.cjs"),n=require("./conflicts.cjs"),r=require("./format.cjs"),i=require("./keymap.cjs");exports.KeymapError=e.KeymapError,exports.KeymapParseError=e.KeymapParseError,exports.canonicalizeShortcut=t.canonicalizeShortcut,exports.createKeymap=i.createKeymap,exports.detectModKey=t.detectModKey,exports.findShortcutConflicts=n.findShortcutConflicts,exports.formatShortcut=r.formatShortcut,exports.matchStep=t.matchStep,exports.parseShortcut=t.parseShortcut,exports.parseStep=t.parseStep;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./conflicts.cjs"),n=require("./format.cjs"),r=require("./keymap.cjs");exports.KeymapError=e.KeymapError,exports.KeymapParseError=e.KeymapParseError,exports.createKeymap=r.createKeymap,exports.findShortcutConflicts=t.findShortcutConflicts,exports.formatShortcut=n.formatShortcut;
package/dist/index.d.ts CHANGED
@@ -3,7 +3,5 @@ export { findShortcutConflicts } from './conflicts';
3
3
  export { KeymapError, KeymapParseError } from './errors';
4
4
  export { formatShortcut } from './format';
5
5
  export { createKeymap } from './keymap';
6
- export type { ModifierKey, Shortcut, ShortcutStep } from './parser';
7
- export { canonicalizeShortcut, detectModKey, matchStep, parseShortcut, parseStep } from './parser';
8
- export type { BindingEntry, BindingOptions, BindingValue, ChordStateChange, Handler, Keymap, KeymapOptions, When, } from './types';
6
+ export type { Binding, BindingEntry, Handler, Keymap, KeymapEvent, KeymapOptions, When } from './types';
9
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACnG,YAAY,EACV,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,OAAO,EACP,MAAM,EACN,aAAa,EACb,IAAI,GACL,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { KeymapError as e, KeymapParseError as t } from "./errors.js";
2
- import { canonicalizeShortcut as n, detectModKey as r, matchStep as i, parseShortcut as a, parseStep as o } from "./parser.js";
3
- import { findShortcutConflicts as s } from "./conflicts.js";
4
- import { formatShortcut as c } from "./format.js";
5
- import { createKeymap as l } from "./keymap.js";
6
- export { e as KeymapError, t as KeymapParseError, n as canonicalizeShortcut, l as createKeymap, r as detectModKey, s as findShortcutConflicts, c as formatShortcut, i as matchStep, a as parseShortcut, o as parseStep };
2
+ import { findShortcutConflicts as n } from "./conflicts.js";
3
+ import { formatShortcut as r } from "./format.js";
4
+ import { createKeymap as i } from "./keymap.js";
5
+ export { e as KeymapError, t as KeymapParseError, i as createKeymap, n as findShortcutConflicts, r as formatShortcut };
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{};function n(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var r={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},i={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function a(e,n){let a=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(a.length===0)throw new t(`Invalid shortcut step: "${e}"`);let o={...r,mod:n},s=new Set,c=[];for(let e of a){let t=Object.hasOwn(o,e)?o[e]:void 0;t?s.add(t):c.push(e)}if(c.length===0)throw new t(`Invalid shortcut step: "${e}"`);if(c.length>1)throw new t(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);let l=c[0];return{key:Object.hasOwn(i,l)?i[l]:l,modifiers:s}}function o(e,t=n()){try{return a(e,t)}catch{return null}}function s(e,r=n()){let i=e.trim().split(/\s+/).filter(Boolean);if(i.length===0)throw new t(`Shortcut must contain at least one key step`);return i.map(e=>a(e,r))}function c(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function l(e,t){if(typeof e.key!=`string`||e.key.toLowerCase()!==t.key)return!1;let{modifiers:n}=t;return e.altKey===n.has(`alt`)&&e.ctrlKey===n.has(`ctrl`)&&e.metaKey===n.has(`meta`)&&e.shiftKey===n.has(`shift`)}function u(e,t){return e.length>t.length?!1:c(e)===c(t.slice(0,e.length))}function d(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n;if(e.trim()===``)return[];let a=s(e,r);return t.filter(e=>e.trigger===i&&(u(a,e.shortcut)||u(e.shortcut,a)))}var f={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},p={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},m={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},h=[`ctrl`,`alt`,`shift`,`meta`];function g(e,t=n()){if(!e.trim())return``;let r;try{r=s(e,t)}catch{return`${e}`,``}let i=t===`meta`;return r.map(e=>{let t=h.filter(t=>e.modifiers.has(t)).map(e=>i?p[e]:m[e]),n=f[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function _(e){return typeof e==`function`?{handler:e,trigger:`keydown`}:{handler:e.handler,trigger:e.trigger??`keydown`,when:e.when}}function v(e,t,n,r,i){let a=0,o=[],s,c=[];function u(e=!1){if(s!==void 0&&clearTimeout(s),e&&a>0&&i?.onChordTimeout)try{i.onChordTimeout(n,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}s=void 0,a=0,o=[],c=[]}function d(f){let p=(a===0?e():o).filter(e=>{let t=e.shortcut[a];return t!==void 0&&l(f,t)});if(p.length===0){let e=a!==0;return u(),e?d(f):void 0}s!==void 0&&clearTimeout(s),s=void 0;let m=p.find(e=>e.shortcut.length===a+1);if(m)return u(),m;let h=p[0].shortcut[a];if(c=p[0].shortcut.slice(0,a+1),a===0&&i?.onChordStart)try{i.onChordStart(n,h,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}else if(a>0&&i?.onChordProgress)try{i.onChordProgress(n,c,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}o=p,a+=1,s=setTimeout(()=>u(!0),t)}return{advance:d,reset:()=>u()}}function y(t={},r={}){let{chordTimeout:i=1e3,modKey:a=n(),preventDefault:o=!0,stopPropagation:l=!1,when:u,onChordState:d}=r,f=Number.isFinite(i)&&i>0?i:1e3;f!==i&&`${i}`;let p=new Map,m=new Map,h=new AbortController,g=[],y=[],b=!1;function x(){if(b)throw new e(`Keymap is disposed`)}function S(){g=[],y=[];for(let e of p.values())e.trigger===`keydown`?g.push(e):y.push(e)}function C(e){return c(s(e,a))}function w(e,t){let n=s(e,a),r=c(n);return p.set(r,{shortcut:n,..._(t)}),S(),r}function T(e){let t=p.delete(e);return t&&S(),t}function E(e){if(d)try{d(e)}catch(e){`${e instanceof Error?e.message:String(e)}`}}function D(e,t){return n=>{let r=(typeof n.composedPath==`function`?n.composedPath():[]).find(e=>m.has(e));if(r&&r!==e)return;let i=n;if(b)return;let a=t.advance(i);u&&!u(i)||!a||a.when&&!a.when(i)||(o&&i.preventDefault(),l&&i.stopPropagation(),a.handler(i))}}for(let[e,n]of Object.entries(t))w(e,n);return{bind(e,t){x();let n=w(e,t);return()=>{b||T(n)}},get disposalSignal(){return h.signal},dispose(){if(!b){b=!0,h.abort();for(let[e,t]of m)e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset();m.clear(),p.clear(),g=[],y=[]}},get disposed(){return b},listBindings(){return[...p.values()].map(e=>({shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){x();let t=m.get(e);if(!t){let n=v(()=>g,f,e,`keydown`,{onChordProgress:(e,t,n)=>E({steps:t,target:e,trigger:n,type:`progressed`}),onChordStart:(e,t,n)=>E({step:t,target:e,trigger:n,type:`started`}),onChordTimeout:(e,t)=>E({target:e,trigger:t,type:`timeout`})}),r=v(()=>y,f,e,`keyup`,{onChordProgress:(e,t,n)=>E({steps:t,target:e,trigger:n,type:`progressed`}),onChordStart:(e,t,n)=>E({step:t,target:e,trigger:n,type:`started`}),onChordTimeout:(e,t)=>E({target:e,trigger:t,type:`timeout`})}),i=D(e,n),a=D(e,r);t={keydown:n,keyup:r,onKeydown:i,onKeyup:a,refs:0},m.set(e,t),e.addEventListener(`keydown`,i),e.addEventListener(`keyup`,a)}t.refs+=1;let n=!1;return()=>{n||(n=!0,--t.refs,!(t?.refs>0)&&(e.removeEventListener(`keydown`,t?.onKeydown),e.removeEventListener(`keyup`,t?.onKeyup),t?.keydown.reset(),t?.keyup.reset(),m.delete(e)))}},[Symbol.dispose](){this.dispose()},unbind(e){x(),T(C(e))||`${e}`}}}exports.KeymapError=e,exports.KeymapParseError=t,exports.canonicalizeShortcut=c,exports.createKeymap=y,exports.detectModKey=n,exports.findShortcutConflicts=d,exports.formatShortcut=g,exports.matchStep=l,exports.parseShortcut=s,exports.parseStep=o;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{};function n(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var r={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},i={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function a(e,n){let a=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(a.length===0)throw new t(`Invalid shortcut step: "${e}"`);let o={...r,mod:n},s=new Set,c=[];for(let e of a){let t=Object.hasOwn(o,e)?o[e]:void 0;t?s.add(t):c.push(e)}if(c.length===0)throw new t(`Invalid shortcut step: "${e}"`);if(c.length>1)throw new t(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);let l=c[0];return{key:Object.hasOwn(i,l)?i[l]:l,modifiers:s}}function o(e,r=n()){let i=e.trim().split(/\s+/).filter(Boolean);if(i.length===0)throw new t(`Shortcut must contain at least one key step`);return i.map(e=>a(e,r))}function s(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function c(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 l(e,t){return e.length>t.length?!1:s(e)===s(t.slice(0,e.length))}function u(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n;if(e.trim()===``)return[];let a=o(e,r);return t.filter(e=>e.trigger===i&&(l(a,e.shortcut)||l(e.shortcut,a)))}var d={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},f={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},p={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},m=[`ctrl`,`alt`,`shift`,`meta`];function h(e,t=n()){if(!e.trim())return``;let r;try{r=o(e,t)}catch{return`${e}`,``}let i=t===`meta`;return r.map(e=>{let t=m.filter(t=>e.modifiers.has(t)).map(e=>i?f[e]:p[e]),n=d[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}var g=()=>{};function _(e,t,n){let r=0,i=[],a;function o(e=!0){a!==void 0&&clearTimeout(a);let t=r>0;a=void 0,r=0,i=[],e&&t&&n.onCancel()}function s(l){let u=(r===0?e():i).filter(e=>{let t=e.shortcut[r];return t!==void 0&&c(l,t)&&(!e.when||e.when(l))});if(u.length===0){let e=r!==0;return o(),e?s(l):{type:`none`}}a!==void 0&&clearTimeout(a),a=void 0;let d=u.find(e=>e.shortcut.length===r+1);if(d)return o(!1),{binding:d,type:`match`};let f=r===0;return i=u,r+=1,n.onProgress(u[0].shortcut.slice(0,r),f),a=setTimeout(()=>{n.onTimeout(),o(!1)},t),{bindings:u,type:`pending`}}return{advance:s,reset:o}}function v(t=[],r={}){let{chordTimeout:i=1e3,modKey:a=n(),when:s}=r,c=Number.isFinite(i)&&i>0?i:1e3;c!==i&&`${i}`;let l=new Map,u=new Map,d=new Set,f=new AbortController,p=[],m=[],h=!1;function v(){if(h)throw new e(`Keymap is disposed`)}function y(){p=[],m=[];for(let e of l.values())e.trigger===`keydown`?p.push(e):m.push(e);for(let e of u.values())e.keydown.reset(),e.keyup.reset()}function b(e){let t={handler:e.handler,id:e.id,preventDefault:e.preventDefault??!0,shortcut:o(e.shortcut,a),stopPropagation:e.stopPropagation??!1,trigger:e.trigger??`keydown`,when:e.when};return l.set(t.id,t),y(),t}function x(e){let t=l.delete(e);return t&&y(),t}function S(e){return{id:e.id,preventDefault:e.preventDefault,shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),stopPropagation:e.stopPropagation,trigger:e.trigger}}function C(e){if(d.size!==0)for(let t of d)try{t(e)}catch{}}function w(e,t,n){return r=>{let i=(typeof r.composedPath==`function`?r.composedPath():[]).find(e=>u.has(e));if(i&&i!==e)return;let a=r;if(h)return;if(s&&!s(a)){n.reset();return}let o=n.advance(a);if(o.type===`none`)return;let c=o.type===`pending`?o.bindings:[o.binding];c.some(e=>e.preventDefault)&&a.preventDefault(),c.some(e=>e.stopPropagation)&&a.stopPropagation(),o.type!==`pending`&&(d.size>0&&C({binding:S(o.binding),target:e,trigger:t,type:`match`}),o.binding.handler(a))}}for(let e of t)b(e);return{bind(e){v();let t=b(e);return()=>{!h&&l.get(t.id)===t&&x(t.id)}},get disposalSignal(){return f.signal},dispose(){if(!h){h=!0;for(let[e,t]of u)e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset();C({type:`dispose`}),f.abort(),u.clear(),l.clear(),p=[],m=[],d.clear()}},get disposed(){return h},listBindings(){return[...l.values()].map(S)},mount(e){v();let t=u.get(e);if(!t){let n=t=>({onCancel:()=>{d.size>0&&C({target:e,trigger:t,type:`chord-cancel`})},onProgress:(n,r)=>{if(d.size===0)return;let i=n.map(e=>({key:e.key,modifiers:new Set(e.modifiers)}));C(r?{step:i[0],target:e,trigger:t,type:`chord-start`}:{steps:i,target:e,trigger:t,type:`chord-progress`})},onTimeout:()=>{d.size>0&&C({target:e,trigger:t,type:`chord-timeout`})}}),r=_(()=>p,c,n(`keydown`)),i=_(()=>m,c,n(`keyup`)),a=w(e,`keydown`,r),o=w(e,`keyup`,i);t={keydown:r,keyup:i,onKeydown:a,onKeyup:o,refs:0},u.set(e,t),e.addEventListener(`keydown`,a),e.addEventListener(`keyup`,o)}t.refs+=1;let n=!1;return()=>{n||(n=!0,--t.refs,!(t?.refs>0)&&(e.removeEventListener(`keydown`,t?.onKeydown),e.removeEventListener(`keyup`,t?.onKeyup),t?.keydown.reset(),t?.keyup.reset(),u.delete(e)))}},[Symbol.dispose](){this.dispose()},tap(e,t){let n=t?.signal?AbortSignal.any([f.signal,t.signal]):f.signal;if(n.aborted)return g;d.add(e);let r=()=>d.delete(e);return n.addEventListener(`abort`,r,{once:!0}),()=>{d.delete(e),n.removeEventListener(`abort`,r)}},unbind(e){v(),x(e)||`${e}`}}}exports.KeymapError=e,exports.KeymapParseError=t,exports.createKeymap=v,exports.findShortcutConflicts=u,exports.formatShortcut=h;
2
2
  //# sourceMappingURL=keymap.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"keymap.cjs","names":[],"sources":["../src/errors.ts","../src/parser.ts","../src/conflicts.ts","../src/_dev.ts","../src/format.ts","../src/keymap.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is KeymapError {\n return err instanceof KeymapError;\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n","import { KeymapParseError } from './errors';\n\nexport type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\nexport type ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\nexport type Shortcut = ShortcutStep[];\n\nexport function detectModKey(): 'ctrl' | 'meta' {\n if (typeof navigator === 'undefined') return 'ctrl';\n\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n\n return /mac/i.test(platform) ? 'meta' : 'ctrl';\n}\n\nconst MODIFIER_ALIASES_BASE: Record<string, ModifierKey> = {\n alt: 'alt',\n cmd: 'meta',\n command: 'meta',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n opt: 'alt',\n option: 'alt',\n shift: 'shift',\n win: 'meta',\n};\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n del: 'delete',\n down: 'arrowdown',\n esc: 'escape',\n left: 'arrowleft',\n right: 'arrowright',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n};\n\nfunction parseStepStrict(raw: string, modKey: 'ctrl' | 'meta'): ShortcutStep {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((part) => part.toLowerCase());\n\n if (parts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n const aliases: Record<string, ModifierKey> = { ...MODIFIER_ALIASES_BASE, mod: modKey };\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const modifier = Object.hasOwn(aliases, part) ? aliases[part]! : undefined;\n\n if (modifier) modifiers.add(modifier);\n else keyParts.push(part);\n }\n\n if (keyParts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const rawKey = keyParts[0]!;\n const key = Object.hasOwn(SPECIAL_KEY_ALIASES, rawKey) ? SPECIAL_KEY_ALIASES[rawKey]! : rawKey;\n\n return { key, modifiers };\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n try {\n return parseStepStrict(raw, modKey);\n } catch {\n return null;\n }\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n const steps = raw.trim().split(/\\s+/).filter(Boolean);\n\n if (steps.length === 0) throw new KeymapParseError('Shortcut must contain at least one key step');\n\n return steps.map((step) => parseStepStrict(step, modKey));\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((step) => {\n const modifiers = [...step.modifiers].sort().join('+');\n\n return modifiers ? `${modifiers}+${step.key}` : step.key;\n })\n .join(' ');\n}\n\nexport function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean {\n // Headless usage (SSR, non-DOM `EventTarget`s, hand-built test events) means `event` isn't\n // guaranteed to be a real `KeyboardEvent` at runtime even though the type says so — treat a\n // missing/non-string `key` as \"doesn't match\" rather than throwing inside the event listener.\n if (typeof event.key !== 'string') return false;\n\n if (event.key.toLowerCase() !== step.key) return false;\n\n const { modifiers } = step;\n\n return (\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift')\n );\n}\n","import type { ShortcutStep } from './parser';\nimport { canonicalizeShortcut, parseShortcut } from './parser';\nimport type { BindingEntry } from './types';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n","const isDev = !(globalThis as { __KEYMAP_PROD__?: boolean }).__KEYMAP_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/keymap] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps: ReturnType<typeof parseShortcut>;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n","import { warn } from './_dev';\nimport { KeymapError } from './errors';\nimport type { Shortcut, ShortcutStep } from './parser';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\nimport type { BindingEntry, BindingValue, ChordStateChange, Handler, Keymap, KeymapOptions, When } from './types';\n\ntype ParsedBinding = {\n handler: Handler;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: When;\n};\n\ntype ChordTracker = ReturnType<typeof createChordTracker>;\ntype MountedTarget = {\n keydown: ChordTracker;\n keyup: ChordTracker;\n onKeydown: EventListener;\n onKeyup: EventListener;\n refs: number;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') return { handler: value, trigger: 'keydown' };\n\n return {\n handler: value.handler,\n trigger: value.trigger ?? 'keydown',\n when: value.when,\n };\n}\n\ntype ChordTrackerCallbacks = {\n onChordStart?: (target: EventTarget, step: ShortcutStep, trigger: 'keydown' | 'keyup') => void;\n onChordProgress?: (target: EventTarget, steps: readonly ShortcutStep[], trigger: 'keydown' | 'keyup') => void;\n onChordTimeout?: (target: EventTarget, trigger: 'keydown' | 'keyup') => void;\n};\n\nfunction createChordTracker(\n getBindings: () => ParsedBinding[],\n chordTimeout: number,\n target: EventTarget,\n trigger: 'keydown' | 'keyup',\n callbacks?: ChordTrackerCallbacks,\n) {\n let pendingIndex = 0;\n let candidates: ParsedBinding[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n let pendingSteps: ShortcutStep[] = [];\n\n function reset(emitTimeout = false): void {\n if (timer !== undefined) clearTimeout(timer);\n\n // Emit timeout only if explicitly requested (from timeout event)\n if (emitTimeout && pendingIndex > 0 && callbacks?.onChordTimeout) {\n try {\n callbacks.onChordTimeout(target, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n timer = undefined;\n pendingIndex = 0;\n candidates = [];\n pendingSteps = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | undefined {\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched = pool.filter((binding) => {\n const step = binding.shortcut[pendingIndex];\n\n return step !== undefined && matchStep(event, step);\n });\n\n if (matched.length === 0) {\n const retryFromRoot = pendingIndex !== 0;\n\n reset();\n\n return retryFromRoot ? advance(event) : undefined;\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n\n const completed = matched.find((binding) => binding.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n // New step in chord progression\n const currentStep = matched[0]!.shortcut[pendingIndex];\n pendingSteps = matched[0]!.shortcut.slice(0, pendingIndex + 1);\n\n // Emit started or progressed\n if (pendingIndex === 0 && callbacks?.onChordStart) {\n try {\n callbacks.onChordStart(target, currentStep, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n } else if (pendingIndex > 0 && callbacks?.onChordProgress) {\n try {\n callbacks.onChordProgress(target, pendingSteps, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n candidates = matched;\n pendingIndex += 1;\n timer = setTimeout(() => reset(true), chordTimeout);\n\n return undefined;\n }\n\n return { advance, reset: () => reset() };\n}\n\n/**\n * Creates a headless keyboard shortcut manager with target-local chord state.\n *\n * Pass a bindings map of shortcut strings to handlers or `BindingOptions`, then call\n * `.mount(target)` to attach to any `EventTarget`. Supports chord sequences\n * (e.g. `\"ctrl+k ctrl+s\"`), per-binding `when` guards, `trigger` (keydown/keyup),\n * and dynamic `bind`/`unbind`.\n *\n * @example\n * const map = createKeymap({\n * 'mod+k mod+s': () => save(),\n * 'mod+shift+p': () => openPalette(),\n * 'g g': () => goToTop(),\n * esc: { handler: closePanel, when: (event) => !isEditableTarget(event.target) },\n * space: { handler: togglePlay, trigger: 'keyup' },\n * }, { modKey: 'ctrl' });\n * const unmount = map.mount(document);\n */\nexport function createKeymap(initialBindings: Record<string, BindingValue> = {}, options: KeymapOptions = {}): Keymap {\n const {\n chordTimeout: rawChordTimeout = 1000,\n modKey = detectModKey(),\n preventDefault = true,\n stopPropagation = false,\n when: globalWhen,\n onChordState: userOnChordState,\n } = options;\n const chordTimeout = Number.isFinite(rawChordTimeout) && rawChordTimeout > 0 ? rawChordTimeout : 1000;\n\n if (chordTimeout !== rawChordTimeout) {\n warn(`chordTimeout must be a positive finite number; received ${rawChordTimeout}. Using default of 1000ms.`);\n }\n\n const bindings = new Map<string, ParsedBinding>();\n const mounted = new Map<EventTarget, MountedTarget>();\n const disposalController = new AbortController();\n let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n let disposed = false;\n\n function assertActive(): void {\n if (disposed) throw new KeymapError('Keymap is disposed');\n }\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const binding of bindings.values()) {\n if (binding.trigger === 'keydown') bindingsDown.push(binding);\n else bindingsUp.push(binding);\n }\n }\n\n function bindingKey(shortcut: string): string {\n return canonicalizeShortcut(parseShortcut(shortcut, modKey));\n }\n\n function addBinding(shortcut: string, value: BindingValue): string {\n const parsed = parseShortcut(shortcut, modKey);\n const key = canonicalizeShortcut(parsed);\n\n bindings.set(key, { shortcut: parsed, ...resolveBinding(value) });\n rebuildTriggerCaches();\n\n return key;\n }\n\n function removeByKey(key: string): boolean {\n const existed = bindings.delete(key);\n\n if (existed) rebuildTriggerCaches();\n\n return existed;\n }\n\n function emitChordState(change: ChordStateChange): void {\n if (!userOnChordState) return;\n\n try {\n userOnChordState(change);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n function makeHandler(target: EventTarget, chord: ChordTracker): EventListener {\n return (event) => {\n const path = typeof event.composedPath === 'function' ? event.composedPath() : [];\n const nearestMountedTarget = path.find((pathTarget) => mounted.has(pathTarget));\n\n if (nearestMountedTarget && nearestMountedTarget !== target) return;\n\n const keyboardEvent = event as KeyboardEvent;\n\n if (disposed) return;\n\n // Track chord progression (independent of guards)\n const binding = chord.advance(keyboardEvent);\n\n // Check guards AFTER tracking chord\n if (globalWhen && !globalWhen(keyboardEvent)) return;\n\n if (!binding || (binding.when && !binding.when(keyboardEvent))) return;\n\n if (preventDefault) keyboardEvent.preventDefault();\n\n if (stopPropagation) keyboardEvent.stopPropagation();\n\n binding.handler(keyboardEvent);\n };\n }\n\n for (const [shortcut, value] of Object.entries(initialBindings)) addBinding(shortcut, value);\n\n return {\n bind(shortcut: string, value: BindingValue): () => void {\n assertActive();\n\n const key = addBinding(shortcut, value);\n\n return () => {\n if (!disposed) removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n for (const [target, record] of mounted) {\n target.removeEventListener('keydown', record.onKeydown);\n target.removeEventListener('keyup', record.onKeyup);\n record.keydown.reset();\n record.keyup.reset();\n }\n\n mounted.clear();\n bindings.clear();\n bindingsDown = [];\n bindingsUp = [];\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((binding) => ({\n shortcut: binding.shortcut.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) })),\n trigger: binding.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n assertActive();\n\n let record = mounted.get(target);\n\n if (!record) {\n const keydown = createChordTracker(() => bindingsDown, chordTimeout, target, 'keydown', {\n onChordProgress: (t, steps, trigger) => emitChordState({ steps, target: t, trigger, type: 'progressed' }),\n onChordStart: (t, step, trigger) => emitChordState({ step, target: t, trigger, type: 'started' }),\n onChordTimeout: (t, trigger) => emitChordState({ target: t, trigger, type: 'timeout' }),\n });\n const keyup = createChordTracker(() => bindingsUp, chordTimeout, target, 'keyup', {\n onChordProgress: (t, steps, trigger) => emitChordState({ steps, target: t, trigger, type: 'progressed' }),\n onChordStart: (t, step, trigger) => emitChordState({ step, target: t, trigger, type: 'started' }),\n onChordTimeout: (t, trigger) => emitChordState({ target: t, trigger, type: 'timeout' }),\n });\n const onKeydown = makeHandler(target, keydown);\n const onKeyup = makeHandler(target, keyup);\n\n record = { keydown, keyup, onKeydown, onKeyup, refs: 0 };\n mounted.set(target, record);\n target.addEventListener('keydown', onKeydown);\n target.addEventListener('keyup', onKeyup);\n }\n\n record.refs += 1;\n\n let unmounted = false;\n\n return () => {\n if (unmounted) return;\n\n unmounted = true;\n record!.refs -= 1;\n\n if (record?.refs > 0) return;\n\n target.removeEventListener('keydown', record?.onKeydown);\n target.removeEventListener('keyup', record?.onKeyup);\n record?.keydown.reset();\n record?.keyup.reset();\n mounted.delete(target);\n };\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n assertActive();\n\n if (!removeByKey(bindingKey(shortcut))) {\n warn(`unbind() called for unknown shortcut: \"${shortcut}\"`);\n }\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"keymap.cjs","names":[],"sources":["../src/errors.ts","../src/parser.ts","../src/conflicts.ts","../src/_dev.ts","../src/format.ts","../src/keymap.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n","import { KeymapParseError } from './errors';\n\nexport type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\nexport type ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\nexport type Shortcut = ShortcutStep[];\n\nexport function detectModKey(): 'ctrl' | 'meta' {\n if (typeof navigator === 'undefined') return 'ctrl';\n\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n\n return /mac/i.test(platform) ? 'meta' : 'ctrl';\n}\n\nconst MODIFIER_ALIASES_BASE: Record<string, ModifierKey> = {\n alt: 'alt',\n cmd: 'meta',\n command: 'meta',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n opt: 'alt',\n option: 'alt',\n shift: 'shift',\n win: 'meta',\n};\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n del: 'delete',\n down: 'arrowdown',\n esc: 'escape',\n left: 'arrowleft',\n right: 'arrowright',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n};\n\nfunction parseStepStrict(raw: string, modKey: 'ctrl' | 'meta'): ShortcutStep {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((part) => part.toLowerCase());\n\n if (parts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n const aliases: Record<string, ModifierKey> = { ...MODIFIER_ALIASES_BASE, mod: modKey };\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const modifier = Object.hasOwn(aliases, part) ? aliases[part]! : undefined;\n\n if (modifier) modifiers.add(modifier);\n else keyParts.push(part);\n }\n\n if (keyParts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const rawKey = keyParts[0]!;\n const key = Object.hasOwn(SPECIAL_KEY_ALIASES, rawKey) ? SPECIAL_KEY_ALIASES[rawKey]! : rawKey;\n\n return { key, modifiers };\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n try {\n return parseStepStrict(raw, modKey);\n } catch {\n return null;\n }\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n const steps = raw.trim().split(/\\s+/).filter(Boolean);\n\n if (steps.length === 0) throw new KeymapParseError('Shortcut must contain at least one key step');\n\n return steps.map((step) => parseStepStrict(step, modKey));\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((step) => {\n const modifiers = [...step.modifiers].sort().join('+');\n\n return modifiers ? `${modifiers}+${step.key}` : step.key;\n })\n .join(' ');\n}\n\nexport function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean {\n // Headless usage (SSR, non-DOM `EventTarget`s, hand-built test events) means `event` isn't\n // guaranteed to be a real `KeyboardEvent` at runtime even though the type says so — treat a\n // missing/non-string `key` as \"doesn't match\" rather than throwing inside the event listener.\n if (typeof event.key !== 'string') return false;\n\n if (event.key.toLowerCase() !== step.key) return false;\n\n const { modifiers } = step;\n\n return (\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift')\n );\n}\n","import type { ShortcutStep } from './parser';\nimport { canonicalizeShortcut, parseShortcut } from './parser';\nimport type { BindingEntry } from './types';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n","const isDev = !(globalThis as { __KEYMAP_PROD__?: boolean }).__KEYMAP_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/keymap] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps: ReturnType<typeof parseShortcut>;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n","import { warn } from './_dev';\nimport { KeymapError } from './errors';\nimport type { Shortcut } from './parser';\nimport { detectModKey, matchStep, parseShortcut } from './parser';\nimport type { Binding, BindingEntry, Handler, Keymap, KeymapEvent, KeymapOptions, When } from './types';\n\ntype ParsedBinding = {\n id: string;\n handler: Handler;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: When;\n preventDefault: boolean;\n stopPropagation: boolean;\n};\n\ntype ChordAdvanceResult =\n | { type: 'none' }\n | { bindings: ParsedBinding[]; type: 'pending' }\n | { binding: ParsedBinding; type: 'match' };\ntype ChordTrackerCallbacks = {\n onCancel: () => void;\n onProgress: (steps: Shortcut, started: boolean) => void;\n onTimeout: () => void;\n};\ntype ChordTracker = ReturnType<typeof createChordTracker>;\ntype MountedTarget = {\n keydown: ChordTracker;\n keyup: ChordTracker;\n onKeydown: EventListener;\n onKeyup: EventListener;\n refs: number;\n};\n\nconst noop = () => {};\n\nfunction createChordTracker(\n getBindings: () => ParsedBinding[],\n chordTimeout: number,\n callbacks: ChordTrackerCallbacks,\n) {\n let pendingIndex = 0;\n let candidates: ParsedBinding[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n function reset(notify = true): void {\n if (timer !== undefined) clearTimeout(timer);\n\n const hadPending = pendingIndex > 0;\n\n timer = undefined;\n pendingIndex = 0;\n candidates = [];\n\n if (notify && hadPending) callbacks.onCancel();\n }\n\n function advance(event: KeyboardEvent): ChordAdvanceResult {\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched = pool.filter((binding) => {\n const step = binding.shortcut[pendingIndex];\n\n return step !== undefined && matchStep(event, step) && (!binding.when || binding.when(event));\n });\n\n if (matched.length === 0) {\n const retryFromRoot = pendingIndex !== 0;\n\n reset();\n\n return retryFromRoot ? advance(event) : { type: 'none' };\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n\n const completed = matched.find((binding) => binding.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset(false);\n\n return { binding: completed, type: 'match' };\n }\n\n const started = pendingIndex === 0;\n\n candidates = matched;\n pendingIndex += 1;\n callbacks.onProgress(matched[0]!.shortcut.slice(0, pendingIndex), started);\n timer = setTimeout(() => {\n callbacks.onTimeout();\n reset(false);\n }, chordTimeout);\n\n return { bindings: matched, type: 'pending' };\n }\n\n return { advance, reset };\n}\n\n/**\n * Creates a headless keyboard shortcut manager with target-local chord state.\n *\n * Pass an ordered array of `Binding` objects (each with an explicit `id`,\n * `shortcut`, and `handler`), then call `.mount(target)` to attach to any\n * `EventTarget`. Supports chord sequences (e.g. `\"ctrl+k ctrl+s\"`), per-binding\n * `when` guards, `trigger` (keydown/keyup), and per-binding\n * `preventDefault`/`stopPropagation`. Dynamic `bind`/`unbind` use the binding\n * `id`, so duplicate shortcuts can coexist with different ids.\n *\n * @example\n * const map = createKeymap([\n * { id: 'save', shortcut: 'mod+k mod+s', handler: save },\n * { id: 'palette', shortcut: 'mod+shift+p', handler: openPalette },\n * { id: 'top', shortcut: 'g g', handler: goToTop },\n * { id: 'close', shortcut: 'escape', handler: closePanel, when: (event) => !isEditableTarget(event.target) },\n * { id: 'play', shortcut: 'space', handler: togglePlay, trigger: 'keyup' },\n * ], { modKey: 'ctrl' });\n * const unmount = map.mount(document);\n */\nexport function createKeymap(initialBindings: readonly Binding[] = [], options: KeymapOptions = {}): Keymap {\n const { chordTimeout: rawChordTimeout = 1000, modKey = detectModKey(), when: globalWhen } = options;\n const chordTimeout = Number.isFinite(rawChordTimeout) && rawChordTimeout > 0 ? rawChordTimeout : 1000;\n\n if (chordTimeout !== rawChordTimeout) {\n warn(`chordTimeout must be a positive finite number; received ${rawChordTimeout}. Using default of 1000ms.`);\n }\n\n // Ordered map keyed by binding id — preserves insertion order and supports\n // duplicate shortcuts with different ids.\n const bindings = new Map<string, ParsedBinding>();\n const mounted = new Map<EventTarget, MountedTarget>();\n const tappers = new Set<(event: KeymapEvent) => void>();\n const disposalController = new AbortController();\n let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n let disposed = false;\n\n function assertActive(): void {\n if (disposed) throw new KeymapError('Keymap is disposed');\n }\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const binding of bindings.values()) {\n if (binding.trigger === 'keydown') bindingsDown.push(binding);\n else bindingsUp.push(binding);\n }\n\n for (const record of mounted.values()) {\n record.keydown.reset();\n record.keyup.reset();\n }\n }\n\n function addBinding(binding: Binding): ParsedBinding {\n const parsedBinding: ParsedBinding = {\n handler: binding.handler,\n id: binding.id,\n preventDefault: binding.preventDefault ?? true,\n shortcut: parseShortcut(binding.shortcut, modKey),\n stopPropagation: binding.stopPropagation ?? false,\n trigger: binding.trigger ?? 'keydown',\n when: binding.when,\n };\n\n bindings.set(parsedBinding.id, parsedBinding);\n rebuildTriggerCaches();\n\n return parsedBinding;\n }\n\n function removeById(id: string): boolean {\n const existed = bindings.delete(id);\n\n if (existed) rebuildTriggerCaches();\n\n return existed;\n }\n\n function toBindingEntry(binding: ParsedBinding): BindingEntry {\n return {\n id: binding.id,\n preventDefault: binding.preventDefault,\n shortcut: binding.shortcut.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) })),\n stopPropagation: binding.stopPropagation,\n trigger: binding.trigger,\n };\n }\n\n function emitTap(event: KeymapEvent): void {\n if (tappers.size === 0) return;\n\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {}\n }\n }\n\n function makeHandler(target: EventTarget, trigger: 'keydown' | 'keyup', chord: ChordTracker): EventListener {\n return (event) => {\n const path = typeof event.composedPath === 'function' ? event.composedPath() : [];\n const nearestMountedTarget = path.find((pathTarget) => mounted.has(pathTarget));\n\n if (nearestMountedTarget && nearestMountedTarget !== target) return;\n\n const keyboardEvent = event as KeyboardEvent;\n\n if (disposed) return;\n\n if (globalWhen && !globalWhen(keyboardEvent)) {\n chord.reset();\n\n return;\n }\n\n const result = chord.advance(keyboardEvent);\n\n if (result.type === 'none') return;\n\n const matchedBindings = result.type === 'pending' ? result.bindings : [result.binding];\n\n if (matchedBindings.some((binding) => binding.preventDefault)) keyboardEvent.preventDefault();\n if (matchedBindings.some((binding) => binding.stopPropagation)) keyboardEvent.stopPropagation();\n\n if (result.type === 'pending') return;\n\n if (tappers.size > 0) {\n emitTap({ binding: toBindingEntry(result.binding), target, trigger, type: 'match' });\n }\n\n result.binding.handler(keyboardEvent);\n };\n }\n\n for (const binding of initialBindings) addBinding(binding);\n\n return {\n bind(binding: Binding): () => void {\n assertActive();\n\n const parsedBinding = addBinding(binding);\n\n return () => {\n if (!disposed && bindings.get(parsedBinding.id) === parsedBinding) removeById(parsedBinding.id);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n\n for (const [target, record] of mounted) {\n target.removeEventListener('keydown', record.onKeydown);\n target.removeEventListener('keyup', record.onKeyup);\n record.keydown.reset();\n record.keyup.reset();\n }\n\n emitTap({ type: 'dispose' });\n disposalController.abort();\n mounted.clear();\n bindings.clear();\n bindingsDown = [];\n bindingsUp = [];\n tappers.clear();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map(toBindingEntry);\n },\n\n mount(target: EventTarget): () => void {\n assertActive();\n\n let record = mounted.get(target);\n\n if (!record) {\n const createCallbacks = (trigger: 'keydown' | 'keyup'): ChordTrackerCallbacks => ({\n onCancel: () => {\n if (tappers.size > 0) emitTap({ target, trigger, type: 'chord-cancel' });\n },\n onProgress: (steps, started) => {\n if (tappers.size === 0) return;\n\n const snapshot = steps.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) }));\n\n if (started) emitTap({ step: snapshot[0]!, target, trigger, type: 'chord-start' });\n else emitTap({ steps: snapshot, target, trigger, type: 'chord-progress' });\n },\n onTimeout: () => {\n if (tappers.size > 0) emitTap({ target, trigger, type: 'chord-timeout' });\n },\n });\n const keydown = createChordTracker(() => bindingsDown, chordTimeout, createCallbacks('keydown'));\n const keyup = createChordTracker(() => bindingsUp, chordTimeout, createCallbacks('keyup'));\n const onKeydown = makeHandler(target, 'keydown', keydown);\n const onKeyup = makeHandler(target, 'keyup', keyup);\n\n record = { keydown, keyup, onKeydown, onKeyup, refs: 0 };\n mounted.set(target, record);\n target.addEventListener('keydown', onKeydown);\n target.addEventListener('keyup', onKeyup);\n }\n\n record.refs += 1;\n\n let unmounted = false;\n\n return () => {\n if (unmounted) return;\n\n unmounted = true;\n record!.refs -= 1;\n\n if (record?.refs > 0) return;\n\n target.removeEventListener('keydown', record?.onKeydown);\n target.removeEventListener('keyup', record?.onKeyup);\n record?.keydown.reset();\n record?.keyup.reset();\n mounted.delete(target);\n };\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n tap(handler: (event: KeymapEvent) => void, options?: { signal?: AbortSignal }): () => void {\n const signal = options?.signal\n ? AbortSignal.any([disposalController.signal, options.signal])\n : disposalController.signal;\n\n if (signal.aborted) return noop;\n\n tappers.add(handler);\n\n const onAbort = () => tappers.delete(handler);\n\n signal.addEventListener('abort', onAbort, { once: true });\n\n return () => {\n tappers.delete(handler);\n signal.removeEventListener('abort', onAbort);\n };\n },\n\n unbind(id: string): void {\n assertActive();\n\n if (!removeById(id)) {\n warn(`unbind() called for unknown id: \"${id}\"`);\n }\n },\n };\n}\n"],"mappings":"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"}
package/dist/keymap.d.ts CHANGED
@@ -1,21 +1,23 @@
1
- import type { BindingValue, Keymap, KeymapOptions } from './types';
1
+ import type { Binding, Keymap, KeymapOptions } from './types';
2
2
  /**
3
3
  * Creates a headless keyboard shortcut manager with target-local chord state.
4
4
  *
5
- * Pass a bindings map of shortcut strings to handlers or `BindingOptions`, then call
6
- * `.mount(target)` to attach to any `EventTarget`. Supports chord sequences
7
- * (e.g. `"ctrl+k ctrl+s"`), per-binding `when` guards, `trigger` (keydown/keyup),
8
- * and dynamic `bind`/`unbind`.
5
+ * Pass an ordered array of `Binding` objects (each with an explicit `id`,
6
+ * `shortcut`, and `handler`), then call `.mount(target)` to attach to any
7
+ * `EventTarget`. Supports chord sequences (e.g. `"ctrl+k ctrl+s"`), per-binding
8
+ * `when` guards, `trigger` (keydown/keyup), and per-binding
9
+ * `preventDefault`/`stopPropagation`. Dynamic `bind`/`unbind` use the binding
10
+ * `id`, so duplicate shortcuts can coexist with different ids.
9
11
  *
10
12
  * @example
11
- * const map = createKeymap({
12
- * 'mod+k mod+s': () => save(),
13
- * 'mod+shift+p': () => openPalette(),
14
- * 'g g': () => goToTop(),
15
- * esc: { handler: closePanel, when: (event) => !isEditableTarget(event.target) },
16
- * space: { handler: togglePlay, trigger: 'keyup' },
17
- * }, { modKey: 'ctrl' });
13
+ * const map = createKeymap([
14
+ * { id: 'save', shortcut: 'mod+k mod+s', handler: save },
15
+ * { id: 'palette', shortcut: 'mod+shift+p', handler: openPalette },
16
+ * { id: 'top', shortcut: 'g g', handler: goToTop },
17
+ * { id: 'close', shortcut: 'escape', handler: closePanel, when: (event) => !isEditableTarget(event.target) },
18
+ * { id: 'play', shortcut: 'space', handler: togglePlay, trigger: 'keyup' },
19
+ * ], { modKey: 'ctrl' });
18
20
  * const unmount = map.mount(document);
19
21
  */
20
- export declare function createKeymap(initialBindings?: Record<string, BindingValue>, options?: KeymapOptions): Keymap;
22
+ export declare function createKeymap(initialBindings?: readonly Binding[], options?: KeymapOptions): Keymap;
21
23
  //# sourceMappingURL=keymap.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"keymap.d.ts","sourceRoot":"","sources":["../src/keymap.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAgB,YAAY,EAA6B,MAAM,EAAE,aAAa,EAAQ,MAAM,SAAS,CAAC;AAyHlH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAuMpH"}
1
+ {"version":3,"file":"keymap.d.ts","sourceRoot":"","sources":["../src/keymap.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAyB,MAAM,EAAe,aAAa,EAAQ,MAAM,SAAS,CAAC;AAiGxG;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAAC,eAAe,GAAE,SAAS,OAAO,EAAO,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAwP1G"}
@@ -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{};function r(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var i={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},a={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function o(e,t){let r=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(r.length===0)throw new n(`Invalid shortcut step: "${e}"`);let o={...i,mod:t},s=new Set,c=[];for(let e of r){let t=Object.hasOwn(o,e)?o[e]:void 0;t?s.add(t):c.push(e)}if(c.length===0)throw new n(`Invalid shortcut step: "${e}"`);if(c.length>1)throw new n(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);let l=c[0];return{key:Object.hasOwn(a,l)?a[l]:l,modifiers:s}}function s(e,t=r()){try{return o(e,t)}catch{return null}}function c(e,t=r()){let i=e.trim().split(/\s+/).filter(Boolean);if(i.length===0)throw new n(`Shortcut must contain at least one key step`);return i.map(e=>o(e,t))}function l(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function u(e,t){if(typeof e.key!=`string`||e.key.toLowerCase()!==t.key)return!1;let{modifiers:n}=t;return e.altKey===n.has(`alt`)&&e.ctrlKey===n.has(`ctrl`)&&e.metaKey===n.has(`meta`)&&e.shiftKey===n.has(`shift`)}function d(e,t){return e.length>t.length?!1:l(e)===l(t.slice(0,e.length))}function f(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n;if(e.trim()===``)return[];let a=c(e,r);return t.filter(e=>e.trigger===i&&(d(a,e.shortcut)||d(e.shortcut,a)))}var p={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},m={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},h={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},g=[`ctrl`,`alt`,`shift`,`meta`];function _(e,t=r()){if(!e.trim())return``;let n;try{n=c(e,t)}catch{return`${e}`,``}let i=t===`meta`;return n.map(e=>{let t=g.filter(t=>e.modifiers.has(t)).map(e=>i?m[e]:h[e]),n=p[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function v(e){return typeof e==`function`?{handler:e,trigger:`keydown`}:{handler:e.handler,trigger:e.trigger??`keydown`,when:e.when}}function y(e,t,n,r,i){let a=0,o=[],s,c=[];function l(e=!1){if(s!==void 0&&clearTimeout(s),e&&a>0&&i?.onChordTimeout)try{i.onChordTimeout(n,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}s=void 0,a=0,o=[],c=[]}function d(f){let p=(a===0?e():o).filter(e=>{let t=e.shortcut[a];return t!==void 0&&u(f,t)});if(p.length===0){let e=a!==0;return l(),e?d(f):void 0}s!==void 0&&clearTimeout(s),s=void 0;let m=p.find(e=>e.shortcut.length===a+1);if(m)return l(),m;let h=p[0].shortcut[a];if(c=p[0].shortcut.slice(0,a+1),a===0&&i?.onChordStart)try{i.onChordStart(n,h,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}else if(a>0&&i?.onChordProgress)try{i.onChordProgress(n,c,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}o=p,a+=1,s=setTimeout(()=>l(!0),t)}return{advance:d,reset:()=>l()}}function b(e={},n={}){let{chordTimeout:i=1e3,modKey:a=r(),preventDefault:o=!0,stopPropagation:s=!1,when:u,onChordState:d}=n,f=Number.isFinite(i)&&i>0?i:1e3;f!==i&&`${i}`;let p=new Map,m=new Map,h=new AbortController,g=[],_=[],b=!1;function x(){if(b)throw new t(`Keymap is disposed`)}function S(){g=[],_=[];for(let e of p.values())e.trigger===`keydown`?g.push(e):_.push(e)}function C(e){return l(c(e,a))}function w(e,t){let n=c(e,a),r=l(n);return p.set(r,{shortcut:n,...v(t)}),S(),r}function T(e){let t=p.delete(e);return t&&S(),t}function E(e){if(d)try{d(e)}catch(e){`${e instanceof Error?e.message:String(e)}`}}function D(e,t){return n=>{let r=(typeof n.composedPath==`function`?n.composedPath():[]).find(e=>m.has(e));if(r&&r!==e)return;let i=n;if(b)return;let a=t.advance(i);u&&!u(i)||!a||a.when&&!a.when(i)||(o&&i.preventDefault(),s&&i.stopPropagation(),a.handler(i))}}for(let[t,n]of Object.entries(e))w(t,n);return{bind(e,t){x();let n=w(e,t);return()=>{b||T(n)}},get disposalSignal(){return h.signal},dispose(){if(!b){b=!0,h.abort();for(let[e,t]of m)e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset();m.clear(),p.clear(),g=[],_=[]}},get disposed(){return b},listBindings(){return[...p.values()].map(e=>({shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){x();let t=m.get(e);if(!t){let n=y(()=>g,f,e,`keydown`,{onChordProgress:(e,t,n)=>E({steps:t,target:e,trigger:n,type:`progressed`}),onChordStart:(e,t,n)=>E({step:t,target:e,trigger:n,type:`started`}),onChordTimeout:(e,t)=>E({target:e,trigger:t,type:`timeout`})}),r=y(()=>_,f,e,`keyup`,{onChordProgress:(e,t,n)=>E({steps:t,target:e,trigger:n,type:`progressed`}),onChordStart:(e,t,n)=>E({step:t,target:e,trigger:n,type:`started`}),onChordTimeout:(e,t)=>E({target:e,trigger:t,type:`timeout`})}),i=D(e,n),a=D(e,r);t={keydown:n,keyup:r,onKeydown:i,onKeyup:a,refs:0},m.set(e,t),e.addEventListener(`keydown`,i),e.addEventListener(`keyup`,a)}t.refs+=1;let n=!1;return()=>{n||(n=!0,--t.refs,!(t?.refs>0)&&(e.removeEventListener(`keydown`,t?.onKeydown),e.removeEventListener(`keyup`,t?.onKeyup),t?.keydown.reset(),t?.keyup.reset(),m.delete(e)))}},[Symbol.dispose](){this.dispose()},unbind(e){x(),T(C(e))||`${e}`}}}return e.KeymapError=t,e.KeymapParseError=n,e.canonicalizeShortcut=l,e.createKeymap=b,e.detectModKey=r,e.findShortcutConflicts=f,e.formatShortcut=_,e.matchStep=u,e.parseShortcut=c,e.parseStep=s,e})({});
1
+ var Keymap=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},n=class extends t{};function r(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var i={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},a={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function o(e,t){let r=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(r.length===0)throw new n(`Invalid shortcut step: "${e}"`);let o={...i,mod:t},s=new Set,c=[];for(let e of r){let t=Object.hasOwn(o,e)?o[e]:void 0;t?s.add(t):c.push(e)}if(c.length===0)throw new n(`Invalid shortcut step: "${e}"`);if(c.length>1)throw new n(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);let l=c[0];return{key:Object.hasOwn(a,l)?a[l]:l,modifiers:s}}function s(e,t=r()){let i=e.trim().split(/\s+/).filter(Boolean);if(i.length===0)throw new n(`Shortcut must contain at least one key step`);return i.map(e=>o(e,t))}function c(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function l(e,t){if(typeof e.key!=`string`||e.key.toLowerCase()!==t.key)return!1;let{modifiers:n}=t;return e.altKey===n.has(`alt`)&&e.ctrlKey===n.has(`ctrl`)&&e.metaKey===n.has(`meta`)&&e.shiftKey===n.has(`shift`)}function u(e,t){return e.length>t.length?!1:c(e)===c(t.slice(0,e.length))}function d(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n;if(e.trim()===``)return[];let a=s(e,r);return t.filter(e=>e.trigger===i&&(u(a,e.shortcut)||u(e.shortcut,a)))}var f={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},p={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},m={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},h=[`ctrl`,`alt`,`shift`,`meta`];function g(e,t=r()){if(!e.trim())return``;let n;try{n=s(e,t)}catch{return`${e}`,``}let i=t===`meta`;return n.map(e=>{let t=h.filter(t=>e.modifiers.has(t)).map(e=>i?p[e]:m[e]),n=f[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}var _=()=>{};function v(e,t,n){let r=0,i=[],a;function o(e=!0){a!==void 0&&clearTimeout(a);let t=r>0;a=void 0,r=0,i=[],e&&t&&n.onCancel()}function s(c){let u=(r===0?e():i).filter(e=>{let t=e.shortcut[r];return t!==void 0&&l(c,t)&&(!e.when||e.when(c))});if(u.length===0){let e=r!==0;return o(),e?s(c):{type:`none`}}a!==void 0&&clearTimeout(a),a=void 0;let d=u.find(e=>e.shortcut.length===r+1);if(d)return o(!1),{binding:d,type:`match`};let f=r===0;return i=u,r+=1,n.onProgress(u[0].shortcut.slice(0,r),f),a=setTimeout(()=>{n.onTimeout(),o(!1)},t),{bindings:u,type:`pending`}}return{advance:s,reset:o}}function y(e=[],n={}){let{chordTimeout:i=1e3,modKey:a=r(),when:o}=n,c=Number.isFinite(i)&&i>0?i:1e3;c!==i&&`${i}`;let l=new Map,u=new Map,d=new Set,f=new AbortController,p=[],m=[],h=!1;function g(){if(h)throw new t(`Keymap is disposed`)}function y(){p=[],m=[];for(let e of l.values())e.trigger===`keydown`?p.push(e):m.push(e);for(let e of u.values())e.keydown.reset(),e.keyup.reset()}function b(e){let t={handler:e.handler,id:e.id,preventDefault:e.preventDefault??!0,shortcut:s(e.shortcut,a),stopPropagation:e.stopPropagation??!1,trigger:e.trigger??`keydown`,when:e.when};return l.set(t.id,t),y(),t}function x(e){let t=l.delete(e);return t&&y(),t}function S(e){return{id:e.id,preventDefault:e.preventDefault,shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),stopPropagation:e.stopPropagation,trigger:e.trigger}}function C(e){if(d.size!==0)for(let t of d)try{t(e)}catch{}}function w(e,t,n){return r=>{let i=(typeof r.composedPath==`function`?r.composedPath():[]).find(e=>u.has(e));if(i&&i!==e)return;let a=r;if(h)return;if(o&&!o(a)){n.reset();return}let s=n.advance(a);if(s.type===`none`)return;let c=s.type===`pending`?s.bindings:[s.binding];c.some(e=>e.preventDefault)&&a.preventDefault(),c.some(e=>e.stopPropagation)&&a.stopPropagation(),s.type!==`pending`&&(d.size>0&&C({binding:S(s.binding),target:e,trigger:t,type:`match`}),s.binding.handler(a))}}for(let t of e)b(t);return{bind(e){g();let t=b(e);return()=>{!h&&l.get(t.id)===t&&x(t.id)}},get disposalSignal(){return f.signal},dispose(){if(!h){h=!0;for(let[e,t]of u)e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset();C({type:`dispose`}),f.abort(),u.clear(),l.clear(),p=[],m=[],d.clear()}},get disposed(){return h},listBindings(){return[...l.values()].map(S)},mount(e){g();let t=u.get(e);if(!t){let n=t=>({onCancel:()=>{d.size>0&&C({target:e,trigger:t,type:`chord-cancel`})},onProgress:(n,r)=>{if(d.size===0)return;let i=n.map(e=>({key:e.key,modifiers:new Set(e.modifiers)}));C(r?{step:i[0],target:e,trigger:t,type:`chord-start`}:{steps:i,target:e,trigger:t,type:`chord-progress`})},onTimeout:()=>{d.size>0&&C({target:e,trigger:t,type:`chord-timeout`})}}),r=v(()=>p,c,n(`keydown`)),i=v(()=>m,c,n(`keyup`)),a=w(e,`keydown`,r),o=w(e,`keyup`,i);t={keydown:r,keyup:i,onKeydown:a,onKeyup:o,refs:0},u.set(e,t),e.addEventListener(`keydown`,a),e.addEventListener(`keyup`,o)}t.refs+=1;let n=!1;return()=>{n||(n=!0,--t.refs,!(t?.refs>0)&&(e.removeEventListener(`keydown`,t?.onKeydown),e.removeEventListener(`keyup`,t?.onKeyup),t?.keydown.reset(),t?.keyup.reset(),u.delete(e)))}},[Symbol.dispose](){this.dispose()},tap(e,t){let n=t?.signal?AbortSignal.any([f.signal,t.signal]):f.signal;if(n.aborted)return _;d.add(e);let r=()=>d.delete(e);return n.addEventListener(`abort`,r,{once:!0}),()=>{d.delete(e),n.removeEventListener(`abort`,r)}},unbind(e){g(),x(e)||`${e}`}}}return e.KeymapError=t,e.KeymapParseError=n,e.createKeymap=y,e.findShortcutConflicts=d,e.formatShortcut=g,e})({});
2
2
  //# sourceMappingURL=keymap.iife.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keymap.iife.js","names":[],"sources":["../src/errors.ts","../src/parser.ts","../src/conflicts.ts","../src/_dev.ts","../src/format.ts","../src/keymap.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is KeymapError {\n return err instanceof KeymapError;\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n","import { KeymapParseError } from './errors';\n\nexport type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\nexport type ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\nexport type Shortcut = ShortcutStep[];\n\nexport function detectModKey(): 'ctrl' | 'meta' {\n if (typeof navigator === 'undefined') return 'ctrl';\n\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n\n return /mac/i.test(platform) ? 'meta' : 'ctrl';\n}\n\nconst MODIFIER_ALIASES_BASE: Record<string, ModifierKey> = {\n alt: 'alt',\n cmd: 'meta',\n command: 'meta',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n opt: 'alt',\n option: 'alt',\n shift: 'shift',\n win: 'meta',\n};\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n del: 'delete',\n down: 'arrowdown',\n esc: 'escape',\n left: 'arrowleft',\n right: 'arrowright',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n};\n\nfunction parseStepStrict(raw: string, modKey: 'ctrl' | 'meta'): ShortcutStep {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((part) => part.toLowerCase());\n\n if (parts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n const aliases: Record<string, ModifierKey> = { ...MODIFIER_ALIASES_BASE, mod: modKey };\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const modifier = Object.hasOwn(aliases, part) ? aliases[part]! : undefined;\n\n if (modifier) modifiers.add(modifier);\n else keyParts.push(part);\n }\n\n if (keyParts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const rawKey = keyParts[0]!;\n const key = Object.hasOwn(SPECIAL_KEY_ALIASES, rawKey) ? SPECIAL_KEY_ALIASES[rawKey]! : rawKey;\n\n return { key, modifiers };\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n try {\n return parseStepStrict(raw, modKey);\n } catch {\n return null;\n }\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n const steps = raw.trim().split(/\\s+/).filter(Boolean);\n\n if (steps.length === 0) throw new KeymapParseError('Shortcut must contain at least one key step');\n\n return steps.map((step) => parseStepStrict(step, modKey));\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((step) => {\n const modifiers = [...step.modifiers].sort().join('+');\n\n return modifiers ? `${modifiers}+${step.key}` : step.key;\n })\n .join(' ');\n}\n\nexport function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean {\n // Headless usage (SSR, non-DOM `EventTarget`s, hand-built test events) means `event` isn't\n // guaranteed to be a real `KeyboardEvent` at runtime even though the type says so — treat a\n // missing/non-string `key` as \"doesn't match\" rather than throwing inside the event listener.\n if (typeof event.key !== 'string') return false;\n\n if (event.key.toLowerCase() !== step.key) return false;\n\n const { modifiers } = step;\n\n return (\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift')\n );\n}\n","import type { ShortcutStep } from './parser';\nimport { canonicalizeShortcut, parseShortcut } from './parser';\nimport type { BindingEntry } from './types';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n","const isDev = !(globalThis as { __KEYMAP_PROD__?: boolean }).__KEYMAP_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/keymap] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps: ReturnType<typeof parseShortcut>;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n","import { warn } from './_dev';\nimport { KeymapError } from './errors';\nimport type { Shortcut, ShortcutStep } from './parser';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\nimport type { BindingEntry, BindingValue, ChordStateChange, Handler, Keymap, KeymapOptions, When } from './types';\n\ntype ParsedBinding = {\n handler: Handler;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: When;\n};\n\ntype ChordTracker = ReturnType<typeof createChordTracker>;\ntype MountedTarget = {\n keydown: ChordTracker;\n keyup: ChordTracker;\n onKeydown: EventListener;\n onKeyup: EventListener;\n refs: number;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') return { handler: value, trigger: 'keydown' };\n\n return {\n handler: value.handler,\n trigger: value.trigger ?? 'keydown',\n when: value.when,\n };\n}\n\ntype ChordTrackerCallbacks = {\n onChordStart?: (target: EventTarget, step: ShortcutStep, trigger: 'keydown' | 'keyup') => void;\n onChordProgress?: (target: EventTarget, steps: readonly ShortcutStep[], trigger: 'keydown' | 'keyup') => void;\n onChordTimeout?: (target: EventTarget, trigger: 'keydown' | 'keyup') => void;\n};\n\nfunction createChordTracker(\n getBindings: () => ParsedBinding[],\n chordTimeout: number,\n target: EventTarget,\n trigger: 'keydown' | 'keyup',\n callbacks?: ChordTrackerCallbacks,\n) {\n let pendingIndex = 0;\n let candidates: ParsedBinding[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n let pendingSteps: ShortcutStep[] = [];\n\n function reset(emitTimeout = false): void {\n if (timer !== undefined) clearTimeout(timer);\n\n // Emit timeout only if explicitly requested (from timeout event)\n if (emitTimeout && pendingIndex > 0 && callbacks?.onChordTimeout) {\n try {\n callbacks.onChordTimeout(target, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n timer = undefined;\n pendingIndex = 0;\n candidates = [];\n pendingSteps = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | undefined {\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched = pool.filter((binding) => {\n const step = binding.shortcut[pendingIndex];\n\n return step !== undefined && matchStep(event, step);\n });\n\n if (matched.length === 0) {\n const retryFromRoot = pendingIndex !== 0;\n\n reset();\n\n return retryFromRoot ? advance(event) : undefined;\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n\n const completed = matched.find((binding) => binding.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n // New step in chord progression\n const currentStep = matched[0]!.shortcut[pendingIndex];\n pendingSteps = matched[0]!.shortcut.slice(0, pendingIndex + 1);\n\n // Emit started or progressed\n if (pendingIndex === 0 && callbacks?.onChordStart) {\n try {\n callbacks.onChordStart(target, currentStep, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n } else if (pendingIndex > 0 && callbacks?.onChordProgress) {\n try {\n callbacks.onChordProgress(target, pendingSteps, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n candidates = matched;\n pendingIndex += 1;\n timer = setTimeout(() => reset(true), chordTimeout);\n\n return undefined;\n }\n\n return { advance, reset: () => reset() };\n}\n\n/**\n * Creates a headless keyboard shortcut manager with target-local chord state.\n *\n * Pass a bindings map of shortcut strings to handlers or `BindingOptions`, then call\n * `.mount(target)` to attach to any `EventTarget`. Supports chord sequences\n * (e.g. `\"ctrl+k ctrl+s\"`), per-binding `when` guards, `trigger` (keydown/keyup),\n * and dynamic `bind`/`unbind`.\n *\n * @example\n * const map = createKeymap({\n * 'mod+k mod+s': () => save(),\n * 'mod+shift+p': () => openPalette(),\n * 'g g': () => goToTop(),\n * esc: { handler: closePanel, when: (event) => !isEditableTarget(event.target) },\n * space: { handler: togglePlay, trigger: 'keyup' },\n * }, { modKey: 'ctrl' });\n * const unmount = map.mount(document);\n */\nexport function createKeymap(initialBindings: Record<string, BindingValue> = {}, options: KeymapOptions = {}): Keymap {\n const {\n chordTimeout: rawChordTimeout = 1000,\n modKey = detectModKey(),\n preventDefault = true,\n stopPropagation = false,\n when: globalWhen,\n onChordState: userOnChordState,\n } = options;\n const chordTimeout = Number.isFinite(rawChordTimeout) && rawChordTimeout > 0 ? rawChordTimeout : 1000;\n\n if (chordTimeout !== rawChordTimeout) {\n warn(`chordTimeout must be a positive finite number; received ${rawChordTimeout}. Using default of 1000ms.`);\n }\n\n const bindings = new Map<string, ParsedBinding>();\n const mounted = new Map<EventTarget, MountedTarget>();\n const disposalController = new AbortController();\n let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n let disposed = false;\n\n function assertActive(): void {\n if (disposed) throw new KeymapError('Keymap is disposed');\n }\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const binding of bindings.values()) {\n if (binding.trigger === 'keydown') bindingsDown.push(binding);\n else bindingsUp.push(binding);\n }\n }\n\n function bindingKey(shortcut: string): string {\n return canonicalizeShortcut(parseShortcut(shortcut, modKey));\n }\n\n function addBinding(shortcut: string, value: BindingValue): string {\n const parsed = parseShortcut(shortcut, modKey);\n const key = canonicalizeShortcut(parsed);\n\n bindings.set(key, { shortcut: parsed, ...resolveBinding(value) });\n rebuildTriggerCaches();\n\n return key;\n }\n\n function removeByKey(key: string): boolean {\n const existed = bindings.delete(key);\n\n if (existed) rebuildTriggerCaches();\n\n return existed;\n }\n\n function emitChordState(change: ChordStateChange): void {\n if (!userOnChordState) return;\n\n try {\n userOnChordState(change);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n function makeHandler(target: EventTarget, chord: ChordTracker): EventListener {\n return (event) => {\n const path = typeof event.composedPath === 'function' ? event.composedPath() : [];\n const nearestMountedTarget = path.find((pathTarget) => mounted.has(pathTarget));\n\n if (nearestMountedTarget && nearestMountedTarget !== target) return;\n\n const keyboardEvent = event as KeyboardEvent;\n\n if (disposed) return;\n\n // Track chord progression (independent of guards)\n const binding = chord.advance(keyboardEvent);\n\n // Check guards AFTER tracking chord\n if (globalWhen && !globalWhen(keyboardEvent)) return;\n\n if (!binding || (binding.when && !binding.when(keyboardEvent))) return;\n\n if (preventDefault) keyboardEvent.preventDefault();\n\n if (stopPropagation) keyboardEvent.stopPropagation();\n\n binding.handler(keyboardEvent);\n };\n }\n\n for (const [shortcut, value] of Object.entries(initialBindings)) addBinding(shortcut, value);\n\n return {\n bind(shortcut: string, value: BindingValue): () => void {\n assertActive();\n\n const key = addBinding(shortcut, value);\n\n return () => {\n if (!disposed) removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n for (const [target, record] of mounted) {\n target.removeEventListener('keydown', record.onKeydown);\n target.removeEventListener('keyup', record.onKeyup);\n record.keydown.reset();\n record.keyup.reset();\n }\n\n mounted.clear();\n bindings.clear();\n bindingsDown = [];\n bindingsUp = [];\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((binding) => ({\n shortcut: binding.shortcut.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) })),\n trigger: binding.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n assertActive();\n\n let record = mounted.get(target);\n\n if (!record) {\n const keydown = createChordTracker(() => bindingsDown, chordTimeout, target, 'keydown', {\n onChordProgress: (t, steps, trigger) => emitChordState({ steps, target: t, trigger, type: 'progressed' }),\n onChordStart: (t, step, trigger) => emitChordState({ step, target: t, trigger, type: 'started' }),\n onChordTimeout: (t, trigger) => emitChordState({ target: t, trigger, type: 'timeout' }),\n });\n const keyup = createChordTracker(() => bindingsUp, chordTimeout, target, 'keyup', {\n onChordProgress: (t, steps, trigger) => emitChordState({ steps, target: t, trigger, type: 'progressed' }),\n onChordStart: (t, step, trigger) => emitChordState({ step, target: t, trigger, type: 'started' }),\n onChordTimeout: (t, trigger) => emitChordState({ target: t, trigger, type: 'timeout' }),\n });\n const onKeydown = makeHandler(target, keydown);\n const onKeyup = makeHandler(target, keyup);\n\n record = { keydown, keyup, onKeydown, onKeyup, refs: 0 };\n mounted.set(target, record);\n target.addEventListener('keydown', onKeydown);\n target.addEventListener('keyup', onKeyup);\n }\n\n record.refs += 1;\n\n let unmounted = false;\n\n return () => {\n if (unmounted) return;\n\n unmounted = true;\n record!.refs -= 1;\n\n if (record?.refs > 0) return;\n\n target.removeEventListener('keydown', record?.onKeydown);\n target.removeEventListener('keyup', record?.onKeyup);\n record?.keydown.reset();\n record?.keyup.reset();\n mounted.delete(target);\n };\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n assertActive();\n\n if (!removeByKey(bindingKey(shortcut))) {\n warn(`unbind() called for unknown shortcut: \"${shortcut}\"`);\n }\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"keymap.iife.js","names":[],"sources":["../src/errors.ts","../src/parser.ts","../src/conflicts.ts","../src/_dev.ts","../src/format.ts","../src/keymap.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n","import { KeymapParseError } from './errors';\n\nexport type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\nexport type ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\nexport type Shortcut = ShortcutStep[];\n\nexport function detectModKey(): 'ctrl' | 'meta' {\n if (typeof navigator === 'undefined') return 'ctrl';\n\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n\n return /mac/i.test(platform) ? 'meta' : 'ctrl';\n}\n\nconst MODIFIER_ALIASES_BASE: Record<string, ModifierKey> = {\n alt: 'alt',\n cmd: 'meta',\n command: 'meta',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n opt: 'alt',\n option: 'alt',\n shift: 'shift',\n win: 'meta',\n};\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n del: 'delete',\n down: 'arrowdown',\n esc: 'escape',\n left: 'arrowleft',\n right: 'arrowright',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n};\n\nfunction parseStepStrict(raw: string, modKey: 'ctrl' | 'meta'): ShortcutStep {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((part) => part.toLowerCase());\n\n if (parts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n const aliases: Record<string, ModifierKey> = { ...MODIFIER_ALIASES_BASE, mod: modKey };\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const modifier = Object.hasOwn(aliases, part) ? aliases[part]! : undefined;\n\n if (modifier) modifiers.add(modifier);\n else keyParts.push(part);\n }\n\n if (keyParts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const rawKey = keyParts[0]!;\n const key = Object.hasOwn(SPECIAL_KEY_ALIASES, rawKey) ? SPECIAL_KEY_ALIASES[rawKey]! : rawKey;\n\n return { key, modifiers };\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n try {\n return parseStepStrict(raw, modKey);\n } catch {\n return null;\n }\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n const steps = raw.trim().split(/\\s+/).filter(Boolean);\n\n if (steps.length === 0) throw new KeymapParseError('Shortcut must contain at least one key step');\n\n return steps.map((step) => parseStepStrict(step, modKey));\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((step) => {\n const modifiers = [...step.modifiers].sort().join('+');\n\n return modifiers ? `${modifiers}+${step.key}` : step.key;\n })\n .join(' ');\n}\n\nexport function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean {\n // Headless usage (SSR, non-DOM `EventTarget`s, hand-built test events) means `event` isn't\n // guaranteed to be a real `KeyboardEvent` at runtime even though the type says so — treat a\n // missing/non-string `key` as \"doesn't match\" rather than throwing inside the event listener.\n if (typeof event.key !== 'string') return false;\n\n if (event.key.toLowerCase() !== step.key) return false;\n\n const { modifiers } = step;\n\n return (\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift')\n );\n}\n","import type { ShortcutStep } from './parser';\nimport { canonicalizeShortcut, parseShortcut } from './parser';\nimport type { BindingEntry } from './types';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n","const isDev = !(globalThis as { __KEYMAP_PROD__?: boolean }).__KEYMAP_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/keymap] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps: ReturnType<typeof parseShortcut>;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n","import { warn } from './_dev';\nimport { KeymapError } from './errors';\nimport type { Shortcut } from './parser';\nimport { detectModKey, matchStep, parseShortcut } from './parser';\nimport type { Binding, BindingEntry, Handler, Keymap, KeymapEvent, KeymapOptions, When } from './types';\n\ntype ParsedBinding = {\n id: string;\n handler: Handler;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: When;\n preventDefault: boolean;\n stopPropagation: boolean;\n};\n\ntype ChordAdvanceResult =\n | { type: 'none' }\n | { bindings: ParsedBinding[]; type: 'pending' }\n | { binding: ParsedBinding; type: 'match' };\ntype ChordTrackerCallbacks = {\n onCancel: () => void;\n onProgress: (steps: Shortcut, started: boolean) => void;\n onTimeout: () => void;\n};\ntype ChordTracker = ReturnType<typeof createChordTracker>;\ntype MountedTarget = {\n keydown: ChordTracker;\n keyup: ChordTracker;\n onKeydown: EventListener;\n onKeyup: EventListener;\n refs: number;\n};\n\nconst noop = () => {};\n\nfunction createChordTracker(\n getBindings: () => ParsedBinding[],\n chordTimeout: number,\n callbacks: ChordTrackerCallbacks,\n) {\n let pendingIndex = 0;\n let candidates: ParsedBinding[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n function reset(notify = true): void {\n if (timer !== undefined) clearTimeout(timer);\n\n const hadPending = pendingIndex > 0;\n\n timer = undefined;\n pendingIndex = 0;\n candidates = [];\n\n if (notify && hadPending) callbacks.onCancel();\n }\n\n function advance(event: KeyboardEvent): ChordAdvanceResult {\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched = pool.filter((binding) => {\n const step = binding.shortcut[pendingIndex];\n\n return step !== undefined && matchStep(event, step) && (!binding.when || binding.when(event));\n });\n\n if (matched.length === 0) {\n const retryFromRoot = pendingIndex !== 0;\n\n reset();\n\n return retryFromRoot ? advance(event) : { type: 'none' };\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n\n const completed = matched.find((binding) => binding.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset(false);\n\n return { binding: completed, type: 'match' };\n }\n\n const started = pendingIndex === 0;\n\n candidates = matched;\n pendingIndex += 1;\n callbacks.onProgress(matched[0]!.shortcut.slice(0, pendingIndex), started);\n timer = setTimeout(() => {\n callbacks.onTimeout();\n reset(false);\n }, chordTimeout);\n\n return { bindings: matched, type: 'pending' };\n }\n\n return { advance, reset };\n}\n\n/**\n * Creates a headless keyboard shortcut manager with target-local chord state.\n *\n * Pass an ordered array of `Binding` objects (each with an explicit `id`,\n * `shortcut`, and `handler`), then call `.mount(target)` to attach to any\n * `EventTarget`. Supports chord sequences (e.g. `\"ctrl+k ctrl+s\"`), per-binding\n * `when` guards, `trigger` (keydown/keyup), and per-binding\n * `preventDefault`/`stopPropagation`. Dynamic `bind`/`unbind` use the binding\n * `id`, so duplicate shortcuts can coexist with different ids.\n *\n * @example\n * const map = createKeymap([\n * { id: 'save', shortcut: 'mod+k mod+s', handler: save },\n * { id: 'palette', shortcut: 'mod+shift+p', handler: openPalette },\n * { id: 'top', shortcut: 'g g', handler: goToTop },\n * { id: 'close', shortcut: 'escape', handler: closePanel, when: (event) => !isEditableTarget(event.target) },\n * { id: 'play', shortcut: 'space', handler: togglePlay, trigger: 'keyup' },\n * ], { modKey: 'ctrl' });\n * const unmount = map.mount(document);\n */\nexport function createKeymap(initialBindings: readonly Binding[] = [], options: KeymapOptions = {}): Keymap {\n const { chordTimeout: rawChordTimeout = 1000, modKey = detectModKey(), when: globalWhen } = options;\n const chordTimeout = Number.isFinite(rawChordTimeout) && rawChordTimeout > 0 ? rawChordTimeout : 1000;\n\n if (chordTimeout !== rawChordTimeout) {\n warn(`chordTimeout must be a positive finite number; received ${rawChordTimeout}. Using default of 1000ms.`);\n }\n\n // Ordered map keyed by binding id — preserves insertion order and supports\n // duplicate shortcuts with different ids.\n const bindings = new Map<string, ParsedBinding>();\n const mounted = new Map<EventTarget, MountedTarget>();\n const tappers = new Set<(event: KeymapEvent) => void>();\n const disposalController = new AbortController();\n let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n let disposed = false;\n\n function assertActive(): void {\n if (disposed) throw new KeymapError('Keymap is disposed');\n }\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const binding of bindings.values()) {\n if (binding.trigger === 'keydown') bindingsDown.push(binding);\n else bindingsUp.push(binding);\n }\n\n for (const record of mounted.values()) {\n record.keydown.reset();\n record.keyup.reset();\n }\n }\n\n function addBinding(binding: Binding): ParsedBinding {\n const parsedBinding: ParsedBinding = {\n handler: binding.handler,\n id: binding.id,\n preventDefault: binding.preventDefault ?? true,\n shortcut: parseShortcut(binding.shortcut, modKey),\n stopPropagation: binding.stopPropagation ?? false,\n trigger: binding.trigger ?? 'keydown',\n when: binding.when,\n };\n\n bindings.set(parsedBinding.id, parsedBinding);\n rebuildTriggerCaches();\n\n return parsedBinding;\n }\n\n function removeById(id: string): boolean {\n const existed = bindings.delete(id);\n\n if (existed) rebuildTriggerCaches();\n\n return existed;\n }\n\n function toBindingEntry(binding: ParsedBinding): BindingEntry {\n return {\n id: binding.id,\n preventDefault: binding.preventDefault,\n shortcut: binding.shortcut.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) })),\n stopPropagation: binding.stopPropagation,\n trigger: binding.trigger,\n };\n }\n\n function emitTap(event: KeymapEvent): void {\n if (tappers.size === 0) return;\n\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {}\n }\n }\n\n function makeHandler(target: EventTarget, trigger: 'keydown' | 'keyup', chord: ChordTracker): EventListener {\n return (event) => {\n const path = typeof event.composedPath === 'function' ? event.composedPath() : [];\n const nearestMountedTarget = path.find((pathTarget) => mounted.has(pathTarget));\n\n if (nearestMountedTarget && nearestMountedTarget !== target) return;\n\n const keyboardEvent = event as KeyboardEvent;\n\n if (disposed) return;\n\n if (globalWhen && !globalWhen(keyboardEvent)) {\n chord.reset();\n\n return;\n }\n\n const result = chord.advance(keyboardEvent);\n\n if (result.type === 'none') return;\n\n const matchedBindings = result.type === 'pending' ? result.bindings : [result.binding];\n\n if (matchedBindings.some((binding) => binding.preventDefault)) keyboardEvent.preventDefault();\n if (matchedBindings.some((binding) => binding.stopPropagation)) keyboardEvent.stopPropagation();\n\n if (result.type === 'pending') return;\n\n if (tappers.size > 0) {\n emitTap({ binding: toBindingEntry(result.binding), target, trigger, type: 'match' });\n }\n\n result.binding.handler(keyboardEvent);\n };\n }\n\n for (const binding of initialBindings) addBinding(binding);\n\n return {\n bind(binding: Binding): () => void {\n assertActive();\n\n const parsedBinding = addBinding(binding);\n\n return () => {\n if (!disposed && bindings.get(parsedBinding.id) === parsedBinding) removeById(parsedBinding.id);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n\n for (const [target, record] of mounted) {\n target.removeEventListener('keydown', record.onKeydown);\n target.removeEventListener('keyup', record.onKeyup);\n record.keydown.reset();\n record.keyup.reset();\n }\n\n emitTap({ type: 'dispose' });\n disposalController.abort();\n mounted.clear();\n bindings.clear();\n bindingsDown = [];\n bindingsUp = [];\n tappers.clear();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map(toBindingEntry);\n },\n\n mount(target: EventTarget): () => void {\n assertActive();\n\n let record = mounted.get(target);\n\n if (!record) {\n const createCallbacks = (trigger: 'keydown' | 'keyup'): ChordTrackerCallbacks => ({\n onCancel: () => {\n if (tappers.size > 0) emitTap({ target, trigger, type: 'chord-cancel' });\n },\n onProgress: (steps, started) => {\n if (tappers.size === 0) return;\n\n const snapshot = steps.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) }));\n\n if (started) emitTap({ step: snapshot[0]!, target, trigger, type: 'chord-start' });\n else emitTap({ steps: snapshot, target, trigger, type: 'chord-progress' });\n },\n onTimeout: () => {\n if (tappers.size > 0) emitTap({ target, trigger, type: 'chord-timeout' });\n },\n });\n const keydown = createChordTracker(() => bindingsDown, chordTimeout, createCallbacks('keydown'));\n const keyup = createChordTracker(() => bindingsUp, chordTimeout, createCallbacks('keyup'));\n const onKeydown = makeHandler(target, 'keydown', keydown);\n const onKeyup = makeHandler(target, 'keyup', keyup);\n\n record = { keydown, keyup, onKeydown, onKeyup, refs: 0 };\n mounted.set(target, record);\n target.addEventListener('keydown', onKeydown);\n target.addEventListener('keyup', onKeyup);\n }\n\n record.refs += 1;\n\n let unmounted = false;\n\n return () => {\n if (unmounted) return;\n\n unmounted = true;\n record!.refs -= 1;\n\n if (record?.refs > 0) return;\n\n target.removeEventListener('keydown', record?.onKeydown);\n target.removeEventListener('keyup', record?.onKeyup);\n record?.keydown.reset();\n record?.keyup.reset();\n mounted.delete(target);\n };\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n tap(handler: (event: KeymapEvent) => void, options?: { signal?: AbortSignal }): () => void {\n const signal = options?.signal\n ? AbortSignal.any([disposalController.signal, options.signal])\n : disposalController.signal;\n\n if (signal.aborted) return noop;\n\n tappers.add(handler);\n\n const onAbort = () => tappers.delete(handler);\n\n signal.addEventListener('abort', onAbort, { once: true });\n\n return () => {\n tappers.delete(handler);\n signal.removeEventListener('abort', onAbort);\n };\n },\n\n unbind(id: string): void {\n assertActive();\n\n if (!removeById(id)) {\n warn(`unbind() called for unknown id: \"${id}\"`);\n }\n },\n };\n}\n"],"mappings":"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"}
package/dist/keymap.js CHANGED
@@ -1,2 +1,2 @@
1
- var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{};function n(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var r={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},i={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function a(e,n){let a=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(a.length===0)throw new t(`Invalid shortcut step: "${e}"`);let o={...r,mod:n},s=new Set,c=[];for(let e of a){let t=Object.hasOwn(o,e)?o[e]:void 0;t?s.add(t):c.push(e)}if(c.length===0)throw new t(`Invalid shortcut step: "${e}"`);if(c.length>1)throw new t(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);let l=c[0];return{key:Object.hasOwn(i,l)?i[l]:l,modifiers:s}}function o(e,t=n()){try{return a(e,t)}catch{return null}}function s(e,r=n()){let i=e.trim().split(/\s+/).filter(Boolean);if(i.length===0)throw new t(`Shortcut must contain at least one key step`);return i.map(e=>a(e,r))}function c(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function l(e,t){if(typeof e.key!=`string`||e.key.toLowerCase()!==t.key)return!1;let{modifiers:n}=t;return e.altKey===n.has(`alt`)&&e.ctrlKey===n.has(`ctrl`)&&e.metaKey===n.has(`meta`)&&e.shiftKey===n.has(`shift`)}function u(e,t){return e.length>t.length?!1:c(e)===c(t.slice(0,e.length))}function d(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n;if(e.trim()===``)return[];let a=s(e,r);return t.filter(e=>e.trigger===i&&(u(a,e.shortcut)||u(e.shortcut,a)))}var f={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},p={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},m={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},h=[`ctrl`,`alt`,`shift`,`meta`];function g(e,t=n()){if(!e.trim())return``;let r;try{r=s(e,t)}catch{return`${e}`,``}let i=t===`meta`;return r.map(e=>{let t=h.filter(t=>e.modifiers.has(t)).map(e=>i?p[e]:m[e]),n=f[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}function _(e){return typeof e==`function`?{handler:e,trigger:`keydown`}:{handler:e.handler,trigger:e.trigger??`keydown`,when:e.when}}function v(e,t,n,r,i){let a=0,o=[],s,c=[];function u(e=!1){if(s!==void 0&&clearTimeout(s),e&&a>0&&i?.onChordTimeout)try{i.onChordTimeout(n,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}s=void 0,a=0,o=[],c=[]}function d(f){let p=(a===0?e():o).filter(e=>{let t=e.shortcut[a];return t!==void 0&&l(f,t)});if(p.length===0){let e=a!==0;return u(),e?d(f):void 0}s!==void 0&&clearTimeout(s),s=void 0;let m=p.find(e=>e.shortcut.length===a+1);if(m)return u(),m;let h=p[0].shortcut[a];if(c=p[0].shortcut.slice(0,a+1),a===0&&i?.onChordStart)try{i.onChordStart(n,h,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}else if(a>0&&i?.onChordProgress)try{i.onChordProgress(n,c,r)}catch(e){`${e instanceof Error?e.message:String(e)}`}o=p,a+=1,s=setTimeout(()=>u(!0),t)}return{advance:d,reset:()=>u()}}function y(t={},r={}){let{chordTimeout:i=1e3,modKey:a=n(),preventDefault:o=!0,stopPropagation:l=!1,when:u,onChordState:d}=r,f=Number.isFinite(i)&&i>0?i:1e3;f!==i&&`${i}`;let p=new Map,m=new Map,h=new AbortController,g=[],y=[],b=!1;function x(){if(b)throw new e(`Keymap is disposed`)}function S(){g=[],y=[];for(let e of p.values())e.trigger===`keydown`?g.push(e):y.push(e)}function C(e){return c(s(e,a))}function w(e,t){let n=s(e,a),r=c(n);return p.set(r,{shortcut:n,..._(t)}),S(),r}function T(e){let t=p.delete(e);return t&&S(),t}function E(e){if(d)try{d(e)}catch(e){`${e instanceof Error?e.message:String(e)}`}}function D(e,t){return n=>{let r=(typeof n.composedPath==`function`?n.composedPath():[]).find(e=>m.has(e));if(r&&r!==e)return;let i=n;if(b)return;let a=t.advance(i);u&&!u(i)||!a||a.when&&!a.when(i)||(o&&i.preventDefault(),l&&i.stopPropagation(),a.handler(i))}}for(let[e,n]of Object.entries(t))w(e,n);return{bind(e,t){x();let n=w(e,t);return()=>{b||T(n)}},get disposalSignal(){return h.signal},dispose(){if(!b){b=!0,h.abort();for(let[e,t]of m)e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset();m.clear(),p.clear(),g=[],y=[]}},get disposed(){return b},listBindings(){return[...p.values()].map(e=>({shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),trigger:e.trigger}))},mount(e){x();let t=m.get(e);if(!t){let n=v(()=>g,f,e,`keydown`,{onChordProgress:(e,t,n)=>E({steps:t,target:e,trigger:n,type:`progressed`}),onChordStart:(e,t,n)=>E({step:t,target:e,trigger:n,type:`started`}),onChordTimeout:(e,t)=>E({target:e,trigger:t,type:`timeout`})}),r=v(()=>y,f,e,`keyup`,{onChordProgress:(e,t,n)=>E({steps:t,target:e,trigger:n,type:`progressed`}),onChordStart:(e,t,n)=>E({step:t,target:e,trigger:n,type:`started`}),onChordTimeout:(e,t)=>E({target:e,trigger:t,type:`timeout`})}),i=D(e,n),a=D(e,r);t={keydown:n,keyup:r,onKeydown:i,onKeyup:a,refs:0},m.set(e,t),e.addEventListener(`keydown`,i),e.addEventListener(`keyup`,a)}t.refs+=1;let n=!1;return()=>{n||(n=!0,--t.refs,!(t?.refs>0)&&(e.removeEventListener(`keydown`,t?.onKeydown),e.removeEventListener(`keyup`,t?.onKeyup),t?.keydown.reset(),t?.keyup.reset(),m.delete(e)))}},[Symbol.dispose](){this.dispose()},unbind(e){x(),T(C(e))||`${e}`}}}export{e as KeymapError,t as KeymapParseError,c as canonicalizeShortcut,y as createKeymap,n as detectModKey,d as findShortcutConflicts,g as formatShortcut,l as matchStep,s as parseShortcut,o as parseStep};
1
+ var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{};function n(){if(typeof navigator>`u`)return`ctrl`;let e=navigator.userAgentData?.platform??navigator.platform??``;return/mac/i.test(e)?`meta`:`ctrl`}var r={alt:`alt`,cmd:`meta`,command:`meta`,control:`ctrl`,ctrl:`ctrl`,meta:`meta`,opt:`alt`,option:`alt`,shift:`shift`,win:`meta`},i={del:`delete`,down:`arrowdown`,esc:`escape`,left:`arrowleft`,right:`arrowright`,space:` `,spacebar:` `,up:`arrowup`};function a(e,n){let a=e.trim().split(`+`).filter(Boolean).map(e=>e.toLowerCase());if(a.length===0)throw new t(`Invalid shortcut step: "${e}"`);let o={...r,mod:n},s=new Set,c=[];for(let e of a){let t=Object.hasOwn(o,e)?o[e]:void 0;t?s.add(t):c.push(e)}if(c.length===0)throw new t(`Invalid shortcut step: "${e}"`);if(c.length>1)throw new t(`Ambiguous shortcut step: "${e}" — multiple non-modifier keys found`);let l=c[0];return{key:Object.hasOwn(i,l)?i[l]:l,modifiers:s}}function o(e,r=n()){let i=e.trim().split(/\s+/).filter(Boolean);if(i.length===0)throw new t(`Shortcut must contain at least one key step`);return i.map(e=>a(e,r))}function s(e){return e.map(e=>{let t=[...e.modifiers].sort().join(`+`);return t?`${t}+${e.key}`:e.key}).join(` `)}function c(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 l(e,t){return e.length>t.length?!1:s(e)===s(t.slice(0,e.length))}function u(e,t,n={}){let{modKey:r,trigger:i=`keydown`}=n;if(e.trim()===``)return[];let a=o(e,r);return t.filter(e=>e.trigger===i&&(l(a,e.shortcut)||l(e.shortcut,a)))}var d={" ":`Space`,arrowdown:`↓`,arrowleft:`←`,arrowright:`→`,arrowup:`↑`,backspace:`⌫`,delete:`⌦`,end:`End`,enter:`↵`,escape:`Esc`,home:`Home`,pagedown:`PgDn`,pageup:`PgUp`,tab:`⇥`},f={alt:`⌥`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`},p={alt:`Alt`,ctrl:`Ctrl`,meta:`Win`,shift:`Shift`},m=[`ctrl`,`alt`,`shift`,`meta`];function h(e,t=n()){if(!e.trim())return``;let r;try{r=o(e,t)}catch{return`${e}`,``}let i=t===`meta`;return r.map(e=>{let t=m.filter(t=>e.modifiers.has(t)).map(e=>i?f[e]:p[e]),n=d[e.key]??e.key.toUpperCase();return i?`${t.join(``)}${n}`:[...t,n].join(`+`)}).join(` `)}var g=()=>{};function _(e,t,n){let r=0,i=[],a;function o(e=!0){a!==void 0&&clearTimeout(a);let t=r>0;a=void 0,r=0,i=[],e&&t&&n.onCancel()}function s(l){let u=(r===0?e():i).filter(e=>{let t=e.shortcut[r];return t!==void 0&&c(l,t)&&(!e.when||e.when(l))});if(u.length===0){let e=r!==0;return o(),e?s(l):{type:`none`}}a!==void 0&&clearTimeout(a),a=void 0;let d=u.find(e=>e.shortcut.length===r+1);if(d)return o(!1),{binding:d,type:`match`};let f=r===0;return i=u,r+=1,n.onProgress(u[0].shortcut.slice(0,r),f),a=setTimeout(()=>{n.onTimeout(),o(!1)},t),{bindings:u,type:`pending`}}return{advance:s,reset:o}}function v(t=[],r={}){let{chordTimeout:i=1e3,modKey:a=n(),when:s}=r,c=Number.isFinite(i)&&i>0?i:1e3;c!==i&&`${i}`;let l=new Map,u=new Map,d=new Set,f=new AbortController,p=[],m=[],h=!1;function v(){if(h)throw new e(`Keymap is disposed`)}function y(){p=[],m=[];for(let e of l.values())e.trigger===`keydown`?p.push(e):m.push(e);for(let e of u.values())e.keydown.reset(),e.keyup.reset()}function b(e){let t={handler:e.handler,id:e.id,preventDefault:e.preventDefault??!0,shortcut:o(e.shortcut,a),stopPropagation:e.stopPropagation??!1,trigger:e.trigger??`keydown`,when:e.when};return l.set(t.id,t),y(),t}function x(e){let t=l.delete(e);return t&&y(),t}function S(e){return{id:e.id,preventDefault:e.preventDefault,shortcut:e.shortcut.map(e=>({key:e.key,modifiers:new Set(e.modifiers)})),stopPropagation:e.stopPropagation,trigger:e.trigger}}function C(e){if(d.size!==0)for(let t of d)try{t(e)}catch{}}function w(e,t,n){return r=>{let i=(typeof r.composedPath==`function`?r.composedPath():[]).find(e=>u.has(e));if(i&&i!==e)return;let a=r;if(h)return;if(s&&!s(a)){n.reset();return}let o=n.advance(a);if(o.type===`none`)return;let c=o.type===`pending`?o.bindings:[o.binding];c.some(e=>e.preventDefault)&&a.preventDefault(),c.some(e=>e.stopPropagation)&&a.stopPropagation(),o.type!==`pending`&&(d.size>0&&C({binding:S(o.binding),target:e,trigger:t,type:`match`}),o.binding.handler(a))}}for(let e of t)b(e);return{bind(e){v();let t=b(e);return()=>{!h&&l.get(t.id)===t&&x(t.id)}},get disposalSignal(){return f.signal},dispose(){if(!h){h=!0;for(let[e,t]of u)e.removeEventListener(`keydown`,t.onKeydown),e.removeEventListener(`keyup`,t.onKeyup),t.keydown.reset(),t.keyup.reset();C({type:`dispose`}),f.abort(),u.clear(),l.clear(),p=[],m=[],d.clear()}},get disposed(){return h},listBindings(){return[...l.values()].map(S)},mount(e){v();let t=u.get(e);if(!t){let n=t=>({onCancel:()=>{d.size>0&&C({target:e,trigger:t,type:`chord-cancel`})},onProgress:(n,r)=>{if(d.size===0)return;let i=n.map(e=>({key:e.key,modifiers:new Set(e.modifiers)}));C(r?{step:i[0],target:e,trigger:t,type:`chord-start`}:{steps:i,target:e,trigger:t,type:`chord-progress`})},onTimeout:()=>{d.size>0&&C({target:e,trigger:t,type:`chord-timeout`})}}),r=_(()=>p,c,n(`keydown`)),i=_(()=>m,c,n(`keyup`)),a=w(e,`keydown`,r),o=w(e,`keyup`,i);t={keydown:r,keyup:i,onKeydown:a,onKeyup:o,refs:0},u.set(e,t),e.addEventListener(`keydown`,a),e.addEventListener(`keyup`,o)}t.refs+=1;let n=!1;return()=>{n||(n=!0,--t.refs,!(t?.refs>0)&&(e.removeEventListener(`keydown`,t?.onKeydown),e.removeEventListener(`keyup`,t?.onKeyup),t?.keydown.reset(),t?.keyup.reset(),u.delete(e)))}},[Symbol.dispose](){this.dispose()},tap(e,t){let n=t?.signal?AbortSignal.any([f.signal,t.signal]):f.signal;if(n.aborted)return g;d.add(e);let r=()=>d.delete(e);return n.addEventListener(`abort`,r,{once:!0}),()=>{d.delete(e),n.removeEventListener(`abort`,r)}},unbind(e){v(),x(e)||`${e}`}}}export{e as KeymapError,t as KeymapParseError,v as createKeymap,u as findShortcutConflicts,h as formatShortcut};
2
2
  //# sourceMappingURL=keymap.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keymap.js","names":[],"sources":["../src/errors.ts","../src/parser.ts","../src/conflicts.ts","../src/_dev.ts","../src/format.ts","../src/keymap.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is KeymapError {\n return err instanceof KeymapError;\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n","import { KeymapParseError } from './errors';\n\nexport type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\nexport type ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\nexport type Shortcut = ShortcutStep[];\n\nexport function detectModKey(): 'ctrl' | 'meta' {\n if (typeof navigator === 'undefined') return 'ctrl';\n\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n\n return /mac/i.test(platform) ? 'meta' : 'ctrl';\n}\n\nconst MODIFIER_ALIASES_BASE: Record<string, ModifierKey> = {\n alt: 'alt',\n cmd: 'meta',\n command: 'meta',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n opt: 'alt',\n option: 'alt',\n shift: 'shift',\n win: 'meta',\n};\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n del: 'delete',\n down: 'arrowdown',\n esc: 'escape',\n left: 'arrowleft',\n right: 'arrowright',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n};\n\nfunction parseStepStrict(raw: string, modKey: 'ctrl' | 'meta'): ShortcutStep {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((part) => part.toLowerCase());\n\n if (parts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n const aliases: Record<string, ModifierKey> = { ...MODIFIER_ALIASES_BASE, mod: modKey };\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const modifier = Object.hasOwn(aliases, part) ? aliases[part]! : undefined;\n\n if (modifier) modifiers.add(modifier);\n else keyParts.push(part);\n }\n\n if (keyParts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const rawKey = keyParts[0]!;\n const key = Object.hasOwn(SPECIAL_KEY_ALIASES, rawKey) ? SPECIAL_KEY_ALIASES[rawKey]! : rawKey;\n\n return { key, modifiers };\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n try {\n return parseStepStrict(raw, modKey);\n } catch {\n return null;\n }\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n const steps = raw.trim().split(/\\s+/).filter(Boolean);\n\n if (steps.length === 0) throw new KeymapParseError('Shortcut must contain at least one key step');\n\n return steps.map((step) => parseStepStrict(step, modKey));\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((step) => {\n const modifiers = [...step.modifiers].sort().join('+');\n\n return modifiers ? `${modifiers}+${step.key}` : step.key;\n })\n .join(' ');\n}\n\nexport function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean {\n // Headless usage (SSR, non-DOM `EventTarget`s, hand-built test events) means `event` isn't\n // guaranteed to be a real `KeyboardEvent` at runtime even though the type says so — treat a\n // missing/non-string `key` as \"doesn't match\" rather than throwing inside the event listener.\n if (typeof event.key !== 'string') return false;\n\n if (event.key.toLowerCase() !== step.key) return false;\n\n const { modifiers } = step;\n\n return (\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift')\n );\n}\n","import type { ShortcutStep } from './parser';\nimport { canonicalizeShortcut, parseShortcut } from './parser';\nimport type { BindingEntry } from './types';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n","const isDev = !(globalThis as { __KEYMAP_PROD__?: boolean }).__KEYMAP_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/keymap] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps: ReturnType<typeof parseShortcut>;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n","import { warn } from './_dev';\nimport { KeymapError } from './errors';\nimport type { Shortcut, ShortcutStep } from './parser';\nimport { canonicalizeShortcut, detectModKey, matchStep, parseShortcut } from './parser';\nimport type { BindingEntry, BindingValue, ChordStateChange, Handler, Keymap, KeymapOptions, When } from './types';\n\ntype ParsedBinding = {\n handler: Handler;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: When;\n};\n\ntype ChordTracker = ReturnType<typeof createChordTracker>;\ntype MountedTarget = {\n keydown: ChordTracker;\n keyup: ChordTracker;\n onKeydown: EventListener;\n onKeyup: EventListener;\n refs: number;\n};\n\nfunction resolveBinding(value: BindingValue): Omit<ParsedBinding, 'shortcut'> {\n if (typeof value === 'function') return { handler: value, trigger: 'keydown' };\n\n return {\n handler: value.handler,\n trigger: value.trigger ?? 'keydown',\n when: value.when,\n };\n}\n\ntype ChordTrackerCallbacks = {\n onChordStart?: (target: EventTarget, step: ShortcutStep, trigger: 'keydown' | 'keyup') => void;\n onChordProgress?: (target: EventTarget, steps: readonly ShortcutStep[], trigger: 'keydown' | 'keyup') => void;\n onChordTimeout?: (target: EventTarget, trigger: 'keydown' | 'keyup') => void;\n};\n\nfunction createChordTracker(\n getBindings: () => ParsedBinding[],\n chordTimeout: number,\n target: EventTarget,\n trigger: 'keydown' | 'keyup',\n callbacks?: ChordTrackerCallbacks,\n) {\n let pendingIndex = 0;\n let candidates: ParsedBinding[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n let pendingSteps: ShortcutStep[] = [];\n\n function reset(emitTimeout = false): void {\n if (timer !== undefined) clearTimeout(timer);\n\n // Emit timeout only if explicitly requested (from timeout event)\n if (emitTimeout && pendingIndex > 0 && callbacks?.onChordTimeout) {\n try {\n callbacks.onChordTimeout(target, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n timer = undefined;\n pendingIndex = 0;\n candidates = [];\n pendingSteps = [];\n }\n\n function advance(event: KeyboardEvent): ParsedBinding | undefined {\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched = pool.filter((binding) => {\n const step = binding.shortcut[pendingIndex];\n\n return step !== undefined && matchStep(event, step);\n });\n\n if (matched.length === 0) {\n const retryFromRoot = pendingIndex !== 0;\n\n reset();\n\n return retryFromRoot ? advance(event) : undefined;\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n\n const completed = matched.find((binding) => binding.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset();\n\n return completed;\n }\n\n // New step in chord progression\n const currentStep = matched[0]!.shortcut[pendingIndex];\n pendingSteps = matched[0]!.shortcut.slice(0, pendingIndex + 1);\n\n // Emit started or progressed\n if (pendingIndex === 0 && callbacks?.onChordStart) {\n try {\n callbacks.onChordStart(target, currentStep, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n } else if (pendingIndex > 0 && callbacks?.onChordProgress) {\n try {\n callbacks.onChordProgress(target, pendingSteps, trigger);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n candidates = matched;\n pendingIndex += 1;\n timer = setTimeout(() => reset(true), chordTimeout);\n\n return undefined;\n }\n\n return { advance, reset: () => reset() };\n}\n\n/**\n * Creates a headless keyboard shortcut manager with target-local chord state.\n *\n * Pass a bindings map of shortcut strings to handlers or `BindingOptions`, then call\n * `.mount(target)` to attach to any `EventTarget`. Supports chord sequences\n * (e.g. `\"ctrl+k ctrl+s\"`), per-binding `when` guards, `trigger` (keydown/keyup),\n * and dynamic `bind`/`unbind`.\n *\n * @example\n * const map = createKeymap({\n * 'mod+k mod+s': () => save(),\n * 'mod+shift+p': () => openPalette(),\n * 'g g': () => goToTop(),\n * esc: { handler: closePanel, when: (event) => !isEditableTarget(event.target) },\n * space: { handler: togglePlay, trigger: 'keyup' },\n * }, { modKey: 'ctrl' });\n * const unmount = map.mount(document);\n */\nexport function createKeymap(initialBindings: Record<string, BindingValue> = {}, options: KeymapOptions = {}): Keymap {\n const {\n chordTimeout: rawChordTimeout = 1000,\n modKey = detectModKey(),\n preventDefault = true,\n stopPropagation = false,\n when: globalWhen,\n onChordState: userOnChordState,\n } = options;\n const chordTimeout = Number.isFinite(rawChordTimeout) && rawChordTimeout > 0 ? rawChordTimeout : 1000;\n\n if (chordTimeout !== rawChordTimeout) {\n warn(`chordTimeout must be a positive finite number; received ${rawChordTimeout}. Using default of 1000ms.`);\n }\n\n const bindings = new Map<string, ParsedBinding>();\n const mounted = new Map<EventTarget, MountedTarget>();\n const disposalController = new AbortController();\n let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n let disposed = false;\n\n function assertActive(): void {\n if (disposed) throw new KeymapError('Keymap is disposed');\n }\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const binding of bindings.values()) {\n if (binding.trigger === 'keydown') bindingsDown.push(binding);\n else bindingsUp.push(binding);\n }\n }\n\n function bindingKey(shortcut: string): string {\n return canonicalizeShortcut(parseShortcut(shortcut, modKey));\n }\n\n function addBinding(shortcut: string, value: BindingValue): string {\n const parsed = parseShortcut(shortcut, modKey);\n const key = canonicalizeShortcut(parsed);\n\n bindings.set(key, { shortcut: parsed, ...resolveBinding(value) });\n rebuildTriggerCaches();\n\n return key;\n }\n\n function removeByKey(key: string): boolean {\n const existed = bindings.delete(key);\n\n if (existed) rebuildTriggerCaches();\n\n return existed;\n }\n\n function emitChordState(change: ChordStateChange): void {\n if (!userOnChordState) return;\n\n try {\n userOnChordState(change);\n } catch (err) {\n warn(`onChordState callback error: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n function makeHandler(target: EventTarget, chord: ChordTracker): EventListener {\n return (event) => {\n const path = typeof event.composedPath === 'function' ? event.composedPath() : [];\n const nearestMountedTarget = path.find((pathTarget) => mounted.has(pathTarget));\n\n if (nearestMountedTarget && nearestMountedTarget !== target) return;\n\n const keyboardEvent = event as KeyboardEvent;\n\n if (disposed) return;\n\n // Track chord progression (independent of guards)\n const binding = chord.advance(keyboardEvent);\n\n // Check guards AFTER tracking chord\n if (globalWhen && !globalWhen(keyboardEvent)) return;\n\n if (!binding || (binding.when && !binding.when(keyboardEvent))) return;\n\n if (preventDefault) keyboardEvent.preventDefault();\n\n if (stopPropagation) keyboardEvent.stopPropagation();\n\n binding.handler(keyboardEvent);\n };\n }\n\n for (const [shortcut, value] of Object.entries(initialBindings)) addBinding(shortcut, value);\n\n return {\n bind(shortcut: string, value: BindingValue): () => void {\n assertActive();\n\n const key = addBinding(shortcut, value);\n\n return () => {\n if (!disposed) removeByKey(key);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n\n for (const [target, record] of mounted) {\n target.removeEventListener('keydown', record.onKeydown);\n target.removeEventListener('keyup', record.onKeyup);\n record.keydown.reset();\n record.keyup.reset();\n }\n\n mounted.clear();\n bindings.clear();\n bindingsDown = [];\n bindingsUp = [];\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map((binding) => ({\n shortcut: binding.shortcut.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) })),\n trigger: binding.trigger,\n }));\n },\n\n mount(target: EventTarget): () => void {\n assertActive();\n\n let record = mounted.get(target);\n\n if (!record) {\n const keydown = createChordTracker(() => bindingsDown, chordTimeout, target, 'keydown', {\n onChordProgress: (t, steps, trigger) => emitChordState({ steps, target: t, trigger, type: 'progressed' }),\n onChordStart: (t, step, trigger) => emitChordState({ step, target: t, trigger, type: 'started' }),\n onChordTimeout: (t, trigger) => emitChordState({ target: t, trigger, type: 'timeout' }),\n });\n const keyup = createChordTracker(() => bindingsUp, chordTimeout, target, 'keyup', {\n onChordProgress: (t, steps, trigger) => emitChordState({ steps, target: t, trigger, type: 'progressed' }),\n onChordStart: (t, step, trigger) => emitChordState({ step, target: t, trigger, type: 'started' }),\n onChordTimeout: (t, trigger) => emitChordState({ target: t, trigger, type: 'timeout' }),\n });\n const onKeydown = makeHandler(target, keydown);\n const onKeyup = makeHandler(target, keyup);\n\n record = { keydown, keyup, onKeydown, onKeyup, refs: 0 };\n mounted.set(target, record);\n target.addEventListener('keydown', onKeydown);\n target.addEventListener('keyup', onKeyup);\n }\n\n record.refs += 1;\n\n let unmounted = false;\n\n return () => {\n if (unmounted) return;\n\n unmounted = true;\n record!.refs -= 1;\n\n if (record?.refs > 0) return;\n\n target.removeEventListener('keydown', record?.onKeydown);\n target.removeEventListener('keyup', record?.onKeyup);\n record?.keydown.reset();\n record?.keyup.reset();\n mounted.delete(target);\n };\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n unbind(shortcut: string): void {\n assertActive();\n\n if (!removeByKey(bindingKey(shortcut))) {\n warn(`unbind() called for unknown shortcut: \"${shortcut}\"`);\n }\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"keymap.js","names":[],"sources":["../src/errors.ts","../src/parser.ts","../src/conflicts.ts","../src/_dev.ts","../src/format.ts","../src/keymap.ts"],"sourcesContent":["/** Base class for all keymap errors. Use `instanceof KeymapError` to catch any keymap-originated error. */\nexport class KeymapError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a shortcut string cannot be parsed — ambiguous or invalid key step. */\nexport class KeymapParseError extends KeymapError {}\n","import { KeymapParseError } from './errors';\n\nexport type ModifierKey = 'alt' | 'ctrl' | 'meta' | 'shift';\n\nexport type ShortcutStep = {\n key: string;\n modifiers: Set<ModifierKey>;\n};\n\nexport type Shortcut = ShortcutStep[];\n\nexport function detectModKey(): 'ctrl' | 'meta' {\n if (typeof navigator === 'undefined') return 'ctrl';\n\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ??\n navigator.platform ??\n '';\n\n return /mac/i.test(platform) ? 'meta' : 'ctrl';\n}\n\nconst MODIFIER_ALIASES_BASE: Record<string, ModifierKey> = {\n alt: 'alt',\n cmd: 'meta',\n command: 'meta',\n control: 'ctrl',\n ctrl: 'ctrl',\n meta: 'meta',\n opt: 'alt',\n option: 'alt',\n shift: 'shift',\n win: 'meta',\n};\n\nconst SPECIAL_KEY_ALIASES: Record<string, string> = {\n del: 'delete',\n down: 'arrowdown',\n esc: 'escape',\n left: 'arrowleft',\n right: 'arrowright',\n space: ' ',\n spacebar: ' ',\n up: 'arrowup',\n};\n\nfunction parseStepStrict(raw: string, modKey: 'ctrl' | 'meta'): ShortcutStep {\n const parts = raw\n .trim()\n .split('+')\n .filter(Boolean)\n .map((part) => part.toLowerCase());\n\n if (parts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n const aliases: Record<string, ModifierKey> = { ...MODIFIER_ALIASES_BASE, mod: modKey };\n const modifiers = new Set<ModifierKey>();\n const keyParts: string[] = [];\n\n for (const part of parts) {\n const modifier = Object.hasOwn(aliases, part) ? aliases[part]! : undefined;\n\n if (modifier) modifiers.add(modifier);\n else keyParts.push(part);\n }\n\n if (keyParts.length === 0) throw new KeymapParseError(`Invalid shortcut step: \"${raw}\"`);\n\n if (keyParts.length > 1) {\n throw new KeymapParseError(`Ambiguous shortcut step: \"${raw}\" — multiple non-modifier keys found`);\n }\n\n const rawKey = keyParts[0]!;\n const key = Object.hasOwn(SPECIAL_KEY_ALIASES, rawKey) ? SPECIAL_KEY_ALIASES[rawKey]! : rawKey;\n\n return { key, modifiers };\n}\n\nexport function parseStep(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): ShortcutStep | null {\n try {\n return parseStepStrict(raw, modKey);\n } catch {\n return null;\n }\n}\n\nexport function parseShortcut(raw: string, modKey: 'ctrl' | 'meta' = detectModKey()): Shortcut {\n const steps = raw.trim().split(/\\s+/).filter(Boolean);\n\n if (steps.length === 0) throw new KeymapParseError('Shortcut must contain at least one key step');\n\n return steps.map((step) => parseStepStrict(step, modKey));\n}\n\nexport function canonicalizeShortcut(steps: readonly ShortcutStep[]): string {\n return steps\n .map((step) => {\n const modifiers = [...step.modifiers].sort().join('+');\n\n return modifiers ? `${modifiers}+${step.key}` : step.key;\n })\n .join(' ');\n}\n\nexport function matchStep(event: KeyboardEvent, step: ShortcutStep): boolean {\n // Headless usage (SSR, non-DOM `EventTarget`s, hand-built test events) means `event` isn't\n // guaranteed to be a real `KeyboardEvent` at runtime even though the type says so — treat a\n // missing/non-string `key` as \"doesn't match\" rather than throwing inside the event listener.\n if (typeof event.key !== 'string') return false;\n\n if (event.key.toLowerCase() !== step.key) return false;\n\n const { modifiers } = step;\n\n return (\n event.altKey === modifiers.has('alt') &&\n event.ctrlKey === modifiers.has('ctrl') &&\n event.metaKey === modifiers.has('meta') &&\n event.shiftKey === modifiers.has('shift')\n );\n}\n","import type { ShortcutStep } from './parser';\nimport { canonicalizeShortcut, parseShortcut } from './parser';\nimport type { BindingEntry } from './types';\n\nexport interface ConflictOptions {\n modKey?: 'ctrl' | 'meta';\n trigger?: 'keydown' | 'keyup';\n}\n\nfunction isPrefixOf(shorter: readonly ShortcutStep[], longer: readonly ShortcutStep[]): boolean {\n if (shorter.length > longer.length) return false;\n\n return canonicalizeShortcut(shorter) === canonicalizeShortcut(longer.slice(0, shorter.length));\n}\n\n/**\n * Finds registered bindings that would conflict with a proposed shortcut — an exact duplicate, a\n * shorter binding that would shadow it as a chord prefix, or a longer binding it would itself\n * shadow. Only compares against entries sharing the same `trigger` — `'keydown'` and `'keyup'`\n * chords are matched independently and never conflict with each other.\n *\n * @example\n * const entries = map.listBindings();\n * findShortcutConflicts('g g', entries); // → the entry for 'g', if one is bound\n */\nexport function findShortcutConflicts(\n shortcut: string,\n entries: readonly BindingEntry[],\n options: ConflictOptions = {},\n): BindingEntry[] {\n const { modKey, trigger = 'keydown' } = options;\n\n // An empty/whitespace-only shortcut would be a trivial prefix of every entry — never report it\n // as a conflict.\n if (shortcut.trim() === '') return [];\n\n const steps = parseShortcut(shortcut, modKey);\n\n return entries.filter(\n (entry) => entry.trigger === trigger && (isPrefixOf(steps, entry.shortcut) || isPrefixOf(entry.shortcut, steps)),\n );\n}\n","const isDev = !(globalThis as { __KEYMAP_PROD__?: boolean }).__KEYMAP_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/keymap] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import { warn } from './_dev';\nimport { detectModKey, parseShortcut } from './parser';\n\nconst KEY_SYMBOLS: Record<string, string> = {\n ' ': 'Space',\n arrowdown: '↓',\n arrowleft: '←',\n arrowright: '→',\n arrowup: '↑',\n backspace: '⌫',\n delete: '⌦',\n end: 'End',\n enter: '↵',\n escape: 'Esc',\n home: 'Home',\n pagedown: 'PgDn',\n pageup: 'PgUp',\n tab: '⇥',\n};\n\nconst MOD_SYMBOLS_MAC: Record<string, string> = {\n alt: '⌥',\n ctrl: '⌃',\n meta: '⌘',\n shift: '⇧',\n};\n\nconst MOD_LABELS_OTHER: Record<string, string> = {\n alt: 'Alt',\n ctrl: 'Ctrl',\n meta: 'Win',\n shift: 'Shift',\n};\n\nconst MOD_ORDER = ['ctrl', 'alt', 'shift', 'meta'] as const;\n\n/**\n * Formats a shortcut string into a human-readable display string.\n *\n * On Mac (when `modKey` is `'meta'`), uses standard Mac symbols (⌘, ⌥, ⇧, ⌃).\n * On other platforms, uses word labels (Ctrl, Alt, Shift, Win).\n *\n * @example\n * formatShortcut('mod+shift+p', 'meta') // '⇧⌘P' (canonical order: ctrl, alt, shift, meta)\n * formatShortcut('mod+shift+p', 'ctrl') // 'Ctrl+Shift+P'\n * formatShortcut('ctrl+k ctrl+s', 'meta') // '⌃K ⌃S'\n */\nexport function formatShortcut(shortcut: string, modKey: 'ctrl' | 'meta' = detectModKey()): string {\n if (!shortcut.trim()) {\n warn(`formatShortcut() received an empty shortcut string`);\n\n return '';\n }\n\n let steps: ReturnType<typeof parseShortcut>;\n\n try {\n steps = parseShortcut(shortcut, modKey);\n } catch {\n warn(`formatShortcut() received an invalid shortcut: \"${shortcut}\"`);\n\n return '';\n }\n\n const isMac = modKey === 'meta';\n\n return steps\n .map((step) => {\n const modParts = MOD_ORDER.filter((m) => step.modifiers.has(m)).map((m) =>\n isMac ? MOD_SYMBOLS_MAC[m] : MOD_LABELS_OTHER[m],\n );\n\n const keyLabel = KEY_SYMBOLS[step.key] ?? step.key.toUpperCase();\n\n return isMac ? `${modParts.join('')}${keyLabel}` : [...modParts, keyLabel].join('+');\n })\n .join(' ');\n}\n","import { warn } from './_dev';\nimport { KeymapError } from './errors';\nimport type { Shortcut } from './parser';\nimport { detectModKey, matchStep, parseShortcut } from './parser';\nimport type { Binding, BindingEntry, Handler, Keymap, KeymapEvent, KeymapOptions, When } from './types';\n\ntype ParsedBinding = {\n id: string;\n handler: Handler;\n shortcut: Shortcut;\n trigger: 'keydown' | 'keyup';\n when?: When;\n preventDefault: boolean;\n stopPropagation: boolean;\n};\n\ntype ChordAdvanceResult =\n | { type: 'none' }\n | { bindings: ParsedBinding[]; type: 'pending' }\n | { binding: ParsedBinding; type: 'match' };\ntype ChordTrackerCallbacks = {\n onCancel: () => void;\n onProgress: (steps: Shortcut, started: boolean) => void;\n onTimeout: () => void;\n};\ntype ChordTracker = ReturnType<typeof createChordTracker>;\ntype MountedTarget = {\n keydown: ChordTracker;\n keyup: ChordTracker;\n onKeydown: EventListener;\n onKeyup: EventListener;\n refs: number;\n};\n\nconst noop = () => {};\n\nfunction createChordTracker(\n getBindings: () => ParsedBinding[],\n chordTimeout: number,\n callbacks: ChordTrackerCallbacks,\n) {\n let pendingIndex = 0;\n let candidates: ParsedBinding[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n function reset(notify = true): void {\n if (timer !== undefined) clearTimeout(timer);\n\n const hadPending = pendingIndex > 0;\n\n timer = undefined;\n pendingIndex = 0;\n candidates = [];\n\n if (notify && hadPending) callbacks.onCancel();\n }\n\n function advance(event: KeyboardEvent): ChordAdvanceResult {\n const pool = pendingIndex === 0 ? getBindings() : candidates;\n const matched = pool.filter((binding) => {\n const step = binding.shortcut[pendingIndex];\n\n return step !== undefined && matchStep(event, step) && (!binding.when || binding.when(event));\n });\n\n if (matched.length === 0) {\n const retryFromRoot = pendingIndex !== 0;\n\n reset();\n\n return retryFromRoot ? advance(event) : { type: 'none' };\n }\n\n if (timer !== undefined) clearTimeout(timer);\n\n timer = undefined;\n\n const completed = matched.find((binding) => binding.shortcut.length === pendingIndex + 1);\n\n if (completed) {\n reset(false);\n\n return { binding: completed, type: 'match' };\n }\n\n const started = pendingIndex === 0;\n\n candidates = matched;\n pendingIndex += 1;\n callbacks.onProgress(matched[0]!.shortcut.slice(0, pendingIndex), started);\n timer = setTimeout(() => {\n callbacks.onTimeout();\n reset(false);\n }, chordTimeout);\n\n return { bindings: matched, type: 'pending' };\n }\n\n return { advance, reset };\n}\n\n/**\n * Creates a headless keyboard shortcut manager with target-local chord state.\n *\n * Pass an ordered array of `Binding` objects (each with an explicit `id`,\n * `shortcut`, and `handler`), then call `.mount(target)` to attach to any\n * `EventTarget`. Supports chord sequences (e.g. `\"ctrl+k ctrl+s\"`), per-binding\n * `when` guards, `trigger` (keydown/keyup), and per-binding\n * `preventDefault`/`stopPropagation`. Dynamic `bind`/`unbind` use the binding\n * `id`, so duplicate shortcuts can coexist with different ids.\n *\n * @example\n * const map = createKeymap([\n * { id: 'save', shortcut: 'mod+k mod+s', handler: save },\n * { id: 'palette', shortcut: 'mod+shift+p', handler: openPalette },\n * { id: 'top', shortcut: 'g g', handler: goToTop },\n * { id: 'close', shortcut: 'escape', handler: closePanel, when: (event) => !isEditableTarget(event.target) },\n * { id: 'play', shortcut: 'space', handler: togglePlay, trigger: 'keyup' },\n * ], { modKey: 'ctrl' });\n * const unmount = map.mount(document);\n */\nexport function createKeymap(initialBindings: readonly Binding[] = [], options: KeymapOptions = {}): Keymap {\n const { chordTimeout: rawChordTimeout = 1000, modKey = detectModKey(), when: globalWhen } = options;\n const chordTimeout = Number.isFinite(rawChordTimeout) && rawChordTimeout > 0 ? rawChordTimeout : 1000;\n\n if (chordTimeout !== rawChordTimeout) {\n warn(`chordTimeout must be a positive finite number; received ${rawChordTimeout}. Using default of 1000ms.`);\n }\n\n // Ordered map keyed by binding id — preserves insertion order and supports\n // duplicate shortcuts with different ids.\n const bindings = new Map<string, ParsedBinding>();\n const mounted = new Map<EventTarget, MountedTarget>();\n const tappers = new Set<(event: KeymapEvent) => void>();\n const disposalController = new AbortController();\n let bindingsDown: ParsedBinding[] = [];\n let bindingsUp: ParsedBinding[] = [];\n let disposed = false;\n\n function assertActive(): void {\n if (disposed) throw new KeymapError('Keymap is disposed');\n }\n\n function rebuildTriggerCaches(): void {\n bindingsDown = [];\n bindingsUp = [];\n\n for (const binding of bindings.values()) {\n if (binding.trigger === 'keydown') bindingsDown.push(binding);\n else bindingsUp.push(binding);\n }\n\n for (const record of mounted.values()) {\n record.keydown.reset();\n record.keyup.reset();\n }\n }\n\n function addBinding(binding: Binding): ParsedBinding {\n const parsedBinding: ParsedBinding = {\n handler: binding.handler,\n id: binding.id,\n preventDefault: binding.preventDefault ?? true,\n shortcut: parseShortcut(binding.shortcut, modKey),\n stopPropagation: binding.stopPropagation ?? false,\n trigger: binding.trigger ?? 'keydown',\n when: binding.when,\n };\n\n bindings.set(parsedBinding.id, parsedBinding);\n rebuildTriggerCaches();\n\n return parsedBinding;\n }\n\n function removeById(id: string): boolean {\n const existed = bindings.delete(id);\n\n if (existed) rebuildTriggerCaches();\n\n return existed;\n }\n\n function toBindingEntry(binding: ParsedBinding): BindingEntry {\n return {\n id: binding.id,\n preventDefault: binding.preventDefault,\n shortcut: binding.shortcut.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) })),\n stopPropagation: binding.stopPropagation,\n trigger: binding.trigger,\n };\n }\n\n function emitTap(event: KeymapEvent): void {\n if (tappers.size === 0) return;\n\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {}\n }\n }\n\n function makeHandler(target: EventTarget, trigger: 'keydown' | 'keyup', chord: ChordTracker): EventListener {\n return (event) => {\n const path = typeof event.composedPath === 'function' ? event.composedPath() : [];\n const nearestMountedTarget = path.find((pathTarget) => mounted.has(pathTarget));\n\n if (nearestMountedTarget && nearestMountedTarget !== target) return;\n\n const keyboardEvent = event as KeyboardEvent;\n\n if (disposed) return;\n\n if (globalWhen && !globalWhen(keyboardEvent)) {\n chord.reset();\n\n return;\n }\n\n const result = chord.advance(keyboardEvent);\n\n if (result.type === 'none') return;\n\n const matchedBindings = result.type === 'pending' ? result.bindings : [result.binding];\n\n if (matchedBindings.some((binding) => binding.preventDefault)) keyboardEvent.preventDefault();\n if (matchedBindings.some((binding) => binding.stopPropagation)) keyboardEvent.stopPropagation();\n\n if (result.type === 'pending') return;\n\n if (tappers.size > 0) {\n emitTap({ binding: toBindingEntry(result.binding), target, trigger, type: 'match' });\n }\n\n result.binding.handler(keyboardEvent);\n };\n }\n\n for (const binding of initialBindings) addBinding(binding);\n\n return {\n bind(binding: Binding): () => void {\n assertActive();\n\n const parsedBinding = addBinding(binding);\n\n return () => {\n if (!disposed && bindings.get(parsedBinding.id) === parsedBinding) removeById(parsedBinding.id);\n };\n },\n\n get disposalSignal(): AbortSignal {\n return disposalController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n\n for (const [target, record] of mounted) {\n target.removeEventListener('keydown', record.onKeydown);\n target.removeEventListener('keyup', record.onKeyup);\n record.keydown.reset();\n record.keyup.reset();\n }\n\n emitTap({ type: 'dispose' });\n disposalController.abort();\n mounted.clear();\n bindings.clear();\n bindingsDown = [];\n bindingsUp = [];\n tappers.clear();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n listBindings(): readonly BindingEntry[] {\n return [...bindings.values()].map(toBindingEntry);\n },\n\n mount(target: EventTarget): () => void {\n assertActive();\n\n let record = mounted.get(target);\n\n if (!record) {\n const createCallbacks = (trigger: 'keydown' | 'keyup'): ChordTrackerCallbacks => ({\n onCancel: () => {\n if (tappers.size > 0) emitTap({ target, trigger, type: 'chord-cancel' });\n },\n onProgress: (steps, started) => {\n if (tappers.size === 0) return;\n\n const snapshot = steps.map((step) => ({ key: step.key, modifiers: new Set(step.modifiers) }));\n\n if (started) emitTap({ step: snapshot[0]!, target, trigger, type: 'chord-start' });\n else emitTap({ steps: snapshot, target, trigger, type: 'chord-progress' });\n },\n onTimeout: () => {\n if (tappers.size > 0) emitTap({ target, trigger, type: 'chord-timeout' });\n },\n });\n const keydown = createChordTracker(() => bindingsDown, chordTimeout, createCallbacks('keydown'));\n const keyup = createChordTracker(() => bindingsUp, chordTimeout, createCallbacks('keyup'));\n const onKeydown = makeHandler(target, 'keydown', keydown);\n const onKeyup = makeHandler(target, 'keyup', keyup);\n\n record = { keydown, keyup, onKeydown, onKeyup, refs: 0 };\n mounted.set(target, record);\n target.addEventListener('keydown', onKeydown);\n target.addEventListener('keyup', onKeyup);\n }\n\n record.refs += 1;\n\n let unmounted = false;\n\n return () => {\n if (unmounted) return;\n\n unmounted = true;\n record!.refs -= 1;\n\n if (record?.refs > 0) return;\n\n target.removeEventListener('keydown', record?.onKeydown);\n target.removeEventListener('keyup', record?.onKeyup);\n record?.keydown.reset();\n record?.keyup.reset();\n mounted.delete(target);\n };\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n\n tap(handler: (event: KeymapEvent) => void, options?: { signal?: AbortSignal }): () => void {\n const signal = options?.signal\n ? AbortSignal.any([disposalController.signal, options.signal])\n : disposalController.signal;\n\n if (signal.aborted) return noop;\n\n tappers.add(handler);\n\n const onAbort = () => tappers.delete(handler);\n\n signal.addEventListener('abort', onAbort, { once: true });\n\n return () => {\n tappers.delete(handler);\n signal.removeEventListener('abort', onAbort);\n };\n },\n\n unbind(id: string): void {\n assertActive();\n\n if (!removeById(id)) {\n warn(`unbind() called for unknown id: \"${id}\"`);\n }\n },\n };\n}\n"],"mappings":"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"}
package/dist/parse.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./parser.cjs");exports.canonicalizeShortcut=e.canonicalizeShortcut,exports.detectModKey=e.detectModKey,exports.matchStep=e.matchStep,exports.parseShortcut=e.parseShortcut,exports.parseStep=e.parseStep;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Parser internals subpath export.
3
+ *
4
+ * These functions and types are not exported from the root entry point. Import
5
+ * them from `@vielzeug/keymap/parse` when building custom tooling, validators,
6
+ * or framework integrations that need direct access to the shortcut parser.
7
+ *
8
+ * @example
9
+ * import { parseShortcut, parseStep, matchStep, canonicalizeShortcut, detectModKey } from '@vielzeug/keymap/parse';
10
+ */
11
+ export type { ModifierKey, Shortcut, ShortcutStep } from './parser';
12
+ export { canonicalizeShortcut, detectModKey, matchStep, parseShortcut, parseStep } from './parser';
13
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC"}
package/dist/parse.js ADDED
@@ -0,0 +1,2 @@
1
+ import { canonicalizeShortcut as e, detectModKey as t, matchStep as n, parseShortcut as r, parseStep as i } from "./parser.js";
2
+ export { e as canonicalizeShortcut, t as detectModKey, n as matchStep, r as parseShortcut, i as parseStep };
package/dist/types.d.ts CHANGED
@@ -1,9 +1,4 @@
1
1
  import type { ShortcutStep } from './parser';
2
- export type BindingEntry = {
3
- readonly shortcut: readonly ShortcutStep[];
4
- readonly trigger: 'keydown' | 'keyup';
5
- };
6
- export type Handler = (event: KeyboardEvent) => void;
7
2
  /**
8
3
  * Guard function for fine-grained binding context.
9
4
  *
@@ -20,59 +15,72 @@ export type Handler = (event: KeyboardEvent) => void;
20
15
  * { when: (e) => !isModalOpen() }
21
16
  *
22
17
  * // Per-binding: only fire when event target is the specific panel
23
- * { 'escape': { handler: closePanel, when: (e) => e.target === panel } }
18
+ * { id: 'close-panel', shortcut: 'escape', handler: closePanel, when: (e) => e.target === panel }
24
19
  *
25
20
  * Both conditions must be met for the handler to execute.
26
21
  */
27
22
  export type When = (event: KeyboardEvent) => boolean;
28
- export type BindingOptions = {
29
- handler: Handler;
30
- trigger?: 'keydown' | 'keyup';
31
- when?: When;
23
+ export type Handler = (event: KeyboardEvent) => void;
24
+ export type KeymapEvent = {
25
+ readonly target: EventTarget;
26
+ readonly trigger: 'keydown' | 'keyup';
27
+ readonly type: 'chord-cancel';
28
+ } | {
29
+ readonly step: ShortcutStep;
30
+ readonly target: EventTarget;
31
+ readonly trigger: 'keydown' | 'keyup';
32
+ readonly type: 'chord-start';
33
+ } | {
34
+ readonly steps: readonly ShortcutStep[];
35
+ readonly target: EventTarget;
36
+ readonly trigger: 'keydown' | 'keyup';
37
+ readonly type: 'chord-progress';
38
+ } | {
39
+ readonly target: EventTarget;
40
+ readonly trigger: 'keydown' | 'keyup';
41
+ readonly type: 'chord-timeout';
42
+ } | {
43
+ readonly binding: BindingEntry;
44
+ readonly target: EventTarget;
45
+ readonly trigger: 'keydown' | 'keyup';
46
+ readonly type: 'match';
47
+ } | {
48
+ readonly type: 'dispose';
32
49
  };
33
- export type BindingValue = Handler | BindingOptions;
34
50
  /**
35
- * Chord state change event emitted when chord progression changes.
51
+ * A single ordered binding.
36
52
  *
37
- * Use `onChordState` callback in `KeymapOptions` to observe chord state
38
- * for debugging, UI hints, or chord history. When a chord fully matches,
39
- * the binding handler fires immediately; no separate 'completed' event.
53
+ * Each binding has an explicit `id` (string) so duplicate shortcuts can coexist
54
+ * with different ids — `unbind(id)` removes by id, not by shortcut string.
55
+ *
56
+ * @example
57
+ * const map = createKeymap([
58
+ * { id: 'save', shortcut: 'mod+s', handler: save },
59
+ * { id: 'palette', shortcut: 'mod+shift+p', handler: openPalette },
60
+ * { id: 'top', shortcut: 'g g', handler: goToTop },
61
+ * { id: 'close', shortcut: 'escape', handler: closePanel, when: (e) => !isEditableTarget(e.target) },
62
+ * { id: 'play', shortcut: 'space', handler: togglePlay, trigger: 'keyup' },
63
+ * ], { modKey: 'ctrl' });
40
64
  */
41
- export type ChordStateChange = {
42
- type: 'started';
43
- target: EventTarget;
44
- step: ShortcutStep;
45
- trigger: 'keydown' | 'keyup';
46
- } | {
47
- type: 'progressed';
48
- target: EventTarget;
49
- steps: readonly ShortcutStep[];
50
- trigger: 'keydown' | 'keyup';
51
- } | {
52
- type: 'timeout';
53
- target: EventTarget;
54
- trigger: 'keydown' | 'keyup';
55
- };
65
+ export interface Binding {
66
+ /** Handler invoked when the shortcut matches. */
67
+ handler: Handler;
68
+ /** Stable identifier for this binding. Used by `unbind(id)`. */
69
+ id: string;
70
+ /** Call `event.preventDefault()` when matched. Defaults to `true`. */
71
+ preventDefault?: boolean;
72
+ /** Shortcut string, e.g. `'mod+s'` or `'g g'`. */
73
+ shortcut: string;
74
+ /** Call `event.stopPropagation()` when matched. Defaults to `false`. */
75
+ stopPropagation?: boolean;
76
+ /** Event phase. Defaults to `'keydown'`. */
77
+ trigger?: 'keydown' | 'keyup';
78
+ /** Per-binding guard. Both global and per-binding guards must pass. */
79
+ when?: When;
80
+ }
56
81
  export interface KeymapOptions {
57
82
  chordTimeout?: number;
58
83
  modKey?: 'ctrl' | 'meta';
59
- /**
60
- * Optional callback to observe chord state changes.
61
- *
62
- * Fires when a chord starts, progresses through steps, completes, or times out.
63
- * Useful for debugging, logging, testing, or implementing chord UI hints.
64
- *
65
- * @example
66
- * {
67
- * onChordState: (change) => {
68
- * if (change.type === 'started') console.log('Chord started:', change.step.key);
69
- * if (change.type === 'timeout') console.log('Chord timed out');
70
- * }
71
- * }
72
- */
73
- onChordState?: (change: ChordStateChange) => void;
74
- preventDefault?: boolean;
75
- stopPropagation?: boolean;
76
84
  /**
77
85
  * Guard function for all bindings in this keymap.
78
86
  *
@@ -85,13 +93,25 @@ export interface KeymapOptions {
85
93
  when?: When;
86
94
  }
87
95
  export interface Keymap {
88
- bind(shortcut: string, value: BindingValue): () => void;
96
+ /** Adds a binding (replacing any existing binding with the same id) and returns an unbind closure. */
97
+ bind(binding: Binding): () => void;
89
98
  readonly disposalSignal: AbortSignal;
90
99
  dispose(): void;
91
100
  readonly disposed: boolean;
92
101
  listBindings(): readonly BindingEntry[];
93
102
  mount(target: EventTarget): () => void;
94
- unbind(shortcut: string): void;
103
+ tap(handler: (event: KeymapEvent) => void, options?: {
104
+ signal?: AbortSignal;
105
+ }): () => void;
106
+ /** Removes the binding with the given id. Warns in development when unknown. */
107
+ unbind(id: string): void;
95
108
  [Symbol.dispose](): void;
96
109
  }
110
+ export type BindingEntry = {
111
+ readonly id: string;
112
+ readonly shortcut: readonly ShortcutStep[];
113
+ readonly trigger: 'keydown' | 'keyup';
114
+ readonly preventDefault: boolean;
115
+ readonly stopPropagation: boolean;
116
+ };
97
117
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAErD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC;AAErD,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9B,IAAI,CAAC,EAAE,IAAI,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,cAAc,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAA;CAAE,GAC1F;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAA;CAAE,GACzG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAA;CAAE,CAAC;AAE3E,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAEzB;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,IAAI,CAAC;IACxD,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC;IACrC,OAAO,IAAI,IAAI,CAAC;IAChB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,YAAY,IAAI,SAAS,YAAY,EAAE,CAAC;IACxC,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,IAAI,CAAC;IACvC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/keymap",
3
- "version": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "Headless keyboard shortcut manager with chord sequences, context guards, and disposable bindings",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,25 +22,38 @@
22
22
  "import": "./dist/index.js",
23
23
  "require": "./dist/index.cjs",
24
24
  "types": "./dist/index.d.ts"
25
+ },
26
+ "./parse": {
27
+ "import": "./dist/parse.js",
28
+ "require": "./dist/parse.cjs",
29
+ "types": "./dist/parse.d.ts"
25
30
  }
26
31
  },
32
+ "typesVersions": {
33
+ "*": {
34
+ "parse": [
35
+ "dist/parse.d.ts"
36
+ ]
37
+ }
38
+ },
39
+ "sideEffects": false,
27
40
  "publishConfig": {
28
41
  "access": "public",
29
42
  "registry": "https://registry.npmjs.org/"
30
43
  },
31
44
  "scripts": {
32
- "build": "vite build && pnpm run build:bundle && pnpm run build:types",
45
+ "build": "vite build && pnpm --silent run build:bundle && pnpm --silent run build:types",
33
46
  "build:bundle": "vite build --config vite.bundle.config.ts",
34
47
  "build:types": "tsc -p tsconfig.declarations.json",
35
48
  "fix": "biome check --write src",
36
49
  "lint": "biome ci src",
37
- "prepublishOnly": "pnpm run build",
50
+ "prepublishOnly": "pnpm --silent run build",
38
51
  "test": "vitest"
39
52
  },
40
53
  "devDependencies": {
41
- "@types/node": "^26.2.0",
54
+ "@types/node": "^26.4.1",
42
55
  "typescript": "^6.0.3",
43
- "vite": "^8.2.1",
44
- "vitest": "^4.1.10"
56
+ "vite": "^8.2.2",
57
+ "vitest": "^5.0.0"
45
58
  }
46
59
  }