@tanstack/angular-hotkeys 0.3.3

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.
Files changed (56) hide show
  1. package/README.md +122 -0
  2. package/dist/hotkeys-provider.cjs +26 -0
  3. package/dist/hotkeys-provider.cjs.map +1 -0
  4. package/dist/hotkeys-provider.d.cts +21 -0
  5. package/dist/hotkeys-provider.d.ts +21 -0
  6. package/dist/hotkeys-provider.js +23 -0
  7. package/dist/hotkeys-provider.js.map +1 -0
  8. package/dist/index.cjs +26 -0
  9. package/dist/index.d.cts +9 -0
  10. package/dist/index.d.ts +9 -0
  11. package/dist/index.js +11 -0
  12. package/dist/injectHeldKeyCodes.cjs +34 -0
  13. package/dist/injectHeldKeyCodes.cjs.map +1 -0
  14. package/dist/injectHeldKeyCodes.d.cts +30 -0
  15. package/dist/injectHeldKeyCodes.d.ts +30 -0
  16. package/dist/injectHeldKeyCodes.js +34 -0
  17. package/dist/injectHeldKeyCodes.js.map +1 -0
  18. package/dist/injectHeldKeys.cjs +29 -0
  19. package/dist/injectHeldKeys.cjs.map +1 -0
  20. package/dist/injectHeldKeys.d.cts +25 -0
  21. package/dist/injectHeldKeys.d.ts +25 -0
  22. package/dist/injectHeldKeys.js +29 -0
  23. package/dist/injectHeldKeys.js.map +1 -0
  24. package/dist/injectHotkey.cjs +93 -0
  25. package/dist/injectHotkey.cjs.map +1 -0
  26. package/dist/injectHotkey.d.cts +73 -0
  27. package/dist/injectHotkey.d.ts +73 -0
  28. package/dist/injectHotkey.js +93 -0
  29. package/dist/injectHotkey.js.map +1 -0
  30. package/dist/injectHotkeyRecorder.cjs +82 -0
  31. package/dist/injectHotkeyRecorder.cjs.map +1 -0
  32. package/dist/injectHotkeyRecorder.d.cts +49 -0
  33. package/dist/injectHotkeyRecorder.d.ts +49 -0
  34. package/dist/injectHotkeyRecorder.js +82 -0
  35. package/dist/injectHotkeyRecorder.js.map +1 -0
  36. package/dist/injectHotkeySequence.cjs +55 -0
  37. package/dist/injectHotkeySequence.cjs.map +1 -0
  38. package/dist/injectHotkeySequence.d.cts +35 -0
  39. package/dist/injectHotkeySequence.d.ts +35 -0
  40. package/dist/injectHotkeySequence.js +55 -0
  41. package/dist/injectHotkeySequence.js.map +1 -0
  42. package/dist/injectKeyHold.cjs +49 -0
  43. package/dist/injectKeyHold.cjs.map +1 -0
  44. package/dist/injectKeyHold.d.cts +41 -0
  45. package/dist/injectKeyHold.d.ts +41 -0
  46. package/dist/injectKeyHold.js +49 -0
  47. package/dist/injectKeyHold.js.map +1 -0
  48. package/package.json +67 -0
  49. package/src/hotkeys-provider.ts +40 -0
  50. package/src/index.ts +13 -0
  51. package/src/injectHeldKeyCodes.ts +31 -0
  52. package/src/injectHeldKeys.ts +26 -0
  53. package/src/injectHotkey.ts +145 -0
  54. package/src/injectHotkeyRecorder.ts +128 -0
  55. package/src/injectHotkeySequence.ts +87 -0
  56. package/src/injectKeyHold.ts +52 -0
@@ -0,0 +1,35 @@
1
+ import { HotkeyCallback, HotkeySequence, SequenceOptions } from "@tanstack/hotkeys";
2
+
3
+ //#region src/injectHotkeySequence.d.ts
4
+ interface InjectHotkeySequenceOptions extends Omit<SequenceOptions, 'enabled'> {
5
+ /** Whether the sequence is enabled. Defaults to true. */
6
+ enabled?: boolean;
7
+ }
8
+ /**
9
+ * Angular inject-based API for registering a keyboard shortcut sequence (Vim-style).
10
+ *
11
+ * Allows you to register multi-key sequences like 'g g' or 'd d' that trigger
12
+ * when the full sequence is pressed within a timeout.
13
+ *
14
+ * @param sequence - Array of hotkey strings that form the sequence (or getter function)
15
+ * @param callback - Function to call when the sequence is completed
16
+ * @param options - Options for the sequence behavior (or getter function)
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * @Component({ ... })
21
+ * export class VimEditorComponent {
22
+ * lastSequence = signal<string | null>(null)
23
+ *
24
+ * constructor() {
25
+ * injectHotkeySequence(['G', 'G'], () => this.lastSequence.set('gg → Go to top'))
26
+ * injectHotkeySequence(['D', 'D'], () => this.lastSequence.set('dd → Delete line'))
27
+ * injectHotkeySequence(['C', 'I', 'W'], () => this.lastSequence.set('ciw'), { timeout: 500 })
28
+ * }
29
+ * }
30
+ * ```
31
+ */
32
+ declare function injectHotkeySequence(sequence: HotkeySequence | (() => HotkeySequence), callback: HotkeyCallback, options?: InjectHotkeySequenceOptions | (() => InjectHotkeySequenceOptions)): void;
33
+ //#endregion
34
+ export { InjectHotkeySequenceOptions, injectHotkeySequence };
35
+ //# sourceMappingURL=injectHotkeySequence.d.ts.map
@@ -0,0 +1,55 @@
1
+ import { injectDefaultHotkeysOptions } from "./hotkeys-provider.js";
2
+ import { getSequenceManager } from "@tanstack/hotkeys";
3
+ import { effect } from "@angular/core";
4
+
5
+ //#region src/injectHotkeySequence.ts
6
+ /**
7
+ * Angular inject-based API for registering a keyboard shortcut sequence (Vim-style).
8
+ *
9
+ * Allows you to register multi-key sequences like 'g g' or 'd d' that trigger
10
+ * when the full sequence is pressed within a timeout.
11
+ *
12
+ * @param sequence - Array of hotkey strings that form the sequence (or getter function)
13
+ * @param callback - Function to call when the sequence is completed
14
+ * @param options - Options for the sequence behavior (or getter function)
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * @Component({ ... })
19
+ * export class VimEditorComponent {
20
+ * lastSequence = signal<string | null>(null)
21
+ *
22
+ * constructor() {
23
+ * injectHotkeySequence(['G', 'G'], () => this.lastSequence.set('gg → Go to top'))
24
+ * injectHotkeySequence(['D', 'D'], () => this.lastSequence.set('dd → Delete line'))
25
+ * injectHotkeySequence(['C', 'I', 'W'], () => this.lastSequence.set('ciw'), { timeout: 500 })
26
+ * }
27
+ * }
28
+ * ```
29
+ */
30
+ function injectHotkeySequence(sequence, callback, options = {}) {
31
+ const defaultOptions = injectDefaultHotkeysOptions();
32
+ effect((onCleanup) => {
33
+ const resolvedSequence = typeof sequence === "function" ? sequence() : sequence;
34
+ const resolvedOptions = typeof options === "function" ? options() : options;
35
+ const { enabled = true, ...sequenceOptions } = {
36
+ ...defaultOptions.hotkeySequence,
37
+ ...resolvedOptions
38
+ };
39
+ if (!enabled || resolvedSequence.length === 0) return;
40
+ const manager = getSequenceManager();
41
+ const registerOptions = {
42
+ ...sequenceOptions,
43
+ enabled: true,
44
+ target: sequenceOptions.target ?? (typeof document !== "undefined" ? document : void 0)
45
+ };
46
+ const handle = manager.register(resolvedSequence, callback, registerOptions);
47
+ onCleanup(() => {
48
+ if (handle.isActive) handle.unregister();
49
+ });
50
+ });
51
+ }
52
+
53
+ //#endregion
54
+ export { injectHotkeySequence };
55
+ //# sourceMappingURL=injectHotkeySequence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectHotkeySequence.js","names":[],"sources":["../src/injectHotkeySequence.ts"],"sourcesContent":["import { effect } from '@angular/core'\nimport { getSequenceManager } from '@tanstack/hotkeys'\nimport { injectDefaultHotkeysOptions } from './hotkeys-provider'\nimport type {\n HotkeyCallback,\n HotkeySequence,\n SequenceOptions,\n} from '@tanstack/hotkeys'\n\nexport interface InjectHotkeySequenceOptions extends Omit<\n SequenceOptions,\n 'enabled'\n> {\n /** Whether the sequence is enabled. Defaults to true. */\n enabled?: boolean\n}\n\n/**\n * Angular inject-based API for registering a keyboard shortcut sequence (Vim-style).\n *\n * Allows you to register multi-key sequences like 'g g' or 'd d' that trigger\n * when the full sequence is pressed within a timeout.\n *\n * @param sequence - Array of hotkey strings that form the sequence (or getter function)\n * @param callback - Function to call when the sequence is completed\n * @param options - Options for the sequence behavior (or getter function)\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class VimEditorComponent {\n * lastSequence = signal<string | null>(null)\n *\n * constructor() {\n * injectHotkeySequence(['G', 'G'], () => this.lastSequence.set('gg → Go to top'))\n * injectHotkeySequence(['D', 'D'], () => this.lastSequence.set('dd → Delete line'))\n * injectHotkeySequence(['C', 'I', 'W'], () => this.lastSequence.set('ciw'), { timeout: 500 })\n * }\n * }\n * ```\n */\nexport function injectHotkeySequence(\n sequence: HotkeySequence | (() => HotkeySequence),\n callback: HotkeyCallback,\n options:\n | InjectHotkeySequenceOptions\n | (() => InjectHotkeySequenceOptions) = {},\n): void {\n const defaultOptions = injectDefaultHotkeysOptions()\n\n effect((onCleanup) => {\n // Resolve reactive values\n const resolvedSequence =\n typeof sequence === 'function' ? sequence() : sequence\n const resolvedOptions = typeof options === 'function' ? options() : options\n\n const mergedOptions = {\n ...defaultOptions.hotkeySequence,\n ...resolvedOptions,\n } as InjectHotkeySequenceOptions\n\n const { enabled = true, ...sequenceOptions } = mergedOptions\n\n if (!enabled || resolvedSequence.length === 0) {\n return\n }\n\n const manager = getSequenceManager()\n\n // Pass through options; default target to document when not provided\n const registerOptions: SequenceOptions = {\n ...sequenceOptions,\n enabled: true,\n target:\n sequenceOptions.target ??\n (typeof document !== 'undefined' ? document : undefined),\n }\n\n const handle = manager.register(resolvedSequence, callback, registerOptions)\n\n onCleanup(() => {\n if (handle.isActive) {\n handle.unregister()\n }\n })\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,qBACd,UACA,UACA,UAE0C,EAAE,EACtC;CACN,MAAM,iBAAiB,6BAA6B;AAEpD,SAAQ,cAAc;EAEpB,MAAM,mBACJ,OAAO,aAAa,aAAa,UAAU,GAAG;EAChD,MAAM,kBAAkB,OAAO,YAAY,aAAa,SAAS,GAAG;EAOpE,MAAM,EAAE,UAAU,MAAM,GAAG,oBALL;GACpB,GAAG,eAAe;GAClB,GAAG;GACJ;AAID,MAAI,CAAC,WAAW,iBAAiB,WAAW,EAC1C;EAGF,MAAM,UAAU,oBAAoB;EAGpC,MAAM,kBAAmC;GACvC,GAAG;GACH,SAAS;GACT,QACE,gBAAgB,WACf,OAAO,aAAa,cAAc,WAAW;GACjD;EAED,MAAM,SAAS,QAAQ,SAAS,kBAAkB,UAAU,gBAAgB;AAE5E,kBAAgB;AACd,OAAI,OAAO,SACT,QAAO,YAAY;IAErB;GACF"}
@@ -0,0 +1,49 @@
1
+ let _tanstack_hotkeys = require("@tanstack/hotkeys");
2
+ let _angular_core = require("@angular/core");
3
+ let _tanstack_angular_store = require("@tanstack/angular-store");
4
+
5
+ //#region src/injectKeyHold.ts
6
+ /**
7
+ * Angular inject-based API that returns a signal that is true when the given key is held.
8
+ *
9
+ * Subscribes to the global KeyStateTracker and uses a computed to determine
10
+ * if the specified key is held.
11
+ *
12
+ * @param key - The key to check (e.g. 'Shift', 'Control', 'Space') - can be a getter function
13
+ * @returns Signal that returns true if the key is currently held down
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * @Component({
18
+ * template: `{{ isShiftHeld() ? 'Shift is pressed!' : 'Press Shift' }}`,
19
+ * })
20
+ * export class ShiftIndicatorComponent {
21
+ * isShiftHeld = injectKeyHold('Shift')
22
+ * }
23
+ * ```
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * @Component({
28
+ * template: `
29
+ * <span [style.opacity]="ctrl() ? 1 : 0.3">Ctrl</span>
30
+ * <span [style.opacity]="shift() ? 1 : 0.3">Shift</span>
31
+ * `,
32
+ * })
33
+ * export class ModifierIndicatorsComponent {
34
+ * ctrl = injectKeyHold('Control')
35
+ * shift = injectKeyHold('Shift')
36
+ * }
37
+ * ```
38
+ */
39
+ function injectKeyHold(key) {
40
+ const heldKeysSelector = (0, _tanstack_angular_store.injectStore)((0, _tanstack_hotkeys.getKeyStateTracker)().store, (state) => state.heldKeys);
41
+ return (0, _angular_core.computed)(() => {
42
+ const normalizedKey = (typeof key === "function" ? key() : key).toLowerCase();
43
+ return heldKeysSelector().some((heldKey) => heldKey.toLowerCase() === normalizedKey);
44
+ });
45
+ }
46
+
47
+ //#endregion
48
+ exports.injectKeyHold = injectKeyHold;
49
+ //# sourceMappingURL=injectKeyHold.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectKeyHold.cjs","names":[],"sources":["../src/injectKeyHold.ts"],"sourcesContent":["import { computed } from '@angular/core'\nimport { injectStore } from '@tanstack/angular-store'\nimport { getKeyStateTracker } from '@tanstack/hotkeys'\nimport type { HeldKey } from '@tanstack/hotkeys'\nimport type { Signal } from '@angular/core'\n\n/**\n * Angular inject-based API that returns a signal that is true when the given key is held.\n *\n * Subscribes to the global KeyStateTracker and uses a computed to determine\n * if the specified key is held.\n *\n * @param key - The key to check (e.g. 'Shift', 'Control', 'Space') - can be a getter function\n * @returns Signal that returns true if the key is currently held down\n *\n * @example\n * ```ts\n * @Component({\n * template: `{{ isShiftHeld() ? 'Shift is pressed!' : 'Press Shift' }}`,\n * })\n * export class ShiftIndicatorComponent {\n * isShiftHeld = injectKeyHold('Shift')\n * }\n * ```\n *\n * @example\n * ```ts\n * @Component({\n * template: `\n * <span [style.opacity]=\"ctrl() ? 1 : 0.3\">Ctrl</span>\n * <span [style.opacity]=\"shift() ? 1 : 0.3\">Shift</span>\n * `,\n * })\n * export class ModifierIndicatorsComponent {\n * ctrl = injectKeyHold('Control')\n * shift = injectKeyHold('Shift')\n * }\n * ```\n */\nexport function injectKeyHold(key: HeldKey | (() => HeldKey)): Signal<boolean> {\n const tracker = getKeyStateTracker()\n const heldKeysSelector = injectStore(tracker.store, (state) => state.heldKeys)\n\n return computed(() => {\n const resolvedKey = typeof key === 'function' ? key() : key\n const normalizedKey = resolvedKey.toLowerCase()\n\n return heldKeysSelector().some(\n (heldKey) => heldKey.toLowerCase() === normalizedKey,\n )\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,cAAc,KAAiD;CAE7E,MAAM,uGAD8B,CACS,QAAQ,UAAU,MAAM,SAAS;AAE9E,0CAAsB;EAEpB,MAAM,iBADc,OAAO,QAAQ,aAAa,KAAK,GAAG,KACtB,aAAa;AAE/C,SAAO,kBAAkB,CAAC,MACvB,YAAY,QAAQ,aAAa,KAAK,cACxC;GACD"}
@@ -0,0 +1,41 @@
1
+ import { HeldKey } from "@tanstack/hotkeys";
2
+ import { Signal } from "@angular/core";
3
+
4
+ //#region src/injectKeyHold.d.ts
5
+ /**
6
+ * Angular inject-based API that returns a signal that is true when the given key is held.
7
+ *
8
+ * Subscribes to the global KeyStateTracker and uses a computed to determine
9
+ * if the specified key is held.
10
+ *
11
+ * @param key - The key to check (e.g. 'Shift', 'Control', 'Space') - can be a getter function
12
+ * @returns Signal that returns true if the key is currently held down
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * @Component({
17
+ * template: `{{ isShiftHeld() ? 'Shift is pressed!' : 'Press Shift' }}`,
18
+ * })
19
+ * export class ShiftIndicatorComponent {
20
+ * isShiftHeld = injectKeyHold('Shift')
21
+ * }
22
+ * ```
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * @Component({
27
+ * template: `
28
+ * <span [style.opacity]="ctrl() ? 1 : 0.3">Ctrl</span>
29
+ * <span [style.opacity]="shift() ? 1 : 0.3">Shift</span>
30
+ * `,
31
+ * })
32
+ * export class ModifierIndicatorsComponent {
33
+ * ctrl = injectKeyHold('Control')
34
+ * shift = injectKeyHold('Shift')
35
+ * }
36
+ * ```
37
+ */
38
+ declare function injectKeyHold(key: HeldKey | (() => HeldKey)): Signal<boolean>;
39
+ //#endregion
40
+ export { injectKeyHold };
41
+ //# sourceMappingURL=injectKeyHold.d.cts.map
@@ -0,0 +1,41 @@
1
+ import { HeldKey } from "@tanstack/hotkeys";
2
+ import { Signal } from "@angular/core";
3
+
4
+ //#region src/injectKeyHold.d.ts
5
+ /**
6
+ * Angular inject-based API that returns a signal that is true when the given key is held.
7
+ *
8
+ * Subscribes to the global KeyStateTracker and uses a computed to determine
9
+ * if the specified key is held.
10
+ *
11
+ * @param key - The key to check (e.g. 'Shift', 'Control', 'Space') - can be a getter function
12
+ * @returns Signal that returns true if the key is currently held down
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * @Component({
17
+ * template: `{{ isShiftHeld() ? 'Shift is pressed!' : 'Press Shift' }}`,
18
+ * })
19
+ * export class ShiftIndicatorComponent {
20
+ * isShiftHeld = injectKeyHold('Shift')
21
+ * }
22
+ * ```
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * @Component({
27
+ * template: `
28
+ * <span [style.opacity]="ctrl() ? 1 : 0.3">Ctrl</span>
29
+ * <span [style.opacity]="shift() ? 1 : 0.3">Shift</span>
30
+ * `,
31
+ * })
32
+ * export class ModifierIndicatorsComponent {
33
+ * ctrl = injectKeyHold('Control')
34
+ * shift = injectKeyHold('Shift')
35
+ * }
36
+ * ```
37
+ */
38
+ declare function injectKeyHold(key: HeldKey | (() => HeldKey)): Signal<boolean>;
39
+ //#endregion
40
+ export { injectKeyHold };
41
+ //# sourceMappingURL=injectKeyHold.d.ts.map
@@ -0,0 +1,49 @@
1
+ import { getKeyStateTracker } from "@tanstack/hotkeys";
2
+ import { computed } from "@angular/core";
3
+ import { injectStore } from "@tanstack/angular-store";
4
+
5
+ //#region src/injectKeyHold.ts
6
+ /**
7
+ * Angular inject-based API that returns a signal that is true when the given key is held.
8
+ *
9
+ * Subscribes to the global KeyStateTracker and uses a computed to determine
10
+ * if the specified key is held.
11
+ *
12
+ * @param key - The key to check (e.g. 'Shift', 'Control', 'Space') - can be a getter function
13
+ * @returns Signal that returns true if the key is currently held down
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * @Component({
18
+ * template: `{{ isShiftHeld() ? 'Shift is pressed!' : 'Press Shift' }}`,
19
+ * })
20
+ * export class ShiftIndicatorComponent {
21
+ * isShiftHeld = injectKeyHold('Shift')
22
+ * }
23
+ * ```
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * @Component({
28
+ * template: `
29
+ * <span [style.opacity]="ctrl() ? 1 : 0.3">Ctrl</span>
30
+ * <span [style.opacity]="shift() ? 1 : 0.3">Shift</span>
31
+ * `,
32
+ * })
33
+ * export class ModifierIndicatorsComponent {
34
+ * ctrl = injectKeyHold('Control')
35
+ * shift = injectKeyHold('Shift')
36
+ * }
37
+ * ```
38
+ */
39
+ function injectKeyHold(key) {
40
+ const heldKeysSelector = injectStore(getKeyStateTracker().store, (state) => state.heldKeys);
41
+ return computed(() => {
42
+ const normalizedKey = (typeof key === "function" ? key() : key).toLowerCase();
43
+ return heldKeysSelector().some((heldKey) => heldKey.toLowerCase() === normalizedKey);
44
+ });
45
+ }
46
+
47
+ //#endregion
48
+ export { injectKeyHold };
49
+ //# sourceMappingURL=injectKeyHold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectKeyHold.js","names":[],"sources":["../src/injectKeyHold.ts"],"sourcesContent":["import { computed } from '@angular/core'\nimport { injectStore } from '@tanstack/angular-store'\nimport { getKeyStateTracker } from '@tanstack/hotkeys'\nimport type { HeldKey } from '@tanstack/hotkeys'\nimport type { Signal } from '@angular/core'\n\n/**\n * Angular inject-based API that returns a signal that is true when the given key is held.\n *\n * Subscribes to the global KeyStateTracker and uses a computed to determine\n * if the specified key is held.\n *\n * @param key - The key to check (e.g. 'Shift', 'Control', 'Space') - can be a getter function\n * @returns Signal that returns true if the key is currently held down\n *\n * @example\n * ```ts\n * @Component({\n * template: `{{ isShiftHeld() ? 'Shift is pressed!' : 'Press Shift' }}`,\n * })\n * export class ShiftIndicatorComponent {\n * isShiftHeld = injectKeyHold('Shift')\n * }\n * ```\n *\n * @example\n * ```ts\n * @Component({\n * template: `\n * <span [style.opacity]=\"ctrl() ? 1 : 0.3\">Ctrl</span>\n * <span [style.opacity]=\"shift() ? 1 : 0.3\">Shift</span>\n * `,\n * })\n * export class ModifierIndicatorsComponent {\n * ctrl = injectKeyHold('Control')\n * shift = injectKeyHold('Shift')\n * }\n * ```\n */\nexport function injectKeyHold(key: HeldKey | (() => HeldKey)): Signal<boolean> {\n const tracker = getKeyStateTracker()\n const heldKeysSelector = injectStore(tracker.store, (state) => state.heldKeys)\n\n return computed(() => {\n const resolvedKey = typeof key === 'function' ? key() : key\n const normalizedKey = resolvedKey.toLowerCase()\n\n return heldKeysSelector().some(\n (heldKey) => heldKey.toLowerCase() === normalizedKey,\n )\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,cAAc,KAAiD;CAE7E,MAAM,mBAAmB,YADT,oBAAoB,CACS,QAAQ,UAAU,MAAM,SAAS;AAE9E,QAAO,eAAe;EAEpB,MAAM,iBADc,OAAO,QAAQ,aAAa,KAAK,GAAG,KACtB,aAAa;AAE/C,SAAO,kBAAkB,CAAC,MACvB,YAAY,QAAQ,aAAa,KAAK,cACxC;GACD"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@tanstack/angular-hotkeys",
3
+ "version": "0.3.3",
4
+ "description": "Angular adapter for TanStack Hotkeys",
5
+ "author": "Tanner Linsley",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/TanStack/hotkeys.git",
10
+ "directory": "packages/angular-hotkeys"
11
+ },
12
+ "homepage": "https://tanstack.com/hotkeys",
13
+ "funding": {
14
+ "type": "github",
15
+ "url": "https://github.com/sponsors/tannerlinsley"
16
+ },
17
+ "keywords": [
18
+ "angular",
19
+ "tanstack",
20
+ "hotkeys",
21
+ "keyboard",
22
+ "shortcuts"
23
+ ],
24
+ "scripts": {
25
+ "clean": "premove ./build ./dist",
26
+ "lint": "eslint ./src",
27
+ "lint:fix": "eslint ./src --fix",
28
+ "test:eslint": "eslint ./src",
29
+ "test:lib": "vitest --passWithNoTests",
30
+ "test:lib:dev": "pnpm test:lib --watch",
31
+ "test:types": "tsc",
32
+ "build": "tsdown"
33
+ },
34
+ "type": "module",
35
+ "main": "./dist/index.cjs",
36
+ "module": "./dist/index.js",
37
+ "types": "./dist/index.d.cts",
38
+ "exports": {
39
+ ".": {
40
+ "import": "./dist/index.js",
41
+ "require": "./dist/index.cjs"
42
+ },
43
+ "./package.json": "./package.json"
44
+ },
45
+ "sideEffects": false,
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
49
+ "files": [
50
+ "dist",
51
+ "src"
52
+ ],
53
+ "dependencies": {
54
+ "@tanstack/angular-store": "^0.9.2",
55
+ "@tanstack/hotkeys": "workspace:*"
56
+ },
57
+ "peerDependencies": {
58
+ "@angular/common": ">=19.0.0",
59
+ "@angular/core": ">=19.0.0"
60
+ },
61
+ "devDependencies": {
62
+ "@angular/common": "^21.2.1",
63
+ "@angular/core": "^21.2.1",
64
+ "@angular/platform-browser-dynamic": "^21.2.1",
65
+ "typescript": "5.9.3"
66
+ }
67
+ }
@@ -0,0 +1,40 @@
1
+ import { InjectionToken, inject } from '@angular/core'
2
+ import type { HotkeyRecorderOptions } from '@tanstack/hotkeys'
3
+ import type { InjectHotkeyOptions } from './injectHotkey'
4
+ import type { InjectHotkeySequenceOptions } from './injectHotkeySequence'
5
+ import type { StaticProvider } from '@angular/core'
6
+
7
+ export interface HotkeysProviderOptions {
8
+ hotkey?: Partial<InjectHotkeyOptions>
9
+ hotkeyRecorder?: Partial<HotkeyRecorderOptions>
10
+ hotkeySequence?: Partial<InjectHotkeySequenceOptions>
11
+ }
12
+
13
+ export interface HotkeysContextValue {
14
+ readonly defaultOptions: HotkeysProviderOptions
15
+ }
16
+
17
+ export const HOTKEYS_INJECTION_TOKEN = new InjectionToken<HotkeysContextValue>(
18
+ 'HOTKEYS_INJECTION_TOKEN',
19
+ {
20
+ providedIn: 'root',
21
+ factory: () => ({ defaultOptions: {} }),
22
+ },
23
+ )
24
+
25
+ export function provideHotkeys(
26
+ defaultOptions: HotkeysProviderOptions,
27
+ ): StaticProvider {
28
+ return {
29
+ provide: HOTKEYS_INJECTION_TOKEN,
30
+ useValue: { defaultOptions },
31
+ }
32
+ }
33
+
34
+ export function injectHotkeysContext(): HotkeysContextValue {
35
+ return inject(HOTKEYS_INJECTION_TOKEN)
36
+ }
37
+
38
+ export function injectDefaultHotkeysOptions(): HotkeysProviderOptions {
39
+ return injectHotkeysContext().defaultOptions
40
+ }
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ // Re-export everything from the core package
2
+ export * from '@tanstack/hotkeys'
3
+
4
+ // Provider
5
+ export * from './hotkeys-provider'
6
+
7
+ // Angular-specific primitives
8
+ export * from './injectHotkey'
9
+ export * from './injectHotkeySequence'
10
+ export * from './injectHeldKeys'
11
+ export * from './injectHeldKeyCodes'
12
+ export * from './injectKeyHold'
13
+ export * from './injectHotkeyRecorder'
@@ -0,0 +1,31 @@
1
+ import { getKeyStateTracker } from '@tanstack/hotkeys'
2
+ import { injectStore } from '@tanstack/angular-store'
3
+ import type { Signal } from '@angular/core'
4
+
5
+ /**
6
+ * Angular inject-based API that returns a signal of a map from held key names to their physical `event.code` values.
7
+ *
8
+ * Useful for debugging which physical key was pressed (e.g. distinguishing
9
+ * left vs right Shift via "ShiftLeft" / "ShiftRight").
10
+ *
11
+ * @returns Signal of record mapping normalized key names to their `event.code` values
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * @Component({
16
+ * template: `
17
+ * @for (key of heldKeys(); track key) {
18
+ * <kbd>{{ key }} <small>{{ heldCodes()[key] }}</small></kbd>
19
+ * }
20
+ * `,
21
+ * })
22
+ * export class KeyDebugComponent {
23
+ * heldKeys = injectHeldKeys()
24
+ * heldCodes = injectHeldKeyCodes()
25
+ * }
26
+ * ```
27
+ */
28
+ export function injectHeldKeyCodes(): Signal<Record<string, string>> {
29
+ const tracker = getKeyStateTracker()
30
+ return injectStore(tracker.store, (state) => state.heldCodes)
31
+ }
@@ -0,0 +1,26 @@
1
+ import { getKeyStateTracker } from '@tanstack/hotkeys'
2
+ import { injectStore } from '@tanstack/angular-store'
3
+ import type { Signal } from '@angular/core'
4
+
5
+ /**
6
+ * Angular inject-based API that returns a signal of currently held keyboard keys.
7
+ *
8
+ * Subscribes to the global KeyStateTracker and updates whenever keys are
9
+ * pressed or released.
10
+ *
11
+ * @returns Signal of array of currently held key names
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * @Component({
16
+ * template: `<div>Currently pressed: {{ heldKeys().join(' + ') || 'None' }}</div>`,
17
+ * })
18
+ * export class KeyDisplayComponent {
19
+ * heldKeys = injectHeldKeys()
20
+ * }
21
+ * ```
22
+ */
23
+ export function injectHeldKeys(): Signal<Array<string>> {
24
+ const tracker = getKeyStateTracker()
25
+ return injectStore(tracker.store, (state) => state.heldKeys)
26
+ }
@@ -0,0 +1,145 @@
1
+ import { effect } 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 {
10
+ Hotkey,
11
+ HotkeyCallback,
12
+ HotkeyOptions,
13
+ RegisterableHotkey,
14
+ } from '@tanstack/hotkeys'
15
+
16
+ export interface InjectHotkeyOptions extends Omit<HotkeyOptions, 'target'> {
17
+ /**
18
+ * The DOM element to attach the event listener to.
19
+ * Can be a direct DOM element, an accessor (for reactive targets that become
20
+ * available after mount), or null. Defaults to document.
21
+ * When using scoped targets, pass an accessor: () => ({ target: elementSignal() })
22
+ * so the hotkey waits for the element to be attached before registering.
23
+ */
24
+ target?: HTMLElement | Document | Window | null
25
+ }
26
+
27
+ /**
28
+ * Angular inject-based API for registering a keyboard hotkey.
29
+ *
30
+ * Uses the singleton HotkeyManager for efficient event handling.
31
+ * The callback receives both the keyboard event and a context object
32
+ * containing the hotkey string and parsed hotkey.
33
+ *
34
+ * Call in an injection context (e.g. constructor or field initializer).
35
+ * Uses effect() to track reactive dependencies and update registration
36
+ * when options or the callback change.
37
+ *
38
+ * @param hotkey - The hotkey string (e.g. 'Mod+S', 'Escape') or getter function
39
+ * @param callback - The function to call when the hotkey is pressed
40
+ * @param options - Options for the hotkey behavior, or getter for reactive options
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * @Component({ ... })
45
+ * export class SaveButtonComponent {
46
+ * private readonly saveCount = signal(0)
47
+ *
48
+ * constructor() {
49
+ * injectHotkey('Mod+S', (event, { hotkey }) => {
50
+ * event.preventDefault()
51
+ * this.saveCount.update(c => c + 1)
52
+ * })
53
+ * }
54
+ * }
55
+ * ```
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * @Component({ ... })
60
+ * export class ModalComponent {
61
+ * isOpen = signal(true)
62
+ *
63
+ * constructor() {
64
+ * injectHotkey('Escape', () => this.close(), () => ({ enabled: this.isOpen() }))
65
+ * }
66
+ * }
67
+ * ```
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * @Component({ ... })
72
+ * export class EditorComponent {
73
+ * private readonly editorRef = viewChild<ElementRef<HTMLDivElement>>('editorRef')
74
+ *
75
+ * constructor() {
76
+ * injectHotkey('Mod+B', () => this.toggleBold(), () => ({
77
+ * target: this.editorRef()?.nativeElement ?? null,
78
+ * }))
79
+ * }
80
+ * }
81
+ * ```
82
+ */
83
+ export function injectHotkey(
84
+ hotkey: RegisterableHotkey | (() => RegisterableHotkey),
85
+ callback: HotkeyCallback,
86
+ options: InjectHotkeyOptions | (() => InjectHotkeyOptions) = {},
87
+ ): void {
88
+ const defaultOptions = injectDefaultHotkeysOptions()
89
+ const manager = getHotkeyManager()
90
+
91
+ effect((onCleanup) => {
92
+ // Resolve reactive values
93
+ const resolvedHotkey = typeof hotkey === 'function' ? hotkey() : hotkey
94
+ const resolvedOptions = typeof options === 'function' ? options() : options
95
+
96
+ const mergedOptions = {
97
+ ...defaultOptions.hotkey,
98
+ ...resolvedOptions,
99
+ } as InjectHotkeyOptions
100
+
101
+ // Normalize to hotkey string
102
+ const platform = mergedOptions.platform ?? detectPlatform()
103
+ const hotkeyString: Hotkey =
104
+ typeof resolvedHotkey === 'string'
105
+ ? resolvedHotkey
106
+ : (formatHotkey(
107
+ rawHotkeyToParsedHotkey(resolvedHotkey, platform),
108
+ ) as Hotkey)
109
+
110
+ // Resolve target: when explicitly provided (even as null), use it and skip if null.
111
+ // When not provided, default to document. Matches React's ref handling.
112
+ const resolvedTarget =
113
+ 'target' in mergedOptions
114
+ ? (mergedOptions.target ?? null)
115
+ : typeof document !== 'undefined'
116
+ ? document
117
+ : null
118
+
119
+ if (!resolvedTarget) {
120
+ return
121
+ }
122
+
123
+ // Extract options without target (target is handled separately)
124
+ const { target: _target, ...optionsWithoutTarget } = mergedOptions
125
+
126
+ // Register the hotkey
127
+ const registration = manager.register(hotkeyString, callback, {
128
+ ...optionsWithoutTarget,
129
+ target: resolvedTarget,
130
+ })
131
+
132
+ // Update callback and options on every effect run
133
+ if (registration.isActive) {
134
+ registration.callback = callback
135
+ registration.setOptions(optionsWithoutTarget)
136
+ }
137
+
138
+ // Cleanup on disposal
139
+ onCleanup(() => {
140
+ if (registration.isActive) {
141
+ registration.unregister()
142
+ }
143
+ })
144
+ })
145
+ }