@remcostoeten/use-shortcut 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,46 +1,78 @@
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
- - Conflict detection (`exact`, `sequence-prefix`)
22
- - Priority ordering and `stopOnMatch`
23
- - Global guard/filter support via `eventFilter`
24
- - React entry point:
25
- - `useShortcut`
26
- - `useShortcutMap`
27
- - `useShortcutGroup`
20
+ This package is for React and Next.js apps. If you are building on that stack, prefer `@remcostoeten/use-shortcut/react`.
21
+
22
+ ## Size
23
+
24
+ Measured in this package on March 12, 2026:
28
25
 
29
- ## API Intention (Consumer-Facing)
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
+ $.key("escape").on(() => closePalette())
44
+
45
+ return <div>Press Cmd/Ctrl+K</div>
46
+ }
47
+ ```
48
+
49
+ Main React exports:
30
50
 
31
51
  - `useShortcut(options?)`
32
- - Main React hook. Use this for the chainable API (`$.mod.key("s").on(...)`).
33
52
  - `useShortcutMap(shortcutMap, options?)`
34
- - React-safe bulk registration for render paths where a declarative object is cleaner than multiple `.on()` calls.
35
53
  - `registerShortcutMap(builder, shortcutMap)`
36
- - Imperative bulk registration helper when you already have a `useShortcut()` builder.
54
+ - `createShortcutGroup()`
55
+ - `useShortcutGroup()`
56
+
57
+ ## Features
37
58
 
38
- Internal helpers follow underscore naming (for example `_createShortcutBuilder`, `_canonicalizeParsed`) and are not re-exported from `src/index.ts`.
59
+ - Chainable shortcut builder: `$.mod.key("k").on(handler)`
60
+ - Bulk shortcut maps: `useShortcutMap()` and `registerShortcutMap()`
61
+ - Modifier support: `ctrl`, `shift`, `alt`, `cmd`, `mod`
62
+ - Sequence support: `$.key("g").then("d")`
63
+ - Scope-aware shortcuts with `.in(...)`, `setScopes`, `enableScope`, `disableScope`
64
+ - Exception predicates and presets with `.except(...)`
65
+ - Recording mode with `$.record({ timeoutMs })`
66
+ - Structured debug stream with `$.onDebug(...)`
67
+ - Per-shortcut attempt inspection with `result.onAttempt(...)`
68
+ - Conflict detection for exact and sequence-prefix overlaps
69
+ - Priority ordering and `stopOnMatch`
70
+ - Global guard/filter support via `eventFilter`
39
71
 
40
72
  ## Shortcut Map Example
41
73
 
42
74
  ```tsx
43
- import { useShortcutMap } from "@remcostoeten/use-shortcut"
75
+ import { useShortcutMap } from "@remcostoeten/use-shortcut/react"
44
76
 
45
77
  function App() {
46
78
  useShortcutMap(
@@ -66,15 +98,49 @@ function App() {
66
98
  }
67
99
  ```
68
100
 
69
- If you already have a builder from `useShortcut()`, you can bulk register with `registerShortcutMap($, shortcutMap)` and unbind the returned handles on cleanup.
101
+ ## Debug Example
70
102
 
71
- ## Architecture Notes
103
+ ```tsx
104
+ import { useShortcut } from "@remcostoeten/use-shortcut/react"
105
+
106
+ const $ = useShortcut({
107
+ debug: {
108
+ console: true,
109
+ includeCode: true,
110
+ includeLocation: true,
111
+ includeKeyCode: true,
112
+ },
113
+ })
114
+
115
+ const removeDebug = $.onDebug((event) => {
116
+ console.log("key", event.input.combo, event.attempts)
117
+ })
118
+
119
+ const result = $.shift.key("e").then("e").on(runProbe, {
120
+ description: "sequence probe",
121
+ })
122
+
123
+ const removeAttempt = result.onAttempt?.((matched, _event, details) => {
124
+ console.log(matched ? "matched" : details?.status, details?.steps)
125
+ })
126
+ ```
72
127
 
73
- - Core runtime lives in `src/builder.ts`
74
- - Parsing/formatting are isolated in `src/parser.ts` and `src/formatter.ts`
75
- - React bindings and map helpers live in `src/hook.ts`
76
- - Type contracts live in `src/types.ts`
77
- - CLI scaffold/copy commands live under `cli/`
128
+ ## Architecture
129
+
130
+ - `src/builder.ts`
131
+ Chainable builder runtime and registration plumbing.
132
+ - `src/runtime/*`
133
+ Listener attachment, matching, conflicts, guards, recording, and debug internals.
134
+ - `src/hook.ts`
135
+ React integration and bulk registration helpers.
136
+ - `src/react.ts`
137
+ Narrow React entrypoint for hook consumers.
138
+ - `src/parser.ts`, `src/formatter.ts`, `src/constants.ts`
139
+ Standalone utility entrypoints.
140
+ - `src/index.ts`
141
+ Full compatibility barrel.
142
+
143
+ The API design keeps the fluent React path front-and-center while still exposing low-level parser and formatter utilities when needed.
78
144
 
79
145
  ## Development
80
146
 
@@ -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};