@tanstack/vue-hotkeys 0.6.0 → 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.
- 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 +22 -5
- package/dist/useHotkey.cjs.map +1 -1
- package/dist/useHotkey.js +22 -5
- 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/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/useHotkey.ts +31 -9
- package/src/useHotkeySequence.ts +34 -15
- package/src/useHotkeySequences.ts +221 -0
package/dist/index.cjs
CHANGED
|
@@ -7,6 +7,7 @@ const require_useHeldKeys = require('./useHeldKeys.cjs');
|
|
|
7
7
|
const require_useHeldKeyCodes = require('./useHeldKeyCodes.cjs');
|
|
8
8
|
const require_useKeyHold = require('./useKeyHold.cjs');
|
|
9
9
|
const require_useHotkeySequence = require('./useHotkeySequence.cjs');
|
|
10
|
+
const require_useHotkeySequences = require('./useHotkeySequences.cjs');
|
|
10
11
|
const require_useHotkeyRecorder = require('./useHotkeyRecorder.cjs');
|
|
11
12
|
const require_useHotkeySequenceRecorder = require('./useHotkeySequenceRecorder.cjs');
|
|
12
13
|
|
|
@@ -19,6 +20,7 @@ exports.useHotkey = require_useHotkey.useHotkey;
|
|
|
19
20
|
exports.useHotkeyRecorder = require_useHotkeyRecorder.useHotkeyRecorder;
|
|
20
21
|
exports.useHotkeySequence = require_useHotkeySequence.useHotkeySequence;
|
|
21
22
|
exports.useHotkeySequenceRecorder = require_useHotkeySequenceRecorder.useHotkeySequenceRecorder;
|
|
23
|
+
exports.useHotkeySequences = require_useHotkeySequences.useHotkeySequences;
|
|
22
24
|
exports.useHotkeys = require_useHotkeys.useHotkeys;
|
|
23
25
|
exports.useHotkeysContext = require_HotkeysProviderContext.useHotkeysContext;
|
|
24
26
|
exports.useKeyHold = require_useKeyHold.useKeyHold;
|
package/dist/index.d.cts
CHANGED
|
@@ -6,7 +6,8 @@ import { UseHotkeyDefinition, useHotkeys } from "./useHotkeys.cjs";
|
|
|
6
6
|
import { useHeldKeys } from "./useHeldKeys.cjs";
|
|
7
7
|
import { useHeldKeyCodes } from "./useHeldKeyCodes.cjs";
|
|
8
8
|
import { useKeyHold } from "./useKeyHold.cjs";
|
|
9
|
+
import { UseHotkeySequenceDefinition, useHotkeySequences } from "./useHotkeySequences.cjs";
|
|
9
10
|
import { VueHotkeyRecorder, useHotkeyRecorder } from "./useHotkeyRecorder.cjs";
|
|
10
11
|
import { VueHotkeySequenceRecorder, useHotkeySequenceRecorder } from "./useHotkeySequenceRecorder.cjs";
|
|
11
12
|
export * from "@tanstack/hotkeys";
|
|
12
|
-
export { HotkeysProvider, HotkeysProviderOptions, UseHotkeyDefinition, UseHotkeyOptions, UseHotkeySequenceOptions, VueHotkeyRecorder, VueHotkeySequenceRecorder, provideHotkeysContext, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyRecorder, useHotkeySequence, useHotkeySequenceRecorder, useHotkeys, useHotkeysContext, useKeyHold };
|
|
13
|
+
export { HotkeysProvider, HotkeysProviderOptions, UseHotkeyDefinition, UseHotkeyOptions, UseHotkeySequenceDefinition, UseHotkeySequenceOptions, VueHotkeyRecorder, VueHotkeySequenceRecorder, provideHotkeysContext, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyRecorder, useHotkeySequence, useHotkeySequenceRecorder, useHotkeySequences, useHotkeys, useHotkeysContext, useKeyHold };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { UseHotkeyDefinition, useHotkeys } from "./useHotkeys.js";
|
|
|
6
6
|
import { useHeldKeys } from "./useHeldKeys.js";
|
|
7
7
|
import { useHeldKeyCodes } from "./useHeldKeyCodes.js";
|
|
8
8
|
import { useKeyHold } from "./useKeyHold.js";
|
|
9
|
+
import { UseHotkeySequenceDefinition, useHotkeySequences } from "./useHotkeySequences.js";
|
|
9
10
|
import { VueHotkeyRecorder, useHotkeyRecorder } from "./useHotkeyRecorder.js";
|
|
10
11
|
import { VueHotkeySequenceRecorder, useHotkeySequenceRecorder } from "./useHotkeySequenceRecorder.js";
|
|
11
12
|
export * from "@tanstack/hotkeys";
|
|
12
|
-
export { HotkeysProvider, HotkeysProviderOptions, UseHotkeyDefinition, UseHotkeyOptions, UseHotkeySequenceOptions, VueHotkeyRecorder, VueHotkeySequenceRecorder, provideHotkeysContext, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyRecorder, useHotkeySequence, useHotkeySequenceRecorder, useHotkeys, useHotkeysContext, useKeyHold };
|
|
13
|
+
export { HotkeysProvider, HotkeysProviderOptions, UseHotkeyDefinition, UseHotkeyOptions, UseHotkeySequenceDefinition, UseHotkeySequenceOptions, VueHotkeyRecorder, VueHotkeySequenceRecorder, provideHotkeysContext, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyRecorder, useHotkeySequence, useHotkeySequenceRecorder, useHotkeySequences, useHotkeys, useHotkeysContext, useKeyHold };
|
package/dist/index.js
CHANGED
|
@@ -6,9 +6,10 @@ import { useHeldKeys } from "./useHeldKeys.js";
|
|
|
6
6
|
import { useHeldKeyCodes } from "./useHeldKeyCodes.js";
|
|
7
7
|
import { useKeyHold } from "./useKeyHold.js";
|
|
8
8
|
import { useHotkeySequence } from "./useHotkeySequence.js";
|
|
9
|
+
import { useHotkeySequences } from "./useHotkeySequences.js";
|
|
9
10
|
import { useHotkeyRecorder } from "./useHotkeyRecorder.js";
|
|
10
11
|
import { useHotkeySequenceRecorder } from "./useHotkeySequenceRecorder.js";
|
|
11
12
|
|
|
12
13
|
export * from "@tanstack/hotkeys"
|
|
13
14
|
|
|
14
|
-
export { HotkeysProvider, provideHotkeysContext, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyRecorder, useHotkeySequence, useHotkeySequenceRecorder, useHotkeys, useHotkeysContext, useKeyHold };
|
|
15
|
+
export { HotkeysProvider, provideHotkeysContext, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyRecorder, useHotkeySequence, useHotkeySequenceRecorder, useHotkeySequences, useHotkeys, useHotkeysContext, useKeyHold };
|
package/dist/useHotkey.cjs
CHANGED
|
@@ -71,6 +71,8 @@ function useHotkey(hotkey, callback, options = {}) {
|
|
|
71
71
|
const defaultOptions = require_HotkeysProviderContext.useDefaultHotkeysOptions();
|
|
72
72
|
const manager = (0, _tanstack_hotkeys.getHotkeyManager)();
|
|
73
73
|
let registration = null;
|
|
74
|
+
let lastHotkeyString = null;
|
|
75
|
+
let lastTarget = null;
|
|
74
76
|
const stopWatcher = (0, vue.watch)(() => {
|
|
75
77
|
const resolvedHotkey = resolveMaybeRefOrGetter(hotkey);
|
|
76
78
|
const resolvedOptions = resolveMaybeRefOrGetter(options);
|
|
@@ -87,17 +89,30 @@ function useHotkey(hotkey, callback, options = {}) {
|
|
|
87
89
|
}, ({ resolvedHotkey, mergedOptions, resolvedEnabled, resolvedTarget }) => {
|
|
88
90
|
const platform = mergedOptions.platform ?? (0, _tanstack_hotkeys.detectPlatform)();
|
|
89
91
|
const hotkeyString = typeof resolvedHotkey === "string" ? resolvedHotkey : (0, _tanstack_hotkeys.formatHotkey)((0, _tanstack_hotkeys.rawHotkeyToParsedHotkey)(resolvedHotkey, platform));
|
|
90
|
-
const finalTarget = resolvedTarget
|
|
91
|
-
if (!finalTarget)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
const finalTarget = resolvedTarget === void 0 ? typeof document !== "undefined" ? document : null : resolvedTarget;
|
|
93
|
+
if (!finalTarget) {
|
|
94
|
+
if (registration?.isActive) {
|
|
95
|
+
registration.unregister();
|
|
96
|
+
registration = null;
|
|
97
|
+
}
|
|
98
|
+
lastHotkeyString = null;
|
|
99
|
+
lastTarget = null;
|
|
100
|
+
return;
|
|
95
101
|
}
|
|
96
102
|
const { target: _target, enabled: _enabled, ...restOptions } = mergedOptions;
|
|
97
103
|
const optionsWithoutTarget = {
|
|
98
104
|
...restOptions,
|
|
99
105
|
...resolvedEnabled === void 0 ? {} : { enabled: resolvedEnabled }
|
|
100
106
|
};
|
|
107
|
+
if (registration?.isActive && lastHotkeyString === hotkeyString && lastTarget === finalTarget) {
|
|
108
|
+
registration.callback = callback;
|
|
109
|
+
registration.setOptions(optionsWithoutTarget);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (registration?.isActive) {
|
|
113
|
+
registration.unregister();
|
|
114
|
+
registration = null;
|
|
115
|
+
}
|
|
101
116
|
registration = manager.register(hotkeyString, callback, {
|
|
102
117
|
...optionsWithoutTarget,
|
|
103
118
|
target: finalTarget
|
|
@@ -106,6 +121,8 @@ function useHotkey(hotkey, callback, options = {}) {
|
|
|
106
121
|
registration.callback = callback;
|
|
107
122
|
registration.setOptions(optionsWithoutTarget);
|
|
108
123
|
}
|
|
124
|
+
lastHotkeyString = hotkeyString;
|
|
125
|
+
lastTarget = finalTarget;
|
|
109
126
|
}, { immediate: true });
|
|
110
127
|
(0, vue.onUnmounted)(() => {
|
|
111
128
|
stopWatcher();
|
package/dist/useHotkey.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHotkey.cjs","names":["useDefaultHotkeysOptions"],"sources":["../src/useHotkey.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport {\n detectPlatform,\n formatHotkey,\n getHotkeyManager,\n rawHotkeyToParsedHotkey,\n} from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyOptions,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport type { MaybeRefOrGetter } from 'vue'\n\nexport interface UseHotkeyOptions extends Omit<\n HotkeyOptions,\n 'enabled' | 'target'\n> {\n /**\n * Whether the hotkey is active.\n * Can be a Ref, a getter function, or a boolean value.\n * Defaults to true.\n */\n enabled?: MaybeRefOrGetter<boolean>\n /**\n * The DOM element to attach the event listener to.\n * Can be a Ref, a getter function, direct DOM element, or null.\n * Defaults to document.\n */\n target?: MaybeRefOrGetter<HTMLElement | Document | Window | null>\n}\n\n/**\n * Vue composable for registering a keyboard hotkey.\n *\n * Uses the singleton HotkeyManager for efficient event handling.\n * The callback receives both the keyboard event and a context object\n * containing the hotkey string and parsed hotkey.\n *\n * This composable automatically tracks reactive dependencies and updates\n * the registration when options or the callback change.\n *\n * @param hotkey - The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object (supports `mod` for cross-platform)\n * @param callback - The function to call when the hotkey is pressed\n * @param options - Options for the hotkey behavior\n *\n * @example\n * ```vue\n * <script setup>\n * import { ref } from 'vue'\n * import { useHotkey } from '@tanstack/vue-hotkeys'\n *\n * const count = ref(0)\n *\n * // Callback always has access to latest count value\n * useHotkey('Mod+S', (event, { hotkey }) => {\n * console.log(`Save triggered, count is ${count.value}`)\n * handleSave()\n * })\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { ref } from 'vue'\n * import { useHotkey } from '@tanstack/vue-hotkeys'\n *\n * const isOpen = ref(false)\n *\n * // enabled option is reactive\n * useHotkey('Escape', () => {\n * isOpen.value = false\n * }, { enabled: isOpen })\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { ref } from 'vue'\n * import { useHotkey } from '@tanstack/vue-hotkeys'\n *\n * const editorRef = ref<HTMLDivElement | null>(null)\n *\n * // Scoped to a specific element\n * useHotkey('Mod+S', () => {\n * save()\n * }, { target: editorRef })\n * </script>\n *\n * <template>\n * <div ref=\"editorRef\">...</div>\n * </template>\n * ```\n */\nexport function useHotkey(\n hotkey: MaybeRefOrGetter<RegisterableHotkey>,\n callback: HotkeyCallback,\n options: MaybeRefOrGetter<UseHotkeyOptions> = {},\n): void {\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n\n let registration: HotkeyRegistrationHandle | null = null\n\n // Watch for changes to reactive dependencies\n const stopWatcher = watch(\n () => {\n const resolvedHotkey = resolveMaybeRefOrGetter(hotkey)\n const resolvedOptions = resolveMaybeRefOrGetter(options)\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedOptions,\n } as UseHotkeyOptions\n const resolvedEnabled =\n mergedOptions.enabled === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.enabled)\n const resolvedTarget =\n mergedOptions.target === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.target)\n\n return {\n resolvedHotkey,\n mergedOptions,\n resolvedEnabled,\n resolvedTarget,\n }\n },\n ({ resolvedHotkey, mergedOptions, resolvedEnabled, resolvedTarget }) => {\n // Normalize to hotkey string\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString: Hotkey =\n typeof resolvedHotkey === 'string'\n ? resolvedHotkey\n : (formatHotkey(\n rawHotkeyToParsedHotkey(resolvedHotkey as any, platform),\n ) as Hotkey)\n\n // Resolve target\n const finalTarget =\n resolvedTarget
|
|
1
|
+
{"version":3,"file":"useHotkey.cjs","names":["useDefaultHotkeysOptions"],"sources":["../src/useHotkey.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport {\n detectPlatform,\n formatHotkey,\n getHotkeyManager,\n rawHotkeyToParsedHotkey,\n} from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyOptions,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport type { MaybeRefOrGetter } from 'vue'\n\nexport interface UseHotkeyOptions extends Omit<\n HotkeyOptions,\n 'enabled' | 'target'\n> {\n /**\n * Whether the hotkey is active.\n * Can be a Ref, a getter function, or a boolean value.\n * Defaults to true.\n */\n enabled?: MaybeRefOrGetter<boolean>\n /**\n * The DOM element to attach the event listener to.\n * Can be a Ref, a getter function, direct DOM element, or null.\n * Defaults to document.\n */\n target?: MaybeRefOrGetter<HTMLElement | Document | Window | null>\n}\n\n/**\n * Vue composable for registering a keyboard hotkey.\n *\n * Uses the singleton HotkeyManager for efficient event handling.\n * The callback receives both the keyboard event and a context object\n * containing the hotkey string and parsed hotkey.\n *\n * This composable automatically tracks reactive dependencies and updates\n * the registration when options or the callback change.\n *\n * @param hotkey - The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object (supports `mod` for cross-platform)\n * @param callback - The function to call when the hotkey is pressed\n * @param options - Options for the hotkey behavior\n *\n * @example\n * ```vue\n * <script setup>\n * import { ref } from 'vue'\n * import { useHotkey } from '@tanstack/vue-hotkeys'\n *\n * const count = ref(0)\n *\n * // Callback always has access to latest count value\n * useHotkey('Mod+S', (event, { hotkey }) => {\n * console.log(`Save triggered, count is ${count.value}`)\n * handleSave()\n * })\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { ref } from 'vue'\n * import { useHotkey } from '@tanstack/vue-hotkeys'\n *\n * const isOpen = ref(false)\n *\n * // enabled option is reactive\n * useHotkey('Escape', () => {\n * isOpen.value = false\n * }, { enabled: isOpen })\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { ref } from 'vue'\n * import { useHotkey } from '@tanstack/vue-hotkeys'\n *\n * const editorRef = ref<HTMLDivElement | null>(null)\n *\n * // Scoped to a specific element\n * useHotkey('Mod+S', () => {\n * save()\n * }, { target: editorRef })\n * </script>\n *\n * <template>\n * <div ref=\"editorRef\">...</div>\n * </template>\n * ```\n */\nexport function useHotkey(\n hotkey: MaybeRefOrGetter<RegisterableHotkey>,\n callback: HotkeyCallback,\n options: MaybeRefOrGetter<UseHotkeyOptions> = {},\n): void {\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n\n let registration: HotkeyRegistrationHandle | null = null\n let lastHotkeyString: Hotkey | null = null\n let lastTarget: HTMLElement | Document | Window | null = null\n\n // Watch for changes to reactive dependencies\n const stopWatcher = watch(\n () => {\n const resolvedHotkey = resolveMaybeRefOrGetter(hotkey)\n const resolvedOptions = resolveMaybeRefOrGetter(options)\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedOptions,\n } as UseHotkeyOptions\n const resolvedEnabled =\n mergedOptions.enabled === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.enabled)\n const resolvedTarget =\n mergedOptions.target === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.target)\n\n return {\n resolvedHotkey,\n mergedOptions,\n resolvedEnabled,\n resolvedTarget,\n }\n },\n ({ resolvedHotkey, mergedOptions, resolvedEnabled, resolvedTarget }) => {\n // Normalize to hotkey string\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString: Hotkey =\n typeof resolvedHotkey === 'string'\n ? resolvedHotkey\n : (formatHotkey(\n rawHotkeyToParsedHotkey(resolvedHotkey as any, platform),\n ) as Hotkey)\n\n // Resolve target\n const finalTarget =\n resolvedTarget === undefined\n ? typeof document !== 'undefined'\n ? document\n : null\n : resolvedTarget\n\n if (!finalTarget) {\n if (registration?.isActive) {\n registration.unregister()\n registration = null\n }\n lastHotkeyString = null\n lastTarget = null\n return\n }\n\n // Extract options without target (target is handled separately)\n const {\n target: _target,\n enabled: _enabled,\n ...restOptions\n } = mergedOptions\n const optionsWithoutTarget = {\n ...restOptions,\n ...(resolvedEnabled === undefined ? {} : { enabled: resolvedEnabled }),\n }\n\n if (\n registration?.isActive &&\n lastHotkeyString === hotkeyString &&\n lastTarget === finalTarget\n ) {\n registration.callback = callback\n registration.setOptions(optionsWithoutTarget)\n return\n }\n\n if (registration?.isActive) {\n registration.unregister()\n registration = null\n }\n\n registration = manager.register(hotkeyString, callback, {\n ...optionsWithoutTarget,\n target: finalTarget,\n })\n\n if (registration.isActive) {\n registration.callback = callback\n registration.setOptions(optionsWithoutTarget)\n }\n\n lastHotkeyString = hotkeyString\n lastTarget = finalTarget\n },\n { immediate: true },\n )\n\n // Cleanup on unmount\n onUnmounted(() => {\n stopWatcher()\n if (registration?.isActive) {\n registration.unregister()\n registration = null\n }\n })\n}\n\nfunction resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {\n return typeof value === 'function' ? (value as () => T)() : unref(value)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmGA,SAAgB,UACd,QACA,UACA,UAA8C,EAAE,EAC1C;CACN,MAAM,iBAAiBA,yDAA0B;CACjD,MAAM,mDAA4B;CAElC,IAAI,eAAgD;CACpD,IAAI,mBAAkC;CACtC,IAAI,aAAqD;CAGzD,MAAM,mCACE;EACJ,MAAM,iBAAiB,wBAAwB,OAAO;EACtD,MAAM,kBAAkB,wBAAwB,QAAQ;EACxD,MAAM,gBAAgB;GACpB,GAAG,eAAe;GAClB,GAAG;GACJ;AAUD,SAAO;GACL;GACA;GACA,iBAXA,cAAc,YAAY,SACtB,SACA,wBAAwB,cAAc,QAAQ;GAUlD,gBARA,cAAc,WAAW,SACrB,SACA,wBAAwB,cAAc,OAAO;GAOlD;KAEF,EAAE,gBAAgB,eAAe,iBAAiB,qBAAqB;EAEtE,MAAM,WAAW,cAAc,mDAA4B;EAC3D,MAAM,eACJ,OAAO,mBAAmB,WACtB,oGAE0B,gBAAuB,SAAS,CACzD;EAGP,MAAM,cACJ,mBAAmB,SACf,OAAO,aAAa,cAClB,WACA,OACF;AAEN,MAAI,CAAC,aAAa;AAChB,OAAI,cAAc,UAAU;AAC1B,iBAAa,YAAY;AACzB,mBAAe;;AAEjB,sBAAmB;AACnB,gBAAa;AACb;;EAIF,MAAM,EACJ,QAAQ,SACR,SAAS,UACT,GAAG,gBACD;EACJ,MAAM,uBAAuB;GAC3B,GAAG;GACH,GAAI,oBAAoB,SAAY,EAAE,GAAG,EAAE,SAAS,iBAAiB;GACtE;AAED,MACE,cAAc,YACd,qBAAqB,gBACrB,eAAe,aACf;AACA,gBAAa,WAAW;AACxB,gBAAa,WAAW,qBAAqB;AAC7C;;AAGF,MAAI,cAAc,UAAU;AAC1B,gBAAa,YAAY;AACzB,kBAAe;;AAGjB,iBAAe,QAAQ,SAAS,cAAc,UAAU;GACtD,GAAG;GACH,QAAQ;GACT,CAAC;AAEF,MAAI,aAAa,UAAU;AACzB,gBAAa,WAAW;AACxB,gBAAa,WAAW,qBAAqB;;AAG/C,qBAAmB;AACnB,eAAa;IAEf,EAAE,WAAW,MAAM,CACpB;AAGD,4BAAkB;AAChB,eAAa;AACb,MAAI,cAAc,UAAU;AAC1B,gBAAa,YAAY;AACzB,kBAAe;;GAEjB;;AAGJ,SAAS,wBAA2B,OAA+B;AACjE,QAAO,OAAO,UAAU,aAAc,OAAmB,kBAAS,MAAM"}
|
package/dist/useHotkey.js
CHANGED
|
@@ -71,6 +71,8 @@ function useHotkey(hotkey, callback, options = {}) {
|
|
|
71
71
|
const defaultOptions = useDefaultHotkeysOptions();
|
|
72
72
|
const manager = getHotkeyManager();
|
|
73
73
|
let registration = null;
|
|
74
|
+
let lastHotkeyString = null;
|
|
75
|
+
let lastTarget = null;
|
|
74
76
|
const stopWatcher = watch(() => {
|
|
75
77
|
const resolvedHotkey = resolveMaybeRefOrGetter(hotkey);
|
|
76
78
|
const resolvedOptions = resolveMaybeRefOrGetter(options);
|
|
@@ -87,17 +89,30 @@ function useHotkey(hotkey, callback, options = {}) {
|
|
|
87
89
|
}, ({ resolvedHotkey, mergedOptions, resolvedEnabled, resolvedTarget }) => {
|
|
88
90
|
const platform = mergedOptions.platform ?? detectPlatform();
|
|
89
91
|
const hotkeyString = typeof resolvedHotkey === "string" ? resolvedHotkey : formatHotkey(rawHotkeyToParsedHotkey(resolvedHotkey, platform));
|
|
90
|
-
const finalTarget = resolvedTarget
|
|
91
|
-
if (!finalTarget)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
const finalTarget = resolvedTarget === void 0 ? typeof document !== "undefined" ? document : null : resolvedTarget;
|
|
93
|
+
if (!finalTarget) {
|
|
94
|
+
if (registration?.isActive) {
|
|
95
|
+
registration.unregister();
|
|
96
|
+
registration = null;
|
|
97
|
+
}
|
|
98
|
+
lastHotkeyString = null;
|
|
99
|
+
lastTarget = null;
|
|
100
|
+
return;
|
|
95
101
|
}
|
|
96
102
|
const { target: _target, enabled: _enabled, ...restOptions } = mergedOptions;
|
|
97
103
|
const optionsWithoutTarget = {
|
|
98
104
|
...restOptions,
|
|
99
105
|
...resolvedEnabled === void 0 ? {} : { enabled: resolvedEnabled }
|
|
100
106
|
};
|
|
107
|
+
if (registration?.isActive && lastHotkeyString === hotkeyString && lastTarget === finalTarget) {
|
|
108
|
+
registration.callback = callback;
|
|
109
|
+
registration.setOptions(optionsWithoutTarget);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (registration?.isActive) {
|
|
113
|
+
registration.unregister();
|
|
114
|
+
registration = null;
|
|
115
|
+
}
|
|
101
116
|
registration = manager.register(hotkeyString, callback, {
|
|
102
117
|
...optionsWithoutTarget,
|
|
103
118
|
target: finalTarget
|
|
@@ -106,6 +121,8 @@ function useHotkey(hotkey, callback, options = {}) {
|
|
|
106
121
|
registration.callback = callback;
|
|
107
122
|
registration.setOptions(optionsWithoutTarget);
|
|
108
123
|
}
|
|
124
|
+
lastHotkeyString = hotkeyString;
|
|
125
|
+
lastTarget = finalTarget;
|
|
109
126
|
}, { immediate: true });
|
|
110
127
|
onUnmounted(() => {
|
|
111
128
|
stopWatcher();
|
package/dist/useHotkey.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHotkey.js","names":[],"sources":["../src/useHotkey.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport {\n detectPlatform,\n formatHotkey,\n getHotkeyManager,\n rawHotkeyToParsedHotkey,\n} from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyOptions,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport type { MaybeRefOrGetter } from 'vue'\n\nexport interface UseHotkeyOptions extends Omit<\n HotkeyOptions,\n 'enabled' | 'target'\n> {\n /**\n * Whether the hotkey is active.\n * Can be a Ref, a getter function, or a boolean value.\n * Defaults to true.\n */\n enabled?: MaybeRefOrGetter<boolean>\n /**\n * The DOM element to attach the event listener to.\n * Can be a Ref, a getter function, direct DOM element, or null.\n * Defaults to document.\n */\n target?: MaybeRefOrGetter<HTMLElement | Document | Window | null>\n}\n\n/**\n * Vue composable for registering a keyboard hotkey.\n *\n * Uses the singleton HotkeyManager for efficient event handling.\n * The callback receives both the keyboard event and a context object\n * containing the hotkey string and parsed hotkey.\n *\n * This composable automatically tracks reactive dependencies and updates\n * the registration when options or the callback change.\n *\n * @param hotkey - The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object (supports `mod` for cross-platform)\n * @param callback - The function to call when the hotkey is pressed\n * @param options - Options for the hotkey behavior\n *\n * @example\n * ```vue\n * <script setup>\n * import { ref } from 'vue'\n * import { useHotkey } from '@tanstack/vue-hotkeys'\n *\n * const count = ref(0)\n *\n * // Callback always has access to latest count value\n * useHotkey('Mod+S', (event, { hotkey }) => {\n * console.log(`Save triggered, count is ${count.value}`)\n * handleSave()\n * })\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { ref } from 'vue'\n * import { useHotkey } from '@tanstack/vue-hotkeys'\n *\n * const isOpen = ref(false)\n *\n * // enabled option is reactive\n * useHotkey('Escape', () => {\n * isOpen.value = false\n * }, { enabled: isOpen })\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { ref } from 'vue'\n * import { useHotkey } from '@tanstack/vue-hotkeys'\n *\n * const editorRef = ref<HTMLDivElement | null>(null)\n *\n * // Scoped to a specific element\n * useHotkey('Mod+S', () => {\n * save()\n * }, { target: editorRef })\n * </script>\n *\n * <template>\n * <div ref=\"editorRef\">...</div>\n * </template>\n * ```\n */\nexport function useHotkey(\n hotkey: MaybeRefOrGetter<RegisterableHotkey>,\n callback: HotkeyCallback,\n options: MaybeRefOrGetter<UseHotkeyOptions> = {},\n): void {\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n\n let registration: HotkeyRegistrationHandle | null = null\n\n // Watch for changes to reactive dependencies\n const stopWatcher = watch(\n () => {\n const resolvedHotkey = resolveMaybeRefOrGetter(hotkey)\n const resolvedOptions = resolveMaybeRefOrGetter(options)\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedOptions,\n } as UseHotkeyOptions\n const resolvedEnabled =\n mergedOptions.enabled === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.enabled)\n const resolvedTarget =\n mergedOptions.target === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.target)\n\n return {\n resolvedHotkey,\n mergedOptions,\n resolvedEnabled,\n resolvedTarget,\n }\n },\n ({ resolvedHotkey, mergedOptions, resolvedEnabled, resolvedTarget }) => {\n // Normalize to hotkey string\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString: Hotkey =\n typeof resolvedHotkey === 'string'\n ? resolvedHotkey\n : (formatHotkey(\n rawHotkeyToParsedHotkey(resolvedHotkey as any, platform),\n ) as Hotkey)\n\n // Resolve target\n const finalTarget =\n resolvedTarget
|
|
1
|
+
{"version":3,"file":"useHotkey.js","names":[],"sources":["../src/useHotkey.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport {\n detectPlatform,\n formatHotkey,\n getHotkeyManager,\n rawHotkeyToParsedHotkey,\n} from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyOptions,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport type { MaybeRefOrGetter } from 'vue'\n\nexport interface UseHotkeyOptions extends Omit<\n HotkeyOptions,\n 'enabled' | 'target'\n> {\n /**\n * Whether the hotkey is active.\n * Can be a Ref, a getter function, or a boolean value.\n * Defaults to true.\n */\n enabled?: MaybeRefOrGetter<boolean>\n /**\n * The DOM element to attach the event listener to.\n * Can be a Ref, a getter function, direct DOM element, or null.\n * Defaults to document.\n */\n target?: MaybeRefOrGetter<HTMLElement | Document | Window | null>\n}\n\n/**\n * Vue composable for registering a keyboard hotkey.\n *\n * Uses the singleton HotkeyManager for efficient event handling.\n * The callback receives both the keyboard event and a context object\n * containing the hotkey string and parsed hotkey.\n *\n * This composable automatically tracks reactive dependencies and updates\n * the registration when options or the callback change.\n *\n * @param hotkey - The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object (supports `mod` for cross-platform)\n * @param callback - The function to call when the hotkey is pressed\n * @param options - Options for the hotkey behavior\n *\n * @example\n * ```vue\n * <script setup>\n * import { ref } from 'vue'\n * import { useHotkey } from '@tanstack/vue-hotkeys'\n *\n * const count = ref(0)\n *\n * // Callback always has access to latest count value\n * useHotkey('Mod+S', (event, { hotkey }) => {\n * console.log(`Save triggered, count is ${count.value}`)\n * handleSave()\n * })\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { ref } from 'vue'\n * import { useHotkey } from '@tanstack/vue-hotkeys'\n *\n * const isOpen = ref(false)\n *\n * // enabled option is reactive\n * useHotkey('Escape', () => {\n * isOpen.value = false\n * }, { enabled: isOpen })\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { ref } from 'vue'\n * import { useHotkey } from '@tanstack/vue-hotkeys'\n *\n * const editorRef = ref<HTMLDivElement | null>(null)\n *\n * // Scoped to a specific element\n * useHotkey('Mod+S', () => {\n * save()\n * }, { target: editorRef })\n * </script>\n *\n * <template>\n * <div ref=\"editorRef\">...</div>\n * </template>\n * ```\n */\nexport function useHotkey(\n hotkey: MaybeRefOrGetter<RegisterableHotkey>,\n callback: HotkeyCallback,\n options: MaybeRefOrGetter<UseHotkeyOptions> = {},\n): void {\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n\n let registration: HotkeyRegistrationHandle | null = null\n let lastHotkeyString: Hotkey | null = null\n let lastTarget: HTMLElement | Document | Window | null = null\n\n // Watch for changes to reactive dependencies\n const stopWatcher = watch(\n () => {\n const resolvedHotkey = resolveMaybeRefOrGetter(hotkey)\n const resolvedOptions = resolveMaybeRefOrGetter(options)\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedOptions,\n } as UseHotkeyOptions\n const resolvedEnabled =\n mergedOptions.enabled === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.enabled)\n const resolvedTarget =\n mergedOptions.target === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.target)\n\n return {\n resolvedHotkey,\n mergedOptions,\n resolvedEnabled,\n resolvedTarget,\n }\n },\n ({ resolvedHotkey, mergedOptions, resolvedEnabled, resolvedTarget }) => {\n // Normalize to hotkey string\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString: Hotkey =\n typeof resolvedHotkey === 'string'\n ? resolvedHotkey\n : (formatHotkey(\n rawHotkeyToParsedHotkey(resolvedHotkey as any, platform),\n ) as Hotkey)\n\n // Resolve target\n const finalTarget =\n resolvedTarget === undefined\n ? typeof document !== 'undefined'\n ? document\n : null\n : resolvedTarget\n\n if (!finalTarget) {\n if (registration?.isActive) {\n registration.unregister()\n registration = null\n }\n lastHotkeyString = null\n lastTarget = null\n return\n }\n\n // Extract options without target (target is handled separately)\n const {\n target: _target,\n enabled: _enabled,\n ...restOptions\n } = mergedOptions\n const optionsWithoutTarget = {\n ...restOptions,\n ...(resolvedEnabled === undefined ? {} : { enabled: resolvedEnabled }),\n }\n\n if (\n registration?.isActive &&\n lastHotkeyString === hotkeyString &&\n lastTarget === finalTarget\n ) {\n registration.callback = callback\n registration.setOptions(optionsWithoutTarget)\n return\n }\n\n if (registration?.isActive) {\n registration.unregister()\n registration = null\n }\n\n registration = manager.register(hotkeyString, callback, {\n ...optionsWithoutTarget,\n target: finalTarget,\n })\n\n if (registration.isActive) {\n registration.callback = callback\n registration.setOptions(optionsWithoutTarget)\n }\n\n lastHotkeyString = hotkeyString\n lastTarget = finalTarget\n },\n { immediate: true },\n )\n\n // Cleanup on unmount\n onUnmounted(() => {\n stopWatcher()\n if (registration?.isActive) {\n registration.unregister()\n registration = null\n }\n })\n}\n\nfunction resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {\n return typeof value === 'function' ? (value as () => T)() : unref(value)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmGA,SAAgB,UACd,QACA,UACA,UAA8C,EAAE,EAC1C;CACN,MAAM,iBAAiB,0BAA0B;CACjD,MAAM,UAAU,kBAAkB;CAElC,IAAI,eAAgD;CACpD,IAAI,mBAAkC;CACtC,IAAI,aAAqD;CAGzD,MAAM,cAAc,YACZ;EACJ,MAAM,iBAAiB,wBAAwB,OAAO;EACtD,MAAM,kBAAkB,wBAAwB,QAAQ;EACxD,MAAM,gBAAgB;GACpB,GAAG,eAAe;GAClB,GAAG;GACJ;AAUD,SAAO;GACL;GACA;GACA,iBAXA,cAAc,YAAY,SACtB,SACA,wBAAwB,cAAc,QAAQ;GAUlD,gBARA,cAAc,WAAW,SACrB,SACA,wBAAwB,cAAc,OAAO;GAOlD;KAEF,EAAE,gBAAgB,eAAe,iBAAiB,qBAAqB;EAEtE,MAAM,WAAW,cAAc,YAAY,gBAAgB;EAC3D,MAAM,eACJ,OAAO,mBAAmB,WACtB,iBACC,aACC,wBAAwB,gBAAuB,SAAS,CACzD;EAGP,MAAM,cACJ,mBAAmB,SACf,OAAO,aAAa,cAClB,WACA,OACF;AAEN,MAAI,CAAC,aAAa;AAChB,OAAI,cAAc,UAAU;AAC1B,iBAAa,YAAY;AACzB,mBAAe;;AAEjB,sBAAmB;AACnB,gBAAa;AACb;;EAIF,MAAM,EACJ,QAAQ,SACR,SAAS,UACT,GAAG,gBACD;EACJ,MAAM,uBAAuB;GAC3B,GAAG;GACH,GAAI,oBAAoB,SAAY,EAAE,GAAG,EAAE,SAAS,iBAAiB;GACtE;AAED,MACE,cAAc,YACd,qBAAqB,gBACrB,eAAe,aACf;AACA,gBAAa,WAAW;AACxB,gBAAa,WAAW,qBAAqB;AAC7C;;AAGF,MAAI,cAAc,UAAU;AAC1B,gBAAa,YAAY;AACzB,kBAAe;;AAGjB,iBAAe,QAAQ,SAAS,cAAc,UAAU;GACtD,GAAG;GACH,QAAQ;GACT,CAAC;AAEF,MAAI,aAAa,UAAU;AACzB,gBAAa,WAAW;AACxB,gBAAa,WAAW,qBAAqB;;AAG/C,qBAAmB;AACnB,eAAa;IAEf,EAAE,WAAW,MAAM,CACpB;AAGD,mBAAkB;AAChB,eAAa;AACb,MAAI,cAAc,UAAU;AAC1B,gBAAa,YAAY;AACzB,kBAAe;;GAEjB;;AAGJ,SAAS,wBAA2B,OAA+B;AACjE,QAAO,OAAO,UAAU,aAAc,OAAmB,GAAG,MAAM,MAAM"}
|
|
@@ -53,6 +53,8 @@ function useHotkeySequence(sequence, callback, options = {}) {
|
|
|
53
53
|
const defaultOptions = require_HotkeysProviderContext.useDefaultHotkeysOptions();
|
|
54
54
|
const manager = (0, _tanstack_hotkeys.getSequenceManager)();
|
|
55
55
|
let registration = null;
|
|
56
|
+
let lastSequenceKey = null;
|
|
57
|
+
let lastTarget = null;
|
|
56
58
|
const stopWatcher = (0, vue.watch)(() => {
|
|
57
59
|
const resolvedSequence = resolveMaybeRefOrGetter(sequence);
|
|
58
60
|
const resolvedOptions = resolveMaybeRefOrGetter(options);
|
|
@@ -67,18 +69,31 @@ function useHotkeySequence(sequence, callback, options = {}) {
|
|
|
67
69
|
resolvedTarget: mergedOptions.target === void 0 ? void 0 : resolveMaybeRefOrGetter(mergedOptions.target)
|
|
68
70
|
};
|
|
69
71
|
}, ({ resolvedSequence, mergedOptions, resolvedEnabled, resolvedTarget }) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
const finalTarget = resolvedTarget === void 0 ? typeof document !== "undefined" ? document : null : resolvedTarget;
|
|
73
|
+
if (resolvedSequence.length === 0 || !finalTarget) {
|
|
74
|
+
if (registration?.isActive) {
|
|
75
|
+
registration.unregister();
|
|
76
|
+
registration = null;
|
|
77
|
+
}
|
|
78
|
+
lastSequenceKey = null;
|
|
79
|
+
lastTarget = null;
|
|
80
|
+
return;
|
|
76
81
|
}
|
|
82
|
+
const sequenceKey = (0, _tanstack_hotkeys.formatHotkeySequence)(resolvedSequence);
|
|
77
83
|
const { target: _target, enabled: _enabled, ...restOptions } = mergedOptions;
|
|
78
84
|
const optionsWithoutTarget = {
|
|
79
85
|
...restOptions,
|
|
80
86
|
...resolvedEnabled === void 0 ? {} : { enabled: resolvedEnabled }
|
|
81
87
|
};
|
|
88
|
+
if (registration?.isActive && lastSequenceKey === sequenceKey && lastTarget === finalTarget) {
|
|
89
|
+
registration.callback = callback;
|
|
90
|
+
registration.setOptions(optionsWithoutTarget);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (registration?.isActive) {
|
|
94
|
+
registration.unregister();
|
|
95
|
+
registration = null;
|
|
96
|
+
}
|
|
82
97
|
registration = manager.register(resolvedSequence, callback, {
|
|
83
98
|
...optionsWithoutTarget,
|
|
84
99
|
target: finalTarget
|
|
@@ -87,6 +102,8 @@ function useHotkeySequence(sequence, callback, options = {}) {
|
|
|
87
102
|
registration.callback = callback;
|
|
88
103
|
registration.setOptions(optionsWithoutTarget);
|
|
89
104
|
}
|
|
105
|
+
lastSequenceKey = sequenceKey;
|
|
106
|
+
lastTarget = finalTarget;
|
|
90
107
|
}, { immediate: true });
|
|
91
108
|
(0, vue.onUnmounted)(() => {
|
|
92
109
|
stopWatcher();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHotkeySequence.cjs","names":["useDefaultHotkeysOptions"],"sources":["../src/useHotkeySequence.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport { getSequenceManager } from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type { MaybeRefOrGetter } from 'vue'\nimport type {\n HotkeyCallback,\n HotkeySequence,\n SequenceOptions,\n SequenceRegistrationHandle,\n} from '@tanstack/hotkeys'\n\nexport interface UseHotkeySequenceOptions extends Omit<\n SequenceOptions,\n 'enabled' | 'target'\n> {\n /**\n * Whether the sequence is active.\n * Can be a Ref, a getter function, or a boolean value.\n * Defaults to true.\n */\n enabled?: MaybeRefOrGetter<boolean>\n /**\n * The DOM element to attach the event listener to.\n * Can be a Ref, a getter function, direct DOM element, or null.\n * Defaults to document.\n */\n target?: MaybeRefOrGetter<HTMLElement | Document | Window | null>\n}\n\n/**\n * Vue composable for registering a keyboard shortcut sequence (Vim-style).\n *\n * This composable allows you to register multi-key sequences like 'g g' or 'd d'\n * that trigger when the full sequence is pressed within a timeout.\n *\n * Each step may include modifiers. You can chain the same modifier across\n * steps (e.g. `Shift+R` then `Shift+T`). Modifier-only keydown events (Shift,\n * Control, Alt, or Meta pressed alone) are ignored while matching—they do not\n * advance the sequence or reset progress.\n *\n * @param sequence - Array of hotkey strings that form the sequence\n * @param callback - Function to call when the sequence is completed\n * @param options - Options for the sequence behavior\n *\n * @example\n * ```vue\n * <script setup>\n * import { useHotkeySequence } from '@tanstack/vue-hotkeys'\n *\n * // 'g g' to go to top\n * useHotkeySequence(['G', 'G'], () => {\n * scrollToTop()\n * })\n *\n * // 'd d' to delete line\n * useHotkeySequence(['D', 'D'], () => {\n * deleteLine()\n * })\n *\n * // 'd i w' to delete inner word\n * useHotkeySequence(['D', 'I', 'W'], () => {\n * deleteInnerWord()\n * }, { timeout: 500 })\n *\n * // Same modifier on consecutive steps (bare Shift between chords is ignored)\n * useHotkeySequence(['Shift+R', 'Shift+T'], () => {\n * nextAction()\n * })\n * </script>\n *\n * <template>\n * <div>...</div>\n * </template>\n * ```\n */\nexport function useHotkeySequence(\n sequence: MaybeRefOrGetter<HotkeySequence>,\n callback: HotkeyCallback,\n options: MaybeRefOrGetter<UseHotkeySequenceOptions> = {},\n): void {\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getSequenceManager()\n\n let registration: SequenceRegistrationHandle | null = null\n\n // Watch for changes to reactive dependencies\n const stopWatcher = watch(\n () => {\n const resolvedSequence = resolveMaybeRefOrGetter(sequence)\n const resolvedOptions = resolveMaybeRefOrGetter(options)\n const mergedOptions = {\n ...defaultOptions.hotkeySequence,\n ...resolvedOptions,\n } as UseHotkeySequenceOptions\n const resolvedEnabled =\n mergedOptions.enabled === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.enabled)\n const resolvedTarget =\n mergedOptions.target === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.target)\n\n return {\n resolvedSequence,\n mergedOptions,\n resolvedEnabled,\n resolvedTarget,\n }\n },\n ({ resolvedSequence, mergedOptions, resolvedEnabled, resolvedTarget }) => {\n
|
|
1
|
+
{"version":3,"file":"useHotkeySequence.cjs","names":["useDefaultHotkeysOptions"],"sources":["../src/useHotkeySequence.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type { MaybeRefOrGetter } from 'vue'\nimport type {\n HotkeyCallback,\n HotkeySequence,\n SequenceOptions,\n SequenceRegistrationHandle,\n} from '@tanstack/hotkeys'\n\nexport interface UseHotkeySequenceOptions extends Omit<\n SequenceOptions,\n 'enabled' | 'target'\n> {\n /**\n * Whether the sequence is active.\n * Can be a Ref, a getter function, or a boolean value.\n * Defaults to true.\n */\n enabled?: MaybeRefOrGetter<boolean>\n /**\n * The DOM element to attach the event listener to.\n * Can be a Ref, a getter function, direct DOM element, or null.\n * Defaults to document.\n */\n target?: MaybeRefOrGetter<HTMLElement | Document | Window | null>\n}\n\n/**\n * Vue composable for registering a keyboard shortcut sequence (Vim-style).\n *\n * This composable allows you to register multi-key sequences like 'g g' or 'd d'\n * that trigger when the full sequence is pressed within a timeout.\n *\n * Each step may include modifiers. You can chain the same modifier across\n * steps (e.g. `Shift+R` then `Shift+T`). Modifier-only keydown events (Shift,\n * Control, Alt, or Meta pressed alone) are ignored while matching—they do not\n * advance the sequence or reset progress.\n *\n * @param sequence - Array of hotkey strings that form the sequence\n * @param callback - Function to call when the sequence is completed\n * @param options - Options for the sequence behavior\n *\n * @example\n * ```vue\n * <script setup>\n * import { useHotkeySequence } from '@tanstack/vue-hotkeys'\n *\n * // 'g g' to go to top\n * useHotkeySequence(['G', 'G'], () => {\n * scrollToTop()\n * })\n *\n * // 'd d' to delete line\n * useHotkeySequence(['D', 'D'], () => {\n * deleteLine()\n * })\n *\n * // 'd i w' to delete inner word\n * useHotkeySequence(['D', 'I', 'W'], () => {\n * deleteInnerWord()\n * }, { timeout: 500 })\n *\n * // Same modifier on consecutive steps (bare Shift between chords is ignored)\n * useHotkeySequence(['Shift+R', 'Shift+T'], () => {\n * nextAction()\n * })\n * </script>\n *\n * <template>\n * <div>...</div>\n * </template>\n * ```\n */\nexport function useHotkeySequence(\n sequence: MaybeRefOrGetter<HotkeySequence>,\n callback: HotkeyCallback,\n options: MaybeRefOrGetter<UseHotkeySequenceOptions> = {},\n): void {\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getSequenceManager()\n\n let registration: SequenceRegistrationHandle | null = null\n let lastSequenceKey: string | null = null\n let lastTarget: HTMLElement | Document | Window | null = null\n\n // Watch for changes to reactive dependencies\n const stopWatcher = watch(\n () => {\n const resolvedSequence = resolveMaybeRefOrGetter(sequence)\n const resolvedOptions = resolveMaybeRefOrGetter(options)\n const mergedOptions = {\n ...defaultOptions.hotkeySequence,\n ...resolvedOptions,\n } as UseHotkeySequenceOptions\n const resolvedEnabled =\n mergedOptions.enabled === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.enabled)\n const resolvedTarget =\n mergedOptions.target === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.target)\n\n return {\n resolvedSequence,\n mergedOptions,\n resolvedEnabled,\n resolvedTarget,\n }\n },\n ({ resolvedSequence, mergedOptions, resolvedEnabled, resolvedTarget }) => {\n const finalTarget =\n resolvedTarget === undefined\n ? typeof document !== 'undefined'\n ? document\n : null\n : resolvedTarget\n\n if (resolvedSequence.length === 0 || !finalTarget) {\n if (registration?.isActive) {\n registration.unregister()\n registration = null\n }\n lastSequenceKey = null\n lastTarget = null\n return\n }\n\n const sequenceKey = formatHotkeySequence(resolvedSequence)\n\n // Extract options without target (target is handled separately)\n const {\n target: _target,\n enabled: _enabled,\n ...restOptions\n } = mergedOptions\n const optionsWithoutTarget = {\n ...restOptions,\n ...(resolvedEnabled === undefined ? {} : { enabled: resolvedEnabled }),\n }\n\n if (\n registration?.isActive &&\n lastSequenceKey === sequenceKey &&\n lastTarget === finalTarget\n ) {\n registration.callback = callback\n registration.setOptions(optionsWithoutTarget)\n return\n }\n\n if (registration?.isActive) {\n registration.unregister()\n registration = null\n }\n\n registration = manager.register(resolvedSequence, callback, {\n ...optionsWithoutTarget,\n target: finalTarget,\n })\n\n if (registration.isActive) {\n registration.callback = callback\n registration.setOptions(optionsWithoutTarget)\n }\n\n lastSequenceKey = sequenceKey\n lastTarget = finalTarget\n },\n { immediate: true },\n )\n\n // Cleanup on unmount\n onUnmounted(() => {\n stopWatcher()\n if (registration?.isActive) {\n registration.unregister()\n registration = null\n }\n })\n}\n\nfunction resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {\n return typeof value === 'function' ? (value as () => T)() : unref(value)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA,SAAgB,kBACd,UACA,UACA,UAAsD,EAAE,EAClD;CACN,MAAM,iBAAiBA,yDAA0B;CACjD,MAAM,qDAA8B;CAEpC,IAAI,eAAkD;CACtD,IAAI,kBAAiC;CACrC,IAAI,aAAqD;CAGzD,MAAM,mCACE;EACJ,MAAM,mBAAmB,wBAAwB,SAAS;EAC1D,MAAM,kBAAkB,wBAAwB,QAAQ;EACxD,MAAM,gBAAgB;GACpB,GAAG,eAAe;GAClB,GAAG;GACJ;AAUD,SAAO;GACL;GACA;GACA,iBAXA,cAAc,YAAY,SACtB,SACA,wBAAwB,cAAc,QAAQ;GAUlD,gBARA,cAAc,WAAW,SACrB,SACA,wBAAwB,cAAc,OAAO;GAOlD;KAEF,EAAE,kBAAkB,eAAe,iBAAiB,qBAAqB;EACxE,MAAM,cACJ,mBAAmB,SACf,OAAO,aAAa,cAClB,WACA,OACF;AAEN,MAAI,iBAAiB,WAAW,KAAK,CAAC,aAAa;AACjD,OAAI,cAAc,UAAU;AAC1B,iBAAa,YAAY;AACzB,mBAAe;;AAEjB,qBAAkB;AAClB,gBAAa;AACb;;EAGF,MAAM,0DAAmC,iBAAiB;EAG1D,MAAM,EACJ,QAAQ,SACR,SAAS,UACT,GAAG,gBACD;EACJ,MAAM,uBAAuB;GAC3B,GAAG;GACH,GAAI,oBAAoB,SAAY,EAAE,GAAG,EAAE,SAAS,iBAAiB;GACtE;AAED,MACE,cAAc,YACd,oBAAoB,eACpB,eAAe,aACf;AACA,gBAAa,WAAW;AACxB,gBAAa,WAAW,qBAAqB;AAC7C;;AAGF,MAAI,cAAc,UAAU;AAC1B,gBAAa,YAAY;AACzB,kBAAe;;AAGjB,iBAAe,QAAQ,SAAS,kBAAkB,UAAU;GAC1D,GAAG;GACH,QAAQ;GACT,CAAC;AAEF,MAAI,aAAa,UAAU;AACzB,gBAAa,WAAW;AACxB,gBAAa,WAAW,qBAAqB;;AAG/C,oBAAkB;AAClB,eAAa;IAEf,EAAE,WAAW,MAAM,CACpB;AAGD,4BAAkB;AAChB,eAAa;AACb,MAAI,cAAc,UAAU;AAC1B,gBAAa,YAAY;AACzB,kBAAe;;GAEjB;;AAGJ,SAAS,wBAA2B,OAA+B;AACjE,QAAO,OAAO,UAAU,aAAc,OAAmB,kBAAS,MAAM"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useDefaultHotkeysOptions } from "./HotkeysProviderContext.js";
|
|
2
|
-
import { getSequenceManager } from "@tanstack/hotkeys";
|
|
2
|
+
import { formatHotkeySequence, getSequenceManager } from "@tanstack/hotkeys";
|
|
3
3
|
import { onUnmounted, unref, watch } from "vue";
|
|
4
4
|
|
|
5
5
|
//#region src/useHotkeySequence.ts
|
|
@@ -53,6 +53,8 @@ function useHotkeySequence(sequence, callback, options = {}) {
|
|
|
53
53
|
const defaultOptions = useDefaultHotkeysOptions();
|
|
54
54
|
const manager = getSequenceManager();
|
|
55
55
|
let registration = null;
|
|
56
|
+
let lastSequenceKey = null;
|
|
57
|
+
let lastTarget = null;
|
|
56
58
|
const stopWatcher = watch(() => {
|
|
57
59
|
const resolvedSequence = resolveMaybeRefOrGetter(sequence);
|
|
58
60
|
const resolvedOptions = resolveMaybeRefOrGetter(options);
|
|
@@ -67,18 +69,31 @@ function useHotkeySequence(sequence, callback, options = {}) {
|
|
|
67
69
|
resolvedTarget: mergedOptions.target === void 0 ? void 0 : resolveMaybeRefOrGetter(mergedOptions.target)
|
|
68
70
|
};
|
|
69
71
|
}, ({ resolvedSequence, mergedOptions, resolvedEnabled, resolvedTarget }) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
const finalTarget = resolvedTarget === void 0 ? typeof document !== "undefined" ? document : null : resolvedTarget;
|
|
73
|
+
if (resolvedSequence.length === 0 || !finalTarget) {
|
|
74
|
+
if (registration?.isActive) {
|
|
75
|
+
registration.unregister();
|
|
76
|
+
registration = null;
|
|
77
|
+
}
|
|
78
|
+
lastSequenceKey = null;
|
|
79
|
+
lastTarget = null;
|
|
80
|
+
return;
|
|
76
81
|
}
|
|
82
|
+
const sequenceKey = formatHotkeySequence(resolvedSequence);
|
|
77
83
|
const { target: _target, enabled: _enabled, ...restOptions } = mergedOptions;
|
|
78
84
|
const optionsWithoutTarget = {
|
|
79
85
|
...restOptions,
|
|
80
86
|
...resolvedEnabled === void 0 ? {} : { enabled: resolvedEnabled }
|
|
81
87
|
};
|
|
88
|
+
if (registration?.isActive && lastSequenceKey === sequenceKey && lastTarget === finalTarget) {
|
|
89
|
+
registration.callback = callback;
|
|
90
|
+
registration.setOptions(optionsWithoutTarget);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (registration?.isActive) {
|
|
94
|
+
registration.unregister();
|
|
95
|
+
registration = null;
|
|
96
|
+
}
|
|
82
97
|
registration = manager.register(resolvedSequence, callback, {
|
|
83
98
|
...optionsWithoutTarget,
|
|
84
99
|
target: finalTarget
|
|
@@ -87,6 +102,8 @@ function useHotkeySequence(sequence, callback, options = {}) {
|
|
|
87
102
|
registration.callback = callback;
|
|
88
103
|
registration.setOptions(optionsWithoutTarget);
|
|
89
104
|
}
|
|
105
|
+
lastSequenceKey = sequenceKey;
|
|
106
|
+
lastTarget = finalTarget;
|
|
90
107
|
}, { immediate: true });
|
|
91
108
|
onUnmounted(() => {
|
|
92
109
|
stopWatcher();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHotkeySequence.js","names":[],"sources":["../src/useHotkeySequence.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport { getSequenceManager } from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type { MaybeRefOrGetter } from 'vue'\nimport type {\n HotkeyCallback,\n HotkeySequence,\n SequenceOptions,\n SequenceRegistrationHandle,\n} from '@tanstack/hotkeys'\n\nexport interface UseHotkeySequenceOptions extends Omit<\n SequenceOptions,\n 'enabled' | 'target'\n> {\n /**\n * Whether the sequence is active.\n * Can be a Ref, a getter function, or a boolean value.\n * Defaults to true.\n */\n enabled?: MaybeRefOrGetter<boolean>\n /**\n * The DOM element to attach the event listener to.\n * Can be a Ref, a getter function, direct DOM element, or null.\n * Defaults to document.\n */\n target?: MaybeRefOrGetter<HTMLElement | Document | Window | null>\n}\n\n/**\n * Vue composable for registering a keyboard shortcut sequence (Vim-style).\n *\n * This composable allows you to register multi-key sequences like 'g g' or 'd d'\n * that trigger when the full sequence is pressed within a timeout.\n *\n * Each step may include modifiers. You can chain the same modifier across\n * steps (e.g. `Shift+R` then `Shift+T`). Modifier-only keydown events (Shift,\n * Control, Alt, or Meta pressed alone) are ignored while matching—they do not\n * advance the sequence or reset progress.\n *\n * @param sequence - Array of hotkey strings that form the sequence\n * @param callback - Function to call when the sequence is completed\n * @param options - Options for the sequence behavior\n *\n * @example\n * ```vue\n * <script setup>\n * import { useHotkeySequence } from '@tanstack/vue-hotkeys'\n *\n * // 'g g' to go to top\n * useHotkeySequence(['G', 'G'], () => {\n * scrollToTop()\n * })\n *\n * // 'd d' to delete line\n * useHotkeySequence(['D', 'D'], () => {\n * deleteLine()\n * })\n *\n * // 'd i w' to delete inner word\n * useHotkeySequence(['D', 'I', 'W'], () => {\n * deleteInnerWord()\n * }, { timeout: 500 })\n *\n * // Same modifier on consecutive steps (bare Shift between chords is ignored)\n * useHotkeySequence(['Shift+R', 'Shift+T'], () => {\n * nextAction()\n * })\n * </script>\n *\n * <template>\n * <div>...</div>\n * </template>\n * ```\n */\nexport function useHotkeySequence(\n sequence: MaybeRefOrGetter<HotkeySequence>,\n callback: HotkeyCallback,\n options: MaybeRefOrGetter<UseHotkeySequenceOptions> = {},\n): void {\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getSequenceManager()\n\n let registration: SequenceRegistrationHandle | null = null\n\n // Watch for changes to reactive dependencies\n const stopWatcher = watch(\n () => {\n const resolvedSequence = resolveMaybeRefOrGetter(sequence)\n const resolvedOptions = resolveMaybeRefOrGetter(options)\n const mergedOptions = {\n ...defaultOptions.hotkeySequence,\n ...resolvedOptions,\n } as UseHotkeySequenceOptions\n const resolvedEnabled =\n mergedOptions.enabled === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.enabled)\n const resolvedTarget =\n mergedOptions.target === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.target)\n\n return {\n resolvedSequence,\n mergedOptions,\n resolvedEnabled,\n resolvedTarget,\n }\n },\n ({ resolvedSequence, mergedOptions, resolvedEnabled, resolvedTarget }) => {\n
|
|
1
|
+
{"version":3,"file":"useHotkeySequence.js","names":[],"sources":["../src/useHotkeySequence.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type { MaybeRefOrGetter } from 'vue'\nimport type {\n HotkeyCallback,\n HotkeySequence,\n SequenceOptions,\n SequenceRegistrationHandle,\n} from '@tanstack/hotkeys'\n\nexport interface UseHotkeySequenceOptions extends Omit<\n SequenceOptions,\n 'enabled' | 'target'\n> {\n /**\n * Whether the sequence is active.\n * Can be a Ref, a getter function, or a boolean value.\n * Defaults to true.\n */\n enabled?: MaybeRefOrGetter<boolean>\n /**\n * The DOM element to attach the event listener to.\n * Can be a Ref, a getter function, direct DOM element, or null.\n * Defaults to document.\n */\n target?: MaybeRefOrGetter<HTMLElement | Document | Window | null>\n}\n\n/**\n * Vue composable for registering a keyboard shortcut sequence (Vim-style).\n *\n * This composable allows you to register multi-key sequences like 'g g' or 'd d'\n * that trigger when the full sequence is pressed within a timeout.\n *\n * Each step may include modifiers. You can chain the same modifier across\n * steps (e.g. `Shift+R` then `Shift+T`). Modifier-only keydown events (Shift,\n * Control, Alt, or Meta pressed alone) are ignored while matching—they do not\n * advance the sequence or reset progress.\n *\n * @param sequence - Array of hotkey strings that form the sequence\n * @param callback - Function to call when the sequence is completed\n * @param options - Options for the sequence behavior\n *\n * @example\n * ```vue\n * <script setup>\n * import { useHotkeySequence } from '@tanstack/vue-hotkeys'\n *\n * // 'g g' to go to top\n * useHotkeySequence(['G', 'G'], () => {\n * scrollToTop()\n * })\n *\n * // 'd d' to delete line\n * useHotkeySequence(['D', 'D'], () => {\n * deleteLine()\n * })\n *\n * // 'd i w' to delete inner word\n * useHotkeySequence(['D', 'I', 'W'], () => {\n * deleteInnerWord()\n * }, { timeout: 500 })\n *\n * // Same modifier on consecutive steps (bare Shift between chords is ignored)\n * useHotkeySequence(['Shift+R', 'Shift+T'], () => {\n * nextAction()\n * })\n * </script>\n *\n * <template>\n * <div>...</div>\n * </template>\n * ```\n */\nexport function useHotkeySequence(\n sequence: MaybeRefOrGetter<HotkeySequence>,\n callback: HotkeyCallback,\n options: MaybeRefOrGetter<UseHotkeySequenceOptions> = {},\n): void {\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getSequenceManager()\n\n let registration: SequenceRegistrationHandle | null = null\n let lastSequenceKey: string | null = null\n let lastTarget: HTMLElement | Document | Window | null = null\n\n // Watch for changes to reactive dependencies\n const stopWatcher = watch(\n () => {\n const resolvedSequence = resolveMaybeRefOrGetter(sequence)\n const resolvedOptions = resolveMaybeRefOrGetter(options)\n const mergedOptions = {\n ...defaultOptions.hotkeySequence,\n ...resolvedOptions,\n } as UseHotkeySequenceOptions\n const resolvedEnabled =\n mergedOptions.enabled === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.enabled)\n const resolvedTarget =\n mergedOptions.target === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.target)\n\n return {\n resolvedSequence,\n mergedOptions,\n resolvedEnabled,\n resolvedTarget,\n }\n },\n ({ resolvedSequence, mergedOptions, resolvedEnabled, resolvedTarget }) => {\n const finalTarget =\n resolvedTarget === undefined\n ? typeof document !== 'undefined'\n ? document\n : null\n : resolvedTarget\n\n if (resolvedSequence.length === 0 || !finalTarget) {\n if (registration?.isActive) {\n registration.unregister()\n registration = null\n }\n lastSequenceKey = null\n lastTarget = null\n return\n }\n\n const sequenceKey = formatHotkeySequence(resolvedSequence)\n\n // Extract options without target (target is handled separately)\n const {\n target: _target,\n enabled: _enabled,\n ...restOptions\n } = mergedOptions\n const optionsWithoutTarget = {\n ...restOptions,\n ...(resolvedEnabled === undefined ? {} : { enabled: resolvedEnabled }),\n }\n\n if (\n registration?.isActive &&\n lastSequenceKey === sequenceKey &&\n lastTarget === finalTarget\n ) {\n registration.callback = callback\n registration.setOptions(optionsWithoutTarget)\n return\n }\n\n if (registration?.isActive) {\n registration.unregister()\n registration = null\n }\n\n registration = manager.register(resolvedSequence, callback, {\n ...optionsWithoutTarget,\n target: finalTarget,\n })\n\n if (registration.isActive) {\n registration.callback = callback\n registration.setOptions(optionsWithoutTarget)\n }\n\n lastSequenceKey = sequenceKey\n lastTarget = finalTarget\n },\n { immediate: true },\n )\n\n // Cleanup on unmount\n onUnmounted(() => {\n stopWatcher()\n if (registration?.isActive) {\n registration.unregister()\n registration = null\n }\n })\n}\n\nfunction resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {\n return typeof value === 'function' ? (value as () => T)() : unref(value)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA,SAAgB,kBACd,UACA,UACA,UAAsD,EAAE,EAClD;CACN,MAAM,iBAAiB,0BAA0B;CACjD,MAAM,UAAU,oBAAoB;CAEpC,IAAI,eAAkD;CACtD,IAAI,kBAAiC;CACrC,IAAI,aAAqD;CAGzD,MAAM,cAAc,YACZ;EACJ,MAAM,mBAAmB,wBAAwB,SAAS;EAC1D,MAAM,kBAAkB,wBAAwB,QAAQ;EACxD,MAAM,gBAAgB;GACpB,GAAG,eAAe;GAClB,GAAG;GACJ;AAUD,SAAO;GACL;GACA;GACA,iBAXA,cAAc,YAAY,SACtB,SACA,wBAAwB,cAAc,QAAQ;GAUlD,gBARA,cAAc,WAAW,SACrB,SACA,wBAAwB,cAAc,OAAO;GAOlD;KAEF,EAAE,kBAAkB,eAAe,iBAAiB,qBAAqB;EACxE,MAAM,cACJ,mBAAmB,SACf,OAAO,aAAa,cAClB,WACA,OACF;AAEN,MAAI,iBAAiB,WAAW,KAAK,CAAC,aAAa;AACjD,OAAI,cAAc,UAAU;AAC1B,iBAAa,YAAY;AACzB,mBAAe;;AAEjB,qBAAkB;AAClB,gBAAa;AACb;;EAGF,MAAM,cAAc,qBAAqB,iBAAiB;EAG1D,MAAM,EACJ,QAAQ,SACR,SAAS,UACT,GAAG,gBACD;EACJ,MAAM,uBAAuB;GAC3B,GAAG;GACH,GAAI,oBAAoB,SAAY,EAAE,GAAG,EAAE,SAAS,iBAAiB;GACtE;AAED,MACE,cAAc,YACd,oBAAoB,eACpB,eAAe,aACf;AACA,gBAAa,WAAW;AACxB,gBAAa,WAAW,qBAAqB;AAC7C;;AAGF,MAAI,cAAc,UAAU;AAC1B,gBAAa,YAAY;AACzB,kBAAe;;AAGjB,iBAAe,QAAQ,SAAS,kBAAkB,UAAU;GAC1D,GAAG;GACH,QAAQ;GACT,CAAC;AAEF,MAAI,aAAa,UAAU;AACzB,gBAAa,WAAW;AACxB,gBAAa,WAAW,qBAAqB;;AAG/C,oBAAkB;AAClB,eAAa;IAEf,EAAE,WAAW,MAAM,CACpB;AAGD,mBAAkB;AAChB,eAAa;AACb,MAAI,cAAc,UAAU;AAC1B,gBAAa,YAAY;AACzB,kBAAe;;GAEjB;;AAGJ,SAAS,wBAA2B,OAA+B;AACjE,QAAO,OAAO,UAAU,aAAc,OAAmB,GAAG,MAAM,MAAM"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const require_HotkeysProviderContext = require('./HotkeysProviderContext.cjs');
|
|
2
|
+
let _tanstack_hotkeys = require("@tanstack/hotkeys");
|
|
3
|
+
let vue = require("vue");
|
|
4
|
+
|
|
5
|
+
//#region src/useHotkeySequences.ts
|
|
6
|
+
/**
|
|
7
|
+
* Vue composable for registering multiple keyboard shortcut sequences at once (Vim-style).
|
|
8
|
+
*
|
|
9
|
+
* Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or a getter/ref
|
|
10
|
+
* that returns one, so you can register variable-length lists safely.
|
|
11
|
+
*
|
|
12
|
+
* Options are merged in this order:
|
|
13
|
+
* HotkeysProvider defaults < commonOptions < per-definition options
|
|
14
|
+
*
|
|
15
|
+
* Definitions with an empty `sequence` are skipped (no registration).
|
|
16
|
+
*
|
|
17
|
+
* @param definitions - Array of sequence definitions, or a getter/ref
|
|
18
|
+
* @param commonOptions - Shared options applied to all sequences (overridden by per-definition options)
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```vue
|
|
22
|
+
* <script setup>
|
|
23
|
+
* import { useHotkeySequences } from '@tanstack/vue-hotkeys'
|
|
24
|
+
*
|
|
25
|
+
* useHotkeySequences([
|
|
26
|
+
* { sequence: ['G', 'G'], callback: () => scrollToTop() },
|
|
27
|
+
* { sequence: ['D', 'D'], callback: () => deleteLine() },
|
|
28
|
+
* ])
|
|
29
|
+
* <\/script>
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```vue
|
|
34
|
+
* <script setup>
|
|
35
|
+
* import { computed } from 'vue'
|
|
36
|
+
* import { useHotkeySequences } from '@tanstack/vue-hotkeys'
|
|
37
|
+
*
|
|
38
|
+
* const items = computed(() => [...])
|
|
39
|
+
* useHotkeySequences(
|
|
40
|
+
* () => items.value.map((item) => ({
|
|
41
|
+
* sequence: item.chords,
|
|
42
|
+
* callback: item.action,
|
|
43
|
+
* options: { enabled: item.enabled },
|
|
44
|
+
* })),
|
|
45
|
+
* { preventDefault: true },
|
|
46
|
+
* )
|
|
47
|
+
* <\/script>
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
function useHotkeySequences(definitions, commonOptions = {}) {
|
|
51
|
+
const defaultOptions = require_HotkeysProviderContext.useDefaultHotkeysOptions();
|
|
52
|
+
const manager = (0, _tanstack_hotkeys.getSequenceManager)();
|
|
53
|
+
const registrations = /* @__PURE__ */ new Map();
|
|
54
|
+
const stopWatcher = (0, vue.watch)(() => {
|
|
55
|
+
const resolvedDefinitions = resolveMaybeRefOrGetter(definitions);
|
|
56
|
+
const resolvedCommonOptions = resolveMaybeRefOrGetter(commonOptions);
|
|
57
|
+
return resolvedDefinitions.map((def, i) => {
|
|
58
|
+
const resolvedSequence = resolveMaybeRefOrGetter(def.sequence);
|
|
59
|
+
const resolvedDefOptions = def.options ? resolveMaybeRefOrGetter(def.options) : {};
|
|
60
|
+
const mergedOptions = {
|
|
61
|
+
...defaultOptions.hotkeySequence,
|
|
62
|
+
...resolvedCommonOptions,
|
|
63
|
+
...resolvedDefOptions
|
|
64
|
+
};
|
|
65
|
+
const sequenceString = (0, _tanstack_hotkeys.formatHotkeySequence)(resolvedSequence);
|
|
66
|
+
const resolvedEnabled = mergedOptions.enabled === void 0 ? void 0 : resolveMaybeRefOrGetter(mergedOptions.enabled);
|
|
67
|
+
const resolvedTarget = mergedOptions.target === void 0 ? void 0 : resolveMaybeRefOrGetter(mergedOptions.target);
|
|
68
|
+
return {
|
|
69
|
+
index: i,
|
|
70
|
+
resolvedSequence,
|
|
71
|
+
sequenceString,
|
|
72
|
+
callback: def.callback,
|
|
73
|
+
mergedOptions,
|
|
74
|
+
resolvedEnabled,
|
|
75
|
+
resolvedTarget
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}, (resolved) => {
|
|
79
|
+
const nextKeys = /* @__PURE__ */ new Set();
|
|
80
|
+
const prepared = [];
|
|
81
|
+
for (const entry of resolved) {
|
|
82
|
+
if (entry.resolvedSequence.length === 0) continue;
|
|
83
|
+
const finalTarget = entry.resolvedTarget ?? (typeof document !== "undefined" ? document : null);
|
|
84
|
+
if (!finalTarget) continue;
|
|
85
|
+
const registrationKey = `${entry.index}:${entry.sequenceString}`;
|
|
86
|
+
nextKeys.add(registrationKey);
|
|
87
|
+
prepared.push({
|
|
88
|
+
registrationKey,
|
|
89
|
+
entry,
|
|
90
|
+
finalTarget
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
for (const [key, record] of [...registrations.entries()]) if (!nextKeys.has(key)) {
|
|
94
|
+
if (record.handle.isActive) record.handle.unregister();
|
|
95
|
+
registrations.delete(key);
|
|
96
|
+
}
|
|
97
|
+
for (const { registrationKey, entry, finalTarget } of prepared) {
|
|
98
|
+
const existing = registrations.get(registrationKey);
|
|
99
|
+
if (existing?.handle.isActive && existing.target === finalTarget) {
|
|
100
|
+
existing.handle.callback = entry.callback;
|
|
101
|
+
const { target: _target, enabled: _enabled, ...restOptions } = entry.mergedOptions;
|
|
102
|
+
const optionsWithoutTarget = {
|
|
103
|
+
...restOptions,
|
|
104
|
+
...entry.resolvedEnabled === void 0 ? {} : { enabled: entry.resolvedEnabled }
|
|
105
|
+
};
|
|
106
|
+
existing.handle.setOptions(optionsWithoutTarget);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (existing) {
|
|
110
|
+
if (existing.handle.isActive) existing.handle.unregister();
|
|
111
|
+
registrations.delete(registrationKey);
|
|
112
|
+
}
|
|
113
|
+
const { target: _target, enabled: _enabled, ...restOptions } = entry.mergedOptions;
|
|
114
|
+
const optionsWithoutTarget = {
|
|
115
|
+
...restOptions,
|
|
116
|
+
...entry.resolvedEnabled === void 0 ? {} : { enabled: entry.resolvedEnabled }
|
|
117
|
+
};
|
|
118
|
+
const handle = manager.register(entry.resolvedSequence, entry.callback, {
|
|
119
|
+
...optionsWithoutTarget,
|
|
120
|
+
target: finalTarget
|
|
121
|
+
});
|
|
122
|
+
registrations.set(registrationKey, {
|
|
123
|
+
handle,
|
|
124
|
+
target: finalTarget
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}, { immediate: true });
|
|
128
|
+
(0, vue.onUnmounted)(() => {
|
|
129
|
+
stopWatcher();
|
|
130
|
+
for (const { handle } of registrations.values()) if (handle.isActive) handle.unregister();
|
|
131
|
+
registrations.clear();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function resolveMaybeRefOrGetter(value) {
|
|
135
|
+
return typeof value === "function" ? value() : (0, vue.unref)(value);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
//#endregion
|
|
139
|
+
exports.useHotkeySequences = useHotkeySequences;
|
|
140
|
+
//# sourceMappingURL=useHotkeySequences.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useHotkeySequences.cjs","names":["useDefaultHotkeysOptions"],"sources":["../src/useHotkeySequences.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type { UseHotkeySequenceOptions } from './useHotkeySequence'\nimport type {\n HotkeyCallback,\n HotkeySequence,\n SequenceRegistrationHandle,\n} from '@tanstack/hotkeys'\nimport type { MaybeRefOrGetter } from 'vue'\n\n/**\n * A single sequence definition for use with `useHotkeySequences`.\n */\nexport interface UseHotkeySequenceDefinition {\n /** Array of hotkey strings that form the sequence */\n sequence: MaybeRefOrGetter<HotkeySequence>\n /** The function to call when the sequence is completed */\n callback: HotkeyCallback\n /** Per-sequence options (merged on top of commonOptions) */\n options?: MaybeRefOrGetter<UseHotkeySequenceOptions>\n}\n\n/**\n * Vue composable for registering multiple keyboard shortcut sequences at once (Vim-style).\n *\n * Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or a getter/ref\n * that returns one, so you can register variable-length lists safely.\n *\n * Options are merged in this order:\n * HotkeysProvider defaults < commonOptions < per-definition options\n *\n * Definitions with an empty `sequence` are skipped (no registration).\n *\n * @param definitions - Array of sequence definitions, or a getter/ref\n * @param commonOptions - Shared options applied to all sequences (overridden by per-definition options)\n *\n * @example\n * ```vue\n * <script setup>\n * import { useHotkeySequences } from '@tanstack/vue-hotkeys'\n *\n * useHotkeySequences([\n * { sequence: ['G', 'G'], callback: () => scrollToTop() },\n * { sequence: ['D', 'D'], callback: () => deleteLine() },\n * ])\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { computed } from 'vue'\n * import { useHotkeySequences } from '@tanstack/vue-hotkeys'\n *\n * const items = computed(() => [...])\n * useHotkeySequences(\n * () => items.value.map((item) => ({\n * sequence: item.chords,\n * callback: item.action,\n * options: { enabled: item.enabled },\n * })),\n * { preventDefault: true },\n * )\n * </script>\n * ```\n */\nexport function useHotkeySequences(\n definitions: MaybeRefOrGetter<Array<UseHotkeySequenceDefinition>>,\n commonOptions: MaybeRefOrGetter<UseHotkeySequenceOptions> = {},\n): void {\n type RegistrationRecord = {\n handle: SequenceRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getSequenceManager()\n\n const registrations = new Map<string, RegistrationRecord>()\n\n const stopWatcher = watch(\n () => {\n const resolvedDefinitions = resolveMaybeRefOrGetter(definitions)\n const resolvedCommonOptions = resolveMaybeRefOrGetter(commonOptions)\n\n return resolvedDefinitions.map((def, i) => {\n const resolvedSequence = resolveMaybeRefOrGetter(def.sequence)\n const resolvedDefOptions = def.options\n ? resolveMaybeRefOrGetter(def.options)\n : {}\n\n const mergedOptions = {\n ...defaultOptions.hotkeySequence,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as UseHotkeySequenceOptions\n\n const sequenceString = formatHotkeySequence(resolvedSequence)\n\n const resolvedEnabled =\n mergedOptions.enabled === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.enabled)\n const resolvedTarget =\n mergedOptions.target === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.target as any)\n\n return {\n index: i,\n resolvedSequence,\n sequenceString,\n callback: def.callback,\n mergedOptions,\n resolvedEnabled,\n resolvedTarget,\n }\n })\n },\n (resolved) => {\n const nextKeys = new Set<string>()\n const prepared: Array<{\n registrationKey: string\n entry: (typeof resolved)[number]\n finalTarget: Document | HTMLElement | Window\n }> = []\n\n for (const entry of resolved) {\n if (entry.resolvedSequence.length === 0) {\n continue\n }\n\n const finalTarget =\n entry.resolvedTarget ??\n (typeof document !== 'undefined' ? document : null)\n\n if (!finalTarget) {\n continue\n }\n\n const registrationKey = `${entry.index}:${entry.sequenceString}`\n nextKeys.add(registrationKey)\n prepared.push({ registrationKey, entry, finalTarget })\n }\n\n for (const [key, record] of [...registrations.entries()]) {\n if (!nextKeys.has(key)) {\n if (record.handle.isActive) {\n record.handle.unregister()\n }\n registrations.delete(key)\n }\n }\n\n for (const { registrationKey, entry, finalTarget } of prepared) {\n const existing = registrations.get(registrationKey)\n if (existing?.handle.isActive && existing.target === finalTarget) {\n existing.handle.callback = entry.callback\n const {\n target: _target,\n enabled: _enabled,\n ...restOptions\n } = entry.mergedOptions\n const optionsWithoutTarget = {\n ...restOptions,\n ...(entry.resolvedEnabled === undefined\n ? {}\n : { enabled: entry.resolvedEnabled }),\n }\n existing.handle.setOptions(optionsWithoutTarget)\n continue\n }\n\n if (existing) {\n if (existing.handle.isActive) {\n existing.handle.unregister()\n }\n registrations.delete(registrationKey)\n }\n\n const {\n target: _target,\n enabled: _enabled,\n ...restOptions\n } = entry.mergedOptions\n const optionsWithoutTarget = {\n ...restOptions,\n ...(entry.resolvedEnabled === undefined\n ? {}\n : { enabled: entry.resolvedEnabled }),\n }\n\n const handle = manager.register(\n entry.resolvedSequence,\n entry.callback,\n {\n ...optionsWithoutTarget,\n target: finalTarget,\n },\n )\n registrations.set(registrationKey, { handle, target: finalTarget })\n }\n },\n { immediate: true },\n )\n\n onUnmounted(() => {\n stopWatcher()\n for (const { handle } of registrations.values()) {\n if (handle.isActive) {\n handle.unregister()\n }\n }\n registrations.clear()\n })\n}\n\nfunction resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {\n return typeof value === 'function' ? (value as () => T)() : unref(value)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,SAAgB,mBACd,aACA,gBAA4D,EAAE,EACxD;CAMN,MAAM,iBAAiBA,yDAA0B;CACjD,MAAM,qDAA8B;CAEpC,MAAM,gCAAgB,IAAI,KAAiC;CAE3D,MAAM,mCACE;EACJ,MAAM,sBAAsB,wBAAwB,YAAY;EAChE,MAAM,wBAAwB,wBAAwB,cAAc;AAEpE,SAAO,oBAAoB,KAAK,KAAK,MAAM;GACzC,MAAM,mBAAmB,wBAAwB,IAAI,SAAS;GAC9D,MAAM,qBAAqB,IAAI,UAC3B,wBAAwB,IAAI,QAAQ,GACpC,EAAE;GAEN,MAAM,gBAAgB;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;GAED,MAAM,6DAAsC,iBAAiB;GAE7D,MAAM,kBACJ,cAAc,YAAY,SACtB,SACA,wBAAwB,cAAc,QAAQ;GACpD,MAAM,iBACJ,cAAc,WAAW,SACrB,SACA,wBAAwB,cAAc,OAAc;AAE1D,UAAO;IACL,OAAO;IACP;IACA;IACA,UAAU,IAAI;IACd;IACA;IACA;IACD;IACD;KAEH,aAAa;EACZ,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAID,EAAE;AAEP,OAAK,MAAM,SAAS,UAAU;AAC5B,OAAI,MAAM,iBAAiB,WAAW,EACpC;GAGF,MAAM,cACJ,MAAM,mBACL,OAAO,aAAa,cAAc,WAAW;AAEhD,OAAI,CAAC,YACH;GAGF,MAAM,kBAAkB,GAAG,MAAM,MAAM,GAAG,MAAM;AAChD,YAAS,IAAI,gBAAgB;AAC7B,YAAS,KAAK;IAAE;IAAiB;IAAO;IAAa,CAAC;;AAGxD,OAAK,MAAM,CAAC,KAAK,WAAW,CAAC,GAAG,cAAc,SAAS,CAAC,CACtD,KAAI,CAAC,SAAS,IAAI,IAAI,EAAE;AACtB,OAAI,OAAO,OAAO,SAChB,QAAO,OAAO,YAAY;AAE5B,iBAAc,OAAO,IAAI;;AAI7B,OAAK,MAAM,EAAE,iBAAiB,OAAO,iBAAiB,UAAU;GAC9D,MAAM,WAAW,cAAc,IAAI,gBAAgB;AACnD,OAAI,UAAU,OAAO,YAAY,SAAS,WAAW,aAAa;AAChE,aAAS,OAAO,WAAW,MAAM;IACjC,MAAM,EACJ,QAAQ,SACR,SAAS,UACT,GAAG,gBACD,MAAM;IACV,MAAM,uBAAuB;KAC3B,GAAG;KACH,GAAI,MAAM,oBAAoB,SAC1B,EAAE,GACF,EAAE,SAAS,MAAM,iBAAiB;KACvC;AACD,aAAS,OAAO,WAAW,qBAAqB;AAChD;;AAGF,OAAI,UAAU;AACZ,QAAI,SAAS,OAAO,SAClB,UAAS,OAAO,YAAY;AAE9B,kBAAc,OAAO,gBAAgB;;GAGvC,MAAM,EACJ,QAAQ,SACR,SAAS,UACT,GAAG,gBACD,MAAM;GACV,MAAM,uBAAuB;IAC3B,GAAG;IACH,GAAI,MAAM,oBAAoB,SAC1B,EAAE,GACF,EAAE,SAAS,MAAM,iBAAiB;IACvC;GAED,MAAM,SAAS,QAAQ,SACrB,MAAM,kBACN,MAAM,UACN;IACE,GAAG;IACH,QAAQ;IACT,CACF;AACD,iBAAc,IAAI,iBAAiB;IAAE;IAAQ,QAAQ;IAAa,CAAC;;IAGvE,EAAE,WAAW,MAAM,CACpB;AAED,4BAAkB;AAChB,eAAa;AACb,OAAK,MAAM,EAAE,YAAY,cAAc,QAAQ,CAC7C,KAAI,OAAO,SACT,QAAO,YAAY;AAGvB,gBAAc,OAAO;GACrB;;AAGJ,SAAS,wBAA2B,OAA+B;AACjE,QAAO,OAAO,UAAU,aAAc,OAAmB,kBAAS,MAAM"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { UseHotkeySequenceOptions } from "./useHotkeySequence.cjs";
|
|
2
|
+
import { HotkeyCallback, HotkeySequence } from "@tanstack/hotkeys";
|
|
3
|
+
import { MaybeRefOrGetter } from "vue";
|
|
4
|
+
|
|
5
|
+
//#region src/useHotkeySequences.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* A single sequence definition for use with `useHotkeySequences`.
|
|
8
|
+
*/
|
|
9
|
+
interface UseHotkeySequenceDefinition {
|
|
10
|
+
/** Array of hotkey strings that form the sequence */
|
|
11
|
+
sequence: MaybeRefOrGetter<HotkeySequence>;
|
|
12
|
+
/** The function to call when the sequence is completed */
|
|
13
|
+
callback: HotkeyCallback;
|
|
14
|
+
/** Per-sequence options (merged on top of commonOptions) */
|
|
15
|
+
options?: MaybeRefOrGetter<UseHotkeySequenceOptions>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Vue composable for registering multiple keyboard shortcut sequences at once (Vim-style).
|
|
19
|
+
*
|
|
20
|
+
* Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or a getter/ref
|
|
21
|
+
* that returns one, so you can register variable-length lists safely.
|
|
22
|
+
*
|
|
23
|
+
* Options are merged in this order:
|
|
24
|
+
* HotkeysProvider defaults < commonOptions < per-definition options
|
|
25
|
+
*
|
|
26
|
+
* Definitions with an empty `sequence` are skipped (no registration).
|
|
27
|
+
*
|
|
28
|
+
* @param definitions - Array of sequence definitions, or a getter/ref
|
|
29
|
+
* @param commonOptions - Shared options applied to all sequences (overridden by per-definition options)
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```vue
|
|
33
|
+
* <script setup>
|
|
34
|
+
* import { useHotkeySequences } from '@tanstack/vue-hotkeys'
|
|
35
|
+
*
|
|
36
|
+
* useHotkeySequences([
|
|
37
|
+
* { sequence: ['G', 'G'], callback: () => scrollToTop() },
|
|
38
|
+
* { sequence: ['D', 'D'], callback: () => deleteLine() },
|
|
39
|
+
* ])
|
|
40
|
+
* </script>
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```vue
|
|
45
|
+
* <script setup>
|
|
46
|
+
* import { computed } from 'vue'
|
|
47
|
+
* import { useHotkeySequences } from '@tanstack/vue-hotkeys'
|
|
48
|
+
*
|
|
49
|
+
* const items = computed(() => [...])
|
|
50
|
+
* useHotkeySequences(
|
|
51
|
+
* () => items.value.map((item) => ({
|
|
52
|
+
* sequence: item.chords,
|
|
53
|
+
* callback: item.action,
|
|
54
|
+
* options: { enabled: item.enabled },
|
|
55
|
+
* })),
|
|
56
|
+
* { preventDefault: true },
|
|
57
|
+
* )
|
|
58
|
+
* </script>
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare function useHotkeySequences(definitions: MaybeRefOrGetter<Array<UseHotkeySequenceDefinition>>, commonOptions?: MaybeRefOrGetter<UseHotkeySequenceOptions>): void;
|
|
62
|
+
//#endregion
|
|
63
|
+
export { UseHotkeySequenceDefinition, useHotkeySequences };
|
|
64
|
+
//# sourceMappingURL=useHotkeySequences.d.cts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { UseHotkeySequenceOptions } from "./useHotkeySequence.js";
|
|
2
|
+
import { HotkeyCallback, HotkeySequence } from "@tanstack/hotkeys";
|
|
3
|
+
import { MaybeRefOrGetter } from "vue";
|
|
4
|
+
|
|
5
|
+
//#region src/useHotkeySequences.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* A single sequence definition for use with `useHotkeySequences`.
|
|
8
|
+
*/
|
|
9
|
+
interface UseHotkeySequenceDefinition {
|
|
10
|
+
/** Array of hotkey strings that form the sequence */
|
|
11
|
+
sequence: MaybeRefOrGetter<HotkeySequence>;
|
|
12
|
+
/** The function to call when the sequence is completed */
|
|
13
|
+
callback: HotkeyCallback;
|
|
14
|
+
/** Per-sequence options (merged on top of commonOptions) */
|
|
15
|
+
options?: MaybeRefOrGetter<UseHotkeySequenceOptions>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Vue composable for registering multiple keyboard shortcut sequences at once (Vim-style).
|
|
19
|
+
*
|
|
20
|
+
* Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or a getter/ref
|
|
21
|
+
* that returns one, so you can register variable-length lists safely.
|
|
22
|
+
*
|
|
23
|
+
* Options are merged in this order:
|
|
24
|
+
* HotkeysProvider defaults < commonOptions < per-definition options
|
|
25
|
+
*
|
|
26
|
+
* Definitions with an empty `sequence` are skipped (no registration).
|
|
27
|
+
*
|
|
28
|
+
* @param definitions - Array of sequence definitions, or a getter/ref
|
|
29
|
+
* @param commonOptions - Shared options applied to all sequences (overridden by per-definition options)
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```vue
|
|
33
|
+
* <script setup>
|
|
34
|
+
* import { useHotkeySequences } from '@tanstack/vue-hotkeys'
|
|
35
|
+
*
|
|
36
|
+
* useHotkeySequences([
|
|
37
|
+
* { sequence: ['G', 'G'], callback: () => scrollToTop() },
|
|
38
|
+
* { sequence: ['D', 'D'], callback: () => deleteLine() },
|
|
39
|
+
* ])
|
|
40
|
+
* </script>
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```vue
|
|
45
|
+
* <script setup>
|
|
46
|
+
* import { computed } from 'vue'
|
|
47
|
+
* import { useHotkeySequences } from '@tanstack/vue-hotkeys'
|
|
48
|
+
*
|
|
49
|
+
* const items = computed(() => [...])
|
|
50
|
+
* useHotkeySequences(
|
|
51
|
+
* () => items.value.map((item) => ({
|
|
52
|
+
* sequence: item.chords,
|
|
53
|
+
* callback: item.action,
|
|
54
|
+
* options: { enabled: item.enabled },
|
|
55
|
+
* })),
|
|
56
|
+
* { preventDefault: true },
|
|
57
|
+
* )
|
|
58
|
+
* </script>
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare function useHotkeySequences(definitions: MaybeRefOrGetter<Array<UseHotkeySequenceDefinition>>, commonOptions?: MaybeRefOrGetter<UseHotkeySequenceOptions>): void;
|
|
62
|
+
//#endregion
|
|
63
|
+
export { UseHotkeySequenceDefinition, useHotkeySequences };
|
|
64
|
+
//# sourceMappingURL=useHotkeySequences.d.ts.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { useDefaultHotkeysOptions } from "./HotkeysProviderContext.js";
|
|
2
|
+
import { formatHotkeySequence, getSequenceManager } from "@tanstack/hotkeys";
|
|
3
|
+
import { onUnmounted, unref, watch } from "vue";
|
|
4
|
+
|
|
5
|
+
//#region src/useHotkeySequences.ts
|
|
6
|
+
/**
|
|
7
|
+
* Vue composable for registering multiple keyboard shortcut sequences at once (Vim-style).
|
|
8
|
+
*
|
|
9
|
+
* Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or a getter/ref
|
|
10
|
+
* that returns one, so you can register variable-length lists safely.
|
|
11
|
+
*
|
|
12
|
+
* Options are merged in this order:
|
|
13
|
+
* HotkeysProvider defaults < commonOptions < per-definition options
|
|
14
|
+
*
|
|
15
|
+
* Definitions with an empty `sequence` are skipped (no registration).
|
|
16
|
+
*
|
|
17
|
+
* @param definitions - Array of sequence definitions, or a getter/ref
|
|
18
|
+
* @param commonOptions - Shared options applied to all sequences (overridden by per-definition options)
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```vue
|
|
22
|
+
* <script setup>
|
|
23
|
+
* import { useHotkeySequences } from '@tanstack/vue-hotkeys'
|
|
24
|
+
*
|
|
25
|
+
* useHotkeySequences([
|
|
26
|
+
* { sequence: ['G', 'G'], callback: () => scrollToTop() },
|
|
27
|
+
* { sequence: ['D', 'D'], callback: () => deleteLine() },
|
|
28
|
+
* ])
|
|
29
|
+
* <\/script>
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```vue
|
|
34
|
+
* <script setup>
|
|
35
|
+
* import { computed } from 'vue'
|
|
36
|
+
* import { useHotkeySequences } from '@tanstack/vue-hotkeys'
|
|
37
|
+
*
|
|
38
|
+
* const items = computed(() => [...])
|
|
39
|
+
* useHotkeySequences(
|
|
40
|
+
* () => items.value.map((item) => ({
|
|
41
|
+
* sequence: item.chords,
|
|
42
|
+
* callback: item.action,
|
|
43
|
+
* options: { enabled: item.enabled },
|
|
44
|
+
* })),
|
|
45
|
+
* { preventDefault: true },
|
|
46
|
+
* )
|
|
47
|
+
* <\/script>
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
function useHotkeySequences(definitions, commonOptions = {}) {
|
|
51
|
+
const defaultOptions = useDefaultHotkeysOptions();
|
|
52
|
+
const manager = getSequenceManager();
|
|
53
|
+
const registrations = /* @__PURE__ */ new Map();
|
|
54
|
+
const stopWatcher = watch(() => {
|
|
55
|
+
const resolvedDefinitions = resolveMaybeRefOrGetter(definitions);
|
|
56
|
+
const resolvedCommonOptions = resolveMaybeRefOrGetter(commonOptions);
|
|
57
|
+
return resolvedDefinitions.map((def, i) => {
|
|
58
|
+
const resolvedSequence = resolveMaybeRefOrGetter(def.sequence);
|
|
59
|
+
const resolvedDefOptions = def.options ? resolveMaybeRefOrGetter(def.options) : {};
|
|
60
|
+
const mergedOptions = {
|
|
61
|
+
...defaultOptions.hotkeySequence,
|
|
62
|
+
...resolvedCommonOptions,
|
|
63
|
+
...resolvedDefOptions
|
|
64
|
+
};
|
|
65
|
+
const sequenceString = formatHotkeySequence(resolvedSequence);
|
|
66
|
+
const resolvedEnabled = mergedOptions.enabled === void 0 ? void 0 : resolveMaybeRefOrGetter(mergedOptions.enabled);
|
|
67
|
+
const resolvedTarget = mergedOptions.target === void 0 ? void 0 : resolveMaybeRefOrGetter(mergedOptions.target);
|
|
68
|
+
return {
|
|
69
|
+
index: i,
|
|
70
|
+
resolvedSequence,
|
|
71
|
+
sequenceString,
|
|
72
|
+
callback: def.callback,
|
|
73
|
+
mergedOptions,
|
|
74
|
+
resolvedEnabled,
|
|
75
|
+
resolvedTarget
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}, (resolved) => {
|
|
79
|
+
const nextKeys = /* @__PURE__ */ new Set();
|
|
80
|
+
const prepared = [];
|
|
81
|
+
for (const entry of resolved) {
|
|
82
|
+
if (entry.resolvedSequence.length === 0) continue;
|
|
83
|
+
const finalTarget = entry.resolvedTarget ?? (typeof document !== "undefined" ? document : null);
|
|
84
|
+
if (!finalTarget) continue;
|
|
85
|
+
const registrationKey = `${entry.index}:${entry.sequenceString}`;
|
|
86
|
+
nextKeys.add(registrationKey);
|
|
87
|
+
prepared.push({
|
|
88
|
+
registrationKey,
|
|
89
|
+
entry,
|
|
90
|
+
finalTarget
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
for (const [key, record] of [...registrations.entries()]) if (!nextKeys.has(key)) {
|
|
94
|
+
if (record.handle.isActive) record.handle.unregister();
|
|
95
|
+
registrations.delete(key);
|
|
96
|
+
}
|
|
97
|
+
for (const { registrationKey, entry, finalTarget } of prepared) {
|
|
98
|
+
const existing = registrations.get(registrationKey);
|
|
99
|
+
if (existing?.handle.isActive && existing.target === finalTarget) {
|
|
100
|
+
existing.handle.callback = entry.callback;
|
|
101
|
+
const { target: _target, enabled: _enabled, ...restOptions } = entry.mergedOptions;
|
|
102
|
+
const optionsWithoutTarget = {
|
|
103
|
+
...restOptions,
|
|
104
|
+
...entry.resolvedEnabled === void 0 ? {} : { enabled: entry.resolvedEnabled }
|
|
105
|
+
};
|
|
106
|
+
existing.handle.setOptions(optionsWithoutTarget);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (existing) {
|
|
110
|
+
if (existing.handle.isActive) existing.handle.unregister();
|
|
111
|
+
registrations.delete(registrationKey);
|
|
112
|
+
}
|
|
113
|
+
const { target: _target, enabled: _enabled, ...restOptions } = entry.mergedOptions;
|
|
114
|
+
const optionsWithoutTarget = {
|
|
115
|
+
...restOptions,
|
|
116
|
+
...entry.resolvedEnabled === void 0 ? {} : { enabled: entry.resolvedEnabled }
|
|
117
|
+
};
|
|
118
|
+
const handle = manager.register(entry.resolvedSequence, entry.callback, {
|
|
119
|
+
...optionsWithoutTarget,
|
|
120
|
+
target: finalTarget
|
|
121
|
+
});
|
|
122
|
+
registrations.set(registrationKey, {
|
|
123
|
+
handle,
|
|
124
|
+
target: finalTarget
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}, { immediate: true });
|
|
128
|
+
onUnmounted(() => {
|
|
129
|
+
stopWatcher();
|
|
130
|
+
for (const { handle } of registrations.values()) if (handle.isActive) handle.unregister();
|
|
131
|
+
registrations.clear();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function resolveMaybeRefOrGetter(value) {
|
|
135
|
+
return typeof value === "function" ? value() : unref(value);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
//#endregion
|
|
139
|
+
export { useHotkeySequences };
|
|
140
|
+
//# sourceMappingURL=useHotkeySequences.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useHotkeySequences.js","names":[],"sources":["../src/useHotkeySequences.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type { UseHotkeySequenceOptions } from './useHotkeySequence'\nimport type {\n HotkeyCallback,\n HotkeySequence,\n SequenceRegistrationHandle,\n} from '@tanstack/hotkeys'\nimport type { MaybeRefOrGetter } from 'vue'\n\n/**\n * A single sequence definition for use with `useHotkeySequences`.\n */\nexport interface UseHotkeySequenceDefinition {\n /** Array of hotkey strings that form the sequence */\n sequence: MaybeRefOrGetter<HotkeySequence>\n /** The function to call when the sequence is completed */\n callback: HotkeyCallback\n /** Per-sequence options (merged on top of commonOptions) */\n options?: MaybeRefOrGetter<UseHotkeySequenceOptions>\n}\n\n/**\n * Vue composable for registering multiple keyboard shortcut sequences at once (Vim-style).\n *\n * Uses the singleton SequenceManager. Accepts a dynamic array of definitions, or a getter/ref\n * that returns one, so you can register variable-length lists safely.\n *\n * Options are merged in this order:\n * HotkeysProvider defaults < commonOptions < per-definition options\n *\n * Definitions with an empty `sequence` are skipped (no registration).\n *\n * @param definitions - Array of sequence definitions, or a getter/ref\n * @param commonOptions - Shared options applied to all sequences (overridden by per-definition options)\n *\n * @example\n * ```vue\n * <script setup>\n * import { useHotkeySequences } from '@tanstack/vue-hotkeys'\n *\n * useHotkeySequences([\n * { sequence: ['G', 'G'], callback: () => scrollToTop() },\n * { sequence: ['D', 'D'], callback: () => deleteLine() },\n * ])\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { computed } from 'vue'\n * import { useHotkeySequences } from '@tanstack/vue-hotkeys'\n *\n * const items = computed(() => [...])\n * useHotkeySequences(\n * () => items.value.map((item) => ({\n * sequence: item.chords,\n * callback: item.action,\n * options: { enabled: item.enabled },\n * })),\n * { preventDefault: true },\n * )\n * </script>\n * ```\n */\nexport function useHotkeySequences(\n definitions: MaybeRefOrGetter<Array<UseHotkeySequenceDefinition>>,\n commonOptions: MaybeRefOrGetter<UseHotkeySequenceOptions> = {},\n): void {\n type RegistrationRecord = {\n handle: SequenceRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getSequenceManager()\n\n const registrations = new Map<string, RegistrationRecord>()\n\n const stopWatcher = watch(\n () => {\n const resolvedDefinitions = resolveMaybeRefOrGetter(definitions)\n const resolvedCommonOptions = resolveMaybeRefOrGetter(commonOptions)\n\n return resolvedDefinitions.map((def, i) => {\n const resolvedSequence = resolveMaybeRefOrGetter(def.sequence)\n const resolvedDefOptions = def.options\n ? resolveMaybeRefOrGetter(def.options)\n : {}\n\n const mergedOptions = {\n ...defaultOptions.hotkeySequence,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as UseHotkeySequenceOptions\n\n const sequenceString = formatHotkeySequence(resolvedSequence)\n\n const resolvedEnabled =\n mergedOptions.enabled === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.enabled)\n const resolvedTarget =\n mergedOptions.target === undefined\n ? undefined\n : resolveMaybeRefOrGetter(mergedOptions.target as any)\n\n return {\n index: i,\n resolvedSequence,\n sequenceString,\n callback: def.callback,\n mergedOptions,\n resolvedEnabled,\n resolvedTarget,\n }\n })\n },\n (resolved) => {\n const nextKeys = new Set<string>()\n const prepared: Array<{\n registrationKey: string\n entry: (typeof resolved)[number]\n finalTarget: Document | HTMLElement | Window\n }> = []\n\n for (const entry of resolved) {\n if (entry.resolvedSequence.length === 0) {\n continue\n }\n\n const finalTarget =\n entry.resolvedTarget ??\n (typeof document !== 'undefined' ? document : null)\n\n if (!finalTarget) {\n continue\n }\n\n const registrationKey = `${entry.index}:${entry.sequenceString}`\n nextKeys.add(registrationKey)\n prepared.push({ registrationKey, entry, finalTarget })\n }\n\n for (const [key, record] of [...registrations.entries()]) {\n if (!nextKeys.has(key)) {\n if (record.handle.isActive) {\n record.handle.unregister()\n }\n registrations.delete(key)\n }\n }\n\n for (const { registrationKey, entry, finalTarget } of prepared) {\n const existing = registrations.get(registrationKey)\n if (existing?.handle.isActive && existing.target === finalTarget) {\n existing.handle.callback = entry.callback\n const {\n target: _target,\n enabled: _enabled,\n ...restOptions\n } = entry.mergedOptions\n const optionsWithoutTarget = {\n ...restOptions,\n ...(entry.resolvedEnabled === undefined\n ? {}\n : { enabled: entry.resolvedEnabled }),\n }\n existing.handle.setOptions(optionsWithoutTarget)\n continue\n }\n\n if (existing) {\n if (existing.handle.isActive) {\n existing.handle.unregister()\n }\n registrations.delete(registrationKey)\n }\n\n const {\n target: _target,\n enabled: _enabled,\n ...restOptions\n } = entry.mergedOptions\n const optionsWithoutTarget = {\n ...restOptions,\n ...(entry.resolvedEnabled === undefined\n ? {}\n : { enabled: entry.resolvedEnabled }),\n }\n\n const handle = manager.register(\n entry.resolvedSequence,\n entry.callback,\n {\n ...optionsWithoutTarget,\n target: finalTarget,\n },\n )\n registrations.set(registrationKey, { handle, target: finalTarget })\n }\n },\n { immediate: true },\n )\n\n onUnmounted(() => {\n stopWatcher()\n for (const { handle } of registrations.values()) {\n if (handle.isActive) {\n handle.unregister()\n }\n }\n registrations.clear()\n })\n}\n\nfunction resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {\n return typeof value === 'function' ? (value as () => T)() : unref(value)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,SAAgB,mBACd,aACA,gBAA4D,EAAE,EACxD;CAMN,MAAM,iBAAiB,0BAA0B;CACjD,MAAM,UAAU,oBAAoB;CAEpC,MAAM,gCAAgB,IAAI,KAAiC;CAE3D,MAAM,cAAc,YACZ;EACJ,MAAM,sBAAsB,wBAAwB,YAAY;EAChE,MAAM,wBAAwB,wBAAwB,cAAc;AAEpE,SAAO,oBAAoB,KAAK,KAAK,MAAM;GACzC,MAAM,mBAAmB,wBAAwB,IAAI,SAAS;GAC9D,MAAM,qBAAqB,IAAI,UAC3B,wBAAwB,IAAI,QAAQ,GACpC,EAAE;GAEN,MAAM,gBAAgB;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;GAED,MAAM,iBAAiB,qBAAqB,iBAAiB;GAE7D,MAAM,kBACJ,cAAc,YAAY,SACtB,SACA,wBAAwB,cAAc,QAAQ;GACpD,MAAM,iBACJ,cAAc,WAAW,SACrB,SACA,wBAAwB,cAAc,OAAc;AAE1D,UAAO;IACL,OAAO;IACP;IACA;IACA,UAAU,IAAI;IACd;IACA;IACA;IACD;IACD;KAEH,aAAa;EACZ,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAID,EAAE;AAEP,OAAK,MAAM,SAAS,UAAU;AAC5B,OAAI,MAAM,iBAAiB,WAAW,EACpC;GAGF,MAAM,cACJ,MAAM,mBACL,OAAO,aAAa,cAAc,WAAW;AAEhD,OAAI,CAAC,YACH;GAGF,MAAM,kBAAkB,GAAG,MAAM,MAAM,GAAG,MAAM;AAChD,YAAS,IAAI,gBAAgB;AAC7B,YAAS,KAAK;IAAE;IAAiB;IAAO;IAAa,CAAC;;AAGxD,OAAK,MAAM,CAAC,KAAK,WAAW,CAAC,GAAG,cAAc,SAAS,CAAC,CACtD,KAAI,CAAC,SAAS,IAAI,IAAI,EAAE;AACtB,OAAI,OAAO,OAAO,SAChB,QAAO,OAAO,YAAY;AAE5B,iBAAc,OAAO,IAAI;;AAI7B,OAAK,MAAM,EAAE,iBAAiB,OAAO,iBAAiB,UAAU;GAC9D,MAAM,WAAW,cAAc,IAAI,gBAAgB;AACnD,OAAI,UAAU,OAAO,YAAY,SAAS,WAAW,aAAa;AAChE,aAAS,OAAO,WAAW,MAAM;IACjC,MAAM,EACJ,QAAQ,SACR,SAAS,UACT,GAAG,gBACD,MAAM;IACV,MAAM,uBAAuB;KAC3B,GAAG;KACH,GAAI,MAAM,oBAAoB,SAC1B,EAAE,GACF,EAAE,SAAS,MAAM,iBAAiB;KACvC;AACD,aAAS,OAAO,WAAW,qBAAqB;AAChD;;AAGF,OAAI,UAAU;AACZ,QAAI,SAAS,OAAO,SAClB,UAAS,OAAO,YAAY;AAE9B,kBAAc,OAAO,gBAAgB;;GAGvC,MAAM,EACJ,QAAQ,SACR,SAAS,UACT,GAAG,gBACD,MAAM;GACV,MAAM,uBAAuB;IAC3B,GAAG;IACH,GAAI,MAAM,oBAAoB,SAC1B,EAAE,GACF,EAAE,SAAS,MAAM,iBAAiB;IACvC;GAED,MAAM,SAAS,QAAQ,SACrB,MAAM,kBACN,MAAM,UACN;IACE,GAAG;IACH,QAAQ;IACT,CACF;AACD,iBAAc,IAAI,iBAAiB;IAAE;IAAQ,QAAQ;IAAa,CAAC;;IAGvE,EAAE,WAAW,MAAM,CACpB;AAED,mBAAkB;AAChB,eAAa;AACb,OAAK,MAAM,EAAE,YAAY,cAAc,QAAQ,CAC7C,KAAI,OAAO,SACT,QAAO,YAAY;AAGvB,gBAAc,OAAO;GACrB;;AAGJ,SAAS,wBAA2B,OAA+B;AACjE,QAAO,OAAO,UAAU,aAAc,OAAmB,GAAG,MAAM,MAAM"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/useHotkey.ts
CHANGED
|
@@ -106,6 +106,8 @@ export function useHotkey(
|
|
|
106
106
|
const manager = getHotkeyManager()
|
|
107
107
|
|
|
108
108
|
let registration: HotkeyRegistrationHandle | null = null
|
|
109
|
+
let lastHotkeyString: Hotkey | null = null
|
|
110
|
+
let lastTarget: HTMLElement | Document | Window | null = null
|
|
109
111
|
|
|
110
112
|
// Watch for changes to reactive dependencies
|
|
111
113
|
const stopWatcher = watch(
|
|
@@ -144,18 +146,22 @@ export function useHotkey(
|
|
|
144
146
|
|
|
145
147
|
// Resolve target
|
|
146
148
|
const finalTarget =
|
|
147
|
-
resolvedTarget
|
|
149
|
+
resolvedTarget === undefined
|
|
150
|
+
? typeof document !== 'undefined'
|
|
151
|
+
? document
|
|
152
|
+
: null
|
|
153
|
+
: resolvedTarget
|
|
148
154
|
|
|
149
155
|
if (!finalTarget) {
|
|
156
|
+
if (registration?.isActive) {
|
|
157
|
+
registration.unregister()
|
|
158
|
+
registration = null
|
|
159
|
+
}
|
|
160
|
+
lastHotkeyString = null
|
|
161
|
+
lastTarget = null
|
|
150
162
|
return
|
|
151
163
|
}
|
|
152
164
|
|
|
153
|
-
// Unregister previous registration if it exists
|
|
154
|
-
if (registration?.isActive) {
|
|
155
|
-
registration.unregister()
|
|
156
|
-
registration = null
|
|
157
|
-
}
|
|
158
|
-
|
|
159
165
|
// Extract options without target (target is handled separately)
|
|
160
166
|
const {
|
|
161
167
|
target: _target,
|
|
@@ -167,17 +173,33 @@ export function useHotkey(
|
|
|
167
173
|
...(resolvedEnabled === undefined ? {} : { enabled: resolvedEnabled }),
|
|
168
174
|
}
|
|
169
175
|
|
|
170
|
-
|
|
176
|
+
if (
|
|
177
|
+
registration?.isActive &&
|
|
178
|
+
lastHotkeyString === hotkeyString &&
|
|
179
|
+
lastTarget === finalTarget
|
|
180
|
+
) {
|
|
181
|
+
registration.callback = callback
|
|
182
|
+
registration.setOptions(optionsWithoutTarget)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (registration?.isActive) {
|
|
187
|
+
registration.unregister()
|
|
188
|
+
registration = null
|
|
189
|
+
}
|
|
190
|
+
|
|
171
191
|
registration = manager.register(hotkeyString, callback, {
|
|
172
192
|
...optionsWithoutTarget,
|
|
173
193
|
target: finalTarget,
|
|
174
194
|
})
|
|
175
195
|
|
|
176
|
-
// Update callback and options
|
|
177
196
|
if (registration.isActive) {
|
|
178
197
|
registration.callback = callback
|
|
179
198
|
registration.setOptions(optionsWithoutTarget)
|
|
180
199
|
}
|
|
200
|
+
|
|
201
|
+
lastHotkeyString = hotkeyString
|
|
202
|
+
lastTarget = finalTarget
|
|
181
203
|
},
|
|
182
204
|
{ immediate: true },
|
|
183
205
|
)
|
package/src/useHotkeySequence.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { onUnmounted, unref, watch } from 'vue'
|
|
2
|
-
import { getSequenceManager } from '@tanstack/hotkeys'
|
|
2
|
+
import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'
|
|
3
3
|
import { useDefaultHotkeysOptions } from './HotkeysProviderContext'
|
|
4
4
|
import type { MaybeRefOrGetter } from 'vue'
|
|
5
5
|
import type {
|
|
@@ -82,6 +82,8 @@ export function useHotkeySequence(
|
|
|
82
82
|
const manager = getSequenceManager()
|
|
83
83
|
|
|
84
84
|
let registration: SequenceRegistrationHandle | null = null
|
|
85
|
+
let lastSequenceKey: string | null = null
|
|
86
|
+
let lastTarget: HTMLElement | Document | Window | null = null
|
|
85
87
|
|
|
86
88
|
// Watch for changes to reactive dependencies
|
|
87
89
|
const stopWatcher = watch(
|
|
@@ -109,23 +111,24 @@ export function useHotkeySequence(
|
|
|
109
111
|
}
|
|
110
112
|
},
|
|
111
113
|
({ resolvedSequence, mergedOptions, resolvedEnabled, resolvedTarget }) => {
|
|
112
|
-
if (resolvedEnabled === false || resolvedSequence.length === 0) {
|
|
113
|
-
return
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Resolve target
|
|
117
114
|
const finalTarget =
|
|
118
|
-
resolvedTarget
|
|
115
|
+
resolvedTarget === undefined
|
|
116
|
+
? typeof document !== 'undefined'
|
|
117
|
+
? document
|
|
118
|
+
: null
|
|
119
|
+
: resolvedTarget
|
|
119
120
|
|
|
120
|
-
if (!finalTarget) {
|
|
121
|
+
if (resolvedSequence.length === 0 || !finalTarget) {
|
|
122
|
+
if (registration?.isActive) {
|
|
123
|
+
registration.unregister()
|
|
124
|
+
registration = null
|
|
125
|
+
}
|
|
126
|
+
lastSequenceKey = null
|
|
127
|
+
lastTarget = null
|
|
121
128
|
return
|
|
122
129
|
}
|
|
123
130
|
|
|
124
|
-
|
|
125
|
-
if (registration?.isActive) {
|
|
126
|
-
registration.unregister()
|
|
127
|
-
registration = null
|
|
128
|
-
}
|
|
131
|
+
const sequenceKey = formatHotkeySequence(resolvedSequence)
|
|
129
132
|
|
|
130
133
|
// Extract options without target (target is handled separately)
|
|
131
134
|
const {
|
|
@@ -138,17 +141,33 @@ export function useHotkeySequence(
|
|
|
138
141
|
...(resolvedEnabled === undefined ? {} : { enabled: resolvedEnabled }),
|
|
139
142
|
}
|
|
140
143
|
|
|
141
|
-
|
|
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
|
+
|
|
142
159
|
registration = manager.register(resolvedSequence, callback, {
|
|
143
160
|
...optionsWithoutTarget,
|
|
144
161
|
target: finalTarget,
|
|
145
162
|
})
|
|
146
163
|
|
|
147
|
-
// Update callback and options
|
|
148
164
|
if (registration.isActive) {
|
|
149
165
|
registration.callback = callback
|
|
150
166
|
registration.setOptions(optionsWithoutTarget)
|
|
151
167
|
}
|
|
168
|
+
|
|
169
|
+
lastSequenceKey = sequenceKey
|
|
170
|
+
lastTarget = finalTarget
|
|
152
171
|
},
|
|
153
172
|
{ immediate: true },
|
|
154
173
|
)
|
|
@@ -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
|
+
}
|