@tanstack/solid-hotkeys 0.5.1 → 0.7.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 (35) hide show
  1. package/dist/HotkeysProvider.cjs.map +1 -1
  2. package/dist/HotkeysProvider.d.cts +2 -1
  3. package/dist/HotkeysProvider.d.ts +2 -1
  4. package/dist/HotkeysProvider.js.map +1 -1
  5. package/dist/createHotkey.cjs +27 -8
  6. package/dist/createHotkey.cjs.map +1 -1
  7. package/dist/createHotkey.js +27 -8
  8. package/dist/createHotkey.js.map +1 -1
  9. package/dist/createHotkeySequence.cjs +27 -8
  10. package/dist/createHotkeySequence.cjs.map +1 -1
  11. package/dist/createHotkeySequence.js +28 -9
  12. package/dist/createHotkeySequence.js.map +1 -1
  13. package/dist/createHotkeySequenceRecorder.cjs +43 -0
  14. package/dist/createHotkeySequenceRecorder.cjs.map +1 -0
  15. package/dist/createHotkeySequenceRecorder.d.cts +19 -0
  16. package/dist/createHotkeySequenceRecorder.d.ts +19 -0
  17. package/dist/createHotkeySequenceRecorder.js +43 -0
  18. package/dist/createHotkeySequenceRecorder.js.map +1 -0
  19. package/dist/createHotkeySequences.cjs +110 -0
  20. package/dist/createHotkeySequences.cjs.map +1 -0
  21. package/dist/createHotkeySequences.d.cts +57 -0
  22. package/dist/createHotkeySequences.d.ts +57 -0
  23. package/dist/createHotkeySequences.js +110 -0
  24. package/dist/createHotkeySequences.js.map +1 -0
  25. package/dist/index.cjs +4 -0
  26. package/dist/index.d.cts +3 -1
  27. package/dist/index.d.ts +3 -1
  28. package/dist/index.js +3 -1
  29. package/package.json +2 -2
  30. package/src/HotkeysProvider.tsx +5 -1
  31. package/src/createHotkey.ts +32 -13
  32. package/src/createHotkeySequence.ts +33 -16
  33. package/src/createHotkeySequenceRecorder.ts +66 -0
  34. package/src/createHotkeySequences.ts +176 -0
  35. package/src/index.ts +2 -0
@@ -0,0 +1,66 @@
1
+ import { createEffect, onCleanup } from 'solid-js'
2
+ import { useStore } from '@tanstack/solid-store'
3
+ import { HotkeySequenceRecorder } from '@tanstack/hotkeys'
4
+ import { useDefaultHotkeysOptions } from './HotkeysProvider'
5
+ import type {
6
+ HotkeySequence,
7
+ HotkeySequenceRecorderOptions,
8
+ } from '@tanstack/hotkeys'
9
+
10
+ export interface SolidHotkeySequenceRecorder {
11
+ isRecording: () => boolean
12
+ steps: () => HotkeySequence
13
+ recordedSequence: () => HotkeySequence | null
14
+ startRecording: () => void
15
+ stopRecording: () => void
16
+ cancelRecording: () => void
17
+ commitRecording: () => void
18
+ }
19
+
20
+ /**
21
+ * SolidJS primitive for recording multi-chord sequences (Vim-style shortcuts).
22
+ */
23
+ export function createHotkeySequenceRecorder(
24
+ options:
25
+ | HotkeySequenceRecorderOptions
26
+ | (() => HotkeySequenceRecorderOptions),
27
+ ): SolidHotkeySequenceRecorder {
28
+ const defaultOptions = useDefaultHotkeysOptions()
29
+
30
+ const resolvedOptions = typeof options === 'function' ? options() : options
31
+ const mergedOptions = {
32
+ ...defaultOptions.hotkeySequenceRecorder,
33
+ ...resolvedOptions,
34
+ } as HotkeySequenceRecorderOptions
35
+
36
+ const recorder = new HotkeySequenceRecorder(mergedOptions)
37
+
38
+ const isRecording = useStore(recorder.store, (state) => state.isRecording)
39
+ const steps = useStore(recorder.store, (state) => state.steps)
40
+ const recordedSequence = useStore(
41
+ recorder.store,
42
+ (state) => state.recordedSequence,
43
+ )
44
+
45
+ createEffect(() => {
46
+ const resolved = typeof options === 'function' ? options() : options
47
+ recorder.setOptions({
48
+ ...defaultOptions.hotkeySequenceRecorder,
49
+ ...resolved,
50
+ } as HotkeySequenceRecorderOptions)
51
+ })
52
+
53
+ onCleanup(() => {
54
+ recorder.destroy()
55
+ })
56
+
57
+ return {
58
+ isRecording,
59
+ steps,
60
+ recordedSequence,
61
+ startRecording: () => recorder.start(),
62
+ stopRecording: () => recorder.stop(),
63
+ cancelRecording: () => recorder.cancel(),
64
+ commitRecording: () => recorder.commit(),
65
+ }
66
+ }
@@ -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
+ }
package/src/index.ts CHANGED
@@ -11,4 +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'
16
+ export * from './createHotkeySequenceRecorder'