@tanstack/angular-hotkeys 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/hotkeys-provider.d.ts +7 -10
  2. package/dist/hotkeys-provider.js +1 -2
  3. package/dist/injectHeldKeyCodes.d.ts +2 -5
  4. package/dist/injectHeldKeyCodes.js +3 -3
  5. package/dist/injectHeldKeys.d.ts +2 -5
  6. package/dist/injectHeldKeys.js +3 -3
  7. package/dist/injectHotkey.d.ts +3 -6
  8. package/dist/injectHotkey.js +3 -3
  9. package/dist/injectHotkeyHint.d.ts +2 -5
  10. package/dist/injectHotkeyHint.js +1 -2
  11. package/dist/injectHotkeyRecorder.d.ts +3 -6
  12. package/dist/injectHotkeyRecorder.js +4 -4
  13. package/dist/injectHotkeyRegistrations.d.ts +3 -6
  14. package/dist/injectHotkeyRegistrations.js +1 -2
  15. package/dist/injectHotkeySequence.d.ts +3 -6
  16. package/dist/injectHotkeySequence.js +1 -2
  17. package/dist/injectHotkeySequenceRecorder.d.ts +3 -6
  18. package/dist/injectHotkeySequenceRecorder.js +4 -4
  19. package/dist/injectHotkeySequences.d.ts +3 -6
  20. package/dist/injectHotkeySequences.js +1 -2
  21. package/dist/injectHotkeys.d.ts +3 -6
  22. package/dist/injectHotkeys.js +3 -3
  23. package/dist/injectKeyHold.d.ts +2 -5
  24. package/dist/injectKeyHold.js +3 -3
  25. package/package.json +12 -17
  26. package/dist/hotkeys-provider.cjs +0 -26
  27. package/dist/hotkeys-provider.cjs.map +0 -1
  28. package/dist/hotkeys-provider.d.cts +0 -22
  29. package/dist/hotkeys-provider.js.map +0 -1
  30. package/dist/index.cjs +0 -36
  31. package/dist/index.d.cts +0 -14
  32. package/dist/injectHeldKeyCodes.cjs +0 -34
  33. package/dist/injectHeldKeyCodes.cjs.map +0 -1
  34. package/dist/injectHeldKeyCodes.d.cts +0 -30
  35. package/dist/injectHeldKeyCodes.js.map +0 -1
  36. package/dist/injectHeldKeys.cjs +0 -29
  37. package/dist/injectHeldKeys.cjs.map +0 -1
  38. package/dist/injectHeldKeys.d.cts +0 -25
  39. package/dist/injectHeldKeys.js.map +0 -1
  40. package/dist/injectHotkey.cjs +0 -122
  41. package/dist/injectHotkey.cjs.map +0 -1
  42. package/dist/injectHotkey.d.cts +0 -75
  43. package/dist/injectHotkey.js.map +0 -1
  44. package/dist/injectHotkeyHint.cjs +0 -20
  45. package/dist/injectHotkeyHint.cjs.map +0 -1
  46. package/dist/injectHotkeyHint.d.cts +0 -15
  47. package/dist/injectHotkeyHint.js.map +0 -1
  48. package/dist/injectHotkeyRecorder.cjs +0 -82
  49. package/dist/injectHotkeyRecorder.cjs.map +0 -1
  50. package/dist/injectHotkeyRecorder.d.cts +0 -49
  51. package/dist/injectHotkeyRecorder.js.map +0 -1
  52. package/dist/injectHotkeyRegistrations.cjs +0 -28
  53. package/dist/injectHotkeyRegistrations.cjs.map +0 -1
  54. package/dist/injectHotkeyRegistrations.d.cts +0 -29
  55. package/dist/injectHotkeyRegistrations.js.map +0 -1
  56. package/dist/injectHotkeySequence.cjs +0 -93
  57. package/dist/injectHotkeySequence.cjs.map +0 -1
  58. package/dist/injectHotkeySequence.d.cts +0 -48
  59. package/dist/injectHotkeySequence.js.map +0 -1
  60. package/dist/injectHotkeySequenceRecorder.cjs +0 -58
  61. package/dist/injectHotkeySequenceRecorder.cjs.map +0 -1
  62. package/dist/injectHotkeySequenceRecorder.d.cts +0 -19
  63. package/dist/injectHotkeySequenceRecorder.js.map +0 -1
  64. package/dist/injectHotkeySequences.cjs +0 -105
  65. package/dist/injectHotkeySequences.cjs.map +0 -1
  66. package/dist/injectHotkeySequences.d.cts +0 -47
  67. package/dist/injectHotkeySequences.js.map +0 -1
  68. package/dist/injectHotkeys.cjs +0 -120
  69. package/dist/injectHotkeys.cjs.map +0 -1
  70. package/dist/injectHotkeys.d.cts +0 -66
  71. package/dist/injectHotkeys.js.map +0 -1
  72. package/dist/injectKeyHold.cjs +0 -49
  73. package/dist/injectKeyHold.cjs.map +0 -1
  74. package/dist/injectKeyHold.d.cts +0 -41
  75. package/dist/injectKeyHold.js.map +0 -1
  76. package/src/hotkeys-provider.ts +0 -44
  77. package/src/index.ts +0 -18
  78. package/src/injectHeldKeyCodes.ts +0 -31
  79. package/src/injectHeldKeys.ts +0 -26
  80. package/src/injectHotkey.ts +0 -171
  81. package/src/injectHotkeyHint.ts +0 -29
  82. package/src/injectHotkeyRecorder.ts +0 -128
  83. package/src/injectHotkeyRegistrations.ts +0 -48
  84. package/src/injectHotkeySequence.ts +0 -140
  85. package/src/injectHotkeySequenceRecorder.ts +0 -103
  86. package/src/injectHotkeySequences.ts +0 -176
  87. package/src/injectHotkeys.ts +0 -189
  88. package/src/injectKeyHold.ts +0 -57
@@ -1,66 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"injectHotkeys.js","names":[],"sources":["../src/injectHotkeys.ts"],"sourcesContent":["import { DestroyRef, effect, inject } from '@angular/core'\nimport {\n detectPlatform,\n getHotkeyManager,\n normalizeRegisterableHotkey,\n} from '@tanstack/hotkeys'\nimport { injectDefaultHotkeysOptions } from './hotkeys-provider'\nimport type { InjectHotkeyOptions } from './injectHotkey'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyRegistrationHandle,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\n\n/**\n * A single hotkey definition for use with `injectHotkeys`.\n */\nexport interface InjectHotkeyDefinition {\n /** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */\n hotkey: RegisterableHotkey | (() => RegisterableHotkey)\n /** The function to call when the hotkey is pressed */\n callback: HotkeyCallback\n /** Per-hotkey options (merged on top of commonOptions) */\n options?: InjectHotkeyOptions | (() => InjectHotkeyOptions)\n}\n\n/**\n * Angular inject-based API for registering multiple keyboard hotkeys at once.\n *\n * Uses the singleton HotkeyManager for efficient event handling.\n * Accepts a dynamic array of hotkey definitions.\n *\n * Call in an injection context (e.g. constructor or field initializer).\n * Uses effect() to track reactive dependencies and update registrations\n * when options or the callback change.\n *\n * Options are merged in this order:\n * provideHotkeys defaults < commonOptions < per-definition options\n *\n * @param hotkeys - Array of hotkey definitions, or getter returning them\n * @param commonOptions - Shared options for all hotkeys, or getter\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class EditorComponent {\n * constructor() {\n * injectHotkeys([\n * { hotkey: 'Mod+S', callback: () => this.save() },\n * { hotkey: 'Mod+Z', callback: () => this.undo() },\n * { hotkey: 'Escape', callback: () => this.close() },\n * ])\n * }\n * }\n * ```\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class DynamicShortcuts {\n * shortcuts = signal([...])\n *\n * constructor() {\n * injectHotkeys(\n * () => this.shortcuts().map((s) => ({\n * hotkey: s.key,\n * callback: s.action,\n * })),\n * )\n * }\n * }\n * ```\n */\nexport function injectHotkeys(\n hotkeys:\n | Array<InjectHotkeyDefinition>\n | (() => Array<InjectHotkeyDefinition>),\n commonOptions: InjectHotkeyOptions | (() => InjectHotkeyOptions) = {},\n): void {\n type RegistrationRecord = {\n handle: HotkeyRegistrationHandle\n target: Document | HTMLElement | Window\n }\n\n const defaultOptions = injectDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n const destroyRef = inject(DestroyRef)\n\n const registrations = new Map<string, RegistrationRecord>()\n\n destroyRef.onDestroy(() => {\n for (const { handle } of registrations.values()) {\n if (handle.isActive) {\n handle.unregister()\n }\n }\n registrations.clear()\n })\n\n effect(() => {\n const resolvedHotkeys = typeof hotkeys === 'function' ? hotkeys() : hotkeys\n const resolvedCommonOptions =\n typeof commonOptions === 'function' ? commonOptions() : commonOptions\n\n const nextKeys = new Set<string>()\n const prepared: Array<{\n registrationKey: string\n def: InjectHotkeyDefinition\n mergedOptions: InjectHotkeyOptions\n hotkeyString: Hotkey\n resolvedTarget: Document | HTMLElement | Window\n }> = []\n\n for (let i = 0; i < resolvedHotkeys.length; i++) {\n const def = resolvedHotkeys[i]!\n const resolvedHotkey =\n typeof def.hotkey === 'function' ? def.hotkey() : def.hotkey\n const resolvedDefOptions =\n typeof def.options === 'function' ? def.options() : (def.options ?? {})\n\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedCommonOptions,\n ...resolvedDefOptions,\n } as InjectHotkeyOptions\n\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString = normalizeRegisterableHotkey(resolvedHotkey, platform)\n\n const resolvedTarget =\n 'target' in mergedOptions\n ? (mergedOptions.target ?? null)\n : typeof document !== 'undefined'\n ? document\n : null\n\n if (!resolvedTarget) {\n continue\n }\n\n const registrationKey = `${i}:${hotkeyString}`\n nextKeys.add(registrationKey)\n prepared.push({\n registrationKey,\n def,\n mergedOptions,\n hotkeyString,\n resolvedTarget,\n })\n }\n\n for (const [key, record] of [...registrations.entries()]) {\n if (!nextKeys.has(key)) {\n if (record.handle.isActive) {\n record.handle.unregister()\n }\n registrations.delete(key)\n }\n }\n\n for (const p of prepared) {\n const existing = registrations.get(p.registrationKey)\n if (existing?.handle.isActive && existing.target === p.resolvedTarget) {\n existing.handle.callback = p.def.callback\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\n existing.handle.setOptions(optionsWithoutTarget)\n continue\n }\n\n if (existing) {\n if (existing.handle.isActive) {\n existing.handle.unregister()\n }\n registrations.delete(p.registrationKey)\n }\n\n const { target: _target, ...optionsWithoutTarget } = p.mergedOptions\n const handle = manager.register(p.hotkeyString, p.def.callback, {\n ...optionsWithoutTarget,\n target: p.resolvedTarget,\n })\n registrations.set(p.registrationKey, {\n handle,\n target: p.resolvedTarget,\n })\n }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,SAAgB,cACd,SAGA,gBAAmE,EAAE,EAC/D;CAMN,MAAM,iBAAiB,6BAA6B;CACpD,MAAM,UAAU,kBAAkB;CAClC,MAAM,aAAa,OAAO,WAAW;CAErC,MAAM,gCAAgB,IAAI,KAAiC;AAE3D,YAAW,gBAAgB;AACzB,OAAK,MAAM,EAAE,YAAY,cAAc,QAAQ,CAC7C,KAAI,OAAO,SACT,QAAO,YAAY;AAGvB,gBAAc,OAAO;GACrB;AAEF,cAAa;EACX,MAAM,kBAAkB,OAAO,YAAY,aAAa,SAAS,GAAG;EACpE,MAAM,wBACJ,OAAO,kBAAkB,aAAa,eAAe,GAAG;EAE1D,MAAM,2BAAW,IAAI,KAAa;EAClC,MAAM,WAMD,EAAE;AAEP,OAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;GAC/C,MAAM,MAAM,gBAAgB;GAC5B,MAAM,iBACJ,OAAO,IAAI,WAAW,aAAa,IAAI,QAAQ,GAAG,IAAI;GACxD,MAAM,qBACJ,OAAO,IAAI,YAAY,aAAa,IAAI,SAAS,GAAI,IAAI,WAAW,EAAE;GAExE,MAAM,gBAAgB;IACpB,GAAG,eAAe;IAClB,GAAG;IACH,GAAG;IACJ;GAGD,MAAM,eAAe,4BAA4B,gBADhC,cAAc,YAAY,gBAAgB,CACe;GAE1E,MAAM,iBACJ,YAAY,gBACP,cAAc,UAAU,OACzB,OAAO,aAAa,cAClB,WACA;AAER,OAAI,CAAC,eACH;GAGF,MAAM,kBAAkB,GAAG,EAAE,GAAG;AAChC,YAAS,IAAI,gBAAgB;AAC7B,YAAS,KAAK;IACZ;IACA;IACA;IACA;IACA;IACD,CAAC;;AAGJ,OAAK,MAAM,CAAC,KAAK,WAAW,CAAC,GAAG,cAAc,SAAS,CAAC,CACtD,KAAI,CAAC,SAAS,IAAI,IAAI,EAAE;AACtB,OAAI,OAAO,OAAO,SAChB,QAAO,OAAO,YAAY;AAE5B,iBAAc,OAAO,IAAI;;AAI7B,OAAK,MAAM,KAAK,UAAU;GACxB,MAAM,WAAW,cAAc,IAAI,EAAE,gBAAgB;AACrD,OAAI,UAAU,OAAO,YAAY,SAAS,WAAW,EAAE,gBAAgB;AACrE,aAAS,OAAO,WAAW,EAAE,IAAI;IACjC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;AACvD,aAAS,OAAO,WAAW,qBAAqB;AAChD;;AAGF,OAAI,UAAU;AACZ,QAAI,SAAS,OAAO,SAClB,UAAS,OAAO,YAAY;AAE9B,kBAAc,OAAO,EAAE,gBAAgB;;GAGzC,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB,EAAE;GACvD,MAAM,SAAS,QAAQ,SAAS,EAAE,cAAc,EAAE,IAAI,UAAU;IAC9D,GAAG;IACH,QAAQ,EAAE;IACX,CAAC;AACF,iBAAc,IAAI,EAAE,iBAAiB;IACnC;IACA,QAAQ,EAAE;IACX,CAAC;;GAEJ"}
@@ -1,49 +0,0 @@
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.injectSelector)((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
@@ -1 +0,0 @@
1
- {"version":3,"file":"injectKeyHold.cjs","names":[],"sources":["../src/injectKeyHold.ts"],"sourcesContent":["import { computed } from '@angular/core'\nimport { injectSelector } from '@tanstack/angular-store'\nimport { getKeyStateTracker } from '@tanstack/hotkeys'\nimport type { IndividualKey } 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(\n key: IndividualKey | (() => IndividualKey),\n): Signal<boolean> {\n const tracker = getKeyStateTracker()\n const heldKeysSelector = injectSelector(\n tracker.store,\n (state) => state.heldKeys,\n )\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,cACd,KACiB;CAEjB,MAAM,0GAD8B,CAE1B,QACP,UAAU,MAAM,SAClB;AAED,0CAAsB;EAEpB,MAAM,iBADc,OAAO,QAAQ,aAAa,KAAK,GAAG,KACtB,aAAa;AAE/C,SAAO,kBAAkB,CAAC,MACvB,YAAY,QAAQ,aAAa,KAAK,cACxC;GACD"}
@@ -1,41 +0,0 @@
1
- import { IndividualKey } 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: IndividualKey | (() => IndividualKey)): Signal<boolean>;
39
- //#endregion
40
- export { injectKeyHold };
41
- //# sourceMappingURL=injectKeyHold.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"injectKeyHold.js","names":[],"sources":["../src/injectKeyHold.ts"],"sourcesContent":["import { computed } from '@angular/core'\nimport { injectSelector } from '@tanstack/angular-store'\nimport { getKeyStateTracker } from '@tanstack/hotkeys'\nimport type { IndividualKey } 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(\n key: IndividualKey | (() => IndividualKey),\n): Signal<boolean> {\n const tracker = getKeyStateTracker()\n const heldKeysSelector = injectSelector(\n tracker.store,\n (state) => state.heldKeys,\n )\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,cACd,KACiB;CAEjB,MAAM,mBAAmB,eADT,oBAAoB,CAE1B,QACP,UAAU,MAAM,SAClB;AAED,QAAO,eAAe;EAEpB,MAAM,iBADc,OAAO,QAAQ,aAAa,KAAK,GAAG,KACtB,aAAa;AAE/C,SAAO,kBAAkB,CAAC,MACvB,YAAY,QAAQ,aAAa,KAAK,cACxC;GACD"}
@@ -1,44 +0,0 @@
1
- import { InjectionToken, inject } from '@angular/core'
2
- import type {
3
- HotkeyRecorderOptions,
4
- HotkeySequenceRecorderOptions,
5
- } from '@tanstack/hotkeys'
6
- import type { InjectHotkeyOptions } from './injectHotkey'
7
- import type { InjectHotkeySequenceOptions } from './injectHotkeySequence'
8
- import type { StaticProvider } from '@angular/core'
9
-
10
- export interface HotkeysProviderOptions {
11
- hotkey?: Partial<InjectHotkeyOptions>
12
- hotkeyRecorder?: Partial<HotkeyRecorderOptions>
13
- hotkeySequenceRecorder?: Partial<HotkeySequenceRecorderOptions>
14
- hotkeySequence?: Partial<InjectHotkeySequenceOptions>
15
- }
16
-
17
- export interface HotkeysContextValue {
18
- readonly defaultOptions: HotkeysProviderOptions
19
- }
20
-
21
- export const HOTKEYS_INJECTION_TOKEN = new InjectionToken<HotkeysContextValue>(
22
- 'HOTKEYS_INJECTION_TOKEN',
23
- {
24
- providedIn: 'root',
25
- factory: () => ({ defaultOptions: {} }),
26
- },
27
- )
28
-
29
- export function provideHotkeys(
30
- defaultOptions: HotkeysProviderOptions,
31
- ): StaticProvider {
32
- return {
33
- provide: HOTKEYS_INJECTION_TOKEN,
34
- useValue: { defaultOptions },
35
- }
36
- }
37
-
38
- export function injectHotkeysContext(): HotkeysContextValue {
39
- return inject(HOTKEYS_INJECTION_TOKEN)
40
- }
41
-
42
- export function injectDefaultHotkeysOptions(): HotkeysProviderOptions {
43
- return injectHotkeysContext().defaultOptions
44
- }
package/src/index.ts DELETED
@@ -1,18 +0,0 @@
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 './injectHotkeys'
10
- export * from './injectHotkeySequence'
11
- export * from './injectHotkeySequences'
12
- export * from './injectHeldKeys'
13
- export * from './injectHeldKeyCodes'
14
- export * from './injectKeyHold'
15
- export * from './injectHotkeyRecorder'
16
- export * from './injectHotkeyRegistrations'
17
- export * from './injectHotkeySequenceRecorder'
18
- export * from './injectHotkeyHint'
@@ -1,31 +0,0 @@
1
- import { getKeyStateTracker } from '@tanstack/hotkeys'
2
- import { injectSelector } 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 injectSelector(tracker.store, (state) => state.heldCodes)
31
- }
@@ -1,26 +0,0 @@
1
- import { getKeyStateTracker } from '@tanstack/hotkeys'
2
- import { injectSelector } 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 injectSelector(tracker.store, (state) => state.heldKeys)
26
- }
@@ -1,171 +0,0 @@
1
- import { DestroyRef, effect, inject } from '@angular/core'
2
- import {
3
- detectPlatform,
4
- getHotkeyManager,
5
- normalizeRegisterableHotkey,
6
- } from '@tanstack/hotkeys'
7
- import { injectDefaultHotkeysOptions } from './hotkeys-provider'
8
- import type {
9
- Hotkey,
10
- HotkeyCallback,
11
- HotkeyOptions,
12
- HotkeyRegistrationHandle,
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
- * `enabled: false` keeps the registration (visible in devtools) and only suppresses firing; the same
38
- * handle is updated instead of unregistering and re-registering when identity is unchanged.
39
- *
40
- * @param hotkey - The hotkey string (e.g. 'Mod+S', 'Escape') or getter function
41
- * @param callback - The function to call when the hotkey is pressed
42
- * @param options - Options for the hotkey behavior, or getter for reactive options
43
- *
44
- * @example
45
- * ```ts
46
- * @Component({ ... })
47
- * export class SaveButtonComponent {
48
- * private readonly saveCount = signal(0)
49
- *
50
- * constructor() {
51
- * injectHotkey('Mod+S', (event, { hotkey }) => {
52
- * event.preventDefault()
53
- * this.saveCount.update(c => c + 1)
54
- * })
55
- * }
56
- * }
57
- * ```
58
- *
59
- * @example
60
- * ```ts
61
- * @Component({ ... })
62
- * export class ModalComponent {
63
- * isOpen = signal(true)
64
- *
65
- * constructor() {
66
- * injectHotkey('Escape', () => this.close(), () => ({ enabled: this.isOpen() }))
67
- * }
68
- * }
69
- * ```
70
- *
71
- * @example
72
- * ```ts
73
- * @Component({ ... })
74
- * export class EditorComponent {
75
- * private readonly editorRef = viewChild<ElementRef<HTMLDivElement>>('editorRef')
76
- *
77
- * constructor() {
78
- * injectHotkey('Mod+B', () => this.toggleBold(), () => ({
79
- * target: this.editorRef()?.nativeElement ?? null,
80
- * }))
81
- * }
82
- * }
83
- * ```
84
- */
85
- export function injectHotkey(
86
- hotkey: RegisterableHotkey | (() => RegisterableHotkey),
87
- callback: HotkeyCallback,
88
- options: InjectHotkeyOptions | (() => InjectHotkeyOptions) = {},
89
- ): void {
90
- const defaultOptions = injectDefaultHotkeysOptions()
91
- const manager = getHotkeyManager()
92
- const destroyRef = inject(DestroyRef)
93
-
94
- let registration: HotkeyRegistrationHandle | null = null
95
- let lastHotkeyString: Hotkey | null = null
96
- let lastTarget: HTMLElement | Document | Window | null = null
97
-
98
- destroyRef.onDestroy(() => {
99
- if (registration?.isActive) {
100
- registration.unregister()
101
- registration = null
102
- }
103
- lastHotkeyString = null
104
- lastTarget = null
105
- })
106
-
107
- effect(() => {
108
- // Resolve reactive values
109
- const resolvedHotkey = typeof hotkey === 'function' ? hotkey() : hotkey
110
- const resolvedOptions = typeof options === 'function' ? options() : options
111
-
112
- const mergedOptions = {
113
- ...defaultOptions.hotkey,
114
- ...resolvedOptions,
115
- } as InjectHotkeyOptions
116
-
117
- // Normalize to hotkey string
118
- const platform = mergedOptions.platform ?? detectPlatform()
119
- const hotkeyString = normalizeRegisterableHotkey(resolvedHotkey, platform)
120
-
121
- // Resolve target: when explicitly provided (even as null), use it and skip if null.
122
- // When not provided, default to document. Matches React's ref handling.
123
- const resolvedTarget =
124
- 'target' in mergedOptions
125
- ? (mergedOptions.target ?? null)
126
- : typeof document !== 'undefined'
127
- ? document
128
- : null
129
-
130
- if (!resolvedTarget) {
131
- if (registration?.isActive) {
132
- registration.unregister()
133
- registration = null
134
- }
135
- lastHotkeyString = null
136
- lastTarget = null
137
- return
138
- }
139
-
140
- // Extract options without target (target is handled separately)
141
- const { target: _target, ...optionsWithoutTarget } = mergedOptions
142
-
143
- if (
144
- registration?.isActive &&
145
- lastHotkeyString === hotkeyString &&
146
- lastTarget === resolvedTarget
147
- ) {
148
- registration.callback = callback
149
- registration.setOptions(optionsWithoutTarget)
150
- return
151
- }
152
-
153
- if (registration?.isActive) {
154
- registration.unregister()
155
- registration = null
156
- }
157
-
158
- registration = manager.register(hotkeyString, callback, {
159
- ...optionsWithoutTarget,
160
- target: resolvedTarget,
161
- })
162
-
163
- if (registration.isActive) {
164
- registration.callback = callback
165
- registration.setOptions(optionsWithoutTarget)
166
- }
167
-
168
- lastHotkeyString = hotkeyString
169
- lastTarget = resolvedTarget
170
- })
171
- }
@@ -1,29 +0,0 @@
1
- import { computed } from '@angular/core'
2
- import { injectSelector } from '@tanstack/angular-store'
3
- import { getKeyStateTracker, matchesHeldModifiers } from '@tanstack/hotkeys'
4
- import type { Signal } from '@angular/core'
5
- import type { HeldModifierOptions, RegisterableHotkey } from '@tanstack/hotkeys'
6
-
7
- /**
8
- * Returns a computed signal indicating whether held modifiers reveal this shortcut.
9
- * Call in an injection context; pass signal getters for changing inputs.
10
- * Uses the nonempty-subset/AltGr rules of `matchesHeldModifiers`; `exact` requires
11
- * every modifier. Combine with the action's enabled state before showing a badge.
12
- * This helper does not register a shortcut or check whether its target is focused.
13
- */
14
- export function injectHotkeyHint(
15
- hotkey: RegisterableHotkey | (() => RegisterableHotkey),
16
- options: HeldModifierOptions | (() => HeldModifierOptions) = {},
17
- ): Signal<boolean> {
18
- const held = injectSelector(
19
- getKeyStateTracker().store,
20
- (state) => state.heldKeys,
21
- )
22
- return computed(() =>
23
- matchesHeldModifiers(
24
- typeof hotkey === 'function' ? hotkey() : hotkey,
25
- held(),
26
- typeof options === 'function' ? options() : options,
27
- ),
28
- )
29
- }
@@ -1,128 +0,0 @@
1
- import {
2
- DestroyRef,
3
- computed,
4
- effect,
5
- inject,
6
- linkedSignal,
7
- untracked,
8
- } from '@angular/core'
9
- import { HotkeyRecorder } from '@tanstack/hotkeys'
10
- import { injectDefaultHotkeysOptions } from './hotkeys-provider'
11
- import type { Atom, ReadonlyAtom } from '@tanstack/angular-store'
12
- import type { Signal } from '@angular/core'
13
- import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys'
14
-
15
- export interface AngularHotkeyRecorder {
16
- /** Whether recording is currently active */
17
- readonly isRecording: () => boolean
18
- /** The currently recorded hotkey (for live preview) */
19
- readonly recordedHotkey: () => Hotkey | null
20
- /** Start recording a new hotkey */
21
- readonly startRecording: () => void
22
- /** Stop recording (same as cancel) */
23
- readonly stopRecording: () => void
24
- /** Cancel recording without saving */
25
- readonly cancelRecording: () => void
26
- }
27
-
28
- /**
29
- * Angular inject-based API for recording keyboard shortcuts.
30
- *
31
- * Thin wrapper around the framework-agnostic HotkeyRecorder class: captures
32
- * keyboard events, converts them to hotkey strings, and handles Escape to
33
- * cancel or Backspace/Delete to clear.
34
- *
35
- * @param options - Configuration options for the recorder (or getter)
36
- * @returns Object with recording state signals and control functions
37
- *
38
- * @example
39
- * ```ts
40
- * @Component({ ... })
41
- * export class ShortcutSettingsComponent {
42
- * shortcut = signal<Hotkey>('Mod+S')
43
- * recorder = injectHotkeyRecorder({
44
- * onRecord: (hotkey) => this.shortcut.set(hotkey),
45
- * onCancel: () => console.log('Recording cancelled'),
46
- * })
47
- *
48
- * constructor() {
49
- * injectHotkey(
50
- * () => this.shortcut(),
51
- * () => this.handleSave(),
52
- * () => ({ enabled: !this.recorder.isRecording() }),
53
- * )
54
- * }
55
- * }
56
- * ```
57
- */
58
- export function injectHotkeyRecorder(
59
- options: HotkeyRecorderOptions | (() => HotkeyRecorderOptions),
60
- ): AngularHotkeyRecorder {
61
- const defaultOptions = injectDefaultHotkeysOptions()
62
- const destroyRef = inject(DestroyRef)
63
-
64
- // Stable signal to lazy initialize the recorder
65
- const recorderSignal = computed(() =>
66
- untracked(() => {
67
- const resolvedOptions =
68
- typeof options === 'function' ? options() : options
69
-
70
- const mergedOptions = {
71
- ...defaultOptions.hotkeyRecorder,
72
- ...resolvedOptions,
73
- } as HotkeyRecorderOptions
74
-
75
- return new HotkeyRecorder(mergedOptions)
76
- }),
77
- )
78
-
79
- // Subscribe to recorder state
80
- const recorderStore = computed(() => untracked(() => recorderSignal().store))
81
- const isRecording = injectLazyStore(
82
- recorderStore,
83
- (state) => state.isRecording,
84
- )
85
- const recordedHotkey = injectLazyStore(
86
- recorderStore,
87
- (state) => state.recordedHotkey,
88
- )
89
-
90
- // Sync options on every effect run (matches React's sync on render)
91
- effect(() => {
92
- const resolved = typeof options === 'function' ? options() : options
93
- recorderSignal().setOptions({
94
- ...defaultOptions.hotkeyRecorder,
95
- ...resolved,
96
- } as HotkeyRecorderOptions)
97
- })
98
-
99
- destroyRef.onDestroy(() => {
100
- recorderSignal().destroy()
101
- })
102
-
103
- return {
104
- isRecording,
105
- recordedHotkey,
106
- startRecording: () => recorderSignal().start(),
107
- stopRecording: () => recorderSignal().stop(),
108
- cancelRecording: () => recorderSignal().cancel(),
109
- }
110
- }
111
-
112
- function injectLazyStore<TState, TSelected = NoInfer<TState>>(
113
- storeSignal: Signal<Atom<TState> | ReadonlyAtom<TState>>,
114
- selector: (state: NoInfer<TState>) => TSelected,
115
- ): Signal<TSelected> {
116
- const slice = linkedSignal(() => selector(storeSignal().get()))
117
-
118
- effect((onCleanup) => {
119
- const currentStore = storeSignal()
120
- slice.set(selector(currentStore.get()))
121
- const { unsubscribe } = currentStore.subscribe((s) => {
122
- slice.set(selector(s))
123
- })
124
- onCleanup(() => unsubscribe())
125
- })
126
-
127
- return slice.asReadonly()
128
- }