@tanstack/angular-hotkeys 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/injectHotkeys.cjs +121 -0
- package/dist/injectHotkeys.cjs.map +1 -0
- package/dist/injectHotkeys.d.cts +66 -0
- package/dist/injectHotkeys.d.ts +66 -0
- package/dist/injectHotkeys.js +121 -0
- package/dist/injectHotkeys.js.map +1 -0
- package/package.json +5 -5
- package/src/index.ts +1 -0
- package/src/injectHotkeys.ts +195 -0
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
|
|
|
58
58
|
> - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference)
|
|
59
59
|
> - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference)
|
|
60
60
|
> - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference)
|
|
61
|
-
> - Svelte Hotkeys
|
|
61
|
+
> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference)
|
|
62
62
|
|
|
63
63
|
## Get Involved
|
|
64
64
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_hotkeys_provider = require('./hotkeys-provider.cjs');
|
|
3
3
|
const require_injectHotkey = require('./injectHotkey.cjs');
|
|
4
|
+
const require_injectHotkeys = require('./injectHotkeys.cjs');
|
|
4
5
|
const require_injectHotkeySequence = require('./injectHotkeySequence.cjs');
|
|
5
6
|
const require_injectHeldKeys = require('./injectHeldKeys.cjs');
|
|
6
7
|
const require_injectHeldKeyCodes = require('./injectHeldKeyCodes.cjs');
|
|
@@ -14,6 +15,7 @@ exports.injectHeldKeys = require_injectHeldKeys.injectHeldKeys;
|
|
|
14
15
|
exports.injectHotkey = require_injectHotkey.injectHotkey;
|
|
15
16
|
exports.injectHotkeyRecorder = require_injectHotkeyRecorder.injectHotkeyRecorder;
|
|
16
17
|
exports.injectHotkeySequence = require_injectHotkeySequence.injectHotkeySequence;
|
|
18
|
+
exports.injectHotkeys = require_injectHotkeys.injectHotkeys;
|
|
17
19
|
exports.injectHotkeysContext = require_hotkeys_provider.injectHotkeysContext;
|
|
18
20
|
exports.injectKeyHold = require_injectKeyHold.injectKeyHold;
|
|
19
21
|
exports.provideHotkeys = require_hotkeys_provider.provideHotkeys;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { InjectHotkeyOptions, injectHotkey } from "./injectHotkey.cjs";
|
|
2
2
|
import { InjectHotkeySequenceOptions, injectHotkeySequence } from "./injectHotkeySequence.cjs";
|
|
3
3
|
import { HOTKEYS_INJECTION_TOKEN, HotkeysContextValue, HotkeysProviderOptions, injectDefaultHotkeysOptions, injectHotkeysContext, provideHotkeys } from "./hotkeys-provider.cjs";
|
|
4
|
+
import { InjectHotkeyDefinition, injectHotkeys } from "./injectHotkeys.cjs";
|
|
4
5
|
import { injectHeldKeys } from "./injectHeldKeys.cjs";
|
|
5
6
|
import { injectHeldKeyCodes } from "./injectHeldKeyCodes.cjs";
|
|
6
7
|
import { injectKeyHold } from "./injectKeyHold.cjs";
|
|
7
8
|
import { AngularHotkeyRecorder, injectHotkeyRecorder } from "./injectHotkeyRecorder.cjs";
|
|
8
9
|
export * from "@tanstack/hotkeys";
|
|
9
|
-
export { AngularHotkeyRecorder, HOTKEYS_INJECTION_TOKEN, HotkeysContextValue, HotkeysProviderOptions, InjectHotkeyOptions, InjectHotkeySequenceOptions, injectDefaultHotkeysOptions, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyRecorder, injectHotkeySequence, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
|
10
|
+
export { AngularHotkeyRecorder, HOTKEYS_INJECTION_TOKEN, HotkeysContextValue, HotkeysProviderOptions, InjectHotkeyDefinition, InjectHotkeyOptions, InjectHotkeySequenceOptions, injectDefaultHotkeysOptions, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyRecorder, injectHotkeySequence, injectHotkeys, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { InjectHotkeyOptions, injectHotkey } from "./injectHotkey.js";
|
|
2
2
|
import { InjectHotkeySequenceOptions, injectHotkeySequence } from "./injectHotkeySequence.js";
|
|
3
3
|
import { HOTKEYS_INJECTION_TOKEN, HotkeysContextValue, HotkeysProviderOptions, injectDefaultHotkeysOptions, injectHotkeysContext, provideHotkeys } from "./hotkeys-provider.js";
|
|
4
|
+
import { InjectHotkeyDefinition, injectHotkeys } from "./injectHotkeys.js";
|
|
4
5
|
import { injectHeldKeys } from "./injectHeldKeys.js";
|
|
5
6
|
import { injectHeldKeyCodes } from "./injectHeldKeyCodes.js";
|
|
6
7
|
import { injectKeyHold } from "./injectKeyHold.js";
|
|
7
8
|
import { AngularHotkeyRecorder, injectHotkeyRecorder } from "./injectHotkeyRecorder.js";
|
|
8
9
|
export * from "@tanstack/hotkeys";
|
|
9
|
-
export { AngularHotkeyRecorder, HOTKEYS_INJECTION_TOKEN, HotkeysContextValue, HotkeysProviderOptions, InjectHotkeyOptions, InjectHotkeySequenceOptions, injectDefaultHotkeysOptions, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyRecorder, injectHotkeySequence, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
|
10
|
+
export { AngularHotkeyRecorder, HOTKEYS_INJECTION_TOKEN, HotkeysContextValue, HotkeysProviderOptions, InjectHotkeyDefinition, InjectHotkeyOptions, InjectHotkeySequenceOptions, injectDefaultHotkeysOptions, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyRecorder, injectHotkeySequence, injectHotkeys, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HOTKEYS_INJECTION_TOKEN, injectDefaultHotkeysOptions, injectHotkeysContext, provideHotkeys } from "./hotkeys-provider.js";
|
|
2
2
|
import { injectHotkey } from "./injectHotkey.js";
|
|
3
|
+
import { injectHotkeys } from "./injectHotkeys.js";
|
|
3
4
|
import { injectHotkeySequence } from "./injectHotkeySequence.js";
|
|
4
5
|
import { injectHeldKeys } from "./injectHeldKeys.js";
|
|
5
6
|
import { injectHeldKeyCodes } from "./injectHeldKeyCodes.js";
|
|
@@ -8,4 +9,4 @@ import { injectHotkeyRecorder } from "./injectHotkeyRecorder.js";
|
|
|
8
9
|
|
|
9
10
|
export * from "@tanstack/hotkeys"
|
|
10
11
|
|
|
11
|
-
export { HOTKEYS_INJECTION_TOKEN, injectDefaultHotkeysOptions, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyRecorder, injectHotkeySequence, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
|
12
|
+
export { HOTKEYS_INJECTION_TOKEN, injectDefaultHotkeysOptions, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyRecorder, injectHotkeySequence, injectHotkeys, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
|
@@ -0,0 +1,121 @@
|
|
|
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/injectHotkeys.ts
|
|
6
|
+
/**
|
|
7
|
+
* Angular inject-based API for registering multiple keyboard hotkeys at once.
|
|
8
|
+
*
|
|
9
|
+
* Uses the singleton HotkeyManager for efficient event handling.
|
|
10
|
+
* Accepts a dynamic array of hotkey definitions.
|
|
11
|
+
*
|
|
12
|
+
* Call in an injection context (e.g. constructor or field initializer).
|
|
13
|
+
* Uses effect() to track reactive dependencies and update registrations
|
|
14
|
+
* when options or the callback change.
|
|
15
|
+
*
|
|
16
|
+
* Options are merged in this order:
|
|
17
|
+
* provideHotkeys defaults < commonOptions < per-definition options
|
|
18
|
+
*
|
|
19
|
+
* @param hotkeys - Array of hotkey definitions, or getter returning them
|
|
20
|
+
* @param commonOptions - Shared options for all hotkeys, or getter
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* @Component({ ... })
|
|
25
|
+
* export class EditorComponent {
|
|
26
|
+
* constructor() {
|
|
27
|
+
* injectHotkeys([
|
|
28
|
+
* { hotkey: 'Mod+S', callback: () => this.save() },
|
|
29
|
+
* { hotkey: 'Mod+Z', callback: () => this.undo() },
|
|
30
|
+
* { hotkey: 'Escape', callback: () => this.close() },
|
|
31
|
+
* ])
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* @Component({ ... })
|
|
39
|
+
* export class DynamicShortcuts {
|
|
40
|
+
* shortcuts = signal([...])
|
|
41
|
+
*
|
|
42
|
+
* constructor() {
|
|
43
|
+
* injectHotkeys(
|
|
44
|
+
* () => this.shortcuts().map((s) => ({
|
|
45
|
+
* hotkey: s.key,
|
|
46
|
+
* callback: s.action,
|
|
47
|
+
* })),
|
|
48
|
+
* )
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
function injectHotkeys(hotkeys, commonOptions = {}) {
|
|
54
|
+
const defaultOptions = require_hotkeys_provider.injectDefaultHotkeysOptions();
|
|
55
|
+
const manager = (0, _tanstack_hotkeys.getHotkeyManager)();
|
|
56
|
+
const destroyRef = (0, _angular_core.inject)(_angular_core.DestroyRef);
|
|
57
|
+
const registrations = /* @__PURE__ */ new Map();
|
|
58
|
+
destroyRef.onDestroy(() => {
|
|
59
|
+
for (const { handle } of registrations.values()) if (handle.isActive) handle.unregister();
|
|
60
|
+
registrations.clear();
|
|
61
|
+
});
|
|
62
|
+
(0, _angular_core.effect)(() => {
|
|
63
|
+
const resolvedHotkeys = typeof hotkeys === "function" ? hotkeys() : hotkeys;
|
|
64
|
+
const resolvedCommonOptions = typeof commonOptions === "function" ? commonOptions() : commonOptions;
|
|
65
|
+
const nextKeys = /* @__PURE__ */ new Set();
|
|
66
|
+
const prepared = [];
|
|
67
|
+
for (let i = 0; i < resolvedHotkeys.length; i++) {
|
|
68
|
+
const def = resolvedHotkeys[i];
|
|
69
|
+
const resolvedHotkey = typeof def.hotkey === "function" ? def.hotkey() : def.hotkey;
|
|
70
|
+
const resolvedDefOptions = typeof def.options === "function" ? def.options() : def.options ?? {};
|
|
71
|
+
const mergedOptions = {
|
|
72
|
+
...defaultOptions.hotkey,
|
|
73
|
+
...resolvedCommonOptions,
|
|
74
|
+
...resolvedDefOptions
|
|
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));
|
|
78
|
+
const resolvedTarget = "target" in mergedOptions ? mergedOptions.target ?? null : typeof document !== "undefined" ? document : null;
|
|
79
|
+
if (!resolvedTarget) continue;
|
|
80
|
+
const registrationKey = `${i}:${hotkeyString}`;
|
|
81
|
+
nextKeys.add(registrationKey);
|
|
82
|
+
prepared.push({
|
|
83
|
+
registrationKey,
|
|
84
|
+
def,
|
|
85
|
+
mergedOptions,
|
|
86
|
+
hotkeyString,
|
|
87
|
+
resolvedTarget
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
for (const [key, record] of [...registrations.entries()]) if (!nextKeys.has(key)) {
|
|
91
|
+
if (record.handle.isActive) record.handle.unregister();
|
|
92
|
+
registrations.delete(key);
|
|
93
|
+
}
|
|
94
|
+
for (const p of prepared) {
|
|
95
|
+
const existing = registrations.get(p.registrationKey);
|
|
96
|
+
if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
|
|
97
|
+
existing.handle.callback = p.def.callback;
|
|
98
|
+
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions;
|
|
99
|
+
existing.handle.setOptions(optionsWithoutTarget);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (existing) {
|
|
103
|
+
if (existing.handle.isActive) existing.handle.unregister();
|
|
104
|
+
registrations.delete(p.registrationKey);
|
|
105
|
+
}
|
|
106
|
+
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions;
|
|
107
|
+
const handle = manager.register(p.hotkeyString, p.def.callback, {
|
|
108
|
+
...optionsWithoutTarget,
|
|
109
|
+
target: p.resolvedTarget
|
|
110
|
+
});
|
|
111
|
+
registrations.set(p.registrationKey, {
|
|
112
|
+
handle,
|
|
113
|
+
target: p.resolvedTarget
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
exports.injectHotkeys = injectHotkeys;
|
|
121
|
+
//# sourceMappingURL=injectHotkeys.cjs.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { InjectHotkeyOptions } from "./injectHotkey.cjs";
|
|
2
|
+
import { HotkeyCallback, RegisterableHotkey } from "@tanstack/hotkeys";
|
|
3
|
+
|
|
4
|
+
//#region src/injectHotkeys.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A single hotkey definition for use with `injectHotkeys`.
|
|
7
|
+
*/
|
|
8
|
+
interface InjectHotkeyDefinition {
|
|
9
|
+
/** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */
|
|
10
|
+
hotkey: RegisterableHotkey | (() => RegisterableHotkey);
|
|
11
|
+
/** The function to call when the hotkey is pressed */
|
|
12
|
+
callback: HotkeyCallback;
|
|
13
|
+
/** Per-hotkey options (merged on top of commonOptions) */
|
|
14
|
+
options?: InjectHotkeyOptions | (() => InjectHotkeyOptions);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Angular inject-based API for registering multiple keyboard hotkeys at once.
|
|
18
|
+
*
|
|
19
|
+
* Uses the singleton HotkeyManager for efficient event handling.
|
|
20
|
+
* Accepts a dynamic array of hotkey definitions.
|
|
21
|
+
*
|
|
22
|
+
* Call in an injection context (e.g. constructor or field initializer).
|
|
23
|
+
* Uses effect() to track reactive dependencies and update registrations
|
|
24
|
+
* when options or the callback change.
|
|
25
|
+
*
|
|
26
|
+
* Options are merged in this order:
|
|
27
|
+
* provideHotkeys defaults < commonOptions < per-definition options
|
|
28
|
+
*
|
|
29
|
+
* @param hotkeys - Array of hotkey definitions, or getter returning them
|
|
30
|
+
* @param commonOptions - Shared options for all hotkeys, or getter
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* @Component({ ... })
|
|
35
|
+
* export class EditorComponent {
|
|
36
|
+
* constructor() {
|
|
37
|
+
* injectHotkeys([
|
|
38
|
+
* { hotkey: 'Mod+S', callback: () => this.save() },
|
|
39
|
+
* { hotkey: 'Mod+Z', callback: () => this.undo() },
|
|
40
|
+
* { hotkey: 'Escape', callback: () => this.close() },
|
|
41
|
+
* ])
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* @Component({ ... })
|
|
49
|
+
* export class DynamicShortcuts {
|
|
50
|
+
* shortcuts = signal([...])
|
|
51
|
+
*
|
|
52
|
+
* constructor() {
|
|
53
|
+
* injectHotkeys(
|
|
54
|
+
* () => this.shortcuts().map((s) => ({
|
|
55
|
+
* hotkey: s.key,
|
|
56
|
+
* callback: s.action,
|
|
57
|
+
* })),
|
|
58
|
+
* )
|
|
59
|
+
* }
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function injectHotkeys(hotkeys: Array<InjectHotkeyDefinition> | (() => Array<InjectHotkeyDefinition>), commonOptions?: InjectHotkeyOptions | (() => InjectHotkeyOptions)): void;
|
|
64
|
+
//#endregion
|
|
65
|
+
export { InjectHotkeyDefinition, injectHotkeys };
|
|
66
|
+
//# sourceMappingURL=injectHotkeys.d.cts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { InjectHotkeyOptions } from "./injectHotkey.js";
|
|
2
|
+
import { HotkeyCallback, RegisterableHotkey } from "@tanstack/hotkeys";
|
|
3
|
+
|
|
4
|
+
//#region src/injectHotkeys.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A single hotkey definition for use with `injectHotkeys`.
|
|
7
|
+
*/
|
|
8
|
+
interface InjectHotkeyDefinition {
|
|
9
|
+
/** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */
|
|
10
|
+
hotkey: RegisterableHotkey | (() => RegisterableHotkey);
|
|
11
|
+
/** The function to call when the hotkey is pressed */
|
|
12
|
+
callback: HotkeyCallback;
|
|
13
|
+
/** Per-hotkey options (merged on top of commonOptions) */
|
|
14
|
+
options?: InjectHotkeyOptions | (() => InjectHotkeyOptions);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Angular inject-based API for registering multiple keyboard hotkeys at once.
|
|
18
|
+
*
|
|
19
|
+
* Uses the singleton HotkeyManager for efficient event handling.
|
|
20
|
+
* Accepts a dynamic array of hotkey definitions.
|
|
21
|
+
*
|
|
22
|
+
* Call in an injection context (e.g. constructor or field initializer).
|
|
23
|
+
* Uses effect() to track reactive dependencies and update registrations
|
|
24
|
+
* when options or the callback change.
|
|
25
|
+
*
|
|
26
|
+
* Options are merged in this order:
|
|
27
|
+
* provideHotkeys defaults < commonOptions < per-definition options
|
|
28
|
+
*
|
|
29
|
+
* @param hotkeys - Array of hotkey definitions, or getter returning them
|
|
30
|
+
* @param commonOptions - Shared options for all hotkeys, or getter
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* @Component({ ... })
|
|
35
|
+
* export class EditorComponent {
|
|
36
|
+
* constructor() {
|
|
37
|
+
* injectHotkeys([
|
|
38
|
+
* { hotkey: 'Mod+S', callback: () => this.save() },
|
|
39
|
+
* { hotkey: 'Mod+Z', callback: () => this.undo() },
|
|
40
|
+
* { hotkey: 'Escape', callback: () => this.close() },
|
|
41
|
+
* ])
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* @Component({ ... })
|
|
49
|
+
* export class DynamicShortcuts {
|
|
50
|
+
* shortcuts = signal([...])
|
|
51
|
+
*
|
|
52
|
+
* constructor() {
|
|
53
|
+
* injectHotkeys(
|
|
54
|
+
* () => this.shortcuts().map((s) => ({
|
|
55
|
+
* hotkey: s.key,
|
|
56
|
+
* callback: s.action,
|
|
57
|
+
* })),
|
|
58
|
+
* )
|
|
59
|
+
* }
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function injectHotkeys(hotkeys: Array<InjectHotkeyDefinition> | (() => Array<InjectHotkeyDefinition>), commonOptions?: InjectHotkeyOptions | (() => InjectHotkeyOptions)): void;
|
|
64
|
+
//#endregion
|
|
65
|
+
export { InjectHotkeyDefinition, injectHotkeys };
|
|
66
|
+
//# sourceMappingURL=injectHotkeys.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { injectDefaultHotkeysOptions } from "./hotkeys-provider.js";
|
|
2
|
+
import { detectPlatform, formatHotkey, getHotkeyManager, rawHotkeyToParsedHotkey } from "@tanstack/hotkeys";
|
|
3
|
+
import { DestroyRef, effect, inject } from "@angular/core";
|
|
4
|
+
|
|
5
|
+
//#region src/injectHotkeys.ts
|
|
6
|
+
/**
|
|
7
|
+
* Angular inject-based API for registering multiple keyboard hotkeys at once.
|
|
8
|
+
*
|
|
9
|
+
* Uses the singleton HotkeyManager for efficient event handling.
|
|
10
|
+
* Accepts a dynamic array of hotkey definitions.
|
|
11
|
+
*
|
|
12
|
+
* Call in an injection context (e.g. constructor or field initializer).
|
|
13
|
+
* Uses effect() to track reactive dependencies and update registrations
|
|
14
|
+
* when options or the callback change.
|
|
15
|
+
*
|
|
16
|
+
* Options are merged in this order:
|
|
17
|
+
* provideHotkeys defaults < commonOptions < per-definition options
|
|
18
|
+
*
|
|
19
|
+
* @param hotkeys - Array of hotkey definitions, or getter returning them
|
|
20
|
+
* @param commonOptions - Shared options for all hotkeys, or getter
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* @Component({ ... })
|
|
25
|
+
* export class EditorComponent {
|
|
26
|
+
* constructor() {
|
|
27
|
+
* injectHotkeys([
|
|
28
|
+
* { hotkey: 'Mod+S', callback: () => this.save() },
|
|
29
|
+
* { hotkey: 'Mod+Z', callback: () => this.undo() },
|
|
30
|
+
* { hotkey: 'Escape', callback: () => this.close() },
|
|
31
|
+
* ])
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* @Component({ ... })
|
|
39
|
+
* export class DynamicShortcuts {
|
|
40
|
+
* shortcuts = signal([...])
|
|
41
|
+
*
|
|
42
|
+
* constructor() {
|
|
43
|
+
* injectHotkeys(
|
|
44
|
+
* () => this.shortcuts().map((s) => ({
|
|
45
|
+
* hotkey: s.key,
|
|
46
|
+
* callback: s.action,
|
|
47
|
+
* })),
|
|
48
|
+
* )
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
function injectHotkeys(hotkeys, commonOptions = {}) {
|
|
54
|
+
const defaultOptions = injectDefaultHotkeysOptions();
|
|
55
|
+
const manager = getHotkeyManager();
|
|
56
|
+
const destroyRef = inject(DestroyRef);
|
|
57
|
+
const registrations = /* @__PURE__ */ new Map();
|
|
58
|
+
destroyRef.onDestroy(() => {
|
|
59
|
+
for (const { handle } of registrations.values()) if (handle.isActive) handle.unregister();
|
|
60
|
+
registrations.clear();
|
|
61
|
+
});
|
|
62
|
+
effect(() => {
|
|
63
|
+
const resolvedHotkeys = typeof hotkeys === "function" ? hotkeys() : hotkeys;
|
|
64
|
+
const resolvedCommonOptions = typeof commonOptions === "function" ? commonOptions() : commonOptions;
|
|
65
|
+
const nextKeys = /* @__PURE__ */ new Set();
|
|
66
|
+
const prepared = [];
|
|
67
|
+
for (let i = 0; i < resolvedHotkeys.length; i++) {
|
|
68
|
+
const def = resolvedHotkeys[i];
|
|
69
|
+
const resolvedHotkey = typeof def.hotkey === "function" ? def.hotkey() : def.hotkey;
|
|
70
|
+
const resolvedDefOptions = typeof def.options === "function" ? def.options() : def.options ?? {};
|
|
71
|
+
const mergedOptions = {
|
|
72
|
+
...defaultOptions.hotkey,
|
|
73
|
+
...resolvedCommonOptions,
|
|
74
|
+
...resolvedDefOptions
|
|
75
|
+
};
|
|
76
|
+
const platform = mergedOptions.platform ?? detectPlatform();
|
|
77
|
+
const hotkeyString = typeof resolvedHotkey === "string" ? resolvedHotkey : formatHotkey(rawHotkeyToParsedHotkey(resolvedHotkey, platform));
|
|
78
|
+
const resolvedTarget = "target" in mergedOptions ? mergedOptions.target ?? null : typeof document !== "undefined" ? document : null;
|
|
79
|
+
if (!resolvedTarget) continue;
|
|
80
|
+
const registrationKey = `${i}:${hotkeyString}`;
|
|
81
|
+
nextKeys.add(registrationKey);
|
|
82
|
+
prepared.push({
|
|
83
|
+
registrationKey,
|
|
84
|
+
def,
|
|
85
|
+
mergedOptions,
|
|
86
|
+
hotkeyString,
|
|
87
|
+
resolvedTarget
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
for (const [key, record] of [...registrations.entries()]) if (!nextKeys.has(key)) {
|
|
91
|
+
if (record.handle.isActive) record.handle.unregister();
|
|
92
|
+
registrations.delete(key);
|
|
93
|
+
}
|
|
94
|
+
for (const p of prepared) {
|
|
95
|
+
const existing = registrations.get(p.registrationKey);
|
|
96
|
+
if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
|
|
97
|
+
existing.handle.callback = p.def.callback;
|
|
98
|
+
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions;
|
|
99
|
+
existing.handle.setOptions(optionsWithoutTarget);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (existing) {
|
|
103
|
+
if (existing.handle.isActive) existing.handle.unregister();
|
|
104
|
+
registrations.delete(p.registrationKey);
|
|
105
|
+
}
|
|
106
|
+
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions;
|
|
107
|
+
const handle = manager.register(p.hotkeyString, p.def.callback, {
|
|
108
|
+
...optionsWithoutTarget,
|
|
109
|
+
target: p.resolvedTarget
|
|
110
|
+
});
|
|
111
|
+
registrations.set(p.registrationKey, {
|
|
112
|
+
handle,
|
|
113
|
+
target: p.resolvedTarget
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
export { injectHotkeys };
|
|
121
|
+
//# sourceMappingURL=injectHotkeys.js.map
|
|
@@ -0,0 +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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/angular-hotkeys",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Angular adapter for TanStack Hotkeys",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,16 +42,16 @@
|
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@tanstack/angular-store": "^0.9.2",
|
|
45
|
-
"@tanstack/hotkeys": "0.4.
|
|
45
|
+
"@tanstack/hotkeys": "0.4.2"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@angular/common": ">=19.0.0",
|
|
49
49
|
"@angular/core": ">=19.0.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@angular/common": "^21.2.
|
|
53
|
-
"@angular/core": "^21.2.
|
|
54
|
-
"@angular/platform-browser-dynamic": "^21.2.
|
|
52
|
+
"@angular/common": "^21.2.5",
|
|
53
|
+
"@angular/core": "^21.2.5",
|
|
54
|
+
"@angular/platform-browser-dynamic": "^21.2.5",
|
|
55
55
|
"typescript": "5.9.3"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { DestroyRef, effect, inject } from '@angular/core'
|
|
2
|
+
import {
|
|
3
|
+
detectPlatform,
|
|
4
|
+
formatHotkey,
|
|
5
|
+
getHotkeyManager,
|
|
6
|
+
rawHotkeyToParsedHotkey,
|
|
7
|
+
} from '@tanstack/hotkeys'
|
|
8
|
+
import { injectDefaultHotkeysOptions } from './hotkeys-provider'
|
|
9
|
+
import type { InjectHotkeyOptions } from './injectHotkey'
|
|
10
|
+
import type {
|
|
11
|
+
Hotkey,
|
|
12
|
+
HotkeyCallback,
|
|
13
|
+
HotkeyRegistrationHandle,
|
|
14
|
+
RegisterableHotkey,
|
|
15
|
+
} from '@tanstack/hotkeys'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A single hotkey definition for use with `injectHotkeys`.
|
|
19
|
+
*/
|
|
20
|
+
export interface InjectHotkeyDefinition {
|
|
21
|
+
/** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */
|
|
22
|
+
hotkey: RegisterableHotkey | (() => RegisterableHotkey)
|
|
23
|
+
/** The function to call when the hotkey is pressed */
|
|
24
|
+
callback: HotkeyCallback
|
|
25
|
+
/** Per-hotkey options (merged on top of commonOptions) */
|
|
26
|
+
options?: InjectHotkeyOptions | (() => InjectHotkeyOptions)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Angular inject-based API for registering multiple keyboard hotkeys at once.
|
|
31
|
+
*
|
|
32
|
+
* Uses the singleton HotkeyManager for efficient event handling.
|
|
33
|
+
* Accepts a dynamic array of hotkey definitions.
|
|
34
|
+
*
|
|
35
|
+
* Call in an injection context (e.g. constructor or field initializer).
|
|
36
|
+
* Uses effect() to track reactive dependencies and update registrations
|
|
37
|
+
* when options or the callback change.
|
|
38
|
+
*
|
|
39
|
+
* Options are merged in this order:
|
|
40
|
+
* provideHotkeys defaults < commonOptions < per-definition options
|
|
41
|
+
*
|
|
42
|
+
* @param hotkeys - Array of hotkey definitions, or getter returning them
|
|
43
|
+
* @param commonOptions - Shared options for all hotkeys, or getter
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* @Component({ ... })
|
|
48
|
+
* export class EditorComponent {
|
|
49
|
+
* constructor() {
|
|
50
|
+
* injectHotkeys([
|
|
51
|
+
* { hotkey: 'Mod+S', callback: () => this.save() },
|
|
52
|
+
* { hotkey: 'Mod+Z', callback: () => this.undo() },
|
|
53
|
+
* { hotkey: 'Escape', callback: () => this.close() },
|
|
54
|
+
* ])
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* @Component({ ... })
|
|
62
|
+
* export class DynamicShortcuts {
|
|
63
|
+
* shortcuts = signal([...])
|
|
64
|
+
*
|
|
65
|
+
* constructor() {
|
|
66
|
+
* injectHotkeys(
|
|
67
|
+
* () => this.shortcuts().map((s) => ({
|
|
68
|
+
* hotkey: s.key,
|
|
69
|
+
* callback: s.action,
|
|
70
|
+
* })),
|
|
71
|
+
* )
|
|
72
|
+
* }
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function injectHotkeys(
|
|
77
|
+
hotkeys:
|
|
78
|
+
| Array<InjectHotkeyDefinition>
|
|
79
|
+
| (() => Array<InjectHotkeyDefinition>),
|
|
80
|
+
commonOptions: InjectHotkeyOptions | (() => InjectHotkeyOptions) = {},
|
|
81
|
+
): void {
|
|
82
|
+
type RegistrationRecord = {
|
|
83
|
+
handle: HotkeyRegistrationHandle
|
|
84
|
+
target: Document | HTMLElement | Window
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const defaultOptions = injectDefaultHotkeysOptions()
|
|
88
|
+
const manager = getHotkeyManager()
|
|
89
|
+
const destroyRef = inject(DestroyRef)
|
|
90
|
+
|
|
91
|
+
const registrations = new Map<string, RegistrationRecord>()
|
|
92
|
+
|
|
93
|
+
destroyRef.onDestroy(() => {
|
|
94
|
+
for (const { handle } of registrations.values()) {
|
|
95
|
+
if (handle.isActive) {
|
|
96
|
+
handle.unregister()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
registrations.clear()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
effect(() => {
|
|
103
|
+
const resolvedHotkeys = typeof hotkeys === 'function' ? hotkeys() : hotkeys
|
|
104
|
+
const resolvedCommonOptions =
|
|
105
|
+
typeof commonOptions === 'function' ? commonOptions() : commonOptions
|
|
106
|
+
|
|
107
|
+
const nextKeys = new Set<string>()
|
|
108
|
+
const prepared: Array<{
|
|
109
|
+
registrationKey: string
|
|
110
|
+
def: InjectHotkeyDefinition
|
|
111
|
+
mergedOptions: InjectHotkeyOptions
|
|
112
|
+
hotkeyString: Hotkey
|
|
113
|
+
resolvedTarget: Document | HTMLElement | Window
|
|
114
|
+
}> = []
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < resolvedHotkeys.length; i++) {
|
|
117
|
+
const def = resolvedHotkeys[i]!
|
|
118
|
+
const resolvedHotkey =
|
|
119
|
+
typeof def.hotkey === 'function' ? def.hotkey() : def.hotkey
|
|
120
|
+
const resolvedDefOptions =
|
|
121
|
+
typeof def.options === 'function' ? def.options() : (def.options ?? {})
|
|
122
|
+
|
|
123
|
+
const mergedOptions = {
|
|
124
|
+
...defaultOptions.hotkey,
|
|
125
|
+
...resolvedCommonOptions,
|
|
126
|
+
...resolvedDefOptions,
|
|
127
|
+
} as InjectHotkeyOptions
|
|
128
|
+
|
|
129
|
+
const platform = mergedOptions.platform ?? detectPlatform()
|
|
130
|
+
const hotkeyString: Hotkey =
|
|
131
|
+
typeof resolvedHotkey === 'string'
|
|
132
|
+
? resolvedHotkey
|
|
133
|
+
: (formatHotkey(
|
|
134
|
+
rawHotkeyToParsedHotkey(resolvedHotkey, platform),
|
|
135
|
+
) as Hotkey)
|
|
136
|
+
|
|
137
|
+
const resolvedTarget =
|
|
138
|
+
'target' in mergedOptions
|
|
139
|
+
? (mergedOptions.target ?? null)
|
|
140
|
+
: typeof document !== 'undefined'
|
|
141
|
+
? document
|
|
142
|
+
: null
|
|
143
|
+
|
|
144
|
+
if (!resolvedTarget) {
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const registrationKey = `${i}:${hotkeyString}`
|
|
149
|
+
nextKeys.add(registrationKey)
|
|
150
|
+
prepared.push({
|
|
151
|
+
registrationKey,
|
|
152
|
+
def,
|
|
153
|
+
mergedOptions,
|
|
154
|
+
hotkeyString,
|
|
155
|
+
resolvedTarget,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const [key, record] of [...registrations.entries()]) {
|
|
160
|
+
if (!nextKeys.has(key)) {
|
|
161
|
+
if (record.handle.isActive) {
|
|
162
|
+
record.handle.unregister()
|
|
163
|
+
}
|
|
164
|
+
registrations.delete(key)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for (const p of prepared) {
|
|
169
|
+
const existing = registrations.get(p.registrationKey)
|
|
170
|
+
if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
|
|
171
|
+
existing.handle.callback = p.def.callback
|
|
172
|
+
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions
|
|
173
|
+
existing.handle.setOptions(optionsWithoutTarget)
|
|
174
|
+
continue
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (existing) {
|
|
178
|
+
if (existing.handle.isActive) {
|
|
179
|
+
existing.handle.unregister()
|
|
180
|
+
}
|
|
181
|
+
registrations.delete(p.registrationKey)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions
|
|
185
|
+
const handle = manager.register(p.hotkeyString, p.def.callback, {
|
|
186
|
+
...optionsWithoutTarget,
|
|
187
|
+
target: p.resolvedTarget,
|
|
188
|
+
})
|
|
189
|
+
registrations.set(p.registrationKey, {
|
|
190
|
+
handle,
|
|
191
|
+
target: p.resolvedTarget,
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
}
|