@tanstack/angular-hotkeys 0.14.5 → 0.15.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/README.md +1 -1
- package/dist/hotkeys-provider.d.ts +57 -0
- package/dist/hotkeys-provider.js +93 -28
- package/dist/index.d.ts +2 -2
- package/dist/injectAppHotkeys.d.ts +2 -2
- package/dist/injectAppHotkeys.js +2 -2
- package/dist/injectHotkey.d.ts +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
# TanStack Hotkeys
|
|
52
52
|
|
|
53
53
|
> [!NOTE]
|
|
54
|
-
> TanStack Hotkeys is
|
|
54
|
+
> TanStack Hotkeys is in beta. We are actively developing the library and are open to feedback and contributions.
|
|
55
55
|
|
|
56
56
|
Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objects, a cross-platform `Mod` key, a singleton Hotkey Manager, and utilities for cheatsheet UIs—built to stay SSR-friendly.
|
|
57
57
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { InjectHotkeyCommonOptions } from "./injectHotkey.js";
|
|
2
2
|
import { AppHotkeys, AppHotkeysState, AppHotkeysStore, CustomHotkeys, HotkeyChordRecorderOptions, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
3
3
|
import { InjectionToken, Provider, Signal } from "@angular/core";
|
|
4
|
+
import { HotkeysProviderScopeProps } from "@tanstack/hotkeys/adapter";
|
|
4
5
|
//#region src/hotkeys-provider.d.ts
|
|
5
6
|
export interface HotkeysProviderOptions {
|
|
6
7
|
/**
|
|
@@ -13,7 +14,19 @@ export interface HotkeysProviderOptions {
|
|
|
13
14
|
}
|
|
14
15
|
/** Options for `provideHotkeys`. */
|
|
15
16
|
export interface ProvideHotkeysOptions {
|
|
17
|
+
/** Merged with the parent provider's defaults. */
|
|
16
18
|
defaultOptions?: HotkeysProviderOptions;
|
|
19
|
+
/**
|
|
20
|
+
* See `HotkeysProviderScopeProps['scope']`. Getters, here and for
|
|
21
|
+
* `exclusive` and `enabled`, run in the provider's injection context, so
|
|
22
|
+
* they can `inject()` the component that provides them and read its
|
|
23
|
+
* signals.
|
|
24
|
+
*/
|
|
25
|
+
scope?: MaybeGetter<HotkeysProviderScopeProps['scope']>;
|
|
26
|
+
/** See `HotkeysProviderScopeProps['exclusive']`. */
|
|
27
|
+
exclusive?: MaybeGetter<HotkeysProviderScopeProps['exclusive']>;
|
|
28
|
+
/** See `HotkeysProviderScopeProps['enabled']`. */
|
|
29
|
+
enabled?: MaybeGetter<HotkeysProviderScopeProps['enabled']>;
|
|
17
30
|
/**
|
|
18
31
|
* Every app hotkey, from `defineAppHotkeys`. Listed by
|
|
19
32
|
* `injectAppHotkeyList` whether or not they are registered.
|
|
@@ -28,16 +41,27 @@ export interface ProvideHotkeysOptions {
|
|
|
28
41
|
onCustomHotkeysChange?: (customHotkeys: CustomHotkeys) => void;
|
|
29
42
|
}
|
|
30
43
|
export interface HotkeysContextValue {
|
|
44
|
+
/** Merged with every ancestor provider's defaults, including `hotkey.scope`. */
|
|
31
45
|
readonly defaultOptions: HotkeysProviderOptions;
|
|
46
|
+
/** Whether this provider and every ancestor are enabled. */
|
|
47
|
+
readonly enabled: Signal<boolean>;
|
|
32
48
|
readonly appHotkeysStore: AppHotkeysStore | null;
|
|
33
49
|
/** Tracks app hotkeys and custom hotkeys so signals and effects re-run. */
|
|
34
50
|
readonly appHotkeysState: Signal<AppHotkeysState | null>;
|
|
35
51
|
}
|
|
36
52
|
export declare const HOTKEYS_INJECTION_TOKEN: InjectionToken<HotkeysContextValue>;
|
|
53
|
+
/** A value, or a signal or getter that returns it. */
|
|
54
|
+
export type MaybeGetter<T> = T | (() => T);
|
|
37
55
|
/**
|
|
38
56
|
* Provides default options, app hotkeys, and the user's custom hotkeys to an
|
|
39
57
|
* injector, such as the application or a component.
|
|
40
58
|
*
|
|
59
|
+
* Provide it in a component to create a hotkey scope. A nested provider
|
|
60
|
+
* inherits app hotkeys and merges default options. With `scope`, hotkeys
|
|
61
|
+
* injected in the component and its children fire only while it exists and
|
|
62
|
+
* is enabled; with `exclusive`, only they fire. The scope activates when the
|
|
63
|
+
* first hotkey in it is injected.
|
|
64
|
+
*
|
|
41
65
|
* @example
|
|
42
66
|
* ```ts
|
|
43
67
|
* bootstrapApplication(App, {
|
|
@@ -51,6 +75,39 @@ export declare const HOTKEYS_INJECTION_TOKEN: InjectionToken<HotkeysContextValue
|
|
|
51
75
|
* ],
|
|
52
76
|
* })
|
|
53
77
|
* ```
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* @Component({
|
|
82
|
+
* selector: 'app-edit-dialog',
|
|
83
|
+
* providers: [provideHotkeys({ scope: 'edit-dialog', exclusive: true })],
|
|
84
|
+
* template: `...`,
|
|
85
|
+
* })
|
|
86
|
+
* export class EditDialog {
|
|
87
|
+
* constructor() {
|
|
88
|
+
* injectHotkey('Escape', () => this.close())
|
|
89
|
+
* }
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* // A scope that follows the providing component's input.
|
|
96
|
+
* @Component({
|
|
97
|
+
* selector: 'app-folder',
|
|
98
|
+
* providers: [
|
|
99
|
+
* provideHotkeys({
|
|
100
|
+
* scope: () => inject(Folder).name(),
|
|
101
|
+
* enabled: () => inject(Folder).current(),
|
|
102
|
+
* }),
|
|
103
|
+
* ],
|
|
104
|
+
* template: `...`,
|
|
105
|
+
* })
|
|
106
|
+
* export class Folder {
|
|
107
|
+
* readonly name = input.required<string>()
|
|
108
|
+
* readonly current = input(false)
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
54
111
|
*/
|
|
55
112
|
export declare function provideHotkeys(options?: ProvideHotkeysOptions): Provider;
|
|
56
113
|
export declare function injectHotkeysContext(): HotkeysContextValue;
|
package/dist/hotkeys-provider.js
CHANGED
|
@@ -1,20 +1,73 @@
|
|
|
1
1
|
import { AppHotkeysStore, publishAppHotkeysStore } from "@tanstack/hotkeys";
|
|
2
|
-
import { DestroyRef, InjectionToken, inject, signal } from "@angular/core";
|
|
2
|
+
import { DestroyRef, InjectionToken, Injector, computed, effect, inject, runInInjectionContext, signal } from "@angular/core";
|
|
3
|
+
import { activateHotkeysProviderScope, getHotkeysProviderScopeKey, mergeHotkeysProviderDefaults } from "@tanstack/hotkeys/adapter";
|
|
3
4
|
|
|
4
5
|
//#region src/hotkeys-provider.ts
|
|
5
6
|
const NO_APP_HOTKEYS = signal(null).asReadonly();
|
|
7
|
+
const ROOT_CONTEXT = {
|
|
8
|
+
defaultOptions: {},
|
|
9
|
+
enabled: signal(true).asReadonly(),
|
|
10
|
+
appHotkeysStore: null,
|
|
11
|
+
appHotkeysState: NO_APP_HOTKEYS
|
|
12
|
+
};
|
|
6
13
|
const HOTKEYS_INJECTION_TOKEN = new InjectionToken("HOTKEYS_INJECTION_TOKEN", {
|
|
7
14
|
providedIn: "root",
|
|
8
|
-
factory: () =>
|
|
9
|
-
defaultOptions: {},
|
|
10
|
-
appHotkeysStore: null,
|
|
11
|
-
appHotkeysState: NO_APP_HOTKEYS
|
|
12
|
-
})
|
|
15
|
+
factory: () => ROOT_CONTEXT
|
|
13
16
|
});
|
|
17
|
+
/** Creates a provider's context. Runs in an injection context. */
|
|
18
|
+
function createHotkeysContext(options) {
|
|
19
|
+
const injector = inject(Injector);
|
|
20
|
+
const read = (value) => typeof value === "function" ? runInInjectionContext(injector, value) : value;
|
|
21
|
+
const parent = inject(HOTKEYS_INJECTION_TOKEN, {
|
|
22
|
+
skipSelf: true,
|
|
23
|
+
optional: true
|
|
24
|
+
}) ?? ROOT_CONTEXT;
|
|
25
|
+
const destroyRef = inject(DestroyRef);
|
|
26
|
+
const { appHotkeys, customHotkeys, onCustomHotkeysChange } = options;
|
|
27
|
+
let store = null;
|
|
28
|
+
let appHotkeysState = parent.appHotkeysState;
|
|
29
|
+
if (appHotkeys) {
|
|
30
|
+
store = new AppHotkeysStore(appHotkeys, {
|
|
31
|
+
customHotkeys,
|
|
32
|
+
onCustomHotkeysChange
|
|
33
|
+
});
|
|
34
|
+
const state = signal(store.store.state);
|
|
35
|
+
const subscription = store.store.subscribe(() => state.set(store.store.state));
|
|
36
|
+
destroyRef.onDestroy(() => subscription.unsubscribe());
|
|
37
|
+
if (typeof document !== "undefined") destroyRef.onDestroy(publishAppHotkeysStore(store));
|
|
38
|
+
appHotkeysState = state.asReadonly();
|
|
39
|
+
}
|
|
40
|
+
const enabled = computed(() => parent.enabled() && read(options.enabled) !== false);
|
|
41
|
+
const defaultOptions = computed(() => mergeHotkeysProviderDefaults(parent.defaultOptions, options.defaultOptions, read(options.scope)));
|
|
42
|
+
const scopeKey = computed(() => getHotkeysProviderScopeKey({
|
|
43
|
+
scope: read(options.scope),
|
|
44
|
+
exclusive: read(options.exclusive),
|
|
45
|
+
active: enabled(),
|
|
46
|
+
parent: parent.defaultOptions.hotkey?.scope
|
|
47
|
+
}));
|
|
48
|
+
if (typeof document !== "undefined") effect((onCleanup) => {
|
|
49
|
+
const key = scopeKey();
|
|
50
|
+
if (key) onCleanup(activateHotkeysProviderScope(key));
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
get defaultOptions() {
|
|
54
|
+
return defaultOptions();
|
|
55
|
+
},
|
|
56
|
+
enabled,
|
|
57
|
+
appHotkeysStore: store ?? parent.appHotkeysStore,
|
|
58
|
+
appHotkeysState
|
|
59
|
+
};
|
|
60
|
+
}
|
|
14
61
|
/**
|
|
15
62
|
* Provides default options, app hotkeys, and the user's custom hotkeys to an
|
|
16
63
|
* injector, such as the application or a component.
|
|
17
64
|
*
|
|
65
|
+
* Provide it in a component to create a hotkey scope. A nested provider
|
|
66
|
+
* inherits app hotkeys and merges default options. With `scope`, hotkeys
|
|
67
|
+
* injected in the component and its children fire only while it exists and
|
|
68
|
+
* is enabled; with `exclusive`, only they fire. The scope activates when the
|
|
69
|
+
* first hotkey in it is injected.
|
|
70
|
+
*
|
|
18
71
|
* @example
|
|
19
72
|
* ```ts
|
|
20
73
|
* bootstrapApplication(App, {
|
|
@@ -28,32 +81,44 @@ const HOTKEYS_INJECTION_TOKEN = new InjectionToken("HOTKEYS_INJECTION_TOKEN", {
|
|
|
28
81
|
* ],
|
|
29
82
|
* })
|
|
30
83
|
* ```
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* @Component({
|
|
88
|
+
* selector: 'app-edit-dialog',
|
|
89
|
+
* providers: [provideHotkeys({ scope: 'edit-dialog', exclusive: true })],
|
|
90
|
+
* template: `...`,
|
|
91
|
+
* })
|
|
92
|
+
* export class EditDialog {
|
|
93
|
+
* constructor() {
|
|
94
|
+
* injectHotkey('Escape', () => this.close())
|
|
95
|
+
* }
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* // A scope that follows the providing component's input.
|
|
102
|
+
* @Component({
|
|
103
|
+
* selector: 'app-folder',
|
|
104
|
+
* providers: [
|
|
105
|
+
* provideHotkeys({
|
|
106
|
+
* scope: () => inject(Folder).name(),
|
|
107
|
+
* enabled: () => inject(Folder).current(),
|
|
108
|
+
* }),
|
|
109
|
+
* ],
|
|
110
|
+
* template: `...`,
|
|
111
|
+
* })
|
|
112
|
+
* export class Folder {
|
|
113
|
+
* readonly name = input.required<string>()
|
|
114
|
+
* readonly current = input(false)
|
|
115
|
+
* }
|
|
116
|
+
* ```
|
|
31
117
|
*/
|
|
32
118
|
function provideHotkeys(options = {}) {
|
|
33
119
|
return {
|
|
34
120
|
provide: HOTKEYS_INJECTION_TOKEN,
|
|
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
|
-
}
|
|
121
|
+
useFactory: () => createHotkeysContext(options)
|
|
57
122
|
};
|
|
58
123
|
}
|
|
59
124
|
function injectHotkeysContext() {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
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";
|
|
2
|
+
import { HOTKEYS_INJECTION_TOKEN, HotkeysContextValue, HotkeysProviderOptions, MaybeGetter, ProvideHotkeysOptions, injectDefaultHotkeysOptions, injectHotkeysContext, provideHotkeys } from "./hotkeys-provider.js";
|
|
3
3
|
import { InjectHotkeyDefinition, injectHotkeys } from "./injectHotkeys.js";
|
|
4
4
|
import { injectHeldKeys } from "./injectHeldKeys.js";
|
|
5
5
|
import { injectHeldKeyCodes } from "./injectHeldKeyCodes.js";
|
|
@@ -9,4 +9,4 @@ import { injectHotkeyRegistrations } from "./injectHotkeyRegistrations.js";
|
|
|
9
9
|
import { injectHotkeyHint } from "./injectHotkeyHint.js";
|
|
10
10
|
import { InjectCustomHotkeysResult, injectAppHotkeyList, injectAppHotkeys, injectCustomHotkeys } from "./injectAppHotkeys.js";
|
|
11
11
|
export * from "@tanstack/hotkeys";
|
|
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 };
|
|
12
|
+
export { AngularHotkeyRecorder, HOTKEYS_INJECTION_TOKEN, HotkeysContextValue, HotkeysProviderOptions, InjectAppHotkeyOptions, InjectCustomHotkeysResult, InjectHotkeyChordOptions, InjectHotkeyCommonOptions, InjectHotkeyDefinition, InjectHotkeyOptions, InjectHotkeyOptionsFor, InjectHotkeySequenceOptions, InjectHotkeyTarget, MaybeGetter, ProvideHotkeysOptions, injectAppHotkeyList, injectAppHotkeys, injectCustomHotkeys, injectDefaultHotkeysOptions, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyHint, injectHotkeyRecorder, injectHotkeyRegistrations, injectHotkeys, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
|
@@ -7,9 +7,9 @@ import { Signal } from "@angular/core";
|
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
9
9
|
* ```ts
|
|
10
|
-
*
|
|
10
|
+
* appHotkeys = injectAppHotkeys()
|
|
11
11
|
* constructor() {
|
|
12
|
-
* injectHotkey(() => this.
|
|
12
|
+
* injectHotkey(() => this.appHotkeys()['doc.save'], () => this.save())
|
|
13
13
|
* }
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
package/dist/injectAppHotkeys.js
CHANGED
|
@@ -17,9 +17,9 @@ function injectRequiredStore(name) {
|
|
|
17
17
|
*
|
|
18
18
|
* @example
|
|
19
19
|
* ```ts
|
|
20
|
-
*
|
|
20
|
+
* appHotkeys = injectAppHotkeys()
|
|
21
21
|
* constructor() {
|
|
22
|
-
* injectHotkey(() => this.
|
|
22
|
+
* injectHotkey(() => this.appHotkeys()['doc.save'], () => this.save())
|
|
23
23
|
* }
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
package/dist/injectHotkey.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AppHotkey, HotkeyCallback, RegisterableHotkey, Target } from "@tanstack/hotkeys";
|
|
1
|
+
import { AppHotkey, HotkeyCallback, HotkeyCallbackContextFor, RegisterableHotkey, Target } from "@tanstack/hotkeys";
|
|
2
2
|
import { AdapterAppHotkeyOptions, AdapterChordOptions, AdapterCommonOptions, AdapterHotkeyOptions, AdapterOptionsFor, AdapterSequenceOptions } from "@tanstack/hotkeys/adapter";
|
|
3
3
|
//#region src/injectHotkey.d.ts
|
|
4
4
|
/**
|
|
@@ -43,5 +43,5 @@ type MaybeGetter<T> = T | (() => T);
|
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
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
|
|
46
|
+
export declare function injectHotkey<const T extends RegisterableHotkey>(hotkey: MaybeGetter<T>, callback: HotkeyCallback<HotkeyCallbackContextFor<T>>, options?: MaybeGetter<InjectHotkeyOptionsFor<T>>): void;
|
|
47
47
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/angular-hotkeys",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
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.16.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@angular/common": ">=19.0.0",
|