@tanstack/solid-hotkeys 0.6.0 → 0.8.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.
@@ -0,0 +1,176 @@
1
+ import { createEffect, onCleanup } from 'solid-js'
2
+ import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'
3
+ import { useDefaultHotkeysOptions } from './HotkeysProvider'
4
+ import type { CreateHotkeySequenceOptions } from './createHotkeySequence'
5
+ import type {
6
+ HotkeyCallback,
7
+ HotkeySequence,
8
+ SequenceRegistrationHandle,
9
+ } from '@tanstack/hotkeys'
10
+
11
+ /**
12
+ * A single sequence definition for use with `createHotkeySequences`.
13
+ */
14
+ export interface CreateHotkeySequenceDefinition {
15
+ /** Array of hotkey strings that form the sequence */
16
+ sequence: HotkeySequence
17
+ /** The function to call when the sequence is completed */
18
+ callback: HotkeyCallback
19
+ /** Per-sequence options (merged on top of commonOptions) */
20
+ options?: CreateHotkeySequenceOptions
21
+ }
22
+
23
+ /**
24
+ * SolidJS primitive for registering multiple keyboard shortcut sequences at once (Vim-style).
25
+ *
26
+ * Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or accessors,
27
+ * so you can react to variable-length lists.
28
+ *
29
+ * Options are merged in this order:
30
+ * HotkeysProvider defaults < commonOptions < per-definition options
31
+ *
32
+ * Definitions with an empty `sequence` are skipped (no registration).
33
+ *
34
+ * @param sequences - Array of sequence definitions, or accessor returning them
35
+ * @param commonOptions - Shared options for all sequences, or accessor
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * function VimPalette() {
40
+ * createHotkeySequences([
41
+ * { sequence: ['G', 'G'], callback: () => scrollToTop() },
42
+ * { sequence: ['D', 'D'], callback: () => deleteLine() },
43
+ * ])
44
+ * }
45
+ * ```
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * function Dynamic(props) {
50
+ * createHotkeySequences(
51
+ * () => props.items.map((item) => ({
52
+ * sequence: item.chords,
53
+ * callback: item.action,
54
+ * options: { enabled: item.enabled },
55
+ * })),
56
+ * { preventDefault: true },
57
+ * )
58
+ * }
59
+ * ```
60
+ */
61
+ export function createHotkeySequences(
62
+ sequences:
63
+ | Array<CreateHotkeySequenceDefinition>
64
+ | (() => Array<CreateHotkeySequenceDefinition>),
65
+ commonOptions:
66
+ | CreateHotkeySequenceOptions
67
+ | (() => CreateHotkeySequenceOptions) = {},
68
+ ): void {
69
+ type RegistrationRecord = {
70
+ handle: SequenceRegistrationHandle
71
+ target: Document | HTMLElement | Window
72
+ }
73
+
74
+ const defaultOptions = useDefaultHotkeysOptions()
75
+ const manager = getSequenceManager()
76
+
77
+ const registrations = new Map<string, RegistrationRecord>()
78
+
79
+ onCleanup(() => {
80
+ for (const { handle } of registrations.values()) {
81
+ if (handle.isActive) {
82
+ handle.unregister()
83
+ }
84
+ }
85
+ registrations.clear()
86
+ })
87
+
88
+ createEffect(() => {
89
+ const resolved = typeof sequences === 'function' ? sequences() : sequences
90
+ const resolvedCommonOptions =
91
+ typeof commonOptions === 'function' ? commonOptions() : commonOptions
92
+
93
+ const nextKeys = new Set<string>()
94
+ const prepared: Array<{
95
+ registrationKey: string
96
+ def: CreateHotkeySequenceDefinition
97
+ mergedOptions: CreateHotkeySequenceOptions
98
+ sequenceString: string
99
+ resolvedTarget: Document | HTMLElement | Window
100
+ }> = []
101
+
102
+ for (let i = 0; i < resolved.length; i++) {
103
+ const def = resolved[i]!
104
+ const resolvedDefOptions = def.options ?? {}
105
+
106
+ const mergedOptions = {
107
+ ...defaultOptions.hotkeySequence,
108
+ ...resolvedCommonOptions,
109
+ ...resolvedDefOptions,
110
+ } as CreateHotkeySequenceOptions
111
+
112
+ if (def.sequence.length === 0) {
113
+ continue
114
+ }
115
+
116
+ const sequenceString = formatHotkeySequence(def.sequence)
117
+
118
+ const resolvedTarget =
119
+ 'target' in mergedOptions
120
+ ? (mergedOptions.target ?? null)
121
+ : typeof document !== 'undefined'
122
+ ? document
123
+ : null
124
+
125
+ if (!resolvedTarget) {
126
+ continue
127
+ }
128
+
129
+ const registrationKey = `${i}:${sequenceString}`
130
+ nextKeys.add(registrationKey)
131
+ prepared.push({
132
+ registrationKey,
133
+ def,
134
+ mergedOptions,
135
+ sequenceString,
136
+ resolvedTarget,
137
+ })
138
+ }
139
+
140
+ for (const [key, record] of [...registrations.entries()]) {
141
+ if (!nextKeys.has(key)) {
142
+ if (record.handle.isActive) {
143
+ record.handle.unregister()
144
+ }
145
+ registrations.delete(key)
146
+ }
147
+ }
148
+
149
+ for (const p of prepared) {
150
+ const existing = registrations.get(p.registrationKey)
151
+ if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
152
+ existing.handle.callback = p.def.callback
153
+ const { target: _target, ...optionsWithoutTarget } = p.mergedOptions
154
+ existing.handle.setOptions(optionsWithoutTarget)
155
+ continue
156
+ }
157
+
158
+ if (existing) {
159
+ if (existing.handle.isActive) {
160
+ existing.handle.unregister()
161
+ }
162
+ registrations.delete(p.registrationKey)
163
+ }
164
+
165
+ const { target: _target, ...optionsWithoutTarget } = p.mergedOptions
166
+ const handle = manager.register(p.def.sequence, p.def.callback, {
167
+ ...optionsWithoutTarget,
168
+ target: p.resolvedTarget,
169
+ })
170
+ registrations.set(p.registrationKey, {
171
+ handle,
172
+ target: p.resolvedTarget,
173
+ })
174
+ }
175
+ })
176
+ }
@@ -1,9 +1,8 @@
1
1
  import { createEffect, onCleanup } from 'solid-js'
2
2
  import {
3
3
  detectPlatform,
4
- formatHotkey,
5
4
  getHotkeyManager,
6
- rawHotkeyToParsedHotkey,
5
+ normalizeRegisterableHotkey,
7
6
  } from '@tanstack/hotkeys'
8
7
  import { useDefaultHotkeysOptions } from './HotkeysProvider'
9
8
  import type { CreateHotkeyOptions } from './createHotkey'
@@ -118,12 +117,7 @@ export function createHotkeys(
118
117
  } as CreateHotkeyOptions
119
118
 
120
119
  const platform = mergedOptions.platform ?? detectPlatform()
121
- const hotkeyString: Hotkey =
122
- typeof def.hotkey === 'string'
123
- ? def.hotkey
124
- : (formatHotkey(
125
- rawHotkeyToParsedHotkey(def.hotkey, platform),
126
- ) as Hotkey)
120
+ const hotkeyString = normalizeRegisterableHotkey(def.hotkey, platform)
127
121
 
128
122
  const resolvedTarget =
129
123
  'target' in mergedOptions
package/src/index.ts CHANGED
@@ -11,5 +11,6 @@ export * from './createHeldKeys'
11
11
  export * from './createHeldKeyCodes'
12
12
  export * from './createKeyHold'
13
13
  export * from './createHotkeySequence'
14
+ export * from './createHotkeySequences'
14
15
  export * from './createHotkeyRecorder'
15
16
  export * from './createHotkeySequenceRecorder'