@tanstack/vue-hotkeys 0.5.1 → 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.
@@ -0,0 +1,45 @@
1
+ import { useDefaultHotkeysOptions } from "./HotkeysProviderContext.js";
2
+ import { HotkeySequenceRecorder } from "@tanstack/hotkeys";
3
+ import { onUnmounted, unref, watch } from "vue";
4
+ import { useStore } from "@tanstack/vue-store";
5
+
6
+ //#region src/useHotkeySequenceRecorder.ts
7
+ /**
8
+ * Vue composable for recording multi-chord sequences (Vim-style shortcuts).
9
+ */
10
+ function useHotkeySequenceRecorder(options) {
11
+ const defaultOptions = useDefaultHotkeysOptions();
12
+ const recorder = new HotkeySequenceRecorder(resolveHotkeySequenceRecorderOptions(options, defaultOptions));
13
+ const isRecording = useStore(recorder.store, (state) => state.isRecording);
14
+ const steps = useStore(recorder.store, (state) => state.steps);
15
+ const recordedSequence = useStore(recorder.store, (state) => state.recordedSequence);
16
+ const stopWatcher = watch(() => resolveHotkeySequenceRecorderOptions(options, defaultOptions), (resolvedOptions) => {
17
+ recorder.setOptions(resolvedOptions);
18
+ }, { immediate: true });
19
+ onUnmounted(() => {
20
+ stopWatcher();
21
+ recorder.destroy();
22
+ });
23
+ return {
24
+ isRecording,
25
+ steps,
26
+ recordedSequence,
27
+ startRecording: () => recorder.start(),
28
+ stopRecording: () => recorder.stop(),
29
+ cancelRecording: () => recorder.cancel(),
30
+ commitRecording: () => recorder.commit()
31
+ };
32
+ }
33
+ function resolveHotkeySequenceRecorderOptions(options, defaultOptions) {
34
+ return {
35
+ ...defaultOptions.hotkeySequenceRecorder,
36
+ ...resolveMaybeRefOrGetter(options)
37
+ };
38
+ }
39
+ function resolveMaybeRefOrGetter(value) {
40
+ return typeof value === "function" ? value() : unref(value);
41
+ }
42
+
43
+ //#endregion
44
+ export { useHotkeySequenceRecorder };
45
+ //# sourceMappingURL=useHotkeySequenceRecorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useHotkeySequenceRecorder.js","names":[],"sources":["../src/useHotkeySequenceRecorder.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport { useStore } from '@tanstack/vue-store'\nimport { HotkeySequenceRecorder } from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type { MaybeRefOrGetter, Ref } from 'vue'\nimport type {\n HotkeySequence,\n HotkeySequenceRecorderOptions,\n} from '@tanstack/hotkeys'\n\nexport interface VueHotkeySequenceRecorder {\n isRecording: Ref<boolean>\n steps: Ref<HotkeySequence>\n recordedSequence: Ref<HotkeySequence | null>\n startRecording: () => void\n stopRecording: () => void\n cancelRecording: () => void\n commitRecording: () => void\n}\n\n/**\n * Vue composable for recording multi-chord sequences (Vim-style shortcuts).\n */\nexport function useHotkeySequenceRecorder(\n options: MaybeRefOrGetter<HotkeySequenceRecorderOptions>,\n): VueHotkeySequenceRecorder {\n const defaultOptions = useDefaultHotkeysOptions()\n const recorder = new HotkeySequenceRecorder(\n resolveHotkeySequenceRecorderOptions(options, defaultOptions),\n )\n\n const isRecording = useStore(recorder.store, (state) => state.isRecording)\n const steps = useStore(recorder.store, (state) => state.steps)\n const recordedSequence = useStore(\n recorder.store,\n (state) => state.recordedSequence,\n )\n\n const stopWatcher = watch(\n () => resolveHotkeySequenceRecorderOptions(options, defaultOptions),\n (resolvedOptions) => {\n recorder.setOptions(resolvedOptions)\n },\n { immediate: true },\n )\n\n onUnmounted(() => {\n stopWatcher()\n recorder.destroy()\n })\n\n return {\n isRecording: isRecording as Ref<boolean>,\n steps: steps as Ref<HotkeySequence>,\n recordedSequence: recordedSequence as Ref<HotkeySequence | null>,\n startRecording: () => recorder.start(),\n stopRecording: () => recorder.stop(),\n cancelRecording: () => recorder.cancel(),\n commitRecording: () => recorder.commit(),\n }\n}\n\nfunction resolveHotkeySequenceRecorderOptions(\n options: MaybeRefOrGetter<HotkeySequenceRecorderOptions>,\n defaultOptions: ReturnType<typeof useDefaultHotkeysOptions>,\n): HotkeySequenceRecorderOptions {\n return {\n ...defaultOptions.hotkeySequenceRecorder,\n ...resolveMaybeRefOrGetter(options),\n } as HotkeySequenceRecorderOptions\n}\n\nfunction resolveMaybeRefOrGetter<T>(value: MaybeRefOrGetter<T>): T {\n return typeof value === 'function' ? (value as () => T)() : unref(value)\n}\n"],"mappings":";;;;;;;;;AAuBA,SAAgB,0BACd,SAC2B;CAC3B,MAAM,iBAAiB,0BAA0B;CACjD,MAAM,WAAW,IAAI,uBACnB,qCAAqC,SAAS,eAAe,CAC9D;CAED,MAAM,cAAc,SAAS,SAAS,QAAQ,UAAU,MAAM,YAAY;CAC1E,MAAM,QAAQ,SAAS,SAAS,QAAQ,UAAU,MAAM,MAAM;CAC9D,MAAM,mBAAmB,SACvB,SAAS,QACR,UAAU,MAAM,iBAClB;CAED,MAAM,cAAc,YACZ,qCAAqC,SAAS,eAAe,GAClE,oBAAoB;AACnB,WAAS,WAAW,gBAAgB;IAEtC,EAAE,WAAW,MAAM,CACpB;AAED,mBAAkB;AAChB,eAAa;AACb,WAAS,SAAS;GAClB;AAEF,QAAO;EACQ;EACN;EACW;EAClB,sBAAsB,SAAS,OAAO;EACtC,qBAAqB,SAAS,MAAM;EACpC,uBAAuB,SAAS,QAAQ;EACxC,uBAAuB,SAAS,QAAQ;EACzC;;AAGH,SAAS,qCACP,SACA,gBAC+B;AAC/B,QAAO;EACL,GAAG,eAAe;EAClB,GAAG,wBAAwB,QAAQ;EACpC;;AAGH,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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/vue-hotkeys",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "description": "Vue adapter for TanStack Hotkeys",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  ],
44
44
  "dependencies": {
45
45
  "@tanstack/vue-store": "^0.9.2",
46
- "@tanstack/hotkeys": "0.4.3"
46
+ "@tanstack/hotkeys": "0.5.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@vitejs/plugin-vue": "^6.0.5",
@@ -1,12 +1,16 @@
1
1
  import { inject, provide } from 'vue'
2
2
  import type { InjectionKey } from 'vue'
3
- import type { HotkeyRecorderOptions } from '@tanstack/hotkeys'
3
+ import type {
4
+ HotkeyRecorderOptions,
5
+ HotkeySequenceRecorderOptions,
6
+ } from '@tanstack/hotkeys'
4
7
  import type { UseHotkeyOptions } from './useHotkey'
5
8
  import type { UseHotkeySequenceOptions } from './useHotkeySequence'
6
9
 
7
10
  export interface HotkeysProviderOptions {
8
11
  hotkey?: Partial<UseHotkeyOptions>
9
12
  hotkeyRecorder?: Partial<HotkeyRecorderOptions>
13
+ hotkeySequenceRecorder?: Partial<HotkeySequenceRecorderOptions>
10
14
  hotkeySequence?: Partial<UseHotkeySequenceOptions>
11
15
  }
12
16
 
package/src/index.ts CHANGED
@@ -12,4 +12,6 @@ export * from './useHeldKeys'
12
12
  export * from './useHeldKeyCodes'
13
13
  export * from './useKeyHold'
14
14
  export * from './useHotkeySequence'
15
+ export * from './useHotkeySequences'
15
16
  export * from './useHotkeyRecorder'
17
+ export * from './useHotkeySequenceRecorder'
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 ?? (typeof document !== 'undefined' ? document : null)
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
- // Register the hotkey
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
  )