@tanstack/hotkeys 0.0.1 → 0.1.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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -45
  3. package/dist/constants.cjs +444 -0
  4. package/dist/constants.cjs.map +1 -0
  5. package/dist/constants.d.cts +226 -0
  6. package/dist/constants.d.ts +226 -0
  7. package/dist/constants.js +428 -0
  8. package/dist/constants.js.map +1 -0
  9. package/dist/format.cjs +178 -0
  10. package/dist/format.cjs.map +1 -0
  11. package/dist/format.d.cts +110 -0
  12. package/dist/format.d.ts +110 -0
  13. package/dist/format.js +175 -0
  14. package/dist/format.js.map +1 -0
  15. package/dist/hotkey-manager.cjs +420 -0
  16. package/dist/hotkey-manager.cjs.map +1 -0
  17. package/dist/hotkey-manager.d.cts +207 -0
  18. package/dist/hotkey-manager.d.ts +207 -0
  19. package/dist/hotkey-manager.js +419 -0
  20. package/dist/hotkey-manager.js.map +1 -0
  21. package/dist/hotkey.d.cts +278 -0
  22. package/dist/hotkey.d.ts +278 -0
  23. package/dist/index.cjs +54 -0
  24. package/dist/index.d.cts +11 -0
  25. package/dist/index.d.ts +11 -0
  26. package/dist/index.js +11 -0
  27. package/dist/key-state-tracker.cjs +197 -0
  28. package/dist/key-state-tracker.cjs.map +1 -0
  29. package/dist/key-state-tracker.d.cts +107 -0
  30. package/dist/key-state-tracker.d.ts +107 -0
  31. package/dist/key-state-tracker.js +196 -0
  32. package/dist/key-state-tracker.js.map +1 -0
  33. package/dist/match.cjs +143 -0
  34. package/dist/match.cjs.map +1 -0
  35. package/dist/match.d.cts +79 -0
  36. package/dist/match.d.ts +79 -0
  37. package/dist/match.js +141 -0
  38. package/dist/match.js.map +1 -0
  39. package/dist/parse.cjs +266 -0
  40. package/dist/parse.cjs.map +1 -0
  41. package/dist/parse.d.cts +169 -0
  42. package/dist/parse.d.ts +169 -0
  43. package/dist/parse.js +258 -0
  44. package/dist/parse.js.map +1 -0
  45. package/dist/recorder.cjs +177 -0
  46. package/dist/recorder.cjs.map +1 -0
  47. package/dist/recorder.d.cts +108 -0
  48. package/dist/recorder.d.ts +108 -0
  49. package/dist/recorder.js +177 -0
  50. package/dist/recorder.js.map +1 -0
  51. package/dist/sequence.cjs +242 -0
  52. package/dist/sequence.cjs.map +1 -0
  53. package/dist/sequence.d.cts +109 -0
  54. package/dist/sequence.d.ts +109 -0
  55. package/dist/sequence.js +240 -0
  56. package/dist/sequence.js.map +1 -0
  57. package/dist/validate.cjs +116 -0
  58. package/dist/validate.cjs.map +1 -0
  59. package/dist/validate.d.cts +56 -0
  60. package/dist/validate.d.ts +56 -0
  61. package/dist/validate.js +114 -0
  62. package/dist/validate.js.map +1 -0
  63. package/package.json +55 -7
  64. package/src/constants.ts +514 -0
  65. package/src/format.ts +261 -0
  66. package/src/hotkey-manager.ts +822 -0
  67. package/src/hotkey.ts +411 -0
  68. package/src/index.ts +10 -0
  69. package/src/key-state-tracker.ts +249 -0
  70. package/src/match.ts +222 -0
  71. package/src/parse.ts +368 -0
  72. package/src/recorder.ts +266 -0
  73. package/src/sequence.ts +391 -0
  74. package/src/validate.ts +171 -0
package/src/format.ts ADDED
@@ -0,0 +1,261 @@
1
+ import {
2
+ KEY_DISPLAY_SYMBOLS,
3
+ MAC_MODIFIER_SYMBOLS,
4
+ MODIFIER_ORDER,
5
+ STANDARD_MODIFIER_LABELS,
6
+ detectPlatform,
7
+ } from './constants'
8
+ import { parseHotkey } from './parse'
9
+ import type { FormatDisplayOptions, Hotkey, ParsedHotkey } from './hotkey'
10
+
11
+ /**
12
+ * Converts a ParsedHotkey back to a hotkey string.
13
+ *
14
+ * @param parsed - The parsed hotkey object
15
+ * @returns A hotkey string in canonical form
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * formatHotkey({ key: 'S', ctrl: true, shift: true, alt: false, meta: false, modifiers: ['Control', 'Shift'] })
20
+ * // Returns: 'Control+Shift+S'
21
+ * ```
22
+ */
23
+ export function formatHotkey(parsed: ParsedHotkey): string {
24
+ const parts: Array<string> = []
25
+
26
+ // Add modifiers in canonical order
27
+ for (const modifier of MODIFIER_ORDER) {
28
+ if (parsed.modifiers.includes(modifier)) {
29
+ parts.push(modifier)
30
+ }
31
+ }
32
+
33
+ // Add the key
34
+ parts.push(parsed.key)
35
+
36
+ return parts.join('+')
37
+ }
38
+
39
+ /**
40
+ * Formats a hotkey for display in a user interface.
41
+ *
42
+ * On macOS, uses symbols (⌘⇧S).
43
+ * On Windows/Linux, uses text (Ctrl+Shift+S).
44
+ *
45
+ * @param hotkey - The hotkey string or ParsedHotkey to format
46
+ * @param options - Formatting options
47
+ * @returns A formatted string suitable for display
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * formatForDisplay('Mod+Shift+S', { platform: 'mac' })
52
+ * // Returns: '⇧⌘S'
53
+ *
54
+ * formatForDisplay('Mod+Shift+S', { platform: 'windows' })
55
+ * // Returns: 'Ctrl+Shift+S'
56
+ *
57
+ * formatForDisplay('Escape')
58
+ * // Returns: 'Esc' (on all platforms)
59
+ * ```
60
+ */
61
+ export function formatForDisplay(
62
+ hotkey: Hotkey | (string & {}) | ParsedHotkey,
63
+ options: FormatDisplayOptions = {},
64
+ ): string {
65
+ const platform = options.platform ?? detectPlatform()
66
+ const parsed =
67
+ typeof hotkey === 'string' ? parseHotkey(hotkey, platform) : hotkey
68
+
69
+ if (platform === 'mac') {
70
+ return formatForMac(parsed)
71
+ }
72
+
73
+ return formatForStandard(parsed)
74
+ }
75
+
76
+ /**
77
+ * Formats a hotkey for macOS display using symbols.
78
+ */
79
+ function formatForMac(parsed: ParsedHotkey): string {
80
+ const parts: Array<string> = []
81
+
82
+ // Add modifiers in macOS order (typically Control, Option, Shift, Command)
83
+ // But we'll use our canonical order and just use symbols
84
+ for (const modifier of MODIFIER_ORDER) {
85
+ if (parsed.modifiers.includes(modifier)) {
86
+ parts.push(MAC_MODIFIER_SYMBOLS[modifier])
87
+ }
88
+ }
89
+
90
+ // Add the key (use symbol if available, otherwise the key itself)
91
+ const keyDisplay = KEY_DISPLAY_SYMBOLS[parsed.key] ?? parsed.key
92
+ parts.push(keyDisplay)
93
+
94
+ // On Mac, modifiers are typically concatenated without separators
95
+ return parts.join('')
96
+ }
97
+
98
+ /**
99
+ * Formats a hotkey for Windows/Linux display using text labels.
100
+ */
101
+ function formatForStandard(parsed: ParsedHotkey): string {
102
+ const parts: Array<string> = []
103
+
104
+ // Add modifiers in canonical order
105
+ for (const modifier of MODIFIER_ORDER) {
106
+ if (parsed.modifiers.includes(modifier)) {
107
+ parts.push(STANDARD_MODIFIER_LABELS[modifier])
108
+ }
109
+ }
110
+
111
+ // Add the key (use symbol/short form if available)
112
+ const keyDisplay = KEY_DISPLAY_SYMBOLS[parsed.key] ?? parsed.key
113
+ parts.push(keyDisplay)
114
+
115
+ // On Windows/Linux, use + as separator
116
+ return parts.join('+')
117
+ }
118
+
119
+ /**
120
+ * Formats a hotkey using platform-agnostic labels.
121
+ * Uses 'Cmd' on Mac and 'Ctrl' for Control, etc.
122
+ *
123
+ * @param hotkey - The hotkey string or ParsedHotkey to format
124
+ * @param platform - The target platform
125
+ * @returns A formatted string with platform-appropriate labels
126
+ */
127
+ export function formatWithLabels(
128
+ hotkey: Hotkey | (string & {}),
129
+ platform: 'mac' | 'windows' | 'linux' = detectPlatform(),
130
+ ): string {
131
+ const parsed =
132
+ typeof hotkey === 'string' ? parseHotkey(hotkey, platform) : hotkey
133
+ const parts: Array<string> = []
134
+
135
+ // Custom labels for more readable output
136
+ const labels: Record<string, string> = {
137
+ Control: 'Ctrl',
138
+ Alt: platform === 'mac' ? 'Option' : 'Alt',
139
+ Shift: 'Shift',
140
+ Meta: platform === 'mac' ? 'Cmd' : 'Win',
141
+ }
142
+
143
+ for (const modifier of MODIFIER_ORDER) {
144
+ if (parsed.modifiers.includes(modifier)) {
145
+ parts.push(labels[modifier] ?? modifier)
146
+ }
147
+ }
148
+
149
+ // Add the key
150
+ parts.push(parsed.key)
151
+
152
+ return parts.join('+')
153
+ }
154
+
155
+ // =============================================================================
156
+ // Debugging Display Labels
157
+ // =============================================================================
158
+
159
+ /**
160
+ * Maps canonical modifier names to debugging-friendly labels per platform.
161
+ */
162
+ const MODIFIER_DEBUG_LABELS: Record<string, Record<string, string>> = {
163
+ mac: { Meta: 'Mod (Cmd)', Control: 'Ctrl', Alt: 'Opt', Shift: 'Shift' },
164
+ windows: { Control: 'Mod (Ctrl)', Meta: 'Win', Alt: 'Alt', Shift: 'Shift' },
165
+ linux: { Control: 'Mod (Ctrl)', Meta: 'Super', Alt: 'Alt', Shift: 'Shift' },
166
+ }
167
+
168
+ /**
169
+ * Options for formatting a single key for debugging display.
170
+ */
171
+ export interface FormatKeyDebuggingOptions {
172
+ /** The target platform. Defaults to auto-detection. */
173
+ platform?: 'mac' | 'windows' | 'linux'
174
+ /**
175
+ * Whether the input value comes from `event.key` or `event.code`.
176
+ *
177
+ * - `'key'` (default): Applies rich platform-aware formatting (modifier
178
+ * labels, special-key symbols, etc.).
179
+ * - `'code'`: Returns the value unchanged — physical key codes like
180
+ * `"MetaLeft"` or `"KeyA"` are already descriptive for debugging.
181
+ */
182
+ source?: 'key' | 'code'
183
+ }
184
+
185
+ /**
186
+ * Formats a single key name for debugging/devtools display.
187
+ *
188
+ * Unlike `formatForDisplay` which formats full hotkey strings for end-user UIs,
189
+ * this function formats individual key names (from `event.key`) with rich
190
+ * platform-aware labels suitable for debugging tools and developer-facing displays.
191
+ *
192
+ * Features:
193
+ * - Modifier keys show their platform role (e.g., "Mod (Cmd)" for Meta on Mac)
194
+ * - On macOS, modifier keys are prefixed with their symbol (e.g., "⌘ Mod (Cmd)")
195
+ * - Special keys use display symbols (ArrowUp -> "↑", Escape -> "Esc")
196
+ * - Regular keys pass through unchanged
197
+ *
198
+ * @param key - A single key name (e.g., "Meta", "Shift", "ArrowUp", "A")
199
+ * @param options - Formatting options
200
+ * @returns A formatted label suitable for debugging display
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * // On macOS:
205
+ * formatKeyForDebuggingDisplay('Meta') // '⌘ Mod (Cmd)'
206
+ * formatKeyForDebuggingDisplay('Control') // '⌃ Ctrl'
207
+ * formatKeyForDebuggingDisplay('Alt') // '⌥ Opt'
208
+ * formatKeyForDebuggingDisplay('Shift') // '⇧ Shift'
209
+ *
210
+ * // On Windows:
211
+ * formatKeyForDebuggingDisplay('Control') // 'Mod (Ctrl)'
212
+ * formatKeyForDebuggingDisplay('Meta') // 'Win'
213
+ *
214
+ * // Special keys (all platforms):
215
+ * formatKeyForDebuggingDisplay('ArrowUp') // '↑'
216
+ * formatKeyForDebuggingDisplay('Escape') // 'Esc'
217
+ * formatKeyForDebuggingDisplay('Space') // '␣'
218
+ *
219
+ * // Regular keys pass through:
220
+ * formatKeyForDebuggingDisplay('A') // 'A'
221
+ *
222
+ * // With source: 'code', values pass through unchanged:
223
+ * formatKeyForDebuggingDisplay('MetaLeft', { source: 'code' }) // 'MetaLeft'
224
+ * formatKeyForDebuggingDisplay('KeyA', { source: 'code' }) // 'KeyA'
225
+ * ```
226
+ */
227
+ export function formatKeyForDebuggingDisplay(
228
+ key: string,
229
+ options: FormatKeyDebuggingOptions = {},
230
+ ): string {
231
+ // For event.code values, pass through unchanged — they're already
232
+ // descriptive for debugging (e.g. "MetaLeft", "KeyA", "ShiftRight").
233
+ if (options.source === 'code') {
234
+ return key
235
+ }
236
+
237
+ const platform = options.platform ?? detectPlatform()
238
+
239
+ // Check if it's a modifier key
240
+ const modLabel = MODIFIER_DEBUG_LABELS[platform]?.[key]
241
+ if (modLabel) {
242
+ // On Mac, prefix modifier labels with their symbol
243
+ if (platform === 'mac') {
244
+ const symbol =
245
+ MAC_MODIFIER_SYMBOLS[key as keyof typeof MAC_MODIFIER_SYMBOLS]
246
+ if (symbol) {
247
+ return `${symbol} ${modLabel}`
248
+ }
249
+ }
250
+ return modLabel
251
+ }
252
+
253
+ // Check if it's a special key with a display symbol
254
+ const symbol = KEY_DISPLAY_SYMBOLS[key]
255
+ if (symbol) {
256
+ return symbol
257
+ }
258
+
259
+ // Regular key — pass through
260
+ return key
261
+ }