@tanstack/angular-hotkeys 0.6.0 → 0.8.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,105 @@
1
+ const require_hotkeys_provider = require('./hotkeys-provider.cjs');
2
+ let _tanstack_hotkeys = require("@tanstack/hotkeys");
3
+ let _angular_core = require("@angular/core");
4
+
5
+ //#region src/injectHotkeySequences.ts
6
+ /**
7
+ * Angular inject-based API for registering multiple keyboard shortcut sequences at once (Vim-style).
8
+ *
9
+ * Uses the singleton SequenceManager. Call in an injection context (e.g. constructor).
10
+ * Uses `effect()` to track reactive dependencies when definitions or options are getters.
11
+ *
12
+ * Options are merged in this order:
13
+ * provideHotkeys defaults < commonOptions < per-definition options
14
+ *
15
+ * Definitions with an empty `sequence` are skipped. Disabled sequences (`enabled: false`)
16
+ * remain registered so they stay visible in devtools; the core manager suppresses execution.
17
+ *
18
+ * @param sequences - Array of sequence definitions, or getter returning them
19
+ * @param commonOptions - Shared options for all sequences, or getter
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * @Component({ ... })
24
+ * export class VimComponent {
25
+ * constructor() {
26
+ * injectHotkeySequences([
27
+ * { sequence: ['G', 'G'], callback: () => this.goTop() },
28
+ * { sequence: ['D', 'D'], callback: () => this.deleteLine() },
29
+ * ])
30
+ * }
31
+ * }
32
+ * ```
33
+ */
34
+ function injectHotkeySequences(sequences, commonOptions = {}) {
35
+ const defaultOptions = require_hotkeys_provider.injectDefaultHotkeysOptions();
36
+ const manager = (0, _tanstack_hotkeys.getSequenceManager)();
37
+ const destroyRef = (0, _angular_core.inject)(_angular_core.DestroyRef);
38
+ const registrations = /* @__PURE__ */ new Map();
39
+ destroyRef.onDestroy(() => {
40
+ for (const { handle } of registrations.values()) if (handle.isActive) handle.unregister();
41
+ registrations.clear();
42
+ });
43
+ (0, _angular_core.effect)(() => {
44
+ const resolvedSequences = typeof sequences === "function" ? sequences() : sequences;
45
+ const resolvedCommonOptions = typeof commonOptions === "function" ? commonOptions() : commonOptions;
46
+ const nextKeys = /* @__PURE__ */ new Set();
47
+ const prepared = [];
48
+ for (let i = 0; i < resolvedSequences.length; i++) {
49
+ const def = resolvedSequences[i];
50
+ const resolvedSequence = typeof def.sequence === "function" ? def.sequence() : def.sequence;
51
+ const resolvedDefOptions = typeof def.options === "function" ? def.options() : def.options ?? {};
52
+ const { enabled = true, ...sequenceOpts } = {
53
+ ...defaultOptions.hotkeySequence,
54
+ ...resolvedCommonOptions,
55
+ ...resolvedDefOptions
56
+ };
57
+ if (resolvedSequence.length === 0) continue;
58
+ const resolvedTarget = "target" in sequenceOpts ? sequenceOpts.target ?? null : typeof document !== "undefined" ? document : null;
59
+ if (!resolvedTarget) continue;
60
+ const registrationKey = `${i}:${(0, _tanstack_hotkeys.formatHotkeySequence)(resolvedSequence)}`;
61
+ nextKeys.add(registrationKey);
62
+ prepared.push({
63
+ registrationKey,
64
+ def,
65
+ resolvedSequence,
66
+ enabled,
67
+ sequenceOpts,
68
+ resolvedTarget
69
+ });
70
+ }
71
+ for (const [key, record] of [...registrations.entries()]) if (!nextKeys.has(key)) {
72
+ if (record.handle.isActive) record.handle.unregister();
73
+ registrations.delete(key);
74
+ }
75
+ for (const p of prepared) {
76
+ const existing = registrations.get(p.registrationKey);
77
+ if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
78
+ existing.handle.callback = p.def.callback;
79
+ const { target: _target, ...optionsWithoutTarget } = p.sequenceOpts;
80
+ existing.handle.setOptions({
81
+ ...optionsWithoutTarget,
82
+ enabled: p.enabled
83
+ });
84
+ continue;
85
+ }
86
+ if (existing) {
87
+ if (existing.handle.isActive) existing.handle.unregister();
88
+ registrations.delete(p.registrationKey);
89
+ }
90
+ const handle = manager.register(p.resolvedSequence, p.def.callback, {
91
+ ...p.sequenceOpts,
92
+ enabled: p.enabled,
93
+ target: p.resolvedTarget
94
+ });
95
+ registrations.set(p.registrationKey, {
96
+ handle,
97
+ target: p.resolvedTarget
98
+ });
99
+ }
100
+ });
101
+ }
102
+
103
+ //#endregion
104
+ exports.injectHotkeySequences = injectHotkeySequences;
105
+ //# sourceMappingURL=injectHotkeySequences.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectHotkeySequences.cjs","names":["injectDefaultHotkeysOptions","DestroyRef"],"sources":["../src/injectHotkeySequences.ts"],"sourcesContent":["import { DestroyRef, effect, inject } from '@angular/core'\nimport { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'\nimport { injectDefaultHotkeysOptions } from './hotkeys-provider'\nimport type { InjectHotkeySequenceOptions } from './injectHotkeySequence'\nimport type {\n HotkeyCallback,\n HotkeySequence,\n SequenceRegistrationHandle,\n} from '@tanstack/hotkeys'\n\n/**\n * A single sequence definition for use with `injectHotkeySequences`.\n */\nexport interface InjectHotkeySequenceDefinition {\n /** Array of hotkey strings that form the sequence */\n sequence: HotkeySequence | (() => 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?: InjectHotkeySequenceOptions | (() => InjectHotkeySequenceOptions)\n}\n\n/**\n * Angular inject-based API for registering multiple keyboard shortcut sequences at once (Vim-style).\n *\n * Uses the singleton SequenceManager. Call in an injection context (e.g. constructor).\n * Uses `effect()` to track reactive dependencies when definitions or options are getters.\n *\n * Options are merged in this order:\n * provideHotkeys defaults < commonOptions < per-definition options\n *\n * Definitions with an empty `sequence` are skipped. Disabled sequences (`enabled: false`)\n * remain registered so they stay visible in devtools; the core manager suppresses execution.\n *\n * @param sequences - Array of sequence definitions, or getter returning them\n * @param commonOptions - Shared options for all sequences, or getter\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class VimComponent {\n * constructor() {\n * injectHotkeySequences([\n * { sequence: ['G', 'G'], callback: () => this.goTop() },\n * { sequence: ['D', 'D'], callback: () => this.deleteLine() },\n * ])\n * }\n * }\n * ```\n */\nexport function injectHotkeySequences(\n sequences:\n | Array<InjectHotkeySequenceDefinition>\n | (() => Array<InjectHotkeySequenceDefinition>),\n commonOptions:\n | InjectHotkeySequenceOptions\n | (() => InjectHotkeySequenceOptions) = {},\n): void {\n type RegistrationRecord = {\n handle: SequenceRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = injectDefaultHotkeysOptions()\n const manager = getSequenceManager()\n const destroyRef = inject(DestroyRef)\n\n const registrations = new Map<string, RegistrationRecord>()\n\n destroyRef.onDestroy(() => {\n for (const { handle } of registrations.values()) {\n if (handle.isActive) {\n handle.unregister()\n }\n }\n registrations.clear()\n })\n\n effect(() => {\n const resolvedSequences =\n typeof sequences === 'function' ? sequences() : sequences\n const resolvedCommonOptions =\n typeof commonOptions === 'function' ? commonOptions() : commonOptions\n\n const nextKeys = new Set<string>()\n const prepared: Array<{\n registrationKey: string\n def: InjectHotkeySequenceDefinition\n resolvedSequence: HotkeySequence\n enabled: boolean\n sequenceOpts: Omit<InjectHotkeySequenceOptions, 'enabled'>\n resolvedTarget: Document | HTMLElement | Window\n }> = []\n\n for (let i = 0; i < resolvedSequences.length; i++) {\n const def = resolvedSequences[i]!\n const resolvedSequence =\n typeof def.sequence === 'function' ? def.sequence() : def.sequence\n const resolvedDefOptions =\n typeof def.options === 'function' ? def.options() : (def.options ?? {})\n\n const mergedOptions = {\n ...defaultOptions.hotkeySequence,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as InjectHotkeySequenceOptions\n\n const { enabled = true, ...sequenceOpts } = mergedOptions\n\n if (resolvedSequence.length === 0) {\n continue\n }\n\n const resolvedTarget =\n 'target' in sequenceOpts\n ? (sequenceOpts.target ?? null)\n : typeof document !== 'undefined'\n ? document\n : null\n\n if (!resolvedTarget) {\n continue\n }\n\n const registrationKey = `${i}:${formatHotkeySequence(resolvedSequence)}`\n nextKeys.add(registrationKey)\n prepared.push({\n registrationKey,\n def,\n resolvedSequence,\n enabled,\n sequenceOpts,\n resolvedTarget,\n })\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 p of prepared) {\n const existing = registrations.get(p.registrationKey)\n if (existing?.handle.isActive && existing.target === p.resolvedTarget) {\n existing.handle.callback = p.def.callback\n const { target: _target, ...optionsWithoutTarget } = p.sequenceOpts\n existing.handle.setOptions({\n ...optionsWithoutTarget,\n enabled: p.enabled,\n })\n continue\n }\n\n if (existing) {\n if (existing.handle.isActive) {\n existing.handle.unregister()\n }\n registrations.delete(p.registrationKey)\n }\n\n const handle = manager.register(p.resolvedSequence, p.def.callback, {\n ...p.sequenceOpts,\n enabled: p.enabled,\n target: p.resolvedTarget,\n })\n registrations.set(p.registrationKey, {\n handle,\n target: p.resolvedTarget,\n })\n }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,SAAgB,sBACd,WAGA,gBAE0C,EAAE,EACtC;CAMN,MAAM,iBAAiBA,sDAA6B;CACpD,MAAM,qDAA8B;CACpC,MAAM,uCAAoBC,yBAAW;CAErC,MAAM,gCAAgB,IAAI,KAAiC;AAE3D,YAAW,gBAAgB;AACzB,OAAK,MAAM,EAAE,YAAY,cAAc,QAAQ,CAC7C,KAAI,OAAO,SACT,QAAO,YAAY;AAGvB,gBAAc,OAAO;GACrB;AAEF,iCAAa;EACX,MAAM,oBACJ,OAAO,cAAc,aAAa,WAAW,GAAG;EAClD,MAAM,wBACJ,OAAO,kBAAkB,aAAa,eAAe,GAAG;EAE1D,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAOD,EAAE;AAEP,OAAK,IAAI,IAAI,GAAG,IAAI,kBAAkB,QAAQ,KAAK;GACjD,MAAM,MAAM,kBAAkB;GAC9B,MAAM,mBACJ,OAAO,IAAI,aAAa,aAAa,IAAI,UAAU,GAAG,IAAI;GAC5D,MAAM,qBACJ,OAAO,IAAI,YAAY,aAAa,IAAI,SAAS,GAAI,IAAI,WAAW,EAAE;GAQxE,MAAM,EAAE,UAAU,MAAM,GAAG,iBANL;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;AAID,OAAI,iBAAiB,WAAW,EAC9B;GAGF,MAAM,iBACJ,YAAY,eACP,aAAa,UAAU,OACxB,OAAO,aAAa,cAClB,WACA;AAER,OAAI,CAAC,eACH;GAGF,MAAM,kBAAkB,GAAG,EAAE,+CAAwB,iBAAiB;AACtE,YAAS,IAAI,gBAAgB;AAC7B,YAAS,KAAK;IACZ;IACA;IACA;IACA;IACA;IACA;IACD,CAAC;;AAGJ,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,KAAK,UAAU;GACxB,MAAM,WAAW,cAAc,IAAI,EAAE,gBAAgB;AACrD,OAAI,UAAU,OAAO,YAAY,SAAS,WAAW,EAAE,gBAAgB;AACrE,aAAS,OAAO,WAAW,EAAE,IAAI;IACjC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;AACvD,aAAS,OAAO,WAAW;KACzB,GAAG;KACH,SAAS,EAAE;KACZ,CAAC;AACF;;AAGF,OAAI,UAAU;AACZ,QAAI,SAAS,OAAO,SAClB,UAAS,OAAO,YAAY;AAE9B,kBAAc,OAAO,EAAE,gBAAgB;;GAGzC,MAAM,SAAS,QAAQ,SAAS,EAAE,kBAAkB,EAAE,IAAI,UAAU;IAClE,GAAG,EAAE;IACL,SAAS,EAAE;IACX,QAAQ,EAAE;IACX,CAAC;AACF,iBAAc,IAAI,EAAE,iBAAiB;IACnC;IACA,QAAQ,EAAE;IACX,CAAC;;GAEJ"}
@@ -0,0 +1,47 @@
1
+ import { InjectHotkeySequenceOptions } from "./injectHotkeySequence.cjs";
2
+ import { HotkeyCallback, HotkeySequence } from "@tanstack/hotkeys";
3
+
4
+ //#region src/injectHotkeySequences.d.ts
5
+ /**
6
+ * A single sequence definition for use with `injectHotkeySequences`.
7
+ */
8
+ interface InjectHotkeySequenceDefinition {
9
+ /** Array of hotkey strings that form the sequence */
10
+ sequence: HotkeySequence | (() => HotkeySequence);
11
+ /** The function to call when the sequence is completed */
12
+ callback: HotkeyCallback;
13
+ /** Per-sequence options (merged on top of commonOptions) */
14
+ options?: InjectHotkeySequenceOptions | (() => InjectHotkeySequenceOptions);
15
+ }
16
+ /**
17
+ * Angular inject-based API for registering multiple keyboard shortcut sequences at once (Vim-style).
18
+ *
19
+ * Uses the singleton SequenceManager. Call in an injection context (e.g. constructor).
20
+ * Uses `effect()` to track reactive dependencies when definitions or options are getters.
21
+ *
22
+ * Options are merged in this order:
23
+ * provideHotkeys defaults < commonOptions < per-definition options
24
+ *
25
+ * Definitions with an empty `sequence` are skipped. Disabled sequences (`enabled: false`)
26
+ * remain registered so they stay visible in devtools; the core manager suppresses execution.
27
+ *
28
+ * @param sequences - Array of sequence definitions, or getter returning them
29
+ * @param commonOptions - Shared options for all sequences, or getter
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * @Component({ ... })
34
+ * export class VimComponent {
35
+ * constructor() {
36
+ * injectHotkeySequences([
37
+ * { sequence: ['G', 'G'], callback: () => this.goTop() },
38
+ * { sequence: ['D', 'D'], callback: () => this.deleteLine() },
39
+ * ])
40
+ * }
41
+ * }
42
+ * ```
43
+ */
44
+ declare function injectHotkeySequences(sequences: Array<InjectHotkeySequenceDefinition> | (() => Array<InjectHotkeySequenceDefinition>), commonOptions?: InjectHotkeySequenceOptions | (() => InjectHotkeySequenceOptions)): void;
45
+ //#endregion
46
+ export { InjectHotkeySequenceDefinition, injectHotkeySequences };
47
+ //# sourceMappingURL=injectHotkeySequences.d.cts.map
@@ -0,0 +1,47 @@
1
+ import { InjectHotkeySequenceOptions } from "./injectHotkeySequence.js";
2
+ import { HotkeyCallback, HotkeySequence } from "@tanstack/hotkeys";
3
+
4
+ //#region src/injectHotkeySequences.d.ts
5
+ /**
6
+ * A single sequence definition for use with `injectHotkeySequences`.
7
+ */
8
+ interface InjectHotkeySequenceDefinition {
9
+ /** Array of hotkey strings that form the sequence */
10
+ sequence: HotkeySequence | (() => HotkeySequence);
11
+ /** The function to call when the sequence is completed */
12
+ callback: HotkeyCallback;
13
+ /** Per-sequence options (merged on top of commonOptions) */
14
+ options?: InjectHotkeySequenceOptions | (() => InjectHotkeySequenceOptions);
15
+ }
16
+ /**
17
+ * Angular inject-based API for registering multiple keyboard shortcut sequences at once (Vim-style).
18
+ *
19
+ * Uses the singleton SequenceManager. Call in an injection context (e.g. constructor).
20
+ * Uses `effect()` to track reactive dependencies when definitions or options are getters.
21
+ *
22
+ * Options are merged in this order:
23
+ * provideHotkeys defaults < commonOptions < per-definition options
24
+ *
25
+ * Definitions with an empty `sequence` are skipped. Disabled sequences (`enabled: false`)
26
+ * remain registered so they stay visible in devtools; the core manager suppresses execution.
27
+ *
28
+ * @param sequences - Array of sequence definitions, or getter returning them
29
+ * @param commonOptions - Shared options for all sequences, or getter
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * @Component({ ... })
34
+ * export class VimComponent {
35
+ * constructor() {
36
+ * injectHotkeySequences([
37
+ * { sequence: ['G', 'G'], callback: () => this.goTop() },
38
+ * { sequence: ['D', 'D'], callback: () => this.deleteLine() },
39
+ * ])
40
+ * }
41
+ * }
42
+ * ```
43
+ */
44
+ declare function injectHotkeySequences(sequences: Array<InjectHotkeySequenceDefinition> | (() => Array<InjectHotkeySequenceDefinition>), commonOptions?: InjectHotkeySequenceOptions | (() => InjectHotkeySequenceOptions)): void;
45
+ //#endregion
46
+ export { InjectHotkeySequenceDefinition, injectHotkeySequences };
47
+ //# sourceMappingURL=injectHotkeySequences.d.ts.map
@@ -0,0 +1,105 @@
1
+ import { injectDefaultHotkeysOptions } from "./hotkeys-provider.js";
2
+ import { formatHotkeySequence, getSequenceManager } from "@tanstack/hotkeys";
3
+ import { DestroyRef, effect, inject } from "@angular/core";
4
+
5
+ //#region src/injectHotkeySequences.ts
6
+ /**
7
+ * Angular inject-based API for registering multiple keyboard shortcut sequences at once (Vim-style).
8
+ *
9
+ * Uses the singleton SequenceManager. Call in an injection context (e.g. constructor).
10
+ * Uses `effect()` to track reactive dependencies when definitions or options are getters.
11
+ *
12
+ * Options are merged in this order:
13
+ * provideHotkeys defaults < commonOptions < per-definition options
14
+ *
15
+ * Definitions with an empty `sequence` are skipped. Disabled sequences (`enabled: false`)
16
+ * remain registered so they stay visible in devtools; the core manager suppresses execution.
17
+ *
18
+ * @param sequences - Array of sequence definitions, or getter returning them
19
+ * @param commonOptions - Shared options for all sequences, or getter
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * @Component({ ... })
24
+ * export class VimComponent {
25
+ * constructor() {
26
+ * injectHotkeySequences([
27
+ * { sequence: ['G', 'G'], callback: () => this.goTop() },
28
+ * { sequence: ['D', 'D'], callback: () => this.deleteLine() },
29
+ * ])
30
+ * }
31
+ * }
32
+ * ```
33
+ */
34
+ function injectHotkeySequences(sequences, commonOptions = {}) {
35
+ const defaultOptions = injectDefaultHotkeysOptions();
36
+ const manager = getSequenceManager();
37
+ const destroyRef = inject(DestroyRef);
38
+ const registrations = /* @__PURE__ */ new Map();
39
+ destroyRef.onDestroy(() => {
40
+ for (const { handle } of registrations.values()) if (handle.isActive) handle.unregister();
41
+ registrations.clear();
42
+ });
43
+ effect(() => {
44
+ const resolvedSequences = typeof sequences === "function" ? sequences() : sequences;
45
+ const resolvedCommonOptions = typeof commonOptions === "function" ? commonOptions() : commonOptions;
46
+ const nextKeys = /* @__PURE__ */ new Set();
47
+ const prepared = [];
48
+ for (let i = 0; i < resolvedSequences.length; i++) {
49
+ const def = resolvedSequences[i];
50
+ const resolvedSequence = typeof def.sequence === "function" ? def.sequence() : def.sequence;
51
+ const resolvedDefOptions = typeof def.options === "function" ? def.options() : def.options ?? {};
52
+ const { enabled = true, ...sequenceOpts } = {
53
+ ...defaultOptions.hotkeySequence,
54
+ ...resolvedCommonOptions,
55
+ ...resolvedDefOptions
56
+ };
57
+ if (resolvedSequence.length === 0) continue;
58
+ const resolvedTarget = "target" in sequenceOpts ? sequenceOpts.target ?? null : typeof document !== "undefined" ? document : null;
59
+ if (!resolvedTarget) continue;
60
+ const registrationKey = `${i}:${formatHotkeySequence(resolvedSequence)}`;
61
+ nextKeys.add(registrationKey);
62
+ prepared.push({
63
+ registrationKey,
64
+ def,
65
+ resolvedSequence,
66
+ enabled,
67
+ sequenceOpts,
68
+ resolvedTarget
69
+ });
70
+ }
71
+ for (const [key, record] of [...registrations.entries()]) if (!nextKeys.has(key)) {
72
+ if (record.handle.isActive) record.handle.unregister();
73
+ registrations.delete(key);
74
+ }
75
+ for (const p of prepared) {
76
+ const existing = registrations.get(p.registrationKey);
77
+ if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
78
+ existing.handle.callback = p.def.callback;
79
+ const { target: _target, ...optionsWithoutTarget } = p.sequenceOpts;
80
+ existing.handle.setOptions({
81
+ ...optionsWithoutTarget,
82
+ enabled: p.enabled
83
+ });
84
+ continue;
85
+ }
86
+ if (existing) {
87
+ if (existing.handle.isActive) existing.handle.unregister();
88
+ registrations.delete(p.registrationKey);
89
+ }
90
+ const handle = manager.register(p.resolvedSequence, p.def.callback, {
91
+ ...p.sequenceOpts,
92
+ enabled: p.enabled,
93
+ target: p.resolvedTarget
94
+ });
95
+ registrations.set(p.registrationKey, {
96
+ handle,
97
+ target: p.resolvedTarget
98
+ });
99
+ }
100
+ });
101
+ }
102
+
103
+ //#endregion
104
+ export { injectHotkeySequences };
105
+ //# sourceMappingURL=injectHotkeySequences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectHotkeySequences.js","names":[],"sources":["../src/injectHotkeySequences.ts"],"sourcesContent":["import { DestroyRef, effect, inject } from '@angular/core'\nimport { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'\nimport { injectDefaultHotkeysOptions } from './hotkeys-provider'\nimport type { InjectHotkeySequenceOptions } from './injectHotkeySequence'\nimport type {\n HotkeyCallback,\n HotkeySequence,\n SequenceRegistrationHandle,\n} from '@tanstack/hotkeys'\n\n/**\n * A single sequence definition for use with `injectHotkeySequences`.\n */\nexport interface InjectHotkeySequenceDefinition {\n /** Array of hotkey strings that form the sequence */\n sequence: HotkeySequence | (() => 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?: InjectHotkeySequenceOptions | (() => InjectHotkeySequenceOptions)\n}\n\n/**\n * Angular inject-based API for registering multiple keyboard shortcut sequences at once (Vim-style).\n *\n * Uses the singleton SequenceManager. Call in an injection context (e.g. constructor).\n * Uses `effect()` to track reactive dependencies when definitions or options are getters.\n *\n * Options are merged in this order:\n * provideHotkeys defaults < commonOptions < per-definition options\n *\n * Definitions with an empty `sequence` are skipped. Disabled sequences (`enabled: false`)\n * remain registered so they stay visible in devtools; the core manager suppresses execution.\n *\n * @param sequences - Array of sequence definitions, or getter returning them\n * @param commonOptions - Shared options for all sequences, or getter\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class VimComponent {\n * constructor() {\n * injectHotkeySequences([\n * { sequence: ['G', 'G'], callback: () => this.goTop() },\n * { sequence: ['D', 'D'], callback: () => this.deleteLine() },\n * ])\n * }\n * }\n * ```\n */\nexport function injectHotkeySequences(\n sequences:\n | Array<InjectHotkeySequenceDefinition>\n | (() => Array<InjectHotkeySequenceDefinition>),\n commonOptions:\n | InjectHotkeySequenceOptions\n | (() => InjectHotkeySequenceOptions) = {},\n): void {\n type RegistrationRecord = {\n handle: SequenceRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = injectDefaultHotkeysOptions()\n const manager = getSequenceManager()\n const destroyRef = inject(DestroyRef)\n\n const registrations = new Map<string, RegistrationRecord>()\n\n destroyRef.onDestroy(() => {\n for (const { handle } of registrations.values()) {\n if (handle.isActive) {\n handle.unregister()\n }\n }\n registrations.clear()\n })\n\n effect(() => {\n const resolvedSequences =\n typeof sequences === 'function' ? sequences() : sequences\n const resolvedCommonOptions =\n typeof commonOptions === 'function' ? commonOptions() : commonOptions\n\n const nextKeys = new Set<string>()\n const prepared: Array<{\n registrationKey: string\n def: InjectHotkeySequenceDefinition\n resolvedSequence: HotkeySequence\n enabled: boolean\n sequenceOpts: Omit<InjectHotkeySequenceOptions, 'enabled'>\n resolvedTarget: Document | HTMLElement | Window\n }> = []\n\n for (let i = 0; i < resolvedSequences.length; i++) {\n const def = resolvedSequences[i]!\n const resolvedSequence =\n typeof def.sequence === 'function' ? def.sequence() : def.sequence\n const resolvedDefOptions =\n typeof def.options === 'function' ? def.options() : (def.options ?? {})\n\n const mergedOptions = {\n ...defaultOptions.hotkeySequence,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as InjectHotkeySequenceOptions\n\n const { enabled = true, ...sequenceOpts } = mergedOptions\n\n if (resolvedSequence.length === 0) {\n continue\n }\n\n const resolvedTarget =\n 'target' in sequenceOpts\n ? (sequenceOpts.target ?? null)\n : typeof document !== 'undefined'\n ? document\n : null\n\n if (!resolvedTarget) {\n continue\n }\n\n const registrationKey = `${i}:${formatHotkeySequence(resolvedSequence)}`\n nextKeys.add(registrationKey)\n prepared.push({\n registrationKey,\n def,\n resolvedSequence,\n enabled,\n sequenceOpts,\n resolvedTarget,\n })\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 p of prepared) {\n const existing = registrations.get(p.registrationKey)\n if (existing?.handle.isActive && existing.target === p.resolvedTarget) {\n existing.handle.callback = p.def.callback\n const { target: _target, ...optionsWithoutTarget } = p.sequenceOpts\n existing.handle.setOptions({\n ...optionsWithoutTarget,\n enabled: p.enabled,\n })\n continue\n }\n\n if (existing) {\n if (existing.handle.isActive) {\n existing.handle.unregister()\n }\n registrations.delete(p.registrationKey)\n }\n\n const handle = manager.register(p.resolvedSequence, p.def.callback, {\n ...p.sequenceOpts,\n enabled: p.enabled,\n target: p.resolvedTarget,\n })\n registrations.set(p.registrationKey, {\n handle,\n target: p.resolvedTarget,\n })\n }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,SAAgB,sBACd,WAGA,gBAE0C,EAAE,EACtC;CAMN,MAAM,iBAAiB,6BAA6B;CACpD,MAAM,UAAU,oBAAoB;CACpC,MAAM,aAAa,OAAO,WAAW;CAErC,MAAM,gCAAgB,IAAI,KAAiC;AAE3D,YAAW,gBAAgB;AACzB,OAAK,MAAM,EAAE,YAAY,cAAc,QAAQ,CAC7C,KAAI,OAAO,SACT,QAAO,YAAY;AAGvB,gBAAc,OAAO;GACrB;AAEF,cAAa;EACX,MAAM,oBACJ,OAAO,cAAc,aAAa,WAAW,GAAG;EAClD,MAAM,wBACJ,OAAO,kBAAkB,aAAa,eAAe,GAAG;EAE1D,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAOD,EAAE;AAEP,OAAK,IAAI,IAAI,GAAG,IAAI,kBAAkB,QAAQ,KAAK;GACjD,MAAM,MAAM,kBAAkB;GAC9B,MAAM,mBACJ,OAAO,IAAI,aAAa,aAAa,IAAI,UAAU,GAAG,IAAI;GAC5D,MAAM,qBACJ,OAAO,IAAI,YAAY,aAAa,IAAI,SAAS,GAAI,IAAI,WAAW,EAAE;GAQxE,MAAM,EAAE,UAAU,MAAM,GAAG,iBANL;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;AAID,OAAI,iBAAiB,WAAW,EAC9B;GAGF,MAAM,iBACJ,YAAY,eACP,aAAa,UAAU,OACxB,OAAO,aAAa,cAClB,WACA;AAER,OAAI,CAAC,eACH;GAGF,MAAM,kBAAkB,GAAG,EAAE,GAAG,qBAAqB,iBAAiB;AACtE,YAAS,IAAI,gBAAgB;AAC7B,YAAS,KAAK;IACZ;IACA;IACA;IACA;IACA;IACA;IACD,CAAC;;AAGJ,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,KAAK,UAAU;GACxB,MAAM,WAAW,cAAc,IAAI,EAAE,gBAAgB;AACrD,OAAI,UAAU,OAAO,YAAY,SAAS,WAAW,EAAE,gBAAgB;AACrE,aAAS,OAAO,WAAW,EAAE,IAAI;IACjC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;AACvD,aAAS,OAAO,WAAW;KACzB,GAAG;KACH,SAAS,EAAE;KACZ,CAAC;AACF;;AAGF,OAAI,UAAU;AACZ,QAAI,SAAS,OAAO,SAClB,UAAS,OAAO,YAAY;AAE9B,kBAAc,OAAO,EAAE,gBAAgB;;GAGzC,MAAM,SAAS,QAAQ,SAAS,EAAE,kBAAkB,EAAE,IAAI,UAAU;IAClE,GAAG,EAAE;IACL,SAAS,EAAE;IACX,QAAQ,EAAE;IACX,CAAC;AACF,iBAAc,IAAI,EAAE,iBAAiB;IACnC;IACA,QAAQ,EAAE;IACX,CAAC;;GAEJ"}
@@ -73,8 +73,7 @@ function injectHotkeys(hotkeys, commonOptions = {}) {
73
73
  ...resolvedCommonOptions,
74
74
  ...resolvedDefOptions
75
75
  };
76
- const platform = mergedOptions.platform ?? (0, _tanstack_hotkeys.detectPlatform)();
77
- const hotkeyString = typeof resolvedHotkey === "string" ? resolvedHotkey : (0, _tanstack_hotkeys.formatHotkey)((0, _tanstack_hotkeys.rawHotkeyToParsedHotkey)(resolvedHotkey, platform));
76
+ const hotkeyString = (0, _tanstack_hotkeys.normalizeRegisterableHotkey)(resolvedHotkey, mergedOptions.platform ?? (0, _tanstack_hotkeys.detectPlatform)());
78
77
  const resolvedTarget = "target" in mergedOptions ? mergedOptions.target ?? null : typeof document !== "undefined" ? document : null;
79
78
  if (!resolvedTarget) continue;
80
79
  const registrationKey = `${i}:${hotkeyString}`;
@@ -1 +1 @@
1
- {"version":3,"file":"injectHotkeys.cjs","names":["injectDefaultHotkeysOptions","DestroyRef"],"sources":["../src/injectHotkeys.ts"],"sourcesContent":["import { DestroyRef, effect, inject } from '@angular/core'\nimport {\n detectPlatform,\n formatHotkey,\n getHotkeyManager,\n rawHotkeyToParsedHotkey,\n} from '@tanstack/hotkeys'\nimport { injectDefaultHotkeysOptions } from './hotkeys-provider'\nimport type { InjectHotkeyOptions } from './injectHotkey'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\n\n/**\n * A single hotkey definition for use with `injectHotkeys`.\n */\nexport interface InjectHotkeyDefinition {\n /** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */\n hotkey: RegisterableHotkey | (() => 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?: InjectHotkeyOptions | (() => InjectHotkeyOptions)\n}\n\n/**\n * Angular inject-based API 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.\n *\n * Call in an injection context (e.g. constructor or field initializer).\n * Uses effect() to track reactive dependencies and update registrations\n * when options or the callback change.\n *\n * Options are merged in this order:\n * provideHotkeys defaults < commonOptions < per-definition options\n *\n * @param hotkeys - Array of hotkey definitions, or getter returning them\n * @param commonOptions - Shared options for all hotkeys, or getter\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class EditorComponent {\n * constructor() {\n * injectHotkeys([\n * { hotkey: 'Mod+S', callback: () => this.save() },\n * { hotkey: 'Mod+Z', callback: () => this.undo() },\n * { hotkey: 'Escape', callback: () => this.close() },\n * ])\n * }\n * }\n * ```\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class DynamicShortcuts {\n * shortcuts = signal([...])\n *\n * constructor() {\n * injectHotkeys(\n * () => this.shortcuts().map((s) => ({\n * hotkey: s.key,\n * callback: s.action,\n * })),\n * )\n * }\n * }\n * ```\n */\nexport function injectHotkeys(\n hotkeys:\n | Array<InjectHotkeyDefinition>\n | (() => Array<InjectHotkeyDefinition>),\n commonOptions: InjectHotkeyOptions | (() => InjectHotkeyOptions) = {},\n): void {\n type RegistrationRecord = {\n handle: HotkeyRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = injectDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n const destroyRef = inject(DestroyRef)\n\n const registrations = new Map<string, RegistrationRecord>()\n\n destroyRef.onDestroy(() => {\n for (const { handle } of registrations.values()) {\n if (handle.isActive) {\n handle.unregister()\n }\n }\n registrations.clear()\n })\n\n effect(() => {\n const resolvedHotkeys = typeof hotkeys === 'function' ? hotkeys() : hotkeys\n const resolvedCommonOptions =\n typeof commonOptions === 'function' ? commonOptions() : commonOptions\n\n const nextKeys = new Set<string>()\n const prepared: Array<{\n registrationKey: string\n def: InjectHotkeyDefinition\n mergedOptions: InjectHotkeyOptions\n hotkeyString: Hotkey\n resolvedTarget: Document | HTMLElement | Window\n }> = []\n\n for (let i = 0; i < resolvedHotkeys.length; i++) {\n const def = resolvedHotkeys[i]!\n const resolvedHotkey =\n typeof def.hotkey === 'function' ? def.hotkey() : def.hotkey\n const resolvedDefOptions =\n typeof def.options === 'function' ? def.options() : (def.options ?? {})\n\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as InjectHotkeyOptions\n\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString: Hotkey =\n typeof resolvedHotkey === 'string'\n ? resolvedHotkey\n : (formatHotkey(\n rawHotkeyToParsedHotkey(resolvedHotkey, platform),\n ) as Hotkey)\n\n const resolvedTarget =\n 'target' in mergedOptions\n ? (mergedOptions.target ?? null)\n : typeof document !== 'undefined'\n ? document\n : null\n\n if (!resolvedTarget) {\n continue\n }\n\n const registrationKey = `${i}:${hotkeyString}`\n nextKeys.add(registrationKey)\n prepared.push({\n registrationKey,\n def,\n mergedOptions,\n hotkeyString,\n resolvedTarget,\n })\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 p of prepared) {\n const existing = registrations.get(p.registrationKey)\n if (existing?.handle.isActive && existing.target === p.resolvedTarget) {\n existing.handle.callback = p.def.callback\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\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(p.registrationKey)\n }\n\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\n const handle = manager.register(p.hotkeyString, p.def.callback, {\n ...optionsWithoutTarget,\n target: p.resolvedTarget,\n })\n registrations.set(p.registrationKey, {\n handle,\n target: p.resolvedTarget,\n })\n }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA,SAAgB,cACd,SAGA,gBAAmE,EAAE,EAC/D;CAMN,MAAM,iBAAiBA,sDAA6B;CACpD,MAAM,mDAA4B;CAClC,MAAM,uCAAoBC,yBAAW;CAErC,MAAM,gCAAgB,IAAI,KAAiC;AAE3D,YAAW,gBAAgB;AACzB,OAAK,MAAM,EAAE,YAAY,cAAc,QAAQ,CAC7C,KAAI,OAAO,SACT,QAAO,YAAY;AAGvB,gBAAc,OAAO;GACrB;AAEF,iCAAa;EACX,MAAM,kBAAkB,OAAO,YAAY,aAAa,SAAS,GAAG;EACpE,MAAM,wBACJ,OAAO,kBAAkB,aAAa,eAAe,GAAG;EAE1D,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAMD,EAAE;AAEP,OAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;GAC/C,MAAM,MAAM,gBAAgB;GAC5B,MAAM,iBACJ,OAAO,IAAI,WAAW,aAAa,IAAI,QAAQ,GAAG,IAAI;GACxD,MAAM,qBACJ,OAAO,IAAI,YAAY,aAAa,IAAI,SAAS,GAAI,IAAI,WAAW,EAAE;GAExE,MAAM,gBAAgB;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;GAED,MAAM,WAAW,cAAc,mDAA4B;GAC3D,MAAM,eACJ,OAAO,mBAAmB,WACtB,oGAE0B,gBAAgB,SAAS,CAClD;GAEP,MAAM,iBACJ,YAAY,gBACP,cAAc,UAAU,OACzB,OAAO,aAAa,cAClB,WACA;AAER,OAAI,CAAC,eACH;GAGF,MAAM,kBAAkB,GAAG,EAAE,GAAG;AAChC,YAAS,IAAI,gBAAgB;AAC7B,YAAS,KAAK;IACZ;IACA;IACA;IACA;IACA;IACD,CAAC;;AAGJ,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,KAAK,UAAU;GACxB,MAAM,WAAW,cAAc,IAAI,EAAE,gBAAgB;AACrD,OAAI,UAAU,OAAO,YAAY,SAAS,WAAW,EAAE,gBAAgB;AACrE,aAAS,OAAO,WAAW,EAAE,IAAI;IACjC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;AACvD,aAAS,OAAO,WAAW,qBAAqB;AAChD;;AAGF,OAAI,UAAU;AACZ,QAAI,SAAS,OAAO,SAClB,UAAS,OAAO,YAAY;AAE9B,kBAAc,OAAO,EAAE,gBAAgB;;GAGzC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;GACvD,MAAM,SAAS,QAAQ,SAAS,EAAE,cAAc,EAAE,IAAI,UAAU;IAC9D,GAAG;IACH,QAAQ,EAAE;IACX,CAAC;AACF,iBAAc,IAAI,EAAE,iBAAiB;IACnC;IACA,QAAQ,EAAE;IACX,CAAC;;GAEJ"}
1
+ {"version":3,"file":"injectHotkeys.cjs","names":["injectDefaultHotkeysOptions","DestroyRef"],"sources":["../src/injectHotkeys.ts"],"sourcesContent":["import { DestroyRef, effect, inject } from '@angular/core'\nimport {\n detectPlatform,\n getHotkeyManager,\n normalizeRegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport { injectDefaultHotkeysOptions } from './hotkeys-provider'\nimport type { InjectHotkeyOptions } from './injectHotkey'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\n\n/**\n * A single hotkey definition for use with `injectHotkeys`.\n */\nexport interface InjectHotkeyDefinition {\n /** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */\n hotkey: RegisterableHotkey | (() => 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?: InjectHotkeyOptions | (() => InjectHotkeyOptions)\n}\n\n/**\n * Angular inject-based API 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.\n *\n * Call in an injection context (e.g. constructor or field initializer).\n * Uses effect() to track reactive dependencies and update registrations\n * when options or the callback change.\n *\n * Options are merged in this order:\n * provideHotkeys defaults < commonOptions < per-definition options\n *\n * @param hotkeys - Array of hotkey definitions, or getter returning them\n * @param commonOptions - Shared options for all hotkeys, or getter\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class EditorComponent {\n * constructor() {\n * injectHotkeys([\n * { hotkey: 'Mod+S', callback: () => this.save() },\n * { hotkey: 'Mod+Z', callback: () => this.undo() },\n * { hotkey: 'Escape', callback: () => this.close() },\n * ])\n * }\n * }\n * ```\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class DynamicShortcuts {\n * shortcuts = signal([...])\n *\n * constructor() {\n * injectHotkeys(\n * () => this.shortcuts().map((s) => ({\n * hotkey: s.key,\n * callback: s.action,\n * })),\n * )\n * }\n * }\n * ```\n */\nexport function injectHotkeys(\n hotkeys:\n | Array<InjectHotkeyDefinition>\n | (() => Array<InjectHotkeyDefinition>),\n commonOptions: InjectHotkeyOptions | (() => InjectHotkeyOptions) = {},\n): void {\n type RegistrationRecord = {\n handle: HotkeyRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = injectDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n const destroyRef = inject(DestroyRef)\n\n const registrations = new Map<string, RegistrationRecord>()\n\n destroyRef.onDestroy(() => {\n for (const { handle } of registrations.values()) {\n if (handle.isActive) {\n handle.unregister()\n }\n }\n registrations.clear()\n })\n\n effect(() => {\n const resolvedHotkeys = typeof hotkeys === 'function' ? hotkeys() : hotkeys\n const resolvedCommonOptions =\n typeof commonOptions === 'function' ? commonOptions() : commonOptions\n\n const nextKeys = new Set<string>()\n const prepared: Array<{\n registrationKey: string\n def: InjectHotkeyDefinition\n mergedOptions: InjectHotkeyOptions\n hotkeyString: Hotkey\n resolvedTarget: Document | HTMLElement | Window\n }> = []\n\n for (let i = 0; i < resolvedHotkeys.length; i++) {\n const def = resolvedHotkeys[i]!\n const resolvedHotkey =\n typeof def.hotkey === 'function' ? def.hotkey() : def.hotkey\n const resolvedDefOptions =\n typeof def.options === 'function' ? def.options() : (def.options ?? {})\n\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as InjectHotkeyOptions\n\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString = normalizeRegisterableHotkey(resolvedHotkey, platform)\n\n const resolvedTarget =\n 'target' in mergedOptions\n ? (mergedOptions.target ?? null)\n : typeof document !== 'undefined'\n ? document\n : null\n\n if (!resolvedTarget) {\n continue\n }\n\n const registrationKey = `${i}:${hotkeyString}`\n nextKeys.add(registrationKey)\n prepared.push({\n registrationKey,\n def,\n mergedOptions,\n hotkeyString,\n resolvedTarget,\n })\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 p of prepared) {\n const existing = registrations.get(p.registrationKey)\n if (existing?.handle.isActive && existing.target === p.resolvedTarget) {\n existing.handle.callback = p.def.callback\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\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(p.registrationKey)\n }\n\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\n const handle = manager.register(p.hotkeyString, p.def.callback, {\n ...optionsWithoutTarget,\n target: p.resolvedTarget,\n })\n registrations.set(p.registrationKey, {\n handle,\n target: p.resolvedTarget,\n })\n }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,SAAgB,cACd,SAGA,gBAAmE,EAAE,EAC/D;CAMN,MAAM,iBAAiBA,sDAA6B;CACpD,MAAM,mDAA4B;CAClC,MAAM,uCAAoBC,yBAAW;CAErC,MAAM,gCAAgB,IAAI,KAAiC;AAE3D,YAAW,gBAAgB;AACzB,OAAK,MAAM,EAAE,YAAY,cAAc,QAAQ,CAC7C,KAAI,OAAO,SACT,QAAO,YAAY;AAGvB,gBAAc,OAAO;GACrB;AAEF,iCAAa;EACX,MAAM,kBAAkB,OAAO,YAAY,aAAa,SAAS,GAAG;EACpE,MAAM,wBACJ,OAAO,kBAAkB,aAAa,eAAe,GAAG;EAE1D,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAMD,EAAE;AAEP,OAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;GAC/C,MAAM,MAAM,gBAAgB;GAC5B,MAAM,iBACJ,OAAO,IAAI,WAAW,aAAa,IAAI,QAAQ,GAAG,IAAI;GACxD,MAAM,qBACJ,OAAO,IAAI,YAAY,aAAa,IAAI,SAAS,GAAI,IAAI,WAAW,EAAE;GAExE,MAAM,gBAAgB;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;GAGD,MAAM,kEAA2C,gBADhC,cAAc,mDAA4B,CACe;GAE1E,MAAM,iBACJ,YAAY,gBACP,cAAc,UAAU,OACzB,OAAO,aAAa,cAClB,WACA;AAER,OAAI,CAAC,eACH;GAGF,MAAM,kBAAkB,GAAG,EAAE,GAAG;AAChC,YAAS,IAAI,gBAAgB;AAC7B,YAAS,KAAK;IACZ;IACA;IACA;IACA;IACA;IACD,CAAC;;AAGJ,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,KAAK,UAAU;GACxB,MAAM,WAAW,cAAc,IAAI,EAAE,gBAAgB;AACrD,OAAI,UAAU,OAAO,YAAY,SAAS,WAAW,EAAE,gBAAgB;AACrE,aAAS,OAAO,WAAW,EAAE,IAAI;IACjC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;AACvD,aAAS,OAAO,WAAW,qBAAqB;AAChD;;AAGF,OAAI,UAAU;AACZ,QAAI,SAAS,OAAO,SAClB,UAAS,OAAO,YAAY;AAE9B,kBAAc,OAAO,EAAE,gBAAgB;;GAGzC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;GACvD,MAAM,SAAS,QAAQ,SAAS,EAAE,cAAc,EAAE,IAAI,UAAU;IAC9D,GAAG;IACH,QAAQ,EAAE;IACX,CAAC;AACF,iBAAc,IAAI,EAAE,iBAAiB;IACnC;IACA,QAAQ,EAAE;IACX,CAAC;;GAEJ"}
@@ -1,5 +1,5 @@
1
1
  import { injectDefaultHotkeysOptions } from "./hotkeys-provider.js";
2
- import { detectPlatform, formatHotkey, getHotkeyManager, rawHotkeyToParsedHotkey } from "@tanstack/hotkeys";
2
+ import { detectPlatform, getHotkeyManager, normalizeRegisterableHotkey } from "@tanstack/hotkeys";
3
3
  import { DestroyRef, effect, inject } from "@angular/core";
4
4
 
5
5
  //#region src/injectHotkeys.ts
@@ -73,8 +73,7 @@ function injectHotkeys(hotkeys, commonOptions = {}) {
73
73
  ...resolvedCommonOptions,
74
74
  ...resolvedDefOptions
75
75
  };
76
- const platform = mergedOptions.platform ?? detectPlatform();
77
- const hotkeyString = typeof resolvedHotkey === "string" ? resolvedHotkey : formatHotkey(rawHotkeyToParsedHotkey(resolvedHotkey, platform));
76
+ const hotkeyString = normalizeRegisterableHotkey(resolvedHotkey, mergedOptions.platform ?? detectPlatform());
78
77
  const resolvedTarget = "target" in mergedOptions ? mergedOptions.target ?? null : typeof document !== "undefined" ? document : null;
79
78
  if (!resolvedTarget) continue;
80
79
  const registrationKey = `${i}:${hotkeyString}`;
@@ -1 +1 @@
1
- {"version":3,"file":"injectHotkeys.js","names":[],"sources":["../src/injectHotkeys.ts"],"sourcesContent":["import { DestroyRef, effect, inject } from '@angular/core'\nimport {\n detectPlatform,\n formatHotkey,\n getHotkeyManager,\n rawHotkeyToParsedHotkey,\n} from '@tanstack/hotkeys'\nimport { injectDefaultHotkeysOptions } from './hotkeys-provider'\nimport type { InjectHotkeyOptions } from './injectHotkey'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\n\n/**\n * A single hotkey definition for use with `injectHotkeys`.\n */\nexport interface InjectHotkeyDefinition {\n /** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */\n hotkey: RegisterableHotkey | (() => 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?: InjectHotkeyOptions | (() => InjectHotkeyOptions)\n}\n\n/**\n * Angular inject-based API 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.\n *\n * Call in an injection context (e.g. constructor or field initializer).\n * Uses effect() to track reactive dependencies and update registrations\n * when options or the callback change.\n *\n * Options are merged in this order:\n * provideHotkeys defaults < commonOptions < per-definition options\n *\n * @param hotkeys - Array of hotkey definitions, or getter returning them\n * @param commonOptions - Shared options for all hotkeys, or getter\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class EditorComponent {\n * constructor() {\n * injectHotkeys([\n * { hotkey: 'Mod+S', callback: () => this.save() },\n * { hotkey: 'Mod+Z', callback: () => this.undo() },\n * { hotkey: 'Escape', callback: () => this.close() },\n * ])\n * }\n * }\n * ```\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class DynamicShortcuts {\n * shortcuts = signal([...])\n *\n * constructor() {\n * injectHotkeys(\n * () => this.shortcuts().map((s) => ({\n * hotkey: s.key,\n * callback: s.action,\n * })),\n * )\n * }\n * }\n * ```\n */\nexport function injectHotkeys(\n hotkeys:\n | Array<InjectHotkeyDefinition>\n | (() => Array<InjectHotkeyDefinition>),\n commonOptions: InjectHotkeyOptions | (() => InjectHotkeyOptions) = {},\n): void {\n type RegistrationRecord = {\n handle: HotkeyRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = injectDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n const destroyRef = inject(DestroyRef)\n\n const registrations = new Map<string, RegistrationRecord>()\n\n destroyRef.onDestroy(() => {\n for (const { handle } of registrations.values()) {\n if (handle.isActive) {\n handle.unregister()\n }\n }\n registrations.clear()\n })\n\n effect(() => {\n const resolvedHotkeys = typeof hotkeys === 'function' ? hotkeys() : hotkeys\n const resolvedCommonOptions =\n typeof commonOptions === 'function' ? commonOptions() : commonOptions\n\n const nextKeys = new Set<string>()\n const prepared: Array<{\n registrationKey: string\n def: InjectHotkeyDefinition\n mergedOptions: InjectHotkeyOptions\n hotkeyString: Hotkey\n resolvedTarget: Document | HTMLElement | Window\n }> = []\n\n for (let i = 0; i < resolvedHotkeys.length; i++) {\n const def = resolvedHotkeys[i]!\n const resolvedHotkey =\n typeof def.hotkey === 'function' ? def.hotkey() : def.hotkey\n const resolvedDefOptions =\n typeof def.options === 'function' ? def.options() : (def.options ?? {})\n\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as InjectHotkeyOptions\n\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString: Hotkey =\n typeof resolvedHotkey === 'string'\n ? resolvedHotkey\n : (formatHotkey(\n rawHotkeyToParsedHotkey(resolvedHotkey, platform),\n ) as Hotkey)\n\n const resolvedTarget =\n 'target' in mergedOptions\n ? (mergedOptions.target ?? null)\n : typeof document !== 'undefined'\n ? document\n : null\n\n if (!resolvedTarget) {\n continue\n }\n\n const registrationKey = `${i}:${hotkeyString}`\n nextKeys.add(registrationKey)\n prepared.push({\n registrationKey,\n def,\n mergedOptions,\n hotkeyString,\n resolvedTarget,\n })\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 p of prepared) {\n const existing = registrations.get(p.registrationKey)\n if (existing?.handle.isActive && existing.target === p.resolvedTarget) {\n existing.handle.callback = p.def.callback\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\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(p.registrationKey)\n }\n\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\n const handle = manager.register(p.hotkeyString, p.def.callback, {\n ...optionsWithoutTarget,\n target: p.resolvedTarget,\n })\n registrations.set(p.registrationKey, {\n handle,\n target: p.resolvedTarget,\n })\n }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA,SAAgB,cACd,SAGA,gBAAmE,EAAE,EAC/D;CAMN,MAAM,iBAAiB,6BAA6B;CACpD,MAAM,UAAU,kBAAkB;CAClC,MAAM,aAAa,OAAO,WAAW;CAErC,MAAM,gCAAgB,IAAI,KAAiC;AAE3D,YAAW,gBAAgB;AACzB,OAAK,MAAM,EAAE,YAAY,cAAc,QAAQ,CAC7C,KAAI,OAAO,SACT,QAAO,YAAY;AAGvB,gBAAc,OAAO;GACrB;AAEF,cAAa;EACX,MAAM,kBAAkB,OAAO,YAAY,aAAa,SAAS,GAAG;EACpE,MAAM,wBACJ,OAAO,kBAAkB,aAAa,eAAe,GAAG;EAE1D,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAMD,EAAE;AAEP,OAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;GAC/C,MAAM,MAAM,gBAAgB;GAC5B,MAAM,iBACJ,OAAO,IAAI,WAAW,aAAa,IAAI,QAAQ,GAAG,IAAI;GACxD,MAAM,qBACJ,OAAO,IAAI,YAAY,aAAa,IAAI,SAAS,GAAI,IAAI,WAAW,EAAE;GAExE,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,gBAAgB,SAAS,CAClD;GAEP,MAAM,iBACJ,YAAY,gBACP,cAAc,UAAU,OACzB,OAAO,aAAa,cAClB,WACA;AAER,OAAI,CAAC,eACH;GAGF,MAAM,kBAAkB,GAAG,EAAE,GAAG;AAChC,YAAS,IAAI,gBAAgB;AAC7B,YAAS,KAAK;IACZ;IACA;IACA;IACA;IACA;IACD,CAAC;;AAGJ,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,KAAK,UAAU;GACxB,MAAM,WAAW,cAAc,IAAI,EAAE,gBAAgB;AACrD,OAAI,UAAU,OAAO,YAAY,SAAS,WAAW,EAAE,gBAAgB;AACrE,aAAS,OAAO,WAAW,EAAE,IAAI;IACjC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;AACvD,aAAS,OAAO,WAAW,qBAAqB;AAChD;;AAGF,OAAI,UAAU;AACZ,QAAI,SAAS,OAAO,SAClB,UAAS,OAAO,YAAY;AAE9B,kBAAc,OAAO,EAAE,gBAAgB;;GAGzC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;GACvD,MAAM,SAAS,QAAQ,SAAS,EAAE,cAAc,EAAE,IAAI,UAAU;IAC9D,GAAG;IACH,QAAQ,EAAE;IACX,CAAC;AACF,iBAAc,IAAI,EAAE,iBAAiB;IACnC;IACA,QAAQ,EAAE;IACX,CAAC;;GAEJ"}
1
+ {"version":3,"file":"injectHotkeys.js","names":[],"sources":["../src/injectHotkeys.ts"],"sourcesContent":["import { DestroyRef, effect, inject } from '@angular/core'\nimport {\n detectPlatform,\n getHotkeyManager,\n normalizeRegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport { injectDefaultHotkeysOptions } from './hotkeys-provider'\nimport type { InjectHotkeyOptions } from './injectHotkey'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\n\n/**\n * A single hotkey definition for use with `injectHotkeys`.\n */\nexport interface InjectHotkeyDefinition {\n /** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */\n hotkey: RegisterableHotkey | (() => 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?: InjectHotkeyOptions | (() => InjectHotkeyOptions)\n}\n\n/**\n * Angular inject-based API 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.\n *\n * Call in an injection context (e.g. constructor or field initializer).\n * Uses effect() to track reactive dependencies and update registrations\n * when options or the callback change.\n *\n * Options are merged in this order:\n * provideHotkeys defaults < commonOptions < per-definition options\n *\n * @param hotkeys - Array of hotkey definitions, or getter returning them\n * @param commonOptions - Shared options for all hotkeys, or getter\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class EditorComponent {\n * constructor() {\n * injectHotkeys([\n * { hotkey: 'Mod+S', callback: () => this.save() },\n * { hotkey: 'Mod+Z', callback: () => this.undo() },\n * { hotkey: 'Escape', callback: () => this.close() },\n * ])\n * }\n * }\n * ```\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class DynamicShortcuts {\n * shortcuts = signal([...])\n *\n * constructor() {\n * injectHotkeys(\n * () => this.shortcuts().map((s) => ({\n * hotkey: s.key,\n * callback: s.action,\n * })),\n * )\n * }\n * }\n * ```\n */\nexport function injectHotkeys(\n hotkeys:\n | Array<InjectHotkeyDefinition>\n | (() => Array<InjectHotkeyDefinition>),\n commonOptions: InjectHotkeyOptions | (() => InjectHotkeyOptions) = {},\n): void {\n type RegistrationRecord = {\n handle: HotkeyRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = injectDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n const destroyRef = inject(DestroyRef)\n\n const registrations = new Map<string, RegistrationRecord>()\n\n destroyRef.onDestroy(() => {\n for (const { handle } of registrations.values()) {\n if (handle.isActive) {\n handle.unregister()\n }\n }\n registrations.clear()\n })\n\n effect(() => {\n const resolvedHotkeys = typeof hotkeys === 'function' ? hotkeys() : hotkeys\n const resolvedCommonOptions =\n typeof commonOptions === 'function' ? commonOptions() : commonOptions\n\n const nextKeys = new Set<string>()\n const prepared: Array<{\n registrationKey: string\n def: InjectHotkeyDefinition\n mergedOptions: InjectHotkeyOptions\n hotkeyString: Hotkey\n resolvedTarget: Document | HTMLElement | Window\n }> = []\n\n for (let i = 0; i < resolvedHotkeys.length; i++) {\n const def = resolvedHotkeys[i]!\n const resolvedHotkey =\n typeof def.hotkey === 'function' ? def.hotkey() : def.hotkey\n const resolvedDefOptions =\n typeof def.options === 'function' ? def.options() : (def.options ?? {})\n\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as InjectHotkeyOptions\n\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString = normalizeRegisterableHotkey(resolvedHotkey, platform)\n\n const resolvedTarget =\n 'target' in mergedOptions\n ? (mergedOptions.target ?? null)\n : typeof document !== 'undefined'\n ? document\n : null\n\n if (!resolvedTarget) {\n continue\n }\n\n const registrationKey = `${i}:${hotkeyString}`\n nextKeys.add(registrationKey)\n prepared.push({\n registrationKey,\n def,\n mergedOptions,\n hotkeyString,\n resolvedTarget,\n })\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 p of prepared) {\n const existing = registrations.get(p.registrationKey)\n if (existing?.handle.isActive && existing.target === p.resolvedTarget) {\n existing.handle.callback = p.def.callback\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\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(p.registrationKey)\n }\n\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\n const handle = manager.register(p.hotkeyString, p.def.callback, {\n ...optionsWithoutTarget,\n target: p.resolvedTarget,\n })\n registrations.set(p.registrationKey, {\n handle,\n target: p.resolvedTarget,\n })\n }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,SAAgB,cACd,SAGA,gBAAmE,EAAE,EAC/D;CAMN,MAAM,iBAAiB,6BAA6B;CACpD,MAAM,UAAU,kBAAkB;CAClC,MAAM,aAAa,OAAO,WAAW;CAErC,MAAM,gCAAgB,IAAI,KAAiC;AAE3D,YAAW,gBAAgB;AACzB,OAAK,MAAM,EAAE,YAAY,cAAc,QAAQ,CAC7C,KAAI,OAAO,SACT,QAAO,YAAY;AAGvB,gBAAc,OAAO;GACrB;AAEF,cAAa;EACX,MAAM,kBAAkB,OAAO,YAAY,aAAa,SAAS,GAAG;EACpE,MAAM,wBACJ,OAAO,kBAAkB,aAAa,eAAe,GAAG;EAE1D,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAMD,EAAE;AAEP,OAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;GAC/C,MAAM,MAAM,gBAAgB;GAC5B,MAAM,iBACJ,OAAO,IAAI,WAAW,aAAa,IAAI,QAAQ,GAAG,IAAI;GACxD,MAAM,qBACJ,OAAO,IAAI,YAAY,aAAa,IAAI,SAAS,GAAI,IAAI,WAAW,EAAE;GAExE,MAAM,gBAAgB;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;GAGD,MAAM,eAAe,4BAA4B,gBADhC,cAAc,YAAY,gBAAgB,CACe;GAE1E,MAAM,iBACJ,YAAY,gBACP,cAAc,UAAU,OACzB,OAAO,aAAa,cAClB,WACA;AAER,OAAI,CAAC,eACH;GAGF,MAAM,kBAAkB,GAAG,EAAE,GAAG;AAChC,YAAS,IAAI,gBAAgB;AAC7B,YAAS,KAAK;IACZ;IACA;IACA;IACA;IACA;IACD,CAAC;;AAGJ,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,KAAK,UAAU;GACxB,MAAM,WAAW,cAAc,IAAI,EAAE,gBAAgB;AACrD,OAAI,UAAU,OAAO,YAAY,SAAS,WAAW,EAAE,gBAAgB;AACrE,aAAS,OAAO,WAAW,EAAE,IAAI;IACjC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;AACvD,aAAS,OAAO,WAAW,qBAAqB;AAChD;;AAGF,OAAI,UAAU;AACZ,QAAI,SAAS,OAAO,SAClB,UAAS,OAAO,YAAY;AAE9B,kBAAc,OAAO,EAAE,gBAAgB;;GAGzC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;GACvD,MAAM,SAAS,QAAQ,SAAS,EAAE,cAAc,EAAE,IAAI,UAAU;IAC9D,GAAG;IACH,QAAQ,EAAE;IACX,CAAC;AACF,iBAAc,IAAI,EAAE,iBAAiB;IACnC;IACA,QAAQ,EAAE;IACX,CAAC;;GAEJ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/angular-hotkeys",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Angular adapter for TanStack Hotkeys",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -42,7 +42,7 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "@tanstack/angular-store": "^0.9.2",
45
- "@tanstack/hotkeys": "0.5.0"
45
+ "@tanstack/hotkeys": "0.6.0"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@angular/common": ">=19.0.0",
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ export * from './hotkeys-provider'
8
8
  export * from './injectHotkey'
9
9
  export * from './injectHotkeys'
10
10
  export * from './injectHotkeySequence'
11
+ export * from './injectHotkeySequences'
11
12
  export * from './injectHeldKeys'
12
13
  export * from './injectHeldKeyCodes'
13
14
  export * from './injectKeyHold'
@@ -1,15 +1,15 @@
1
- import { effect } from '@angular/core'
1
+ import { DestroyRef, effect, inject } from '@angular/core'
2
2
  import {
3
3
  detectPlatform,
4
- formatHotkey,
5
4
  getHotkeyManager,
6
- rawHotkeyToParsedHotkey,
5
+ normalizeRegisterableHotkey,
7
6
  } from '@tanstack/hotkeys'
8
7
  import { injectDefaultHotkeysOptions } from './hotkeys-provider'
9
8
  import type {
10
9
  Hotkey,
11
10
  HotkeyCallback,
12
11
  HotkeyOptions,
12
+ HotkeyRegistrationHandle,
13
13
  RegisterableHotkey,
14
14
  } from '@tanstack/hotkeys'
15
15
 
@@ -34,6 +34,8 @@ export interface InjectHotkeyOptions extends Omit<HotkeyOptions, 'target'> {
34
34
  * Call in an injection context (e.g. constructor or field initializer).
35
35
  * Uses effect() to track reactive dependencies and update registration
36
36
  * when options or the callback change.
37
+ * `enabled: false` keeps the registration (visible in devtools) and only suppresses firing; the same
38
+ * handle is updated instead of unregistering and re-registering when identity is unchanged.
37
39
  *
38
40
  * @param hotkey - The hotkey string (e.g. 'Mod+S', 'Escape') or getter function
39
41
  * @param callback - The function to call when the hotkey is pressed
@@ -87,8 +89,22 @@ export function injectHotkey(
87
89
  ): void {
88
90
  const defaultOptions = injectDefaultHotkeysOptions()
89
91
  const manager = getHotkeyManager()
92
+ const destroyRef = inject(DestroyRef)
90
93
 
91
- effect((onCleanup) => {
94
+ let registration: HotkeyRegistrationHandle | null = null
95
+ let lastHotkeyString: Hotkey | null = null
96
+ let lastTarget: HTMLElement | Document | Window | null = null
97
+
98
+ destroyRef.onDestroy(() => {
99
+ if (registration?.isActive) {
100
+ registration.unregister()
101
+ registration = null
102
+ }
103
+ lastHotkeyString = null
104
+ lastTarget = null
105
+ })
106
+
107
+ effect(() => {
92
108
  // Resolve reactive values
93
109
  const resolvedHotkey = typeof hotkey === 'function' ? hotkey() : hotkey
94
110
  const resolvedOptions = typeof options === 'function' ? options() : options
@@ -100,12 +116,7 @@ export function injectHotkey(
100
116
 
101
117
  // Normalize to hotkey string
102
118
  const platform = mergedOptions.platform ?? detectPlatform()
103
- const hotkeyString: Hotkey =
104
- typeof resolvedHotkey === 'string'
105
- ? resolvedHotkey
106
- : (formatHotkey(
107
- rawHotkeyToParsedHotkey(resolvedHotkey, platform),
108
- ) as Hotkey)
119
+ const hotkeyString = normalizeRegisterableHotkey(resolvedHotkey, platform)
109
120
 
110
121
  // Resolve target: when explicitly provided (even as null), use it and skip if null.
111
122
  // When not provided, default to document. Matches React's ref handling.
@@ -117,29 +128,44 @@ export function injectHotkey(
117
128
  : null
118
129
 
119
130
  if (!resolvedTarget) {
131
+ if (registration?.isActive) {
132
+ registration.unregister()
133
+ registration = null
134
+ }
135
+ lastHotkeyString = null
136
+ lastTarget = null
120
137
  return
121
138
  }
122
139
 
123
140
  // Extract options without target (target is handled separately)
124
141
  const { target: _target, ...optionsWithoutTarget } = mergedOptions
125
142
 
126
- // Register the hotkey
127
- const registration = manager.register(hotkeyString, callback, {
143
+ if (
144
+ registration?.isActive &&
145
+ lastHotkeyString === hotkeyString &&
146
+ lastTarget === resolvedTarget
147
+ ) {
148
+ registration.callback = callback
149
+ registration.setOptions(optionsWithoutTarget)
150
+ return
151
+ }
152
+
153
+ if (registration?.isActive) {
154
+ registration.unregister()
155
+ registration = null
156
+ }
157
+
158
+ registration = manager.register(hotkeyString, callback, {
128
159
  ...optionsWithoutTarget,
129
160
  target: resolvedTarget,
130
161
  })
131
162
 
132
- // Update callback and options on every effect run
133
163
  if (registration.isActive) {
134
164
  registration.callback = callback
135
165
  registration.setOptions(optionsWithoutTarget)
136
166
  }
137
167
 
138
- // Cleanup on disposal
139
- onCleanup(() => {
140
- if (registration.isActive) {
141
- registration.unregister()
142
- }
143
- })
168
+ lastHotkeyString = hotkeyString
169
+ lastTarget = resolvedTarget
144
170
  })
145
171
  }