@tanstack/hotkeys 0.8.0 → 0.9.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 +15 -1
- package/dist/{manager.utils.js → _event-target.cjs} +5 -41
- package/dist/_event-target.cjs.map +1 -0
- package/dist/{manager.utils.cjs → _event-target.js} +3 -47
- package/dist/_event-target.js.map +1 -0
- package/dist/_keyboard-event.cjs +83 -0
- package/dist/_keyboard-event.cjs.map +1 -0
- package/dist/_keyboard-event.js +76 -0
- package/dist/_keyboard-event.js.map +1 -0
- package/dist/_match.cjs +49 -0
- package/dist/_match.cjs.map +1 -0
- package/dist/_match.js +49 -0
- package/dist/_match.js.map +1 -0
- package/dist/_named-keys.cjs +68 -0
- package/dist/_named-keys.cjs.map +1 -0
- package/dist/_named-keys.d.cts +8 -0
- package/dist/_named-keys.d.ts +8 -0
- package/dist/_named-keys.js +66 -0
- package/dist/_named-keys.js.map +1 -0
- package/dist/_recorder-chord.cjs +44 -0
- package/dist/_recorder-chord.cjs.map +1 -0
- package/dist/_recorder-chord.js +43 -0
- package/dist/_recorder-chord.js.map +1 -0
- package/dist/_recording-guard.cjs +59 -0
- package/dist/_recording-guard.cjs.map +1 -0
- package/dist/_recording-guard.js +56 -0
- package/dist/_recording-guard.js.map +1 -0
- package/dist/_registration.cjs +54 -0
- package/dist/_registration.cjs.map +1 -0
- package/dist/_registration.js +50 -0
- package/dist/_registration.js.map +1 -0
- package/dist/conflicts.cjs +55 -0
- package/dist/conflicts.cjs.map +1 -0
- package/dist/conflicts.d.cts +39 -0
- package/dist/conflicts.d.ts +39 -0
- package/dist/conflicts.js +55 -0
- package/dist/conflicts.js.map +1 -0
- package/dist/constants.cjs +46 -171
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +4 -129
- package/dist/constants.d.ts +4 -129
- package/dist/constants.js +48 -164
- package/dist/constants.js.map +1 -1
- package/dist/display-labels.cjs +119 -0
- package/dist/display-labels.cjs.map +1 -0
- package/dist/display-labels.d.cts +93 -0
- package/dist/display-labels.d.ts +93 -0
- package/dist/display-labels.js +113 -0
- package/dist/display-labels.js.map +1 -0
- package/dist/format.cjs +30 -36
- package/dist/format.cjs.map +1 -1
- package/dist/format.d.cts +46 -7
- package/dist/format.d.ts +46 -7
- package/dist/format.js +31 -37
- package/dist/format.js.map +1 -1
- package/dist/hint.cjs +24 -0
- package/dist/hint.cjs.map +1 -0
- package/dist/hint.d.cts +23 -0
- package/dist/hint.d.ts +23 -0
- package/dist/hint.js +24 -0
- package/dist/hint.js.map +1 -0
- package/dist/hotkey-manager.cjs +53 -22
- package/dist/hotkey-manager.cjs.map +1 -1
- package/dist/hotkey-manager.d.cts +6 -2
- package/dist/hotkey-manager.d.ts +6 -2
- package/dist/hotkey-manager.js +49 -18
- package/dist/hotkey-manager.js.map +1 -1
- package/dist/hotkey-recorder.cjs +66 -10
- package/dist/hotkey-recorder.cjs.map +1 -1
- package/dist/hotkey-recorder.d.cts +5 -2
- package/dist/hotkey-recorder.d.ts +5 -2
- package/dist/hotkey-recorder.js +65 -9
- package/dist/hotkey-recorder.js.map +1 -1
- package/dist/hotkey-sequence-recorder.cjs +85 -22
- package/dist/hotkey-sequence-recorder.cjs.map +1 -1
- package/dist/hotkey-sequence-recorder.d.cts +9 -2
- package/dist/hotkey-sequence-recorder.d.ts +9 -2
- package/dist/hotkey-sequence-recorder.js +84 -21
- package/dist/hotkey-sequence-recorder.js.map +1 -1
- package/dist/{hotkey.d.ts → hotkey.types.d.cts} +55 -120
- package/dist/{hotkey.d.cts → hotkey.types.d.ts} +55 -120
- package/dist/index.cjs +18 -10
- package/dist/index.d.cts +14 -9
- package/dist/index.d.ts +14 -9
- package/dist/index.js +10 -6
- package/dist/key-state-tracker.cjs +24 -24
- package/dist/key-state-tracker.cjs.map +1 -1
- package/dist/key-state-tracker.js +24 -24
- package/dist/key-state-tracker.js.map +1 -1
- package/dist/key.types.d.cts +82 -0
- package/dist/key.types.d.ts +82 -0
- package/dist/match.cjs +21 -30
- package/dist/match.cjs.map +1 -1
- package/dist/match.d.cts +19 -3
- package/dist/match.d.ts +19 -3
- package/dist/match.js +19 -29
- package/dist/match.js.map +1 -1
- package/dist/parse.cjs +43 -26
- package/dist/parse.cjs.map +1 -1
- package/dist/parse.d.cts +10 -4
- package/dist/parse.d.ts +10 -4
- package/dist/parse.js +35 -19
- package/dist/parse.js.map +1 -1
- package/dist/platform.cjs +55 -0
- package/dist/platform.cjs.map +1 -0
- package/dist/platform.d.cts +44 -0
- package/dist/platform.d.ts +44 -0
- package/dist/platform.js +53 -0
- package/dist/platform.js.map +1 -0
- package/dist/recorder-options.d.cts +34 -0
- package/dist/recorder-options.d.ts +34 -0
- package/dist/sequence-manager.cjs +54 -28
- package/dist/sequence-manager.cjs.map +1 -1
- package/dist/sequence-manager.d.cts +18 -3
- package/dist/sequence-manager.d.ts +18 -3
- package/dist/sequence-manager.js +49 -23
- package/dist/sequence-manager.js.map +1 -1
- package/dist/validate.cjs +6 -10
- package/dist/validate.cjs.map +1 -1
- package/dist/validate.d.cts +13 -2
- package/dist/validate.d.ts +13 -2
- package/dist/validate.js +7 -11
- package/dist/validate.js.map +1 -1
- package/package.json +1 -1
- package/src/{manager.utils.ts → _event-target.ts} +0 -74
- package/src/_keyboard-event.ts +101 -0
- package/src/_match.ts +96 -0
- package/src/_named-keys.ts +62 -0
- package/src/_recorder-chord.ts +76 -0
- package/src/_recording-guard.ts +63 -0
- package/src/_registration.ts +79 -0
- package/src/conflicts.ts +125 -0
- package/src/constants.ts +48 -196
- package/src/display-labels.ts +123 -0
- package/src/format.ts +111 -52
- package/src/hint.ts +42 -0
- package/src/hotkey-manager.ts +95 -40
- package/src/hotkey-recorder.ts +91 -15
- package/src/hotkey-sequence-recorder.ts +110 -28
- package/src/{hotkey.ts → hotkey.types.ts} +57 -224
- package/src/index.ts +11 -5
- package/src/key-state-tracker.ts +36 -24
- package/src/key.types.ts +268 -0
- package/src/match.ts +41 -90
- package/src/parse.ts +67 -26
- package/src/platform.ts +66 -0
- package/src/recorder-options.ts +34 -0
- package/src/sequence-manager.ts +77 -42
- package/src/validate.ts +29 -21
- package/dist/manager.utils.cjs.map +0 -1
- package/dist/manager.utils.d.cts +0 -13
- package/dist/manager.utils.d.ts +0 -13
- package/dist/manager.utils.js.map +0 -1
- package/dist/recorder-chord.cjs +0 -18
- package/dist/recorder-chord.cjs.map +0 -1
- package/dist/recorder-chord.js +0 -18
- package/dist/recorder-chord.js.map +0 -1
- package/src/recorder-chord.ts +0 -25
package/src/constants.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LOGICAL_ONLY_NAMED_KEYS, SHARED_NAMED_KEYS } from './_named-keys'
|
|
1
2
|
import type {
|
|
2
3
|
CanonicalModifier,
|
|
3
4
|
EditingKey,
|
|
@@ -6,41 +7,7 @@ import type {
|
|
|
6
7
|
NavigationKey,
|
|
7
8
|
NumberKey,
|
|
8
9
|
PunctuationKey,
|
|
9
|
-
} from './
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Detects the current platform based on browser navigator properties.
|
|
13
|
-
*
|
|
14
|
-
* Used internally to resolve platform-adaptive modifiers like 'Mod' (Command on Mac,
|
|
15
|
-
* Control elsewhere) and for platform-specific hotkey formatting.
|
|
16
|
-
*
|
|
17
|
-
* @returns The detected platform: 'mac', 'windows', or 'linux'
|
|
18
|
-
* @remarks Defaults to 'linux' in SSR environments where navigator is undefined
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const platform = detectPlatform() // 'mac' | 'windows' | 'linux'
|
|
23
|
-
* const modifier = resolveModifier('Mod', platform) // 'Meta' on Mac, 'Control' elsewhere
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
export function detectPlatform(): 'mac' | 'windows' | 'linux' {
|
|
27
|
-
if (typeof navigator === 'undefined') {
|
|
28
|
-
return 'linux' // Default for SSR
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
32
|
-
const platform = navigator.platform?.toLowerCase() ?? ''
|
|
33
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
34
|
-
const userAgent = navigator.userAgent?.toLowerCase() ?? ''
|
|
35
|
-
|
|
36
|
-
if (platform.includes('mac') || userAgent.includes('mac')) {
|
|
37
|
-
return 'mac'
|
|
38
|
-
}
|
|
39
|
-
if (platform.includes('win') || userAgent.includes('win')) {
|
|
40
|
-
return 'windows'
|
|
41
|
-
}
|
|
42
|
-
return 'linux'
|
|
43
|
-
}
|
|
10
|
+
} from './key.types'
|
|
44
11
|
|
|
45
12
|
/**
|
|
46
13
|
* Canonical order for modifiers in normalized hotkey strings.
|
|
@@ -129,37 +96,6 @@ export const MODIFIER_ALIASES: Record<string, CanonicalModifier | 'Mod'> = {
|
|
|
129
96
|
mod: 'Mod',
|
|
130
97
|
}
|
|
131
98
|
|
|
132
|
-
/**
|
|
133
|
-
* Resolves the platform-adaptive 'Mod' modifier to the appropriate canonical modifier.
|
|
134
|
-
*
|
|
135
|
-
* The 'Mod' token represents the "primary modifier" on each platform:
|
|
136
|
-
* - macOS: 'Meta' (Command key ⌘)
|
|
137
|
-
* - Windows/Linux: 'Control' (Ctrl key)
|
|
138
|
-
*
|
|
139
|
-
* This enables cross-platform hotkey definitions like 'Mod+S' that automatically
|
|
140
|
-
* map to Command+S on Mac and Ctrl+S on Windows/Linux.
|
|
141
|
-
*
|
|
142
|
-
* @param modifier - The modifier to resolve. If 'Mod', resolves based on platform.
|
|
143
|
-
* @param platform - The target platform. Defaults to auto-detection.
|
|
144
|
-
* @returns The canonical modifier name ('Control', 'Shift', 'Alt', or 'Meta')
|
|
145
|
-
*
|
|
146
|
-
* @example
|
|
147
|
-
* ```ts
|
|
148
|
-
* resolveModifier('Mod', 'mac') // 'Meta'
|
|
149
|
-
* resolveModifier('Mod', 'windows') // 'Control'
|
|
150
|
-
* resolveModifier('Control', 'mac') // 'Control' (unchanged)
|
|
151
|
-
* ```
|
|
152
|
-
*/
|
|
153
|
-
export function resolveModifier(
|
|
154
|
-
modifier: CanonicalModifier | 'Mod',
|
|
155
|
-
platform: 'mac' | 'windows' | 'linux' = detectPlatform(),
|
|
156
|
-
): CanonicalModifier {
|
|
157
|
-
if (modifier === 'Mod') {
|
|
158
|
-
return platform === 'mac' ? 'Meta' : 'Control'
|
|
159
|
-
}
|
|
160
|
-
return modifier
|
|
161
|
-
}
|
|
162
|
-
|
|
163
99
|
/**
|
|
164
100
|
* Set of all valid letter keys (A-Z).
|
|
165
101
|
*
|
|
@@ -216,7 +152,7 @@ export const NUMBER_KEYS = new Set<NumberKey>([
|
|
|
216
152
|
])
|
|
217
153
|
|
|
218
154
|
/**
|
|
219
|
-
* Set of all valid function keys (F1-
|
|
155
|
+
* Set of all valid function keys (F1-F24).
|
|
220
156
|
*
|
|
221
157
|
* Function keys are commonly used for system shortcuts (e.g., F12 for DevTools,
|
|
222
158
|
* Alt+F4 to close windows) and application-specific commands.
|
|
@@ -234,6 +170,18 @@ export const FUNCTION_KEYS = new Set<FunctionKey>([
|
|
|
234
170
|
'F10',
|
|
235
171
|
'F11',
|
|
236
172
|
'F12',
|
|
173
|
+
'F13',
|
|
174
|
+
'F14',
|
|
175
|
+
'F15',
|
|
176
|
+
'F16',
|
|
177
|
+
'F17',
|
|
178
|
+
'F18',
|
|
179
|
+
'F19',
|
|
180
|
+
'F20',
|
|
181
|
+
'F21',
|
|
182
|
+
'F22',
|
|
183
|
+
'F23',
|
|
184
|
+
'F24',
|
|
237
185
|
])
|
|
238
186
|
|
|
239
187
|
/**
|
|
@@ -293,6 +241,28 @@ export const PUNCTUATION_KEYS = new Set<PunctuationKey>([
|
|
|
293
241
|
'.',
|
|
294
242
|
';',
|
|
295
243
|
'`',
|
|
244
|
+
"'",
|
|
245
|
+
'+',
|
|
246
|
+
'?',
|
|
247
|
+
'!',
|
|
248
|
+
'@',
|
|
249
|
+
'#',
|
|
250
|
+
'$',
|
|
251
|
+
'%',
|
|
252
|
+
'^',
|
|
253
|
+
'&',
|
|
254
|
+
'*',
|
|
255
|
+
'(',
|
|
256
|
+
')',
|
|
257
|
+
'_',
|
|
258
|
+
'{',
|
|
259
|
+
'}',
|
|
260
|
+
'|',
|
|
261
|
+
':',
|
|
262
|
+
'"',
|
|
263
|
+
'<',
|
|
264
|
+
'>',
|
|
265
|
+
'~',
|
|
296
266
|
])
|
|
297
267
|
|
|
298
268
|
/**
|
|
@@ -313,6 +283,7 @@ export const PUNCTUATION_CODE_MAP: Record<string, string> = {
|
|
|
313
283
|
Equal: '=',
|
|
314
284
|
Minus: '-',
|
|
315
285
|
Period: '.',
|
|
286
|
+
Quote: "'",
|
|
316
287
|
Semicolon: ';',
|
|
317
288
|
Slash: '/',
|
|
318
289
|
}
|
|
@@ -338,8 +309,15 @@ export const ALL_KEYS = new Set([
|
|
|
338
309
|
...NAVIGATION_KEYS,
|
|
339
310
|
...EDITING_KEYS,
|
|
340
311
|
...PUNCTUATION_KEYS,
|
|
312
|
+
...SHARED_NAMED_KEYS,
|
|
313
|
+
...LOGICAL_ONLY_NAMED_KEYS,
|
|
341
314
|
])
|
|
342
315
|
|
|
316
|
+
// Share canonical spellings with validation, including newly supported named keys.
|
|
317
|
+
const CANONICAL_KEY_NAMES = new Map<string, string>(
|
|
318
|
+
Array.from(ALL_KEYS, (key) => [key.toLowerCase(), key]),
|
|
319
|
+
)
|
|
320
|
+
|
|
343
321
|
/**
|
|
344
322
|
* Maps key name aliases to their canonical form.
|
|
345
323
|
*
|
|
@@ -360,6 +338,8 @@ export const ALL_KEYS = new Set([
|
|
|
360
338
|
* ```
|
|
361
339
|
*/
|
|
362
340
|
const KEY_ALIASES: Record<string, string> = {
|
|
341
|
+
Plus: '+',
|
|
342
|
+
plus: '+',
|
|
363
343
|
// Escape variants
|
|
364
344
|
Esc: 'Escape',
|
|
365
345
|
esc: 'Escape',
|
|
@@ -471,133 +451,5 @@ export function normalizeKeyName(key: string): string {
|
|
|
471
451
|
return upper.length === 1 ? upper : key
|
|
472
452
|
}
|
|
473
453
|
|
|
474
|
-
|
|
475
|
-
const upperKey = key.toUpperCase()
|
|
476
|
-
if (/^F([1-9]|1[0-2])$/.test(upperKey)) {
|
|
477
|
-
return upperKey
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
return key
|
|
454
|
+
return CANONICAL_KEY_NAMES.get(key.toLowerCase()) ?? key
|
|
481
455
|
}
|
|
482
|
-
|
|
483
|
-
// =============================================================================
|
|
484
|
-
// Display Symbols
|
|
485
|
-
// =============================================================================
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Modifier key symbols for macOS display.
|
|
489
|
-
*
|
|
490
|
-
* Used by formatting functions to display hotkeys with macOS-style symbols
|
|
491
|
-
* (e.g., ⌘ for Command, ⌃ for Control) instead of text labels. This provides
|
|
492
|
-
* a native macOS look and feel in hotkey displays.
|
|
493
|
-
*
|
|
494
|
-
* @example
|
|
495
|
-
* ```ts
|
|
496
|
-
* MAC_MODIFIER_SYMBOLS['Meta'] // '⌘'
|
|
497
|
-
* MAC_MODIFIER_SYMBOLS['Control'] // '⌃'
|
|
498
|
-
* MAC_MODIFIER_SYMBOLS['Alt'] // '⌥'
|
|
499
|
-
* MAC_MODIFIER_SYMBOLS['Shift'] // '⇧'
|
|
500
|
-
* ```
|
|
501
|
-
*/
|
|
502
|
-
export const MAC_MODIFIER_SYMBOLS: Record<CanonicalModifier | 'Mod', string> = {
|
|
503
|
-
Control: '⌃',
|
|
504
|
-
Alt: '⌥',
|
|
505
|
-
Shift: '⇧',
|
|
506
|
-
Meta: '⌘',
|
|
507
|
-
Mod: '⌘',
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
/**
|
|
511
|
-
* Modifier key labels for macOS display.
|
|
512
|
-
*
|
|
513
|
-
* Used by formatting functions to display hotkeys with macOS-style text labels
|
|
514
|
-
* (e.g., 'Control' for Control, 'Option' for Alt, 'Cmd' for Meta) instead of symbols.
|
|
515
|
-
* This provides a familiar macOS look and feel in hotkey displays.
|
|
516
|
-
*
|
|
517
|
-
* @example
|
|
518
|
-
* ```ts
|
|
519
|
-
* MAC_MODIFIER_LABELS['Control'] // 'control'
|
|
520
|
-
* MAC_MODIFIER_LABELS['Alt'] // 'option'
|
|
521
|
-
* MAC_MODIFIER_LABELS['Shift'] // 'shift'
|
|
522
|
-
* MAC_MODIFIER_LABELS['Meta'] // 'cmd'
|
|
523
|
-
* ```
|
|
524
|
-
*/
|
|
525
|
-
export const MAC_MODIFIER_LABELS: Record<CanonicalModifier | 'Mod', string> = {
|
|
526
|
-
Control: 'Control',
|
|
527
|
-
Alt: 'Option',
|
|
528
|
-
Shift: 'Shift',
|
|
529
|
-
Meta: 'Cmd',
|
|
530
|
-
Mod: 'Cmd',
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* Modifier key labels for Windows/Linux display.
|
|
535
|
-
*
|
|
536
|
-
* Used by formatting functions to display hotkeys with standard text labels
|
|
537
|
-
* (e.g., 'Ctrl' for Control, 'Win' for Meta/Windows key) instead of symbols.
|
|
538
|
-
* This provides a familiar Windows/Linux look and feel in hotkey displays.
|
|
539
|
-
*
|
|
540
|
-
* @example
|
|
541
|
-
* ```ts
|
|
542
|
-
* STANDARD_MODIFIER_LABELS['Control'] // 'Ctrl'
|
|
543
|
-
* STANDARD_MODIFIER_LABELS['Meta'] // 'Win'
|
|
544
|
-
* STANDARD_MODIFIER_LABELS['Alt'] // 'Alt'
|
|
545
|
-
* STANDARD_MODIFIER_LABELS['Shift'] // 'Shift'
|
|
546
|
-
* ```
|
|
547
|
-
*/
|
|
548
|
-
export const WINDOWS_MODIFIER_LABELS: Record<
|
|
549
|
-
CanonicalModifier | 'Mod',
|
|
550
|
-
string
|
|
551
|
-
> = {
|
|
552
|
-
Control: 'Ctrl',
|
|
553
|
-
Alt: 'Alt',
|
|
554
|
-
Shift: 'Shift',
|
|
555
|
-
Meta: 'Win',
|
|
556
|
-
Mod: 'Ctrl',
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
export const LINUX_MODIFIER_LABELS: Record<CanonicalModifier | 'Mod', string> =
|
|
560
|
-
{
|
|
561
|
-
...WINDOWS_MODIFIER_LABELS,
|
|
562
|
-
Meta: 'Super',
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
export const PUNCTUATION_KEY_DISPLAY_LABELS = {
|
|
566
|
-
'`': 'Backquote',
|
|
567
|
-
'\\': 'Backslash',
|
|
568
|
-
'[': 'Left Bracket',
|
|
569
|
-
']': 'Right Bracket',
|
|
570
|
-
',': 'Comma',
|
|
571
|
-
'=': 'Equal',
|
|
572
|
-
'-': 'Minus',
|
|
573
|
-
'.': 'Period',
|
|
574
|
-
';': 'Semicolon',
|
|
575
|
-
} as const satisfies Record<string, string>
|
|
576
|
-
|
|
577
|
-
/**
|
|
578
|
-
* Special key symbols for display formatting.
|
|
579
|
-
*
|
|
580
|
-
* Maps certain keys to their visual symbols for better readability in hotkey displays.
|
|
581
|
-
* Used by formatting functions to show symbols like ↑ for ArrowUp or ↵ for Enter
|
|
582
|
-
* instead of text labels.
|
|
583
|
-
*
|
|
584
|
-
* @example
|
|
585
|
-
* ```ts
|
|
586
|
-
* KEY_DISPLAY_SYMBOLS['ArrowUp'] // '↑'
|
|
587
|
-
* KEY_DISPLAY_SYMBOLS['Enter'] // '↵'
|
|
588
|
-
* KEY_DISPLAY_SYMBOLS['Escape'] // 'Esc'
|
|
589
|
-
* KEY_DISPLAY_SYMBOLS['Space'] // '␣'
|
|
590
|
-
* ```
|
|
591
|
-
*/
|
|
592
|
-
export const KEY_DISPLAY_SYMBOLS = {
|
|
593
|
-
ArrowUp: '↑',
|
|
594
|
-
ArrowDown: '↓',
|
|
595
|
-
ArrowLeft: '←',
|
|
596
|
-
ArrowRight: '→',
|
|
597
|
-
Enter: '↵',
|
|
598
|
-
Escape: 'Esc',
|
|
599
|
-
Backspace: '⌫',
|
|
600
|
-
Delete: '⌦',
|
|
601
|
-
Tab: '⇥',
|
|
602
|
-
Space: '␣',
|
|
603
|
-
} as const satisfies Record<string, string>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { CanonicalModifier } from './key.types'
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Display Symbols
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Modifier key symbols for macOS display.
|
|
9
|
+
*
|
|
10
|
+
* Used by formatting functions to display hotkeys with macOS-style symbols
|
|
11
|
+
* (e.g., ⌘ for Command, ⌃ for Control) instead of text labels. This provides
|
|
12
|
+
* a native macOS look and feel in hotkey displays.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* MAC_MODIFIER_SYMBOLS['Meta'] // '⌘'
|
|
17
|
+
* MAC_MODIFIER_SYMBOLS['Control'] // '⌃'
|
|
18
|
+
* MAC_MODIFIER_SYMBOLS['Alt'] // '⌥'
|
|
19
|
+
* MAC_MODIFIER_SYMBOLS['Shift'] // '⇧'
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const MAC_MODIFIER_SYMBOLS: Record<CanonicalModifier | 'Mod', string> = {
|
|
23
|
+
Control: '⌃',
|
|
24
|
+
Alt: '⌥',
|
|
25
|
+
Shift: '⇧',
|
|
26
|
+
Meta: '⌘',
|
|
27
|
+
Mod: '⌘',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Modifier key labels for macOS display.
|
|
32
|
+
*
|
|
33
|
+
* Used by formatting functions to display hotkeys with macOS-style text labels
|
|
34
|
+
* (e.g., 'Control' for Control, 'Option' for Alt, 'Cmd' for Meta) instead of symbols.
|
|
35
|
+
* This provides a familiar macOS look and feel in hotkey displays.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* MAC_MODIFIER_LABELS['Control'] // 'control'
|
|
40
|
+
* MAC_MODIFIER_LABELS['Alt'] // 'option'
|
|
41
|
+
* MAC_MODIFIER_LABELS['Shift'] // 'shift'
|
|
42
|
+
* MAC_MODIFIER_LABELS['Meta'] // 'cmd'
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export const MAC_MODIFIER_LABELS: Record<CanonicalModifier | 'Mod', string> = {
|
|
46
|
+
Control: 'Control',
|
|
47
|
+
Alt: 'Option',
|
|
48
|
+
Shift: 'Shift',
|
|
49
|
+
Meta: 'Cmd',
|
|
50
|
+
Mod: 'Cmd',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Modifier key labels for Windows/Linux display.
|
|
55
|
+
*
|
|
56
|
+
* Used by formatting functions to display hotkeys with standard text labels
|
|
57
|
+
* (e.g., 'Ctrl' for Control, 'Win' for Meta/Windows key) instead of symbols.
|
|
58
|
+
* This provides a familiar Windows/Linux look and feel in hotkey displays.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* STANDARD_MODIFIER_LABELS['Control'] // 'Ctrl'
|
|
63
|
+
* STANDARD_MODIFIER_LABELS['Meta'] // 'Win'
|
|
64
|
+
* STANDARD_MODIFIER_LABELS['Alt'] // 'Alt'
|
|
65
|
+
* STANDARD_MODIFIER_LABELS['Shift'] // 'Shift'
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export const WINDOWS_MODIFIER_LABELS: Record<
|
|
69
|
+
CanonicalModifier | 'Mod',
|
|
70
|
+
string
|
|
71
|
+
> = {
|
|
72
|
+
Control: 'Ctrl',
|
|
73
|
+
Alt: 'Alt',
|
|
74
|
+
Shift: 'Shift',
|
|
75
|
+
Meta: 'Win',
|
|
76
|
+
Mod: 'Ctrl',
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const LINUX_MODIFIER_LABELS: Record<CanonicalModifier | 'Mod', string> =
|
|
80
|
+
{
|
|
81
|
+
...WINDOWS_MODIFIER_LABELS,
|
|
82
|
+
Meta: 'Super',
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const PUNCTUATION_KEY_DISPLAY_LABELS = {
|
|
86
|
+
'`': 'Backquote',
|
|
87
|
+
'\\': 'Backslash',
|
|
88
|
+
'[': 'Left Bracket',
|
|
89
|
+
']': 'Right Bracket',
|
|
90
|
+
',': 'Comma',
|
|
91
|
+
'=': 'Equal',
|
|
92
|
+
'-': 'Minus',
|
|
93
|
+
'.': 'Period',
|
|
94
|
+
';': 'Semicolon',
|
|
95
|
+
} as const satisfies Record<string, string>
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Special key symbols for display formatting.
|
|
99
|
+
*
|
|
100
|
+
* Maps certain keys to their visual symbols for better readability in hotkey displays.
|
|
101
|
+
* Used by formatting functions to show symbols like ↑ for ArrowUp or ↵ for Enter
|
|
102
|
+
* instead of text labels.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* KEY_DISPLAY_SYMBOLS['ArrowUp'] // '↑'
|
|
107
|
+
* KEY_DISPLAY_SYMBOLS['Enter'] // '↵'
|
|
108
|
+
* KEY_DISPLAY_SYMBOLS['Escape'] // 'Esc'
|
|
109
|
+
* KEY_DISPLAY_SYMBOLS['Space'] // '␣'
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export const KEY_DISPLAY_SYMBOLS = {
|
|
113
|
+
ArrowUp: '↑',
|
|
114
|
+
ArrowDown: '↓',
|
|
115
|
+
ArrowLeft: '←',
|
|
116
|
+
ArrowRight: '→',
|
|
117
|
+
Enter: '↵',
|
|
118
|
+
Escape: 'Esc',
|
|
119
|
+
Backspace: '⌫',
|
|
120
|
+
Delete: '⌦',
|
|
121
|
+
Tab: '⇥',
|
|
122
|
+
Space: '␣',
|
|
123
|
+
} as const satisfies Record<string, string>
|
package/src/format.ts
CHANGED
|
@@ -3,27 +3,28 @@ import {
|
|
|
3
3
|
LINUX_MODIFIER_LABELS,
|
|
4
4
|
MAC_MODIFIER_LABELS,
|
|
5
5
|
MAC_MODIFIER_SYMBOLS,
|
|
6
|
-
MODIFIER_ALIASES,
|
|
7
|
-
MODIFIER_ORDER,
|
|
8
6
|
PUNCTUATION_KEY_DISPLAY_LABELS,
|
|
9
7
|
WINDOWS_MODIFIER_LABELS,
|
|
10
|
-
|
|
8
|
+
} from './display-labels'
|
|
9
|
+
import {
|
|
10
|
+
MODIFIER_ALIASES,
|
|
11
|
+
MODIFIER_ORDER,
|
|
12
|
+
PUNCTUATION_CODE_MAP,
|
|
13
|
+
normalizeKeyName,
|
|
11
14
|
} from './constants'
|
|
15
|
+
import { detectPlatform } from './platform'
|
|
16
|
+
import { assertLogicalKey, splitHotkeyParts } from './_keyboard-event'
|
|
12
17
|
import {
|
|
13
18
|
isModifierKey,
|
|
14
19
|
normalizeHotkeyFromParsed,
|
|
15
20
|
normalizeRegisterableHotkey,
|
|
16
21
|
} from './parse'
|
|
17
|
-
import type {
|
|
18
|
-
|
|
19
|
-
FormatDisplayOptions,
|
|
20
|
-
Hotkey,
|
|
21
|
-
ParsedHotkey,
|
|
22
|
-
RegisterableHotkey,
|
|
23
|
-
} from './hotkey'
|
|
22
|
+
import type { CanonicalModifier } from './key.types'
|
|
23
|
+
import type { Hotkey, ParsedHotkey, RegisterableHotkey } from './hotkey.types'
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Joins stored sequence steps with spaces, preserving physical code tokens.
|
|
27
|
+
* For user-facing labels, call formatForDisplay on each step instead.
|
|
27
28
|
*
|
|
28
29
|
* @param sequence - Array of hotkey strings that form the sequence
|
|
29
30
|
* @returns A space-separated string (e.g. ['G','G'] → 'G G')
|
|
@@ -51,6 +52,7 @@ export function formatHotkeySequence(sequence: Array<Hotkey>): string {
|
|
|
51
52
|
* ```
|
|
52
53
|
*/
|
|
53
54
|
export function formatHotkey(parsed: ParsedHotkey): string {
|
|
55
|
+
if (parsed.code === undefined) assertLogicalKey(parsed.key)
|
|
54
56
|
const parts: Array<string> = []
|
|
55
57
|
|
|
56
58
|
// Add modifiers in canonical order
|
|
@@ -61,21 +63,27 @@ export function formatHotkey(parsed: ParsedHotkey): string {
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
// Add the key
|
|
64
|
-
parts.push(parsed.key)
|
|
66
|
+
parts.push(parsed.code !== undefined ? `[${parsed.code}]` : parsed.key)
|
|
65
67
|
|
|
66
68
|
return parts.join('+')
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
export type DisplayHotkey = RegisterableHotkey | ParsedHotkey | (string & {})
|
|
72
|
+
|
|
69
73
|
/**
|
|
70
74
|
* Formats a hotkey for display in a user interface.
|
|
71
75
|
*
|
|
72
76
|
* On macOS, uses symbols (⌘⇧S) in the same modifier order as {@link normalizeHotkeyFromParsed}.
|
|
73
77
|
* On Windows/Linux, uses text (Ctrl+Shift+S) with `+` separators.
|
|
74
78
|
* The separator can be customized with `separatorToken`.
|
|
79
|
+
* Physical codes use conventional display labels: `[KeyS]` becomes `S` and
|
|
80
|
+
* `[Digit2]` becomes `2`. These labels do not infer the active keyboard layout
|
|
81
|
+
* or change matching. Supply a resolved `layoutMap` for layout-aware labels;
|
|
82
|
+
* `keyLabels` overrides both layout entries and fallback labels.
|
|
75
83
|
*
|
|
76
|
-
* @param hotkey -
|
|
84
|
+
* @param hotkey - A logical or physical string, raw binding, or ParsedHotkey
|
|
77
85
|
* @param options - Formatting options
|
|
78
|
-
* @returns
|
|
86
|
+
* @returns Display text, or individual key labels when parts is true
|
|
79
87
|
*
|
|
80
88
|
* @example
|
|
81
89
|
* ```ts
|
|
@@ -90,45 +98,73 @@ export function formatHotkey(parsed: ParsedHotkey): string {
|
|
|
90
98
|
* ```
|
|
91
99
|
*/
|
|
92
100
|
export function formatForDisplay(
|
|
93
|
-
hotkey:
|
|
101
|
+
hotkey: DisplayHotkey,
|
|
102
|
+
options: FormatDisplayOptions & { parts: true },
|
|
103
|
+
): Array<string>
|
|
104
|
+
export function formatForDisplay(
|
|
105
|
+
hotkey: DisplayHotkey,
|
|
106
|
+
options?: FormatDisplayOptions & { parts?: false },
|
|
107
|
+
): string
|
|
108
|
+
export function formatForDisplay(
|
|
109
|
+
hotkey: DisplayHotkey,
|
|
110
|
+
options: FormatDisplayOptions,
|
|
111
|
+
): string | Array<string>
|
|
112
|
+
export function formatForDisplay(
|
|
113
|
+
hotkey: DisplayHotkey,
|
|
94
114
|
options: FormatDisplayOptions = {},
|
|
95
|
-
): string {
|
|
115
|
+
): string | Array<string> {
|
|
96
116
|
const platform = options.platform ?? detectPlatform()
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
117
|
+
const symbols = options.useSymbols ?? true
|
|
118
|
+
const modifierSymbols =
|
|
119
|
+
typeof symbols === 'boolean' ? symbols : (symbols.modifiers ?? true)
|
|
120
|
+
const keySymbols =
|
|
121
|
+
typeof symbols === 'boolean' ? symbols : (symbols.keys ?? true)
|
|
122
|
+
const separator =
|
|
123
|
+
options.separatorToken ??
|
|
124
|
+
(platform === 'mac' && modifierSymbols ? ' ' : '+')
|
|
125
|
+
const normalized =
|
|
126
|
+
typeof hotkey === 'object' && 'modifiers' in hotkey
|
|
127
|
+
? normalizeHotkeyFromParsed(hotkey, platform)
|
|
128
|
+
: normalizeRegisterableHotkey(hotkey as RegisterableHotkey, platform)
|
|
129
|
+
const parts = splitHotkeyParts(normalized).map((segment) => {
|
|
130
|
+
if (isModifierKey(segment)) {
|
|
131
|
+
const modifier = (MODIFIER_ALIASES[segment] ??
|
|
132
|
+
MODIFIER_ALIASES[segment.toLowerCase()]) as CanonicalModifier | 'Mod'
|
|
133
|
+
return platform === 'mac'
|
|
134
|
+
? modifierSymbols
|
|
135
|
+
? MAC_MODIFIER_SYMBOLS[modifier]
|
|
136
|
+
: MAC_MODIFIER_LABELS[modifier]
|
|
137
|
+
: platform === 'windows'
|
|
138
|
+
? WINDOWS_MODIFIER_LABELS[modifier]
|
|
139
|
+
: LINUX_MODIFIER_LABELS[modifier]
|
|
140
|
+
}
|
|
141
|
+
const code = /^\[([A-Za-z][A-Za-z0-9]*)\]$/.exec(segment)?.[1]
|
|
142
|
+
const label = options.keyLabels?.[code ?? segment]
|
|
143
|
+
if (label !== undefined) return label
|
|
144
|
+
// Shorten conventional position labels for display only; stored identity is unchanged.
|
|
145
|
+
const numpad = code?.startsWith('Numpad') ?? false
|
|
146
|
+
let displayKey = code ?? segment
|
|
147
|
+
const mappedKey = code ? options.layoutMap?.get(code) : undefined
|
|
148
|
+
if (mappedKey) {
|
|
149
|
+
// Layout entries are logical labels; normalize their case and whitespace names.
|
|
150
|
+
displayKey = normalizeKeyName(mappedKey)
|
|
151
|
+
} else if (code) {
|
|
152
|
+
if (/^Key[A-Z]$/.test(code)) displayKey = code.slice(3)
|
|
153
|
+
else if (/^Digit[0-9]$/.test(code)) displayKey = code.slice(5)
|
|
154
|
+
else if (numpad) displayKey = code.slice(6)
|
|
155
|
+
else displayKey = PUNCTUATION_CODE_MAP[code] ?? code
|
|
156
|
+
}
|
|
157
|
+
const labels = keySymbols
|
|
158
|
+
? KEY_DISPLAY_SYMBOLS
|
|
159
|
+
: PUNCTUATION_KEY_DISPLAY_LABELS
|
|
160
|
+
const display =
|
|
161
|
+
(labels as Partial<Record<string, string>>)[displayKey] ?? displayKey
|
|
162
|
+
// Retain numpad identity while making longer names such as MemoryAdd readable.
|
|
163
|
+
return numpad
|
|
164
|
+
? `Numpad ${display.replace(/([a-z])([A-Z])/g, '$1 $2')}`
|
|
165
|
+
: display
|
|
166
|
+
})
|
|
167
|
+
return options.parts ? parts : parts.join(separator)
|
|
132
168
|
}
|
|
133
169
|
|
|
134
170
|
/**
|
|
@@ -136,7 +172,30 @@ export function formatForDisplay(
|
|
|
136
172
|
*/
|
|
137
173
|
export function formatWithLabels(
|
|
138
174
|
hotkey: RegisterableHotkey,
|
|
139
|
-
options: Omit<FormatDisplayOptions, 'useSymbols'> = {},
|
|
175
|
+
options: Omit<FormatDisplayOptions, 'useSymbols' | 'parts'> = {},
|
|
140
176
|
): string {
|
|
141
177
|
return formatForDisplay(hotkey, { ...options, useSymbols: false })
|
|
142
178
|
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Options for formatting hotkeys for display.
|
|
182
|
+
*/
|
|
183
|
+
export interface FormatDisplayOptions {
|
|
184
|
+
/** The target platform. Defaults to auto-detection. */
|
|
185
|
+
platform?: 'mac' | 'windows' | 'linux'
|
|
186
|
+
/** Whether to use symbols for the display. Defaults to true. */
|
|
187
|
+
useSymbols?: boolean | { modifiers?: boolean; keys?: boolean }
|
|
188
|
+
/** Return individual key labels instead of a joined string. Default: false. */
|
|
189
|
+
parts?: boolean
|
|
190
|
+
/**
|
|
191
|
+
* An already-resolved code-to-key map, such as KeyboardLayoutMap or Map.
|
|
192
|
+
* Used only for physical bindings. Mapped keys receive normal display formatting;
|
|
193
|
+
* missing or empty entries use conventional fallback labels. The caller owns
|
|
194
|
+
* loading and refreshing the map; this formatter never calls browser APIs.
|
|
195
|
+
*/
|
|
196
|
+
layoutMap?: { get: (code: string) => string | undefined }
|
|
197
|
+
/** Final display overrides for logical keys or physical codes; take precedence over layoutMap. Never changes matching. */
|
|
198
|
+
keyLabels?: Readonly<Record<string, string>>
|
|
199
|
+
/** Override the separator between display tokens. Defaults to platform-specific formatting when null/undefined. */
|
|
200
|
+
separatorToken?: string | null
|
|
201
|
+
}
|
package/src/hint.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { MODIFIER_KEYS, normalizeKeyName } from './constants'
|
|
2
|
+
import { parseRegisterableHotkey } from './parse'
|
|
3
|
+
import type { CanonicalModifier } from './key.types'
|
|
4
|
+
import type { RegisterableHotkey } from './hotkey.types'
|
|
5
|
+
|
|
6
|
+
export interface HeldModifierOptions {
|
|
7
|
+
/** Resolve Mod for this platform. Defaults to platform detection. */
|
|
8
|
+
platform?: 'mac' | 'windows' | 'linux'
|
|
9
|
+
/** Require every binding modifier to be held. Default: false (nonempty subset). */
|
|
10
|
+
exact?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns whether held modifiers should reveal a shortcut hint.
|
|
15
|
+
* By default, any nonempty subset of the binding's modifiers qualifies; an
|
|
16
|
+
* unrelated modifier hides the hint. `exact` requires every binding modifier.
|
|
17
|
+
* Nonmodifier keys are ignored. This does not check registration or focus.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* matchesHeldModifiers('Alt+Shift+[KeyK]', ['Alt']) // true
|
|
21
|
+
* matchesHeldModifiers('Alt+Shift+[KeyK]', ['Alt', 'Control']) // false
|
|
22
|
+
*/
|
|
23
|
+
export function matchesHeldModifiers(
|
|
24
|
+
hotkey: RegisterableHotkey,
|
|
25
|
+
heldKeys: ReadonlyArray<string>,
|
|
26
|
+
options: HeldModifierOptions = {},
|
|
27
|
+
): boolean {
|
|
28
|
+
// AltGraph (AltGr) types alternate characters such as @ or € on many layouts.
|
|
29
|
+
// It can synthesize Ctrl+Alt; character entry should not reveal shortcut hints.
|
|
30
|
+
if (heldKeys.includes('AltGraph')) return false
|
|
31
|
+
const held = [
|
|
32
|
+
...new Set(
|
|
33
|
+
heldKeys.map(normalizeKeyName).filter((key) => MODIFIER_KEYS.has(key)),
|
|
34
|
+
),
|
|
35
|
+
]
|
|
36
|
+
const { modifiers } = parseRegisterableHotkey(hotkey, options.platform)
|
|
37
|
+
return (
|
|
38
|
+
held.length > 0 &&
|
|
39
|
+
held.every((key) => modifiers.includes(key as CanonicalModifier)) &&
|
|
40
|
+
(!options.exact || held.length === modifiers.length)
|
|
41
|
+
)
|
|
42
|
+
}
|