@tanstack/vue-hotkeys 0.6.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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"}
@@ -63,8 +63,7 @@ function useHotkeys(hotkeys, commonOptions = {}) {
63
63
  ...resolvedCommonOptions,
64
64
  ...resolvedDefOptions
65
65
  };
66
- const platform = mergedOptions.platform ?? (0, _tanstack_hotkeys.detectPlatform)();
67
- const hotkeyString = typeof resolvedHotkey === "string" ? resolvedHotkey : (0, _tanstack_hotkeys.formatHotkey)((0, _tanstack_hotkeys.rawHotkeyToParsedHotkey)(resolvedHotkey, platform));
66
+ const hotkeyString = (0, _tanstack_hotkeys.normalizeRegisterableHotkey)(resolvedHotkey, mergedOptions.platform ?? (0, _tanstack_hotkeys.detectPlatform)());
68
67
  const resolvedEnabled = mergedOptions.enabled === void 0 ? void 0 : resolveMaybeRefOrGetter(mergedOptions.enabled);
69
68
  const resolvedTarget = mergedOptions.target === void 0 ? void 0 : resolveMaybeRefOrGetter(mergedOptions.target);
70
69
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"useHotkeys.cjs","names":["useDefaultHotkeysOptions"],"sources":["../src/useHotkeys.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 { UseHotkeyOptions } from './useHotkey'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport type { MaybeRefOrGetter } from 'vue'\n\n/**\n * A single hotkey definition for use with `useHotkeys`.\n */\nexport interface UseHotkeyDefinition {\n /** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */\n hotkey: MaybeRefOrGetter<RegisterableHotkey>\n /** The function to call when the hotkey is pressed */\n callback: HotkeyCallback\n /** Per-hotkey options (merged on top of commonOptions) */\n options?: MaybeRefOrGetter<UseHotkeyOptions>\n}\n\n/**\n * Vue composable for registering multiple keyboard hotkeys at once.\n *\n * Uses the singleton HotkeyManager for efficient event handling.\n * Accepts a dynamic array of hotkey definitions, making it safe to use\n * with variable-length lists.\n *\n * Options are merged in this order:\n * HotkeysProvider defaults < commonOptions < per-definition options\n *\n * @param hotkeys - Array of hotkey definitions to register, or a getter/ref\n * @param commonOptions - Shared options applied to all hotkeys (overridden by per-definition options)\n *\n * @example\n * ```vue\n * <script setup>\n * import { useHotkeys } from '@tanstack/vue-hotkeys'\n *\n * useHotkeys([\n * { hotkey: 'Mod+S', callback: () => save() },\n * { hotkey: 'Mod+Z', callback: () => undo() },\n * { hotkey: 'Escape', callback: () => close() },\n * ])\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { computed } from 'vue'\n * import { useHotkeys } from '@tanstack/vue-hotkeys'\n *\n * const items = computed(() => [...])\n * // Dynamic hotkeys from reactive data\n * useHotkeys(\n * () => items.value.map((item) => ({\n * hotkey: item.shortcut,\n * callback: item.action,\n * options: { enabled: item.enabled },\n * })),\n * { preventDefault: true },\n * )\n * </script>\n * ```\n */\nexport function useHotkeys(\n hotkeys: MaybeRefOrGetter<Array<UseHotkeyDefinition>>,\n commonOptions: MaybeRefOrGetter<UseHotkeyOptions> = {},\n): void {\n type RegistrationRecord = {\n handle: HotkeyRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n\n const registrations = new Map<string, RegistrationRecord>()\n\n const stopWatcher = watch(\n () => {\n const resolvedHotkeys = resolveMaybeRefOrGetter(hotkeys)\n const resolvedCommonOptions = resolveMaybeRefOrGetter(commonOptions)\n\n return resolvedHotkeys.map((def, i) => {\n const resolvedHotkey = resolveMaybeRefOrGetter(def.hotkey)\n const resolvedDefOptions = def.options\n ? resolveMaybeRefOrGetter(def.options)\n : {}\n\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as UseHotkeyOptions\n\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 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 hotkeyString,\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 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.hotkeyString}`\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(entry.hotkeyString, entry.callback, {\n ...optionsWithoutTarget,\n target: finalTarget,\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,SAAgB,WACd,SACA,gBAAoD,EAAE,EAChD;CAMN,MAAM,iBAAiBA,yDAA0B;CACjD,MAAM,mDAA4B;CAElC,MAAM,gCAAgB,IAAI,KAAiC;CAE3D,MAAM,mCACE;EACJ,MAAM,kBAAkB,wBAAwB,QAAQ;EACxD,MAAM,wBAAwB,wBAAwB,cAAc;AAEpE,SAAO,gBAAgB,KAAK,KAAK,MAAM;GACrC,MAAM,iBAAiB,wBAAwB,IAAI,OAAO;GAC1D,MAAM,qBAAqB,IAAI,UAC3B,wBAAwB,IAAI,QAAQ,GACpC,EAAE;GAEN,MAAM,gBAAgB;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;GAED,MAAM,WAAW,cAAc,mDAA4B;GAC3D,MAAM,eACJ,OAAO,mBAAmB,WACtB,oGAE0B,gBAAuB,SAAS,CACzD;GAEP,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,UAAU,IAAI;IACd;IACA;IACA;IACD;IACD;KAEH,aAAa;EACZ,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAID,EAAE;AAEP,OAAK,MAAM,SAAS,UAAU;GAC5B,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,SAAS,MAAM,cAAc,MAAM,UAAU;IAClE,GAAG;IACH,QAAQ;IACT,CAAC;AACF,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"}
1
+ {"version":3,"file":"useHotkeys.cjs","names":["useDefaultHotkeysOptions"],"sources":["../src/useHotkeys.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport {\n detectPlatform,\n getHotkeyManager,\n normalizeRegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type { UseHotkeyOptions } from './useHotkey'\nimport type {\n HotkeyCallback,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport type { MaybeRefOrGetter } from 'vue'\n\n/**\n * A single hotkey definition for use with `useHotkeys`.\n */\nexport interface UseHotkeyDefinition {\n /** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */\n hotkey: MaybeRefOrGetter<RegisterableHotkey>\n /** The function to call when the hotkey is pressed */\n callback: HotkeyCallback\n /** Per-hotkey options (merged on top of commonOptions) */\n options?: MaybeRefOrGetter<UseHotkeyOptions>\n}\n\n/**\n * Vue composable for registering multiple keyboard hotkeys at once.\n *\n * Uses the singleton HotkeyManager for efficient event handling.\n * Accepts a dynamic array of hotkey definitions, making it safe to use\n * with variable-length lists.\n *\n * Options are merged in this order:\n * HotkeysProvider defaults < commonOptions < per-definition options\n *\n * @param hotkeys - Array of hotkey definitions to register, or a getter/ref\n * @param commonOptions - Shared options applied to all hotkeys (overridden by per-definition options)\n *\n * @example\n * ```vue\n * <script setup>\n * import { useHotkeys } from '@tanstack/vue-hotkeys'\n *\n * useHotkeys([\n * { hotkey: 'Mod+S', callback: () => save() },\n * { hotkey: 'Mod+Z', callback: () => undo() },\n * { hotkey: 'Escape', callback: () => close() },\n * ])\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { computed } from 'vue'\n * import { useHotkeys } from '@tanstack/vue-hotkeys'\n *\n * const items = computed(() => [...])\n * // Dynamic hotkeys from reactive data\n * useHotkeys(\n * () => items.value.map((item) => ({\n * hotkey: item.shortcut,\n * callback: item.action,\n * options: { enabled: item.enabled },\n * })),\n * { preventDefault: true },\n * )\n * </script>\n * ```\n */\nexport function useHotkeys(\n hotkeys: MaybeRefOrGetter<Array<UseHotkeyDefinition>>,\n commonOptions: MaybeRefOrGetter<UseHotkeyOptions> = {},\n): void {\n type RegistrationRecord = {\n handle: HotkeyRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n\n const registrations = new Map<string, RegistrationRecord>()\n\n const stopWatcher = watch(\n () => {\n const resolvedHotkeys = resolveMaybeRefOrGetter(hotkeys)\n const resolvedCommonOptions = resolveMaybeRefOrGetter(commonOptions)\n\n return resolvedHotkeys.map((def, i) => {\n const resolvedHotkey = resolveMaybeRefOrGetter(def.hotkey)\n const resolvedDefOptions = def.options\n ? resolveMaybeRefOrGetter(def.options)\n : {}\n\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as UseHotkeyOptions\n\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString = normalizeRegisterableHotkey(\n resolvedHotkey,\n platform,\n )\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 hotkeyString,\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 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.hotkeyString}`\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(entry.hotkeyString, entry.callback, {\n ...optionsWithoutTarget,\n target: finalTarget,\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAgB,WACd,SACA,gBAAoD,EAAE,EAChD;CAMN,MAAM,iBAAiBA,yDAA0B;CACjD,MAAM,mDAA4B;CAElC,MAAM,gCAAgB,IAAI,KAAiC;CAE3D,MAAM,mCACE;EACJ,MAAM,kBAAkB,wBAAwB,QAAQ;EACxD,MAAM,wBAAwB,wBAAwB,cAAc;AAEpE,SAAO,gBAAgB,KAAK,KAAK,MAAM;GACrC,MAAM,iBAAiB,wBAAwB,IAAI,OAAO;GAC1D,MAAM,qBAAqB,IAAI,UAC3B,wBAAwB,IAAI,QAAQ,GACpC,EAAE;GAEN,MAAM,gBAAgB;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;GAGD,MAAM,kEACJ,gBAFe,cAAc,mDAA4B,CAI1D;GAED,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,UAAU,IAAI;IACd;IACA;IACA;IACD;IACD;KAEH,aAAa;EACZ,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAID,EAAE;AAEP,OAAK,MAAM,SAAS,UAAU;GAC5B,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,SAAS,MAAM,cAAc,MAAM,UAAU;IAClE,GAAG;IACH,QAAQ;IACT,CAAC;AACF,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"}
@@ -1,5 +1,5 @@
1
1
  import { useDefaultHotkeysOptions } from "./HotkeysProviderContext.js";
2
- import { detectPlatform, formatHotkey, getHotkeyManager, rawHotkeyToParsedHotkey } from "@tanstack/hotkeys";
2
+ import { detectPlatform, getHotkeyManager, normalizeRegisterableHotkey } from "@tanstack/hotkeys";
3
3
  import { onUnmounted, unref, watch } from "vue";
4
4
 
5
5
  //#region src/useHotkeys.ts
@@ -63,8 +63,7 @@ function useHotkeys(hotkeys, commonOptions = {}) {
63
63
  ...resolvedCommonOptions,
64
64
  ...resolvedDefOptions
65
65
  };
66
- const platform = mergedOptions.platform ?? detectPlatform();
67
- const hotkeyString = typeof resolvedHotkey === "string" ? resolvedHotkey : formatHotkey(rawHotkeyToParsedHotkey(resolvedHotkey, platform));
66
+ const hotkeyString = normalizeRegisterableHotkey(resolvedHotkey, mergedOptions.platform ?? detectPlatform());
68
67
  const resolvedEnabled = mergedOptions.enabled === void 0 ? void 0 : resolveMaybeRefOrGetter(mergedOptions.enabled);
69
68
  const resolvedTarget = mergedOptions.target === void 0 ? void 0 : resolveMaybeRefOrGetter(mergedOptions.target);
70
69
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"useHotkeys.js","names":[],"sources":["../src/useHotkeys.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 { UseHotkeyOptions } from './useHotkey'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport type { MaybeRefOrGetter } from 'vue'\n\n/**\n * A single hotkey definition for use with `useHotkeys`.\n */\nexport interface UseHotkeyDefinition {\n /** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */\n hotkey: MaybeRefOrGetter<RegisterableHotkey>\n /** The function to call when the hotkey is pressed */\n callback: HotkeyCallback\n /** Per-hotkey options (merged on top of commonOptions) */\n options?: MaybeRefOrGetter<UseHotkeyOptions>\n}\n\n/**\n * Vue composable for registering multiple keyboard hotkeys at once.\n *\n * Uses the singleton HotkeyManager for efficient event handling.\n * Accepts a dynamic array of hotkey definitions, making it safe to use\n * with variable-length lists.\n *\n * Options are merged in this order:\n * HotkeysProvider defaults < commonOptions < per-definition options\n *\n * @param hotkeys - Array of hotkey definitions to register, or a getter/ref\n * @param commonOptions - Shared options applied to all hotkeys (overridden by per-definition options)\n *\n * @example\n * ```vue\n * <script setup>\n * import { useHotkeys } from '@tanstack/vue-hotkeys'\n *\n * useHotkeys([\n * { hotkey: 'Mod+S', callback: () => save() },\n * { hotkey: 'Mod+Z', callback: () => undo() },\n * { hotkey: 'Escape', callback: () => close() },\n * ])\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { computed } from 'vue'\n * import { useHotkeys } from '@tanstack/vue-hotkeys'\n *\n * const items = computed(() => [...])\n * // Dynamic hotkeys from reactive data\n * useHotkeys(\n * () => items.value.map((item) => ({\n * hotkey: item.shortcut,\n * callback: item.action,\n * options: { enabled: item.enabled },\n * })),\n * { preventDefault: true },\n * )\n * </script>\n * ```\n */\nexport function useHotkeys(\n hotkeys: MaybeRefOrGetter<Array<UseHotkeyDefinition>>,\n commonOptions: MaybeRefOrGetter<UseHotkeyOptions> = {},\n): void {\n type RegistrationRecord = {\n handle: HotkeyRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n\n const registrations = new Map<string, RegistrationRecord>()\n\n const stopWatcher = watch(\n () => {\n const resolvedHotkeys = resolveMaybeRefOrGetter(hotkeys)\n const resolvedCommonOptions = resolveMaybeRefOrGetter(commonOptions)\n\n return resolvedHotkeys.map((def, i) => {\n const resolvedHotkey = resolveMaybeRefOrGetter(def.hotkey)\n const resolvedDefOptions = def.options\n ? resolveMaybeRefOrGetter(def.options)\n : {}\n\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as UseHotkeyOptions\n\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 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 hotkeyString,\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 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.hotkeyString}`\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(entry.hotkeyString, entry.callback, {\n ...optionsWithoutTarget,\n target: finalTarget,\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,SAAgB,WACd,SACA,gBAAoD,EAAE,EAChD;CAMN,MAAM,iBAAiB,0BAA0B;CACjD,MAAM,UAAU,kBAAkB;CAElC,MAAM,gCAAgB,IAAI,KAAiC;CAE3D,MAAM,cAAc,YACZ;EACJ,MAAM,kBAAkB,wBAAwB,QAAQ;EACxD,MAAM,wBAAwB,wBAAwB,cAAc;AAEpE,SAAO,gBAAgB,KAAK,KAAK,MAAM;GACrC,MAAM,iBAAiB,wBAAwB,IAAI,OAAO;GAC1D,MAAM,qBAAqB,IAAI,UAC3B,wBAAwB,IAAI,QAAQ,GACpC,EAAE;GAEN,MAAM,gBAAgB;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;GAED,MAAM,WAAW,cAAc,YAAY,gBAAgB;GAC3D,MAAM,eACJ,OAAO,mBAAmB,WACtB,iBACC,aACC,wBAAwB,gBAAuB,SAAS,CACzD;GAEP,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,UAAU,IAAI;IACd;IACA;IACA;IACD;IACD;KAEH,aAAa;EACZ,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAID,EAAE;AAEP,OAAK,MAAM,SAAS,UAAU;GAC5B,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,SAAS,MAAM,cAAc,MAAM,UAAU;IAClE,GAAG;IACH,QAAQ;IACT,CAAC;AACF,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"}
1
+ {"version":3,"file":"useHotkeys.js","names":[],"sources":["../src/useHotkeys.ts"],"sourcesContent":["import { onUnmounted, unref, watch } from 'vue'\nimport {\n detectPlatform,\n getHotkeyManager,\n normalizeRegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport { useDefaultHotkeysOptions } from './HotkeysProviderContext'\nimport type { UseHotkeyOptions } from './useHotkey'\nimport type {\n HotkeyCallback,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport type { MaybeRefOrGetter } from 'vue'\n\n/**\n * A single hotkey definition for use with `useHotkeys`.\n */\nexport interface UseHotkeyDefinition {\n /** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */\n hotkey: MaybeRefOrGetter<RegisterableHotkey>\n /** The function to call when the hotkey is pressed */\n callback: HotkeyCallback\n /** Per-hotkey options (merged on top of commonOptions) */\n options?: MaybeRefOrGetter<UseHotkeyOptions>\n}\n\n/**\n * Vue composable for registering multiple keyboard hotkeys at once.\n *\n * Uses the singleton HotkeyManager for efficient event handling.\n * Accepts a dynamic array of hotkey definitions, making it safe to use\n * with variable-length lists.\n *\n * Options are merged in this order:\n * HotkeysProvider defaults < commonOptions < per-definition options\n *\n * @param hotkeys - Array of hotkey definitions to register, or a getter/ref\n * @param commonOptions - Shared options applied to all hotkeys (overridden by per-definition options)\n *\n * @example\n * ```vue\n * <script setup>\n * import { useHotkeys } from '@tanstack/vue-hotkeys'\n *\n * useHotkeys([\n * { hotkey: 'Mod+S', callback: () => save() },\n * { hotkey: 'Mod+Z', callback: () => undo() },\n * { hotkey: 'Escape', callback: () => close() },\n * ])\n * </script>\n * ```\n *\n * @example\n * ```vue\n * <script setup>\n * import { computed } from 'vue'\n * import { useHotkeys } from '@tanstack/vue-hotkeys'\n *\n * const items = computed(() => [...])\n * // Dynamic hotkeys from reactive data\n * useHotkeys(\n * () => items.value.map((item) => ({\n * hotkey: item.shortcut,\n * callback: item.action,\n * options: { enabled: item.enabled },\n * })),\n * { preventDefault: true },\n * )\n * </script>\n * ```\n */\nexport function useHotkeys(\n hotkeys: MaybeRefOrGetter<Array<UseHotkeyDefinition>>,\n commonOptions: MaybeRefOrGetter<UseHotkeyOptions> = {},\n): void {\n type RegistrationRecord = {\n handle: HotkeyRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = useDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n\n const registrations = new Map<string, RegistrationRecord>()\n\n const stopWatcher = watch(\n () => {\n const resolvedHotkeys = resolveMaybeRefOrGetter(hotkeys)\n const resolvedCommonOptions = resolveMaybeRefOrGetter(commonOptions)\n\n return resolvedHotkeys.map((def, i) => {\n const resolvedHotkey = resolveMaybeRefOrGetter(def.hotkey)\n const resolvedDefOptions = def.options\n ? resolveMaybeRefOrGetter(def.options)\n : {}\n\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as UseHotkeyOptions\n\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString = normalizeRegisterableHotkey(\n resolvedHotkey,\n platform,\n )\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 hotkeyString,\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 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.hotkeyString}`\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(entry.hotkeyString, entry.callback, {\n ...optionsWithoutTarget,\n target: finalTarget,\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAgB,WACd,SACA,gBAAoD,EAAE,EAChD;CAMN,MAAM,iBAAiB,0BAA0B;CACjD,MAAM,UAAU,kBAAkB;CAElC,MAAM,gCAAgB,IAAI,KAAiC;CAE3D,MAAM,cAAc,YACZ;EACJ,MAAM,kBAAkB,wBAAwB,QAAQ;EACxD,MAAM,wBAAwB,wBAAwB,cAAc;AAEpE,SAAO,gBAAgB,KAAK,KAAK,MAAM;GACrC,MAAM,iBAAiB,wBAAwB,IAAI,OAAO;GAC1D,MAAM,qBAAqB,IAAI,UAC3B,wBAAwB,IAAI,QAAQ,GACpC,EAAE;GAEN,MAAM,gBAAgB;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;GAGD,MAAM,eAAe,4BACnB,gBAFe,cAAc,YAAY,gBAAgB,CAI1D;GAED,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,UAAU,IAAI;IACd;IACA;IACA;IACD;IACD;KAEH,aAAa;EACZ,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAID,EAAE;AAEP,OAAK,MAAM,SAAS,UAAU;GAC5B,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,SAAS,MAAM,cAAc,MAAM,UAAU;IAClE,GAAG;IACH,QAAQ;IACT,CAAC;AACF,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.6.0",
3
+ "version": "0.8.1",
4
4
  "description": "Vue adapter for TanStack Hotkeys",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -42,8 +42,8 @@
42
42
  "src"
43
43
  ],
44
44
  "dependencies": {
45
- "@tanstack/vue-store": "^0.9.2",
46
- "@tanstack/hotkeys": "0.5.0"
45
+ "@tanstack/vue-store": "^0.9.3",
46
+ "@tanstack/hotkeys": "0.6.1"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@vitejs/plugin-vue": "^6.0.5",
package/src/index.ts CHANGED
@@ -12,5 +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'
16
17
  export * from './useHotkeySequenceRecorder'
package/src/useHotkey.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import { onUnmounted, unref, watch } from 'vue'
2
2
  import {
3
3
  detectPlatform,
4
- formatHotkey,
5
4
  getHotkeyManager,
6
- rawHotkeyToParsedHotkey,
5
+ normalizeRegisterableHotkey,
7
6
  } from '@tanstack/hotkeys'
8
7
  import { useDefaultHotkeysOptions } from './HotkeysProviderContext'
9
8
  import type {
@@ -106,6 +105,8 @@ export function useHotkey(
106
105
  const manager = getHotkeyManager()
107
106
 
108
107
  let registration: HotkeyRegistrationHandle | null = null
108
+ let lastHotkeyString: Hotkey | null = null
109
+ let lastTarget: HTMLElement | Document | Window | null = null
109
110
 
110
111
  // Watch for changes to reactive dependencies
111
112
  const stopWatcher = watch(
@@ -135,27 +136,26 @@ export function useHotkey(
135
136
  ({ resolvedHotkey, mergedOptions, resolvedEnabled, resolvedTarget }) => {
136
137
  // Normalize to hotkey string
137
138
  const platform = mergedOptions.platform ?? detectPlatform()
138
- const hotkeyString: Hotkey =
139
- typeof resolvedHotkey === 'string'
140
- ? resolvedHotkey
141
- : (formatHotkey(
142
- rawHotkeyToParsedHotkey(resolvedHotkey as any, platform),
143
- ) as Hotkey)
139
+ const hotkeyString = normalizeRegisterableHotkey(resolvedHotkey, platform)
144
140
 
145
141
  // Resolve target
146
142
  const finalTarget =
147
- resolvedTarget ?? (typeof document !== 'undefined' ? document : null)
143
+ resolvedTarget === undefined
144
+ ? typeof document !== 'undefined'
145
+ ? document
146
+ : null
147
+ : resolvedTarget
148
148
 
149
149
  if (!finalTarget) {
150
+ if (registration?.isActive) {
151
+ registration.unregister()
152
+ registration = null
153
+ }
154
+ lastHotkeyString = null
155
+ lastTarget = null
150
156
  return
151
157
  }
152
158
 
153
- // Unregister previous registration if it exists
154
- if (registration?.isActive) {
155
- registration.unregister()
156
- registration = null
157
- }
158
-
159
159
  // Extract options without target (target is handled separately)
160
160
  const {
161
161
  target: _target,
@@ -167,17 +167,33 @@ export function useHotkey(
167
167
  ...(resolvedEnabled === undefined ? {} : { enabled: resolvedEnabled }),
168
168
  }
169
169
 
170
- // Register the hotkey
170
+ if (
171
+ registration?.isActive &&
172
+ lastHotkeyString === hotkeyString &&
173
+ lastTarget === finalTarget
174
+ ) {
175
+ registration.callback = callback
176
+ registration.setOptions(optionsWithoutTarget)
177
+ return
178
+ }
179
+
180
+ if (registration?.isActive) {
181
+ registration.unregister()
182
+ registration = null
183
+ }
184
+
171
185
  registration = manager.register(hotkeyString, callback, {
172
186
  ...optionsWithoutTarget,
173
187
  target: finalTarget,
174
188
  })
175
189
 
176
- // Update callback and options
177
190
  if (registration.isActive) {
178
191
  registration.callback = callback
179
192
  registration.setOptions(optionsWithoutTarget)
180
193
  }
194
+
195
+ lastHotkeyString = hotkeyString
196
+ lastTarget = finalTarget
181
197
  },
182
198
  { immediate: true },
183
199
  )
@@ -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 ?? (typeof document !== 'undefined' ? document : null)
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
- // Unregister previous registration if it exists
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
- // Register the sequence
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
  )