@tanstack/vue-hotkeys 0.6.0 → 0.8.1
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 +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/useHotkey.cjs +23 -7
- package/dist/useHotkey.cjs.map +1 -1
- package/dist/useHotkey.js +24 -8
- package/dist/useHotkey.js.map +1 -1
- package/dist/useHotkeySequence.cjs +23 -6
- package/dist/useHotkeySequence.cjs.map +1 -1
- package/dist/useHotkeySequence.js +24 -7
- package/dist/useHotkeySequence.js.map +1 -1
- package/dist/useHotkeySequences.cjs +140 -0
- package/dist/useHotkeySequences.cjs.map +1 -0
- package/dist/useHotkeySequences.d.cts +64 -0
- package/dist/useHotkeySequences.d.ts +64 -0
- package/dist/useHotkeySequences.js +140 -0
- package/dist/useHotkeySequences.js.map +1 -0
- package/dist/useHotkeys.cjs +1 -2
- package/dist/useHotkeys.cjs.map +1 -1
- package/dist/useHotkeys.js +2 -3
- package/dist/useHotkeys.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/useHotkey.ts +33 -17
- package/src/useHotkeySequence.ts +34 -15
- package/src/useHotkeySequences.ts +221 -0
- package/src/useHotkeys.ts +5 -9
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { onUnmounted, unref, watch } from 'vue'
|
|
2
|
+
import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'
|
|
3
|
+
import { useDefaultHotkeysOptions } from './HotkeysProviderContext'
|
|
4
|
+
import type { UseHotkeySequenceOptions } from './useHotkeySequence'
|
|
5
|
+
import type {
|
|
6
|
+
HotkeyCallback,
|
|
7
|
+
HotkeySequence,
|
|
8
|
+
SequenceRegistrationHandle,
|
|
9
|
+
} from '@tanstack/hotkeys'
|
|
10
|
+
import type { MaybeRefOrGetter } from 'vue'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A single sequence definition for use with `useHotkeySequences`.
|
|
14
|
+
*/
|
|
15
|
+
export interface UseHotkeySequenceDefinition {
|
|
16
|
+
/** Array of hotkey strings that form the sequence */
|
|
17
|
+
sequence: MaybeRefOrGetter<HotkeySequence>
|
|
18
|
+
/** The function to call when the sequence is completed */
|
|
19
|
+
callback: HotkeyCallback
|
|
20
|
+
/** Per-sequence options (merged on top of commonOptions) */
|
|
21
|
+
options?: MaybeRefOrGetter<UseHotkeySequenceOptions>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Vue composable for registering multiple keyboard shortcut sequences at once (Vim-style).
|
|
26
|
+
*
|
|
27
|
+
* Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or a getter/ref
|
|
28
|
+
* that returns one, so you can register variable-length lists safely.
|
|
29
|
+
*
|
|
30
|
+
* Options are merged in this order:
|
|
31
|
+
* HotkeysProvider defaults < commonOptions < per-definition options
|
|
32
|
+
*
|
|
33
|
+
* Definitions with an empty `sequence` are skipped (no registration).
|
|
34
|
+
*
|
|
35
|
+
* @param definitions - Array of sequence definitions, or a getter/ref
|
|
36
|
+
* @param commonOptions - Shared options applied to all sequences (overridden by per-definition options)
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```vue
|
|
40
|
+
* <script setup>
|
|
41
|
+
* import { useHotkeySequences } from '@tanstack/vue-hotkeys'
|
|
42
|
+
*
|
|
43
|
+
* useHotkeySequences([
|
|
44
|
+
* { sequence: ['G', 'G'], callback: () => scrollToTop() },
|
|
45
|
+
* { sequence: ['D', 'D'], callback: () => deleteLine() },
|
|
46
|
+
* ])
|
|
47
|
+
* </script>
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```vue
|
|
52
|
+
* <script setup>
|
|
53
|
+
* import { computed } from 'vue'
|
|
54
|
+
* import { useHotkeySequences } from '@tanstack/vue-hotkeys'
|
|
55
|
+
*
|
|
56
|
+
* const items = computed(() => [...])
|
|
57
|
+
* useHotkeySequences(
|
|
58
|
+
* () => items.value.map((item) => ({
|
|
59
|
+
* sequence: item.chords,
|
|
60
|
+
* callback: item.action,
|
|
61
|
+
* options: { enabled: item.enabled },
|
|
62
|
+
* })),
|
|
63
|
+
* { preventDefault: true },
|
|
64
|
+
* )
|
|
65
|
+
* </script>
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function useHotkeySequences(
|
|
69
|
+
definitions: MaybeRefOrGetter<Array<UseHotkeySequenceDefinition>>,
|
|
70
|
+
commonOptions: MaybeRefOrGetter<UseHotkeySequenceOptions> = {},
|
|
71
|
+
): void {
|
|
72
|
+
type RegistrationRecord = {
|
|
73
|
+
handle: SequenceRegistrationHandle
|
|
74
|
+
target: Document | HTMLElement | Window
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const defaultOptions = useDefaultHotkeysOptions()
|
|
78
|
+
const manager = getSequenceManager()
|
|
79
|
+
|
|
80
|
+
const registrations = new Map<string, RegistrationRecord>()
|
|
81
|
+
|
|
82
|
+
const stopWatcher = watch(
|
|
83
|
+
() => {
|
|
84
|
+
const resolvedDefinitions = resolveMaybeRefOrGetter(definitions)
|
|
85
|
+
const resolvedCommonOptions = resolveMaybeRefOrGetter(commonOptions)
|
|
86
|
+
|
|
87
|
+
return resolvedDefinitions.map((def, i) => {
|
|
88
|
+
const resolvedSequence = resolveMaybeRefOrGetter(def.sequence)
|
|
89
|
+
const resolvedDefOptions = def.options
|
|
90
|
+
? resolveMaybeRefOrGetter(def.options)
|
|
91
|
+
: {}
|
|
92
|
+
|
|
93
|
+
const mergedOptions = {
|
|
94
|
+
...defaultOptions.hotkeySequence,
|
|
95
|
+
...resolvedCommonOptions,
|
|
96
|
+
...resolvedDefOptions,
|
|
97
|
+
} as UseHotkeySequenceOptions
|
|
98
|
+
|
|
99
|
+
const sequenceString = formatHotkeySequence(resolvedSequence)
|
|
100
|
+
|
|
101
|
+
const resolvedEnabled =
|
|
102
|
+
mergedOptions.enabled === undefined
|
|
103
|
+
? undefined
|
|
104
|
+
: resolveMaybeRefOrGetter(mergedOptions.enabled)
|
|
105
|
+
const resolvedTarget =
|
|
106
|
+
mergedOptions.target === undefined
|
|
107
|
+
? undefined
|
|
108
|
+
: resolveMaybeRefOrGetter(mergedOptions.target as any)
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
index: i,
|
|
112
|
+
resolvedSequence,
|
|
113
|
+
sequenceString,
|
|
114
|
+
callback: def.callback,
|
|
115
|
+
mergedOptions,
|
|
116
|
+
resolvedEnabled,
|
|
117
|
+
resolvedTarget,
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
},
|
|
121
|
+
(resolved) => {
|
|
122
|
+
const nextKeys = new Set<string>()
|
|
123
|
+
const prepared: Array<{
|
|
124
|
+
registrationKey: string
|
|
125
|
+
entry: (typeof resolved)[number]
|
|
126
|
+
finalTarget: Document | HTMLElement | Window
|
|
127
|
+
}> = []
|
|
128
|
+
|
|
129
|
+
for (const entry of resolved) {
|
|
130
|
+
if (entry.resolvedSequence.length === 0) {
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const finalTarget =
|
|
135
|
+
entry.resolvedTarget ??
|
|
136
|
+
(typeof document !== 'undefined' ? document : null)
|
|
137
|
+
|
|
138
|
+
if (!finalTarget) {
|
|
139
|
+
continue
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const registrationKey = `${entry.index}:${entry.sequenceString}`
|
|
143
|
+
nextKeys.add(registrationKey)
|
|
144
|
+
prepared.push({ registrationKey, entry, finalTarget })
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const [key, record] of [...registrations.entries()]) {
|
|
148
|
+
if (!nextKeys.has(key)) {
|
|
149
|
+
if (record.handle.isActive) {
|
|
150
|
+
record.handle.unregister()
|
|
151
|
+
}
|
|
152
|
+
registrations.delete(key)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const { registrationKey, entry, finalTarget } of prepared) {
|
|
157
|
+
const existing = registrations.get(registrationKey)
|
|
158
|
+
if (existing?.handle.isActive && existing.target === finalTarget) {
|
|
159
|
+
existing.handle.callback = entry.callback
|
|
160
|
+
const {
|
|
161
|
+
target: _target,
|
|
162
|
+
enabled: _enabled,
|
|
163
|
+
...restOptions
|
|
164
|
+
} = entry.mergedOptions
|
|
165
|
+
const optionsWithoutTarget = {
|
|
166
|
+
...restOptions,
|
|
167
|
+
...(entry.resolvedEnabled === undefined
|
|
168
|
+
? {}
|
|
169
|
+
: { enabled: entry.resolvedEnabled }),
|
|
170
|
+
}
|
|
171
|
+
existing.handle.setOptions(optionsWithoutTarget)
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (existing) {
|
|
176
|
+
if (existing.handle.isActive) {
|
|
177
|
+
existing.handle.unregister()
|
|
178
|
+
}
|
|
179
|
+
registrations.delete(registrationKey)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const {
|
|
183
|
+
target: _target,
|
|
184
|
+
enabled: _enabled,
|
|
185
|
+
...restOptions
|
|
186
|
+
} = entry.mergedOptions
|
|
187
|
+
const optionsWithoutTarget = {
|
|
188
|
+
...restOptions,
|
|
189
|
+
...(entry.resolvedEnabled === undefined
|
|
190
|
+
? {}
|
|
191
|
+
: { enabled: entry.resolvedEnabled }),
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const handle = manager.register(
|
|
195
|
+
entry.resolvedSequence,
|
|
196
|
+
entry.callback,
|
|
197
|
+
{
|
|
198
|
+
...optionsWithoutTarget,
|
|
199
|
+
target: finalTarget,
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
registrations.set(registrationKey, { handle, target: finalTarget })
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
{ immediate: true },
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
onUnmounted(() => {
|
|
209
|
+
stopWatcher()
|
|
210
|
+
for (const { handle } of registrations.values()) {
|
|
211
|
+
if (handle.isActive) {
|
|
212
|
+
handle.unregister()
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
registrations.clear()
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {
|
|
220
|
+
return typeof value === 'function' ? (value as () => T)() : unref(value)
|
|
221
|
+
}
|
package/src/useHotkeys.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { onUnmounted, unref, watch } from 'vue'
|
|
2
2
|
import {
|
|
3
3
|
detectPlatform,
|
|
4
|
-
formatHotkey,
|
|
5
4
|
getHotkeyManager,
|
|
6
|
-
|
|
5
|
+
normalizeRegisterableHotkey,
|
|
7
6
|
} from '@tanstack/hotkeys'
|
|
8
7
|
import { useDefaultHotkeysOptions } from './HotkeysProviderContext'
|
|
9
8
|
import type { UseHotkeyOptions } from './useHotkey'
|
|
10
9
|
import type {
|
|
11
|
-
Hotkey,
|
|
12
10
|
HotkeyCallback,
|
|
13
11
|
HotkeyRegistrationHandle,
|
|
14
12
|
RegisterableHotkey,
|
|
@@ -104,12 +102,10 @@ export function useHotkeys(
|
|
|
104
102
|
} as UseHotkeyOptions
|
|
105
103
|
|
|
106
104
|
const platform = mergedOptions.platform ?? detectPlatform()
|
|
107
|
-
const hotkeyString
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
rawHotkeyToParsedHotkey(resolvedHotkey as any, platform),
|
|
112
|
-
) as Hotkey)
|
|
105
|
+
const hotkeyString = normalizeRegisterableHotkey(
|
|
106
|
+
resolvedHotkey,
|
|
107
|
+
platform,
|
|
108
|
+
)
|
|
113
109
|
|
|
114
110
|
const resolvedEnabled =
|
|
115
111
|
mergedOptions.enabled === undefined
|