@tanstack/angular-hotkeys 0.19.1 → 0.21.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/_zone.js +14 -0
- package/dist/hotkeys-provider.d.ts +35 -4
- package/dist/hotkeys-provider.js +23 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/injectFormatForDisplay.d.ts +25 -0
- package/dist/injectFormatForDisplay.js +35 -0
- package/dist/injectHeldKeyCodes.js +2 -1
- package/dist/injectHeldKeys.js +2 -1
- package/dist/injectHotkey.d.ts +5 -4
- package/dist/injectHotkeyHint.js +2 -1
- package/dist/injectHotkeyRecorder.js +18 -8
- package/dist/injectHotkeys.d.ts +3 -1
- package/dist/injectHotkeys.js +14 -4
- package/dist/injectKeyHold.js +2 -1
- package/package.json +4 -3
- package/skills/angular-hotkeys/SKILL.md +8 -9
package/dist/_zone.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NgZone, inject } from "@angular/core";
|
|
2
|
+
|
|
3
|
+
//#region src/_zone.ts
|
|
4
|
+
/**
|
|
5
|
+
* Runs `fn` outside the Angular zone, so core listeners it attaches don't
|
|
6
|
+
* run change detection on every event. Zoneless apps call `fn` directly.
|
|
7
|
+
* Must be called in an injection context.
|
|
8
|
+
*/
|
|
9
|
+
function runOutsideZone(fn) {
|
|
10
|
+
return inject(NgZone).runOutsideAngular(fn);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
export { runOutsideZone };
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { InjectHotkeyCommonOptions } from "./injectHotkey.js";
|
|
2
|
-
import { AppHotkeys, AppHotkeysState, AppHotkeysStore, CustomHotkeys, HotkeyChordRecorderOptions, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
2
|
+
import { AppHotkeys, AppHotkeysState, AppHotkeysStore, CustomHotkeys, HotkeyChordRecorderOptions, HotkeyPlatform, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
3
3
|
import { InjectionToken, Provider, Signal } from "@angular/core";
|
|
4
4
|
import { HotkeysProviderScopeProps } from "@tanstack/hotkeys/adapter";
|
|
5
5
|
//#region src/hotkeys-provider.d.ts
|
|
6
6
|
export interface HotkeysProviderOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The user's platform, for labels from `injectFormatForDisplay`, hotkeys,
|
|
9
|
+
* and recorders. Detected when unset. On a server, set it from the request
|
|
10
|
+
* with `detectPlatform(userAgent)`.
|
|
11
|
+
*/
|
|
12
|
+
platform?: HotkeyPlatform;
|
|
7
13
|
/**
|
|
8
14
|
* Defaults for every hotkey registered by `injectHotkey` and `injectHotkeys`.
|
|
9
15
|
* `requireReset` applies to chords and `timeout` applies to sequences.
|
|
@@ -14,8 +20,12 @@ export interface HotkeysProviderOptions {
|
|
|
14
20
|
}
|
|
15
21
|
/** Options for `provideHotkeys`. */
|
|
16
22
|
export interface ProvideHotkeysOptions {
|
|
17
|
-
/**
|
|
18
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Merged with the parent provider's defaults. A getter runs in the
|
|
25
|
+
* provider's injection context, so it can `inject(REQUEST)` to set
|
|
26
|
+
* `platform` from the request during server rendering.
|
|
27
|
+
*/
|
|
28
|
+
defaultOptions?: MaybeGetter<HotkeysProviderOptions>;
|
|
19
29
|
/**
|
|
20
30
|
* See `HotkeysProviderScopeProps['scope']`. Getters, here and for
|
|
21
31
|
* `exclusive` and `enabled`, run in the provider's injection context, so
|
|
@@ -62,7 +72,9 @@ export type MaybeGetter<T> = T | (() => T);
|
|
|
62
72
|
* inherits app hotkeys and merges default options. With `scope`, hotkeys
|
|
63
73
|
* injected in the component and its children fire only while it exists and
|
|
64
74
|
* is enabled; with `exclusive`, only they fire. The scope activates when the
|
|
65
|
-
* first hotkey in it is injected.
|
|
75
|
+
* first hotkey in it is injected. Angular creates the provider lazily, so a
|
|
76
|
+
* component with no hotkeys of its own calls `injectHotkeysContext()` to
|
|
77
|
+
* activate its scope.
|
|
66
78
|
*
|
|
67
79
|
* @example
|
|
68
80
|
* ```ts
|
|
@@ -94,6 +106,21 @@ export type MaybeGetter<T> = T | (() => T);
|
|
|
94
106
|
*
|
|
95
107
|
* @example
|
|
96
108
|
* ```ts
|
|
109
|
+
* // An overlay with no hotkeys that suspends the page's hotkeys.
|
|
110
|
+
* @Component({
|
|
111
|
+
* selector: 'app-loading-overlay',
|
|
112
|
+
* providers: [provideHotkeys({ scope: 'loading', exclusive: true })],
|
|
113
|
+
* template: `...`,
|
|
114
|
+
* })
|
|
115
|
+
* export class LoadingOverlay {
|
|
116
|
+
* constructor() {
|
|
117
|
+
* injectHotkeysContext()
|
|
118
|
+
* }
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
97
124
|
* // A scope that follows the providing component's input.
|
|
98
125
|
* @Component({
|
|
99
126
|
* selector: 'app-folder',
|
|
@@ -112,6 +139,10 @@ export type MaybeGetter<T> = T | (() => T);
|
|
|
112
139
|
* ```
|
|
113
140
|
*/
|
|
114
141
|
export declare function provideHotkeys(options?: ProvideHotkeysOptions): Provider;
|
|
142
|
+
/**
|
|
143
|
+
* Returns the nearest provider's context. Injecting it activates that
|
|
144
|
+
* provider's scope.
|
|
145
|
+
*/
|
|
115
146
|
export declare function injectHotkeysContext(): HotkeysContextValue;
|
|
116
147
|
export declare function injectDefaultHotkeysOptions(): HotkeysProviderOptions;
|
|
117
148
|
//#endregion
|
package/dist/hotkeys-provider.js
CHANGED
|
@@ -43,7 +43,7 @@ function createHotkeysContext(options) {
|
|
|
43
43
|
appHotkeysState = state.asReadonly();
|
|
44
44
|
}
|
|
45
45
|
const enabled = computed(() => parent.enabled() && read(options.enabled) !== false);
|
|
46
|
-
const defaultOptions = computed(() => mergeHotkeysProviderDefaults(parent.defaultOptions, options.defaultOptions, read(options.scope)));
|
|
46
|
+
const defaultOptions = computed(() => mergeHotkeysProviderDefaults(parent.defaultOptions, read(options.defaultOptions), read(options.scope)));
|
|
47
47
|
const scopeKey = computed(() => getHotkeysProviderScopeKey({
|
|
48
48
|
scope: read(options.scope),
|
|
49
49
|
exclusive: read(options.exclusive),
|
|
@@ -71,7 +71,9 @@ function createHotkeysContext(options) {
|
|
|
71
71
|
* inherits app hotkeys and merges default options. With `scope`, hotkeys
|
|
72
72
|
* injected in the component and its children fire only while it exists and
|
|
73
73
|
* is enabled; with `exclusive`, only they fire. The scope activates when the
|
|
74
|
-
* first hotkey in it is injected.
|
|
74
|
+
* first hotkey in it is injected. Angular creates the provider lazily, so a
|
|
75
|
+
* component with no hotkeys of its own calls `injectHotkeysContext()` to
|
|
76
|
+
* activate its scope.
|
|
75
77
|
*
|
|
76
78
|
* @example
|
|
77
79
|
* ```ts
|
|
@@ -103,6 +105,21 @@ function createHotkeysContext(options) {
|
|
|
103
105
|
*
|
|
104
106
|
* @example
|
|
105
107
|
* ```ts
|
|
108
|
+
* // An overlay with no hotkeys that suspends the page's hotkeys.
|
|
109
|
+
* @Component({
|
|
110
|
+
* selector: 'app-loading-overlay',
|
|
111
|
+
* providers: [provideHotkeys({ scope: 'loading', exclusive: true })],
|
|
112
|
+
* template: `...`,
|
|
113
|
+
* })
|
|
114
|
+
* export class LoadingOverlay {
|
|
115
|
+
* constructor() {
|
|
116
|
+
* injectHotkeysContext()
|
|
117
|
+
* }
|
|
118
|
+
* }
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
106
123
|
* // A scope that follows the providing component's input.
|
|
107
124
|
* @Component({
|
|
108
125
|
* selector: 'app-folder',
|
|
@@ -126,6 +143,10 @@ function provideHotkeys(options = {}) {
|
|
|
126
143
|
useFactory: () => createHotkeysContext(options)
|
|
127
144
|
};
|
|
128
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Returns the nearest provider's context. Injecting it activates that
|
|
148
|
+
* provider's scope.
|
|
149
|
+
*/
|
|
129
150
|
function injectHotkeysContext() {
|
|
130
151
|
return inject(HOTKEYS_INJECTION_TOKEN);
|
|
131
152
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,5 +9,6 @@ import { injectHotkeyRegistrations } from "./injectHotkeyRegistrations.js";
|
|
|
9
9
|
import { injectHotkeyScopesState } from "./injectHotkeyScopesState.js";
|
|
10
10
|
import { injectHotkeyHint } from "./injectHotkeyHint.js";
|
|
11
11
|
import { InjectCustomHotkeysResult, injectAppHotkeyList, injectAppHotkeys, injectCustomHotkeys } from "./injectAppHotkeys.js";
|
|
12
|
+
import { injectFormatForDisplay } from "./injectFormatForDisplay.js";
|
|
12
13
|
export * from "@tanstack/hotkeys";
|
|
13
|
-
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, injectHotkeyScopesState, injectHotkeys, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
|
14
|
+
export { AngularHotkeyRecorder, HOTKEYS_INJECTION_TOKEN, HotkeysContextValue, HotkeysProviderOptions, InjectAppHotkeyOptions, InjectCustomHotkeysResult, InjectHotkeyChordOptions, InjectHotkeyCommonOptions, InjectHotkeyDefinition, InjectHotkeyOptions, InjectHotkeyOptionsFor, InjectHotkeySequenceOptions, InjectHotkeyTarget, MaybeGetter, ProvideHotkeysOptions, injectAppHotkeyList, injectAppHotkeys, injectCustomHotkeys, injectDefaultHotkeysOptions, injectFormatForDisplay, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyHint, injectHotkeyRecorder, injectHotkeyRegistrations, injectHotkeyScopesState, injectHotkeys, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,8 @@ import { injectHotkeyRegistrations } from "./injectHotkeyRegistrations.js";
|
|
|
9
9
|
import { injectHotkeyScopesState } from "./injectHotkeyScopesState.js";
|
|
10
10
|
import { injectHotkeyHint } from "./injectHotkeyHint.js";
|
|
11
11
|
import { injectAppHotkeyList, injectAppHotkeys, injectCustomHotkeys } from "./injectAppHotkeys.js";
|
|
12
|
+
import { injectFormatForDisplay } from "./injectFormatForDisplay.js";
|
|
12
13
|
|
|
13
14
|
export * from "@tanstack/hotkeys"
|
|
14
15
|
|
|
15
|
-
export { HOTKEYS_INJECTION_TOKEN, injectAppHotkeyList, injectAppHotkeys, injectCustomHotkeys, injectDefaultHotkeysOptions, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyHint, injectHotkeyRecorder, injectHotkeyRegistrations, injectHotkeyScopesState, injectHotkeys, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
|
16
|
+
export { HOTKEYS_INJECTION_TOKEN, injectAppHotkeyList, injectAppHotkeys, injectCustomHotkeys, injectDefaultHotkeysOptions, injectFormatForDisplay, injectHeldKeyCodes, injectHeldKeys, injectHotkey, injectHotkeyHint, injectHotkeyRecorder, injectHotkeyRegistrations, injectHotkeyScopesState, injectHotkeys, injectHotkeysContext, injectKeyHold, provideHotkeys };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { formatForDisplay } from "@tanstack/hotkeys";
|
|
2
|
+
//#region src/injectFormatForDisplay.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Returns `formatForDisplay` with the provider's `platform` as the default,
|
|
5
|
+
* so labels match the user's platform. Call it in an injection context, such
|
|
6
|
+
* as a field initializer; the returned function works anywhere, including
|
|
7
|
+
* templates. An explicit `platform` option wins.
|
|
8
|
+
*
|
|
9
|
+
* Server-rendered apps should set the provider's `defaultOptions.platform`
|
|
10
|
+
* from the request user agent, such as
|
|
11
|
+
* `detectPlatform(inject(REQUEST)?.headers.get('user-agent'))`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* @Component({
|
|
16
|
+
* selector: 'app-save-button',
|
|
17
|
+
* template: `<kbd>{{ format('Mod+S') }}</kbd>`,
|
|
18
|
+
* })
|
|
19
|
+
* export class SaveButton {
|
|
20
|
+
* format = injectFormatForDisplay()
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function injectFormatForDisplay(): typeof formatForDisplay;
|
|
25
|
+
//#endregion
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { injectHotkeysContext } from "./hotkeys-provider.js";
|
|
2
|
+
import { formatForDisplay } from "@tanstack/hotkeys";
|
|
3
|
+
|
|
4
|
+
//#region src/injectFormatForDisplay.ts
|
|
5
|
+
/**
|
|
6
|
+
* Returns `formatForDisplay` with the provider's `platform` as the default,
|
|
7
|
+
* so labels match the user's platform. Call it in an injection context, such
|
|
8
|
+
* as a field initializer; the returned function works anywhere, including
|
|
9
|
+
* templates. An explicit `platform` option wins.
|
|
10
|
+
*
|
|
11
|
+
* Server-rendered apps should set the provider's `defaultOptions.platform`
|
|
12
|
+
* from the request user agent, such as
|
|
13
|
+
* `detectPlatform(inject(REQUEST)?.headers.get('user-agent'))`.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* @Component({
|
|
18
|
+
* selector: 'app-save-button',
|
|
19
|
+
* template: `<kbd>{{ format('Mod+S') }}</kbd>`,
|
|
20
|
+
* })
|
|
21
|
+
* export class SaveButton {
|
|
22
|
+
* format = injectFormatForDisplay()
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function injectFormatForDisplay() {
|
|
27
|
+
const context = injectHotkeysContext();
|
|
28
|
+
return ((hotkey, options) => formatForDisplay(hotkey, {
|
|
29
|
+
...options,
|
|
30
|
+
platform: options?.platform ?? context.defaultOptions.platform
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { injectFormatForDisplay };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { injectSelector } from "./_injectSelector.js";
|
|
2
|
+
import { runOutsideZone } from "./_zone.js";
|
|
2
3
|
import { getKeyStateTracker } from "@tanstack/hotkeys";
|
|
3
4
|
|
|
4
5
|
//#region src/injectHeldKeyCodes.ts
|
|
@@ -26,7 +27,7 @@ import { getKeyStateTracker } from "@tanstack/hotkeys";
|
|
|
26
27
|
* ```
|
|
27
28
|
*/
|
|
28
29
|
function injectHeldKeyCodes() {
|
|
29
|
-
const tracker = getKeyStateTracker
|
|
30
|
+
const tracker = runOutsideZone(getKeyStateTracker);
|
|
30
31
|
return injectSelector(tracker.store, (state) => state.heldCodes);
|
|
31
32
|
}
|
|
32
33
|
|
package/dist/injectHeldKeys.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { injectSelector } from "./_injectSelector.js";
|
|
2
|
+
import { runOutsideZone } from "./_zone.js";
|
|
2
3
|
import { getKeyStateTracker } from "@tanstack/hotkeys";
|
|
3
4
|
|
|
4
5
|
//#region src/injectHeldKeys.ts
|
|
@@ -21,7 +22,7 @@ import { getKeyStateTracker } from "@tanstack/hotkeys";
|
|
|
21
22
|
* ```
|
|
22
23
|
*/
|
|
23
24
|
function injectHeldKeys() {
|
|
24
|
-
const tracker = getKeyStateTracker
|
|
25
|
+
const tracker = runOutsideZone(getKeyStateTracker);
|
|
25
26
|
return injectSelector(tracker.store, (state) => state.heldKeys);
|
|
26
27
|
}
|
|
27
28
|
|
package/dist/injectHotkey.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { AppHotkey, HotkeyCallback, HotkeyCallbackContextFor, RegisterableHotkey, Target } from "@tanstack/hotkeys";
|
|
2
|
+
import { ElementRef, Signal } from "@angular/core";
|
|
2
3
|
import { AdapterAppHotkeyOptions, AdapterChordOptions, AdapterCommonOptions, AdapterHotkeyOptions, AdapterOptionsFor, AdapterSequenceOptions } from "@tanstack/hotkeys/adapter";
|
|
3
4
|
//#region src/injectHotkey.d.ts
|
|
4
5
|
/**
|
|
5
|
-
* The element to listen on
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* The element to listen on: an element, an `ElementRef`, or a signal of
|
|
7
|
+
* either, such as `viewChild<ElementRef>('panel')`. A `null` target, or a
|
|
8
|
+
* signal of `undefined`, skips registration until the element exists.
|
|
8
9
|
*/
|
|
9
|
-
export type InjectHotkeyTarget = Target | null;
|
|
10
|
+
export type InjectHotkeyTarget = Target | ElementRef<Target> | Signal<Target | ElementRef<Target> | null | undefined> | null;
|
|
10
11
|
/** Options shared by chords and sequences. */
|
|
11
12
|
export type InjectHotkeyOptions = AdapterHotkeyOptions<InjectHotkeyTarget>;
|
|
12
13
|
/** Options for a chord such as `'Mod+S'`. */
|
package/dist/injectHotkeyHint.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { injectHotkeysContext } from "./hotkeys-provider.js";
|
|
2
2
|
import { injectSelector } from "./_injectSelector.js";
|
|
3
|
+
import { runOutsideZone } from "./_zone.js";
|
|
3
4
|
import { getHotkeyManager, getKeyStateTracker, isAppHotkey, matchesHeldModifiers, resolveAppHotkey } from "@tanstack/hotkeys";
|
|
4
5
|
import { computed } from "@angular/core";
|
|
5
6
|
|
|
@@ -17,7 +18,7 @@ function injectHotkeyHint(hotkey, options = {}) {
|
|
|
17
18
|
const context = injectHotkeysContext();
|
|
18
19
|
const manager = getHotkeyManager();
|
|
19
20
|
const registrations = injectSelector(manager.registrations, (state) => state);
|
|
20
|
-
const held = injectSelector(getKeyStateTracker
|
|
21
|
+
const held = injectSelector(runOutsideZone(getKeyStateTracker).store, (state) => state.heldKeys);
|
|
21
22
|
return computed(() => {
|
|
22
23
|
context.appHotkeysState();
|
|
23
24
|
const value = typeof hotkey === "function" ? hotkey() : hotkey;
|
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
import { injectDefaultHotkeysOptions } from "./hotkeys-provider.js";
|
|
2
2
|
import { HotkeyRecorder } from "@tanstack/hotkeys";
|
|
3
|
-
import { DestroyRef, computed, effect, inject, linkedSignal, untracked } from "@angular/core";
|
|
3
|
+
import { DestroyRef, NgZone, computed, effect, inject, linkedSignal, untracked } from "@angular/core";
|
|
4
4
|
import { withDefaults } from "@tanstack/hotkeys/adapter";
|
|
5
5
|
|
|
6
6
|
//#region src/injectHotkeyRecorder.ts
|
|
7
7
|
function injectHotkeyRecorder(options) {
|
|
8
8
|
const defaultOptions = injectDefaultHotkeysOptions();
|
|
9
9
|
const destroyRef = inject(DestroyRef);
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
const zone = inject(NgZone);
|
|
11
|
+
const resolve = () => {
|
|
12
|
+
const resolved = withDefaults(defaultOptions.hotkeyRecorder, typeof options === "function" ? options() : options);
|
|
13
|
+
for (const key of [
|
|
14
|
+
"onRecord",
|
|
15
|
+
"onCancel",
|
|
16
|
+
"onClear",
|
|
17
|
+
"onReject"
|
|
18
|
+
]) {
|
|
19
|
+
const callback = resolved[key];
|
|
20
|
+
if (callback) resolved[key] = (...args) => zone.run(callback, null, args);
|
|
21
|
+
}
|
|
22
|
+
return resolved;
|
|
23
|
+
};
|
|
24
|
+
const recorderSignal = computed(() => untracked(() => new HotkeyRecorder(resolve())));
|
|
14
25
|
const recorderStore = computed(() => untracked(() => recorderSignal().store));
|
|
15
26
|
const isRecording = injectLazyStore(recorderStore, (state) => state.isRecording);
|
|
16
27
|
const steps = injectLazyStore(recorderStore, (state) => state.steps);
|
|
17
28
|
const recordedHotkey = injectLazyStore(recorderStore, (state) => state.recordedHotkey);
|
|
18
29
|
effect(() => {
|
|
19
|
-
|
|
20
|
-
recorderSignal().setOptions(withDefaults(defaultOptions.hotkeyRecorder, resolved));
|
|
30
|
+
recorderSignal().setOptions(resolve());
|
|
21
31
|
});
|
|
22
32
|
destroyRef.onDestroy(() => {
|
|
23
33
|
recorderSignal().destroy();
|
|
@@ -26,7 +36,7 @@ function injectHotkeyRecorder(options) {
|
|
|
26
36
|
isRecording,
|
|
27
37
|
steps,
|
|
28
38
|
recordedHotkey,
|
|
29
|
-
startRecording: () => recorderSignal().start(),
|
|
39
|
+
startRecording: () => zone.runOutsideAngular(() => recorderSignal().start()),
|
|
30
40
|
stopRecording: () => recorderSignal().stop(),
|
|
31
41
|
cancelRecording: () => recorderSignal().cancel(),
|
|
32
42
|
commitRecording: () => recorderSignal().commit()
|
package/dist/injectHotkeys.d.ts
CHANGED
|
@@ -13,7 +13,9 @@ export type InjectHotkeyDefinition = AdapterHotkeyDefinition<InjectHotkeyTarget>
|
|
|
13
13
|
* Options merge in this order: provider defaults, app hotkey declaration,
|
|
14
14
|
* commonOptions, per-hotkey options. Each option applies only to the kinds
|
|
15
15
|
* that support it. Unbound app hotkeys and empty sequences register nothing.
|
|
16
|
-
* A `
|
|
16
|
+
* A target may be an element, an `ElementRef`, or a signal of either, such
|
|
17
|
+
* as `viewChild('panel')`. A `null` target, or a signal of `undefined`, skips
|
|
18
|
+
* registration until the element exists.
|
|
17
19
|
*
|
|
18
20
|
* @example
|
|
19
21
|
* ```ts
|
package/dist/injectHotkeys.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { injectHotkeysContext } from "./hotkeys-provider.js";
|
|
2
|
-
import { DestroyRef, effect, inject, untracked } from "@angular/core";
|
|
2
|
+
import { DestroyRef, ElementRef, NgZone, effect, inject, untracked } from "@angular/core";
|
|
3
3
|
import { createHotkeyBindings, prepareHotkeyDefinitions } from "@tanstack/hotkeys/adapter";
|
|
4
4
|
|
|
5
5
|
//#region src/injectHotkeys.ts
|
|
@@ -10,7 +10,9 @@ import { createHotkeyBindings, prepareHotkeyDefinitions } from "@tanstack/hotkey
|
|
|
10
10
|
* Options merge in this order: provider defaults, app hotkey declaration,
|
|
11
11
|
* commonOptions, per-hotkey options. Each option applies only to the kinds
|
|
12
12
|
* that support it. Unbound app hotkeys and empty sequences register nothing.
|
|
13
|
-
* A `
|
|
13
|
+
* A target may be an element, an `ElementRef`, or a signal of either, such
|
|
14
|
+
* as `viewChild('panel')`. A `null` target, or a signal of `undefined`, skips
|
|
15
|
+
* registration until the element exists.
|
|
14
16
|
*
|
|
15
17
|
* @example
|
|
16
18
|
* ```ts
|
|
@@ -22,6 +24,7 @@ import { createHotkeyBindings, prepareHotkeyDefinitions } from "@tanstack/hotkey
|
|
|
22
24
|
*/
|
|
23
25
|
function injectHotkeys(hotkeys, commonOptions = {}) {
|
|
24
26
|
const context = injectHotkeysContext();
|
|
27
|
+
const zone = inject(NgZone);
|
|
25
28
|
const bindings = createHotkeyBindings();
|
|
26
29
|
inject(DestroyRef).onDestroy(() => bindings.destroy());
|
|
27
30
|
effect(() => {
|
|
@@ -30,9 +33,16 @@ function injectHotkeys(hotkeys, commonOptions = {}) {
|
|
|
30
33
|
appHotkeysStore: context.appHotkeysStore,
|
|
31
34
|
defaults: context.defaultOptions.hotkey,
|
|
32
35
|
commonOptions: typeof commonOptions === "function" ? commonOptions() : commonOptions,
|
|
33
|
-
resolveTarget: (target) =>
|
|
36
|
+
resolveTarget: (target) => {
|
|
37
|
+
const value = typeof target === "function" ? target() : target;
|
|
38
|
+
return (value instanceof ElementRef ? value.nativeElement : value) ?? null;
|
|
39
|
+
}
|
|
34
40
|
});
|
|
35
|
-
|
|
41
|
+
for (const definition of definitions) {
|
|
42
|
+
const callback = definition.callback;
|
|
43
|
+
definition.callback = (...args) => zone.run(callback, null, args);
|
|
44
|
+
}
|
|
45
|
+
untracked(() => zone.runOutsideAngular(() => bindings.update(definitions)));
|
|
36
46
|
});
|
|
37
47
|
}
|
|
38
48
|
|
package/dist/injectKeyHold.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { injectSelector } from "./_injectSelector.js";
|
|
2
|
+
import { runOutsideZone } from "./_zone.js";
|
|
2
3
|
import { getKeyStateTracker } from "@tanstack/hotkeys";
|
|
3
4
|
import { computed } from "@angular/core";
|
|
4
5
|
|
|
@@ -37,7 +38,7 @@ import { computed } from "@angular/core";
|
|
|
37
38
|
* ```
|
|
38
39
|
*/
|
|
39
40
|
function injectKeyHold(key) {
|
|
40
|
-
const tracker = getKeyStateTracker
|
|
41
|
+
const tracker = runOutsideZone(getKeyStateTracker);
|
|
41
42
|
const heldKeysSelector = injectSelector(tracker.store, (state) => state.heldKeys);
|
|
42
43
|
return computed(() => {
|
|
43
44
|
const normalizedKey = (typeof key === "function" ? key() : key).toLowerCase();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/angular-hotkeys",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Angular adapter for TanStack Hotkeys",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"skills"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@tanstack/hotkeys": "0.
|
|
40
|
+
"@tanstack/hotkeys": "0.23.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@angular/common": ">=19.0.0",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"@angular/common": "^22.2.1",
|
|
48
48
|
"@angular/core": "^22.2.1",
|
|
49
49
|
"@angular/platform-browser-dynamic": "^22.2.1",
|
|
50
|
-
"typescript": "6.0.3"
|
|
50
|
+
"typescript": "6.0.3",
|
|
51
|
+
"zone.js": "~0.16.3"
|
|
51
52
|
},
|
|
52
53
|
"scripts": {
|
|
53
54
|
"clean": "premove ./build ./dist",
|
|
@@ -48,13 +48,8 @@ export class PanelComponent {
|
|
|
48
48
|
() => this.save(),
|
|
49
49
|
() => ({ enabled: this.isDirty() }),
|
|
50
50
|
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
() => this.close(),
|
|
54
|
-
() => ({
|
|
55
|
-
target: this.panel()?.nativeElement ?? null,
|
|
56
|
-
}),
|
|
57
|
-
)
|
|
51
|
+
// A viewChild signal target registers once the element renders.
|
|
52
|
+
injectHotkey('Escape', () => this.close(), { target: this.panel })
|
|
58
53
|
}
|
|
59
54
|
|
|
60
55
|
save() {}
|
|
@@ -69,8 +64,9 @@ export class PanelComponent {
|
|
|
69
64
|
| `injectHotkey(hotkey, callback, options?)` | Register one chord, sequence, or app hotkey for the component's or service's lifetime; options can be a function that reads signals. |
|
|
70
65
|
| `injectHotkeys(definitions, commonOptions?)` | Register several hotkeys, or a function returning a list built from data, in one call. |
|
|
71
66
|
| `provideHotkeys(options)` | Set default options, a scope (`exclusive` for dialogs), `enabled`, and app hotkeys in the app config or a component's `providers`. |
|
|
72
|
-
| `injectHotkeysContext()` | Read the nearest provided defaults and enabled state
|
|
67
|
+
| `injectHotkeysContext()` | Read the nearest provided defaults and enabled state. Activates that provider's scope. |
|
|
73
68
|
| `injectDefaultHotkeysOptions()` | Read the default options merged from every provider above. |
|
|
69
|
+
| `injectFormatForDisplay()` | Get `formatForDisplay` with the provider's `platform` filled in, for labels in server-rendered apps. Call it in an injection context. |
|
|
74
70
|
| `injectAppHotkeys()` | Get a signal of the declared app hotkeys with the user's custom hotkeys applied. |
|
|
75
71
|
| `injectAppHotkeyList()` | Get a signal listing every app hotkey with its status, for a cheat sheet or settings screen. |
|
|
76
72
|
| `injectCustomHotkeys()` | Read, change, and reset the user's custom hotkeys, and check a new one for conflicts. |
|
|
@@ -116,10 +112,13 @@ A component's `providers` apply to the component itself, so it can register its
|
|
|
116
112
|
|
|
117
113
|
## Pitfalls
|
|
118
114
|
|
|
119
|
-
- **Pass options as a function.** `{ enabled: this.isDirty() }` is read once, in the constructor.
|
|
115
|
+
- **Pass options as a function.** `{ enabled: this.isDirty() }` is read once, in the constructor. Use `() => ({ ... })`.
|
|
116
|
+
- **Pass the `viewChild` signal as `target`, not its value.** `this.panel()` in the constructor is `undefined`, and an undefined target means `document`. `{ target: this.panel }` waits for the element.
|
|
117
|
+
- **An exclusive provider with no hotkeys never activates.** Angular creates providers lazily. Call `injectHotkeysContext()` in the component's constructor to activate the scope.
|
|
120
118
|
- **Replace `@HostListener('document:keydown')` and `(keydown.control.s)` bindings with `injectHotkey`.**
|
|
121
119
|
- **Register runtime lists in the constructor.** `injectHotkey` throws outside an injection context, such as in `ngOnInit`. For hotkeys from data, use `injectHotkeys(() => this.items().map(...))`.
|
|
122
120
|
- **Call the inject functions directly.** A shortcut service or `injectShortcut(keys: string)` wrapper has to cast strings to hotkeys, which turns off type checking and drops sequences, signal options, and app hotkeys.
|
|
121
|
+
- **Server-rendered labels need the user's platform.** On the server, `formatForDisplay` can't see the user's OS, so it renders Linux labels that differ after hydration for Mac and Windows users. Pass `provideHotkeys({ defaultOptions: () => ({ platform: detectPlatform(inject(REQUEST)?.headers.get('user-agent')) }) })`, and format labels with `format = injectFormatForDisplay()` as a field, then `format(hotkey)` in the template. Keep plain `formatForDisplay` for labels that render only in the browser.
|
|
123
122
|
|
|
124
123
|
When declaring app hotkeys, building a cheat sheet, or letting users rebind shortcuts, load `@tanstack/hotkeys#app-hotkeys` and read [app hotkeys in Angular](references/app-hotkeys.md).
|
|
125
124
|
|