@tanstack/lit-hotkeys 0.14.0 → 0.15.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,11 @@
1
+ import { getHotkeyManager } from "@tanstack/hotkeys";
2
+ import { subscribePublishedAppHotkeys as subscribeAppHotkeys } from "@tanstack/hotkeys/adapter";
3
+
4
+ //#region src/controllers/_app-hotkeys.ts
5
+ /** The app hotkeys store published by `provideAppHotkeys`, if any. */
6
+ function getAppHotkeysStore() {
7
+ return getHotkeyManager().appHotkeys.state;
8
+ }
9
+
10
+ //#endregion
11
+ export { getAppHotkeysStore, subscribeAppHotkeys };
@@ -0,0 +1,48 @@
1
+ import { AppHotkey, AppHotkeyListItem, CustomHotkeys, Hotkey, RegisteredAppHotkeys } from "@tanstack/hotkeys";
2
+ import { ReactiveController, ReactiveControllerHost } from "lit";
3
+ //#region src/controllers/app-hotkeys.d.ts
4
+ /**
5
+ * A Lit ReactiveController for app hotkeys provided with `provideAppHotkeys`:
6
+ * the effective app hotkeys, a cheat sheet list with live status, and the
7
+ * user's custom hotkeys. Requests an update when any of them change.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * class CheatSheet extends LitElement {
12
+ * private app = new AppHotkeysController(this)
13
+ *
14
+ * render() {
15
+ * return html`<ul>
16
+ * ${this.app.list.map(
17
+ * ({ appHotkey, status }) => html`<li>${appHotkey.meta?.name}: ${status}</li>`,
18
+ * )}
19
+ * </ul>`
20
+ * }
21
+ * }
22
+ * ```
23
+ */
24
+ export declare class AppHotkeysController implements ReactiveController {
25
+ private _host;
26
+ private _unsubscribe;
27
+ constructor(_host: ReactiveControllerHost);
28
+ /** Effective app hotkeys, typed by `Register`. */
29
+ get appHotkeys(): RegisteredAppHotkeys;
30
+ /** Every app hotkey with its live status, in declaration order. */
31
+ get list(): Array<AppHotkeyListItem<RegisteredAppHotkeys[keyof RegisteredAppHotkeys]>>;
32
+ /** The user's hotkeys that differ from the defaults. */
33
+ get customHotkeys(): CustomHotkeys;
34
+ /**
35
+ * Sets the user's hotkey for an app hotkey. `null` unbinds it.
36
+ * Returns conflicting app hotkeys; when any exist, nothing changes.
37
+ */
38
+ setCustomHotkey(id: keyof RegisteredAppHotkeys & string, hotkey: Hotkey | null): Array<AppHotkey>;
39
+ /** Restores the default hotkey for an app hotkey. */
40
+ resetCustomHotkey(id: keyof RegisteredAppHotkeys & string): void;
41
+ /** Restores every default hotkey. */
42
+ resetCustomHotkeys(): void;
43
+ /** Other app hotkeys that would collide if `id` used `hotkey`. */
44
+ findConflicts(id: keyof RegisteredAppHotkeys & string, hotkey: Hotkey): Array<AppHotkey>;
45
+ hostConnected(): void;
46
+ hostDisconnected(): void;
47
+ }
48
+ //#endregion
@@ -0,0 +1,84 @@
1
+ import { getAppHotkeysStore, subscribeAppHotkeys } from "./_app-hotkeys.js";
2
+ import { getHotkeyManager, toHotkeyRegistrationView } from "@tanstack/hotkeys";
3
+
4
+ //#region src/controllers/app-hotkeys.ts
5
+ function requireStore() {
6
+ const store = getAppHotkeysStore();
7
+ if (!store) throw new Error("AppHotkeysController requires provideAppHotkeys()");
8
+ return store;
9
+ }
10
+ /**
11
+ * A Lit ReactiveController for app hotkeys provided with `provideAppHotkeys`:
12
+ * the effective app hotkeys, a cheat sheet list with live status, and the
13
+ * user's custom hotkeys. Requests an update when any of them change.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * class CheatSheet extends LitElement {
18
+ * private app = new AppHotkeysController(this)
19
+ *
20
+ * render() {
21
+ * return html`<ul>
22
+ * ${this.app.list.map(
23
+ * ({ appHotkey, status }) => html`<li>${appHotkey.meta?.name}: ${status}</li>`,
24
+ * )}
25
+ * </ul>`
26
+ * }
27
+ * }
28
+ * ```
29
+ */
30
+ var AppHotkeysController = class {
31
+ _host;
32
+ _unsubscribe;
33
+ constructor(_host) {
34
+ this._host = _host;
35
+ this._host.addController(this);
36
+ }
37
+ /** Effective app hotkeys, typed by `Register`. */
38
+ get appHotkeys() {
39
+ return requireStore().appHotkeys;
40
+ }
41
+ /** Every app hotkey with its live status, in declaration order. */
42
+ get list() {
43
+ return requireStore().getList(Array.from(getHotkeyManager().registrations.state.values()).map(toHotkeyRegistrationView));
44
+ }
45
+ /** The user's hotkeys that differ from the defaults. */
46
+ get customHotkeys() {
47
+ return requireStore().customHotkeys;
48
+ }
49
+ /**
50
+ * Sets the user's hotkey for an app hotkey. `null` unbinds it.
51
+ * Returns conflicting app hotkeys; when any exist, nothing changes.
52
+ */
53
+ setCustomHotkey(id, hotkey) {
54
+ return requireStore().setCustomHotkey(id, hotkey);
55
+ }
56
+ /** Restores the default hotkey for an app hotkey. */
57
+ resetCustomHotkey(id) {
58
+ requireStore().resetCustomHotkey(id);
59
+ }
60
+ /** Restores every default hotkey. */
61
+ resetCustomHotkeys() {
62
+ requireStore().resetCustomHotkeys();
63
+ }
64
+ /** Other app hotkeys that would collide if `id` used `hotkey`. */
65
+ findConflicts(id, hotkey) {
66
+ return requireStore().findConflicts(id, hotkey);
67
+ }
68
+ hostConnected() {
69
+ const update = () => this._host.requestUpdate();
70
+ const unsubscribeAppHotkeys = subscribeAppHotkeys(update);
71
+ const registrations = getHotkeyManager().registrations.subscribe(update);
72
+ this._unsubscribe = () => {
73
+ unsubscribeAppHotkeys();
74
+ registrations.unsubscribe();
75
+ };
76
+ }
77
+ hostDisconnected() {
78
+ this._unsubscribe?.();
79
+ this._unsubscribe = void 0;
80
+ }
81
+ };
82
+
83
+ //#endregion
84
+ export { AppHotkeysController };
@@ -1,4 +1,4 @@
1
- import { HeldModifierOptions, RegisterableHotkey } from "@tanstack/hotkeys";
1
+ import { AppHotkey, HeldModifierOptions, RegisterableHotkey } from "@tanstack/hotkeys";
2
2
  import { ReactiveController, ReactiveControllerHost } from "lit";
3
3
  //#region src/controllers/hotkey-hint.d.ts
4
4
  /** Modifier hint controller. Getter arguments can read reactive host properties. */
@@ -9,7 +9,7 @@ export declare class HotkeyHintController implements ReactiveController {
9
9
  private unsubscribe?;
10
10
  private visible;
11
11
  /** Attaches to the host; getter arguments can read changing reactive properties. */
12
- constructor(host: ReactiveControllerHost, hotkey: RegisterableHotkey | (() => RegisterableHotkey), options?: HeldModifierOptions | (() => HeldModifierOptions));
12
+ constructor(host: ReactiveControllerHost, hotkey: RegisterableHotkey | AppHotkey | (() => RegisterableHotkey | AppHotkey), options?: HeldModifierOptions | (() => HeldModifierOptions));
13
13
  /** Current visibility; combine with the action’s enabled state when rendering. */
14
14
  get value(): boolean;
15
15
  /** Subscribes on connection and requests renders only when hint visibility changes. */
@@ -1,4 +1,5 @@
1
- import { getKeyStateTracker, matchesHeldModifiers } from "@tanstack/hotkeys";
1
+ import { getAppHotkeysStore, subscribeAppHotkeys } from "./_app-hotkeys.js";
2
+ import { getKeyStateTracker, isAppHotkey, matchesHeldModifiers, resolveAppHotkey } from "@tanstack/hotkeys";
2
3
 
3
4
  //#region src/controllers/hotkey-hint.ts
4
5
  /** Modifier hint controller. Getter arguments can read reactive host properties. */
@@ -22,10 +23,15 @@ var HotkeyHintController = class {
22
23
  /** Subscribes on connection and requests renders only when hint visibility changes. */
23
24
  hostConnected() {
24
25
  this.update();
25
- const subscription = getKeyStateTracker().store.subscribe(() => {
26
+ const refresh = () => {
26
27
  if (this.update()) this.host.requestUpdate();
27
- });
28
- this.unsubscribe = () => subscription.unsubscribe();
28
+ };
29
+ const subscription = getKeyStateTracker().store.subscribe(refresh);
30
+ const unsubscribeAppHotkeys = subscribeAppHotkeys(refresh);
31
+ this.unsubscribe = () => {
32
+ subscription.unsubscribe();
33
+ unsubscribeAppHotkeys();
34
+ };
29
35
  }
30
36
  /** Re-evaluates getter arguments when host properties trigger an update. */
31
37
  hostUpdate() {
@@ -39,7 +45,9 @@ var HotkeyHintController = class {
39
45
  }
40
46
  /** Recomputes visibility and reports whether the host needs another render. */
41
47
  update() {
42
- const next = matchesHeldModifiers(typeof this.hotkey === "function" ? this.hotkey() : this.hotkey, getKeyStateTracker().store.state.heldKeys, typeof this.options === "function" ? this.options() : this.options);
48
+ const value = typeof this.hotkey === "function" ? this.hotkey() : this.hotkey;
49
+ const hotkey = isAppHotkey(value) ? resolveAppHotkey(value, getAppHotkeysStore()).hotkey : value;
50
+ const next = hotkey !== null && matchesHeldModifiers(hotkey, getKeyStateTracker().store.state.heldKeys, typeof this.options === "function" ? this.options() : this.options);
43
51
  const changed = next !== this.visible;
44
52
  this.visible = next;
45
53
  return changed;
@@ -1,12 +1,14 @@
1
- import { Hotkey, HotkeyRecorderOptions } from "@tanstack/hotkeys";
1
+ import { Hotkey, HotkeyChordRecorderOptions, HotkeyRecorderOptions, HotkeySequence, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
2
2
  import { ReactiveController, ReactiveControllerHost } from "lit";
3
3
  //#region src/controllers/hotkey-recorder.d.ts
4
4
  /**
5
5
  * A Lit ReactiveController that records keyboard shortcuts.
6
6
  *
7
- * Wraps the framework-agnostic `HotkeyRecorder` class, managing all the
8
- * complexity of capturing keyboard events, converting them to hotkey strings,
9
- * and handling edge cases like Escape to cancel or Backspace/Delete to clear.
7
+ * With the default `maxSteps: 1`, the first valid chord is recorded. With a
8
+ * larger `maxSteps`, each chord adds a step; the recording commits on Enter
9
+ * (unless `commitKeys: 'none'`), after `idleTimeoutMs`, or at `maxSteps`, and
10
+ * `onRecord` receives a chord (one step) or a sequence. Escape cancels;
11
+ * Backspace/Delete removes the last step or clears.
10
12
  *
11
13
  * @example
12
14
  * ```ts
@@ -46,17 +48,25 @@ export declare class HotkeyRecorderController implements ReactiveController {
46
48
  private _unsubscribe;
47
49
  /** Whether recording is currently active. */
48
50
  private _isRecording;
49
- /** The currently recorded hotkey (for live preview). */
51
+ /** The last committed hotkey. */
50
52
  private _recordedHotkey;
53
+ /** Chords recorded so far in a multi-step recording. */
54
+ private _steps;
51
55
  /** Whether recording is currently active. */
52
56
  get isRecording(): boolean;
53
- /** The currently recorded hotkey (for live preview). */
57
+ /** The last committed hotkey: a chord, or a sequence of two or more steps. */
54
58
  get recordedHotkey(): Hotkey | null;
59
+ /** Chords recorded so far in a multi-step recording (for live preview). */
60
+ get steps(): HotkeySequence;
55
61
  /**
56
- * @param _host - The Lit component that owns this controller.
57
- * @param _options - Options or a getter, read during recording.
62
+ * @param host - The Lit component that owns this controller.
63
+ * @param options - Options or a getter, read during recording.
58
64
  */
59
- constructor(_host: ReactiveControllerHost, _options: HotkeyRecorderOptions | (() => HotkeyRecorderOptions));
65
+ constructor(host: ReactiveControllerHost, options: HotkeyChordRecorderOptions);
66
+ constructor(host: ReactiveControllerHost, options: HotkeySequenceRecorderOptions);
67
+ constructor(host: ReactiveControllerHost, options: () => HotkeySequenceRecorderOptions);
68
+ constructor(host: ReactiveControllerHost, options: () => HotkeyChordRecorderOptions);
69
+ constructor(host: ReactiveControllerHost, options: () => HotkeyRecorderOptions);
60
70
  /** Subscribes to the recorder store and updates the internal state when changes occur. */
61
71
  hostConnected(): void;
62
72
  /** Unsubscribes from the recorder store and destroys the recorder instance to prevent memory leaks. */
@@ -69,5 +79,7 @@ export declare class HotkeyRecorderController implements ReactiveController {
69
79
  stopRecording(): void;
70
80
  /** Cancel recording without saving. */
71
81
  cancelRecording(): void;
82
+ /** Commit the recorded steps now (multi-step recordings). */
83
+ commitRecording(): void;
72
84
  }
73
85
  //#endregion
@@ -4,9 +4,11 @@ import { HotkeyRecorder } from "@tanstack/hotkeys";
4
4
  /**
5
5
  * A Lit ReactiveController that records keyboard shortcuts.
6
6
  *
7
- * Wraps the framework-agnostic `HotkeyRecorder` class, managing all the
8
- * complexity of capturing keyboard events, converting them to hotkey strings,
9
- * and handling edge cases like Escape to cancel or Backspace/Delete to clear.
7
+ * With the default `maxSteps: 1`, the first valid chord is recorded. With a
8
+ * larger `maxSteps`, each chord adds a step; the recording commits on Enter
9
+ * (unless `commitKeys: 'none'`), after `idleTimeoutMs`, or at `maxSteps`, and
10
+ * `onRecord` receives a chord (one step) or a sequence. Escape cancels;
11
+ * Backspace/Delete removes the last step or clears.
10
12
  *
11
13
  * @example
12
14
  * ```ts
@@ -46,20 +48,22 @@ var HotkeyRecorderController = class {
46
48
  _unsubscribe;
47
49
  /** Whether recording is currently active. */
48
50
  _isRecording = false;
49
- /** The currently recorded hotkey (for live preview). */
51
+ /** The last committed hotkey. */
50
52
  _recordedHotkey = null;
53
+ /** Chords recorded so far in a multi-step recording. */
54
+ _steps = [];
51
55
  /** Whether recording is currently active. */
52
56
  get isRecording() {
53
57
  return this._isRecording;
54
58
  }
55
- /** The currently recorded hotkey (for live preview). */
59
+ /** The last committed hotkey: a chord, or a sequence of two or more steps. */
56
60
  get recordedHotkey() {
57
61
  return this._recordedHotkey;
58
62
  }
59
- /**
60
- * @param _host - The Lit component that owns this controller.
61
- * @param _options - Options or a getter, read during recording.
62
- */
63
+ /** Chords recorded so far in a multi-step recording (for live preview). */
64
+ get steps() {
65
+ return this._steps;
66
+ }
63
67
  constructor(_host, _options) {
64
68
  this._host = _host;
65
69
  this._options = _options;
@@ -72,10 +76,11 @@ var HotkeyRecorderController = class {
72
76
  /** Subscribes to the recorder store and updates the internal state when changes occur. */
73
77
  hostConnected() {
74
78
  const subscription = this._recorder.store.subscribe(() => {
75
- const { isRecording, recordedHotkey } = this._recorder.store.state;
76
- if (isRecording !== this._isRecording || recordedHotkey !== this._recordedHotkey) {
79
+ const { isRecording, recordedHotkey, steps } = this._recorder.store.state;
80
+ if (isRecording !== this._isRecording || recordedHotkey !== this._recordedHotkey || steps !== this._steps) {
77
81
  this._isRecording = isRecording;
78
82
  this._recordedHotkey = recordedHotkey;
83
+ this._steps = steps;
79
84
  this._host.requestUpdate();
80
85
  }
81
86
  });
@@ -88,6 +93,7 @@ var HotkeyRecorderController = class {
88
93
  this._recorder.stop();
89
94
  this._isRecording = false;
90
95
  this._recordedHotkey = null;
96
+ this._steps = [];
91
97
  }
92
98
  /** Updates the recorder options (e.g. callbacks). */
93
99
  setOptions(options) {
@@ -108,6 +114,10 @@ var HotkeyRecorderController = class {
108
114
  cancelRecording() {
109
115
  this._recorder.cancel();
110
116
  }
117
+ /** Commit the recorded steps now (multi-step recordings). */
118
+ commitRecording() {
119
+ this._recorder.commit();
120
+ }
111
121
  };
112
122
 
113
123
  //#endregion
@@ -1,11 +1,10 @@
1
- import { HotkeyRegistrationView, SequenceRegistrationView } from "@tanstack/hotkeys";
1
+ import { HotkeyRegistrationView } from "@tanstack/hotkeys";
2
2
  import { ReactiveController, ReactiveControllerHost } from "lit";
3
3
  //#region src/controllers/hotkey-registrations.d.ts
4
4
  /**
5
- * A Lit ReactiveController that tracks all hotkey and sequence registrations.
6
- *
7
- * Subscribes to the singleton HotkeyManager and SequenceManager stores and
8
- * triggers host updates whenever registrations change.
5
+ * A Lit ReactiveController that reads every hotkey registration, chords and
6
+ * sequences, from the singleton HotkeyManager and requests an update when
7
+ * they change. Narrow each view with `kind`.
9
8
  *
10
9
  * @example
11
10
  * ```ts
@@ -13,27 +12,21 @@ import { ReactiveController, ReactiveControllerHost } from "lit";
13
12
  * private registrations = new HotkeyRegistrationsController(this)
14
13
  *
15
14
  * render() {
16
- * return html`
17
- * <ul>
18
- * ${this.registrations.hotkeys.map(
19
- * (reg) => html`<li>${reg.options.meta?.name ?? reg.hotkey}</li>`,
20
- * )}
21
- * </ul>
22
- * `
15
+ * return html`<ul>
16
+ * ${this.registrations.hotkeys.map(
17
+ * (reg) => html`<li>${reg.options.meta?.name}: ${formatForDisplay(reg.hotkey)}</li>`,
18
+ * )}
19
+ * </ul>`
23
20
  * }
24
21
  * }
25
22
  * ```
26
23
  */
27
24
  export declare class HotkeyRegistrationsController implements ReactiveController {
28
25
  private _host;
29
- private _unsubscribeHotkeys;
30
- private _unsubscribeSequences;
26
+ private _unsubscribe;
31
27
  private _hotkeys;
32
- private _sequences;
33
- /** All registered hotkeys (public view, no callbacks). */
28
+ /** Every registration, chords and sequences (public view, no callbacks). */
34
29
  get hotkeys(): Array<HotkeyRegistrationView>;
35
- /** All registered sequences. */
36
- get sequences(): Array<SequenceRegistrationView>;
37
30
  constructor(_host: ReactiveControllerHost);
38
31
  hostConnected(): void;
39
32
  hostDisconnected(): void;
@@ -1,11 +1,10 @@
1
- import { getHotkeyManager, getSequenceManager, toHotkeyRegistrationView } from "@tanstack/hotkeys";
1
+ import { getHotkeyManager, toHotkeyRegistrationView } from "@tanstack/hotkeys";
2
2
 
3
3
  //#region src/controllers/hotkey-registrations.ts
4
4
  /**
5
- * A Lit ReactiveController that tracks all hotkey and sequence registrations.
6
- *
7
- * Subscribes to the singleton HotkeyManager and SequenceManager stores and
8
- * triggers host updates whenever registrations change.
5
+ * A Lit ReactiveController that reads every hotkey registration, chords and
6
+ * sequences, from the singleton HotkeyManager and requests an update when
7
+ * they change. Narrow each view with `kind`.
9
8
  *
10
9
  * @example
11
10
  * ```ts
@@ -13,58 +12,41 @@ import { getHotkeyManager, getSequenceManager, toHotkeyRegistrationView } from "
13
12
  * private registrations = new HotkeyRegistrationsController(this)
14
13
  *
15
14
  * render() {
16
- * return html`
17
- * <ul>
18
- * ${this.registrations.hotkeys.map(
19
- * (reg) => html`<li>${reg.options.meta?.name ?? reg.hotkey}</li>`,
20
- * )}
21
- * </ul>
22
- * `
15
+ * return html`<ul>
16
+ * ${this.registrations.hotkeys.map(
17
+ * (reg) => html`<li>${reg.options.meta?.name}: ${formatForDisplay(reg.hotkey)}</li>`,
18
+ * )}
19
+ * </ul>`
23
20
  * }
24
21
  * }
25
22
  * ```
26
23
  */
27
24
  var HotkeyRegistrationsController = class {
28
25
  _host;
29
- _unsubscribeHotkeys;
30
- _unsubscribeSequences;
26
+ _unsubscribe;
31
27
  _hotkeys = [];
32
- _sequences = [];
33
- /** All registered hotkeys (public view, no callbacks). */
28
+ /** Every registration, chords and sequences (public view, no callbacks). */
34
29
  get hotkeys() {
35
30
  return this._hotkeys;
36
31
  }
37
- /** All registered sequences. */
38
- get sequences() {
39
- return this._sequences;
40
- }
41
32
  constructor(_host) {
42
33
  this._host = _host;
43
34
  this._host.addController(this);
44
35
  }
45
36
  hostConnected() {
46
- const hotkeyManager = getHotkeyManager();
47
- const sequenceManager = getSequenceManager();
48
- const hotkeysSub = hotkeyManager.registrations.subscribe(() => {
49
- this._hotkeys = Array.from(hotkeyManager.registrations.state.values()).map(toHotkeyRegistrationView);
50
- this._host.requestUpdate();
51
- });
52
- const sequencesSub = sequenceManager.registrations.subscribe(() => {
53
- this._sequences = Array.from(sequenceManager.registrations.state.values());
37
+ const manager = getHotkeyManager();
38
+ const read = () => Array.from(manager.registrations.state.values()).map(toHotkeyRegistrationView);
39
+ const subscription = manager.registrations.subscribe(() => {
40
+ this._hotkeys = read();
54
41
  this._host.requestUpdate();
55
42
  });
56
- this._unsubscribeHotkeys = () => hotkeysSub.unsubscribe();
57
- this._unsubscribeSequences = () => sequencesSub.unsubscribe();
58
- this._hotkeys = Array.from(hotkeyManager.registrations.state.values()).map(toHotkeyRegistrationView);
59
- this._sequences = Array.from(sequenceManager.registrations.state.values());
43
+ this._unsubscribe = () => subscription.unsubscribe();
44
+ this._hotkeys = read();
60
45
  }
61
46
  hostDisconnected() {
62
- this._unsubscribeHotkeys?.();
63
- this._unsubscribeSequences?.();
64
- this._unsubscribeHotkeys = void 0;
65
- this._unsubscribeSequences = void 0;
47
+ this._unsubscribe?.();
48
+ this._unsubscribe = void 0;
66
49
  this._hotkeys = [];
67
- this._sequences = [];
68
50
  }
69
51
  };
70
52
 
@@ -1,18 +1,42 @@
1
- import { HotkeyCallback, HotkeyOptions, RegisterableHotkey } from "@tanstack/hotkeys";
1
+ import { AppHotkey, HotkeyCallback, RegisterableHotkey, Target } from "@tanstack/hotkeys";
2
+ import { AdapterAppHotkeyOptions, AdapterChordOptions, AdapterCommonOptions, AdapterHotkeyOptions, AdapterOptionsFor, AdapterSequenceOptions } from "@tanstack/hotkeys/adapter";
2
3
  import { ReactiveController, ReactiveControllerHost } from "lit";
3
4
  //#region src/controllers/hotkey.d.ts
5
+ /** The element to listen on. A `null` target skips registration until it is set. */
6
+ export type HotkeyControllerTarget = Target | null;
7
+ /** Options shared by chords and sequences. */
8
+ export type HotkeyControllerOptions = AdapterHotkeyOptions<HotkeyControllerTarget>;
9
+ /** Options for a chord such as `'Mod+S'`. */
10
+ export type HotkeyControllerChordOptions = AdapterChordOptions<HotkeyControllerTarget>;
11
+ /** Options for a sequence such as `['G', 'G']`. */
12
+ export type HotkeyControllerSequenceOptions = AdapterSequenceOptions<HotkeyControllerTarget>;
13
+ /** Options for every kind; each applies to the kinds that support it. */
14
+ export type HotkeyControllerCommonOptions = AdapterCommonOptions<HotkeyControllerTarget>;
15
+ /** The options type for a hotkey: chord options for chords, sequence options for sequences. */
16
+ export type HotkeyControllerOptionsFor<T extends RegisterableHotkey> = AdapterOptionsFor<T, HotkeyControllerTarget>;
17
+ /** Call-site options for an app hotkey, typed by its declared default kind. */
18
+ export type AppHotkeyControllerOptions<T extends AppHotkey = AppHotkey> = AdapterAppHotkeyOptions<T, HotkeyControllerTarget>;
19
+ type MaybeGetter<T> = T | (() => T);
4
20
  /**
5
- * A Lit ReactiveController that registers a keyboard hotkey when the host
6
- * element is connected and unregisters it when the host is disconnected.
21
+ * A Lit ReactiveController that registers a hotkey when the host element is
22
+ * connected and unregisters it when the host is disconnected. The hotkey can
23
+ * be a chord such as `'Mod+S'`, a sequence such as `['G', 'G']`, or an app
24
+ * hotkey from `defineAppHotkeys`, which uses its custom hotkey from
25
+ * `provideAppHotkeys`.
26
+ *
27
+ * Chords accept `requireReset`; sequences accept `timeout`. Pass a getter for
28
+ * the hotkey or options to re-read them after each host update.
7
29
  *
8
30
  * @example
9
31
  * ```ts
10
32
  * class MyElement extends LitElement {
11
- * private hotkey = new HotkeyController(this, 'Mod+S', () => this.save())
33
+ * private save = new HotkeyController(this, 'Mod+S', () => this.doSave())
34
+ * private top = new HotkeyController(this, ['G', 'G'], () => this.scrollToTop())
12
35
  *
13
36
  * constructor() {
14
37
  * super()
15
- * this.addController(this.hotkey)
38
+ * this.addController(this.save)
39
+ * this.addController(this.top)
16
40
  * }
17
41
  * }
18
42
  * ```
@@ -24,13 +48,15 @@ export declare class HotkeyController implements ReactiveController {
24
48
  private _bindings;
25
49
  private _connected;
26
50
  private _boundCallback;
51
+ private _unsubscribe;
27
52
  /**
28
- * @param _host - The Lit component that owns this controller. Add the controller with `addController()`.
29
- * @param _hotkey - The shortcut to register.
53
+ * @param host - The Lit component that owns this controller. Add the controller with `addController()`.
54
+ * @param hotkey - The chord, sequence, or app hotkey to register, or a getter. A getter is read on connection and after host updates.
30
55
  * @param callback - Called with the host as `this`.
31
- * @param _options - Options or a getter. Property getters are read on connection and after host updates.
56
+ * @param options - Options or a getter. Property getters are read on connection and after host updates.
32
57
  */
33
- constructor(_host: ReactiveControllerHost, _hotkey: RegisterableHotkey, callback: HotkeyCallback, _options?: HotkeyOptions | (() => HotkeyOptions));
58
+ constructor(host: ReactiveControllerHost, hotkey: MaybeGetter<AppHotkey>, callback: HotkeyCallback, options?: MaybeGetter<AppHotkeyControllerOptions>);
59
+ constructor(host: ReactiveControllerHost, hotkey: MaybeGetter<RegisterableHotkey>, callback: HotkeyCallback, options?: MaybeGetter<HotkeyControllerCommonOptions>);
34
60
  /** Registers when connected and a target is available. */
35
61
  hostConnected(): void;
36
62
  /** Refreshes options after rendering, when scoped targets are available. */
@@ -1,19 +1,27 @@
1
- import { HOTKEY_DEFAULT_OPTIONS } from "../constants.js";
2
- import { createHotkeyBindings } from "@tanstack/hotkeys/adapter";
1
+ import { getAppHotkeysStore, subscribeAppHotkeys } from "./_app-hotkeys.js";
2
+ import { createHotkeyBindings, prepareHotkeyDefinitions } from "@tanstack/hotkeys/adapter";
3
3
 
4
4
  //#region src/controllers/hotkey.ts
5
5
  /**
6
- * A Lit ReactiveController that registers a keyboard hotkey when the host
7
- * element is connected and unregisters it when the host is disconnected.
6
+ * A Lit ReactiveController that registers a hotkey when the host element is
7
+ * connected and unregisters it when the host is disconnected. The hotkey can
8
+ * be a chord such as `'Mod+S'`, a sequence such as `['G', 'G']`, or an app
9
+ * hotkey from `defineAppHotkeys`, which uses its custom hotkey from
10
+ * `provideAppHotkeys`.
11
+ *
12
+ * Chords accept `requireReset`; sequences accept `timeout`. Pass a getter for
13
+ * the hotkey or options to re-read them after each host update.
8
14
  *
9
15
  * @example
10
16
  * ```ts
11
17
  * class MyElement extends LitElement {
12
- * private hotkey = new HotkeyController(this, 'Mod+S', () => this.save())
18
+ * private save = new HotkeyController(this, 'Mod+S', () => this.doSave())
19
+ * private top = new HotkeyController(this, ['G', 'G'], () => this.scrollToTop())
13
20
  *
14
21
  * constructor() {
15
22
  * super()
16
- * this.addController(this.hotkey)
23
+ * this.addController(this.save)
24
+ * this.addController(this.top)
17
25
  * }
18
26
  * }
19
27
  * ```
@@ -25,13 +33,8 @@ var HotkeyController = class {
25
33
  _bindings = createHotkeyBindings();
26
34
  _connected = false;
27
35
  _boundCallback;
28
- /**
29
- * @param _host - The Lit component that owns this controller. Add the controller with `addController()`.
30
- * @param _hotkey - The shortcut to register.
31
- * @param callback - Called with the host as `this`.
32
- * @param _options - Options or a getter. Property getters are read on connection and after host updates.
33
- */
34
- constructor(_host, _hotkey, callback, _options = HOTKEY_DEFAULT_OPTIONS) {
36
+ _unsubscribe;
37
+ constructor(_host, _hotkey, callback, _options = {}) {
35
38
  this._host = _host;
36
39
  this._hotkey = _hotkey;
37
40
  this._options = _options;
@@ -40,6 +43,7 @@ var HotkeyController = class {
40
43
  /** Registers when connected and a target is available. */
41
44
  hostConnected() {
42
45
  this._connected = true;
46
+ this._unsubscribe = subscribeAppHotkeys(() => this._sync());
43
47
  this._sync();
44
48
  }
45
49
  /** Refreshes options after rendering, when scoped targets are available. */
@@ -48,15 +52,20 @@ var HotkeyController = class {
48
52
  }
49
53
  _sync() {
50
54
  const options = typeof this._options === "function" ? this._options() : this._options;
51
- this._bindings.update([{
52
- hotkey: this._hotkey,
55
+ this._bindings.update(prepareHotkeyDefinitions([{
56
+ hotkey: typeof this._hotkey === "function" ? this._hotkey() : this._hotkey,
53
57
  callback: this._boundCallback,
54
58
  options
55
- }]);
59
+ }], {
60
+ appHotkeysStore: getAppHotkeysStore(),
61
+ resolveTarget: (target) => target
62
+ }));
56
63
  }
57
64
  /** Releases registrations when the host disconnects. */
58
65
  hostDisconnected() {
59
66
  this._connected = false;
67
+ this._unsubscribe?.();
68
+ this._unsubscribe = void 0;
60
69
  this._bindings.destroy();
61
70
  }
62
71
  };
@@ -1,10 +1,9 @@
1
+ import { AppHotkeyControllerOptions, HotkeyController, HotkeyControllerChordOptions, HotkeyControllerCommonOptions, HotkeyControllerOptions, HotkeyControllerOptionsFor, HotkeyControllerSequenceOptions, HotkeyControllerTarget } from "./hotkey.js";
2
+ import { AppHotkeysController } from "./app-hotkeys.js";
1
3
  import { HeldKeyCodesController } from "./held-key-codes.js";
2
4
  import { HeldKeysController } from "./held-keys.js";
3
- import { HotkeyController } from "./hotkey.js";
4
5
  import { HotkeyRecorderController } from "./hotkey-recorder.js";
5
6
  import { HotkeyRegistrationsController } from "./hotkey-registrations.js";
6
- import { HotkeySequenceController } from "./hotkey-sequence.js";
7
- import { HotkeySequenceRecorderController } from "./hotkey-sequence-recorder.js";
8
7
  import { KeyHoldController } from "./key-hold.js";
9
8
  import { HotkeyHintController } from "./hotkey-hint.js";
10
- export { HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyHintController, HotkeyRecorderController, HotkeyRegistrationsController, HotkeySequenceController, HotkeySequenceRecorderController, KeyHoldController };
9
+ export { AppHotkeyControllerOptions, AppHotkeysController, HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyControllerChordOptions, HotkeyControllerCommonOptions, HotkeyControllerOptions, HotkeyControllerOptionsFor, HotkeyControllerSequenceOptions, HotkeyControllerTarget, HotkeyHintController, HotkeyRecorderController, HotkeyRegistrationsController, KeyHoldController };
@@ -49,8 +49,8 @@ export declare class KeyHoldController implements ReactiveController {
49
49
  /** Whether the tracked key is currently held down. */
50
50
  get value(): boolean;
51
51
  /**
52
- * @param host - The Lit component that owns this controller.
53
- * @param key - The key to track (e.g. 'Shift', 'Control', 'A').
52
+ * @param _host - The Lit component that owns this controller.
53
+ * @param _key - The key to track (e.g. 'Shift', 'Control', 'A').
54
54
  */
55
55
  constructor(_host: ReactiveControllerHost, _key: IndividualKey);
56
56
  hostConnected(): void;
@@ -51,8 +51,8 @@ var KeyHoldController = class {
51
51
  return this._value;
52
52
  }
53
53
  /**
54
- * @param host - The Lit component that owns this controller.
55
- * @param key - The key to track (e.g. 'Shift', 'Control', 'A').
54
+ * @param _host - The Lit component that owns this controller.
55
+ * @param _key - The key to track (e.g. 'Shift', 'Control', 'A').
56
56
  */
57
57
  constructor(_host, _key) {
58
58
  this._host = _host;
@@ -1,11 +1,14 @@
1
- import { HotkeyCallback, HotkeyOptions, RegisterableHotkey } from "@tanstack/hotkeys";
1
+ import { AppHotkeyControllerOptions, HotkeyControllerOptionsFor } from "../controllers/hotkey.js";
2
+ import { AppHotkey, HotkeyCallback, RegisterableHotkey } from "@tanstack/hotkeys";
2
3
  import { LitElement } from "lit";
3
4
  //#region src/decorators/hotkey.d.ts
5
+ type HotkeyDecorator = <T extends HotkeyCallback>(proto: LitElement, propertyKey: string, descriptor?: TypedPropertyDescriptor<T>) => void;
4
6
  /**
5
- * Decorator that registers a keyboard hotkey on the element when it connects
6
- * and unregisters when it disconnects. Uses {@link HotkeyController} under the hood.
7
+ * Decorator that registers a hotkey on the element when it connects and
8
+ * unregisters when it disconnects: a chord such as `'Mod+S'`, a sequence such
9
+ * as `['G', 'G']`, or an app hotkey. Uses {@link HotkeyController} under the hood.
7
10
  *
8
- * @param hotkey - The key or key combo to listen for (e.g. `'Mod+S'` or a raw hotkey object).
11
+ * @param hotkey - The chord, sequence, or app hotkey to listen for.
9
12
  * @param options - Optional registration options (target, platform, enabled, etc.).
10
13
  * @returns A method decorator for use on LitElement methods.
11
14
  *
@@ -15,10 +18,11 @@ import { LitElement } from "lit";
15
18
  * @hotkey('Mod+S')
16
19
  * save() { this.doSave() }
17
20
  *
18
- * @hotkey('Escape')
19
- * close() { this.dismiss() }
21
+ * @hotkey(['G', 'G'], { timeout: 500 })
22
+ * goToTop() { window.scrollTo(0, 0) }
20
23
  * }
21
24
  * ```
22
25
  */
23
- export declare function hotkey(hotkey: RegisterableHotkey, options?: HotkeyOptions): <T extends HotkeyCallback>(proto: LitElement, propertyKey: string, descriptor?: TypedPropertyDescriptor<T>) => void;
26
+ export declare function hotkey<T extends AppHotkey>(hotkey: T, options?: AppHotkeyControllerOptions<T>): HotkeyDecorator;
27
+ export declare function hotkey<T extends RegisterableHotkey>(hotkey: T, options?: HotkeyControllerOptionsFor<T>): HotkeyDecorator;
24
28
  //#endregion
@@ -1,27 +1,7 @@
1
- import { HOTKEY_DEFAULT_OPTIONS } from "../constants.js";
2
1
  import { HotkeyController } from "../controllers/hotkey.js";
3
2
 
4
3
  //#region src/decorators/hotkey.ts
5
- /**
6
- * Decorator that registers a keyboard hotkey on the element when it connects
7
- * and unregisters when it disconnects. Uses {@link HotkeyController} under the hood.
8
- *
9
- * @param hotkey - The key or key combo to listen for (e.g. `'Mod+S'` or a raw hotkey object).
10
- * @param options - Optional registration options (target, platform, enabled, etc.).
11
- * @returns A method decorator for use on LitElement methods.
12
- *
13
- * @example
14
- * ```ts
15
- * class MyElement extends LitElement {
16
- * @hotkey('Mod+S')
17
- * save() { this.doSave() }
18
- *
19
- * @hotkey('Escape')
20
- * close() { this.dismiss() }
21
- * }
22
- * ```
23
- */
24
- function hotkey(hotkey, options = HOTKEY_DEFAULT_OPTIONS) {
4
+ function hotkey(hotkey, options = {}) {
25
5
  return function(proto, propertyKey, descriptor) {
26
6
  const originalConnected = proto.connectedCallback;
27
7
  const controllerKey = Symbol(`@hotkey:${propertyKey}`);
@@ -1,3 +1,2 @@
1
- import { hotkeySequence } from "./hotkey-sequence.js";
2
1
  import { hotkey } from "./hotkey.js";
3
- export { hotkey, hotkeySequence };
2
+ export { hotkey };
package/dist/index.d.ts CHANGED
@@ -1,15 +1,13 @@
1
- import { hotkeySequence } from "./decorators/hotkey-sequence.js";
1
+ import { AppHotkeyControllerOptions, HotkeyController, HotkeyControllerChordOptions, HotkeyControllerCommonOptions, HotkeyControllerOptions, HotkeyControllerOptionsFor, HotkeyControllerSequenceOptions, HotkeyControllerTarget } from "./controllers/hotkey.js";
2
2
  import { hotkey } from "./decorators/hotkey.js";
3
3
  import "./decorators/index.js";
4
+ import { AppHotkeysController } from "./controllers/app-hotkeys.js";
4
5
  import { HeldKeyCodesController } from "./controllers/held-key-codes.js";
5
6
  import { HeldKeysController } from "./controllers/held-keys.js";
6
- import { HotkeyController } from "./controllers/hotkey.js";
7
7
  import { HotkeyRecorderController } from "./controllers/hotkey-recorder.js";
8
8
  import { HotkeyRegistrationsController } from "./controllers/hotkey-registrations.js";
9
- import { HotkeySequenceController } from "./controllers/hotkey-sequence.js";
10
- import { HotkeySequenceRecorderController } from "./controllers/hotkey-sequence-recorder.js";
11
9
  import { KeyHoldController } from "./controllers/key-hold.js";
12
10
  import { HotkeyHintController } from "./controllers/hotkey-hint.js";
13
11
  import "./controllers/index.js";
14
12
  export * from "@tanstack/hotkeys";
15
- export { HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyHintController, HotkeyRecorderController, HotkeyRegistrationsController, HotkeySequenceController, HotkeySequenceRecorderController, KeyHoldController, hotkey, hotkeySequence };
13
+ export { AppHotkeyControllerOptions, AppHotkeysController, HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyControllerChordOptions, HotkeyControllerCommonOptions, HotkeyControllerOptions, HotkeyControllerOptionsFor, HotkeyControllerSequenceOptions, HotkeyControllerTarget, HotkeyHintController, HotkeyRecorderController, HotkeyRegistrationsController, KeyHoldController, hotkey };
package/dist/index.js CHANGED
@@ -1,15 +1,13 @@
1
- import { HotkeySequenceController } from "./controllers/hotkey-sequence.js";
2
- import { hotkeySequence } from "./decorators/hotkey-sequence.js";
3
1
  import { HotkeyController } from "./controllers/hotkey.js";
4
2
  import { hotkey } from "./decorators/hotkey.js";
3
+ import { AppHotkeysController } from "./controllers/app-hotkeys.js";
5
4
  import { HeldKeyCodesController } from "./controllers/held-key-codes.js";
6
5
  import { HeldKeysController } from "./controllers/held-keys.js";
7
6
  import { HotkeyRecorderController } from "./controllers/hotkey-recorder.js";
8
7
  import { HotkeyRegistrationsController } from "./controllers/hotkey-registrations.js";
9
- import { HotkeySequenceRecorderController } from "./controllers/hotkey-sequence-recorder.js";
10
8
  import { KeyHoldController } from "./controllers/key-hold.js";
11
9
  import { HotkeyHintController } from "./controllers/hotkey-hint.js";
12
10
 
13
11
  export * from "@tanstack/hotkeys"
14
12
 
15
- export { HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyHintController, HotkeyRecorderController, HotkeyRegistrationsController, HotkeySequenceController, HotkeySequenceRecorderController, KeyHoldController, hotkey, hotkeySequence };
13
+ export { AppHotkeysController, HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyHintController, HotkeyRecorderController, HotkeyRegistrationsController, KeyHoldController, hotkey };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/lit-hotkeys",
3
- "version": "0.14.0",
3
+ "version": "0.15.1",
4
4
  "description": "Lit adapter for TanStack Hotkeys",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "dist"
34
34
  ],
35
35
  "dependencies": {
36
- "@tanstack/hotkeys": "0.11.0"
36
+ "@tanstack/hotkeys": "0.12.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "lit": "^3.3.3"
package/dist/constants.js DELETED
@@ -1,24 +0,0 @@
1
- //#region src/constants.ts
2
- const HOTKEY_DEFAULT_OPTIONS = {
3
- enabled: true,
4
- preventDefault: true,
5
- stopPropagation: true,
6
- eventType: "keydown",
7
- requireReset: false,
8
- ignoreInputs: void 0,
9
- platform: void 0,
10
- conflictBehavior: "warn"
11
- };
12
- const HOTKEY_SEQUENCE_DEFAULT_OPTIONS = {
13
- enabled: true,
14
- timeout: 1e3,
15
- preventDefault: true,
16
- stopPropagation: true,
17
- eventType: "keydown",
18
- ignoreInputs: void 0,
19
- platform: void 0,
20
- conflictBehavior: "warn"
21
- };
22
-
23
- //#endregion
24
- export { HOTKEY_DEFAULT_OPTIONS, HOTKEY_SEQUENCE_DEFAULT_OPTIONS };
@@ -1,81 +0,0 @@
1
- import { HotkeySequence, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
2
- import { ReactiveController, ReactiveControllerHost } from "lit";
3
- //#region src/controllers/hotkey-sequence-recorder.d.ts
4
- /**
5
- * A Lit ReactiveController that records multi-chord sequences (Vim-style shortcuts).
6
- *
7
- * Wraps the framework-agnostic `HotkeySequenceRecorder` class, managing store
8
- * subscriptions and host update lifecycle automatically.
9
- *
10
- * @example
11
- * ```ts
12
- * class ShortcutSettings extends LitElement {
13
- * private recorder = new HotkeySequenceRecorderController(this, {
14
- * onRecord: (sequence) => {
15
- * this.sequence = sequence
16
- * this.requestUpdate()
17
- * },
18
- * onCancel: () => {
19
- * console.log('Recording cancelled')
20
- * },
21
- * })
22
- *
23
- * private sequence: HotkeySequence | null = null
24
- *
25
- * render() {
26
- * return html`
27
- * <button @click=${() => this.recorder.startRecording()}>
28
- * ${this.recorder.isRecording ? 'Recording...' : 'Edit Sequence'}
29
- * </button>
30
- * ${this.recorder.steps.length
31
- * ? html`<div>Steps: ${this.recorder.steps.join(' → ')}</div>`
32
- * : nothing}
33
- * ${this.recorder.recordedSequence
34
- * ? html`<div>Recorded: ${this.recorder.recordedSequence.join(' → ')}</div>`
35
- * : nothing}
36
- * `
37
- * }
38
- * }
39
- * ```
40
- */
41
- export declare class HotkeySequenceRecorderController implements ReactiveController {
42
- private _host;
43
- private _options;
44
- /** The recorder instance. */
45
- private _recorder;
46
- private _optionOverrides;
47
- /** The unsubscribe function. */
48
- private _unsubscribe;
49
- /** Whether recording is currently active. */
50
- private _isRecording;
51
- /** Chords captured in the current session. */
52
- private _steps;
53
- /** Last committed sequence, or null if none. */
54
- private _recordedSequence;
55
- /** Whether recording is currently active. */
56
- get isRecording(): boolean;
57
- /** Chords captured in the current session. */
58
- get steps(): HotkeySequence;
59
- /** Last committed sequence, or null if none. */
60
- get recordedSequence(): HotkeySequence | null;
61
- /**
62
- * @param _host - The Lit component that owns this controller.
63
- * @param _options - Options or a getter, read during recording.
64
- */
65
- constructor(_host: ReactiveControllerHost, _options: HotkeySequenceRecorderOptions | (() => HotkeySequenceRecorderOptions));
66
- /** Subscribes to the recorder store and updates internal state when changes occur. */
67
- hostConnected(): void;
68
- /** Unsubscribes from the recorder store and destroys the recorder instance to prevent memory leaks. */
69
- hostDisconnected(): void;
70
- /** Updates the recorder options. */
71
- setOptions(options: Partial<HotkeySequenceRecorderOptions>): void;
72
- /** Start recording a new sequence. */
73
- startRecording(): void;
74
- /** Stop recording (same as cancel but without calling onCancel). */
75
- stopRecording(): void;
76
- /** Cancel recording without saving. */
77
- cancelRecording(): void;
78
- /** Commit current steps as a sequence (no-op if empty). */
79
- commitRecording(): void;
80
- }
81
- //#endregion
@@ -1,128 +0,0 @@
1
- import { HotkeySequenceRecorder } from "@tanstack/hotkeys";
2
-
3
- //#region src/controllers/hotkey-sequence-recorder.ts
4
- /**
5
- * A Lit ReactiveController that records multi-chord sequences (Vim-style shortcuts).
6
- *
7
- * Wraps the framework-agnostic `HotkeySequenceRecorder` class, managing store
8
- * subscriptions and host update lifecycle automatically.
9
- *
10
- * @example
11
- * ```ts
12
- * class ShortcutSettings extends LitElement {
13
- * private recorder = new HotkeySequenceRecorderController(this, {
14
- * onRecord: (sequence) => {
15
- * this.sequence = sequence
16
- * this.requestUpdate()
17
- * },
18
- * onCancel: () => {
19
- * console.log('Recording cancelled')
20
- * },
21
- * })
22
- *
23
- * private sequence: HotkeySequence | null = null
24
- *
25
- * render() {
26
- * return html`
27
- * <button @click=${() => this.recorder.startRecording()}>
28
- * ${this.recorder.isRecording ? 'Recording...' : 'Edit Sequence'}
29
- * </button>
30
- * ${this.recorder.steps.length
31
- * ? html`<div>Steps: ${this.recorder.steps.join(' → ')}</div>`
32
- * : nothing}
33
- * ${this.recorder.recordedSequence
34
- * ? html`<div>Recorded: ${this.recorder.recordedSequence.join(' → ')}</div>`
35
- * : nothing}
36
- * `
37
- * }
38
- * }
39
- * ```
40
- */
41
- var HotkeySequenceRecorderController = class {
42
- _host;
43
- _options;
44
- /** The recorder instance. */
45
- _recorder;
46
- _optionOverrides = {};
47
- /** The unsubscribe function. */
48
- _unsubscribe;
49
- /** Whether recording is currently active. */
50
- _isRecording = false;
51
- /** Chords captured in the current session. */
52
- _steps = [];
53
- /** Last committed sequence, or null if none. */
54
- _recordedSequence = null;
55
- /** Whether recording is currently active. */
56
- get isRecording() {
57
- return this._isRecording;
58
- }
59
- /** Chords captured in the current session. */
60
- get steps() {
61
- return this._steps;
62
- }
63
- /** Last committed sequence, or null if none. */
64
- get recordedSequence() {
65
- return this._recordedSequence;
66
- }
67
- /**
68
- * @param _host - The Lit component that owns this controller.
69
- * @param _options - Options or a getter, read during recording.
70
- */
71
- constructor(_host, _options) {
72
- this._host = _host;
73
- this._options = _options;
74
- this._recorder = new HotkeySequenceRecorder(() => ({
75
- ...typeof this._options === "function" ? this._options() : this._options,
76
- ...this._optionOverrides
77
- }));
78
- this._host.addController(this);
79
- }
80
- /** Subscribes to the recorder store and updates internal state when changes occur. */
81
- hostConnected() {
82
- const subscription = this._recorder.store.subscribe(() => {
83
- const { isRecording, steps, recordedSequence } = this._recorder.store.state;
84
- if (isRecording !== this._isRecording || steps !== this._steps || recordedSequence !== this._recordedSequence) {
85
- this._isRecording = isRecording;
86
- this._steps = steps;
87
- this._recordedSequence = recordedSequence;
88
- this._host.requestUpdate();
89
- }
90
- });
91
- this._unsubscribe = () => subscription.unsubscribe();
92
- }
93
- /** Unsubscribes from the recorder store and destroys the recorder instance to prevent memory leaks. */
94
- hostDisconnected() {
95
- this._unsubscribe?.();
96
- this._unsubscribe = void 0;
97
- this._recorder.stop();
98
- this._isRecording = false;
99
- this._steps = [];
100
- this._recordedSequence = null;
101
- }
102
- /** Updates the recorder options. */
103
- setOptions(options) {
104
- this._optionOverrides = {
105
- ...this._optionOverrides,
106
- ...options
107
- };
108
- }
109
- /** Start recording a new sequence. */
110
- startRecording() {
111
- this._recorder.start();
112
- }
113
- /** Stop recording (same as cancel but without calling onCancel). */
114
- stopRecording() {
115
- this._recorder.stop();
116
- }
117
- /** Cancel recording without saving. */
118
- cancelRecording() {
119
- this._recorder.cancel();
120
- }
121
- /** Commit current steps as a sequence (no-op if empty). */
122
- commitRecording() {
123
- this._recorder.commit();
124
- }
125
- };
126
-
127
- //#endregion
128
- export { HotkeySequenceRecorderController };
@@ -1,42 +0,0 @@
1
- import { HotkeyCallback, HotkeySequence, SequenceOptions } from "@tanstack/hotkeys";
2
- import { ReactiveController, ReactiveControllerHost } from "lit";
3
- //#region src/controllers/hotkey-sequence.d.ts
4
- /**
5
- * A Lit ReactiveController that registers a keyboard sequence (e.g. Vim-style)
6
- * when the host element is connected and unregisters it when the host is disconnected.
7
- *
8
- * @example
9
- * ```ts
10
- * class MyElement extends LitElement {
11
- * private seq = new HotkeySequenceController(this, ['G', 'G'], () => this.goToTop())
12
- *
13
- * constructor() {
14
- * super()
15
- * this.addController(this.seq)
16
- * }
17
- * }
18
- * ```
19
- */
20
- export declare class HotkeySequenceController implements ReactiveController {
21
- private _host;
22
- private _sequence;
23
- private _options;
24
- private _bindings;
25
- private _connected;
26
- private _boundCallback;
27
- /**
28
- * @param _host - The Lit component that owns this controller. Add the controller with `addController()`.
29
- * @param _sequence - The sequence to register.
30
- * @param callback - Called with the host as `this`.
31
- * @param _options - Options or a getter. Property getters are read on connection and after host updates.
32
- */
33
- constructor(_host: ReactiveControllerHost, _sequence: HotkeySequence, callback: HotkeyCallback, _options?: SequenceOptions | (() => SequenceOptions));
34
- /** Registers when connected and a target is available. */
35
- hostConnected(): void;
36
- /** Refreshes options after rendering, when scoped targets are available. */
37
- hostUpdated(): void;
38
- private _sync;
39
- /** Releases registrations when the host disconnects. */
40
- hostDisconnected(): void;
41
- }
42
- //#endregion
@@ -1,65 +0,0 @@
1
- import { HOTKEY_SEQUENCE_DEFAULT_OPTIONS } from "../constants.js";
2
- import { createHotkeySequenceBindings } from "@tanstack/hotkeys/adapter";
3
-
4
- //#region src/controllers/hotkey-sequence.ts
5
- /**
6
- * A Lit ReactiveController that registers a keyboard sequence (e.g. Vim-style)
7
- * when the host element is connected and unregisters it when the host is disconnected.
8
- *
9
- * @example
10
- * ```ts
11
- * class MyElement extends LitElement {
12
- * private seq = new HotkeySequenceController(this, ['G', 'G'], () => this.goToTop())
13
- *
14
- * constructor() {
15
- * super()
16
- * this.addController(this.seq)
17
- * }
18
- * }
19
- * ```
20
- */
21
- var HotkeySequenceController = class {
22
- _host;
23
- _sequence;
24
- _options;
25
- _bindings = createHotkeySequenceBindings();
26
- _connected = false;
27
- _boundCallback;
28
- /**
29
- * @param _host - The Lit component that owns this controller. Add the controller with `addController()`.
30
- * @param _sequence - The sequence to register.
31
- * @param callback - Called with the host as `this`.
32
- * @param _options - Options or a getter. Property getters are read on connection and after host updates.
33
- */
34
- constructor(_host, _sequence, callback, _options = HOTKEY_SEQUENCE_DEFAULT_OPTIONS) {
35
- this._host = _host;
36
- this._sequence = _sequence;
37
- this._options = _options;
38
- this._boundCallback = callback.bind(this._host);
39
- }
40
- /** Registers when connected and a target is available. */
41
- hostConnected() {
42
- this._connected = true;
43
- this._sync();
44
- }
45
- /** Refreshes options after rendering, when scoped targets are available. */
46
- hostUpdated() {
47
- if (this._connected) this._sync();
48
- }
49
- _sync() {
50
- const options = typeof this._options === "function" ? this._options() : this._options;
51
- this._bindings.update([{
52
- sequence: this._sequence,
53
- callback: this._boundCallback,
54
- options
55
- }]);
56
- }
57
- /** Releases registrations when the host disconnects. */
58
- hostDisconnected() {
59
- this._connected = false;
60
- this._bindings.destroy();
61
- }
62
- };
63
-
64
- //#endregion
65
- export { HotkeySequenceController };
@@ -1,25 +0,0 @@
1
- import { HotkeyCallback, HotkeySequence, SequenceOptions } from "@tanstack/hotkeys";
2
- import { LitElement } from "lit";
3
- //#region src/decorators/hotkey-sequence.d.ts
4
- /**
5
- * Decorator that registers a keyboard sequence (e.g. Vim-style) on the element
6
- * when it connects and unregisters when it disconnects. Uses
7
- * {@link HotkeySequenceController} under the hood.
8
- *
9
- * @param sequence - The key sequence to listen for (e.g. `['G', 'G']` for "g g").
10
- * @param options - Optional sequence options (target, timeout, enabled, etc.).
11
- * @returns A method decorator for use on LitElement methods.
12
- *
13
- * @example
14
- * ```ts
15
- * class MyElement extends LitElement {
16
- * @hotkeySequence(['G', 'G'])
17
- * goToTop() { window.scrollTo(0, 0) }
18
- *
19
- * @hotkeySequence(['D', 'D'], { timeout: 500 })
20
- * deleteLine() { this.deleteCurrentLine() }
21
- * }
22
- * ```
23
- */
24
- export declare function hotkeySequence(sequence: HotkeySequence, options?: SequenceOptions): <T extends HotkeyCallback>(proto: LitElement, methodName: string, descriptor: TypedPropertyDescriptor<T>) => void;
25
- //#endregion
@@ -1,44 +0,0 @@
1
- import { HOTKEY_SEQUENCE_DEFAULT_OPTIONS } from "../constants.js";
2
- import { HotkeySequenceController } from "../controllers/hotkey-sequence.js";
3
-
4
- //#region src/decorators/hotkey-sequence.ts
5
- /**
6
- * Decorator that registers a keyboard sequence (e.g. Vim-style) on the element
7
- * when it connects and unregisters when it disconnects. Uses
8
- * {@link HotkeySequenceController} under the hood.
9
- *
10
- * @param sequence - The key sequence to listen for (e.g. `['G', 'G']` for "g g").
11
- * @param options - Optional sequence options (target, timeout, enabled, etc.).
12
- * @returns A method decorator for use on LitElement methods.
13
- *
14
- * @example
15
- * ```ts
16
- * class MyElement extends LitElement {
17
- * @hotkeySequence(['G', 'G'])
18
- * goToTop() { window.scrollTo(0, 0) }
19
- *
20
- * @hotkeySequence(['D', 'D'], { timeout: 500 })
21
- * deleteLine() { this.deleteCurrentLine() }
22
- * }
23
- * ```
24
- */
25
- function hotkeySequence(sequence, options = HOTKEY_SEQUENCE_DEFAULT_OPTIONS) {
26
- return function(proto, methodName, descriptor) {
27
- const originalConnected = proto.connectedCallback;
28
- const controllerKey = Symbol(`@hotkeySequence:${methodName}`);
29
- proto.connectedCallback = function() {
30
- originalConnected.call(this);
31
- const host = this;
32
- if (!host[controllerKey]) {
33
- const callback = descriptor.value ? descriptor.value.bind(this) : host[methodName].bind(this);
34
- if (typeof callback !== "function") throw new Error(`@hotkeySequence decorator can only be applied to functions`);
35
- const controller = new HotkeySequenceController(this, sequence, callback, options);
36
- host[controllerKey] = controller;
37
- this.addController(controller);
38
- }
39
- };
40
- };
41
- }
42
-
43
- //#endregion
44
- export { hotkeySequence };