@remcostoeten/use-shortcut 2.1.0 → 2.3.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,48 +1,119 @@
1
1
  # @remcostoeten/use-shortcut
2
2
 
3
- WIP keyboard shortcut library for React with a chainable API.
3
+ Tiny, chainable keyboard shortcuts for React and Next.js.
4
4
 
5
- ## Status
5
+ The package keeps the fluent `useShortcut()` API, but it is now documented as explicit entrypoints so consumers can choose the narrowest surface that fits their use case.
6
6
 
7
- - Focus right now: runtime architecture and DX refinement
8
- - Documentation scope: feature/status overview only (full API docs will be expanded later)
7
+ ## Entrypoints
9
8
 
10
- ## Implemented Features
9
+ - `@remcostoeten/use-shortcut`
10
+ Full compatibility barrel.
11
+ - `@remcostoeten/use-shortcut/react`
12
+ Recommended React entrypoint.
13
+ - `@remcostoeten/use-shortcut/parser`
14
+ Parser and matcher utilities.
15
+ - `@remcostoeten/use-shortcut/formatter`
16
+ Display formatting utilities such as `formatShortcut()` and `getModifierSymbols()`.
17
+ - `@remcostoeten/use-shortcut/constants`
18
+ Platform and normalization constants.
11
19
 
12
- - Chainable shortcut builder: `$.mod.key("k").on(handler)`
13
- - Bulk shortcut maps: `useShortcutMap()` and `registerShortcutMap()`
14
- - Modifier support: `ctrl`, `shift`, `alt`, `cmd`, `mod`
15
- - Sequence support: `$.key("g").then("d")`
16
- - Scope-aware shortcuts:
17
- - Register with `.in("editor")`
18
- - Runtime controls: `setScopes`, `enableScope`, `disableScope`, `getScopes`, `isScopeActive`
19
- - Exception predicates/presets with `.except(...)`
20
- - Recording mode: `$.record({ timeoutMs })`
21
- - Structured debug stream: `$.onDebug(...)` for every keypress
22
- - Per-shortcut attempt inspection: `result.onAttempt((matched, event, details) => ...)`
23
- - Conflict detection (`exact`, `sequence-prefix`)
24
- - Priority ordering and `stopOnMatch`
25
- - Global guard/filter support via `eventFilter`
26
- - React entry point:
27
- - `useShortcut`
28
- - `useShortcutMap`
29
- - `useShortcutGroup`
20
+ This package is for React and Next.js apps. If you are building on that stack, prefer `@remcostoeten/use-shortcut/react`.
30
21
 
31
- ## API Intention (Consumer-Facing)
22
+ ## Size
23
+
24
+ Measured in this package on March 12, 2026:
25
+
26
+ - root published ESM build: about `16.5 kB` minified
27
+ - app bundle for `useShortcut` only: about `13.8 kB` minified
28
+ - gzip for the React hook path: about `5.3 kB`
29
+
30
+ That means the runtime is already small in practice. The entrypoint split mainly prevents accidental convenience-barrel imports and makes the architecture explicit.
31
+
32
+ ## React API
33
+
34
+ The public runtime API is React-only. Parser, formatter, and constants exports are supporting utilities inside the same React/Next.js package, not a separate framework-agnostic runtime.
35
+
36
+ ```tsx
37
+ import { useShortcut } from "@remcostoeten/use-shortcut/react"
38
+
39
+ function App() {
40
+ const $ = useShortcut()
41
+
42
+ $.mod.key("k").on(() => openPalette(), { preventDefault: true })
43
+ $.bind("mod+p").on(() => openProjects())
44
+ $.key("escape").on(() => closePalette())
45
+
46
+ return <div>Press Cmd/Ctrl+K</div>
47
+ }
48
+ ```
49
+
50
+ Main React exports:
32
51
 
33
52
  - `useShortcut(options?)`
34
- - Main React hook. Use this for the chainable API (`$.mod.key("s").on(...)`).
35
53
  - `useShortcutMap(shortcutMap, options?)`
36
- - React-safe bulk registration for render paths where a declarative object is cleaner than multiple `.on()` calls.
37
54
  - `registerShortcutMap(builder, shortcutMap)`
38
- - Imperative bulk registration helper when you already have a `useShortcut()` builder.
55
+ - `createShortcutGroup()`
56
+ - `useShortcutGroup()`
57
+
58
+ ## Bound combo example
59
+
60
+ Use `.bind()` when your shortcuts already exist as strings in config or user
61
+ settings.
62
+
63
+ ```tsx
64
+ import { useShortcut } from "@remcostoeten/use-shortcut/react"
65
+
66
+ const appShortcuts = {
67
+ openCommandPalette: {
68
+ combo: "mod+k",
69
+ description: "Open command palette",
70
+ },
71
+ closeDialog: {
72
+ combo: ["escape", "mod+d"],
73
+ description: "Close dialog",
74
+ },
75
+ }
76
+
77
+ function App() {
78
+ const $ = useShortcut()
79
+
80
+ $.bind(appShortcuts.openCommandPalette.combo).on(() => {
81
+ openPalette()
82
+ }, {
83
+ description: appShortcuts.openCommandPalette.description,
84
+ preventDefault: true,
85
+ })
86
+
87
+ $.bind(appShortcuts.closeDialog.combo).on(() => {
88
+ closeDialog()
89
+ }, {
90
+ description: appShortcuts.closeDialog.description,
91
+ })
39
92
 
40
- Internal helpers follow underscore naming (for example `_createShortcutBuilder`, `_canonicalizeParsed`) and are not re-exported from `src/index.ts`.
93
+ return <div>Shortcuts ready</div>
94
+ }
95
+ ```
96
+
97
+ ## Features
98
+
99
+ - Chainable shortcut builder: `$.mod.key("k").on(handler)`
100
+ - Pre-bound combos with `$.bind("mod+k").on(handler)`
101
+ - Bulk shortcut maps: `useShortcutMap()` and `registerShortcutMap()`
102
+ - Modifier support: `ctrl`, `shift`, `alt`, `cmd`, `mod`
103
+ - Sequence support: `$.key("g").then("d")`
104
+ - Scope-aware shortcuts with `.in(...)`, `setScopes`, `enableScope`, `disableScope`
105
+ - Exception predicates and presets with `.except(...)`
106
+ - Recording mode with `$.record({ timeoutMs })`
107
+ - Structured debug stream with `$.onDebug(...)`
108
+ - Per-shortcut attempt inspection with `result.onAttempt(...)`
109
+ - Conflict detection for exact and sequence-prefix overlaps
110
+ - Priority ordering and `stopOnMatch`
111
+ - Global guard/filter support via `eventFilter`
41
112
 
42
113
  ## Shortcut Map Example
43
114
 
44
115
  ```tsx
45
- import { useShortcutMap } from "@remcostoeten/use-shortcut"
116
+ import { useShortcutMap } from "@remcostoeten/use-shortcut/react"
46
117
 
47
118
  function App() {
48
119
  useShortcutMap(
@@ -68,11 +139,11 @@ function App() {
68
139
  }
69
140
  ```
70
141
 
71
- If you already have a builder from `useShortcut()`, you can bulk register with `registerShortcutMap($, shortcutMap)` and unbind the returned handles on cleanup.
72
-
73
142
  ## Debug Example
74
143
 
75
144
  ```tsx
145
+ import { useShortcut } from "@remcostoeten/use-shortcut/react"
146
+
76
147
  const $ = useShortcut({
77
148
  debug: {
78
149
  console: true,
@@ -82,7 +153,7 @@ const $ = useShortcut({
82
153
  },
83
154
  })
84
155
 
85
- const unsubscribeDebug = $.onDebug((event) => {
156
+ const removeDebug = $.onDebug((event) => {
86
157
  console.log("key", event.input.combo, event.attempts)
87
158
  })
88
159
 
@@ -90,18 +161,27 @@ const result = $.shift.key("e").then("e").on(runProbe, {
90
161
  description: "sequence probe",
91
162
  })
92
163
 
93
- const unsubscribeAttempt = result.onAttempt?.((matched, _event, details) => {
164
+ const removeAttempt = result.onAttempt?.((matched, _event, details) => {
94
165
  console.log(matched ? "matched" : details?.status, details?.steps)
95
166
  })
96
167
  ```
97
168
 
98
- ## Architecture Notes
99
-
100
- - Core runtime lives in `src/builder.ts`
101
- - Parsing/formatting are isolated in `src/parser.ts` and `src/formatter.ts`
102
- - React bindings and map helpers live in `src/hook.ts`
103
- - Type contracts live in `src/types.ts`
104
- - CLI scaffold/copy commands live under `cli/`
169
+ ## Architecture
170
+
171
+ - `src/builder.ts`
172
+ Chainable builder runtime and registration plumbing.
173
+ - `src/runtime/*`
174
+ Listener attachment, matching, conflicts, guards, recording, and debug internals.
175
+ - `src/hook.ts`
176
+ React integration and bulk registration helpers.
177
+ - `src/react.ts`
178
+ Narrow React entrypoint for hook consumers.
179
+ - `src/parser.ts`, `src/formatter.ts`, `src/constants.ts`
180
+ Standalone utility entrypoints.
181
+ - `src/index.ts`
182
+ Full compatibility barrel.
183
+
184
+ The API design keeps the fluent React path front-and-center while still exposing low-level parser and formatter utilities when needed.
105
185
 
106
186
  ## Development
107
187
 
@@ -0,0 +1,36 @@
1
+ /** Supported runtime OS identifiers used by formatter and parser normalization. */
2
+ declare const OS: {
3
+ readonly MAC: "mac";
4
+ readonly WINDOWS: "windows";
5
+ readonly LINUX: "linux";
6
+ };
7
+ type PlatformType = (typeof OS)[keyof typeof OS];
8
+ /** Public platform constant alias (`Platform.MAC`, `Platform.WINDOWS`, `Platform.LINUX`). */
9
+ declare const Platform: {
10
+ readonly MAC: "mac";
11
+ readonly WINDOWS: "windows";
12
+ readonly LINUX: "linux";
13
+ };
14
+ /**
15
+ * Detect the current OS platform for modifier normalization and display formatting.
16
+ * Result is memoized for the page lifecycle.
17
+ */
18
+ declare function detectPlatform(): PlatformType;
19
+ /** Canonical modifier token names used internally across parsing/formatting. */
20
+ declare const ModifierKey: {
21
+ readonly META: "meta";
22
+ readonly CTRL: "ctrl";
23
+ readonly ALT: "alt";
24
+ readonly SHIFT: "shift";
25
+ };
26
+ type ModifierKeyType = (typeof ModifierKey)[keyof typeof ModifierKey];
27
+ /** Alias map from user-facing modifier tokens to canonical modifier keys. */
28
+ declare const ModifierAliases: Record<string, ModifierKeyType>;
29
+ /** Alias map from human shortcut key tokens to `KeyboardEvent.key`-compatible values. */
30
+ declare const SpecialKeyMap: Record<string, string>;
31
+ /** Platform-specific display labels/symbols for modifier keys. */
32
+ declare const ModifierDisplaySymbols: Record<PlatformType, Record<ModifierKeyType, string>>;
33
+ /** Platform-specific canonical order for modifier rendering and combo normalization. */
34
+ declare const ModifierDisplayOrder: Record<PlatformType, ModifierKeyType[]>;
35
+
36
+ export { ModifierAliases, ModifierDisplayOrder, ModifierDisplaySymbols, ModifierKey, type ModifierKeyType, OS, Platform, type PlatformType, SpecialKeyMap, detectPlatform };
@@ -0,0 +1 @@
1
+ 'use strict';var o={MAC:"mac",WINDOWS:"windows",LINUX:"linux"},n=o,t=null;function i(){if(t)return t;if(typeof navigator>"u")return t=o.WINDOWS,t;let r=(navigator.userAgentData?.platform?.toLowerCase()??navigator.platform??navigator.userAgent??"").toLowerCase();return r.includes("mac")||r.includes("iphone")||r.includes("ipad")||r.includes("ipod")?(t=o.MAC,t):r.includes("linux")||r.includes("android")?(t=o.LINUX,t):(r.includes("win"),t=o.WINDOWS,t)}var e={META:"meta",CTRL:"ctrl",ALT:"alt",SHIFT:"shift"},s={command:e.META,cmd:e.META,"\u2318":e.META,meta:e.META,win:e.META,windows:e.META,super:e.META,mod:e.META,control:e.CTRL,ctrl:e.CTRL,"\u2303":e.CTRL,ctl:e.CTRL,alt:e.ALT,option:e.ALT,opt:e.ALT,"\u2325":e.ALT,shift:e.SHIFT,"\u21E7":e.SHIFT,shft:e.SHIFT},f={up:"ArrowUp",down:"ArrowDown",left:"ArrowLeft",right:"ArrowRight",home:"Home",end:"End",pageup:"PageUp",pagedown:"PageDown",enter:"Enter",return:"Enter",space:" ",spacebar:" ",tab:"Tab",backspace:"Backspace",delete:"Delete",del:"Delete",escape:"Escape",esc:"Escape",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",plus:"+",minus:"-",comma:",",period:".",slash:"/",backslash:"\\",bracket:"[",closebracket:"]"},T={[o.MAC]:{[e.META]:"\u2318",[e.CTRL]:"\u2303",[e.ALT]:"\u2325",[e.SHIFT]:"\u21E7"},[o.WINDOWS]:{[e.META]:"Ctrl",[e.CTRL]:"Ctrl",[e.ALT]:"Alt",[e.SHIFT]:"Shift"},[o.LINUX]:{[e.META]:"Super",[e.CTRL]:"Ctrl",[e.ALT]:"Alt",[e.SHIFT]:"Shift"}},p={[o.MAC]:[e.CTRL,e.ALT,e.SHIFT,e.META],[o.WINDOWS]:[e.META,e.ALT,e.SHIFT,e.CTRL],[o.LINUX]:[e.META,e.ALT,e.SHIFT,e.CTRL]};exports.ModifierAliases=s;exports.ModifierDisplayOrder=p;exports.ModifierDisplaySymbols=T;exports.ModifierKey=e;exports.OS=o;exports.Platform=n;exports.SpecialKeyMap=f;exports.detectPlatform=i;
@@ -0,0 +1 @@
1
+ var o={MAC:"mac",WINDOWS:"windows",LINUX:"linux"},n=o,t=null;function i(){if(t)return t;if(typeof navigator>"u")return t=o.WINDOWS,t;let r=(navigator.userAgentData?.platform?.toLowerCase()??navigator.platform??navigator.userAgent??"").toLowerCase();return r.includes("mac")||r.includes("iphone")||r.includes("ipad")||r.includes("ipod")?(t=o.MAC,t):r.includes("linux")||r.includes("android")?(t=o.LINUX,t):(r.includes("win"),t=o.WINDOWS,t)}var e={META:"meta",CTRL:"ctrl",ALT:"alt",SHIFT:"shift"},s={command:e.META,cmd:e.META,"\u2318":e.META,meta:e.META,win:e.META,windows:e.META,super:e.META,mod:e.META,control:e.CTRL,ctrl:e.CTRL,"\u2303":e.CTRL,ctl:e.CTRL,alt:e.ALT,option:e.ALT,opt:e.ALT,"\u2325":e.ALT,shift:e.SHIFT,"\u21E7":e.SHIFT,shft:e.SHIFT},f={up:"ArrowUp",down:"ArrowDown",left:"ArrowLeft",right:"ArrowRight",home:"Home",end:"End",pageup:"PageUp",pagedown:"PageDown",enter:"Enter",return:"Enter",space:" ",spacebar:" ",tab:"Tab",backspace:"Backspace",delete:"Delete",del:"Delete",escape:"Escape",esc:"Escape",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",plus:"+",minus:"-",comma:",",period:".",slash:"/",backslash:"\\",bracket:"[",closebracket:"]"},T={[o.MAC]:{[e.META]:"\u2318",[e.CTRL]:"\u2303",[e.ALT]:"\u2325",[e.SHIFT]:"\u21E7"},[o.WINDOWS]:{[e.META]:"Ctrl",[e.CTRL]:"Ctrl",[e.ALT]:"Alt",[e.SHIFT]:"Shift"},[o.LINUX]:{[e.META]:"Super",[e.CTRL]:"Ctrl",[e.ALT]:"Alt",[e.SHIFT]:"Shift"}},p={[o.MAC]:[e.CTRL,e.ALT,e.SHIFT,e.META],[o.WINDOWS]:[e.META,e.ALT,e.SHIFT,e.CTRL],[o.LINUX]:[e.META,e.ALT,e.SHIFT,e.CTRL]};export{s as ModifierAliases,p as ModifierDisplayOrder,T as ModifierDisplaySymbols,e as ModifierKey,o as OS,n as Platform,f as SpecialKeyMap,i as detectPlatform};
@@ -0,0 +1,30 @@
1
+ import { PlatformType, ModifierKeyType } from './constants.js';
2
+
3
+ /**
4
+ * Format a shortcut string for display with platform-aware symbols
5
+ *
6
+ * @param shortcut - Shortcut string (e.g., "cmd+s")
7
+ * @param platform - Optional platform override (default: auto-detect)
8
+ * @returns Formatted display string (e.g., "⌘S" on Mac, "Ctrl+S" on Windows)
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * formatShortcut("cmd+s") // "⌘S" on Mac, "Ctrl+S" on Windows
13
+ * formatShortcut("ctrl+shift+p", "mac") // "⌃⇧P"
14
+ * ```
15
+ */
16
+ declare function formatShortcut(shortcut: string, platform?: PlatformType): string;
17
+ /**
18
+ * Get the modifier key symbols for a platform
19
+ *
20
+ * @param platform - Optional platform override (default: auto-detect)
21
+ * @returns Object mapping modifier keys to display symbols
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * getModifierSymbols("mac") // { meta: "⌘", ctrl: "⌃", alt: "⌥", shift: "⇧" }
26
+ * ```
27
+ */
28
+ declare function getModifierSymbols(platform?: PlatformType): Record<ModifierKeyType, string>;
29
+
30
+ export { formatShortcut, getModifierSymbols };
@@ -0,0 +1 @@
1
+ 'use strict';var r={MAC:"mac",WINDOWS:"windows",LINUX:"linux"},T=r,o=null;function p(){if(o)return o;if(typeof navigator>"u")return o=r.WINDOWS,o;let t=(navigator.userAgentData?.platform?.toLowerCase()??navigator.platform??navigator.userAgent??"").toLowerCase();return t.includes("mac")||t.includes("iphone")||t.includes("ipad")||t.includes("ipod")?(o=r.MAC,o):t.includes("linux")||t.includes("android")?(o=r.LINUX,o):(t.includes("win"),o=r.WINDOWS,o)}var e={META:"meta",CTRL:"ctrl",ALT:"alt",SHIFT:"shift"},u={command:e.META,cmd:e.META,"\u2318":e.META,meta:e.META,win:e.META,windows:e.META,super:e.META,mod:e.META,control:e.CTRL,ctrl:e.CTRL,"\u2303":e.CTRL,ctl:e.CTRL,alt:e.ALT,option:e.ALT,opt:e.ALT,"\u2325":e.ALT,shift:e.SHIFT,"\u21E7":e.SHIFT,shft:e.SHIFT},A={up:"ArrowUp",down:"ArrowDown",left:"ArrowLeft",right:"ArrowRight",home:"Home",end:"End",pageup:"PageUp",pagedown:"PageDown",enter:"Enter",return:"Enter",space:" ",spacebar:" ",tab:"Tab",backspace:"Backspace",delete:"Delete",del:"Delete",escape:"Escape",esc:"Escape",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",plus:"+",minus:"-",comma:",",period:".",slash:"/",backslash:"\\",bracket:"[",closebracket:"]"},m={[r.MAC]:{[e.META]:"\u2318",[e.CTRL]:"\u2303",[e.ALT]:"\u2325",[e.SHIFT]:"\u21E7"},[r.WINDOWS]:{[e.META]:"Ctrl",[e.CTRL]:"Ctrl",[e.ALT]:"Alt",[e.SHIFT]:"Shift"},[r.LINUX]:{[e.META]:"Super",[e.CTRL]:"Ctrl",[e.ALT]:"Alt",[e.SHIFT]:"Shift"}},M={[r.MAC]:[e.CTRL,e.ALT,e.SHIFT,e.META],[r.WINDOWS]:[e.META,e.ALT,e.SHIFT,e.CTRL],[r.LINUX]:[e.META,e.ALT,e.SHIFT,e.CTRL]};function S(a){let t=p(),n=a.toLowerCase().trim().split(/[\s+-]+/).filter(Boolean);if(n.length===0)throw new Error(`Invalid shortcut: "${a}"`);let c={meta:false,ctrl:false,alt:false,shift:false},f=n.pop();for(let l of n){let d=u[l];d?l==="mod"?t===T.MAC?c.meta=true:c.ctrl=true:c[d]=true:f=l+f;}let s=A[f]||f;return {modifiers:c,key:s.length===1?s.toLowerCase():s,original:a}}var g={ArrowUp:"\u2191",ArrowDown:"\u2193",ArrowLeft:"\u2190",ArrowRight:"\u2192",Home:"Home",End:"End",PageUp:"PgUp",PageDown:"PgDn"},h={...g,Enter:"\u21A9",Tab:"\u21E5",Escape:"\u238B",Backspace:"\u232B",Delete:"\u2326"," ":"\u2423"},E={...g,Enter:"Enter",Tab:"Tab",Escape:"Esc",Backspace:"Backspace",Delete:"Del"," ":"Space"};function K(a,t){let i=t??p(),n=S(a),c=m[i],f=M[i],s=[];for(let y of f)n.modifiers[y]&&s.push(c[y]);let l=L(n.key,i);s.push(l);let d=i===r.MAC?"":"+";return s.join(d)}function L(a,t){return (t===r.MAC?h:E)[a]||a.toUpperCase()}function F(a){let t=a??p();return m[t]}exports.formatShortcut=K;exports.getModifierSymbols=F;
@@ -0,0 +1 @@
1
+ var r={MAC:"mac",WINDOWS:"windows",LINUX:"linux"},T=r,o=null;function p(){if(o)return o;if(typeof navigator>"u")return o=r.WINDOWS,o;let t=(navigator.userAgentData?.platform?.toLowerCase()??navigator.platform??navigator.userAgent??"").toLowerCase();return t.includes("mac")||t.includes("iphone")||t.includes("ipad")||t.includes("ipod")?(o=r.MAC,o):t.includes("linux")||t.includes("android")?(o=r.LINUX,o):(t.includes("win"),o=r.WINDOWS,o)}var e={META:"meta",CTRL:"ctrl",ALT:"alt",SHIFT:"shift"},u={command:e.META,cmd:e.META,"\u2318":e.META,meta:e.META,win:e.META,windows:e.META,super:e.META,mod:e.META,control:e.CTRL,ctrl:e.CTRL,"\u2303":e.CTRL,ctl:e.CTRL,alt:e.ALT,option:e.ALT,opt:e.ALT,"\u2325":e.ALT,shift:e.SHIFT,"\u21E7":e.SHIFT,shft:e.SHIFT},A={up:"ArrowUp",down:"ArrowDown",left:"ArrowLeft",right:"ArrowRight",home:"Home",end:"End",pageup:"PageUp",pagedown:"PageDown",enter:"Enter",return:"Enter",space:" ",spacebar:" ",tab:"Tab",backspace:"Backspace",delete:"Delete",del:"Delete",escape:"Escape",esc:"Escape",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",plus:"+",minus:"-",comma:",",period:".",slash:"/",backslash:"\\",bracket:"[",closebracket:"]"},m={[r.MAC]:{[e.META]:"\u2318",[e.CTRL]:"\u2303",[e.ALT]:"\u2325",[e.SHIFT]:"\u21E7"},[r.WINDOWS]:{[e.META]:"Ctrl",[e.CTRL]:"Ctrl",[e.ALT]:"Alt",[e.SHIFT]:"Shift"},[r.LINUX]:{[e.META]:"Super",[e.CTRL]:"Ctrl",[e.ALT]:"Alt",[e.SHIFT]:"Shift"}},M={[r.MAC]:[e.CTRL,e.ALT,e.SHIFT,e.META],[r.WINDOWS]:[e.META,e.ALT,e.SHIFT,e.CTRL],[r.LINUX]:[e.META,e.ALT,e.SHIFT,e.CTRL]};function S(a){let t=p(),n=a.toLowerCase().trim().split(/[\s+-]+/).filter(Boolean);if(n.length===0)throw new Error(`Invalid shortcut: "${a}"`);let c={meta:false,ctrl:false,alt:false,shift:false},f=n.pop();for(let l of n){let d=u[l];d?l==="mod"?t===T.MAC?c.meta=true:c.ctrl=true:c[d]=true:f=l+f;}let s=A[f]||f;return {modifiers:c,key:s.length===1?s.toLowerCase():s,original:a}}var g={ArrowUp:"\u2191",ArrowDown:"\u2193",ArrowLeft:"\u2190",ArrowRight:"\u2192",Home:"Home",End:"End",PageUp:"PgUp",PageDown:"PgDn"},h={...g,Enter:"\u21A9",Tab:"\u21E5",Escape:"\u238B",Backspace:"\u232B",Delete:"\u2326"," ":"\u2423"},E={...g,Enter:"Enter",Tab:"Tab",Escape:"Esc",Backspace:"Backspace",Delete:"Del"," ":"Space"};function K(a,t){let i=t??p(),n=S(a),c=m[i],f=M[i],s=[];for(let y of f)n.modifiers[y]&&s.push(c[y]);let l=L(n.key,i);s.push(l);let d=i===r.MAC?"":"+";return s.join(d)}function L(a,t){return (t===r.MAC?h:E)[a]||a.toUpperCase()}function F(a){let t=a??p();return m[t]}export{K as formatShortcut,F as getModifierSymbols};