@tanstack/vue-hotkeys 0.0.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 +122 -0
- package/dist/HotkeysProvider.cjs +31 -0
- package/dist/HotkeysProvider.cjs.map +1 -0
- package/dist/HotkeysProvider.d.cts +34 -0
- package/dist/HotkeysProvider.d.ts +34 -0
- package/dist/HotkeysProvider.js +31 -0
- package/dist/HotkeysProvider.js.map +1 -0
- package/dist/HotkeysProviderContext.cjs +20 -0
- package/dist/HotkeysProviderContext.cjs.map +1 -0
- package/dist/HotkeysProviderContext.d.cts +19 -0
- package/dist/HotkeysProviderContext.d.ts +19 -0
- package/dist/HotkeysProviderContext.js +18 -0
- package/dist/HotkeysProviderContext.js.map +1 -0
- package/dist/index.cjs +27 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +12 -0
- package/dist/useHeldKeyCodes.cjs +37 -0
- package/dist/useHeldKeyCodes.cjs.map +1 -0
- package/dist/useHeldKeyCodes.d.cts +33 -0
- package/dist/useHeldKeyCodes.d.ts +33 -0
- package/dist/useHeldKeyCodes.js +37 -0
- package/dist/useHeldKeyCodes.js.map +1 -0
- package/dist/useHeldKeys.cjs +35 -0
- package/dist/useHeldKeys.cjs.map +1 -0
- package/dist/useHeldKeys.d.cts +31 -0
- package/dist/useHeldKeys.d.ts +31 -0
- package/dist/useHeldKeys.js +35 -0
- package/dist/useHeldKeys.js.map +1 -0
- package/dist/useHotkey.cjs +124 -0
- package/dist/useHotkey.cjs.map +1 -0
- package/dist/useHotkey.d.cts +86 -0
- package/dist/useHotkey.d.ts +86 -0
- package/dist/useHotkey.js +124 -0
- package/dist/useHotkey.js.map +1 -0
- package/dist/useHotkeyRecorder.cjs +80 -0
- package/dist/useHotkeyRecorder.cjs.map +1 -0
- package/dist/useHotkeyRecorder.d.cts +61 -0
- package/dist/useHotkeyRecorder.d.ts +61 -0
- package/dist/useHotkeyRecorder.js +80 -0
- package/dist/useHotkeyRecorder.js.map +1 -0
- package/dist/useHotkeySequence.cjs +95 -0
- package/dist/useHotkeySequence.cjs.map +1 -0
- package/dist/useHotkeySequence.d.cts +58 -0
- package/dist/useHotkeySequence.d.ts +58 -0
- package/dist/useHotkeySequence.js +95 -0
- package/dist/useHotkeySequence.js.map +1 -0
- package/dist/useKeyHold.cjs +60 -0
- package/dist/useKeyHold.cjs.map +1 -0
- package/dist/useKeyHold.d.cts +52 -0
- package/dist/useKeyHold.d.ts +52 -0
- package/dist/useKeyHold.js +60 -0
- package/dist/useKeyHold.js.map +1 -0
- package/package.json +66 -0
- package/src/HotkeysProvider.tsx +29 -0
- package/src/HotkeysProviderContext.ts +37 -0
- package/src/index.ts +14 -0
- package/src/useHeldKeyCodes.ts +34 -0
- package/src/useHeldKeys.ts +32 -0
- package/src/useHotkey.ts +197 -0
- package/src/useHotkeyRecorder.ts +112 -0
- package/src/useHotkeySequence.ts +158 -0
- package/src/useKeyHold.ts +65 -0
- package/src/vue.d.ts +6 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { onUnmounted, unref, watch } from 'vue'
|
|
2
|
+
import { 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
|
+
* @param sequence - Array of hotkey strings that form the sequence
|
|
37
|
+
* @param callback - Function to call when the sequence is completed
|
|
38
|
+
* @param options - Options for the sequence behavior
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```vue
|
|
42
|
+
* <script setup>
|
|
43
|
+
* import { useHotkeySequence } from '@tanstack/vue-hotkeys'
|
|
44
|
+
*
|
|
45
|
+
* // 'g g' to go to top
|
|
46
|
+
* useHotkeySequence(['G', 'G'], () => {
|
|
47
|
+
* scrollToTop()
|
|
48
|
+
* })
|
|
49
|
+
*
|
|
50
|
+
* // 'd d' to delete line
|
|
51
|
+
* useHotkeySequence(['D', 'D'], () => {
|
|
52
|
+
* deleteLine()
|
|
53
|
+
* })
|
|
54
|
+
*
|
|
55
|
+
* // 'd i w' to delete inner word
|
|
56
|
+
* useHotkeySequence(['D', 'I', 'W'], () => {
|
|
57
|
+
* deleteInnerWord()
|
|
58
|
+
* }, { timeout: 500 })
|
|
59
|
+
* </script>
|
|
60
|
+
*
|
|
61
|
+
* <template>
|
|
62
|
+
* <div>...</div>
|
|
63
|
+
* </template>
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function useHotkeySequence(
|
|
67
|
+
sequence: MaybeRefOrGetter<HotkeySequence>,
|
|
68
|
+
callback: HotkeyCallback,
|
|
69
|
+
options: MaybeRefOrGetter<UseHotkeySequenceOptions> = {},
|
|
70
|
+
): void {
|
|
71
|
+
const defaultOptions = useDefaultHotkeysOptions()
|
|
72
|
+
const manager = getSequenceManager()
|
|
73
|
+
|
|
74
|
+
let registration: SequenceRegistrationHandle | null = null
|
|
75
|
+
|
|
76
|
+
// Watch for changes to reactive dependencies
|
|
77
|
+
const stopWatcher = watch(
|
|
78
|
+
() => {
|
|
79
|
+
const resolvedSequence = resolveMaybeRefOrGetter(sequence)
|
|
80
|
+
const resolvedOptions = resolveMaybeRefOrGetter(options)
|
|
81
|
+
const mergedOptions = {
|
|
82
|
+
...defaultOptions.hotkeySequence,
|
|
83
|
+
...resolvedOptions,
|
|
84
|
+
} as UseHotkeySequenceOptions
|
|
85
|
+
const resolvedEnabled =
|
|
86
|
+
mergedOptions.enabled === undefined
|
|
87
|
+
? undefined
|
|
88
|
+
: resolveMaybeRefOrGetter(mergedOptions.enabled)
|
|
89
|
+
const resolvedTarget =
|
|
90
|
+
mergedOptions.target === undefined
|
|
91
|
+
? undefined
|
|
92
|
+
: resolveMaybeRefOrGetter(mergedOptions.target)
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
resolvedSequence,
|
|
96
|
+
mergedOptions,
|
|
97
|
+
resolvedEnabled,
|
|
98
|
+
resolvedTarget,
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
({ resolvedSequence, mergedOptions, resolvedEnabled, resolvedTarget }) => {
|
|
102
|
+
if (resolvedEnabled === false || resolvedSequence.length === 0) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Resolve target
|
|
107
|
+
const finalTarget =
|
|
108
|
+
resolvedTarget ?? (typeof document !== 'undefined' ? document : null)
|
|
109
|
+
|
|
110
|
+
if (!finalTarget) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Unregister previous registration if it exists
|
|
115
|
+
if (registration?.isActive) {
|
|
116
|
+
registration.unregister()
|
|
117
|
+
registration = null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Extract options without target (target is handled separately)
|
|
121
|
+
const {
|
|
122
|
+
target: _target,
|
|
123
|
+
enabled: _enabled,
|
|
124
|
+
...restOptions
|
|
125
|
+
} = mergedOptions
|
|
126
|
+
const optionsWithoutTarget = {
|
|
127
|
+
...restOptions,
|
|
128
|
+
...(resolvedEnabled === undefined ? {} : { enabled: resolvedEnabled }),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Register the sequence
|
|
132
|
+
registration = manager.register(resolvedSequence, callback, {
|
|
133
|
+
...optionsWithoutTarget,
|
|
134
|
+
target: finalTarget,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Update callback and options
|
|
138
|
+
if (registration.isActive) {
|
|
139
|
+
registration.callback = callback
|
|
140
|
+
registration.setOptions(optionsWithoutTarget)
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{ immediate: true },
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
// Cleanup on unmount
|
|
147
|
+
onUnmounted(() => {
|
|
148
|
+
stopWatcher()
|
|
149
|
+
if (registration?.isActive) {
|
|
150
|
+
registration.unregister()
|
|
151
|
+
registration = null
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {
|
|
157
|
+
return typeof value === 'function' ? (value as () => T)() : unref(value)
|
|
158
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useStore } from '@tanstack/vue-store'
|
|
2
|
+
import { getKeyStateTracker } from '@tanstack/hotkeys'
|
|
3
|
+
import { unref } from 'vue'
|
|
4
|
+
import type { MaybeRefOrGetter, Ref } from 'vue'
|
|
5
|
+
import type { HeldKey } from '@tanstack/hotkeys'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Vue composable that returns a reactive ref indicating whether a specific key is currently being held.
|
|
9
|
+
*
|
|
10
|
+
* This composable uses `useStore` from `@tanstack/vue-store` to subscribe
|
|
11
|
+
* to the global KeyStateTracker and uses a selector to determine if
|
|
12
|
+
* the specified key is held.
|
|
13
|
+
*
|
|
14
|
+
* @param key - The key to check (e.g., 'Shift', 'Control', 'A')
|
|
15
|
+
* @returns Reactive ref that is true if the key is currently held down
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```vue
|
|
19
|
+
* <script setup>
|
|
20
|
+
* import { useKeyHold } from '@tanstack/vue-hotkeys'
|
|
21
|
+
*
|
|
22
|
+
* const isShiftHeld = useKeyHold('Shift')
|
|
23
|
+
* </script>
|
|
24
|
+
*
|
|
25
|
+
* <template>
|
|
26
|
+
* <div :style="{ opacity: isShiftHeld ? 1 : 0.5 }">
|
|
27
|
+
* {{ isShiftHeld ? 'Shift is pressed!' : 'Press Shift' }}
|
|
28
|
+
* </div>
|
|
29
|
+
* </template>
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```vue
|
|
34
|
+
* <script setup>
|
|
35
|
+
* import { useKeyHold } from '@tanstack/vue-hotkeys'
|
|
36
|
+
*
|
|
37
|
+
* const ctrl = useKeyHold('Control')
|
|
38
|
+
* const shift = useKeyHold('Shift')
|
|
39
|
+
* const alt = useKeyHold('Alt')
|
|
40
|
+
* </script>
|
|
41
|
+
*
|
|
42
|
+
* <template>
|
|
43
|
+
* <div>
|
|
44
|
+
* <span :style="{ opacity: ctrl ? 1 : 0.3 }">Ctrl</span>
|
|
45
|
+
* <span :style="{ opacity: shift ? 1 : 0.3 }">Shift</span>
|
|
46
|
+
* <span :style="{ opacity: alt ? 1 : 0.3 }">Alt</span>
|
|
47
|
+
* </div>
|
|
48
|
+
* </template>
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function useKeyHold(key: MaybeRefOrGetter<HeldKey>): Ref<boolean> {
|
|
52
|
+
const tracker = getKeyStateTracker()
|
|
53
|
+
|
|
54
|
+
const isHeld = useStore(tracker.store, (state) => {
|
|
55
|
+
const keyValue = unref(key)
|
|
56
|
+
const normalizedKey = (
|
|
57
|
+
typeof keyValue === 'string' ? keyValue : String(keyValue)
|
|
58
|
+
).toLowerCase()
|
|
59
|
+
return state.heldKeys.some(
|
|
60
|
+
(heldKey) => heldKey.toLowerCase() === normalizedKey,
|
|
61
|
+
)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return isHeld as Ref<boolean>
|
|
65
|
+
}
|