@tanstack/angular-hotkeys 0.11.0 → 0.12.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/dist/hotkeys-provider.d.ts +7 -10
- package/dist/hotkeys-provider.js +1 -2
- package/dist/injectHeldKeyCodes.d.ts +2 -5
- package/dist/injectHeldKeyCodes.js +3 -3
- package/dist/injectHeldKeys.d.ts +2 -5
- package/dist/injectHeldKeys.js +3 -3
- package/dist/injectHotkey.d.ts +3 -6
- package/dist/injectHotkey.js +3 -3
- package/dist/injectHotkeyHint.d.ts +2 -5
- package/dist/injectHotkeyHint.js +1 -2
- package/dist/injectHotkeyRecorder.d.ts +3 -6
- package/dist/injectHotkeyRecorder.js +4 -4
- package/dist/injectHotkeyRegistrations.d.ts +3 -6
- package/dist/injectHotkeyRegistrations.js +1 -2
- package/dist/injectHotkeySequence.d.ts +3 -6
- package/dist/injectHotkeySequence.js +1 -2
- package/dist/injectHotkeySequenceRecorder.d.ts +3 -6
- package/dist/injectHotkeySequenceRecorder.js +4 -4
- package/dist/injectHotkeySequences.d.ts +3 -6
- package/dist/injectHotkeySequences.js +1 -2
- package/dist/injectHotkeys.d.ts +3 -6
- package/dist/injectHotkeys.js +3 -3
- package/dist/injectKeyHold.d.ts +2 -5
- package/dist/injectKeyHold.js +3 -3
- package/package.json +12 -17
- package/dist/hotkeys-provider.cjs +0 -26
- package/dist/hotkeys-provider.cjs.map +0 -1
- package/dist/hotkeys-provider.d.cts +0 -22
- package/dist/hotkeys-provider.js.map +0 -1
- package/dist/index.cjs +0 -36
- package/dist/index.d.cts +0 -14
- package/dist/injectHeldKeyCodes.cjs +0 -34
- package/dist/injectHeldKeyCodes.cjs.map +0 -1
- package/dist/injectHeldKeyCodes.d.cts +0 -30
- package/dist/injectHeldKeyCodes.js.map +0 -1
- package/dist/injectHeldKeys.cjs +0 -29
- package/dist/injectHeldKeys.cjs.map +0 -1
- package/dist/injectHeldKeys.d.cts +0 -25
- package/dist/injectHeldKeys.js.map +0 -1
- package/dist/injectHotkey.cjs +0 -122
- package/dist/injectHotkey.cjs.map +0 -1
- package/dist/injectHotkey.d.cts +0 -75
- package/dist/injectHotkey.js.map +0 -1
- package/dist/injectHotkeyHint.cjs +0 -20
- package/dist/injectHotkeyHint.cjs.map +0 -1
- package/dist/injectHotkeyHint.d.cts +0 -15
- package/dist/injectHotkeyHint.js.map +0 -1
- package/dist/injectHotkeyRecorder.cjs +0 -82
- package/dist/injectHotkeyRecorder.cjs.map +0 -1
- package/dist/injectHotkeyRecorder.d.cts +0 -49
- package/dist/injectHotkeyRecorder.js.map +0 -1
- package/dist/injectHotkeyRegistrations.cjs +0 -28
- package/dist/injectHotkeyRegistrations.cjs.map +0 -1
- package/dist/injectHotkeyRegistrations.d.cts +0 -29
- package/dist/injectHotkeyRegistrations.js.map +0 -1
- package/dist/injectHotkeySequence.cjs +0 -93
- package/dist/injectHotkeySequence.cjs.map +0 -1
- package/dist/injectHotkeySequence.d.cts +0 -48
- package/dist/injectHotkeySequence.js.map +0 -1
- package/dist/injectHotkeySequenceRecorder.cjs +0 -58
- package/dist/injectHotkeySequenceRecorder.cjs.map +0 -1
- package/dist/injectHotkeySequenceRecorder.d.cts +0 -19
- package/dist/injectHotkeySequenceRecorder.js.map +0 -1
- package/dist/injectHotkeySequences.cjs +0 -105
- package/dist/injectHotkeySequences.cjs.map +0 -1
- package/dist/injectHotkeySequences.d.cts +0 -47
- package/dist/injectHotkeySequences.js.map +0 -1
- package/dist/injectHotkeys.cjs +0 -120
- package/dist/injectHotkeys.cjs.map +0 -1
- package/dist/injectHotkeys.d.cts +0 -66
- package/dist/injectHotkeys.js.map +0 -1
- package/dist/injectKeyHold.cjs +0 -49
- package/dist/injectKeyHold.cjs.map +0 -1
- package/dist/injectKeyHold.d.cts +0 -41
- package/dist/injectKeyHold.js.map +0 -1
- package/src/hotkeys-provider.ts +0 -44
- package/src/index.ts +0 -18
- package/src/injectHeldKeyCodes.ts +0 -31
- package/src/injectHeldKeys.ts +0 -26
- package/src/injectHotkey.ts +0 -171
- package/src/injectHotkeyHint.ts +0 -29
- package/src/injectHotkeyRecorder.ts +0 -128
- package/src/injectHotkeyRegistrations.ts +0 -48
- package/src/injectHotkeySequence.ts +0 -140
- package/src/injectHotkeySequenceRecorder.ts +0 -103
- package/src/injectHotkeySequences.ts +0 -176
- package/src/injectHotkeys.ts +0 -189
- package/src/injectKeyHold.ts +0 -57
package/dist/injectHotkeys.d.cts
DELETED
|
@@ -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"}
|
package/dist/injectKeyHold.cjs
DELETED
|
@@ -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"}
|
package/dist/injectKeyHold.d.cts
DELETED
|
@@ -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"}
|
package/src/hotkeys-provider.ts
DELETED
|
@@ -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
|
-
}
|
package/src/injectHeldKeys.ts
DELETED
|
@@ -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
|
-
}
|
package/src/injectHotkey.ts
DELETED
|
@@ -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
|
-
}
|
package/src/injectHotkeyHint.ts
DELETED
|
@@ -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
|
-
}
|