@tanstack/vue-hotkeys 0.11.0 → 0.12.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/dist/HotkeysProvider.d.ts +5 -9
- package/dist/HotkeysProvider.js +1 -2
- package/dist/HotkeysProviderContext.d.ts +5 -8
- package/dist/HotkeysProviderContext.js +1 -2
- package/dist/useHeldKeyCodes.d.ts +2 -5
- package/dist/useHeldKeyCodes.js +3 -3
- package/dist/useHeldKeys.d.ts +2 -5
- package/dist/useHeldKeys.js +3 -3
- package/dist/useHotkey.d.ts +3 -6
- package/dist/useHotkey.js +3 -3
- package/dist/useHotkeyHint.d.ts +2 -5
- package/dist/useHotkeyHint.js +1 -2
- package/dist/useHotkeyRecorder.d.ts +3 -6
- package/dist/useHotkeyRecorder.js +1 -2
- package/dist/useHotkeyRegistrations.d.ts +3 -6
- package/dist/useHotkeyRegistrations.js +1 -2
- package/dist/useHotkeySequence.d.ts +3 -6
- package/dist/useHotkeySequence.js +1 -2
- package/dist/useHotkeySequenceRecorder.d.ts +3 -6
- package/dist/useHotkeySequenceRecorder.js +1 -2
- package/dist/useHotkeySequences.d.ts +3 -6
- package/dist/useHotkeySequences.js +1 -2
- package/dist/useHotkeys.d.ts +3 -6
- package/dist/useHotkeys.js +3 -3
- package/dist/useKeyHold.d.ts +2 -5
- package/dist/useKeyHold.js +3 -3
- package/package.json +12 -17
- package/dist/HotkeysProvider.cjs +0 -31
- package/dist/HotkeysProvider.cjs.map +0 -1
- package/dist/HotkeysProvider.d.cts +0 -34
- package/dist/HotkeysProvider.js.map +0 -1
- package/dist/HotkeysProviderContext.cjs +0 -20
- package/dist/HotkeysProviderContext.cjs.map +0 -1
- package/dist/HotkeysProviderContext.d.cts +0 -20
- package/dist/HotkeysProviderContext.js.map +0 -1
- package/dist/index.cjs +0 -37
- package/dist/index.d.cts +0 -15
- package/dist/useHeldKeyCodes.cjs +0 -37
- package/dist/useHeldKeyCodes.cjs.map +0 -1
- package/dist/useHeldKeyCodes.d.cts +0 -33
- package/dist/useHeldKeyCodes.js.map +0 -1
- package/dist/useHeldKeys.cjs +0 -35
- package/dist/useHeldKeys.cjs.map +0 -1
- package/dist/useHeldKeys.d.cts +0 -31
- package/dist/useHeldKeys.js.map +0 -1
- package/dist/useHotkey.cjs +0 -140
- package/dist/useHotkey.cjs.map +0 -1
- package/dist/useHotkey.d.cts +0 -86
- package/dist/useHotkey.js.map +0 -1
- package/dist/useHotkeyHint.cjs +0 -20
- package/dist/useHotkeyHint.cjs.map +0 -1
- package/dist/useHotkeyHint.d.cts +0 -15
- package/dist/useHotkeyHint.js.map +0 -1
- package/dist/useHotkeyRecorder.cjs +0 -80
- package/dist/useHotkeyRecorder.cjs.map +0 -1
- package/dist/useHotkeyRecorder.d.cts +0 -61
- package/dist/useHotkeyRecorder.js.map +0 -1
- package/dist/useHotkeyRegistrations.cjs +0 -26
- package/dist/useHotkeyRegistrations.cjs.map +0 -1
- package/dist/useHotkeyRegistrations.d.cts +0 -27
- package/dist/useHotkeyRegistrations.js.map +0 -1
- package/dist/useHotkeySequence.cjs +0 -122
- package/dist/useHotkeySequence.cjs.map +0 -1
- package/dist/useHotkeySequence.d.cts +0 -68
- package/dist/useHotkeySequence.js.map +0 -1
- package/dist/useHotkeySequenceRecorder.cjs +0 -45
- package/dist/useHotkeySequenceRecorder.cjs.map +0 -1
- package/dist/useHotkeySequenceRecorder.d.cts +0 -20
- package/dist/useHotkeySequenceRecorder.js.map +0 -1
- package/dist/useHotkeySequences.cjs +0 -140
- package/dist/useHotkeySequences.cjs.map +0 -1
- package/dist/useHotkeySequences.d.cts +0 -64
- package/dist/useHotkeySequences.js.map +0 -1
- package/dist/useHotkeys.cjs +0 -139
- package/dist/useHotkeys.cjs.map +0 -1
- package/dist/useHotkeys.d.cts +0 -65
- package/dist/useHotkeys.js.map +0 -1
- package/dist/useKeyHold.cjs +0 -60
- package/dist/useKeyHold.cjs.map +0 -1
- package/dist/useKeyHold.d.cts +0 -52
- package/dist/useKeyHold.js.map +0 -1
- package/src/HotkeysProvider.tsx +0 -29
- package/src/HotkeysProviderContext.ts +0 -41
- package/src/index.ts +0 -19
- package/src/useHeldKeyCodes.ts +0 -34
- package/src/useHeldKeys.ts +0 -32
- package/src/useHotkey.ts +0 -213
- package/src/useHotkeyHint.ts +0 -25
- package/src/useHotkeyRecorder.ts +0 -112
- package/src/useHotkeyRegistrations.ts +0 -46
- package/src/useHotkeySequence.ts +0 -187
- package/src/useHotkeySequenceRecorder.ts +0 -75
- package/src/useHotkeySequences.ts +0 -221
- package/src/useHotkeys.ts +0 -221
- package/src/useKeyHold.ts +0 -65
- package/src/vue.d.ts +0 -6
package/src/useHotkey.ts
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { onUnmounted, unref, watch } from 'vue'
|
|
2
|
-
import {
|
|
3
|
-
detectPlatform,
|
|
4
|
-
getHotkeyManager,
|
|
5
|
-
normalizeRegisterableHotkey,
|
|
6
|
-
} from '@tanstack/hotkeys'
|
|
7
|
-
import { useDefaultHotkeysOptions } from './HotkeysProviderContext'
|
|
8
|
-
import type {
|
|
9
|
-
Hotkey,
|
|
10
|
-
HotkeyCallback,
|
|
11
|
-
HotkeyOptions,
|
|
12
|
-
HotkeyRegistrationHandle,
|
|
13
|
-
RegisterableHotkey,
|
|
14
|
-
} from '@tanstack/hotkeys'
|
|
15
|
-
import type { MaybeRefOrGetter } from 'vue'
|
|
16
|
-
|
|
17
|
-
export interface UseHotkeyOptions extends Omit<
|
|
18
|
-
HotkeyOptions,
|
|
19
|
-
'enabled' | 'target'
|
|
20
|
-
> {
|
|
21
|
-
/**
|
|
22
|
-
* Whether the hotkey is active.
|
|
23
|
-
* Can be a Ref, a getter function, or a boolean value.
|
|
24
|
-
* Defaults to true.
|
|
25
|
-
*/
|
|
26
|
-
enabled?: MaybeRefOrGetter<boolean>
|
|
27
|
-
/**
|
|
28
|
-
* The DOM element to attach the event listener to.
|
|
29
|
-
* Can be a Ref, a getter function, direct DOM element, or null.
|
|
30
|
-
* Defaults to document.
|
|
31
|
-
*/
|
|
32
|
-
target?: MaybeRefOrGetter<HTMLElement | Document | Window | null>
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Vue composable for registering a keyboard hotkey.
|
|
37
|
-
*
|
|
38
|
-
* Uses the singleton HotkeyManager for efficient event handling.
|
|
39
|
-
* The callback receives both the keyboard event and a context object
|
|
40
|
-
* containing the hotkey string and parsed hotkey.
|
|
41
|
-
*
|
|
42
|
-
* This composable automatically tracks reactive dependencies and updates
|
|
43
|
-
* the registration when options or the callback change.
|
|
44
|
-
*
|
|
45
|
-
* @param hotkey - The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object (supports `mod` for cross-platform)
|
|
46
|
-
* @param callback - The function to call when the hotkey is pressed
|
|
47
|
-
* @param options - Options for the hotkey behavior
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```vue
|
|
51
|
-
* <script setup>
|
|
52
|
-
* import { ref } from 'vue'
|
|
53
|
-
* import { useHotkey } from '@tanstack/vue-hotkeys'
|
|
54
|
-
*
|
|
55
|
-
* const count = ref(0)
|
|
56
|
-
*
|
|
57
|
-
* // Callback always has access to latest count value
|
|
58
|
-
* useHotkey('Mod+S', (event, { hotkey }) => {
|
|
59
|
-
* console.log(`Save triggered, count is ${count.value}`)
|
|
60
|
-
* handleSave()
|
|
61
|
-
* })
|
|
62
|
-
* </script>
|
|
63
|
-
* ```
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```vue
|
|
67
|
-
* <script setup>
|
|
68
|
-
* import { ref } from 'vue'
|
|
69
|
-
* import { useHotkey } from '@tanstack/vue-hotkeys'
|
|
70
|
-
*
|
|
71
|
-
* const isOpen = ref(false)
|
|
72
|
-
*
|
|
73
|
-
* // enabled option is reactive
|
|
74
|
-
* useHotkey('Escape', () => {
|
|
75
|
-
* isOpen.value = false
|
|
76
|
-
* }, { enabled: isOpen })
|
|
77
|
-
* </script>
|
|
78
|
-
* ```
|
|
79
|
-
*
|
|
80
|
-
* @example
|
|
81
|
-
* ```vue
|
|
82
|
-
* <script setup>
|
|
83
|
-
* import { ref } from 'vue'
|
|
84
|
-
* import { useHotkey } from '@tanstack/vue-hotkeys'
|
|
85
|
-
*
|
|
86
|
-
* const editorRef = ref<HTMLDivElement | null>(null)
|
|
87
|
-
*
|
|
88
|
-
* // Scoped to a specific element
|
|
89
|
-
* useHotkey('Mod+S', () => {
|
|
90
|
-
* save()
|
|
91
|
-
* }, { target: editorRef })
|
|
92
|
-
* </script>
|
|
93
|
-
*
|
|
94
|
-
* <template>
|
|
95
|
-
* <div ref="editorRef">...</div>
|
|
96
|
-
* </template>
|
|
97
|
-
* ```
|
|
98
|
-
*/
|
|
99
|
-
export function useHotkey(
|
|
100
|
-
hotkey: MaybeRefOrGetter<RegisterableHotkey>,
|
|
101
|
-
callback: HotkeyCallback,
|
|
102
|
-
options: MaybeRefOrGetter<UseHotkeyOptions> = {},
|
|
103
|
-
): void {
|
|
104
|
-
const defaultOptions = useDefaultHotkeysOptions()
|
|
105
|
-
const manager = getHotkeyManager()
|
|
106
|
-
|
|
107
|
-
let registration: HotkeyRegistrationHandle | null = null
|
|
108
|
-
let lastHotkeyString: Hotkey | null = null
|
|
109
|
-
let lastTarget: HTMLElement | Document | Window | null = null
|
|
110
|
-
|
|
111
|
-
// Watch for changes to reactive dependencies
|
|
112
|
-
const stopWatcher = watch(
|
|
113
|
-
() => {
|
|
114
|
-
const resolvedHotkey = resolveMaybeRefOrGetter(hotkey)
|
|
115
|
-
const resolvedOptions = resolveMaybeRefOrGetter(options)
|
|
116
|
-
const mergedOptions = {
|
|
117
|
-
...defaultOptions.hotkey,
|
|
118
|
-
...resolvedOptions,
|
|
119
|
-
} as UseHotkeyOptions
|
|
120
|
-
const resolvedEnabled =
|
|
121
|
-
mergedOptions.enabled === undefined
|
|
122
|
-
? undefined
|
|
123
|
-
: resolveMaybeRefOrGetter(mergedOptions.enabled)
|
|
124
|
-
const resolvedTarget =
|
|
125
|
-
mergedOptions.target === undefined
|
|
126
|
-
? undefined
|
|
127
|
-
: resolveMaybeRefOrGetter(mergedOptions.target)
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
resolvedHotkey,
|
|
131
|
-
mergedOptions,
|
|
132
|
-
resolvedEnabled,
|
|
133
|
-
resolvedTarget,
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
({ resolvedHotkey, mergedOptions, resolvedEnabled, resolvedTarget }) => {
|
|
137
|
-
// Normalize to hotkey string
|
|
138
|
-
const platform = mergedOptions.platform ?? detectPlatform()
|
|
139
|
-
const hotkeyString = normalizeRegisterableHotkey(resolvedHotkey, platform)
|
|
140
|
-
|
|
141
|
-
// Resolve target
|
|
142
|
-
const finalTarget =
|
|
143
|
-
resolvedTarget === undefined
|
|
144
|
-
? typeof document !== 'undefined'
|
|
145
|
-
? document
|
|
146
|
-
: null
|
|
147
|
-
: resolvedTarget
|
|
148
|
-
|
|
149
|
-
if (!finalTarget) {
|
|
150
|
-
if (registration?.isActive) {
|
|
151
|
-
registration.unregister()
|
|
152
|
-
registration = null
|
|
153
|
-
}
|
|
154
|
-
lastHotkeyString = null
|
|
155
|
-
lastTarget = null
|
|
156
|
-
return
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Extract options without target (target is handled separately)
|
|
160
|
-
const {
|
|
161
|
-
target: _target,
|
|
162
|
-
enabled: _enabled,
|
|
163
|
-
...restOptions
|
|
164
|
-
} = mergedOptions
|
|
165
|
-
const optionsWithoutTarget = {
|
|
166
|
-
...restOptions,
|
|
167
|
-
...(resolvedEnabled === undefined ? {} : { enabled: resolvedEnabled }),
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
registration?.isActive &&
|
|
172
|
-
lastHotkeyString === hotkeyString &&
|
|
173
|
-
lastTarget === finalTarget
|
|
174
|
-
) {
|
|
175
|
-
registration.callback = callback
|
|
176
|
-
registration.setOptions(optionsWithoutTarget)
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (registration?.isActive) {
|
|
181
|
-
registration.unregister()
|
|
182
|
-
registration = null
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
registration = manager.register(hotkeyString, callback, {
|
|
186
|
-
...optionsWithoutTarget,
|
|
187
|
-
target: finalTarget,
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
if (registration.isActive) {
|
|
191
|
-
registration.callback = callback
|
|
192
|
-
registration.setOptions(optionsWithoutTarget)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
lastHotkeyString = hotkeyString
|
|
196
|
-
lastTarget = finalTarget
|
|
197
|
-
},
|
|
198
|
-
{ immediate: true },
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
// Cleanup on unmount
|
|
202
|
-
onUnmounted(() => {
|
|
203
|
-
stopWatcher()
|
|
204
|
-
if (registration?.isActive) {
|
|
205
|
-
registration.unregister()
|
|
206
|
-
registration = null
|
|
207
|
-
}
|
|
208
|
-
})
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {
|
|
212
|
-
return typeof value === 'function' ? (value as () => T)() : unref(value)
|
|
213
|
-
}
|
package/src/useHotkeyHint.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { computed, toValue } from 'vue'
|
|
2
|
-
import { useSelector } from '@tanstack/vue-store'
|
|
3
|
-
import { getKeyStateTracker, matchesHeldModifiers } from '@tanstack/hotkeys'
|
|
4
|
-
import type { ComputedRef, MaybeRefOrGetter } from 'vue'
|
|
5
|
-
import type { HeldModifierOptions, RegisterableHotkey } from '@tanstack/hotkeys'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Returns a computed ref indicating whether held modifiers reveal this shortcut.
|
|
9
|
-
* Pass refs or getters to track changing bindings and options.
|
|
10
|
-
* Uses the nonempty-subset/AltGr rules of `matchesHeldModifiers`; `exact` requires
|
|
11
|
-
* every modifier. Combine with the action's enabled state before showing a badge.
|
|
12
|
-
* This helper does not register a shortcut or check whether its target is focused.
|
|
13
|
-
*/
|
|
14
|
-
export function useHotkeyHint(
|
|
15
|
-
hotkey: MaybeRefOrGetter<RegisterableHotkey>,
|
|
16
|
-
options: MaybeRefOrGetter<HeldModifierOptions> = {},
|
|
17
|
-
): ComputedRef<boolean> {
|
|
18
|
-
const held = useSelector(
|
|
19
|
-
getKeyStateTracker().store,
|
|
20
|
-
(state) => state.heldKeys,
|
|
21
|
-
)
|
|
22
|
-
return computed(() =>
|
|
23
|
-
matchesHeldModifiers(toValue(hotkey), held.value, toValue(options)),
|
|
24
|
-
)
|
|
25
|
-
}
|
package/src/useHotkeyRecorder.ts
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { onUnmounted, unref, watch } from 'vue'
|
|
2
|
-
import { useSelector } from '@tanstack/vue-store'
|
|
3
|
-
import { HotkeyRecorder } from '@tanstack/hotkeys'
|
|
4
|
-
import { useDefaultHotkeysOptions } from './HotkeysProviderContext'
|
|
5
|
-
import type { MaybeRefOrGetter, Ref } from 'vue'
|
|
6
|
-
import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys'
|
|
7
|
-
|
|
8
|
-
export interface VueHotkeyRecorder {
|
|
9
|
-
/** Whether recording is currently active */
|
|
10
|
-
isRecording: Ref<boolean>
|
|
11
|
-
/** The currently recorded hotkey (for live preview) */
|
|
12
|
-
recordedHotkey: Ref<Hotkey | null>
|
|
13
|
-
/** Start recording a new hotkey */
|
|
14
|
-
startRecording: () => void
|
|
15
|
-
/** Stop recording (same as cancel) */
|
|
16
|
-
stopRecording: () => void
|
|
17
|
-
/** Cancel recording without saving */
|
|
18
|
-
cancelRecording: () => void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Vue composable for recording keyboard shortcuts.
|
|
23
|
-
*
|
|
24
|
-
* This composable provides a thin wrapper around the framework-agnostic `HotkeyRecorder`
|
|
25
|
-
* class, managing all the complexity of capturing keyboard events, converting them
|
|
26
|
-
* to hotkey strings, and handling edge cases like Escape to cancel or Backspace/Delete
|
|
27
|
-
* to clear.
|
|
28
|
-
*
|
|
29
|
-
* @param options - Configuration options for the recorder
|
|
30
|
-
* @returns An object with recording state and control functions
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* ```vue
|
|
34
|
-
* <script setup>
|
|
35
|
-
* import { ref } from 'vue'
|
|
36
|
-
* import { useHotkeyRecorder } from '@tanstack/vue-hotkeys'
|
|
37
|
-
*
|
|
38
|
-
* const shortcut = ref<Hotkey>('Mod+S')
|
|
39
|
-
*
|
|
40
|
-
* const recorder = useHotkeyRecorder({
|
|
41
|
-
* onRecord: (hotkey) => {
|
|
42
|
-
* shortcut.value = hotkey
|
|
43
|
-
* },
|
|
44
|
-
* onCancel: () => {
|
|
45
|
-
* console.log('Recording cancelled')
|
|
46
|
-
* },
|
|
47
|
-
* })
|
|
48
|
-
* </script>
|
|
49
|
-
*
|
|
50
|
-
* <template>
|
|
51
|
-
* <div>
|
|
52
|
-
* <button @click="recorder.startRecording()">
|
|
53
|
-
* {{ recorder.isRecording ? 'Recording...' : 'Edit Shortcut' }}
|
|
54
|
-
* </button>
|
|
55
|
-
* <div v-if="recorder.recordedHotkey">
|
|
56
|
-
* Recording: {{ recorder.recordedHotkey }}
|
|
57
|
-
* </div>
|
|
58
|
-
* </div>
|
|
59
|
-
* </template>
|
|
60
|
-
* ```
|
|
61
|
-
*/
|
|
62
|
-
export function useHotkeyRecorder(
|
|
63
|
-
options: MaybeRefOrGetter<HotkeyRecorderOptions>,
|
|
64
|
-
): VueHotkeyRecorder {
|
|
65
|
-
const defaultOptions = useDefaultHotkeysOptions()
|
|
66
|
-
const recorder = new HotkeyRecorder(
|
|
67
|
-
resolveRecorderOptions(options, defaultOptions),
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
// Subscribe to recorder state using useSelector
|
|
71
|
-
const isRecording = useSelector(recorder.store, (state) => state.isRecording)
|
|
72
|
-
const recordedHotkey = useSelector(
|
|
73
|
-
recorder.store,
|
|
74
|
-
(state) => state.recordedHotkey,
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
const stopWatcher = watch(
|
|
78
|
-
() => resolveRecorderOptions(options, defaultOptions),
|
|
79
|
-
(resolvedOptions) => {
|
|
80
|
-
recorder.setOptions(resolvedOptions)
|
|
81
|
-
},
|
|
82
|
-
{ immediate: true },
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
// Cleanup on unmount
|
|
86
|
-
onUnmounted(() => {
|
|
87
|
-
stopWatcher()
|
|
88
|
-
recorder.destroy()
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
isRecording: isRecording as Ref<boolean>,
|
|
93
|
-
recordedHotkey: recordedHotkey as Ref<Hotkey | null>,
|
|
94
|
-
startRecording: () => recorder.start(),
|
|
95
|
-
stopRecording: () => recorder.stop(),
|
|
96
|
-
cancelRecording: () => recorder.cancel(),
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function resolveRecorderOptions(
|
|
101
|
-
options: MaybeRefOrGetter<HotkeyRecorderOptions>,
|
|
102
|
-
defaultOptions: ReturnType<typeof useDefaultHotkeysOptions>,
|
|
103
|
-
): HotkeyRecorderOptions {
|
|
104
|
-
return {
|
|
105
|
-
...defaultOptions.hotkeyRecorder,
|
|
106
|
-
...resolveMaybeRefOrGetter(options),
|
|
107
|
-
} as HotkeyRecorderOptions
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {
|
|
111
|
-
return typeof value === 'function' ? (value as () => T)() : unref(value)
|
|
112
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { useSelector } from '@tanstack/vue-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
|
-
import type { Ref } from 'vue'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Return type for useHotkeyRegistrations.
|
|
15
|
-
*/
|
|
16
|
-
export interface HotkeyRegistrationsResult {
|
|
17
|
-
/** All registered hotkeys (public view, no callbacks) */
|
|
18
|
-
hotkeys: Ref<Array<HotkeyRegistrationView>>
|
|
19
|
-
/** All registered sequences */
|
|
20
|
-
sequences: Ref<Array<SequenceRegistrationView>>
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Vue composable that reactively reads all hotkey and sequence registrations
|
|
25
|
-
* from the singleton managers.
|
|
26
|
-
*
|
|
27
|
-
* This is a standalone composable that does NOT require the HotkeysProvider.
|
|
28
|
-
* It subscribes to both HotkeyManager and SequenceManager stores and
|
|
29
|
-
* triggers reactivity when registrations change.
|
|
30
|
-
*
|
|
31
|
-
* @returns Object with `hotkeys` and `sequences` refs
|
|
32
|
-
*/
|
|
33
|
-
export function useHotkeyRegistrations(): HotkeyRegistrationsResult {
|
|
34
|
-
const hotkeyManager = getHotkeyManager()
|
|
35
|
-
const sequenceManager = getSequenceManager()
|
|
36
|
-
|
|
37
|
-
const hotkeys = useSelector(hotkeyManager.registrations, (state) =>
|
|
38
|
-
Array.from(state.values()).map(toHotkeyRegistrationView),
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
const sequences = useSelector(sequenceManager.registrations, (state) =>
|
|
42
|
-
Array.from(state.values()),
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
return { hotkeys, sequences }
|
|
46
|
-
}
|
package/src/useHotkeySequence.ts
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { onUnmounted, unref, watch } from 'vue'
|
|
2
|
-
import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'
|
|
3
|
-
import { useDefaultHotkeysOptions } from './HotkeysProviderContext'
|
|
4
|
-
import type { MaybeRefOrGetter } from 'vue'
|
|
5
|
-
import type {
|
|
6
|
-
HotkeyCallback,
|
|
7
|
-
HotkeySequence,
|
|
8
|
-
SequenceOptions,
|
|
9
|
-
SequenceRegistrationHandle,
|
|
10
|
-
} from '@tanstack/hotkeys'
|
|
11
|
-
|
|
12
|
-
export interface UseHotkeySequenceOptions extends Omit<
|
|
13
|
-
SequenceOptions,
|
|
14
|
-
'enabled' | 'target'
|
|
15
|
-
> {
|
|
16
|
-
/**
|
|
17
|
-
* Whether the sequence is active.
|
|
18
|
-
* Can be a Ref, a getter function, or a boolean value.
|
|
19
|
-
* Defaults to true.
|
|
20
|
-
*/
|
|
21
|
-
enabled?: MaybeRefOrGetter<boolean>
|
|
22
|
-
/**
|
|
23
|
-
* The DOM element to attach the event listener to.
|
|
24
|
-
* Can be a Ref, a getter function, direct DOM element, or null.
|
|
25
|
-
* Defaults to document.
|
|
26
|
-
*/
|
|
27
|
-
target?: MaybeRefOrGetter<HTMLElement | Document | Window | null>
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Vue composable for registering a keyboard shortcut sequence (Vim-style).
|
|
32
|
-
*
|
|
33
|
-
* This composable allows you to register multi-key sequences like 'g g' or 'd d'
|
|
34
|
-
* that trigger when the full sequence is pressed within a timeout.
|
|
35
|
-
*
|
|
36
|
-
* Each step may include modifiers. You can chain the same modifier across
|
|
37
|
-
* steps (e.g. `Shift+R` then `Shift+T`). Modifier-only keydown events (Shift,
|
|
38
|
-
* Control, Alt, or Meta pressed alone) are ignored while matching—they do not
|
|
39
|
-
* advance the sequence or reset progress.
|
|
40
|
-
*
|
|
41
|
-
* @param sequence - Array of hotkey strings that form the sequence
|
|
42
|
-
* @param callback - Function to call when the sequence is completed
|
|
43
|
-
* @param options - Options for the sequence behavior
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```vue
|
|
47
|
-
* <script setup>
|
|
48
|
-
* import { useHotkeySequence } from '@tanstack/vue-hotkeys'
|
|
49
|
-
*
|
|
50
|
-
* // 'g g' to go to top
|
|
51
|
-
* useHotkeySequence(['G', 'G'], () => {
|
|
52
|
-
* scrollToTop()
|
|
53
|
-
* })
|
|
54
|
-
*
|
|
55
|
-
* // 'd d' to delete line
|
|
56
|
-
* useHotkeySequence(['D', 'D'], () => {
|
|
57
|
-
* deleteLine()
|
|
58
|
-
* })
|
|
59
|
-
*
|
|
60
|
-
* // 'd i w' to delete inner word
|
|
61
|
-
* useHotkeySequence(['D', 'I', 'W'], () => {
|
|
62
|
-
* deleteInnerWord()
|
|
63
|
-
* }, { timeout: 500 })
|
|
64
|
-
*
|
|
65
|
-
* // Same modifier on consecutive steps (bare Shift between chords is ignored)
|
|
66
|
-
* useHotkeySequence(['Shift+R', 'Shift+T'], () => {
|
|
67
|
-
* nextAction()
|
|
68
|
-
* })
|
|
69
|
-
* </script>
|
|
70
|
-
*
|
|
71
|
-
* <template>
|
|
72
|
-
* <div>...</div>
|
|
73
|
-
* </template>
|
|
74
|
-
* ```
|
|
75
|
-
*/
|
|
76
|
-
export function useHotkeySequence(
|
|
77
|
-
sequence: MaybeRefOrGetter<HotkeySequence>,
|
|
78
|
-
callback: HotkeyCallback,
|
|
79
|
-
options: MaybeRefOrGetter<UseHotkeySequenceOptions> = {},
|
|
80
|
-
): void {
|
|
81
|
-
const defaultOptions = useDefaultHotkeysOptions()
|
|
82
|
-
const manager = getSequenceManager()
|
|
83
|
-
|
|
84
|
-
let registration: SequenceRegistrationHandle | null = null
|
|
85
|
-
let lastSequenceKey: string | null = null
|
|
86
|
-
let lastTarget: HTMLElement | Document | Window | null = null
|
|
87
|
-
|
|
88
|
-
// Watch for changes to reactive dependencies
|
|
89
|
-
const stopWatcher = watch(
|
|
90
|
-
() => {
|
|
91
|
-
const resolvedSequence = resolveMaybeRefOrGetter(sequence)
|
|
92
|
-
const resolvedOptions = resolveMaybeRefOrGetter(options)
|
|
93
|
-
const mergedOptions = {
|
|
94
|
-
...defaultOptions.hotkeySequence,
|
|
95
|
-
...resolvedOptions,
|
|
96
|
-
} as UseHotkeySequenceOptions
|
|
97
|
-
const resolvedEnabled =
|
|
98
|
-
mergedOptions.enabled === undefined
|
|
99
|
-
? undefined
|
|
100
|
-
: resolveMaybeRefOrGetter(mergedOptions.enabled)
|
|
101
|
-
const resolvedTarget =
|
|
102
|
-
mergedOptions.target === undefined
|
|
103
|
-
? undefined
|
|
104
|
-
: resolveMaybeRefOrGetter(mergedOptions.target)
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
resolvedSequence,
|
|
108
|
-
mergedOptions,
|
|
109
|
-
resolvedEnabled,
|
|
110
|
-
resolvedTarget,
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
({ resolvedSequence, mergedOptions, resolvedEnabled, resolvedTarget }) => {
|
|
114
|
-
const finalTarget =
|
|
115
|
-
resolvedTarget === undefined
|
|
116
|
-
? typeof document !== 'undefined'
|
|
117
|
-
? document
|
|
118
|
-
: null
|
|
119
|
-
: resolvedTarget
|
|
120
|
-
|
|
121
|
-
if (resolvedSequence.length === 0 || !finalTarget) {
|
|
122
|
-
if (registration?.isActive) {
|
|
123
|
-
registration.unregister()
|
|
124
|
-
registration = null
|
|
125
|
-
}
|
|
126
|
-
lastSequenceKey = null
|
|
127
|
-
lastTarget = null
|
|
128
|
-
return
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const sequenceKey = formatHotkeySequence(resolvedSequence)
|
|
132
|
-
|
|
133
|
-
// Extract options without target (target is handled separately)
|
|
134
|
-
const {
|
|
135
|
-
target: _target,
|
|
136
|
-
enabled: _enabled,
|
|
137
|
-
...restOptions
|
|
138
|
-
} = mergedOptions
|
|
139
|
-
const optionsWithoutTarget = {
|
|
140
|
-
...restOptions,
|
|
141
|
-
...(resolvedEnabled === undefined ? {} : { enabled: resolvedEnabled }),
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
registration?.isActive &&
|
|
146
|
-
lastSequenceKey === sequenceKey &&
|
|
147
|
-
lastTarget === finalTarget
|
|
148
|
-
) {
|
|
149
|
-
registration.callback = callback
|
|
150
|
-
registration.setOptions(optionsWithoutTarget)
|
|
151
|
-
return
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (registration?.isActive) {
|
|
155
|
-
registration.unregister()
|
|
156
|
-
registration = null
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
registration = manager.register(resolvedSequence, callback, {
|
|
160
|
-
...optionsWithoutTarget,
|
|
161
|
-
target: finalTarget,
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
if (registration.isActive) {
|
|
165
|
-
registration.callback = callback
|
|
166
|
-
registration.setOptions(optionsWithoutTarget)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
lastSequenceKey = sequenceKey
|
|
170
|
-
lastTarget = finalTarget
|
|
171
|
-
},
|
|
172
|
-
{ immediate: true },
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
// Cleanup on unmount
|
|
176
|
-
onUnmounted(() => {
|
|
177
|
-
stopWatcher()
|
|
178
|
-
if (registration?.isActive) {
|
|
179
|
-
registration.unregister()
|
|
180
|
-
registration = null
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {
|
|
186
|
-
return typeof value === 'function' ? (value as () => T)() : unref(value)
|
|
187
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { onUnmounted, unref, watch } from 'vue'
|
|
2
|
-
import { useSelector } from '@tanstack/vue-store'
|
|
3
|
-
import { HotkeySequenceRecorder } from '@tanstack/hotkeys'
|
|
4
|
-
import { useDefaultHotkeysOptions } from './HotkeysProviderContext'
|
|
5
|
-
import type { MaybeRefOrGetter, Ref } from 'vue'
|
|
6
|
-
import type {
|
|
7
|
-
HotkeySequence,
|
|
8
|
-
HotkeySequenceRecorderOptions,
|
|
9
|
-
} from '@tanstack/hotkeys'
|
|
10
|
-
|
|
11
|
-
export interface VueHotkeySequenceRecorder {
|
|
12
|
-
isRecording: Ref<boolean>
|
|
13
|
-
steps: Ref<HotkeySequence>
|
|
14
|
-
recordedSequence: Ref<HotkeySequence | null>
|
|
15
|
-
startRecording: () => void
|
|
16
|
-
stopRecording: () => void
|
|
17
|
-
cancelRecording: () => void
|
|
18
|
-
commitRecording: () => void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Vue composable for recording multi-chord sequences (Vim-style shortcuts).
|
|
23
|
-
*/
|
|
24
|
-
export function useHotkeySequenceRecorder(
|
|
25
|
-
options: MaybeRefOrGetter<HotkeySequenceRecorderOptions>,
|
|
26
|
-
): VueHotkeySequenceRecorder {
|
|
27
|
-
const defaultOptions = useDefaultHotkeysOptions()
|
|
28
|
-
const recorder = new HotkeySequenceRecorder(
|
|
29
|
-
resolveHotkeySequenceRecorderOptions(options, defaultOptions),
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
const isRecording = useSelector(recorder.store, (state) => state.isRecording)
|
|
33
|
-
const steps = useSelector(recorder.store, (state) => state.steps)
|
|
34
|
-
const recordedSequence = useSelector(
|
|
35
|
-
recorder.store,
|
|
36
|
-
(state) => state.recordedSequence,
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
const stopWatcher = watch(
|
|
40
|
-
() => resolveHotkeySequenceRecorderOptions(options, defaultOptions),
|
|
41
|
-
(resolvedOptions) => {
|
|
42
|
-
recorder.setOptions(resolvedOptions)
|
|
43
|
-
},
|
|
44
|
-
{ immediate: true },
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
onUnmounted(() => {
|
|
48
|
-
stopWatcher()
|
|
49
|
-
recorder.destroy()
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
isRecording: isRecording as Ref<boolean>,
|
|
54
|
-
steps: steps as Ref<HotkeySequence>,
|
|
55
|
-
recordedSequence: recordedSequence as Ref<HotkeySequence | null>,
|
|
56
|
-
startRecording: () => recorder.start(),
|
|
57
|
-
stopRecording: () => recorder.stop(),
|
|
58
|
-
cancelRecording: () => recorder.cancel(),
|
|
59
|
-
commitRecording: () => recorder.commit(),
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function resolveHotkeySequenceRecorderOptions(
|
|
64
|
-
options: MaybeRefOrGetter<HotkeySequenceRecorderOptions>,
|
|
65
|
-
defaultOptions: ReturnType<typeof useDefaultHotkeysOptions>,
|
|
66
|
-
): HotkeySequenceRecorderOptions {
|
|
67
|
-
return {
|
|
68
|
-
...defaultOptions.hotkeySequenceRecorder,
|
|
69
|
-
...resolveMaybeRefOrGetter(options),
|
|
70
|
-
} as HotkeySequenceRecorderOptions
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {
|
|
74
|
-
return typeof value === 'function' ? (value as () => T)() : unref(value)
|
|
75
|
-
}
|