@tanstack/angular-hotkeys 0.13.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hotkeys-provider.d.ts +48 -9
- package/dist/hotkeys-provider.js +49 -4
- package/dist/index.d.ts +5 -7
- package/dist/index.js +3 -5
- package/dist/injectAppHotkeys.d.ts +45 -0
- package/dist/injectAppHotkeys.js +66 -0
- package/dist/injectHotkey.d.ts +33 -58
- package/dist/injectHotkey.js +6 -115
- package/dist/injectHotkeyHint.d.ts +7 -7
- package/dist/injectHotkeyHint.js +14 -7
- package/dist/injectHotkeyRecorder.d.ts +20 -10
- package/dist/injectHotkeyRecorder.js +6 -33
- package/dist/injectHotkeyRegistrations.d.ts +20 -18
- package/dist/injectHotkeyRegistrations.js +20 -14
- package/dist/injectHotkeys.d.ts +16 -52
- package/dist/injectHotkeys.js +24 -104
- package/package.json +2 -2
- package/dist/injectHotkeySequence.d.ts +0 -45
- package/dist/injectHotkeySequence.js +0 -92
- package/dist/injectHotkeySequenceRecorder.d.ts +0 -16
- package/dist/injectHotkeySequenceRecorder.js +0 -58
- package/dist/injectHotkeySequences.d.ts +0 -44
- package/dist/injectHotkeySequences.js +0 -104
|
@@ -3,48 +3,19 @@ import { HotkeyRecorder } from "@tanstack/hotkeys";
|
|
|
3
3
|
import { DestroyRef, computed, effect, inject, linkedSignal, untracked } from "@angular/core";
|
|
4
4
|
|
|
5
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
|
-
* )
|
|
31
|
-
* }
|
|
32
|
-
* }
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
6
|
function injectHotkeyRecorder(options) {
|
|
36
7
|
const defaultOptions = injectDefaultHotkeysOptions();
|
|
37
8
|
const destroyRef = inject(DestroyRef);
|
|
38
9
|
const recorderSignal = computed(() => untracked(() => {
|
|
39
10
|
const resolvedOptions = typeof options === "function" ? options() : options;
|
|
40
|
-
|
|
11
|
+
return new HotkeyRecorder({
|
|
41
12
|
...defaultOptions.hotkeyRecorder,
|
|
42
13
|
...resolvedOptions
|
|
43
|
-
};
|
|
44
|
-
return new HotkeyRecorder(mergedOptions);
|
|
14
|
+
});
|
|
45
15
|
}));
|
|
46
16
|
const recorderStore = computed(() => untracked(() => recorderSignal().store));
|
|
47
17
|
const isRecording = injectLazyStore(recorderStore, (state) => state.isRecording);
|
|
18
|
+
const steps = injectLazyStore(recorderStore, (state) => state.steps);
|
|
48
19
|
const recordedHotkey = injectLazyStore(recorderStore, (state) => state.recordedHotkey);
|
|
49
20
|
effect(() => {
|
|
50
21
|
const resolved = typeof options === "function" ? options() : options;
|
|
@@ -58,10 +29,12 @@ function injectHotkeyRecorder(options) {
|
|
|
58
29
|
});
|
|
59
30
|
return {
|
|
60
31
|
isRecording,
|
|
32
|
+
steps,
|
|
61
33
|
recordedHotkey,
|
|
62
34
|
startRecording: () => recorderSignal().start(),
|
|
63
35
|
stopRecording: () => recorderSignal().stop(),
|
|
64
|
-
cancelRecording: () => recorderSignal().cancel()
|
|
36
|
+
cancelRecording: () => recorderSignal().cancel(),
|
|
37
|
+
commitRecording: () => recorderSignal().commit()
|
|
65
38
|
};
|
|
66
39
|
}
|
|
67
40
|
function injectLazyStore(storeSignal, selector) {
|
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
import { HotkeyRegistrationView
|
|
1
|
+
import { HotkeyRegistrationView } from "@tanstack/hotkeys";
|
|
2
2
|
import { Signal } from "@angular/core";
|
|
3
3
|
//#region src/injectHotkeyRegistrations.d.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/** Signal for all registered hotkeys (public view, no callbacks) */
|
|
9
|
-
hotkeys: Signal<Array<HotkeyRegistrationView>>;
|
|
10
|
-
/** Signal for all registered sequences */
|
|
11
|
-
sequences: Signal<Array<SequenceRegistrationView>>;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Angular injectable that reactively reads all hotkey and sequence registrations
|
|
15
|
-
* from the singleton managers.
|
|
5
|
+
* Angular inject-based API that reactively reads every hotkey registration,
|
|
6
|
+
* chords and sequences, from the singleton HotkeyManager. Narrow each view
|
|
7
|
+
* with `kind`.
|
|
16
8
|
*
|
|
17
|
-
* This
|
|
18
|
-
* It subscribes to both HotkeyManager and SequenceManager stores and
|
|
19
|
-
* triggers change detection when registrations change.
|
|
9
|
+
* This does NOT require `provideHotkeys`.
|
|
20
10
|
*
|
|
21
|
-
*
|
|
11
|
+
* @returns A signal of all registrations, without callbacks
|
|
22
12
|
*
|
|
23
|
-
* @
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* @Component({
|
|
16
|
+
* template: `
|
|
17
|
+
* @for (reg of registrations(); track reg.id) {
|
|
18
|
+
* <li>{{ reg.options.meta?.name }}</li>
|
|
19
|
+
* }
|
|
20
|
+
* `,
|
|
21
|
+
* })
|
|
22
|
+
* export class ShortcutPalette {
|
|
23
|
+
* registrations = injectHotkeyRegistrations()
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
24
26
|
*/
|
|
25
|
-
export declare function injectHotkeyRegistrations():
|
|
27
|
+
export declare function injectHotkeyRegistrations(): Signal<Array<HotkeyRegistrationView>>;
|
|
26
28
|
//#endregion
|
|
@@ -1,26 +1,32 @@
|
|
|
1
|
-
import { getHotkeyManager,
|
|
1
|
+
import { getHotkeyManager, toHotkeyRegistrationView } from "@tanstack/hotkeys";
|
|
2
2
|
import { injectSelector } from "@tanstack/angular-store";
|
|
3
3
|
|
|
4
4
|
//#region src/injectHotkeyRegistrations.ts
|
|
5
5
|
/**
|
|
6
|
-
* Angular
|
|
7
|
-
* from the singleton
|
|
6
|
+
* Angular inject-based API that reactively reads every hotkey registration,
|
|
7
|
+
* chords and sequences, from the singleton HotkeyManager. Narrow each view
|
|
8
|
+
* with `kind`.
|
|
8
9
|
*
|
|
9
|
-
* This
|
|
10
|
-
* It subscribes to both HotkeyManager and SequenceManager stores and
|
|
11
|
-
* triggers change detection when registrations change.
|
|
10
|
+
* This does NOT require `provideHotkeys`.
|
|
12
11
|
*
|
|
13
|
-
*
|
|
12
|
+
* @returns A signal of all registrations, without callbacks
|
|
14
13
|
*
|
|
15
|
-
* @
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* @Component({
|
|
17
|
+
* template: `
|
|
18
|
+
* @for (reg of registrations(); track reg.id) {
|
|
19
|
+
* <li>{{ reg.options.meta?.name }}</li>
|
|
20
|
+
* }
|
|
21
|
+
* `,
|
|
22
|
+
* })
|
|
23
|
+
* export class ShortcutPalette {
|
|
24
|
+
* registrations = injectHotkeyRegistrations()
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
16
27
|
*/
|
|
17
28
|
function injectHotkeyRegistrations() {
|
|
18
|
-
|
|
19
|
-
const sequenceManager = getSequenceManager();
|
|
20
|
-
return {
|
|
21
|
-
hotkeys: injectSelector(hotkeyManager.registrations, (state) => Array.from(state.values()).map(toHotkeyRegistrationView)),
|
|
22
|
-
sequences: injectSelector(sequenceManager.registrations, (state) => Array.from(state.values()))
|
|
23
|
-
};
|
|
29
|
+
return injectSelector(getHotkeyManager().registrations, (state) => Array.from(state.values()).map(toHotkeyRegistrationView));
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
//#endregion
|
package/dist/injectHotkeys.d.ts
CHANGED
|
@@ -1,63 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { InjectHotkeyCommonOptions, InjectHotkeyTarget } from "./injectHotkey.js";
|
|
2
|
+
import { AdapterHotkeyDefinition } from "@tanstack/hotkeys/adapter";
|
|
3
3
|
//#region src/injectHotkeys.d.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* One hotkey for `injectHotkeys`. Chords accept `requireReset`; sequences
|
|
6
|
+
* accept `timeout`; hotkeys of unknown kind accept only shared options.
|
|
6
7
|
*/
|
|
7
|
-
export
|
|
8
|
-
/** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */
|
|
9
|
-
hotkey: RegisterableHotkey | (() => RegisterableHotkey);
|
|
10
|
-
/** The function to call when the hotkey is pressed */
|
|
11
|
-
callback: HotkeyCallback;
|
|
12
|
-
/** Per-hotkey options (merged on top of commonOptions) */
|
|
13
|
-
options?: InjectHotkeyOptions | (() => InjectHotkeyOptions);
|
|
14
|
-
}
|
|
8
|
+
export type InjectHotkeyDefinition = AdapterHotkeyDefinition<InjectHotkeyTarget>;
|
|
15
9
|
/**
|
|
16
|
-
* Angular inject-based API for registering
|
|
10
|
+
* Angular inject-based API for registering several hotkeys at once: chords,
|
|
11
|
+
* sequences, and app hotkeys. Pass a getter for a reactive list.
|
|
17
12
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* Uses effect() to track reactive dependencies and update registrations
|
|
23
|
-
* when options or the callback change.
|
|
24
|
-
*
|
|
25
|
-
* Options are merged in this order:
|
|
26
|
-
* provideHotkeys defaults < commonOptions < per-definition options
|
|
27
|
-
*
|
|
28
|
-
* @param hotkeys - Array of hotkey definitions, or getter returning them
|
|
29
|
-
* @param commonOptions - Shared options for all hotkeys, or getter
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```ts
|
|
33
|
-
* @Component({ ... })
|
|
34
|
-
* export class EditorComponent {
|
|
35
|
-
* constructor() {
|
|
36
|
-
* injectHotkeys([
|
|
37
|
-
* { hotkey: 'Mod+S', callback: () => this.save() },
|
|
38
|
-
* { hotkey: 'Mod+Z', callback: () => this.undo() },
|
|
39
|
-
* { hotkey: 'Escape', callback: () => this.close() },
|
|
40
|
-
* ])
|
|
41
|
-
* }
|
|
42
|
-
* }
|
|
43
|
-
* ```
|
|
13
|
+
* Options merge in this order: provider defaults, app hotkey declaration,
|
|
14
|
+
* commonOptions, per-hotkey options. Each option applies only to the kinds
|
|
15
|
+
* that support it. Unbound app hotkeys and empty sequences register nothing.
|
|
16
|
+
* A `null` target skips registration until it is set.
|
|
44
17
|
*
|
|
45
18
|
* @example
|
|
46
19
|
* ```ts
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* constructor() {
|
|
52
|
-
* injectHotkeys(
|
|
53
|
-
* () => this.shortcuts().map((s) => ({
|
|
54
|
-
* hotkey: s.key,
|
|
55
|
-
* callback: s.action,
|
|
56
|
-
* })),
|
|
57
|
-
* )
|
|
58
|
-
* }
|
|
59
|
-
* }
|
|
20
|
+
* injectHotkeys([
|
|
21
|
+
* { hotkey: 'Mod+S', callback: () => this.save() },
|
|
22
|
+
* { hotkey: ['G', 'G'], callback: () => this.scrollToTop(), options: { timeout: 500 } },
|
|
23
|
+
* ])
|
|
60
24
|
* ```
|
|
61
25
|
*/
|
|
62
|
-
export declare function injectHotkeys(hotkeys:
|
|
26
|
+
export declare function injectHotkeys(hotkeys: ReadonlyArray<InjectHotkeyDefinition> | (() => ReadonlyArray<InjectHotkeyDefinition>), commonOptions?: InjectHotkeyCommonOptions | (() => InjectHotkeyCommonOptions)): void;
|
|
63
27
|
//#endregion
|
package/dist/injectHotkeys.js
CHANGED
|
@@ -1,118 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { injectHotkeysContext } from "./hotkeys-provider.js";
|
|
2
|
+
import { DestroyRef, effect, inject, untracked } from "@angular/core";
|
|
3
|
+
import { createHotkeyBindings, prepareHotkeyDefinitions } from "@tanstack/hotkeys/adapter";
|
|
4
4
|
|
|
5
5
|
//#region src/injectHotkeys.ts
|
|
6
6
|
/**
|
|
7
|
-
* Angular inject-based API for registering
|
|
7
|
+
* Angular inject-based API for registering several hotkeys at once: chords,
|
|
8
|
+
* sequences, and app hotkeys. Pass a getter for a reactive list.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Uses effect() to track reactive dependencies and update registrations
|
|
14
|
-
* when options or the callback change.
|
|
15
|
-
*
|
|
16
|
-
* Options are merged in this order:
|
|
17
|
-
* provideHotkeys defaults < commonOptions < per-definition options
|
|
18
|
-
*
|
|
19
|
-
* @param hotkeys - Array of hotkey definitions, or getter returning them
|
|
20
|
-
* @param commonOptions - Shared options for all hotkeys, or getter
|
|
10
|
+
* Options merge in this order: provider defaults, app hotkey declaration,
|
|
11
|
+
* commonOptions, per-hotkey options. Each option applies only to the kinds
|
|
12
|
+
* that support it. Unbound app hotkeys and empty sequences register nothing.
|
|
13
|
+
* A `null` target skips registration until it is set.
|
|
21
14
|
*
|
|
22
15
|
* @example
|
|
23
16
|
* ```ts
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* { hotkey: 'Mod+S', callback: () => this.save() },
|
|
29
|
-
* { hotkey: 'Mod+Z', callback: () => this.undo() },
|
|
30
|
-
* { hotkey: 'Escape', callback: () => this.close() },
|
|
31
|
-
* ])
|
|
32
|
-
* }
|
|
33
|
-
* }
|
|
34
|
-
* ```
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* ```ts
|
|
38
|
-
* @Component({ ... })
|
|
39
|
-
* export class DynamicShortcuts {
|
|
40
|
-
* shortcuts = signal([...])
|
|
41
|
-
*
|
|
42
|
-
* constructor() {
|
|
43
|
-
* injectHotkeys(
|
|
44
|
-
* () => this.shortcuts().map((s) => ({
|
|
45
|
-
* hotkey: s.key,
|
|
46
|
-
* callback: s.action,
|
|
47
|
-
* })),
|
|
48
|
-
* )
|
|
49
|
-
* }
|
|
50
|
-
* }
|
|
17
|
+
* injectHotkeys([
|
|
18
|
+
* { hotkey: 'Mod+S', callback: () => this.save() },
|
|
19
|
+
* { hotkey: ['G', 'G'], callback: () => this.scrollToTop(), options: { timeout: 500 } },
|
|
20
|
+
* ])
|
|
51
21
|
* ```
|
|
52
22
|
*/
|
|
53
23
|
function injectHotkeys(hotkeys, commonOptions = {}) {
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
const registrations = /* @__PURE__ */ new Map();
|
|
58
|
-
destroyRef.onDestroy(() => {
|
|
59
|
-
for (const { handle } of registrations.values()) if (handle.isActive) handle.unregister();
|
|
60
|
-
registrations.clear();
|
|
61
|
-
});
|
|
24
|
+
const context = injectHotkeysContext();
|
|
25
|
+
const bindings = createHotkeyBindings();
|
|
26
|
+
inject(DestroyRef).onDestroy(() => bindings.destroy());
|
|
62
27
|
effect(() => {
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const mergedOptions = {
|
|
72
|
-
...defaultOptions.hotkey,
|
|
73
|
-
...resolvedCommonOptions,
|
|
74
|
-
...resolvedDefOptions
|
|
75
|
-
};
|
|
76
|
-
const platform = mergedOptions.platform ?? detectPlatform();
|
|
77
|
-
const hotkeyString = normalizeRegisterableHotkey(resolvedHotkey, platform);
|
|
78
|
-
const resolvedTarget = "target" in mergedOptions ? mergedOptions.target ?? null : typeof document !== "undefined" ? document : null;
|
|
79
|
-
if (!resolvedTarget) continue;
|
|
80
|
-
const registrationKey = `${i}:${hotkeyString}`;
|
|
81
|
-
nextKeys.add(registrationKey);
|
|
82
|
-
prepared.push({
|
|
83
|
-
registrationKey,
|
|
84
|
-
def,
|
|
85
|
-
mergedOptions,
|
|
86
|
-
hotkeyString,
|
|
87
|
-
resolvedTarget
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
for (const [key, record] of [...registrations.entries()]) if (!nextKeys.has(key)) {
|
|
91
|
-
if (record.handle.isActive) record.handle.unregister();
|
|
92
|
-
registrations.delete(key);
|
|
93
|
-
}
|
|
94
|
-
for (const p of prepared) {
|
|
95
|
-
const existing = registrations.get(p.registrationKey);
|
|
96
|
-
if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
|
|
97
|
-
existing.handle.callback = p.def.callback;
|
|
98
|
-
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions;
|
|
99
|
-
existing.handle.setOptions(optionsWithoutTarget);
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (existing) {
|
|
103
|
-
if (existing.handle.isActive) existing.handle.unregister();
|
|
104
|
-
registrations.delete(p.registrationKey);
|
|
105
|
-
}
|
|
106
|
-
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions;
|
|
107
|
-
const handle = manager.register(p.hotkeyString, p.def.callback, {
|
|
108
|
-
...optionsWithoutTarget,
|
|
109
|
-
target: p.resolvedTarget
|
|
110
|
-
});
|
|
111
|
-
registrations.set(p.registrationKey, {
|
|
112
|
-
handle,
|
|
113
|
-
target: p.resolvedTarget
|
|
114
|
-
});
|
|
115
|
-
}
|
|
28
|
+
context.appHotkeysState();
|
|
29
|
+
const definitions = prepareHotkeyDefinitions(typeof hotkeys === "function" ? hotkeys() : hotkeys, {
|
|
30
|
+
appHotkeysStore: context.appHotkeysStore,
|
|
31
|
+
defaults: context.defaultOptions.hotkey,
|
|
32
|
+
commonOptions: typeof commonOptions === "function" ? commonOptions() : commonOptions,
|
|
33
|
+
resolveTarget: (target) => target
|
|
34
|
+
});
|
|
35
|
+
untracked(() => bindings.update(definitions));
|
|
116
36
|
});
|
|
117
37
|
}
|
|
118
38
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/angular-hotkeys",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Angular adapter for TanStack Hotkeys",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@tanstack/angular-store": "^0.11.2",
|
|
39
|
-
"@tanstack/hotkeys": "0.
|
|
39
|
+
"@tanstack/hotkeys": "0.12.1"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@angular/common": ">=19.0.0",
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { HotkeyCallback, HotkeySequence, SequenceOptions } from "@tanstack/hotkeys";
|
|
2
|
-
//#region src/injectHotkeySequence.d.ts
|
|
3
|
-
export interface InjectHotkeySequenceOptions extends Omit<SequenceOptions, 'enabled' | 'target'> {
|
|
4
|
-
/** Whether the sequence is enabled. Defaults to true. */
|
|
5
|
-
enabled?: boolean;
|
|
6
|
-
/**
|
|
7
|
-
* The DOM element to attach the event listener to.
|
|
8
|
-
* Can be a direct DOM element, an accessor target, or null.
|
|
9
|
-
* Defaults to document when omitted.
|
|
10
|
-
*/
|
|
11
|
-
target?: HTMLElement | Document | Window | null;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Angular inject-based API for registering a keyboard shortcut sequence (Vim-style).
|
|
15
|
-
*
|
|
16
|
-
* Allows you to register multi-key sequences like 'g g' or 'd d' that trigger
|
|
17
|
-
* when the full sequence is pressed within a timeout.
|
|
18
|
-
*
|
|
19
|
-
* Each step may include modifiers. You can chain the same modifier across
|
|
20
|
-
* steps (e.g. `Shift+R` then `Shift+T`). Modifier-only keydown events (Shift,
|
|
21
|
-
* Control, Alt, or Meta pressed alone) are ignored while matching—they do not
|
|
22
|
-
* advance the sequence or reset progress.
|
|
23
|
-
*
|
|
24
|
-
* @param sequence - Array of hotkey strings that form the sequence (or getter function)
|
|
25
|
-
* @param callback - Function to call when the sequence is completed
|
|
26
|
-
* @param options - Options for the sequence behavior (or getter function). `enabled: false` still registers
|
|
27
|
-
* the sequence (visible in devtools); only execution is suppressed.
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```ts
|
|
31
|
-
* @Component({ ... })
|
|
32
|
-
* export class VimEditorComponent {
|
|
33
|
-
* lastSequence = signal<string | null>(null)
|
|
34
|
-
*
|
|
35
|
-
* constructor() {
|
|
36
|
-
* injectHotkeySequence(['G', 'G'], () => this.lastSequence.set('gg → Go to top'))
|
|
37
|
-
* injectHotkeySequence(['D', 'D'], () => this.lastSequence.set('dd → Delete line'))
|
|
38
|
-
* injectHotkeySequence(['C', 'I', 'W'], () => this.lastSequence.set('ciw'), { timeout: 500 })
|
|
39
|
-
* injectHotkeySequence(['Shift+R', 'Shift+T'], () => this.lastSequence.set('⇧R⇧T'))
|
|
40
|
-
* }
|
|
41
|
-
* }
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export declare function injectHotkeySequence(sequence: HotkeySequence | (() => HotkeySequence), callback: HotkeyCallback, options?: InjectHotkeySequenceOptions | (() => InjectHotkeySequenceOptions)): void;
|
|
45
|
-
//#endregion
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { injectDefaultHotkeysOptions } from "./hotkeys-provider.js";
|
|
2
|
-
import { formatHotkeySequence, getSequenceManager } from "@tanstack/hotkeys";
|
|
3
|
-
import { DestroyRef, effect, inject } from "@angular/core";
|
|
4
|
-
|
|
5
|
-
//#region src/injectHotkeySequence.ts
|
|
6
|
-
/**
|
|
7
|
-
* Angular inject-based API for registering a keyboard shortcut sequence (Vim-style).
|
|
8
|
-
*
|
|
9
|
-
* Allows you to register multi-key sequences like 'g g' or 'd d' that trigger
|
|
10
|
-
* when the full sequence is pressed within a timeout.
|
|
11
|
-
*
|
|
12
|
-
* Each step may include modifiers. You can chain the same modifier across
|
|
13
|
-
* steps (e.g. `Shift+R` then `Shift+T`). Modifier-only keydown events (Shift,
|
|
14
|
-
* Control, Alt, or Meta pressed alone) are ignored while matching—they do not
|
|
15
|
-
* advance the sequence or reset progress.
|
|
16
|
-
*
|
|
17
|
-
* @param sequence - Array of hotkey strings that form the sequence (or getter function)
|
|
18
|
-
* @param callback - Function to call when the sequence is completed
|
|
19
|
-
* @param options - Options for the sequence behavior (or getter function). `enabled: false` still registers
|
|
20
|
-
* the sequence (visible in devtools); only execution is suppressed.
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```ts
|
|
24
|
-
* @Component({ ... })
|
|
25
|
-
* export class VimEditorComponent {
|
|
26
|
-
* lastSequence = signal<string | null>(null)
|
|
27
|
-
*
|
|
28
|
-
* constructor() {
|
|
29
|
-
* injectHotkeySequence(['G', 'G'], () => this.lastSequence.set('gg → Go to top'))
|
|
30
|
-
* injectHotkeySequence(['D', 'D'], () => this.lastSequence.set('dd → Delete line'))
|
|
31
|
-
* injectHotkeySequence(['C', 'I', 'W'], () => this.lastSequence.set('ciw'), { timeout: 500 })
|
|
32
|
-
* injectHotkeySequence(['Shift+R', 'Shift+T'], () => this.lastSequence.set('⇧R⇧T'))
|
|
33
|
-
* }
|
|
34
|
-
* }
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
function injectHotkeySequence(sequence, callback, options = {}) {
|
|
38
|
-
const defaultOptions = injectDefaultHotkeysOptions();
|
|
39
|
-
const manager = getSequenceManager();
|
|
40
|
-
const destroyRef = inject(DestroyRef);
|
|
41
|
-
let handle = null;
|
|
42
|
-
let lastSequenceKey = null;
|
|
43
|
-
let lastTarget = null;
|
|
44
|
-
destroyRef.onDestroy(() => {
|
|
45
|
-
if (handle?.isActive) {
|
|
46
|
-
handle.unregister();
|
|
47
|
-
handle = null;
|
|
48
|
-
}
|
|
49
|
-
lastSequenceKey = null;
|
|
50
|
-
lastTarget = null;
|
|
51
|
-
});
|
|
52
|
-
effect(() => {
|
|
53
|
-
const resolvedSequence = typeof sequence === "function" ? sequence() : sequence;
|
|
54
|
-
const resolvedOptions = typeof options === "function" ? options() : options;
|
|
55
|
-
const { enabled = true, ...sequenceOptions } = {
|
|
56
|
-
...defaultOptions.hotkeySequence,
|
|
57
|
-
...resolvedOptions
|
|
58
|
-
};
|
|
59
|
-
const resolvedTarget = "target" in sequenceOptions ? sequenceOptions.target ?? null : typeof document !== "undefined" ? document : null;
|
|
60
|
-
if (resolvedSequence.length === 0 || !resolvedTarget) {
|
|
61
|
-
if (handle?.isActive) {
|
|
62
|
-
handle.unregister();
|
|
63
|
-
handle = null;
|
|
64
|
-
}
|
|
65
|
-
lastSequenceKey = null;
|
|
66
|
-
lastTarget = null;
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const sequenceKey = formatHotkeySequence(resolvedSequence);
|
|
70
|
-
const registerPayload = {
|
|
71
|
-
...sequenceOptions,
|
|
72
|
-
enabled,
|
|
73
|
-
target: resolvedTarget
|
|
74
|
-
};
|
|
75
|
-
const { target: _t, ...optionsWithoutTarget } = registerPayload;
|
|
76
|
-
if (handle?.isActive && lastSequenceKey === sequenceKey && lastTarget === resolvedTarget) {
|
|
77
|
-
handle.callback = callback;
|
|
78
|
-
handle.setOptions(optionsWithoutTarget);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (handle?.isActive) {
|
|
82
|
-
handle.unregister();
|
|
83
|
-
handle = null;
|
|
84
|
-
}
|
|
85
|
-
handle = manager.register(resolvedSequence, callback, registerPayload);
|
|
86
|
-
lastSequenceKey = sequenceKey;
|
|
87
|
-
lastTarget = resolvedTarget;
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
//#endregion
|
|
92
|
-
export { injectHotkeySequence };
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { HotkeySequence, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
2
|
-
//#region src/injectHotkeySequenceRecorder.d.ts
|
|
3
|
-
export interface AngularHotkeySequenceRecorder {
|
|
4
|
-
readonly isRecording: () => boolean;
|
|
5
|
-
readonly steps: () => HotkeySequence;
|
|
6
|
-
readonly recordedSequence: () => HotkeySequence | null;
|
|
7
|
-
readonly startRecording: () => void;
|
|
8
|
-
readonly stopRecording: () => void;
|
|
9
|
-
readonly cancelRecording: () => void;
|
|
10
|
-
readonly commitRecording: () => void;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Angular inject-based API for recording multi-chord sequences.
|
|
14
|
-
*/
|
|
15
|
-
export declare function injectHotkeySequenceRecorder(options: HotkeySequenceRecorderOptions | (() => HotkeySequenceRecorderOptions)): AngularHotkeySequenceRecorder;
|
|
16
|
-
//#endregion
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { injectDefaultHotkeysOptions } from "./hotkeys-provider.js";
|
|
2
|
-
import { HotkeySequenceRecorder } from "@tanstack/hotkeys";
|
|
3
|
-
import { DestroyRef, computed, effect, inject, linkedSignal, untracked } from "@angular/core";
|
|
4
|
-
|
|
5
|
-
//#region src/injectHotkeySequenceRecorder.ts
|
|
6
|
-
/**
|
|
7
|
-
* Angular inject-based API for recording multi-chord sequences.
|
|
8
|
-
*/
|
|
9
|
-
function injectHotkeySequenceRecorder(options) {
|
|
10
|
-
const defaultOptions = injectDefaultHotkeysOptions();
|
|
11
|
-
const destroyRef = inject(DestroyRef);
|
|
12
|
-
const recorderSignal = computed(() => untracked(() => {
|
|
13
|
-
const resolvedOptions = typeof options === "function" ? options() : options;
|
|
14
|
-
const mergedOptions = {
|
|
15
|
-
...defaultOptions.hotkeySequenceRecorder,
|
|
16
|
-
...resolvedOptions
|
|
17
|
-
};
|
|
18
|
-
return new HotkeySequenceRecorder(mergedOptions);
|
|
19
|
-
}));
|
|
20
|
-
const recorderStore = computed(() => untracked(() => recorderSignal().store));
|
|
21
|
-
const isRecording = injectLazyStore(recorderStore, (state) => state.isRecording);
|
|
22
|
-
const steps = injectLazyStore(recorderStore, (state) => state.steps);
|
|
23
|
-
const recordedSequence = injectLazyStore(recorderStore, (state) => state.recordedSequence);
|
|
24
|
-
effect(() => {
|
|
25
|
-
const resolved = typeof options === "function" ? options() : options;
|
|
26
|
-
recorderSignal().setOptions({
|
|
27
|
-
...defaultOptions.hotkeySequenceRecorder,
|
|
28
|
-
...resolved
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
destroyRef.onDestroy(() => {
|
|
32
|
-
recorderSignal().destroy();
|
|
33
|
-
});
|
|
34
|
-
return {
|
|
35
|
-
isRecording,
|
|
36
|
-
steps,
|
|
37
|
-
recordedSequence,
|
|
38
|
-
startRecording: () => recorderSignal().start(),
|
|
39
|
-
stopRecording: () => recorderSignal().stop(),
|
|
40
|
-
cancelRecording: () => recorderSignal().cancel(),
|
|
41
|
-
commitRecording: () => recorderSignal().commit()
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function injectLazyStore(storeSignal, selector) {
|
|
45
|
-
const slice = linkedSignal(() => selector(storeSignal().get()));
|
|
46
|
-
effect((onCleanup) => {
|
|
47
|
-
const currentStore = storeSignal();
|
|
48
|
-
slice.set(selector(currentStore.get()));
|
|
49
|
-
const { unsubscribe } = currentStore.subscribe((s) => {
|
|
50
|
-
slice.set(selector(s));
|
|
51
|
-
});
|
|
52
|
-
onCleanup(() => unsubscribe());
|
|
53
|
-
});
|
|
54
|
-
return slice.asReadonly();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
//#endregion
|
|
58
|
-
export { injectHotkeySequenceRecorder };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { InjectHotkeySequenceOptions } from "./injectHotkeySequence.js";
|
|
2
|
-
import { HotkeyCallback, HotkeySequence } from "@tanstack/hotkeys";
|
|
3
|
-
//#region src/injectHotkeySequences.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* A single sequence definition for use with `injectHotkeySequences`.
|
|
6
|
-
*/
|
|
7
|
-
export interface InjectHotkeySequenceDefinition {
|
|
8
|
-
/** Array of hotkey strings that form the sequence */
|
|
9
|
-
sequence: HotkeySequence | (() => HotkeySequence);
|
|
10
|
-
/** The function to call when the sequence is completed */
|
|
11
|
-
callback: HotkeyCallback;
|
|
12
|
-
/** Per-sequence options (merged on top of commonOptions) */
|
|
13
|
-
options?: InjectHotkeySequenceOptions | (() => InjectHotkeySequenceOptions);
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Angular inject-based API for registering multiple keyboard shortcut sequences at once (Vim-style).
|
|
17
|
-
*
|
|
18
|
-
* Uses the singleton SequenceManager. Call in an injection context (e.g. constructor).
|
|
19
|
-
* Uses `effect()` to track reactive dependencies when definitions or options are getters.
|
|
20
|
-
*
|
|
21
|
-
* Options are merged in this order:
|
|
22
|
-
* provideHotkeys defaults < commonOptions < per-definition options
|
|
23
|
-
*
|
|
24
|
-
* Definitions with an empty `sequence` are skipped. Disabled sequences (`enabled: false`)
|
|
25
|
-
* remain registered so they stay visible in devtools; the core manager suppresses execution.
|
|
26
|
-
*
|
|
27
|
-
* @param sequences - Array of sequence definitions, or getter returning them
|
|
28
|
-
* @param commonOptions - Shared options for all sequences, or getter
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```ts
|
|
32
|
-
* @Component({ ... })
|
|
33
|
-
* export class VimComponent {
|
|
34
|
-
* constructor() {
|
|
35
|
-
* injectHotkeySequences([
|
|
36
|
-
* { sequence: ['G', 'G'], callback: () => this.goTop() },
|
|
37
|
-
* { sequence: ['D', 'D'], callback: () => this.deleteLine() },
|
|
38
|
-
* ])
|
|
39
|
-
* }
|
|
40
|
-
* }
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
export declare function injectHotkeySequences(sequences: Array<InjectHotkeySequenceDefinition> | (() => Array<InjectHotkeySequenceDefinition>), commonOptions?: InjectHotkeySequenceOptions | (() => InjectHotkeySequenceOptions)): void;
|
|
44
|
-
//#endregion
|