@tanstack/lit-hotkeys 0.20.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -67,6 +67,7 @@ var AppHotkeysController = class {
67
67
  return requireStore().findConflicts(id, hotkey);
68
68
  }
69
69
  hostConnected() {
70
+ if (this._unsubscribe) return;
70
71
  if (isServer()) return;
71
72
  const update = () => this._host.requestUpdate();
72
73
  const unsubscribeAppHotkeys = subscribeAppHotkeys(update);
@@ -43,6 +43,7 @@ var HeldKeyCodesController = class {
43
43
  }
44
44
  /** Subscribes to the tracker store and updates the internal state when changes occur. */
45
45
  hostConnected() {
46
+ if (this._unsubscribe) return;
46
47
  const tracker = getKeyStateTracker();
47
48
  this._value = tracker.store.state.heldCodes;
48
49
  this._host.requestUpdate();
@@ -42,8 +42,11 @@ var HeldKeysController = class {
42
42
  }
43
43
  /** Subscribes to the tracker store and updates the internal state when changes occur. */
44
44
  hostConnected() {
45
- if (isServer()) return;
45
+ if (this._unsubscribe) return;
46
46
  const tracker = getKeyStateTracker();
47
+ this._value = tracker.store.state.heldKeys;
48
+ this._host.requestUpdate();
49
+ if (isServer()) return;
47
50
  const subscription = tracker.store.subscribe(() => {
48
51
  const { heldKeys } = tracker.store.state;
49
52
  this._value = heldKeys;
@@ -33,6 +33,7 @@ var HotkeyHintController = class {
33
33
  * while disconnected.
34
34
  */
35
35
  hostConnected() {
36
+ if (this.unsubscribe) return;
36
37
  this.update();
37
38
  this.host.requestUpdate();
38
39
  if (isServer()) return;
@@ -76,6 +76,7 @@ var HotkeyRecorderController = class {
76
76
  }
77
77
  /** Subscribes to the recorder store and updates the internal state when changes occur. */
78
78
  hostConnected() {
79
+ if (this._unsubscribe) return;
79
80
  if (isServer()) return;
80
81
  const subscription = this._recorder.store.subscribe(() => {
81
82
  const { isRecording, recordedHotkey, steps } = this._recorder.store.state;
@@ -35,6 +35,7 @@ var HotkeyRegistrationsController = class {
35
35
  this._host.addController(this);
36
36
  }
37
37
  hostConnected() {
38
+ if (this._unsubscribe) return;
38
39
  const manager = getHotkeyManager();
39
40
  const read = () => Array.from(manager.registrations.state.values()).map(toHotkeyRegistrationView);
40
41
  this._hotkeys = read();
@@ -40,6 +40,7 @@ var HotkeyScopesStateController = class {
40
40
  * disconnected.
41
41
  */
42
42
  hostConnected() {
43
+ if (this._unsubscribe) return;
43
44
  this._host.requestUpdate();
44
45
  if (isServer()) return;
45
46
  const subscription = getHotkeyManager().scopes.subscribe(() => this._host.requestUpdate());
@@ -32,12 +32,6 @@ type MaybeGetter<T> = T | (() => T);
32
32
  * class MyElement extends LitElement {
33
33
  * private save = new HotkeyController(this, 'Mod+S', () => this.doSave())
34
34
  * private top = new HotkeyController(this, ['G', 'G'], () => this.scrollToTop())
35
- *
36
- * constructor() {
37
- * super()
38
- * this.addController(this.save)
39
- * this.addController(this.top)
40
- * }
41
35
  * }
42
36
  * ```
43
37
  */
@@ -50,7 +44,7 @@ export declare class HotkeyController implements ReactiveController {
50
44
  private _boundCallback;
51
45
  private _unsubscribe;
52
46
  /**
53
- * @param host - The Lit component that owns this controller. Add the controller with `addController()`.
47
+ * @param host - The Lit component that owns this controller. The controller adds itself to it.
54
48
  * @param hotkey - The chord, sequence, or app hotkey to register, or a getter. A getter is read on connection and after host updates. `undefined`, `null`, or `''` registers nothing, for an optional shortcut.
55
49
  * @param callback - Called with the host as `this`.
56
50
  * @param options - Options or a getter. Property getters are read on connection and after host updates.
@@ -18,12 +18,6 @@ import { createHotkeyBindings, prepareHotkeyDefinitions } from "@tanstack/hotkey
18
18
  * class MyElement extends LitElement {
19
19
  * private save = new HotkeyController(this, 'Mod+S', () => this.doSave())
20
20
  * private top = new HotkeyController(this, ['G', 'G'], () => this.scrollToTop())
21
- *
22
- * constructor() {
23
- * super()
24
- * this.addController(this.save)
25
- * this.addController(this.top)
26
- * }
27
21
  * }
28
22
  * ```
29
23
  */
@@ -40,10 +34,11 @@ var HotkeyController = class {
40
34
  this._hotkey = _hotkey;
41
35
  this._options = _options;
42
36
  this._boundCallback = callback.bind(this._host);
37
+ this._host.addController(this);
43
38
  }
44
39
  /** Registers when connected and a target is available. */
45
40
  hostConnected() {
46
- if (isServer()) return;
41
+ if (this._connected || isServer()) return;
47
42
  this._connected = true;
48
43
  this._unsubscribe = subscribeAppHotkeys(() => this._sync());
49
44
  this._sync();
@@ -63,6 +63,7 @@ var KeyHoldController = class {
63
63
  this._host.addController(this);
64
64
  }
65
65
  hostConnected() {
66
+ if (this._unsubscribe) return;
66
67
  if (this._refresh()) this._host.requestUpdate();
67
68
  if (isServer()) return;
68
69
  const subscription = getKeyStateTracker().store.subscribe(() => {
@@ -1,20 +1,27 @@
1
1
  import { AppHotkeyControllerOptions, HotkeyControllerOptionsFor } from "../controllers/hotkey.js";
2
2
  import { AppHotkey, HotkeyCallback, HotkeyCallbackContext, HotkeyCallbackContextFor, RegisterableHotkey } from "@tanstack/hotkeys";
3
- import { LitElement } from "lit";
3
+ import { ReactiveElement } from "lit";
4
4
  //#region src/decorators/hotkey.d.ts
5
5
  /**
6
- * A method decorator. The method's context parameter can be typed as the
7
- * context for the decorated hotkey's kind, such as `HotkeyChordCallbackContext`.
6
+ * A method decorator for legacy (`experimentalDecorators`) and standard
7
+ * decorators. The method's context parameter can be typed as the context for
8
+ * the decorated hotkey's kind, such as `HotkeyChordCallbackContext`.
8
9
  */
9
- type HotkeyDecorator<TContext extends HotkeyCallbackContext = HotkeyCallbackContext> = <T extends HotkeyCallback<TContext>>(proto: LitElement, propertyKey: string, descriptor?: TypedPropertyDescriptor<T>) => void;
10
+ export type HotkeyDecorator<TContext extends HotkeyCallbackContext = HotkeyCallbackContext> = {
11
+ <T extends HotkeyCallback<TContext>>(proto: ReactiveElement, name: string | symbol, descriptor?: TypedPropertyDescriptor<T>): void;
12
+ <THost extends ReactiveElement, T extends HotkeyCallback<TContext>>(method: T, context: ClassMethodDecoratorContext<THost, T>): void;
13
+ };
10
14
  /**
11
15
  * Decorator that registers a hotkey on the element when it connects and
12
16
  * unregisters when it disconnects: a chord such as `'Mod+S'`, a sequence such
13
17
  * as `['G', 'G']`, or an app hotkey. Uses {@link HotkeyController} under the hood.
14
18
  *
19
+ * Works with legacy (`experimentalDecorators`) and standard decorators. The
20
+ * method is looked up when the hotkey fires, so subclass overrides run.
21
+ *
15
22
  * @param hotkey - The chord, sequence, or app hotkey to listen for.
16
23
  * @param options - Optional registration options (target, platform, enabled, etc.).
17
- * @returns A method decorator for use on LitElement methods.
24
+ * @returns A method decorator for use on Lit element methods.
18
25
  *
19
26
  * @example
20
27
  * ```ts
@@ -1,21 +1,18 @@
1
1
  import { HotkeyController } from "../controllers/hotkey.js";
2
2
 
3
3
  //#region src/decorators/hotkey.ts
4
- function hotkey(hotkey, options = {}) {
5
- return function(proto, propertyKey, descriptor) {
6
- const originalConnected = proto.connectedCallback;
7
- const controllerKey = Symbol(`@hotkey:${propertyKey}`);
8
- proto.connectedCallback = function() {
9
- originalConnected.call(this);
10
- const host = this;
11
- if (!host[controllerKey]) {
12
- const callback = descriptor?.value ? descriptor.value.bind(this) : host[propertyKey];
13
- if (typeof callback !== "function") throw new Error(`@hotkey decorator can only be applied to functions`);
14
- const controller = new HotkeyController(this, hotkey, callback, options);
15
- host[controllerKey] = controller;
16
- this.addController(controller);
17
- }
4
+ function hotkey(hotkey, options) {
5
+ return (proto, context) => {
6
+ const standard = typeof context === "object";
7
+ const init = (host) => {
8
+ new HotkeyController(host, hotkey, function(event, callbackContext) {
9
+ return (standard ? context.access.get(this) : this[context]).call(this, event, callbackContext);
10
+ }, options);
18
11
  };
12
+ if (standard) context.addInitializer(function() {
13
+ init(this);
14
+ });
15
+ else proto.constructor.addInitializer(init);
19
16
  };
20
17
  }
21
18
 
@@ -1,2 +1,2 @@
1
- import { hotkey } from "./hotkey.js";
2
- export { hotkey };
1
+ import { HotkeyDecorator, hotkey } from "./hotkey.js";
2
+ export { HotkeyDecorator, hotkey };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AppHotkeyControllerOptions, HotkeyController, HotkeyControllerChordOptions, HotkeyControllerCommonOptions, HotkeyControllerOptions, HotkeyControllerOptionsFor, HotkeyControllerSequenceOptions, HotkeyControllerTarget } from "./controllers/hotkey.js";
2
- import { hotkey } from "./decorators/hotkey.js";
2
+ import { HotkeyDecorator, hotkey } from "./decorators/hotkey.js";
3
3
  import "./decorators/index.js";
4
4
  import { AppHotkeysController } from "./controllers/app-hotkeys.js";
5
5
  import { HeldKeyCodesController } from "./controllers/held-key-codes.js";
@@ -11,4 +11,4 @@ import { HotkeyHintController } from "./controllers/hotkey-hint.js";
11
11
  import { HotkeyScopesStateController } from "./controllers/hotkey-scopes-state.js";
12
12
  import "./controllers/index.js";
13
13
  export * from "@tanstack/hotkeys";
14
- export { AppHotkeyControllerOptions, AppHotkeysController, HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyControllerChordOptions, HotkeyControllerCommonOptions, HotkeyControllerOptions, HotkeyControllerOptionsFor, HotkeyControllerSequenceOptions, HotkeyControllerTarget, HotkeyHintController, HotkeyRecorderController, HotkeyRegistrationsController, HotkeyScopesStateController, KeyHoldController, hotkey };
14
+ export { AppHotkeyControllerOptions, AppHotkeysController, HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyControllerChordOptions, HotkeyControllerCommonOptions, HotkeyControllerOptions, HotkeyControllerOptionsFor, HotkeyControllerSequenceOptions, HotkeyControllerTarget, HotkeyDecorator, HotkeyHintController, HotkeyRecorderController, HotkeyRegistrationsController, HotkeyScopesStateController, KeyHoldController, hotkey };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/lit-hotkeys",
3
- "version": "0.20.1",
3
+ "version": "0.22.0",
4
4
  "description": "Lit adapter for TanStack Hotkeys",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "skills"
36
36
  ],
37
37
  "dependencies": {
38
- "@tanstack/hotkeys": "0.22.0"
38
+ "@tanstack/hotkeys": "0.23.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "lit": "^3.3.3"
@@ -41,17 +41,12 @@ export class MyEditor extends LitElement {
41
41
  @hotkey('Mod+K')
42
42
  openSearch() {}
43
43
 
44
- constructor() {
45
- super()
46
- this.addController(
47
- new HotkeyController(
48
- this,
49
- 'Mod+S',
50
- () => this.save(),
51
- () => ({ enabled: this.dirty }),
52
- ),
53
- )
54
- }
44
+ private saveHotkey = new HotkeyController(
45
+ this,
46
+ 'Mod+S',
47
+ () => this.save(),
48
+ () => ({ enabled: this.dirty }),
49
+ )
55
50
 
56
51
  save() {}
57
52
 
@@ -66,7 +61,7 @@ export class MyEditor extends LitElement {
66
61
  | API | What it's for |
67
62
  | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
68
63
  | `@hotkey(hotkey, options?)` | Run the decorated method when the hotkey fires, with options fixed when the class is defined. |
69
- | `new HotkeyController(host, hotkey, callback, options?)` | Register a hotkey whose options can change; add it with `this.addController(...)`. |
64
+ | `new HotkeyController(host, hotkey, callback, options?)` | Register a hotkey whose options can change. It adds itself to `host`, so a class field is enough. |
70
65
  | `provideAppHotkeys(appHotkeys, options?)` | Declare app hotkeys and load the user's custom hotkeys once at startup, since Lit has no provider. |
71
66
  | `getHotkeyManager().activateScope(scope, options?)` | Turn on a scope, such as an exclusive one for a dialog; it returns the function that turns the scope off. |
72
67
  | `new AppHotkeysController(host)` | Read `appHotkeys`, the cheat sheet `list`, and `customHotkeys`, and change custom hotkeys with `setCustomHotkey`, `resetCustomHotkey`, and `findConflicts`. |
@@ -82,9 +77,9 @@ The package also re-exports `@tanstack/hotkeys`, such as `formatForDisplay`, `ma
82
77
 
83
78
  ## Pitfalls
84
79
 
85
- - **Add `HotkeyController` to its host.** It is the only controller that doesn't add itself, so without `this.addController(...)` it never registers.
86
- - **`@hotkey` is a legacy decorator.** It needs `"experimentalDecorators": true` and `"useDefineForClassFields": false`; with standard decorators it fails with TS1241. Use `HotkeyController` in that case.
87
- - **Decorator options are fixed when the class is defined.** For options that read instance state, or a `target` from a `ref`, use `HotkeyController` with an options function, which reruns after each update.
80
+ - **`@hotkey` works with standard and legacy decorators.** It needs no tsconfig change. Lit's `@property` and `@state` have their own rules: with standard decorators they need `accessor` fields; with `"experimentalDecorators": true` they need `"useDefineForClassFields": false`. Match the project's existing mode.
81
+ - **Decorator options are fixed when the class is defined.** For options that read instance state, or a `target` from a `ref`, use `HotkeyController` with an options function, which reruns after each update of its host.
82
+ - **Render a `ref` target in the controller's host.** The options function reruns only when the host updates. If a child element renders the target, keep it in a reactive property or call `requestUpdate()` on the host when it changes.
88
83
  - **Release every scope you activate.** Lit has no provider, so a dialog activates its scope in `connectedCallback` with `this.release = getHotkeyManager().activateScope('dialog-1', { exclusive: true })` and calls `this.release()` in `disconnectedCallback`. A forgotten release keeps every other hotkey suspended. Give each open dialog its own scope name.
89
84
 
90
85
  When declaring app hotkeys, building a cheat sheet, or letting users rebind shortcuts, load `@tanstack/hotkeys#app-hotkeys` and read [app hotkeys in Lit](references/app-hotkeys.md).