@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.
- package/README.md +122 -0
- package/dist/hotkeys-provider.cjs +26 -0
- package/dist/hotkeys-provider.cjs.map +1 -0
- package/dist/hotkeys-provider.d.cts +21 -0
- package/dist/hotkeys-provider.d.ts +21 -0
- package/dist/hotkeys-provider.js +23 -0
- package/dist/hotkeys-provider.js.map +1 -0
- package/dist/index.cjs +26 -0
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +11 -0
- package/dist/injectHeldKeyCodes.cjs +34 -0
- package/dist/injectHeldKeyCodes.cjs.map +1 -0
- package/dist/injectHeldKeyCodes.d.cts +30 -0
- package/dist/injectHeldKeyCodes.d.ts +30 -0
- package/dist/injectHeldKeyCodes.js +34 -0
- package/dist/injectHeldKeyCodes.js.map +1 -0
- package/dist/injectHeldKeys.cjs +29 -0
- package/dist/injectHeldKeys.cjs.map +1 -0
- package/dist/injectHeldKeys.d.cts +25 -0
- package/dist/injectHeldKeys.d.ts +25 -0
- package/dist/injectHeldKeys.js +29 -0
- package/dist/injectHeldKeys.js.map +1 -0
- package/dist/injectHotkey.cjs +93 -0
- package/dist/injectHotkey.cjs.map +1 -0
- package/dist/injectHotkey.d.cts +73 -0
- package/dist/injectHotkey.d.ts +73 -0
- package/dist/injectHotkey.js +93 -0
- package/dist/injectHotkey.js.map +1 -0
- package/dist/injectHotkeyRecorder.cjs +82 -0
- package/dist/injectHotkeyRecorder.cjs.map +1 -0
- package/dist/injectHotkeyRecorder.d.cts +49 -0
- package/dist/injectHotkeyRecorder.d.ts +49 -0
- package/dist/injectHotkeyRecorder.js +82 -0
- package/dist/injectHotkeyRecorder.js.map +1 -0
- package/dist/injectHotkeySequence.cjs +55 -0
- package/dist/injectHotkeySequence.cjs.map +1 -0
- package/dist/injectHotkeySequence.d.cts +35 -0
- package/dist/injectHotkeySequence.d.ts +35 -0
- package/dist/injectHotkeySequence.js +55 -0
- package/dist/injectHotkeySequence.js.map +1 -0
- package/dist/injectKeyHold.cjs +49 -0
- package/dist/injectKeyHold.cjs.map +1 -0
- package/dist/injectKeyHold.d.cts +41 -0
- package/dist/injectKeyHold.d.ts +41 -0
- package/dist/injectKeyHold.js +49 -0
- package/dist/injectKeyHold.js.map +1 -0
- package/package.json +67 -0
- package/src/hotkeys-provider.ts +40 -0
- package/src/index.ts +13 -0
- package/src/injectHeldKeyCodes.ts +31 -0
- package/src/injectHeldKeys.ts +26 -0
- package/src/injectHotkey.ts +145 -0
- package/src/injectHotkeyRecorder.ts +128 -0
- package/src/injectHotkeySequence.ts +87 -0
- package/src/injectKeyHold.ts +52 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injectHotkey.cjs","names":["injectDefaultHotkeysOptions"],"sources":["../src/injectHotkey.ts"],"sourcesContent":["import { effect } from '@angular/core'\nimport {\n detectPlatform,\n formatHotkey,\n getHotkeyManager,\n rawHotkeyToParsedHotkey,\n} from '@tanstack/hotkeys'\nimport { injectDefaultHotkeysOptions } from './hotkeys-provider'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyOptions,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\n\nexport interface InjectHotkeyOptions extends Omit<HotkeyOptions, 'target'> {\n /**\n * The DOM element to attach the event listener to.\n * Can be a direct DOM element, an accessor (for reactive targets that become\n * available after mount), or null. Defaults to document.\n * When using scoped targets, pass an accessor: () => ({ target: elementSignal() })\n * so the hotkey waits for the element to be attached before registering.\n */\n target?: HTMLElement | Document | Window | null\n}\n\n/**\n * Angular inject-based API for registering a keyboard hotkey.\n *\n * Uses the singleton HotkeyManager for efficient event handling.\n * The callback receives both the keyboard event and a context object\n * containing the hotkey string and parsed hotkey.\n *\n * Call in an injection context (e.g. constructor or field initializer).\n * Uses effect() to track reactive dependencies and update registration\n * when options or the callback change.\n *\n * @param hotkey - The hotkey string (e.g. 'Mod+S', 'Escape') or getter function\n * @param callback - The function to call when the hotkey is pressed\n * @param options - Options for the hotkey behavior, or getter for reactive options\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class SaveButtonComponent {\n * private readonly saveCount = signal(0)\n *\n * constructor() {\n * injectHotkey('Mod+S', (event, { hotkey }) => {\n * event.preventDefault()\n * this.saveCount.update(c => c + 1)\n * })\n * }\n * }\n * ```\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class ModalComponent {\n * isOpen = signal(true)\n *\n * constructor() {\n * injectHotkey('Escape', () => this.close(), () => ({ enabled: this.isOpen() }))\n * }\n * }\n * ```\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class EditorComponent {\n * private readonly editorRef = viewChild<ElementRef<HTMLDivElement>>('editorRef')\n *\n * constructor() {\n * injectHotkey('Mod+B', () => this.toggleBold(), () => ({\n * target: this.editorRef()?.nativeElement ?? null,\n * }))\n * }\n * }\n * ```\n */\nexport function injectHotkey(\n hotkey: RegisterableHotkey | (() => RegisterableHotkey),\n callback: HotkeyCallback,\n options: InjectHotkeyOptions | (() => InjectHotkeyOptions) = {},\n): void {\n const defaultOptions = injectDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n\n effect((onCleanup) => {\n // Resolve reactive values\n const resolvedHotkey = typeof hotkey === 'function' ? hotkey() : hotkey\n const resolvedOptions = typeof options === 'function' ? options() : options\n\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedOptions,\n } as InjectHotkeyOptions\n\n // Normalize to hotkey string\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString: Hotkey =\n typeof resolvedHotkey === 'string'\n ? resolvedHotkey\n : (formatHotkey(\n rawHotkeyToParsedHotkey(resolvedHotkey, platform),\n ) as Hotkey)\n\n // Resolve target: when explicitly provided (even as null), use it and skip if null.\n // When not provided, default to document. Matches React's ref handling.\n const resolvedTarget =\n 'target' in mergedOptions\n ? (mergedOptions.target ?? null)\n : typeof document !== 'undefined'\n ? document\n : null\n\n if (!resolvedTarget) {\n return\n }\n\n // Extract options without target (target is handled separately)\n const { target: _target, ...optionsWithoutTarget } = mergedOptions\n\n // Register the hotkey\n const registration = manager.register(hotkeyString, callback, {\n ...optionsWithoutTarget,\n target: resolvedTarget,\n })\n\n // Update callback and options on every effect run\n if (registration.isActive) {\n registration.callback = callback\n registration.setOptions(optionsWithoutTarget)\n }\n\n // Cleanup on disposal\n onCleanup(() => {\n if (registration.isActive) {\n registration.unregister()\n }\n })\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkFA,SAAgB,aACd,QACA,UACA,UAA6D,EAAE,EACzD;CACN,MAAM,iBAAiBA,sDAA6B;CACpD,MAAM,mDAA4B;AAElC,4BAAQ,cAAc;EAEpB,MAAM,iBAAiB,OAAO,WAAW,aAAa,QAAQ,GAAG;EACjE,MAAM,kBAAkB,OAAO,YAAY,aAAa,SAAS,GAAG;EAEpE,MAAM,gBAAgB;GACpB,GAAG,eAAe;GAClB,GAAG;GACJ;EAGD,MAAM,WAAW,cAAc,mDAA4B;EAC3D,MAAM,eACJ,OAAO,mBAAmB,WACtB,oGAE0B,gBAAgB,SAAS,CAClD;EAIP,MAAM,iBACJ,YAAY,gBACP,cAAc,UAAU,OACzB,OAAO,aAAa,cAClB,WACA;AAER,MAAI,CAAC,eACH;EAIF,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB;EAGrD,MAAM,eAAe,QAAQ,SAAS,cAAc,UAAU;GAC5D,GAAG;GACH,QAAQ;GACT,CAAC;AAGF,MAAI,aAAa,UAAU;AACzB,gBAAa,WAAW;AACxB,gBAAa,WAAW,qBAAqB;;AAI/C,kBAAgB;AACd,OAAI,aAAa,SACf,cAAa,YAAY;IAE3B;GACF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { HotkeyCallback, HotkeyOptions, RegisterableHotkey } from "@tanstack/hotkeys";
|
|
2
|
+
|
|
3
|
+
//#region src/injectHotkey.d.ts
|
|
4
|
+
interface InjectHotkeyOptions extends Omit<HotkeyOptions, 'target'> {
|
|
5
|
+
/**
|
|
6
|
+
* The DOM element to attach the event listener to.
|
|
7
|
+
* Can be a direct DOM element, an accessor (for reactive targets that become
|
|
8
|
+
* available after mount), or null. Defaults to document.
|
|
9
|
+
* When using scoped targets, pass an accessor: () => ({ target: elementSignal() })
|
|
10
|
+
* so the hotkey waits for the element to be attached before registering.
|
|
11
|
+
*/
|
|
12
|
+
target?: HTMLElement | Document | Window | null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Angular inject-based API for registering a keyboard hotkey.
|
|
16
|
+
*
|
|
17
|
+
* Uses the singleton HotkeyManager for efficient event handling.
|
|
18
|
+
* The callback receives both the keyboard event and a context object
|
|
19
|
+
* containing the hotkey string and parsed hotkey.
|
|
20
|
+
*
|
|
21
|
+
* Call in an injection context (e.g. constructor or field initializer).
|
|
22
|
+
* Uses effect() to track reactive dependencies and update registration
|
|
23
|
+
* when options or the callback change.
|
|
24
|
+
*
|
|
25
|
+
* @param hotkey - The hotkey string (e.g. 'Mod+S', 'Escape') or getter function
|
|
26
|
+
* @param callback - The function to call when the hotkey is pressed
|
|
27
|
+
* @param options - Options for the hotkey behavior, or getter for reactive options
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* @Component({ ... })
|
|
32
|
+
* export class SaveButtonComponent {
|
|
33
|
+
* private readonly saveCount = signal(0)
|
|
34
|
+
*
|
|
35
|
+
* constructor() {
|
|
36
|
+
* injectHotkey('Mod+S', (event, { hotkey }) => {
|
|
37
|
+
* event.preventDefault()
|
|
38
|
+
* this.saveCount.update(c => c + 1)
|
|
39
|
+
* })
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* @Component({ ... })
|
|
47
|
+
* export class ModalComponent {
|
|
48
|
+
* isOpen = signal(true)
|
|
49
|
+
*
|
|
50
|
+
* constructor() {
|
|
51
|
+
* injectHotkey('Escape', () => this.close(), () => ({ enabled: this.isOpen() }))
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* @Component({ ... })
|
|
59
|
+
* export class EditorComponent {
|
|
60
|
+
* private readonly editorRef = viewChild<ElementRef<HTMLDivElement>>('editorRef')
|
|
61
|
+
*
|
|
62
|
+
* constructor() {
|
|
63
|
+
* injectHotkey('Mod+B', () => this.toggleBold(), () => ({
|
|
64
|
+
* target: this.editorRef()?.nativeElement ?? null,
|
|
65
|
+
* }))
|
|
66
|
+
* }
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function injectHotkey(hotkey: RegisterableHotkey | (() => RegisterableHotkey), callback: HotkeyCallback, options?: InjectHotkeyOptions | (() => InjectHotkeyOptions)): void;
|
|
71
|
+
//#endregion
|
|
72
|
+
export { InjectHotkeyOptions, injectHotkey };
|
|
73
|
+
//# sourceMappingURL=injectHotkey.d.cts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { HotkeyCallback, HotkeyOptions, RegisterableHotkey } from "@tanstack/hotkeys";
|
|
2
|
+
|
|
3
|
+
//#region src/injectHotkey.d.ts
|
|
4
|
+
interface InjectHotkeyOptions extends Omit<HotkeyOptions, 'target'> {
|
|
5
|
+
/**
|
|
6
|
+
* The DOM element to attach the event listener to.
|
|
7
|
+
* Can be a direct DOM element, an accessor (for reactive targets that become
|
|
8
|
+
* available after mount), or null. Defaults to document.
|
|
9
|
+
* When using scoped targets, pass an accessor: () => ({ target: elementSignal() })
|
|
10
|
+
* so the hotkey waits for the element to be attached before registering.
|
|
11
|
+
*/
|
|
12
|
+
target?: HTMLElement | Document | Window | null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Angular inject-based API for registering a keyboard hotkey.
|
|
16
|
+
*
|
|
17
|
+
* Uses the singleton HotkeyManager for efficient event handling.
|
|
18
|
+
* The callback receives both the keyboard event and a context object
|
|
19
|
+
* containing the hotkey string and parsed hotkey.
|
|
20
|
+
*
|
|
21
|
+
* Call in an injection context (e.g. constructor or field initializer).
|
|
22
|
+
* Uses effect() to track reactive dependencies and update registration
|
|
23
|
+
* when options or the callback change.
|
|
24
|
+
*
|
|
25
|
+
* @param hotkey - The hotkey string (e.g. 'Mod+S', 'Escape') or getter function
|
|
26
|
+
* @param callback - The function to call when the hotkey is pressed
|
|
27
|
+
* @param options - Options for the hotkey behavior, or getter for reactive options
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* @Component({ ... })
|
|
32
|
+
* export class SaveButtonComponent {
|
|
33
|
+
* private readonly saveCount = signal(0)
|
|
34
|
+
*
|
|
35
|
+
* constructor() {
|
|
36
|
+
* injectHotkey('Mod+S', (event, { hotkey }) => {
|
|
37
|
+
* event.preventDefault()
|
|
38
|
+
* this.saveCount.update(c => c + 1)
|
|
39
|
+
* })
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* @Component({ ... })
|
|
47
|
+
* export class ModalComponent {
|
|
48
|
+
* isOpen = signal(true)
|
|
49
|
+
*
|
|
50
|
+
* constructor() {
|
|
51
|
+
* injectHotkey('Escape', () => this.close(), () => ({ enabled: this.isOpen() }))
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* @Component({ ... })
|
|
59
|
+
* export class EditorComponent {
|
|
60
|
+
* private readonly editorRef = viewChild<ElementRef<HTMLDivElement>>('editorRef')
|
|
61
|
+
*
|
|
62
|
+
* constructor() {
|
|
63
|
+
* injectHotkey('Mod+B', () => this.toggleBold(), () => ({
|
|
64
|
+
* target: this.editorRef()?.nativeElement ?? null,
|
|
65
|
+
* }))
|
|
66
|
+
* }
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function injectHotkey(hotkey: RegisterableHotkey | (() => RegisterableHotkey), callback: HotkeyCallback, options?: InjectHotkeyOptions | (() => InjectHotkeyOptions)): void;
|
|
71
|
+
//#endregion
|
|
72
|
+
export { InjectHotkeyOptions, injectHotkey };
|
|
73
|
+
//# sourceMappingURL=injectHotkey.d.ts.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { injectDefaultHotkeysOptions } from "./hotkeys-provider.js";
|
|
2
|
+
import { detectPlatform, formatHotkey, getHotkeyManager, rawHotkeyToParsedHotkey } from "@tanstack/hotkeys";
|
|
3
|
+
import { effect } from "@angular/core";
|
|
4
|
+
|
|
5
|
+
//#region src/injectHotkey.ts
|
|
6
|
+
/**
|
|
7
|
+
* Angular inject-based API for registering a keyboard hotkey.
|
|
8
|
+
*
|
|
9
|
+
* Uses the singleton HotkeyManager for efficient event handling.
|
|
10
|
+
* The callback receives both the keyboard event and a context object
|
|
11
|
+
* containing the hotkey string and parsed hotkey.
|
|
12
|
+
*
|
|
13
|
+
* Call in an injection context (e.g. constructor or field initializer).
|
|
14
|
+
* Uses effect() to track reactive dependencies and update registration
|
|
15
|
+
* when options or the callback change.
|
|
16
|
+
*
|
|
17
|
+
* @param hotkey - The hotkey string (e.g. 'Mod+S', 'Escape') or getter function
|
|
18
|
+
* @param callback - The function to call when the hotkey is pressed
|
|
19
|
+
* @param options - Options for the hotkey behavior, or getter for reactive options
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* @Component({ ... })
|
|
24
|
+
* export class SaveButtonComponent {
|
|
25
|
+
* private readonly saveCount = signal(0)
|
|
26
|
+
*
|
|
27
|
+
* constructor() {
|
|
28
|
+
* injectHotkey('Mod+S', (event, { hotkey }) => {
|
|
29
|
+
* event.preventDefault()
|
|
30
|
+
* this.saveCount.update(c => c + 1)
|
|
31
|
+
* })
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* @Component({ ... })
|
|
39
|
+
* export class ModalComponent {
|
|
40
|
+
* isOpen = signal(true)
|
|
41
|
+
*
|
|
42
|
+
* constructor() {
|
|
43
|
+
* injectHotkey('Escape', () => this.close(), () => ({ enabled: this.isOpen() }))
|
|
44
|
+
* }
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* @Component({ ... })
|
|
51
|
+
* export class EditorComponent {
|
|
52
|
+
* private readonly editorRef = viewChild<ElementRef<HTMLDivElement>>('editorRef')
|
|
53
|
+
*
|
|
54
|
+
* constructor() {
|
|
55
|
+
* injectHotkey('Mod+B', () => this.toggleBold(), () => ({
|
|
56
|
+
* target: this.editorRef()?.nativeElement ?? null,
|
|
57
|
+
* }))
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
function injectHotkey(hotkey, callback, options = {}) {
|
|
63
|
+
const defaultOptions = injectDefaultHotkeysOptions();
|
|
64
|
+
const manager = getHotkeyManager();
|
|
65
|
+
effect((onCleanup) => {
|
|
66
|
+
const resolvedHotkey = typeof hotkey === "function" ? hotkey() : hotkey;
|
|
67
|
+
const resolvedOptions = typeof options === "function" ? options() : options;
|
|
68
|
+
const mergedOptions = {
|
|
69
|
+
...defaultOptions.hotkey,
|
|
70
|
+
...resolvedOptions
|
|
71
|
+
};
|
|
72
|
+
const platform = mergedOptions.platform ?? detectPlatform();
|
|
73
|
+
const hotkeyString = typeof resolvedHotkey === "string" ? resolvedHotkey : formatHotkey(rawHotkeyToParsedHotkey(resolvedHotkey, platform));
|
|
74
|
+
const resolvedTarget = "target" in mergedOptions ? mergedOptions.target ?? null : typeof document !== "undefined" ? document : null;
|
|
75
|
+
if (!resolvedTarget) return;
|
|
76
|
+
const { target: _target, ...optionsWithoutTarget } = mergedOptions;
|
|
77
|
+
const registration = manager.register(hotkeyString, callback, {
|
|
78
|
+
...optionsWithoutTarget,
|
|
79
|
+
target: resolvedTarget
|
|
80
|
+
});
|
|
81
|
+
if (registration.isActive) {
|
|
82
|
+
registration.callback = callback;
|
|
83
|
+
registration.setOptions(optionsWithoutTarget);
|
|
84
|
+
}
|
|
85
|
+
onCleanup(() => {
|
|
86
|
+
if (registration.isActive) registration.unregister();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
export { injectHotkey };
|
|
93
|
+
//# sourceMappingURL=injectHotkey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injectHotkey.js","names":[],"sources":["../src/injectHotkey.ts"],"sourcesContent":["import { effect } from '@angular/core'\nimport {\n detectPlatform,\n formatHotkey,\n getHotkeyManager,\n rawHotkeyToParsedHotkey,\n} from '@tanstack/hotkeys'\nimport { injectDefaultHotkeysOptions } from './hotkeys-provider'\nimport type {\n Hotkey,\n HotkeyCallback,\n HotkeyOptions,\n RegisterableHotkey,\n} from '@tanstack/hotkeys'\n\nexport interface InjectHotkeyOptions extends Omit<HotkeyOptions, 'target'> {\n /**\n * The DOM element to attach the event listener to.\n * Can be a direct DOM element, an accessor (for reactive targets that become\n * available after mount), or null. Defaults to document.\n * When using scoped targets, pass an accessor: () => ({ target: elementSignal() })\n * so the hotkey waits for the element to be attached before registering.\n */\n target?: HTMLElement | Document | Window | null\n}\n\n/**\n * Angular inject-based API for registering a keyboard hotkey.\n *\n * Uses the singleton HotkeyManager for efficient event handling.\n * The callback receives both the keyboard event and a context object\n * containing the hotkey string and parsed hotkey.\n *\n * Call in an injection context (e.g. constructor or field initializer).\n * Uses effect() to track reactive dependencies and update registration\n * when options or the callback change.\n *\n * @param hotkey - The hotkey string (e.g. 'Mod+S', 'Escape') or getter function\n * @param callback - The function to call when the hotkey is pressed\n * @param options - Options for the hotkey behavior, or getter for reactive options\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class SaveButtonComponent {\n * private readonly saveCount = signal(0)\n *\n * constructor() {\n * injectHotkey('Mod+S', (event, { hotkey }) => {\n * event.preventDefault()\n * this.saveCount.update(c => c + 1)\n * })\n * }\n * }\n * ```\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class ModalComponent {\n * isOpen = signal(true)\n *\n * constructor() {\n * injectHotkey('Escape', () => this.close(), () => ({ enabled: this.isOpen() }))\n * }\n * }\n * ```\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class EditorComponent {\n * private readonly editorRef = viewChild<ElementRef<HTMLDivElement>>('editorRef')\n *\n * constructor() {\n * injectHotkey('Mod+B', () => this.toggleBold(), () => ({\n * target: this.editorRef()?.nativeElement ?? null,\n * }))\n * }\n * }\n * ```\n */\nexport function injectHotkey(\n hotkey: RegisterableHotkey | (() => RegisterableHotkey),\n callback: HotkeyCallback,\n options: InjectHotkeyOptions | (() => InjectHotkeyOptions) = {},\n): void {\n const defaultOptions = injectDefaultHotkeysOptions()\n const manager = getHotkeyManager()\n\n effect((onCleanup) => {\n // Resolve reactive values\n const resolvedHotkey = typeof hotkey === 'function' ? hotkey() : hotkey\n const resolvedOptions = typeof options === 'function' ? options() : options\n\n const mergedOptions = {\n ...defaultOptions.hotkey,\n ...resolvedOptions,\n } as InjectHotkeyOptions\n\n // Normalize to hotkey string\n const platform = mergedOptions.platform ?? detectPlatform()\n const hotkeyString: Hotkey =\n typeof resolvedHotkey === 'string'\n ? resolvedHotkey\n : (formatHotkey(\n rawHotkeyToParsedHotkey(resolvedHotkey, platform),\n ) as Hotkey)\n\n // Resolve target: when explicitly provided (even as null), use it and skip if null.\n // When not provided, default to document. Matches React's ref handling.\n const resolvedTarget =\n 'target' in mergedOptions\n ? (mergedOptions.target ?? null)\n : typeof document !== 'undefined'\n ? document\n : null\n\n if (!resolvedTarget) {\n return\n }\n\n // Extract options without target (target is handled separately)\n const { target: _target, ...optionsWithoutTarget } = mergedOptions\n\n // Register the hotkey\n const registration = manager.register(hotkeyString, callback, {\n ...optionsWithoutTarget,\n target: resolvedTarget,\n })\n\n // Update callback and options on every effect run\n if (registration.isActive) {\n registration.callback = callback\n registration.setOptions(optionsWithoutTarget)\n }\n\n // Cleanup on disposal\n onCleanup(() => {\n if (registration.isActive) {\n registration.unregister()\n }\n })\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkFA,SAAgB,aACd,QACA,UACA,UAA6D,EAAE,EACzD;CACN,MAAM,iBAAiB,6BAA6B;CACpD,MAAM,UAAU,kBAAkB;AAElC,SAAQ,cAAc;EAEpB,MAAM,iBAAiB,OAAO,WAAW,aAAa,QAAQ,GAAG;EACjE,MAAM,kBAAkB,OAAO,YAAY,aAAa,SAAS,GAAG;EAEpE,MAAM,gBAAgB;GACpB,GAAG,eAAe;GAClB,GAAG;GACJ;EAGD,MAAM,WAAW,cAAc,YAAY,gBAAgB;EAC3D,MAAM,eACJ,OAAO,mBAAmB,WACtB,iBACC,aACC,wBAAwB,gBAAgB,SAAS,CAClD;EAIP,MAAM,iBACJ,YAAY,gBACP,cAAc,UAAU,OACzB,OAAO,aAAa,cAClB,WACA;AAER,MAAI,CAAC,eACH;EAIF,MAAM,EAAE,QAAQ,SAAS,GAAG,yBAAyB;EAGrD,MAAM,eAAe,QAAQ,SAAS,cAAc,UAAU;GAC5D,GAAG;GACH,QAAQ;GACT,CAAC;AAGF,MAAI,aAAa,UAAU;AACzB,gBAAa,WAAW;AACxB,gBAAa,WAAW,qBAAqB;;AAI/C,kBAAgB;AACd,OAAI,aAAa,SACf,cAAa,YAAY;IAE3B;GACF"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const require_hotkeys_provider = require('./hotkeys-provider.cjs');
|
|
2
|
+
let _tanstack_hotkeys = require("@tanstack/hotkeys");
|
|
3
|
+
let _angular_core = require("@angular/core");
|
|
4
|
+
|
|
5
|
+
//#region src/injectHotkeyRecorder.ts
|
|
6
|
+
/**
|
|
7
|
+
* Angular inject-based API for recording keyboard shortcuts.
|
|
8
|
+
*
|
|
9
|
+
* Thin wrapper around the framework-agnostic HotkeyRecorder class: captures
|
|
10
|
+
* keyboard events, converts them to hotkey strings, and handles Escape to
|
|
11
|
+
* cancel or Backspace/Delete to clear.
|
|
12
|
+
*
|
|
13
|
+
* @param options - Configuration options for the recorder (or getter)
|
|
14
|
+
* @returns Object with recording state signals and control functions
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* @Component({ ... })
|
|
19
|
+
* export class ShortcutSettingsComponent {
|
|
20
|
+
* shortcut = signal<Hotkey>('Mod+S')
|
|
21
|
+
* recorder = injectHotkeyRecorder({
|
|
22
|
+
* onRecord: (hotkey) => this.shortcut.set(hotkey),
|
|
23
|
+
* onCancel: () => console.log('Recording cancelled'),
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* constructor() {
|
|
27
|
+
* injectHotkey(
|
|
28
|
+
* () => this.shortcut(),
|
|
29
|
+
* () => this.handleSave(),
|
|
30
|
+
* () => ({ enabled: !this.recorder.isRecording() }),
|
|
31
|
+
* )
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
function injectHotkeyRecorder(options) {
|
|
37
|
+
const defaultOptions = require_hotkeys_provider.injectDefaultHotkeysOptions();
|
|
38
|
+
const destroyRef = (0, _angular_core.inject)(_angular_core.DestroyRef);
|
|
39
|
+
const recorderSignal = (0, _angular_core.computed)(() => (0, _angular_core.untracked)(() => {
|
|
40
|
+
const resolvedOptions = typeof options === "function" ? options() : options;
|
|
41
|
+
return new _tanstack_hotkeys.HotkeyRecorder({
|
|
42
|
+
...defaultOptions.hotkeyRecorder,
|
|
43
|
+
...resolvedOptions
|
|
44
|
+
});
|
|
45
|
+
}));
|
|
46
|
+
const recorderStore = (0, _angular_core.computed)(() => (0, _angular_core.untracked)(() => recorderSignal().store));
|
|
47
|
+
const isRecording = injectLazyStore(recorderStore, (state) => state.isRecording);
|
|
48
|
+
const recordedHotkey = injectLazyStore(recorderStore, (state) => state.recordedHotkey);
|
|
49
|
+
(0, _angular_core.effect)(() => {
|
|
50
|
+
const resolved = typeof options === "function" ? options() : options;
|
|
51
|
+
recorderSignal().setOptions({
|
|
52
|
+
...defaultOptions.hotkeyRecorder,
|
|
53
|
+
...resolved
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
destroyRef.onDestroy(() => {
|
|
57
|
+
recorderSignal().destroy();
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
isRecording,
|
|
61
|
+
recordedHotkey,
|
|
62
|
+
startRecording: () => recorderSignal().start(),
|
|
63
|
+
stopRecording: () => recorderSignal().stop(),
|
|
64
|
+
cancelRecording: () => recorderSignal().cancel()
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function injectLazyStore(storeSignal, selector) {
|
|
68
|
+
const slice = (0, _angular_core.linkedSignal)(() => selector(storeSignal().get()));
|
|
69
|
+
(0, _angular_core.effect)((onCleanup) => {
|
|
70
|
+
const currentStore = storeSignal();
|
|
71
|
+
slice.set(selector(currentStore.get()));
|
|
72
|
+
const { unsubscribe } = currentStore.subscribe((s) => {
|
|
73
|
+
slice.set(selector(s));
|
|
74
|
+
});
|
|
75
|
+
onCleanup(() => unsubscribe());
|
|
76
|
+
});
|
|
77
|
+
return slice.asReadonly();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
exports.injectHotkeyRecorder = injectHotkeyRecorder;
|
|
82
|
+
//# sourceMappingURL=injectHotkeyRecorder.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injectHotkeyRecorder.cjs","names":["injectDefaultHotkeysOptions","DestroyRef","HotkeyRecorder"],"sources":["../src/injectHotkeyRecorder.ts"],"sourcesContent":["import {\n DestroyRef,\n computed,\n effect,\n inject,\n linkedSignal,\n untracked,\n} from '@angular/core'\nimport { HotkeyRecorder } from '@tanstack/hotkeys'\nimport { injectDefaultHotkeysOptions } from './hotkeys-provider'\nimport type { Atom, ReadonlyAtom } from '@tanstack/angular-store'\nimport type { Signal } from '@angular/core'\nimport type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys'\n\nexport interface AngularHotkeyRecorder {\n /** Whether recording is currently active */\n readonly isRecording: () => boolean\n /** The currently recorded hotkey (for live preview) */\n readonly recordedHotkey: () => Hotkey | null\n /** Start recording a new hotkey */\n readonly startRecording: () => void\n /** Stop recording (same as cancel) */\n readonly stopRecording: () => void\n /** Cancel recording without saving */\n readonly cancelRecording: () => void\n}\n\n/**\n * Angular inject-based API for recording keyboard shortcuts.\n *\n * Thin wrapper around the framework-agnostic HotkeyRecorder class: captures\n * keyboard events, converts them to hotkey strings, and handles Escape to\n * cancel or Backspace/Delete to clear.\n *\n * @param options - Configuration options for the recorder (or getter)\n * @returns Object with recording state signals and control functions\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class ShortcutSettingsComponent {\n * shortcut = signal<Hotkey>('Mod+S')\n * recorder = injectHotkeyRecorder({\n * onRecord: (hotkey) => this.shortcut.set(hotkey),\n * onCancel: () => console.log('Recording cancelled'),\n * })\n *\n * constructor() {\n * injectHotkey(\n * () => this.shortcut(),\n * () => this.handleSave(),\n * () => ({ enabled: !this.recorder.isRecording() }),\n * )\n * }\n * }\n * ```\n */\nexport function injectHotkeyRecorder(\n options: HotkeyRecorderOptions | (() => HotkeyRecorderOptions),\n): AngularHotkeyRecorder {\n const defaultOptions = injectDefaultHotkeysOptions()\n const destroyRef = inject(DestroyRef)\n\n // Stable signal to lazy initialize the recorder\n const recorderSignal = computed(() =>\n untracked(() => {\n const resolvedOptions =\n typeof options === 'function' ? options() : options\n\n const mergedOptions = {\n ...defaultOptions.hotkeyRecorder,\n ...resolvedOptions,\n } as HotkeyRecorderOptions\n\n return new HotkeyRecorder(mergedOptions)\n }),\n )\n\n // Subscribe to recorder state\n const recorderStore = computed(() => untracked(() => recorderSignal().store))\n const isRecording = injectLazyStore(\n recorderStore,\n (state) => state.isRecording,\n )\n const recordedHotkey = injectLazyStore(\n recorderStore,\n (state) => state.recordedHotkey,\n )\n\n // Sync options on every effect run (matches React's sync on render)\n effect(() => {\n const resolved = typeof options === 'function' ? options() : options\n recorderSignal().setOptions({\n ...defaultOptions.hotkeyRecorder,\n ...resolved,\n } as HotkeyRecorderOptions)\n })\n\n destroyRef.onDestroy(() => {\n recorderSignal().destroy()\n })\n\n return {\n isRecording,\n recordedHotkey,\n startRecording: () => recorderSignal().start(),\n stopRecording: () => recorderSignal().stop(),\n cancelRecording: () => recorderSignal().cancel(),\n }\n}\n\nfunction injectLazyStore<TState, TSelected = NoInfer<TState>>(\n storeSignal: Signal<Atom<TState> | ReadonlyAtom<TState>>,\n selector: (state: NoInfer<TState>) => TSelected,\n): Signal<TSelected> {\n const slice = linkedSignal(() => selector(storeSignal().get()))\n\n effect((onCleanup) => {\n const currentStore = storeSignal()\n slice.set(selector(currentStore.get()))\n const { unsubscribe } = currentStore.subscribe((s) => {\n slice.set(selector(s))\n })\n onCleanup(() => unsubscribe())\n })\n\n return slice.asReadonly()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,SAAgB,qBACd,SACuB;CACvB,MAAM,iBAAiBA,sDAA6B;CACpD,MAAM,uCAAoBC,yBAAW;CAGrC,MAAM,sFACY;EACd,MAAM,kBACJ,OAAO,YAAY,aAAa,SAAS,GAAG;AAO9C,SAAO,IAAIC,iCALW;GACpB,GAAG,eAAe;GAClB,GAAG;GACJ,CAEuC;GACxC,CACH;CAGD,MAAM,qFAA+C,gBAAgB,CAAC,MAAM,CAAC;CAC7E,MAAM,cAAc,gBAClB,gBACC,UAAU,MAAM,YAClB;CACD,MAAM,iBAAiB,gBACrB,gBACC,UAAU,MAAM,eAClB;AAGD,iCAAa;EACX,MAAM,WAAW,OAAO,YAAY,aAAa,SAAS,GAAG;AAC7D,kBAAgB,CAAC,WAAW;GAC1B,GAAG,eAAe;GAClB,GAAG;GACJ,CAA0B;GAC3B;AAEF,YAAW,gBAAgB;AACzB,kBAAgB,CAAC,SAAS;GAC1B;AAEF,QAAO;EACL;EACA;EACA,sBAAsB,gBAAgB,CAAC,OAAO;EAC9C,qBAAqB,gBAAgB,CAAC,MAAM;EAC5C,uBAAuB,gBAAgB,CAAC,QAAQ;EACjD;;AAGH,SAAS,gBACP,aACA,UACmB;CACnB,MAAM,8CAA2B,SAAS,aAAa,CAAC,KAAK,CAAC,CAAC;AAE/D,4BAAQ,cAAc;EACpB,MAAM,eAAe,aAAa;AAClC,QAAM,IAAI,SAAS,aAAa,KAAK,CAAC,CAAC;EACvC,MAAM,EAAE,gBAAgB,aAAa,WAAW,MAAM;AACpD,SAAM,IAAI,SAAS,EAAE,CAAC;IACtB;AACF,kBAAgB,aAAa,CAAC;GAC9B;AAEF,QAAO,MAAM,YAAY"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Hotkey, HotkeyRecorderOptions } from "@tanstack/hotkeys";
|
|
2
|
+
|
|
3
|
+
//#region src/injectHotkeyRecorder.d.ts
|
|
4
|
+
interface AngularHotkeyRecorder {
|
|
5
|
+
/** Whether recording is currently active */
|
|
6
|
+
readonly isRecording: () => boolean;
|
|
7
|
+
/** The currently recorded hotkey (for live preview) */
|
|
8
|
+
readonly recordedHotkey: () => Hotkey | null;
|
|
9
|
+
/** Start recording a new hotkey */
|
|
10
|
+
readonly startRecording: () => void;
|
|
11
|
+
/** Stop recording (same as cancel) */
|
|
12
|
+
readonly stopRecording: () => void;
|
|
13
|
+
/** Cancel recording without saving */
|
|
14
|
+
readonly cancelRecording: () => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Angular inject-based API for recording keyboard shortcuts.
|
|
18
|
+
*
|
|
19
|
+
* Thin wrapper around the framework-agnostic HotkeyRecorder class: captures
|
|
20
|
+
* keyboard events, converts them to hotkey strings, and handles Escape to
|
|
21
|
+
* cancel or Backspace/Delete to clear.
|
|
22
|
+
*
|
|
23
|
+
* @param options - Configuration options for the recorder (or getter)
|
|
24
|
+
* @returns Object with recording state signals and control functions
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* @Component({ ... })
|
|
29
|
+
* export class ShortcutSettingsComponent {
|
|
30
|
+
* shortcut = signal<Hotkey>('Mod+S')
|
|
31
|
+
* recorder = injectHotkeyRecorder({
|
|
32
|
+
* onRecord: (hotkey) => this.shortcut.set(hotkey),
|
|
33
|
+
* onCancel: () => console.log('Recording cancelled'),
|
|
34
|
+
* })
|
|
35
|
+
*
|
|
36
|
+
* constructor() {
|
|
37
|
+
* injectHotkey(
|
|
38
|
+
* () => this.shortcut(),
|
|
39
|
+
* () => this.handleSave(),
|
|
40
|
+
* () => ({ enabled: !this.recorder.isRecording() }),
|
|
41
|
+
* )
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare function injectHotkeyRecorder(options: HotkeyRecorderOptions | (() => HotkeyRecorderOptions)): AngularHotkeyRecorder;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { AngularHotkeyRecorder, injectHotkeyRecorder };
|
|
49
|
+
//# sourceMappingURL=injectHotkeyRecorder.d.cts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Hotkey, HotkeyRecorderOptions } from "@tanstack/hotkeys";
|
|
2
|
+
|
|
3
|
+
//#region src/injectHotkeyRecorder.d.ts
|
|
4
|
+
interface AngularHotkeyRecorder {
|
|
5
|
+
/** Whether recording is currently active */
|
|
6
|
+
readonly isRecording: () => boolean;
|
|
7
|
+
/** The currently recorded hotkey (for live preview) */
|
|
8
|
+
readonly recordedHotkey: () => Hotkey | null;
|
|
9
|
+
/** Start recording a new hotkey */
|
|
10
|
+
readonly startRecording: () => void;
|
|
11
|
+
/** Stop recording (same as cancel) */
|
|
12
|
+
readonly stopRecording: () => void;
|
|
13
|
+
/** Cancel recording without saving */
|
|
14
|
+
readonly cancelRecording: () => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Angular inject-based API for recording keyboard shortcuts.
|
|
18
|
+
*
|
|
19
|
+
* Thin wrapper around the framework-agnostic HotkeyRecorder class: captures
|
|
20
|
+
* keyboard events, converts them to hotkey strings, and handles Escape to
|
|
21
|
+
* cancel or Backspace/Delete to clear.
|
|
22
|
+
*
|
|
23
|
+
* @param options - Configuration options for the recorder (or getter)
|
|
24
|
+
* @returns Object with recording state signals and control functions
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* @Component({ ... })
|
|
29
|
+
* export class ShortcutSettingsComponent {
|
|
30
|
+
* shortcut = signal<Hotkey>('Mod+S')
|
|
31
|
+
* recorder = injectHotkeyRecorder({
|
|
32
|
+
* onRecord: (hotkey) => this.shortcut.set(hotkey),
|
|
33
|
+
* onCancel: () => console.log('Recording cancelled'),
|
|
34
|
+
* })
|
|
35
|
+
*
|
|
36
|
+
* constructor() {
|
|
37
|
+
* injectHotkey(
|
|
38
|
+
* () => this.shortcut(),
|
|
39
|
+
* () => this.handleSave(),
|
|
40
|
+
* () => ({ enabled: !this.recorder.isRecording() }),
|
|
41
|
+
* )
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare function injectHotkeyRecorder(options: HotkeyRecorderOptions | (() => HotkeyRecorderOptions)): AngularHotkeyRecorder;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { AngularHotkeyRecorder, injectHotkeyRecorder };
|
|
49
|
+
//# sourceMappingURL=injectHotkeyRecorder.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { injectDefaultHotkeysOptions } from "./hotkeys-provider.js";
|
|
2
|
+
import { HotkeyRecorder } from "@tanstack/hotkeys";
|
|
3
|
+
import { DestroyRef, computed, effect, inject, linkedSignal, untracked } from "@angular/core";
|
|
4
|
+
|
|
5
|
+
//#region src/injectHotkeyRecorder.ts
|
|
6
|
+
/**
|
|
7
|
+
* Angular inject-based API for recording keyboard shortcuts.
|
|
8
|
+
*
|
|
9
|
+
* Thin wrapper around the framework-agnostic HotkeyRecorder class: captures
|
|
10
|
+
* keyboard events, converts them to hotkey strings, and handles Escape to
|
|
11
|
+
* cancel or Backspace/Delete to clear.
|
|
12
|
+
*
|
|
13
|
+
* @param options - Configuration options for the recorder (or getter)
|
|
14
|
+
* @returns Object with recording state signals and control functions
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* @Component({ ... })
|
|
19
|
+
* export class ShortcutSettingsComponent {
|
|
20
|
+
* shortcut = signal<Hotkey>('Mod+S')
|
|
21
|
+
* recorder = injectHotkeyRecorder({
|
|
22
|
+
* onRecord: (hotkey) => this.shortcut.set(hotkey),
|
|
23
|
+
* onCancel: () => console.log('Recording cancelled'),
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* constructor() {
|
|
27
|
+
* injectHotkey(
|
|
28
|
+
* () => this.shortcut(),
|
|
29
|
+
* () => this.handleSave(),
|
|
30
|
+
* () => ({ enabled: !this.recorder.isRecording() }),
|
|
31
|
+
* )
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
function injectHotkeyRecorder(options) {
|
|
37
|
+
const defaultOptions = injectDefaultHotkeysOptions();
|
|
38
|
+
const destroyRef = inject(DestroyRef);
|
|
39
|
+
const recorderSignal = computed(() => untracked(() => {
|
|
40
|
+
const resolvedOptions = typeof options === "function" ? options() : options;
|
|
41
|
+
return new HotkeyRecorder({
|
|
42
|
+
...defaultOptions.hotkeyRecorder,
|
|
43
|
+
...resolvedOptions
|
|
44
|
+
});
|
|
45
|
+
}));
|
|
46
|
+
const recorderStore = computed(() => untracked(() => recorderSignal().store));
|
|
47
|
+
const isRecording = injectLazyStore(recorderStore, (state) => state.isRecording);
|
|
48
|
+
const recordedHotkey = injectLazyStore(recorderStore, (state) => state.recordedHotkey);
|
|
49
|
+
effect(() => {
|
|
50
|
+
const resolved = typeof options === "function" ? options() : options;
|
|
51
|
+
recorderSignal().setOptions({
|
|
52
|
+
...defaultOptions.hotkeyRecorder,
|
|
53
|
+
...resolved
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
destroyRef.onDestroy(() => {
|
|
57
|
+
recorderSignal().destroy();
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
isRecording,
|
|
61
|
+
recordedHotkey,
|
|
62
|
+
startRecording: () => recorderSignal().start(),
|
|
63
|
+
stopRecording: () => recorderSignal().stop(),
|
|
64
|
+
cancelRecording: () => recorderSignal().cancel()
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function injectLazyStore(storeSignal, selector) {
|
|
68
|
+
const slice = linkedSignal(() => selector(storeSignal().get()));
|
|
69
|
+
effect((onCleanup) => {
|
|
70
|
+
const currentStore = storeSignal();
|
|
71
|
+
slice.set(selector(currentStore.get()));
|
|
72
|
+
const { unsubscribe } = currentStore.subscribe((s) => {
|
|
73
|
+
slice.set(selector(s));
|
|
74
|
+
});
|
|
75
|
+
onCleanup(() => unsubscribe());
|
|
76
|
+
});
|
|
77
|
+
return slice.asReadonly();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
export { injectHotkeyRecorder };
|
|
82
|
+
//# sourceMappingURL=injectHotkeyRecorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injectHotkeyRecorder.js","names":[],"sources":["../src/injectHotkeyRecorder.ts"],"sourcesContent":["import {\n DestroyRef,\n computed,\n effect,\n inject,\n linkedSignal,\n untracked,\n} from '@angular/core'\nimport { HotkeyRecorder } from '@tanstack/hotkeys'\nimport { injectDefaultHotkeysOptions } from './hotkeys-provider'\nimport type { Atom, ReadonlyAtom } from '@tanstack/angular-store'\nimport type { Signal } from '@angular/core'\nimport type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys'\n\nexport interface AngularHotkeyRecorder {\n /** Whether recording is currently active */\n readonly isRecording: () => boolean\n /** The currently recorded hotkey (for live preview) */\n readonly recordedHotkey: () => Hotkey | null\n /** Start recording a new hotkey */\n readonly startRecording: () => void\n /** Stop recording (same as cancel) */\n readonly stopRecording: () => void\n /** Cancel recording without saving */\n readonly cancelRecording: () => void\n}\n\n/**\n * Angular inject-based API for recording keyboard shortcuts.\n *\n * Thin wrapper around the framework-agnostic HotkeyRecorder class: captures\n * keyboard events, converts them to hotkey strings, and handles Escape to\n * cancel or Backspace/Delete to clear.\n *\n * @param options - Configuration options for the recorder (or getter)\n * @returns Object with recording state signals and control functions\n *\n * @example\n * ```ts\n * @Component({ ... })\n * export class ShortcutSettingsComponent {\n * shortcut = signal<Hotkey>('Mod+S')\n * recorder = injectHotkeyRecorder({\n * onRecord: (hotkey) => this.shortcut.set(hotkey),\n * onCancel: () => console.log('Recording cancelled'),\n * })\n *\n * constructor() {\n * injectHotkey(\n * () => this.shortcut(),\n * () => this.handleSave(),\n * () => ({ enabled: !this.recorder.isRecording() }),\n * )\n * }\n * }\n * ```\n */\nexport function injectHotkeyRecorder(\n options: HotkeyRecorderOptions | (() => HotkeyRecorderOptions),\n): AngularHotkeyRecorder {\n const defaultOptions = injectDefaultHotkeysOptions()\n const destroyRef = inject(DestroyRef)\n\n // Stable signal to lazy initialize the recorder\n const recorderSignal = computed(() =>\n untracked(() => {\n const resolvedOptions =\n typeof options === 'function' ? options() : options\n\n const mergedOptions = {\n ...defaultOptions.hotkeyRecorder,\n ...resolvedOptions,\n } as HotkeyRecorderOptions\n\n return new HotkeyRecorder(mergedOptions)\n }),\n )\n\n // Subscribe to recorder state\n const recorderStore = computed(() => untracked(() => recorderSignal().store))\n const isRecording = injectLazyStore(\n recorderStore,\n (state) => state.isRecording,\n )\n const recordedHotkey = injectLazyStore(\n recorderStore,\n (state) => state.recordedHotkey,\n )\n\n // Sync options on every effect run (matches React's sync on render)\n effect(() => {\n const resolved = typeof options === 'function' ? options() : options\n recorderSignal().setOptions({\n ...defaultOptions.hotkeyRecorder,\n ...resolved,\n } as HotkeyRecorderOptions)\n })\n\n destroyRef.onDestroy(() => {\n recorderSignal().destroy()\n })\n\n return {\n isRecording,\n recordedHotkey,\n startRecording: () => recorderSignal().start(),\n stopRecording: () => recorderSignal().stop(),\n cancelRecording: () => recorderSignal().cancel(),\n }\n}\n\nfunction injectLazyStore<TState, TSelected = NoInfer<TState>>(\n storeSignal: Signal<Atom<TState> | ReadonlyAtom<TState>>,\n selector: (state: NoInfer<TState>) => TSelected,\n): Signal<TSelected> {\n const slice = linkedSignal(() => selector(storeSignal().get()))\n\n effect((onCleanup) => {\n const currentStore = storeSignal()\n slice.set(selector(currentStore.get()))\n const { unsubscribe } = currentStore.subscribe((s) => {\n slice.set(selector(s))\n })\n onCleanup(() => unsubscribe())\n })\n\n return slice.asReadonly()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,SAAgB,qBACd,SACuB;CACvB,MAAM,iBAAiB,6BAA6B;CACpD,MAAM,aAAa,OAAO,WAAW;CAGrC,MAAM,iBAAiB,eACrB,gBAAgB;EACd,MAAM,kBACJ,OAAO,YAAY,aAAa,SAAS,GAAG;AAO9C,SAAO,IAAI,eALW;GACpB,GAAG,eAAe;GAClB,GAAG;GACJ,CAEuC;GACxC,CACH;CAGD,MAAM,gBAAgB,eAAe,gBAAgB,gBAAgB,CAAC,MAAM,CAAC;CAC7E,MAAM,cAAc,gBAClB,gBACC,UAAU,MAAM,YAClB;CACD,MAAM,iBAAiB,gBACrB,gBACC,UAAU,MAAM,eAClB;AAGD,cAAa;EACX,MAAM,WAAW,OAAO,YAAY,aAAa,SAAS,GAAG;AAC7D,kBAAgB,CAAC,WAAW;GAC1B,GAAG,eAAe;GAClB,GAAG;GACJ,CAA0B;GAC3B;AAEF,YAAW,gBAAgB;AACzB,kBAAgB,CAAC,SAAS;GAC1B;AAEF,QAAO;EACL;EACA;EACA,sBAAsB,gBAAgB,CAAC,OAAO;EAC9C,qBAAqB,gBAAgB,CAAC,MAAM;EAC5C,uBAAuB,gBAAgB,CAAC,QAAQ;EACjD;;AAGH,SAAS,gBACP,aACA,UACmB;CACnB,MAAM,QAAQ,mBAAmB,SAAS,aAAa,CAAC,KAAK,CAAC,CAAC;AAE/D,SAAQ,cAAc;EACpB,MAAM,eAAe,aAAa;AAClC,QAAM,IAAI,SAAS,aAAa,KAAK,CAAC,CAAC;EACvC,MAAM,EAAE,gBAAgB,aAAa,WAAW,MAAM;AACpD,SAAM,IAAI,SAAS,EAAE,CAAC;IACtB;AACF,kBAAgB,aAAa,CAAC;GAC9B;AAEF,QAAO,MAAM,YAAY"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const require_hotkeys_provider = require('./hotkeys-provider.cjs');
|
|
2
|
+
let _tanstack_hotkeys = require("@tanstack/hotkeys");
|
|
3
|
+
let _angular_core = require("@angular/core");
|
|
4
|
+
|
|
5
|
+
//#region src/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 = require_hotkeys_provider.injectDefaultHotkeysOptions();
|
|
32
|
+
(0, _angular_core.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 = (0, _tanstack_hotkeys.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
|
+
exports.injectHotkeySequence = injectHotkeySequence;
|
|
55
|
+
//# sourceMappingURL=injectHotkeySequence.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injectHotkeySequence.cjs","names":["injectDefaultHotkeysOptions"],"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,iBAAiBA,sDAA6B;AAEpD,4BAAQ,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,qDAA8B;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,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.cts.map
|