@tanstack/solid-hotkeys 0.10.0 → 0.12.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 (86) hide show
  1. package/README.md +15 -1
  2. package/dist/HotkeysProvider.d.ts +6 -9
  3. package/dist/HotkeysProvider.js +1 -2
  4. package/dist/createHeldKeyCodes.d.ts +2 -4
  5. package/dist/createHeldKeyCodes.js +3 -3
  6. package/dist/createHeldKeys.d.ts +2 -4
  7. package/dist/createHeldKeys.js +3 -3
  8. package/dist/createHotkey.d.ts +3 -6
  9. package/dist/createHotkey.js +3 -3
  10. package/dist/createHotkeyHint.d.ts +11 -0
  11. package/dist/createHotkeyHint.js +19 -0
  12. package/dist/createHotkeyRecorder.d.ts +3 -6
  13. package/dist/createHotkeyRecorder.js +4 -4
  14. package/dist/createHotkeyRegistrations.d.ts +3 -6
  15. package/dist/createHotkeyRegistrations.js +1 -2
  16. package/dist/createHotkeySequence.d.ts +3 -6
  17. package/dist/createHotkeySequence.js +1 -2
  18. package/dist/createHotkeySequenceRecorder.d.ts +3 -6
  19. package/dist/createHotkeySequenceRecorder.js +4 -4
  20. package/dist/createHotkeySequences.d.ts +3 -6
  21. package/dist/createHotkeySequences.js +1 -2
  22. package/dist/createHotkeys.d.ts +3 -6
  23. package/dist/createHotkeys.js +1 -2
  24. package/dist/createKeyHold.d.ts +2 -5
  25. package/dist/createKeyHold.js +3 -3
  26. package/dist/index.d.ts +2 -1
  27. package/dist/index.js +2 -1
  28. package/package.json +12 -17
  29. package/dist/HotkeysProvider.cjs +0 -27
  30. package/dist/HotkeysProvider.cjs.map +0 -1
  31. package/dist/HotkeysProvider.d.cts +0 -25
  32. package/dist/HotkeysProvider.js.map +0 -1
  33. package/dist/createHeldKeyCodes.cjs +0 -42
  34. package/dist/createHeldKeyCodes.cjs.map +0 -1
  35. package/dist/createHeldKeyCodes.d.cts +0 -36
  36. package/dist/createHeldKeyCodes.js.map +0 -1
  37. package/dist/createHeldKeys.cjs +0 -33
  38. package/dist/createHeldKeys.cjs.map +0 -1
  39. package/dist/createHeldKeys.d.cts +0 -27
  40. package/dist/createHeldKeys.js.map +0 -1
  41. package/dist/createHotkey.cjs +0 -120
  42. package/dist/createHotkey.cjs.map +0 -1
  43. package/dist/createHotkey.d.cts +0 -72
  44. package/dist/createHotkey.js.map +0 -1
  45. package/dist/createHotkeyRecorder.cjs +0 -78
  46. package/dist/createHotkeyRecorder.cjs.map +0 -1
  47. package/dist/createHotkeyRecorder.d.cts +0 -60
  48. package/dist/createHotkeyRecorder.js.map +0 -1
  49. package/dist/createHotkeyRegistrations.cjs +0 -26
  50. package/dist/createHotkeyRegistrations.cjs.map +0 -1
  51. package/dist/createHotkeyRegistrations.d.cts +0 -26
  52. package/dist/createHotkeyRegistrations.js.map +0 -1
  53. package/dist/createHotkeySequence.cjs +0 -107
  54. package/dist/createHotkeySequence.cjs.map +0 -1
  55. package/dist/createHotkeySequence.d.cts +0 -56
  56. package/dist/createHotkeySequence.js.map +0 -1
  57. package/dist/createHotkeySequenceRecorder.cjs +0 -43
  58. package/dist/createHotkeySequenceRecorder.cjs.map +0 -1
  59. package/dist/createHotkeySequenceRecorder.d.cts +0 -19
  60. package/dist/createHotkeySequenceRecorder.js.map +0 -1
  61. package/dist/createHotkeySequences.cjs +0 -112
  62. package/dist/createHotkeySequences.cjs.map +0 -1
  63. package/dist/createHotkeySequences.d.cts +0 -57
  64. package/dist/createHotkeySequences.js.map +0 -1
  65. package/dist/createHotkeys.cjs +0 -112
  66. package/dist/createHotkeys.cjs.map +0 -1
  67. package/dist/createHotkeys.d.cts +0 -57
  68. package/dist/createHotkeys.js.map +0 -1
  69. package/dist/createKeyHold.cjs +0 -56
  70. package/dist/createKeyHold.cjs.map +0 -1
  71. package/dist/createKeyHold.d.cts +0 -47
  72. package/dist/createKeyHold.js.map +0 -1
  73. package/dist/index.cjs +0 -33
  74. package/dist/index.d.cts +0 -13
  75. package/src/HotkeysProvider.tsx +0 -51
  76. package/src/createHeldKeyCodes.ts +0 -38
  77. package/src/createHeldKeys.ts +0 -29
  78. package/src/createHotkey.ts +0 -172
  79. package/src/createHotkeyRecorder.ts +0 -103
  80. package/src/createHotkeyRegistrations.ts +0 -45
  81. package/src/createHotkeySequence.ts +0 -155
  82. package/src/createHotkeySequenceRecorder.ts +0 -66
  83. package/src/createHotkeySequences.ts +0 -181
  84. package/src/createHotkeys.ts +0 -187
  85. package/src/createKeyHold.ts +0 -59
  86. package/src/index.ts +0 -17
@@ -1,45 +0,0 @@
1
- import { useSelector } from '@tanstack/solid-store'
2
- import {
3
- getHotkeyManager,
4
- getSequenceManager,
5
- toHotkeyRegistrationView,
6
- } from '@tanstack/hotkeys'
7
- import type {
8
- HotkeyRegistrationView,
9
- SequenceRegistrationView,
10
- } from '@tanstack/hotkeys'
11
-
12
- /**
13
- * Return type for createHotkeyRegistrations.
14
- */
15
- export interface HotkeyRegistrationsResult {
16
- /** Accessor for all registered hotkeys (public view, no callbacks) */
17
- hotkeys: () => Array<HotkeyRegistrationView>
18
- /** Accessor for all registered sequences */
19
- sequences: () => Array<SequenceRegistrationView>
20
- }
21
-
22
- /**
23
- * Solid primitive that reactively reads all hotkey and sequence registrations
24
- * from the singleton managers.
25
- *
26
- * This is a standalone primitive that does NOT require the HotkeysProvider.
27
- * It subscribes to both HotkeyManager and SequenceManager stores and
28
- * triggers reactivity when registrations change.
29
- *
30
- * @returns Object with `hotkeys` and `sequences` signal accessors
31
- */
32
- export function createHotkeyRegistrations(): HotkeyRegistrationsResult {
33
- const hotkeyManager = getHotkeyManager()
34
- const sequenceManager = getSequenceManager()
35
-
36
- const hotkeys = useSelector(hotkeyManager.registrations, (state) =>
37
- Array.from(state.values()).map(toHotkeyRegistrationView),
38
- )
39
-
40
- const sequences = useSelector(sequenceManager.registrations, (state) =>
41
- Array.from(state.values()),
42
- )
43
-
44
- return { hotkeys, sequences }
45
- }
@@ -1,155 +0,0 @@
1
- import { createEffect, onCleanup, untrack } from 'solid-js'
2
- import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'
3
- import { useDefaultHotkeysOptions } from './HotkeysProvider'
4
- import type {
5
- HotkeyCallback,
6
- HotkeySequence,
7
- SequenceOptions,
8
- SequenceRegistrationHandle,
9
- } from '@tanstack/hotkeys'
10
-
11
- export interface CreateHotkeySequenceOptions extends Omit<
12
- SequenceOptions,
13
- 'target'
14
- > {
15
- /**
16
- * The DOM element to attach the event listener to.
17
- * Can be a direct DOM element, an accessor, or null. Defaults to document.
18
- */
19
- target?: HTMLElement | Document | Window | null
20
- }
21
-
22
- /**
23
- * SolidJS primitive for registering a keyboard shortcut sequence (Vim-style).
24
- *
25
- * This primitive allows you to register multi-key sequences like 'g g' or 'd d'
26
- * that trigger when the full sequence is pressed within a timeout.
27
- *
28
- * Each step may include modifiers. You can chain the same modifier across
29
- * steps (e.g. `Shift+R` then `Shift+T`). Modifier-only keydown events (Shift,
30
- * Control, Alt, or Meta pressed alone) are ignored while matching—they do not
31
- * advance the sequence or reset progress.
32
- *
33
- * @param sequence - Array of hotkey strings that form the sequence (or accessor function)
34
- * @param callback - Function to call when the sequence is completed
35
- * @param options - Options for the sequence behavior (or accessor function)
36
- *
37
- * @example
38
- * ```tsx
39
- * function VimEditor() {
40
- * // 'g g' to go to top
41
- * createHotkeySequence(['G', 'G'], () => {
42
- * scrollToTop()
43
- * })
44
- *
45
- * // 'd d' to delete line
46
- * createHotkeySequence(['D', 'D'], () => {
47
- * deleteLine()
48
- * })
49
- *
50
- * // 'd i w' to delete inner word
51
- * createHotkeySequence(['D', 'I', 'W'], () => {
52
- * deleteInnerWord()
53
- * }, { timeout: 500 })
54
- *
55
- * // Same modifier on consecutive steps (bare Shift between chords is ignored)
56
- * createHotkeySequence(['Shift+R', 'Shift+T'], () => {
57
- * nextAction()
58
- * })
59
- *
60
- * return <div>...</div>
61
- * }
62
- * ```
63
- */
64
- export function createHotkeySequence(
65
- sequence: HotkeySequence | (() => HotkeySequence),
66
- callback: HotkeyCallback,
67
- options:
68
- | CreateHotkeySequenceOptions
69
- | (() => CreateHotkeySequenceOptions) = {},
70
- ): void {
71
- const defaultOptions = useDefaultHotkeysOptions()
72
- const manager = getSequenceManager()
73
-
74
- let registration: SequenceRegistrationHandle | null = null
75
- let lastSequenceKey: string | null = null
76
- let lastTarget: HTMLElement | Document | Window | null = null
77
-
78
- onCleanup(() => {
79
- if (registration?.isActive) {
80
- registration.unregister()
81
- registration = null
82
- }
83
- lastSequenceKey = null
84
- lastTarget = null
85
- })
86
-
87
- createEffect(() => {
88
- // Resolve reactive values
89
- const resolvedSequence =
90
- typeof sequence === 'function' ? sequence() : sequence
91
- const resolvedOptions = typeof options === 'function' ? options() : options
92
-
93
- const mergedOptions = {
94
- ...defaultOptions.hotkeySequence,
95
- ...resolvedOptions,
96
- } as CreateHotkeySequenceOptions
97
-
98
- // Extract options without target (target is handled separately)
99
- const { target: _target, ...optionsWithoutTarget } = mergedOptions
100
-
101
- // Resolve target: when explicitly provided (even as null), use it and skip if null.
102
- // When not provided, default to document. Matches createHotkey.
103
- const resolvedTarget =
104
- 'target' in mergedOptions
105
- ? (mergedOptions.target ?? null)
106
- : typeof document !== 'undefined'
107
- ? document
108
- : null
109
-
110
- if (resolvedSequence.length === 0 || !resolvedTarget) {
111
- if (registration?.isActive) {
112
- registration.unregister()
113
- registration = null
114
- }
115
- lastSequenceKey = null
116
- lastTarget = null
117
- return
118
- }
119
-
120
- const sequenceKey = formatHotkeySequence(resolvedSequence)
121
-
122
- // Untrack store mutations so that registering/updating the sequence doesn't
123
- // create a circular reactive dependency when createHotkeyRegistrations
124
- // subscribes to the same store.
125
- untrack(() => {
126
- if (
127
- registration?.isActive &&
128
- lastSequenceKey === sequenceKey &&
129
- lastTarget === resolvedTarget
130
- ) {
131
- registration.callback = callback
132
- registration.setOptions(optionsWithoutTarget)
133
- return
134
- }
135
-
136
- if (registration?.isActive) {
137
- registration.unregister()
138
- registration = null
139
- }
140
-
141
- registration = manager.register(resolvedSequence, callback, {
142
- ...mergedOptions,
143
- target: resolvedTarget,
144
- })
145
-
146
- if (registration.isActive) {
147
- registration.callback = callback
148
- registration.setOptions(optionsWithoutTarget)
149
- }
150
-
151
- lastSequenceKey = sequenceKey
152
- lastTarget = resolvedTarget
153
- })
154
- })
155
- }
@@ -1,66 +0,0 @@
1
- import { createEffect, onCleanup } from 'solid-js'
2
- import { useSelector } 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 = useSelector(recorder.store, (state) => state.isRecording)
39
- const steps = useSelector(recorder.store, (state) => state.steps)
40
- const recordedSequence = useSelector(
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
- }
@@ -1,181 +0,0 @@
1
- import { createEffect, onCleanup, untrack } 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
- // Untrack store mutations so that registering/updating sequences doesn't
141
- // create a circular reactive dependency when createHotkeyRegistrations
142
- // subscribes to the same store.
143
- untrack(() => {
144
- for (const [key, record] of [...registrations.entries()]) {
145
- if (!nextKeys.has(key)) {
146
- if (record.handle.isActive) {
147
- record.handle.unregister()
148
- }
149
- registrations.delete(key)
150
- }
151
- }
152
-
153
- for (const p of prepared) {
154
- const existing = registrations.get(p.registrationKey)
155
- if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
156
- existing.handle.callback = p.def.callback
157
- const { target: _target, ...optionsWithoutTarget } = p.mergedOptions
158
- existing.handle.setOptions(optionsWithoutTarget)
159
- continue
160
- }
161
-
162
- if (existing) {
163
- if (existing.handle.isActive) {
164
- existing.handle.unregister()
165
- }
166
- registrations.delete(p.registrationKey)
167
- }
168
-
169
- const { target: _target, ...optionsWithoutTarget } = p.mergedOptions
170
- const handle = manager.register(p.def.sequence, p.def.callback, {
171
- ...optionsWithoutTarget,
172
- target: p.resolvedTarget,
173
- })
174
- registrations.set(p.registrationKey, {
175
- handle,
176
- target: p.resolvedTarget,
177
- })
178
- }
179
- })
180
- })
181
- }
@@ -1,187 +0,0 @@
1
- import { createEffect, onCleanup, untrack } from 'solid-js'
2
- import {
3
- detectPlatform,
4
- getHotkeyManager,
5
- normalizeRegisterableHotkey,
6
- } from '@tanstack/hotkeys'
7
- import { useDefaultHotkeysOptions } from './HotkeysProvider'
8
- import type { CreateHotkeyOptions } from './createHotkey'
9
- import type {
10
- Hotkey,
11
- HotkeyCallback,
12
- HotkeyRegistrationHandle,
13
- RegisterableHotkey,
14
- } from '@tanstack/hotkeys'
15
-
16
- /**
17
- * A single hotkey definition for use with `createHotkeys`.
18
- */
19
- export interface CreateHotkeyDefinition {
20
- /** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */
21
- hotkey: RegisterableHotkey
22
- /** The function to call when the hotkey is pressed */
23
- callback: HotkeyCallback
24
- /** Per-hotkey options (merged on top of commonOptions) */
25
- options?: CreateHotkeyOptions
26
- }
27
-
28
- /**
29
- * SolidJS primitive for registering multiple keyboard hotkeys at once.
30
- *
31
- * Uses the singleton HotkeyManager for efficient event handling.
32
- * Accepts a dynamic array of hotkey definitions, making it safe to use
33
- * with variable-length lists.
34
- *
35
- * Options are merged in this order:
36
- * HotkeysProvider defaults < commonOptions < per-definition options
37
- *
38
- * @param hotkeys - Array of hotkey definitions, or accessor returning them
39
- * @param commonOptions - Shared options applied to all hotkeys, or accessor
40
- *
41
- * @example
42
- * ```tsx
43
- * function Editor() {
44
- * createHotkeys([
45
- * { hotkey: 'Mod+S', callback: () => save() },
46
- * { hotkey: 'Mod+Z', callback: () => undo() },
47
- * { hotkey: 'Escape', callback: () => close() },
48
- * ])
49
- * }
50
- * ```
51
- *
52
- * @example
53
- * ```tsx
54
- * function MenuShortcuts(props) {
55
- * createHotkeys(
56
- * () => props.items.map((item) => ({
57
- * hotkey: item.shortcut,
58
- * callback: item.action,
59
- * options: { enabled: item.enabled },
60
- * })),
61
- * { preventDefault: true },
62
- * )
63
- * }
64
- * ```
65
- */
66
- export function createHotkeys(
67
- hotkeys:
68
- | Array<CreateHotkeyDefinition>
69
- | (() => Array<CreateHotkeyDefinition>),
70
- commonOptions: CreateHotkeyOptions | (() => CreateHotkeyOptions) = {},
71
- ): void {
72
- type RegistrationRecord = {
73
- handle: HotkeyRegistrationHandle
74
- target: Document | HTMLElement | Window
75
- }
76
-
77
- const defaultOptions = useDefaultHotkeysOptions()
78
- const manager = getHotkeyManager()
79
-
80
- const registrations = new Map<string, RegistrationRecord>()
81
-
82
- // Clean up only when this component/scope disposes — not before every effect
83
- // re-run. An inner `onCleanup` inside `createEffect` runs whenever reactive
84
- // deps change (e.g. dynamic hotkey list), which unregistered every hotkey and
85
- // re-registered them, flooding the manager store and could freeze the tab.
86
- onCleanup(() => {
87
- for (const { handle } of registrations.values()) {
88
- if (handle.isActive) {
89
- handle.unregister()
90
- }
91
- }
92
- registrations.clear()
93
- })
94
-
95
- createEffect(() => {
96
- const resolvedHotkeys = typeof hotkeys === 'function' ? hotkeys() : hotkeys
97
- const resolvedCommonOptions =
98
- typeof commonOptions === 'function' ? commonOptions() : commonOptions
99
-
100
- const nextKeys = new Set<string>()
101
- const prepared: Array<{
102
- registrationKey: string
103
- def: CreateHotkeyDefinition
104
- mergedOptions: CreateHotkeyOptions
105
- hotkeyString: Hotkey
106
- resolvedTarget: Document | HTMLElement | Window
107
- }> = []
108
-
109
- for (let i = 0; i < resolvedHotkeys.length; i++) {
110
- const def = resolvedHotkeys[i]!
111
- const resolvedDefOptions = def.options ?? {}
112
-
113
- const mergedOptions = {
114
- ...defaultOptions.hotkey,
115
- ...resolvedCommonOptions,
116
- ...resolvedDefOptions,
117
- } as CreateHotkeyOptions
118
-
119
- const platform = mergedOptions.platform ?? detectPlatform()
120
- const hotkeyString = normalizeRegisterableHotkey(def.hotkey, platform)
121
-
122
- const resolvedTarget =
123
- 'target' in mergedOptions
124
- ? (mergedOptions.target ?? null)
125
- : typeof document !== 'undefined'
126
- ? document
127
- : null
128
-
129
- if (!resolvedTarget) {
130
- continue
131
- }
132
-
133
- const registrationKey = `${i}:${hotkeyString}`
134
- nextKeys.add(registrationKey)
135
- prepared.push({
136
- registrationKey,
137
- def,
138
- mergedOptions,
139
- hotkeyString,
140
- resolvedTarget,
141
- })
142
- }
143
-
144
- // Untrack store mutations so that registering/updating hotkeys doesn't
145
- // create a circular reactive dependency when createHotkeyRegistrations
146
- // subscribes to the same store.
147
- untrack(() => {
148
- // Drop stale keys first so list index shifts don't briefly double-register
149
- // the same hotkey+target (conflictBehavior: 'warn' + devtools can loop).
150
- for (const [key, record] of [...registrations.entries()]) {
151
- if (!nextKeys.has(key)) {
152
- if (record.handle.isActive) {
153
- record.handle.unregister()
154
- }
155
- registrations.delete(key)
156
- }
157
- }
158
-
159
- for (const p of prepared) {
160
- const existing = registrations.get(p.registrationKey)
161
- if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
162
- existing.handle.callback = p.def.callback
163
- const { target: _target, ...optionsWithoutTarget } = p.mergedOptions
164
- existing.handle.setOptions(optionsWithoutTarget)
165
- continue
166
- }
167
-
168
- if (existing) {
169
- if (existing.handle.isActive) {
170
- existing.handle.unregister()
171
- }
172
- registrations.delete(p.registrationKey)
173
- }
174
-
175
- const { target: _target, ...optionsWithoutTarget } = p.mergedOptions
176
- const handle = manager.register(p.hotkeyString, p.def.callback, {
177
- ...optionsWithoutTarget,
178
- target: p.resolvedTarget,
179
- })
180
- registrations.set(p.registrationKey, {
181
- handle,
182
- target: p.resolvedTarget,
183
- })
184
- }
185
- })
186
- })
187
- }
@@ -1,59 +0,0 @@
1
- import { createMemo } from 'solid-js'
2
- import { useSelector } from '@tanstack/solid-store'
3
- import { getKeyStateTracker } from '@tanstack/hotkeys'
4
- import type { IndividualKey } from '@tanstack/hotkeys'
5
-
6
- /**
7
- * SolidJS primitive that returns whether a specific key is currently being held.
8
- *
9
- * This primitive uses `useSelector` from `@tanstack/solid-store` to subscribe
10
- * to the global KeyStateTracker and uses a selector to determine if the
11
- * specified key is held.
12
- *
13
- * @param key - The key to check (e.g., 'Shift', 'Control', 'A') - can be an accessor function
14
- * @returns Signal accessor that returns true if the key is currently held down
15
- *
16
- * @example
17
- * ```tsx
18
- * function ShiftIndicator() {
19
- * const isShiftHeld = createKeyHold('Shift')
20
- *
21
- * return (
22
- * <div style={{ opacity: isShiftHeld() ? 1 : 0.5 }}>
23
- * {isShiftHeld() ? 'Shift is pressed!' : 'Press Shift'}
24
- * </div>
25
- * )
26
- * }
27
- * ```
28
- *
29
- * @example
30
- * ```tsx
31
- * function ModifierIndicators() {
32
- * const ctrl = createKeyHold('Control')
33
- * const shift = createKeyHold('Shift')
34
- * const alt = createKeyHold('Alt')
35
- *
36
- * return (
37
- * <div>
38
- * <span style={{ opacity: ctrl() ? 1 : 0.3 }}>Ctrl</span>
39
- * <span style={{ opacity: shift() ? 1 : 0.3 }}>Shift</span>
40
- * <span style={{ opacity: alt() ? 1 : 0.3 }}>Alt</span>
41
- * </div>
42
- * )
43
- * }
44
- * ```
45
- */
46
- export function createKeyHold(
47
- key: IndividualKey | (() => IndividualKey),
48
- ): () => boolean {
49
- const tracker = getKeyStateTracker()
50
- const heldKeysSelector = useSelector(tracker.store, (state) => state.heldKeys)
51
-
52
- return createMemo(() => {
53
- const resolvedKey = typeof key === 'function' ? key() : key
54
- const normalizedKey = resolvedKey.toLowerCase()
55
- return heldKeysSelector().some(
56
- (heldKey) => heldKey.toLowerCase() === normalizedKey,
57
- )
58
- })
59
- }