@tanstack/angular-hotkeys 0.13.0 → 0.14.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 +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
|
@@ -1,19 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { InjectionToken, StaticProvider } from "@angular/core";
|
|
1
|
+
import { InjectHotkeyCommonOptions } from "./injectHotkey.js";
|
|
2
|
+
import { AppHotkeys, AppHotkeysState, AppHotkeysStore, CustomHotkeys, HotkeyChordRecorderOptions, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
3
|
+
import { InjectionToken, Provider, Signal } from "@angular/core";
|
|
5
4
|
//#region src/hotkeys-provider.d.ts
|
|
6
5
|
export interface HotkeysProviderOptions {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Defaults for every hotkey registered by `injectHotkey` and `injectHotkeys`.
|
|
8
|
+
* `requireReset` applies to chords and `timeout` applies to sequences.
|
|
9
|
+
*/
|
|
10
|
+
hotkey?: InjectHotkeyCommonOptions;
|
|
11
|
+
/** Defaults for every `injectHotkeyRecorder`. */
|
|
12
|
+
hotkeyRecorder?: Partial<Omit<HotkeyChordRecorderOptions & HotkeySequenceRecorderOptions, 'maxSteps' | 'onRecord' | 'validate'>>;
|
|
13
|
+
}
|
|
14
|
+
/** Options for `provideHotkeys`. */
|
|
15
|
+
export interface ProvideHotkeysOptions {
|
|
16
|
+
defaultOptions?: HotkeysProviderOptions;
|
|
17
|
+
/**
|
|
18
|
+
* Every app hotkey, from `defineAppHotkeys`. Listed by
|
|
19
|
+
* `injectAppHotkeyList` whether or not they are registered.
|
|
20
|
+
*/
|
|
21
|
+
appHotkeys?: AppHotkeys;
|
|
22
|
+
/**
|
|
23
|
+
* Starting custom hotkeys, such as saved data. Read once; the provider owns
|
|
24
|
+
* the state afterwards. Invalid entries are ignored with a warning.
|
|
25
|
+
*/
|
|
26
|
+
customHotkeys?: CustomHotkeys | null;
|
|
27
|
+
/** Called with the custom hotkeys after every change, ready to save. */
|
|
28
|
+
onCustomHotkeysChange?: (customHotkeys: CustomHotkeys) => void;
|
|
11
29
|
}
|
|
12
30
|
export interface HotkeysContextValue {
|
|
13
31
|
readonly defaultOptions: HotkeysProviderOptions;
|
|
32
|
+
readonly appHotkeysStore: AppHotkeysStore | null;
|
|
33
|
+
/** Tracks app hotkeys and custom hotkeys so signals and effects re-run. */
|
|
34
|
+
readonly appHotkeysState: Signal<AppHotkeysState | null>;
|
|
14
35
|
}
|
|
15
36
|
export declare const HOTKEYS_INJECTION_TOKEN: InjectionToken<HotkeysContextValue>;
|
|
16
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Provides default options, app hotkeys, and the user's custom hotkeys to an
|
|
39
|
+
* injector, such as the application or a component.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* bootstrapApplication(App, {
|
|
44
|
+
* providers: [
|
|
45
|
+
* provideHotkeys({
|
|
46
|
+
* appHotkeys,
|
|
47
|
+
* customHotkeys: JSON.parse(localStorage.getItem('hotkeys') ?? '{}'),
|
|
48
|
+
* onCustomHotkeysChange: (custom) =>
|
|
49
|
+
* localStorage.setItem('hotkeys', JSON.stringify(custom)),
|
|
50
|
+
* }),
|
|
51
|
+
* ],
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function provideHotkeys(options?: ProvideHotkeysOptions): Provider;
|
|
17
56
|
export declare function injectHotkeysContext(): HotkeysContextValue;
|
|
18
57
|
export declare function injectDefaultHotkeysOptions(): HotkeysProviderOptions;
|
|
19
58
|
//#endregion
|
package/dist/hotkeys-provider.js
CHANGED
|
@@ -1,14 +1,59 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AppHotkeysStore, publishAppHotkeysStore } from "@tanstack/hotkeys";
|
|
2
|
+
import { DestroyRef, InjectionToken, inject, signal } from "@angular/core";
|
|
2
3
|
|
|
3
4
|
//#region src/hotkeys-provider.ts
|
|
5
|
+
const NO_APP_HOTKEYS = signal(null).asReadonly();
|
|
4
6
|
const HOTKEYS_INJECTION_TOKEN = new InjectionToken("HOTKEYS_INJECTION_TOKEN", {
|
|
5
7
|
providedIn: "root",
|
|
6
|
-
factory: () => ({
|
|
8
|
+
factory: () => ({
|
|
9
|
+
defaultOptions: {},
|
|
10
|
+
appHotkeysStore: null,
|
|
11
|
+
appHotkeysState: NO_APP_HOTKEYS
|
|
12
|
+
})
|
|
7
13
|
});
|
|
8
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Provides default options, app hotkeys, and the user's custom hotkeys to an
|
|
16
|
+
* injector, such as the application or a component.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* bootstrapApplication(App, {
|
|
21
|
+
* providers: [
|
|
22
|
+
* provideHotkeys({
|
|
23
|
+
* appHotkeys,
|
|
24
|
+
* customHotkeys: JSON.parse(localStorage.getItem('hotkeys') ?? '{}'),
|
|
25
|
+
* onCustomHotkeysChange: (custom) =>
|
|
26
|
+
* localStorage.setItem('hotkeys', JSON.stringify(custom)),
|
|
27
|
+
* }),
|
|
28
|
+
* ],
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function provideHotkeys(options = {}) {
|
|
9
33
|
return {
|
|
10
34
|
provide: HOTKEYS_INJECTION_TOKEN,
|
|
11
|
-
|
|
35
|
+
useFactory: () => {
|
|
36
|
+
const defaultOptions = options.defaultOptions ?? {};
|
|
37
|
+
if (!options.appHotkeys) return {
|
|
38
|
+
defaultOptions,
|
|
39
|
+
appHotkeysStore: null,
|
|
40
|
+
appHotkeysState: NO_APP_HOTKEYS
|
|
41
|
+
};
|
|
42
|
+
const store = new AppHotkeysStore(options.appHotkeys, {
|
|
43
|
+
customHotkeys: options.customHotkeys,
|
|
44
|
+
onCustomHotkeysChange: options.onCustomHotkeysChange
|
|
45
|
+
});
|
|
46
|
+
const state = signal(store.store.state);
|
|
47
|
+
const subscription = store.store.subscribe(() => state.set(store.store.state));
|
|
48
|
+
const destroyRef = inject(DestroyRef);
|
|
49
|
+
destroyRef.onDestroy(() => subscription.unsubscribe());
|
|
50
|
+
if (typeof document !== "undefined") destroyRef.onDestroy(publishAppHotkeysStore(store));
|
|
51
|
+
return {
|
|
52
|
+
defaultOptions,
|
|
53
|
+
appHotkeysStore: store,
|
|
54
|
+
appHotkeysState: state.asReadonly()
|
|
55
|
+
};
|
|
56
|
+
}
|
|
12
57
|
};
|
|
13
58
|
}
|
|
14
59
|
function injectHotkeysContext() {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { InjectHotkeyOptions, injectHotkey } from "./injectHotkey.js";
|
|
2
|
-
import {
|
|
3
|
-
import { HOTKEYS_INJECTION_TOKEN, HotkeysContextValue, HotkeysProviderOptions, injectDefaultHotkeysOptions, injectHotkeysContext, provideHotkeys } from "./hotkeys-provider.js";
|
|
1
|
+
import { InjectAppHotkeyOptions, InjectHotkeyChordOptions, InjectHotkeyCommonOptions, InjectHotkeyOptions, InjectHotkeyOptionsFor, InjectHotkeySequenceOptions, InjectHotkeyTarget, injectHotkey } from "./injectHotkey.js";
|
|
2
|
+
import { HOTKEYS_INJECTION_TOKEN, HotkeysContextValue, HotkeysProviderOptions, ProvideHotkeysOptions, injectDefaultHotkeysOptions, injectHotkeysContext, provideHotkeys } from "./hotkeys-provider.js";
|
|
4
3
|
import { InjectHotkeyDefinition, injectHotkeys } from "./injectHotkeys.js";
|
|
5
|
-
import { InjectHotkeySequenceDefinition, injectHotkeySequences } from "./injectHotkeySequences.js";
|
|
6
4
|
import { injectHeldKeys } from "./injectHeldKeys.js";
|
|
7
5
|
import { injectHeldKeyCodes } from "./injectHeldKeyCodes.js";
|
|
8
6
|
import { injectKeyHold } from "./injectKeyHold.js";
|
|
9
7
|
import { AngularHotkeyRecorder, injectHotkeyRecorder } from "./injectHotkeyRecorder.js";
|
|
10
|
-
import {
|
|
11
|
-
import { AngularHotkeySequenceRecorder, injectHotkeySequenceRecorder } from "./injectHotkeySequenceRecorder.js";
|
|
8
|
+
import { injectHotkeyRegistrations } from "./injectHotkeyRegistrations.js";
|
|
12
9
|
import { injectHotkeyHint } from "./injectHotkeyHint.js";
|
|
10
|
+
import { InjectCustomHotkeysResult, injectAppHotkeyList, injectAppHotkeys, injectCustomHotkeys } from "./injectAppHotkeys.js";
|
|
13
11
|
export * from "@tanstack/hotkeys";
|
|
14
|
-
export { AngularHotkeyRecorder,
|
|
12
|
+
export { AngularHotkeyRecorder, HOTKEYS_INJECTION_TOKEN, HotkeysContextValue, HotkeysProviderOptions, InjectAppHotkeyOptions, InjectCustomHotkeysResult, InjectHotkeyChordOptions, InjectHotkeyCommonOptions, InjectHotkeyDefinition, InjectHotkeyOptions, InjectHotkeyOptionsFor, InjectHotkeySequenceOptions, InjectHotkeyTarget, ProvideHotkeysOptions, injectAppHotkeyList, injectAppHotkeys, injectCustomHotkeys, injectDefaultHotkeysOptions, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyHint, injectHotkeyRecorder, injectHotkeyRegistrations, injectHotkeys, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { HOTKEYS_INJECTION_TOKEN, injectDefaultHotkeysOptions, injectHotkeysContext, provideHotkeys } from "./hotkeys-provider.js";
|
|
2
|
-
import { injectHotkey } from "./injectHotkey.js";
|
|
3
2
|
import { injectHotkeys } from "./injectHotkeys.js";
|
|
4
|
-
import {
|
|
5
|
-
import { injectHotkeySequences } from "./injectHotkeySequences.js";
|
|
3
|
+
import { injectHotkey } from "./injectHotkey.js";
|
|
6
4
|
import { injectHeldKeys } from "./injectHeldKeys.js";
|
|
7
5
|
import { injectHeldKeyCodes } from "./injectHeldKeyCodes.js";
|
|
8
6
|
import { injectKeyHold } from "./injectKeyHold.js";
|
|
9
7
|
import { injectHotkeyRecorder } from "./injectHotkeyRecorder.js";
|
|
10
8
|
import { injectHotkeyRegistrations } from "./injectHotkeyRegistrations.js";
|
|
11
|
-
import { injectHotkeySequenceRecorder } from "./injectHotkeySequenceRecorder.js";
|
|
12
9
|
import { injectHotkeyHint } from "./injectHotkeyHint.js";
|
|
10
|
+
import { injectAppHotkeyList, injectAppHotkeys, injectCustomHotkeys } from "./injectAppHotkeys.js";
|
|
13
11
|
|
|
14
12
|
export * from "@tanstack/hotkeys"
|
|
15
13
|
|
|
16
|
-
export { HOTKEYS_INJECTION_TOKEN, injectDefaultHotkeysOptions, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyHint, injectHotkeyRecorder, injectHotkeyRegistrations,
|
|
14
|
+
export { HOTKEYS_INJECTION_TOKEN, injectAppHotkeyList, injectAppHotkeys, injectCustomHotkeys, injectDefaultHotkeysOptions, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyHint, injectHotkeyRecorder, injectHotkeyRegistrations, injectHotkeys, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { AppHotkey, AppHotkeyListItem, CustomHotkeys, Hotkey, RegisteredAppHotkeys } from "@tanstack/hotkeys";
|
|
2
|
+
import { Signal } from "@angular/core";
|
|
3
|
+
//#region src/injectAppHotkeys.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Returns a signal of the effective app hotkeys from `provideHotkeys`, typed
|
|
6
|
+
* by `Register`: each declaration with the user's custom hotkey applied.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* hotkeys = injectAppHotkeys()
|
|
11
|
+
* constructor() {
|
|
12
|
+
* injectHotkey(() => this.hotkeys()['doc.save'], () => this.save())
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function injectAppHotkeys(): Signal<RegisteredAppHotkeys>;
|
|
17
|
+
/**
|
|
18
|
+
* Lists every app hotkey with its live status, in declaration order, for
|
|
19
|
+
* cheat sheets and settings screens. App hotkeys whose components have not
|
|
20
|
+
* mounted are `'inactive'`; those without a hotkey are `'unbound'`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function injectAppHotkeyList(): Signal<Array<AppHotkeyListItem<RegisteredAppHotkeys[keyof RegisteredAppHotkeys]>>>;
|
|
23
|
+
/** Custom hotkey state and actions returned by `injectCustomHotkeys`. */
|
|
24
|
+
export interface InjectCustomHotkeysResult {
|
|
25
|
+
/** The user's hotkeys that differ from the defaults. */
|
|
26
|
+
readonly customHotkeys: Signal<CustomHotkeys>;
|
|
27
|
+
/**
|
|
28
|
+
* Sets the user's hotkey for an app hotkey. `null` unbinds it.
|
|
29
|
+
* Returns conflicting app hotkeys; when any exist, nothing changes.
|
|
30
|
+
*/
|
|
31
|
+
readonly setCustomHotkey: (id: keyof RegisteredAppHotkeys & string, hotkey: Hotkey | null) => Array<AppHotkey>;
|
|
32
|
+
/** Restores the default hotkey for an app hotkey. */
|
|
33
|
+
readonly resetCustomHotkey: (id: keyof RegisteredAppHotkeys & string) => void;
|
|
34
|
+
/** Restores every default hotkey. */
|
|
35
|
+
readonly resetCustomHotkeys: () => void;
|
|
36
|
+
/** Other app hotkeys that would collide if `id` used `hotkey`. */
|
|
37
|
+
readonly findConflicts: (id: keyof RegisteredAppHotkeys & string, hotkey: Hotkey) => Array<AppHotkey>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Reads and changes the user's custom hotkeys. Changes apply to every
|
|
41
|
+
* registered app hotkey immediately and are passed to `onCustomHotkeysChange`
|
|
42
|
+
* from `provideHotkeys`, ready to save.
|
|
43
|
+
*/
|
|
44
|
+
export declare function injectCustomHotkeys(): InjectCustomHotkeysResult;
|
|
45
|
+
//#endregion
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { injectHotkeysContext } from "./hotkeys-provider.js";
|
|
2
|
+
import { injectHotkeyRegistrations } from "./injectHotkeyRegistrations.js";
|
|
3
|
+
import { computed } from "@angular/core";
|
|
4
|
+
|
|
5
|
+
//#region src/injectAppHotkeys.ts
|
|
6
|
+
function injectRequiredStore(name) {
|
|
7
|
+
const context = injectHotkeysContext();
|
|
8
|
+
if (!context.appHotkeysStore) throw new Error(`${name} requires provideHotkeys with appHotkeys`);
|
|
9
|
+
return {
|
|
10
|
+
store: context.appHotkeysStore,
|
|
11
|
+
state: context.appHotkeysState
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns a signal of the effective app hotkeys from `provideHotkeys`, typed
|
|
16
|
+
* by `Register`: each declaration with the user's custom hotkey applied.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* hotkeys = injectAppHotkeys()
|
|
21
|
+
* constructor() {
|
|
22
|
+
* injectHotkey(() => this.hotkeys()['doc.save'], () => this.save())
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function injectAppHotkeys() {
|
|
27
|
+
const { store, state } = injectRequiredStore("injectAppHotkeys");
|
|
28
|
+
return computed(() => {
|
|
29
|
+
state();
|
|
30
|
+
return store.appHotkeys;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Lists every app hotkey with its live status, in declaration order, for
|
|
35
|
+
* cheat sheets and settings screens. App hotkeys whose components have not
|
|
36
|
+
* mounted are `'inactive'`; those without a hotkey are `'unbound'`.
|
|
37
|
+
*/
|
|
38
|
+
function injectAppHotkeyList() {
|
|
39
|
+
const { store, state } = injectRequiredStore("injectAppHotkeyList");
|
|
40
|
+
const registrations = injectHotkeyRegistrations();
|
|
41
|
+
return computed(() => {
|
|
42
|
+
state();
|
|
43
|
+
return store.getList(registrations());
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Reads and changes the user's custom hotkeys. Changes apply to every
|
|
48
|
+
* registered app hotkey immediately and are passed to `onCustomHotkeysChange`
|
|
49
|
+
* from `provideHotkeys`, ready to save.
|
|
50
|
+
*/
|
|
51
|
+
function injectCustomHotkeys() {
|
|
52
|
+
const { store, state } = injectRequiredStore("injectCustomHotkeys");
|
|
53
|
+
return {
|
|
54
|
+
customHotkeys: computed(() => {
|
|
55
|
+
state();
|
|
56
|
+
return store.customHotkeys;
|
|
57
|
+
}),
|
|
58
|
+
setCustomHotkey: (id, hotkey) => store.setCustomHotkey(id, hotkey),
|
|
59
|
+
resetCustomHotkey: (id) => store.resetCustomHotkey(id),
|
|
60
|
+
resetCustomHotkeys: () => store.resetCustomHotkeys(),
|
|
61
|
+
findConflicts: (id, hotkey) => store.findConflicts(id, hotkey)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { injectAppHotkeyList, injectAppHotkeys, injectCustomHotkeys };
|
package/dist/injectHotkey.d.ts
CHANGED
|
@@ -1,72 +1,47 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AppHotkey, HotkeyCallback, RegisterableHotkey, Target } from "@tanstack/hotkeys";
|
|
2
|
+
import { AdapterAppHotkeyOptions, AdapterChordOptions, AdapterCommonOptions, AdapterHotkeyOptions, AdapterOptionsFor, AdapterSequenceOptions } from "@tanstack/hotkeys/adapter";
|
|
2
3
|
//#region src/injectHotkey.d.ts
|
|
3
|
-
export interface InjectHotkeyOptions extends Omit<HotkeyOptions, 'target'> {
|
|
4
|
-
/**
|
|
5
|
-
* The DOM element to attach the event listener to.
|
|
6
|
-
* Can be a direct DOM element, an accessor (for reactive targets that become
|
|
7
|
-
* available after mount), or null. Defaults to document.
|
|
8
|
-
* When using scoped targets, pass an accessor: () => ({ target: elementSignal() })
|
|
9
|
-
* so the hotkey waits for the element to be attached before registering.
|
|
10
|
-
*/
|
|
11
|
-
target?: HTMLElement | Document | Window | null;
|
|
12
|
-
}
|
|
13
4
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* constructor() {
|
|
37
|
-
* injectHotkey('Mod+S', (event, { hotkey }) => {
|
|
38
|
-
* event.preventDefault()
|
|
39
|
-
* this.saveCount.update(c => c + 1)
|
|
40
|
-
* })
|
|
41
|
-
* }
|
|
42
|
-
* }
|
|
43
|
-
* ```
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* @Component({ ... })
|
|
48
|
-
* export class ModalComponent {
|
|
49
|
-
* isOpen = signal(true)
|
|
5
|
+
* The element to listen on. A `null` target skips registration, so pass a
|
|
6
|
+
* getter such as `() => ({ target: this.element() })` for elements that
|
|
7
|
+
* attach after the view renders.
|
|
8
|
+
*/
|
|
9
|
+
export type InjectHotkeyTarget = Target | null;
|
|
10
|
+
/** Options shared by chords and sequences. */
|
|
11
|
+
export type InjectHotkeyOptions = AdapterHotkeyOptions<InjectHotkeyTarget>;
|
|
12
|
+
/** Options for a chord such as `'Mod+S'`. */
|
|
13
|
+
export type InjectHotkeyChordOptions = AdapterChordOptions<InjectHotkeyTarget>;
|
|
14
|
+
/** Options for a sequence such as `['G', 'G']`. */
|
|
15
|
+
export type InjectHotkeySequenceOptions = AdapterSequenceOptions<InjectHotkeyTarget>;
|
|
16
|
+
/** Options applied to every hotkey, such as provider defaults. Each applies to the kinds that support it. */
|
|
17
|
+
export type InjectHotkeyCommonOptions = AdapterCommonOptions<InjectHotkeyTarget>;
|
|
18
|
+
/** The options type for a hotkey: chord options for chords, sequence options for sequences. */
|
|
19
|
+
export type InjectHotkeyOptionsFor<T extends RegisterableHotkey> = AdapterOptionsFor<T, InjectHotkeyTarget>;
|
|
20
|
+
/** Call-site options for an app hotkey, typed by its declared default kind. */
|
|
21
|
+
export type InjectAppHotkeyOptions<T extends AppHotkey = AppHotkey> = AdapterAppHotkeyOptions<T, InjectHotkeyTarget>;
|
|
22
|
+
type MaybeGetter<T> = T | (() => T);
|
|
23
|
+
/**
|
|
24
|
+
* Angular inject-based API for registering a hotkey: a chord such as
|
|
25
|
+
* `'Mod+S'`, a sequence such as `['G', 'G']`, or an app hotkey from
|
|
26
|
+
* `defineAppHotkeys`. The hotkey and options may be getters that read signals.
|
|
50
27
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* ```
|
|
28
|
+
* Chords accept `requireReset`; sequences accept `timeout`. A one-step
|
|
29
|
+
* sequence registers as its chord. An app hotkey uses its effective hotkey
|
|
30
|
+
* from `provideHotkeys` (including the user's custom hotkey) and its declared
|
|
31
|
+
* meta and options, and registers nothing while unbound.
|
|
56
32
|
*
|
|
57
33
|
* @example
|
|
58
34
|
* ```ts
|
|
59
35
|
* @Component({ ... })
|
|
60
36
|
* export class EditorComponent {
|
|
61
|
-
* private readonly editorRef = viewChild<ElementRef<HTMLDivElement>>('editorRef')
|
|
62
|
-
*
|
|
63
37
|
* constructor() {
|
|
64
|
-
* injectHotkey('Mod+
|
|
65
|
-
*
|
|
66
|
-
* }))
|
|
38
|
+
* injectHotkey('Mod+S', () => this.save())
|
|
39
|
+
* injectHotkey(['G', 'G'], () => this.scrollToTop(), { timeout: 500 })
|
|
40
|
+
* injectHotkey('Escape', () => this.close(), () => ({ enabled: this.isOpen() }))
|
|
67
41
|
* }
|
|
68
42
|
* }
|
|
69
43
|
* ```
|
|
70
44
|
*/
|
|
71
|
-
export declare function injectHotkey(hotkey:
|
|
45
|
+
export declare function injectHotkey<T extends AppHotkey>(hotkey: MaybeGetter<T>, callback: HotkeyCallback, options?: MaybeGetter<InjectAppHotkeyOptions<T>>): void;
|
|
46
|
+
export declare function injectHotkey<T extends RegisterableHotkey>(hotkey: MaybeGetter<T>, callback: HotkeyCallback, options?: MaybeGetter<InjectHotkeyOptionsFor<T>>): void;
|
|
72
47
|
//#endregion
|
package/dist/injectHotkey.js
CHANGED
|
@@ -1,121 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { detectPlatform, getHotkeyManager, normalizeRegisterableHotkey } from "@tanstack/hotkeys";
|
|
3
|
-
import { DestroyRef, effect, inject } from "@angular/core";
|
|
1
|
+
import { injectHotkeys } from "./injectHotkeys.js";
|
|
4
2
|
|
|
5
3
|
//#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
|
-
* `enabled: false` keeps the registration (visible in devtools) and only suppresses firing; the same
|
|
17
|
-
* handle is updated instead of unregistering and re-registering when identity is unchanged.
|
|
18
|
-
*
|
|
19
|
-
* @param hotkey - The hotkey string (e.g. 'Mod+S', 'Escape') or getter function
|
|
20
|
-
* @param callback - The function to call when the hotkey is pressed
|
|
21
|
-
* @param options - Options for the hotkey behavior, or getter for reactive options
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* @Component({ ... })
|
|
26
|
-
* export class SaveButtonComponent {
|
|
27
|
-
* private readonly saveCount = signal(0)
|
|
28
|
-
*
|
|
29
|
-
* constructor() {
|
|
30
|
-
* injectHotkey('Mod+S', (event, { hotkey }) => {
|
|
31
|
-
* event.preventDefault()
|
|
32
|
-
* this.saveCount.update(c => c + 1)
|
|
33
|
-
* })
|
|
34
|
-
* }
|
|
35
|
-
* }
|
|
36
|
-
* ```
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```ts
|
|
40
|
-
* @Component({ ... })
|
|
41
|
-
* export class ModalComponent {
|
|
42
|
-
* isOpen = signal(true)
|
|
43
|
-
*
|
|
44
|
-
* constructor() {
|
|
45
|
-
* injectHotkey('Escape', () => this.close(), () => ({ enabled: this.isOpen() }))
|
|
46
|
-
* }
|
|
47
|
-
* }
|
|
48
|
-
* ```
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```ts
|
|
52
|
-
* @Component({ ... })
|
|
53
|
-
* export class EditorComponent {
|
|
54
|
-
* private readonly editorRef = viewChild<ElementRef<HTMLDivElement>>('editorRef')
|
|
55
|
-
*
|
|
56
|
-
* constructor() {
|
|
57
|
-
* injectHotkey('Mod+B', () => this.toggleBold(), () => ({
|
|
58
|
-
* target: this.editorRef()?.nativeElement ?? null,
|
|
59
|
-
* }))
|
|
60
|
-
* }
|
|
61
|
-
* }
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
4
|
function injectHotkey(hotkey, callback, options = {}) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
let lastTarget = null;
|
|
71
|
-
destroyRef.onDestroy(() => {
|
|
72
|
-
if (registration?.isActive) {
|
|
73
|
-
registration.unregister();
|
|
74
|
-
registration = null;
|
|
75
|
-
}
|
|
76
|
-
lastHotkeyString = null;
|
|
77
|
-
lastTarget = null;
|
|
78
|
-
});
|
|
79
|
-
effect(() => {
|
|
80
|
-
const resolvedHotkey = typeof hotkey === "function" ? hotkey() : hotkey;
|
|
81
|
-
const resolvedOptions = typeof options === "function" ? options() : options;
|
|
82
|
-
const mergedOptions = {
|
|
83
|
-
...defaultOptions.hotkey,
|
|
84
|
-
...resolvedOptions
|
|
85
|
-
};
|
|
86
|
-
const platform = mergedOptions.platform ?? detectPlatform();
|
|
87
|
-
const hotkeyString = normalizeRegisterableHotkey(resolvedHotkey, platform);
|
|
88
|
-
const resolvedTarget = "target" in mergedOptions ? mergedOptions.target ?? null : typeof document !== "undefined" ? document : null;
|
|
89
|
-
if (!resolvedTarget) {
|
|
90
|
-
if (registration?.isActive) {
|
|
91
|
-
registration.unregister();
|
|
92
|
-
registration = null;
|
|
93
|
-
}
|
|
94
|
-
lastHotkeyString = null;
|
|
95
|
-
lastTarget = null;
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
const { target: _target, ...optionsWithoutTarget } = mergedOptions;
|
|
99
|
-
if (registration?.isActive && lastHotkeyString === hotkeyString && lastTarget === resolvedTarget) {
|
|
100
|
-
registration.callback = callback;
|
|
101
|
-
registration.setOptions(optionsWithoutTarget);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
if (registration?.isActive) {
|
|
105
|
-
registration.unregister();
|
|
106
|
-
registration = null;
|
|
107
|
-
}
|
|
108
|
-
registration = manager.register(hotkeyString, callback, {
|
|
109
|
-
...optionsWithoutTarget,
|
|
110
|
-
target: resolvedTarget
|
|
111
|
-
});
|
|
112
|
-
if (registration.isActive) {
|
|
113
|
-
registration.callback = callback;
|
|
114
|
-
registration.setOptions(optionsWithoutTarget);
|
|
115
|
-
}
|
|
116
|
-
lastHotkeyString = hotkeyString;
|
|
117
|
-
lastTarget = resolvedTarget;
|
|
118
|
-
});
|
|
5
|
+
injectHotkeys(() => [{
|
|
6
|
+
hotkey: typeof hotkey === "function" ? hotkey() : hotkey,
|
|
7
|
+
callback,
|
|
8
|
+
options: typeof options === "function" ? options() : options
|
|
9
|
+
}]);
|
|
119
10
|
}
|
|
120
11
|
|
|
121
12
|
//#endregion
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { HeldModifierOptions, RegisterableHotkey } from "@tanstack/hotkeys";
|
|
1
|
+
import { AppHotkey, HeldModifierOptions, RegisterableHotkey } from "@tanstack/hotkeys";
|
|
2
2
|
import { Signal } from "@angular/core";
|
|
3
3
|
//#region src/injectHotkeyHint.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* Returns a
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Returns a signal indicating whether held modifiers reveal this shortcut.
|
|
6
|
+
* Pass getters to track changing hotkeys and options. A sequence reveals its
|
|
7
|
+
* hint from its first step, and an app hotkey uses its effective hotkey
|
|
8
|
+
* (false while unbound). This helper does not register a shortcut or check
|
|
9
|
+
* whether its target is focused.
|
|
10
10
|
*/
|
|
11
|
-
export declare function injectHotkeyHint(hotkey: RegisterableHotkey | (() => RegisterableHotkey), options?: HeldModifierOptions | (() => HeldModifierOptions)): Signal<boolean>;
|
|
11
|
+
export declare function injectHotkeyHint(hotkey: RegisterableHotkey | AppHotkey | (() => RegisterableHotkey | AppHotkey), options?: HeldModifierOptions | (() => HeldModifierOptions)): Signal<boolean>;
|
|
12
12
|
//#endregion
|
package/dist/injectHotkeyHint.js
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { injectHotkeysContext } from "./hotkeys-provider.js";
|
|
2
|
+
import { getKeyStateTracker, isAppHotkey, matchesHeldModifiers, resolveAppHotkey } from "@tanstack/hotkeys";
|
|
2
3
|
import { computed } from "@angular/core";
|
|
3
4
|
import { injectSelector } from "@tanstack/angular-store";
|
|
4
5
|
|
|
5
6
|
//#region src/injectHotkeyHint.ts
|
|
6
7
|
/**
|
|
7
|
-
* Returns a
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Returns a signal indicating whether held modifiers reveal this shortcut.
|
|
9
|
+
* Pass getters to track changing hotkeys and options. A sequence reveals its
|
|
10
|
+
* hint from its first step, and an app hotkey uses its effective hotkey
|
|
11
|
+
* (false while unbound). This helper does not register a shortcut or check
|
|
12
|
+
* whether its target is focused.
|
|
12
13
|
*/
|
|
13
14
|
function injectHotkeyHint(hotkey, options = {}) {
|
|
15
|
+
const context = injectHotkeysContext();
|
|
14
16
|
const held = injectSelector(getKeyStateTracker().store, (state) => state.heldKeys);
|
|
15
|
-
return computed(() =>
|
|
17
|
+
return computed(() => {
|
|
18
|
+
context.appHotkeysState();
|
|
19
|
+
const value = typeof hotkey === "function" ? hotkey() : hotkey;
|
|
20
|
+
const resolved = isAppHotkey(value) ? resolveAppHotkey(value, context.appHotkeysStore).hotkey : value;
|
|
21
|
+
return resolved !== null && matchesHeldModifiers(resolved, held(), typeof options === "function" ? options() : options);
|
|
22
|
+
});
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
//#endregion
|
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
import { Hotkey, HotkeyRecorderOptions } from "@tanstack/hotkeys";
|
|
1
|
+
import { Hotkey, HotkeyChord, HotkeyChordRecorderOptions, HotkeyRecorderOptions, HotkeySequence, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
2
2
|
//#region src/injectHotkeyRecorder.d.ts
|
|
3
|
-
export interface AngularHotkeyRecorder {
|
|
3
|
+
export interface AngularHotkeyRecorder<THotkey extends Hotkey = Hotkey> {
|
|
4
4
|
/** Whether recording is currently active */
|
|
5
5
|
readonly isRecording: () => boolean;
|
|
6
|
-
/**
|
|
7
|
-
readonly
|
|
6
|
+
/** Chords recorded so far in a multi-step recording (for live preview) */
|
|
7
|
+
readonly steps: () => HotkeySequence;
|
|
8
|
+
/** The last committed hotkey */
|
|
9
|
+
readonly recordedHotkey: () => THotkey | null;
|
|
8
10
|
/** Start recording a new hotkey */
|
|
9
11
|
readonly startRecording: () => void;
|
|
10
|
-
/** Stop recording
|
|
12
|
+
/** Stop recording without calling onCancel */
|
|
11
13
|
readonly stopRecording: () => void;
|
|
12
|
-
/** Cancel recording
|
|
14
|
+
/** Cancel recording and call onCancel */
|
|
13
15
|
readonly cancelRecording: () => void;
|
|
16
|
+
/** Commit the recorded steps now (multi-step recordings) */
|
|
17
|
+
readonly commitRecording: () => void;
|
|
14
18
|
}
|
|
15
19
|
/**
|
|
16
20
|
* Angular inject-based API for recording keyboard shortcuts.
|
|
17
21
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
22
|
+
* With the default `maxSteps: 1`, the first valid chord is recorded. With a
|
|
23
|
+
* larger `maxSteps`, each chord adds a step; the recording commits on Enter
|
|
24
|
+
* (unless `commitKeys: 'none'`), after `idleTimeoutMs`, or at `maxSteps`, and
|
|
25
|
+
* `onRecord` receives a chord (one step) or a sequence. Escape cancels;
|
|
26
|
+
* Backspace/Delete removes the last step or clears.
|
|
21
27
|
*
|
|
22
28
|
* @param options - Configuration options for the recorder (or getter)
|
|
23
29
|
* @returns Object with recording state signals and control functions
|
|
@@ -41,5 +47,9 @@ export interface AngularHotkeyRecorder {
|
|
|
41
47
|
* }
|
|
42
48
|
* ```
|
|
43
49
|
*/
|
|
44
|
-
export declare function injectHotkeyRecorder(options:
|
|
50
|
+
export declare function injectHotkeyRecorder(options: HotkeyChordRecorderOptions): AngularHotkeyRecorder<HotkeyChord>;
|
|
51
|
+
export declare function injectHotkeyRecorder(options: HotkeySequenceRecorderOptions): AngularHotkeyRecorder;
|
|
52
|
+
export declare function injectHotkeyRecorder(options: () => HotkeySequenceRecorderOptions): AngularHotkeyRecorder;
|
|
53
|
+
export declare function injectHotkeyRecorder(options: () => HotkeyChordRecorderOptions): AngularHotkeyRecorder<HotkeyChord>;
|
|
54
|
+
export declare function injectHotkeyRecorder(options: () => HotkeyRecorderOptions): AngularHotkeyRecorder;
|
|
45
55
|
//#endregion
|