@tanstack/angular-hotkeys 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/angular-hotkeys",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Angular adapter for TanStack Hotkeys",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
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,4 +1,4 @@
1
- import { effect } from '@angular/core'
1
+ import { DestroyRef, effect, inject } from '@angular/core'
2
2
  import {
3
3
  detectPlatform,
4
4
  formatHotkey,
@@ -10,6 +10,7 @@ import type {
10
10
  Hotkey,
11
11
  HotkeyCallback,
12
12
  HotkeyOptions,
13
+ HotkeyRegistrationHandle,
13
14
  RegisterableHotkey,
14
15
  } from '@tanstack/hotkeys'
15
16
 
@@ -34,6 +35,8 @@ export interface InjectHotkeyOptions extends Omit<HotkeyOptions, 'target'> {
34
35
  * Call in an injection context (e.g. constructor or field initializer).
35
36
  * Uses effect() to track reactive dependencies and update registration
36
37
  * when options or the callback change.
38
+ * `enabled: false` keeps the registration (visible in devtools) and only suppresses firing; the same
39
+ * handle is updated instead of unregistering and re-registering when identity is unchanged.
37
40
  *
38
41
  * @param hotkey - The hotkey string (e.g. 'Mod+S', 'Escape') or getter function
39
42
  * @param callback - The function to call when the hotkey is pressed
@@ -87,8 +90,22 @@ export function injectHotkey(
87
90
  ): void {
88
91
  const defaultOptions = injectDefaultHotkeysOptions()
89
92
  const manager = getHotkeyManager()
93
+ const destroyRef = inject(DestroyRef)
90
94
 
91
- effect((onCleanup) => {
95
+ let registration: HotkeyRegistrationHandle | null = null
96
+ let lastHotkeyString: Hotkey | null = null
97
+ let lastTarget: HTMLElement | Document | Window | null = null
98
+
99
+ destroyRef.onDestroy(() => {
100
+ if (registration?.isActive) {
101
+ registration.unregister()
102
+ registration = null
103
+ }
104
+ lastHotkeyString = null
105
+ lastTarget = null
106
+ })
107
+
108
+ effect(() => {
92
109
  // Resolve reactive values
93
110
  const resolvedHotkey = typeof hotkey === 'function' ? hotkey() : hotkey
94
111
  const resolvedOptions = typeof options === 'function' ? options() : options
@@ -117,29 +134,44 @@ export function injectHotkey(
117
134
  : null
118
135
 
119
136
  if (!resolvedTarget) {
137
+ if (registration?.isActive) {
138
+ registration.unregister()
139
+ registration = null
140
+ }
141
+ lastHotkeyString = null
142
+ lastTarget = null
120
143
  return
121
144
  }
122
145
 
123
146
  // Extract options without target (target is handled separately)
124
147
  const { target: _target, ...optionsWithoutTarget } = mergedOptions
125
148
 
126
- // Register the hotkey
127
- const registration = manager.register(hotkeyString, callback, {
149
+ if (
150
+ registration?.isActive &&
151
+ lastHotkeyString === hotkeyString &&
152
+ lastTarget === resolvedTarget
153
+ ) {
154
+ registration.callback = callback
155
+ registration.setOptions(optionsWithoutTarget)
156
+ return
157
+ }
158
+
159
+ if (registration?.isActive) {
160
+ registration.unregister()
161
+ registration = null
162
+ }
163
+
164
+ registration = manager.register(hotkeyString, callback, {
128
165
  ...optionsWithoutTarget,
129
166
  target: resolvedTarget,
130
167
  })
131
168
 
132
- // Update callback and options on every effect run
133
169
  if (registration.isActive) {
134
170
  registration.callback = callback
135
171
  registration.setOptions(optionsWithoutTarget)
136
172
  }
137
173
 
138
- // Cleanup on disposal
139
- onCleanup(() => {
140
- if (registration.isActive) {
141
- registration.unregister()
142
- }
143
- })
174
+ lastHotkeyString = hotkeyString
175
+ lastTarget = resolvedTarget
144
176
  })
145
177
  }
@@ -1,18 +1,27 @@
1
- import { effect } from '@angular/core'
2
- import { getSequenceManager } from '@tanstack/hotkeys'
1
+ import { DestroyRef, effect, inject } from '@angular/core'
2
+ import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'
3
3
  import { injectDefaultHotkeysOptions } from './hotkeys-provider'
4
4
  import type {
5
5
  HotkeyCallback,
6
6
  HotkeySequence,
7
7
  SequenceOptions,
8
+ SequenceRegistrationHandle,
8
9
  } from '@tanstack/hotkeys'
9
10
 
11
+ type SequenceTarget = Document | HTMLElement | Window
12
+
10
13
  export interface InjectHotkeySequenceOptions extends Omit<
11
14
  SequenceOptions,
12
- 'enabled'
15
+ 'enabled' | 'target'
13
16
  > {
14
17
  /** Whether the sequence is enabled. Defaults to true. */
15
18
  enabled?: boolean
19
+ /**
20
+ * The DOM element to attach the event listener to.
21
+ * Can be a direct DOM element, an accessor target, or null.
22
+ * Defaults to document when omitted.
23
+ */
24
+ target?: HTMLElement | Document | Window | null
16
25
  }
17
26
 
18
27
  /**
@@ -28,7 +37,8 @@ export interface InjectHotkeySequenceOptions extends Omit<
28
37
  *
29
38
  * @param sequence - Array of hotkey strings that form the sequence (or getter function)
30
39
  * @param callback - Function to call when the sequence is completed
31
- * @param options - Options for the sequence behavior (or getter function)
40
+ * @param options - Options for the sequence behavior (or getter function). `enabled: false` still registers
41
+ * the sequence (visible in devtools); only execution is suppressed.
32
42
  *
33
43
  * @example
34
44
  * ```ts
@@ -53,8 +63,23 @@ export function injectHotkeySequence(
53
63
  | (() => InjectHotkeySequenceOptions) = {},
54
64
  ): void {
55
65
  const defaultOptions = injectDefaultHotkeysOptions()
66
+ const manager = getSequenceManager()
67
+ const destroyRef = inject(DestroyRef)
68
+
69
+ let handle: SequenceRegistrationHandle | null = null
70
+ let lastSequenceKey: string | null = null
71
+ let lastTarget: SequenceTarget | null = null
56
72
 
57
- effect((onCleanup) => {
73
+ destroyRef.onDestroy(() => {
74
+ if (handle?.isActive) {
75
+ handle.unregister()
76
+ handle = null
77
+ }
78
+ lastSequenceKey = null
79
+ lastTarget = null
80
+ })
81
+
82
+ effect(() => {
58
83
  // Resolve reactive values
59
84
  const resolvedSequence =
60
85
  typeof sequence === 'function' ? sequence() : sequence
@@ -67,27 +92,49 @@ export function injectHotkeySequence(
67
92
 
68
93
  const { enabled = true, ...sequenceOptions } = mergedOptions
69
94
 
70
- if (!enabled || resolvedSequence.length === 0) {
95
+ const resolvedTarget =
96
+ 'target' in sequenceOptions
97
+ ? (sequenceOptions.target ?? null)
98
+ : typeof document !== 'undefined'
99
+ ? document
100
+ : null
101
+
102
+ if (resolvedSequence.length === 0 || !resolvedTarget) {
103
+ if (handle?.isActive) {
104
+ handle.unregister()
105
+ handle = null
106
+ }
107
+ lastSequenceKey = null
108
+ lastTarget = null
71
109
  return
72
110
  }
73
111
 
74
- const manager = getSequenceManager()
112
+ const sequenceKey = formatHotkeySequence(resolvedSequence)
75
113
 
76
- // Pass through options; default target to document when not provided
77
- const registerOptions: SequenceOptions = {
114
+ const registerPayload: SequenceOptions = {
78
115
  ...sequenceOptions,
79
- enabled: true,
80
- target:
81
- sequenceOptions.target ??
82
- (typeof document !== 'undefined' ? document : undefined),
116
+ enabled,
117
+ target: resolvedTarget,
83
118
  }
119
+ const { target: _t, ...optionsWithoutTarget } = registerPayload
84
120
 
85
- const handle = manager.register(resolvedSequence, callback, registerOptions)
121
+ if (
122
+ handle?.isActive &&
123
+ lastSequenceKey === sequenceKey &&
124
+ lastTarget === resolvedTarget
125
+ ) {
126
+ handle.callback = callback
127
+ handle.setOptions(optionsWithoutTarget)
128
+ return
129
+ }
86
130
 
87
- onCleanup(() => {
88
- if (handle.isActive) {
89
- handle.unregister()
90
- }
91
- })
131
+ if (handle?.isActive) {
132
+ handle.unregister()
133
+ handle = null
134
+ }
135
+
136
+ handle = manager.register(resolvedSequence, callback, registerPayload)
137
+ lastSequenceKey = sequenceKey
138
+ lastTarget = resolvedTarget
92
139
  })
93
140
  }