@tanstack/ember-hotkeys 0.2.0 → 0.3.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.
@@ -0,0 +1,56 @@
1
+ import { getHotkeyManager, isAppHotkey, isHotkeySequence } from "@tanstack/hotkeys";
2
+ import { isDestroyed, isDestroying, registerDestructor } from "@ember/destroyable";
3
+ import { scheduleOnce } from "@ember/runloop";
4
+ import { createHotkeyBindings, prepareHotkeyDefinitions, subscribePublishedAppHotkeys } from "@tanstack/hotkeys/adapter";
5
+
6
+ //#region src/bindings.ts
7
+ /** Copies a hotkey so tracked getters are read while Ember tracks the caller. */
8
+ function copyHotkey(hotkey) {
9
+ if (typeof hotkey === "string" || isAppHotkey(hotkey)) return hotkey;
10
+ if (isHotkeySequence(hotkey)) return [...hotkey];
11
+ return { ...hotkey };
12
+ }
13
+ /**
14
+ * Registrations owned by a helper or modifier. `set` records the latest
15
+ * definitions while Ember tracks the caller; registration changes publish
16
+ * after rendering, and again when app hotkeys or custom hotkeys change.
17
+ */
18
+ function createOwnedBindings(owner) {
19
+ const bindings = createHotkeyBindings();
20
+ const state = {
21
+ definitions: [],
22
+ commonOptions: {},
23
+ element: void 0
24
+ };
25
+ function update() {
26
+ if (isDestroying(owner) || isDestroyed(owner)) return;
27
+ const { definitions, commonOptions, element } = state;
28
+ bindings.update(prepareHotkeyDefinitions(definitions, {
29
+ appHotkeysStore: getHotkeyManager().appHotkeys.state,
30
+ commonOptions: element ? {
31
+ ...commonOptions,
32
+ target: element
33
+ } : commonOptions,
34
+ resolveTarget: element ? () => element : (target) => target
35
+ }));
36
+ }
37
+ const schedule = () => scheduleOnce("afterRender", state, update);
38
+ const unsubscribe = subscribePublishedAppHotkeys(schedule);
39
+ registerDestructor(owner, () => {
40
+ unsubscribe();
41
+ bindings.destroy();
42
+ });
43
+ return { set(definitions, commonOptions, element) {
44
+ state.definitions = definitions.map((definition) => ({
45
+ ...definition,
46
+ hotkey: copyHotkey(definition.hotkey),
47
+ options: { ...definition.options }
48
+ }));
49
+ state.commonOptions = { ...commonOptions };
50
+ state.element = element;
51
+ schedule();
52
+ } };
53
+ }
54
+
55
+ //#endregion
56
+ export { createOwnedBindings };
@@ -1,11 +1,8 @@
1
+ import { DefaultHotkeysOptions } from "./types.js";
1
2
  import { UseHotkey } from "./useHotkey.js";
2
3
  import { UseHotkeys } from "./useHotkeys.js";
3
- import { UseHotkeySequence } from "./useHotkeySequence.js";
4
- import { UseHotkeySequences } from "./useHotkeySequences.js";
5
4
  import { MaybeGetter } from "./utils.js";
6
- import { DefaultHotkeysOptions } from "./types.js";
7
5
  import { useHotkeyRecorder } from "./useHotkeyRecorder.js";
8
- import { useHotkeySequenceRecorder } from "./useHotkeySequenceRecorder.js";
9
6
  import { OnHotkeys } from "./onHotkeys.js";
10
7
  import { OnHotkey } from "./onHotkey.js";
11
8
  //#region src/createHotkeysScope.d.ts
@@ -15,10 +12,7 @@ export interface EmberHotkeysScope {
15
12
  onHotkeys: typeof OnHotkeys;
16
13
  useHotkey: typeof UseHotkey;
17
14
  useHotkeys: typeof UseHotkeys;
18
- useHotkeySequence: typeof UseHotkeySequence;
19
- useHotkeySequences: typeof UseHotkeySequences;
20
15
  useHotkeyRecorder: typeof useHotkeyRecorder;
21
- useHotkeySequenceRecorder: typeof useHotkeySequenceRecorder;
22
16
  }
23
17
  /**
24
18
  * Creates contextual helpers, modifiers, and recorders with shared, optionally reactive defaults.
@@ -1,10 +1,7 @@
1
1
  import { UseHotkey } from "./useHotkey.js";
2
2
  import { UseHotkeys } from "./useHotkeys.js";
3
- import { UseHotkeySequence } from "./useHotkeySequence.js";
4
- import { UseHotkeySequences } from "./useHotkeySequences.js";
5
3
  import { read } from "./utils.js";
6
4
  import { useHotkeyRecorder } from "./useHotkeyRecorder.js";
7
- import { useHotkeySequenceRecorder } from "./useHotkeySequenceRecorder.js";
8
5
  import { OnHotkey } from "./onHotkey.js";
9
6
  import { OnHotkeys } from "./onHotkeys.js";
10
7
 
@@ -47,30 +44,10 @@ function createHotkeysScope(defaultOptions = {}) {
47
44
  });
48
45
  }
49
46
  },
50
- useHotkeySequence: class extends UseHotkeySequence {
51
- compute(...[args, options]) {
52
- super.compute(args, {
53
- ...read(defaultOptions).hotkeySequence,
54
- ...options
55
- });
56
- }
57
- },
58
- useHotkeySequences: class extends UseHotkeySequences {
59
- compute(...[args, options]) {
60
- super.compute(args, {
61
- ...read(defaultOptions).hotkeySequence,
62
- ...options
63
- });
64
- }
65
- },
66
- useHotkeyRecorder: (owner, options) => useHotkeyRecorder(owner, () => ({
47
+ useHotkeyRecorder: ((owner, options) => useHotkeyRecorder(owner, () => ({
67
48
  ...read(defaultOptions).hotkeyRecorder,
68
49
  ...read(options)
69
- })),
70
- useHotkeySequenceRecorder: (owner, options) => useHotkeySequenceRecorder(owner, () => ({
71
- ...read(defaultOptions).hotkeySequenceRecorder,
72
- ...read(options)
73
- }))
50
+ })))
74
51
  };
75
52
  }
76
53
 
package/dist/index.d.ts CHANGED
@@ -1,19 +1,16 @@
1
+ import { DefaultHotkeysOptions, EmberAppHotkeyList, EmberCustomHotkeys, EmberHotkeyCommonOptions, EmberHotkeyDefinition, EmberHotkeyRecorder, EmberHotkeyState, EmberHotkeyTarget, HotkeyRegistrationsResult } from "./types.js";
1
2
  import { UseHotkey } from "./useHotkey.js";
2
3
  import { UseHotkeys } from "./useHotkeys.js";
3
- import { UseHotkeySequence } from "./useHotkeySequence.js";
4
- import { UseHotkeySequences } from "./useHotkeySequences.js";
5
4
  import { MaybeGetter } from "./utils.js";
6
- import { DefaultHotkeysOptions, EmberHotkeyRecorder, EmberHotkeySequenceRecorder, EmberHotkeyState, HotkeyRegistrationsResult } from "./types.js";
7
5
  import { useHeldKeys } from "./useHeldKeys.js";
8
6
  import { useHeldKeyCodes } from "./useHeldKeyCodes.js";
9
7
  import { useKeyHold } from "./useKeyHold.js";
10
8
  import { useHotkeyHint } from "./useHotkeyHint.js";
11
9
  import { useHotkeyRegistrations } from "./useHotkeyRegistrations.js";
12
10
  import { useHotkeyRecorder } from "./useHotkeyRecorder.js";
13
- import { useHotkeySequenceRecorder } from "./useHotkeySequenceRecorder.js";
11
+ import { useAppHotkeyList, useAppHotkeys, useCustomHotkeys } from "./useAppHotkeys.js";
14
12
  import { ElementHotkeyOptions, OnHotkeys } from "./onHotkeys.js";
15
13
  import { OnHotkey } from "./onHotkey.js";
16
14
  import { EmberHotkeysScope, createHotkeysScope } from "./createHotkeysScope.js";
17
- import { HotkeyDefinition, HotkeySequenceDefinition } from "@tanstack/hotkeys/adapter";
18
15
  export * from "@tanstack/hotkeys";
19
- export { DefaultHotkeysOptions, type ElementHotkeyOptions, EmberHotkeyRecorder, EmberHotkeySequenceRecorder, EmberHotkeyState, EmberHotkeysScope, type HotkeyDefinition, HotkeyRegistrationsResult, type HotkeySequenceDefinition, type MaybeGetter, createHotkeysScope, OnHotkey as onHotkey, OnHotkeys as onHotkeys, useHeldKeyCodes, useHeldKeys, UseHotkey as useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations, UseHotkeySequence as useHotkeySequence, useHotkeySequenceRecorder, UseHotkeySequences as useHotkeySequences, UseHotkeys as useHotkeys, useKeyHold };
16
+ export { DefaultHotkeysOptions, type ElementHotkeyOptions, EmberAppHotkeyList, EmberCustomHotkeys, EmberHotkeyCommonOptions, EmberHotkeyDefinition, EmberHotkeyRecorder, EmberHotkeyState, EmberHotkeyTarget, EmberHotkeysScope, HotkeyRegistrationsResult, type MaybeGetter, createHotkeysScope, OnHotkey as onHotkey, OnHotkeys as onHotkeys, useAppHotkeyList, useAppHotkeys, useCustomHotkeys, useHeldKeyCodes, useHeldKeys, UseHotkey as useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations, UseHotkeys as useHotkeys, useKeyHold };
package/dist/index.js CHANGED
@@ -1,18 +1,16 @@
1
1
  import { UseHotkey } from "./useHotkey.js";
2
2
  import { UseHotkeys } from "./useHotkeys.js";
3
- import { UseHotkeySequence } from "./useHotkeySequence.js";
4
- import { UseHotkeySequences } from "./useHotkeySequences.js";
5
3
  import { useHeldKeys } from "./useHeldKeys.js";
6
4
  import { useHeldKeyCodes } from "./useHeldKeyCodes.js";
7
5
  import { useKeyHold } from "./useKeyHold.js";
8
6
  import { useHotkeyHint } from "./useHotkeyHint.js";
9
7
  import { useHotkeyRegistrations } from "./useHotkeyRegistrations.js";
10
8
  import { useHotkeyRecorder } from "./useHotkeyRecorder.js";
11
- import { useHotkeySequenceRecorder } from "./useHotkeySequenceRecorder.js";
9
+ import { useAppHotkeyList, useAppHotkeys, useCustomHotkeys } from "./useAppHotkeys.js";
12
10
  import { OnHotkey } from "./onHotkey.js";
13
11
  import { OnHotkeys } from "./onHotkeys.js";
14
12
  import { createHotkeysScope } from "./createHotkeysScope.js";
15
13
 
16
14
  export * from "@tanstack/hotkeys"
17
15
 
18
- export { createHotkeysScope, OnHotkey as onHotkey, OnHotkeys as onHotkeys, useHeldKeyCodes, useHeldKeys, UseHotkey as useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations, UseHotkeySequence as useHotkeySequence, useHotkeySequenceRecorder, UseHotkeySequences as useHotkeySequences, UseHotkeys as useHotkeys, useKeyHold };
16
+ export { createHotkeysScope, OnHotkey as onHotkey, OnHotkeys as onHotkeys, useAppHotkeyList, useAppHotkeys, useCustomHotkeys, useHeldKeyCodes, useHeldKeys, UseHotkey as useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations, UseHotkeys as useHotkeys, useKeyHold };
@@ -1,19 +1,16 @@
1
1
  import { ElementHotkeyOptions } from "./onHotkeys.js";
2
- import { HotkeyCallback, RegisterableHotkey } from "@tanstack/hotkeys";
2
+ import { AppHotkey, HotkeyCallback, RegisterableHotkey } from "@tanstack/hotkeys";
3
3
  import Modifier from "ember-modifier";
4
4
  //#region src/onHotkey.d.ts
5
- /** Attaches a shortcut to the element that owns the modifier. */
5
+ /** Attaches a hotkey (chord, sequence, or app hotkey) to the element that owns the modifier. */
6
6
  export declare class OnHotkey extends Modifier<{
7
7
  Element: HTMLElement;
8
8
  Args: {
9
- Positional: [RegisterableHotkey, HotkeyCallback];
9
+ Positional: [RegisterableHotkey | AppHotkey, HotkeyCallback];
10
10
  Named: ElementHotkeyOptions;
11
11
  };
12
12
  }> {
13
13
  private bindings;
14
- private definition;
15
- private cleanup;
16
- modify(element: HTMLElement, [hotkey, callback]: [RegisterableHotkey, HotkeyCallback], options: ElementHotkeyOptions): void;
17
- private update;
14
+ modify(element: HTMLElement, [hotkey, callback]: [RegisterableHotkey | AppHotkey, HotkeyCallback], options: ElementHotkeyOptions): void;
18
15
  }
19
16
  //#endregion
package/dist/onHotkey.js CHANGED
@@ -1,28 +1,16 @@
1
- import { isDestroyed, isDestroying, registerDestructor } from "@ember/destroyable";
2
- import { scheduleOnce } from "@ember/runloop";
3
- import { createHotkeyBindings } from "@tanstack/hotkeys/adapter";
1
+ import { createOwnedBindings } from "./bindings.js";
4
2
  import Modifier from "ember-modifier";
5
3
 
6
4
  //#region src/onHotkey.ts
7
- /** Attaches a shortcut to the element that owns the modifier. */
5
+ /** Attaches a hotkey (chord, sequence, or app hotkey) to the element that owns the modifier. */
8
6
  var OnHotkey = class extends Modifier {
9
- bindings = createHotkeyBindings();
10
- definition;
11
- cleanup = registerDestructor(this, () => this.bindings.destroy());
7
+ bindings = createOwnedBindings(this);
12
8
  modify(element, [hotkey, callback], options) {
13
- this.cleanup;
14
- this.definition = {
15
- hotkey: typeof hotkey === "string" ? hotkey : { ...hotkey },
9
+ this.bindings.set([{
10
+ hotkey,
16
11
  callback,
17
- options: {
18
- ...options,
19
- target: element
20
- }
21
- };
22
- scheduleOnce("afterRender", this, this.update);
23
- }
24
- update() {
25
- if (this.definition && !isDestroying(this) && !isDestroyed(this)) this.bindings.update([this.definition]);
12
+ options
13
+ }], {}, element);
26
14
  }
27
15
  };
28
16
 
@@ -1,22 +1,17 @@
1
- import { HotkeyOptions } from "@tanstack/hotkeys";
2
- import { HotkeyDefinition } from "@tanstack/hotkeys/adapter";
1
+ import { EmberHotkeyCommonOptions, EmberHotkeyDefinition } from "./types.js";
3
2
  import Modifier from "ember-modifier";
4
3
  //#region src/onHotkeys.d.ts
5
4
  /** Options for a modifier whose target is always its containing element. */
6
- export type ElementHotkeyOptions = Omit<HotkeyOptions, 'target'>;
7
- /** Attach a reactive list of shortcuts directly to an element. */
5
+ export type ElementHotkeyOptions = Omit<EmberHotkeyCommonOptions, 'target'>;
6
+ /** Attach a reactive list of hotkeys directly to an element. */
8
7
  export declare class OnHotkeys extends Modifier<{
9
8
  Element: HTMLElement;
10
9
  Args: {
11
- Positional: [Array<HotkeyDefinition>];
10
+ Positional: [ReadonlyArray<EmberHotkeyDefinition>];
12
11
  Named: ElementHotkeyOptions;
13
12
  };
14
13
  }> {
15
14
  private bindings;
16
- private definitions;
17
- private options;
18
- private cleanup;
19
- modify(element: HTMLElement, [definitions]: [Array<HotkeyDefinition>], options: ElementHotkeyOptions): void;
20
- private update;
15
+ modify(element: HTMLElement, [definitions]: [ReadonlyArray<EmberHotkeyDefinition>], options: ElementHotkeyOptions): void;
21
16
  }
22
17
  //#endregion
package/dist/onHotkeys.js CHANGED
@@ -1,33 +1,12 @@
1
- import { isDestroyed, isDestroying, registerDestructor } from "@ember/destroyable";
2
- import { scheduleOnce } from "@ember/runloop";
3
- import { createHotkeyBindings } from "@tanstack/hotkeys/adapter";
1
+ import { createOwnedBindings } from "./bindings.js";
4
2
  import Modifier from "ember-modifier";
5
3
 
6
4
  //#region src/onHotkeys.ts
7
- /** Attach a reactive list of shortcuts directly to an element. */
5
+ /** Attach a reactive list of hotkeys directly to an element. */
8
6
  var OnHotkeys = class extends Modifier {
9
- bindings = createHotkeyBindings();
10
- definitions = [];
11
- options = {};
12
- cleanup = registerDestructor(this, () => this.bindings.destroy());
7
+ bindings = createOwnedBindings(this);
13
8
  modify(element, [definitions], options) {
14
- this.cleanup;
15
- this.definitions = definitions.map((definition) => ({
16
- ...definition,
17
- hotkey: typeof definition.hotkey === "string" ? definition.hotkey : { ...definition.hotkey },
18
- options: {
19
- ...definition.options,
20
- target: element
21
- }
22
- }));
23
- this.options = {
24
- ...options,
25
- target: element
26
- };
27
- scheduleOnce("afterRender", this, this.update);
28
- }
29
- update() {
30
- if (!isDestroying(this) && !isDestroyed(this)) this.bindings.update(this.definitions, this.options);
9
+ this.bindings.set(definitions, options, element);
31
10
  }
32
11
  };
33
12
 
package/dist/types.d.ts CHANGED
@@ -1,49 +1,61 @@
1
- import { Hotkey, HotkeyOptions, HotkeyRecorderOptions, HotkeyRegistrationView, HotkeySequence, HotkeySequenceRecorderOptions, SequenceOptions, SequenceRegistrationView } from "@tanstack/hotkeys";
1
+ import { AppHotkey, AppHotkeyListItem, CustomHotkeys, Hotkey, HotkeyChordRecorderOptions, HotkeyRegistrationView, HotkeySequence, HotkeySequenceRecorderOptions, RegisteredAppHotkeys, Target } from "@tanstack/hotkeys";
2
+ import { AdapterCommonOptions, AdapterHotkeyDefinition } from "@tanstack/hotkeys/adapter";
2
3
  //#region src/types.d.ts
3
4
  /** A reactive value. Read `.value` where the framework tracks dependencies. */
4
5
  export interface EmberHotkeyState<T> {
5
6
  readonly value: T;
6
7
  }
8
+ /** The element to listen on. A `null` target skips registration until it is set. */
9
+ export type EmberHotkeyTarget = Target | null;
10
+ /** One hotkey for `useHotkeys` and `onHotkeys`: a chord, a sequence, or an app hotkey. */
11
+ export type EmberHotkeyDefinition = AdapterHotkeyDefinition<EmberHotkeyTarget>;
12
+ /** Options for every kind; each applies to the kinds that support it. */
13
+ export type EmberHotkeyCommonOptions = AdapterCommonOptions<EmberHotkeyTarget>;
7
14
  /** Recording state and controls owned by the containing component. */
8
- export interface EmberHotkeyRecorder {
15
+ export interface EmberHotkeyRecorder<THotkey extends Hotkey = Hotkey> {
9
16
  /** Whether the recorder is listening for a shortcut. */
10
17
  readonly isRecording: boolean;
11
- /** The last recorded binding, including brackets for physical codes. */
12
- readonly recordedHotkey: Hotkey | null;
18
+ /** Chords captured so far in a multi-step recording. */
19
+ readonly steps: HotkeySequence;
20
+ /** The last committed hotkey, including brackets for physical codes. */
21
+ readonly recordedHotkey: THotkey | null;
13
22
  /** Start a new recording session. */
14
23
  startRecording: () => void;
15
24
  /** Stop and clear recorder state without calling onRecord or onCancel. */
16
25
  stopRecording: () => void;
17
26
  /** Stop, clear recorder state, and call onCancel. */
18
27
  cancelRecording: () => void;
19
- }
20
- /** Sequence recording state and controls. */
21
- export interface EmberHotkeySequenceRecorder {
22
- /** Whether the recorder is listening for chords. */
23
- readonly isRecording: boolean;
24
- /** Chords captured in the current session. */
25
- readonly steps: HotkeySequence;
26
- /** The last committed sequence. */
27
- readonly recordedSequence: HotkeySequence | null;
28
- /** Start a new recording session. */
29
- startRecording: () => void;
30
- /** Stop without committing or calling onCancel. */
31
- stopRecording: () => void;
32
- /** Discard the current session and call onCancel. */
33
- cancelRecording: () => void;
34
- /** Commit the current steps. Does nothing when no steps are recorded. */
28
+ /** Commit the current steps (multi-step recordings). */
35
29
  commitRecording: () => void;
36
30
  }
37
- /** Live registration views from the shared hotkey and sequence managers. */
31
+ /** Live registration views from the shared hotkey manager. */
38
32
  export interface HotkeyRegistrationsResult {
33
+ /** Every registration, chords and sequences. Narrow each view with `kind`. */
39
34
  readonly hotkeys: Array<HotkeyRegistrationView>;
40
- readonly sequences: Array<SequenceRegistrationView>;
35
+ }
36
+ /** Every app hotkey with its live status. */
37
+ export type EmberAppHotkeyList = EmberHotkeyState<Array<AppHotkeyListItem<RegisteredAppHotkeys[keyof RegisteredAppHotkeys]>>>;
38
+ /** Custom hotkey state and actions. */
39
+ export interface EmberCustomHotkeys {
40
+ /** The user's hotkeys that differ from the defaults. */
41
+ readonly value: CustomHotkeys;
42
+ /**
43
+ * Sets the user's hotkey for an app hotkey. `null` unbinds it.
44
+ * Returns conflicting app hotkeys; when any exist, nothing changes.
45
+ */
46
+ setCustomHotkey: (id: keyof RegisteredAppHotkeys & string, hotkey: Hotkey | null) => Array<AppHotkey>;
47
+ /** Restores the default hotkey for an app hotkey. */
48
+ resetCustomHotkey: (id: keyof RegisteredAppHotkeys & string) => void;
49
+ /** Restores every default hotkey. */
50
+ resetCustomHotkeys: () => void;
51
+ /** Other app hotkeys that would collide if `id` used `hotkey`. */
52
+ findConflicts: (id: keyof RegisteredAppHotkeys & string, hotkey: Hotkey) => Array<AppHotkey>;
41
53
  }
42
54
  /** Shared defaults; call-specific options take precedence. */
43
55
  export interface DefaultHotkeysOptions {
44
- hotkey?: HotkeyOptions;
45
- hotkeySequence?: SequenceOptions;
46
- hotkeyRecorder?: Partial<HotkeyRecorderOptions>;
47
- hotkeySequenceRecorder?: Partial<HotkeySequenceRecorderOptions>;
56
+ /** Defaults for every hotkey. `requireReset` applies to chords and `timeout` to sequences. */
57
+ hotkey?: EmberHotkeyCommonOptions;
58
+ /** Defaults for every recorder. */
59
+ hotkeyRecorder?: Partial<Omit<HotkeyChordRecorderOptions & HotkeySequenceRecorderOptions, 'maxSteps' | 'onRecord' | 'validate'>>;
48
60
  }
49
61
  //#endregion
@@ -0,0 +1,18 @@
1
+ import { EmberAppHotkeyList, EmberCustomHotkeys, EmberHotkeyState } from "./types.js";
2
+ import { RegisteredAppHotkeys } from "@tanstack/hotkeys";
3
+ //#region src/useAppHotkeys.d.ts
4
+ /** Reads the effective app hotkeys from `provideAppHotkeys`, typed by `Register`. */
5
+ export declare function useAppHotkeys(owner: object): EmberHotkeyState<RegisteredAppHotkeys>;
6
+ /**
7
+ * Lists every app hotkey with its live status, for cheat sheets and settings
8
+ * screens. App hotkeys whose components have not rendered are `'inactive'`;
9
+ * those without a hotkey are `'unbound'`.
10
+ */
11
+ export declare function useAppHotkeyList(owner: object): EmberAppHotkeyList;
12
+ /**
13
+ * Reads and changes the user's custom hotkeys. Changes apply to every
14
+ * registered app hotkey immediately and are passed to
15
+ * `onCustomHotkeysChange`, ready to save.
16
+ */
17
+ export declare function useCustomHotkeys(owner: object): EmberCustomHotkeys;
18
+ //#endregion
@@ -0,0 +1,48 @@
1
+ import { trackPublishedAppHotkeys } from "./utils.js";
2
+ import { useHotkeyRegistrations } from "./useHotkeyRegistrations.js";
3
+
4
+ //#region src/useAppHotkeys.ts
5
+ function requireStore(store) {
6
+ const current = store();
7
+ if (!current) throw new Error("App hotkeys require provideAppHotkeys()");
8
+ return current;
9
+ }
10
+ /** Reads the effective app hotkeys from `provideAppHotkeys`, typed by `Register`. */
11
+ function useAppHotkeys(owner) {
12
+ const store = trackPublishedAppHotkeys(owner);
13
+ return { get value() {
14
+ return requireStore(store).appHotkeys;
15
+ } };
16
+ }
17
+ /**
18
+ * Lists every app hotkey with its live status, for cheat sheets and settings
19
+ * screens. App hotkeys whose components have not rendered are `'inactive'`;
20
+ * those without a hotkey are `'unbound'`.
21
+ */
22
+ function useAppHotkeyList(owner) {
23
+ const store = trackPublishedAppHotkeys(owner);
24
+ const registrations = useHotkeyRegistrations(owner);
25
+ return { get value() {
26
+ return requireStore(store).getList(registrations.hotkeys);
27
+ } };
28
+ }
29
+ /**
30
+ * Reads and changes the user's custom hotkeys. Changes apply to every
31
+ * registered app hotkey immediately and are passed to
32
+ * `onCustomHotkeysChange`, ready to save.
33
+ */
34
+ function useCustomHotkeys(owner) {
35
+ const store = trackPublishedAppHotkeys(owner);
36
+ return {
37
+ get value() {
38
+ return requireStore(store).customHotkeys;
39
+ },
40
+ setCustomHotkey: (id, hotkey) => requireStore(store).setCustomHotkey(id, hotkey),
41
+ resetCustomHotkey: (id) => requireStore(store).resetCustomHotkey(id),
42
+ resetCustomHotkeys: () => requireStore(store).resetCustomHotkeys(),
43
+ findConflicts: (id, hotkey) => requireStore(store).findConflicts(id, hotkey)
44
+ };
45
+ }
46
+
47
+ //#endregion
48
+ export { useAppHotkeyList, useAppHotkeys, useCustomHotkeys };
@@ -1,18 +1,21 @@
1
- import { HotkeyCallback, HotkeyOptions, RegisterableHotkey } from "@tanstack/hotkeys";
1
+ import { EmberHotkeyCommonOptions } from "./types.js";
2
+ import { AppHotkey, HotkeyCallback, RegisterableHotkey } from "@tanstack/hotkeys";
2
3
  import Helper from "@ember/component/helper";
3
4
  //#region src/useHotkey.d.ts
4
- /** Use {{useHotkey "Mod+S" this.save enabled=this.enabled}} in a template. */
5
+ /**
6
+ * Registers a hotkey for the helper's template lifecycle: a chord such as
7
+ * `"Mod+S"`, a sequence such as `(array "G" "G")`, or an app hotkey.
8
+ *
9
+ * Use `{{useHotkey "Mod+S" this.save enabled=this.enabled}}` in a template.
10
+ */
5
11
  export declare class UseHotkey extends Helper<{
6
12
  Args: {
7
- Positional: [RegisterableHotkey, HotkeyCallback];
8
- Named: HotkeyOptions;
13
+ Positional: [RegisterableHotkey | AppHotkey, HotkeyCallback];
14
+ Named: EmberHotkeyCommonOptions;
9
15
  };
10
16
  Return: void;
11
17
  }> {
12
18
  private bindings;
13
- private definition;
14
- private cleanup;
15
- compute([hotkey, callback]: [RegisterableHotkey, HotkeyCallback], options: HotkeyOptions): void;
16
- private update;
19
+ compute([hotkey, callback]: [RegisterableHotkey | AppHotkey, HotkeyCallback], options: EmberHotkeyCommonOptions): void;
17
20
  }
18
21
  //#endregion
package/dist/useHotkey.js CHANGED
@@ -1,25 +1,21 @@
1
+ import { createOwnedBindings } from "./bindings.js";
1
2
  import Helper from "@ember/component/helper";
2
- import { isDestroyed, isDestroying, registerDestructor } from "@ember/destroyable";
3
- import { scheduleOnce } from "@ember/runloop";
4
- import { createHotkeyBindings } from "@tanstack/hotkeys/adapter";
5
3
 
6
4
  //#region src/useHotkey.ts
7
- /** Use {{useHotkey "Mod+S" this.save enabled=this.enabled}} in a template. */
5
+ /**
6
+ * Registers a hotkey for the helper's template lifecycle: a chord such as
7
+ * `"Mod+S"`, a sequence such as `(array "G" "G")`, or an app hotkey.
8
+ *
9
+ * Use `{{useHotkey "Mod+S" this.save enabled=this.enabled}}` in a template.
10
+ */
8
11
  var UseHotkey = class extends Helper {
9
- bindings = createHotkeyBindings();
10
- definition;
11
- cleanup = registerDestructor(this, () => this.bindings.destroy());
12
+ bindings = createOwnedBindings(this);
12
13
  compute([hotkey, callback], options) {
13
- this.cleanup;
14
- this.definition = {
15
- hotkey: typeof hotkey === "string" ? hotkey : { ...hotkey },
14
+ this.bindings.set([{
15
+ hotkey,
16
16
  callback,
17
- options: { ...options }
18
- };
19
- scheduleOnce("afterRender", this, this.update);
20
- }
21
- update() {
22
- if (this.definition && !isDestroying(this) && !isDestroyed(this)) this.bindings.update([this.definition]);
17
+ options
18
+ }], {});
23
19
  }
24
20
  };
25
21
 
@@ -1,7 +1,11 @@
1
- import { MaybeGetter } from "./utils.js";
2
1
  import { EmberHotkeyState } from "./types.js";
3
- import { HeldModifierOptions, RegisterableHotkey } from "@tanstack/hotkeys";
2
+ import { MaybeGetter } from "./utils.js";
3
+ import { AppHotkey, HeldModifierOptions, RegisterableHotkey } from "@tanstack/hotkeys";
4
4
  //#region src/useHotkeyHint.d.ts
5
- /** Recomputes for held modifiers and tracked changes to the binding or options. */
6
- export declare function useHotkeyHint(owner: object, hotkey: MaybeGetter<RegisterableHotkey>, options?: MaybeGetter<HeldModifierOptions>): EmberHotkeyState<boolean>;
5
+ /**
6
+ * Recomputes for held modifiers and tracked changes to the hotkey or options.
7
+ * A sequence reveals its hint from its first step, and an app hotkey uses its
8
+ * effective hotkey (false while unbound).
9
+ */
10
+ export declare function useHotkeyHint(owner: object, hotkey: MaybeGetter<RegisterableHotkey | AppHotkey>, options?: MaybeGetter<HeldModifierOptions>): EmberHotkeyState<boolean>;
7
11
  //#endregion
@@ -1,10 +1,19 @@
1
- import { read, select } from "./utils.js";
2
- import { getKeyStateTracker, matchesHeldModifiers } from "@tanstack/hotkeys";
1
+ import { read, select, trackPublishedAppHotkeys } from "./utils.js";
2
+ import { getKeyStateTracker, isAppHotkey, matchesHeldModifiers, resolveAppHotkey } from "@tanstack/hotkeys";
3
3
 
4
4
  //#region src/useHotkeyHint.ts
5
- /** Recomputes for held modifiers and tracked changes to the binding or options. */
5
+ /**
6
+ * Recomputes for held modifiers and tracked changes to the hotkey or options.
7
+ * A sequence reveals its hint from its first step, and an app hotkey uses its
8
+ * effective hotkey (false while unbound).
9
+ */
6
10
  function useHotkeyHint(owner, hotkey, options = {}) {
7
- return select(owner, getKeyStateTracker().store, (state) => matchesHeldModifiers(read(hotkey), state.heldKeys, read(options)));
11
+ const appHotkeys = trackPublishedAppHotkeys(owner);
12
+ return select(owner, getKeyStateTracker().store, (state) => {
13
+ const value = read(hotkey);
14
+ const resolved = isAppHotkey(value) ? resolveAppHotkey(value, appHotkeys()).hotkey : value;
15
+ return resolved !== null && matchesHeldModifiers(resolved, state.heldKeys, read(options));
16
+ });
8
17
  }
9
18
 
10
19
  //#endregion
@@ -1,7 +1,16 @@
1
- import { MaybeGetter } from "./utils.js";
2
1
  import { EmberHotkeyRecorder } from "./types.js";
3
- import { HotkeyRecorderOptions } from "@tanstack/hotkeys";
2
+ import { HotkeyChord, HotkeyChordRecorderOptions, HotkeyRecorderOptions, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
4
3
  //#region src/useHotkeyRecorder.d.ts
5
- /** Creates an owned recorder with autotracked state and current callback options. */
6
- export declare function useHotkeyRecorder(owner: object, options: MaybeGetter<HotkeyRecorderOptions>): EmberHotkeyRecorder;
4
+ /**
5
+ * Creates an owned recorder with autotracked state and current callback
6
+ * options. With the default `maxSteps: 1`, the first valid chord is recorded.
7
+ * With a larger `maxSteps`, each chord adds a step; the recording commits on
8
+ * Enter (unless `commitKeys: 'none'`), after `idleTimeoutMs`, or at
9
+ * `maxSteps`, and `onRecord` receives a chord (one step) or a sequence.
10
+ */
11
+ export declare function useHotkeyRecorder(owner: object, options: HotkeyChordRecorderOptions): EmberHotkeyRecorder<HotkeyChord>;
12
+ export declare function useHotkeyRecorder(owner: object, options: HotkeySequenceRecorderOptions): EmberHotkeyRecorder;
13
+ export declare function useHotkeyRecorder(owner: object, options: () => HotkeySequenceRecorderOptions): EmberHotkeyRecorder;
14
+ export declare function useHotkeyRecorder(owner: object, options: () => HotkeyChordRecorderOptions): EmberHotkeyRecorder<HotkeyChord>;
15
+ export declare function useHotkeyRecorder(owner: object, options: () => HotkeyRecorderOptions): EmberHotkeyRecorder;
7
16
  //#endregion
@@ -3,7 +3,6 @@ import { HotkeyRecorder } from "@tanstack/hotkeys";
3
3
  import { isDestroyed, isDestroying, registerDestructor } from "@ember/destroyable";
4
4
 
5
5
  //#region src/useHotkeyRecorder.ts
6
- /** Creates an owned recorder with autotracked state and current callback options. */
7
6
  function useHotkeyRecorder(owner, options) {
8
7
  const recorder = new HotkeyRecorder(() => read(options));
9
8
  const state = select(owner, recorder.store, (value) => value);
@@ -12,6 +11,9 @@ function useHotkeyRecorder(owner, options) {
12
11
  get isRecording() {
13
12
  return state.value.isRecording;
14
13
  },
14
+ get steps() {
15
+ return state.value.steps;
16
+ },
15
17
  get recordedHotkey() {
16
18
  return state.value.recordedHotkey;
17
19
  },
@@ -20,7 +22,8 @@ function useHotkeyRecorder(owner, options) {
20
22
  recorder.start();
21
23
  },
22
24
  stopRecording: () => recorder.stop(),
23
- cancelRecording: () => recorder.cancel()
25
+ cancelRecording: () => recorder.cancel(),
26
+ commitRecording: () => recorder.commit()
24
27
  };
25
28
  }
26
29
 
@@ -1,5 +1,5 @@
1
1
  import { HotkeyRegistrationsResult } from "./types.js";
2
2
  //#region src/useHotkeyRegistrations.d.ts
3
- /** Reads public snapshots of hotkey and sequence registrations. */
3
+ /** Reads public snapshots of every registration, chords and sequences. */
4
4
  export declare function useHotkeyRegistrations(owner: object): HotkeyRegistrationsResult;
5
5
  //#endregion
@@ -1,19 +1,13 @@
1
1
  import { select } from "./utils.js";
2
- import { getHotkeyManager, getSequenceManager, toHotkeyRegistrationView } from "@tanstack/hotkeys";
2
+ import { getHotkeyManager, toHotkeyRegistrationView } from "@tanstack/hotkeys";
3
3
 
4
4
  //#region src/useHotkeyRegistrations.ts
5
- /** Reads public snapshots of hotkey and sequence registrations. */
5
+ /** Reads public snapshots of every registration, chords and sequences. */
6
6
  function useHotkeyRegistrations(owner) {
7
7
  const hotkeys = select(owner, getHotkeyManager().registrations, (state) => Array.from(state.values()).map(toHotkeyRegistrationView));
8
- const sequences = select(owner, getSequenceManager().registrations, (state) => Array.from(state.values()));
9
- return {
10
- get hotkeys() {
11
- return hotkeys.value;
12
- },
13
- get sequences() {
14
- return sequences.value;
15
- }
16
- };
8
+ return { get hotkeys() {
9
+ return hotkeys.value;
10
+ } };
17
11
  }
18
12
 
19
13
  //#endregion
@@ -1,20 +1,15 @@
1
- import { HotkeyOptions } from "@tanstack/hotkeys";
1
+ import { EmberHotkeyCommonOptions, EmberHotkeyDefinition } from "./types.js";
2
2
  import Helper from "@ember/component/helper";
3
- import { HotkeyDefinition } from "@tanstack/hotkeys/adapter";
4
3
  //#region src/useHotkeys.d.ts
5
- /** Registers a reactive list of shortcuts with the helper's template lifecycle. */
4
+ /** Registers a reactive list of hotkeys with the helper's template lifecycle. */
6
5
  export declare class UseHotkeys extends Helper<{
7
6
  Args: {
8
- Positional: [Array<HotkeyDefinition>];
9
- Named: HotkeyOptions;
7
+ Positional: [ReadonlyArray<EmberHotkeyDefinition>];
8
+ Named: EmberHotkeyCommonOptions;
10
9
  };
11
10
  Return: void;
12
11
  }> {
13
12
  private bindings;
14
- private definitions;
15
- private options;
16
- private cleanup;
17
- compute([definitions]: [Array<HotkeyDefinition>], options: HotkeyOptions): void;
18
- private update;
13
+ compute([definitions]: [ReadonlyArray<EmberHotkeyDefinition>], options: EmberHotkeyCommonOptions): void;
19
14
  }
20
15
  //#endregion
@@ -1,27 +1,12 @@
1
+ import { createOwnedBindings } from "./bindings.js";
1
2
  import Helper from "@ember/component/helper";
2
- import { isDestroyed, isDestroying, registerDestructor } from "@ember/destroyable";
3
- import { scheduleOnce } from "@ember/runloop";
4
- import { createHotkeyBindings } from "@tanstack/hotkeys/adapter";
5
3
 
6
4
  //#region src/useHotkeys.ts
7
- /** Registers a reactive list of shortcuts with the helper's template lifecycle. */
5
+ /** Registers a reactive list of hotkeys with the helper's template lifecycle. */
8
6
  var UseHotkeys = class extends Helper {
9
- bindings = createHotkeyBindings();
10
- definitions = [];
11
- options = {};
12
- cleanup = registerDestructor(this, () => this.bindings.destroy());
7
+ bindings = createOwnedBindings(this);
13
8
  compute([definitions], options) {
14
- this.cleanup;
15
- this.definitions = definitions.map((definition) => ({
16
- ...definition,
17
- hotkey: typeof definition.hotkey === "string" ? definition.hotkey : { ...definition.hotkey },
18
- options: { ...definition.options }
19
- }));
20
- this.options = { ...options };
21
- scheduleOnce("afterRender", this, this.update);
22
- }
23
- update() {
24
- if (!isDestroying(this) && !isDestroyed(this)) this.bindings.update(this.definitions, this.options);
9
+ this.bindings.set(definitions, options);
25
10
  }
26
11
  };
27
12
 
@@ -1,5 +1,5 @@
1
- import { MaybeGetter } from "./utils.js";
2
1
  import { EmberHotkeyState } from "./types.js";
2
+ import { MaybeGetter } from "./utils.js";
3
3
  import { IndividualKey } from "@tanstack/hotkeys";
4
4
  //#region src/useKeyHold.d.ts
5
5
  /** Getter arguments can read changing tracked component properties. */
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import "@tanstack/hotkeys";
1
2
  import "@tanstack/store";
2
3
  //#region src/utils.d.ts
3
4
  export type MaybeGetter<T> = T | (() => T);
package/dist/utils.js CHANGED
@@ -1,4 +1,6 @@
1
+ import { getHotkeyManager } from "@tanstack/hotkeys";
1
2
  import { registerDestructor } from "@ember/destroyable";
3
+ import { subscribePublishedAppHotkeys } from "@tanstack/hotkeys/adapter";
2
4
  import { trackedObject } from "@ember/reactive/collections";
3
5
 
4
6
  //#region src/utils.ts
@@ -15,6 +17,20 @@ function select(owner, store, selector) {
15
17
  return selector(state.snapshot);
16
18
  } };
17
19
  }
20
+ /**
21
+ * Returns the store published by `provideAppHotkeys`. Reading it tracks app
22
+ * hotkey and custom hotkey changes for the owner's lifetime.
23
+ */
24
+ function trackPublishedAppHotkeys(owner) {
25
+ const state = trackedObject({ version: 0 });
26
+ registerDestructor(owner, subscribePublishedAppHotkeys(() => {
27
+ state.version++;
28
+ }));
29
+ return () => {
30
+ state.version;
31
+ return getHotkeyManager().appHotkeys.state;
32
+ };
33
+ }
18
34
 
19
35
  //#endregion
20
- export { read, select };
36
+ export { read, select, trackPublishedAppHotkeys };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ember-hotkeys",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Ember adapter for TanStack Hotkeys",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -38,7 +38,7 @@
38
38
  ],
39
39
  "dependencies": {
40
40
  "@embroider/addon-shim": "^1.10.3",
41
- "@tanstack/hotkeys": "0.11.0",
41
+ "@tanstack/hotkeys": "0.12.1",
42
42
  "@tanstack/store": "^0.11.2",
43
43
  "ember-modifier": "4.2.2"
44
44
  },
@@ -1,18 +0,0 @@
1
- import { HotkeyCallback, HotkeySequence, SequenceOptions } from "@tanstack/hotkeys";
2
- import Helper from "@ember/component/helper";
3
- //#region src/useHotkeySequence.d.ts
4
- /** Use {{useHotkeySequence this.sequence this.run}} in a template. */
5
- export declare class UseHotkeySequence extends Helper<{
6
- Args: {
7
- Positional: [HotkeySequence, HotkeyCallback];
8
- Named: SequenceOptions;
9
- };
10
- Return: void;
11
- }> {
12
- private bindings;
13
- private definition;
14
- private cleanup;
15
- compute([sequence, callback]: [HotkeySequence, HotkeyCallback], options: SequenceOptions): void;
16
- private update;
17
- }
18
- //#endregion
@@ -1,27 +0,0 @@
1
- import Helper from "@ember/component/helper";
2
- import { isDestroyed, isDestroying, registerDestructor } from "@ember/destroyable";
3
- import { scheduleOnce } from "@ember/runloop";
4
- import { createHotkeySequenceBindings } from "@tanstack/hotkeys/adapter";
5
-
6
- //#region src/useHotkeySequence.ts
7
- /** Use {{useHotkeySequence this.sequence this.run}} in a template. */
8
- var UseHotkeySequence = class extends Helper {
9
- bindings = createHotkeySequenceBindings();
10
- definition;
11
- cleanup = registerDestructor(this, () => this.bindings.destroy());
12
- compute([sequence, callback], options) {
13
- this.cleanup;
14
- this.definition = {
15
- sequence: [...sequence],
16
- callback,
17
- options: { ...options }
18
- };
19
- scheduleOnce("afterRender", this, this.update);
20
- }
21
- update() {
22
- if (this.definition && !isDestroying(this) && !isDestroyed(this)) this.bindings.update([this.definition]);
23
- }
24
- };
25
-
26
- //#endregion
27
- export { UseHotkeySequence };
@@ -1,7 +0,0 @@
1
- import { MaybeGetter } from "./utils.js";
2
- import { EmberHotkeySequenceRecorder } from "./types.js";
3
- import { HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
4
- //#region src/useHotkeySequenceRecorder.d.ts
5
- /** Creates an owned sequence recorder with explicit commit and cancel controls. */
6
- export declare function useHotkeySequenceRecorder(owner: object, options: MaybeGetter<HotkeySequenceRecorderOptions>): EmberHotkeySequenceRecorder;
7
- //#endregion
@@ -1,32 +0,0 @@
1
- import { read, select } from "./utils.js";
2
- import { HotkeySequenceRecorder } from "@tanstack/hotkeys";
3
- import { isDestroyed, isDestroying, registerDestructor } from "@ember/destroyable";
4
-
5
- //#region src/useHotkeySequenceRecorder.ts
6
- /** Creates an owned sequence recorder with explicit commit and cancel controls. */
7
- function useHotkeySequenceRecorder(owner, options) {
8
- const recorder = new HotkeySequenceRecorder(() => read(options));
9
- const state = select(owner, recorder.store, (value) => value);
10
- registerDestructor(owner, () => recorder.destroy());
11
- return {
12
- get isRecording() {
13
- return state.value.isRecording;
14
- },
15
- get steps() {
16
- return state.value.steps;
17
- },
18
- get recordedSequence() {
19
- return state.value.recordedSequence;
20
- },
21
- startRecording() {
22
- if (isDestroying(owner) || isDestroyed(owner)) return;
23
- recorder.start();
24
- },
25
- stopRecording: () => recorder.stop(),
26
- cancelRecording: () => recorder.cancel(),
27
- commitRecording: () => recorder.commit()
28
- };
29
- }
30
-
31
- //#endregion
32
- export { useHotkeySequenceRecorder };
@@ -1,20 +0,0 @@
1
- import { SequenceOptions } from "@tanstack/hotkeys";
2
- import Helper from "@ember/component/helper";
3
- import { HotkeySequenceDefinition } from "@tanstack/hotkeys/adapter";
4
- //#region src/useHotkeySequences.d.ts
5
- /** Registers a reactive list of sequences with the helper's template lifecycle. */
6
- export declare class UseHotkeySequences extends Helper<{
7
- Args: {
8
- Positional: [Array<HotkeySequenceDefinition>];
9
- Named: SequenceOptions;
10
- };
11
- Return: void;
12
- }> {
13
- private bindings;
14
- private definitions;
15
- private options;
16
- private cleanup;
17
- compute([definitions]: [Array<HotkeySequenceDefinition>], options: SequenceOptions): void;
18
- private update;
19
- }
20
- //#endregion
@@ -1,29 +0,0 @@
1
- import Helper from "@ember/component/helper";
2
- import { isDestroyed, isDestroying, registerDestructor } from "@ember/destroyable";
3
- import { scheduleOnce } from "@ember/runloop";
4
- import { createHotkeySequenceBindings } from "@tanstack/hotkeys/adapter";
5
-
6
- //#region src/useHotkeySequences.ts
7
- /** Registers a reactive list of sequences with the helper's template lifecycle. */
8
- var UseHotkeySequences = class extends Helper {
9
- bindings = createHotkeySequenceBindings();
10
- definitions = [];
11
- options = {};
12
- cleanup = registerDestructor(this, () => this.bindings.destroy());
13
- compute([definitions], options) {
14
- this.cleanup;
15
- this.definitions = definitions.map((definition) => ({
16
- ...definition,
17
- sequence: [...definition.sequence],
18
- options: { ...definition.options }
19
- }));
20
- this.options = { ...options };
21
- scheduleOnce("afterRender", this, this.update);
22
- }
23
- update() {
24
- if (!isDestroying(this) && !isDestroyed(this)) this.bindings.update(this.definitions, this.options);
25
- }
26
- };
27
-
28
- //#endregion
29
- export { UseHotkeySequences };