@tanstack/lit-hotkeys 0.16.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/hotkey-hint.d.ts +9 -2
- package/dist/controllers/hotkey-hint.js +14 -4
- package/dist/controllers/hotkey-scopes-state.d.ts +40 -0
- package/dist/controllers/hotkey-scopes-state.js +54 -0
- package/dist/controllers/index.d.ts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/package.json +6 -4
- package/skills/lit-hotkeys/SKILL.md +94 -0
- package/skills/lit-hotkeys/references/app-hotkeys.md +71 -0
|
@@ -10,9 +10,16 @@ export declare class HotkeyHintController implements ReactiveController {
|
|
|
10
10
|
private visible;
|
|
11
11
|
/** Attaches to the host; getter arguments can read changing reactive properties. */
|
|
12
12
|
constructor(host: ReactiveControllerHost, hotkey: RegisterableHotkey | AppHotkey | (() => RegisterableHotkey | AppHotkey), options?: HeldModifierOptions | (() => HeldModifierOptions));
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Current visibility. True only while the hotkey can fire: some registration
|
|
15
|
+
* of it is enabled and in an active scope. A hotkey on an element counts only while focus is inside it.
|
|
16
|
+
*/
|
|
14
17
|
get value(): boolean;
|
|
15
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Subscribes on connection, then requests renders only when hint visibility
|
|
20
|
+
* changes. Reconnecting always requests one, since the hint may have changed
|
|
21
|
+
* while disconnected.
|
|
22
|
+
*/
|
|
16
23
|
hostConnected(): void;
|
|
17
24
|
/** Re-evaluates getter arguments when host properties trigger an update. */
|
|
18
25
|
hostUpdate(): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getAppHotkeysStore, subscribeAppHotkeys } from "./_app-hotkeys.js";
|
|
2
|
-
import { getKeyStateTracker, isAppHotkey, matchesHeldModifiers, resolveAppHotkey } from "@tanstack/hotkeys";
|
|
2
|
+
import { getHotkeyManager, getKeyStateTracker, isAppHotkey, matchesHeldModifiers, resolveAppHotkey } from "@tanstack/hotkeys";
|
|
3
3
|
|
|
4
4
|
//#region src/controllers/hotkey-hint.ts
|
|
5
5
|
/** Modifier hint controller. Getter arguments can read reactive host properties. */
|
|
@@ -16,20 +16,30 @@ var HotkeyHintController = class {
|
|
|
16
16
|
this.options = options;
|
|
17
17
|
host.addController(this);
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Current visibility. True only while the hotkey can fire: some registration
|
|
21
|
+
* of it is enabled and in an active scope. A hotkey on an element counts only while focus is inside it.
|
|
22
|
+
*/
|
|
20
23
|
get value() {
|
|
21
24
|
return this.visible;
|
|
22
25
|
}
|
|
23
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Subscribes on connection, then requests renders only when hint visibility
|
|
28
|
+
* changes. Reconnecting always requests one, since the hint may have changed
|
|
29
|
+
* while disconnected.
|
|
30
|
+
*/
|
|
24
31
|
hostConnected() {
|
|
25
32
|
this.update();
|
|
33
|
+
this.host.requestUpdate();
|
|
26
34
|
const refresh = () => {
|
|
27
35
|
if (this.update()) this.host.requestUpdate();
|
|
28
36
|
};
|
|
29
37
|
const subscription = getKeyStateTracker().store.subscribe(refresh);
|
|
38
|
+
const registrations = getHotkeyManager().registrations.subscribe(refresh);
|
|
30
39
|
const unsubscribeAppHotkeys = subscribeAppHotkeys(refresh);
|
|
31
40
|
this.unsubscribe = () => {
|
|
32
41
|
subscription.unsubscribe();
|
|
42
|
+
registrations.unsubscribe();
|
|
33
43
|
unsubscribeAppHotkeys();
|
|
34
44
|
};
|
|
35
45
|
}
|
|
@@ -47,7 +57,7 @@ var HotkeyHintController = class {
|
|
|
47
57
|
update() {
|
|
48
58
|
const value = typeof this.hotkey === "function" ? this.hotkey() : this.hotkey;
|
|
49
59
|
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);
|
|
60
|
+
const next = hotkey !== null && matchesHeldModifiers(hotkey, getKeyStateTracker().store.state.heldKeys, typeof this.options === "function" ? this.options() : this.options) && getHotkeyManager().hasActiveRegistration(value);
|
|
51
61
|
const changed = next !== this.visible;
|
|
52
62
|
this.visible = next;
|
|
53
63
|
return changed;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { HotkeyScopesState } from "@tanstack/hotkeys";
|
|
2
|
+
import { ReactiveController, ReactiveControllerHost } from "lit";
|
|
3
|
+
//#region src/controllers/hotkey-scopes-state.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A Lit ReactiveController that reads the hotkey manager's scope state: the
|
|
6
|
+
* active scopes, the exclusive scope that owns the keyboard, and the scopes it
|
|
7
|
+
* allows or suspends. Triggers host updates when scopes activate or release.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* class KeyboardOwner extends LitElement {
|
|
12
|
+
* private scopes = new HotkeyScopesStateController(this)
|
|
13
|
+
*
|
|
14
|
+
* render() {
|
|
15
|
+
* const { exclusive } = this.scopes.value
|
|
16
|
+
* return exclusive ? html`<p>Shortcuts belong to ${exclusive}</p>` : nothing
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare class HotkeyScopesStateController implements ReactiveController {
|
|
22
|
+
private _host;
|
|
23
|
+
/** The unsubscribe function to unsubscribe from the scopes store. */
|
|
24
|
+
private _unsubscribe;
|
|
25
|
+
/** The current scope state. */
|
|
26
|
+
get value(): HotkeyScopesState;
|
|
27
|
+
/**
|
|
28
|
+
* @param _host - The Lit component that owns this controller.
|
|
29
|
+
*/
|
|
30
|
+
constructor(_host: ReactiveControllerHost);
|
|
31
|
+
/**
|
|
32
|
+
* Subscribes to the scopes store and updates the host when it changes.
|
|
33
|
+
* Connecting requests an update too, since scopes may have changed while
|
|
34
|
+
* disconnected.
|
|
35
|
+
*/
|
|
36
|
+
hostConnected(): void;
|
|
37
|
+
/** Unsubscribes from the scopes store. */
|
|
38
|
+
hostDisconnected(): void;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getHotkeyManager } from "@tanstack/hotkeys";
|
|
2
|
+
|
|
3
|
+
//#region src/controllers/hotkey-scopes-state.ts
|
|
4
|
+
/**
|
|
5
|
+
* A Lit ReactiveController that reads the hotkey manager's scope state: the
|
|
6
|
+
* active scopes, the exclusive scope that owns the keyboard, and the scopes it
|
|
7
|
+
* allows or suspends. Triggers host updates when scopes activate or release.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* class KeyboardOwner extends LitElement {
|
|
12
|
+
* private scopes = new HotkeyScopesStateController(this)
|
|
13
|
+
*
|
|
14
|
+
* render() {
|
|
15
|
+
* const { exclusive } = this.scopes.value
|
|
16
|
+
* return exclusive ? html`<p>Shortcuts belong to ${exclusive}</p>` : nothing
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
var HotkeyScopesStateController = class {
|
|
22
|
+
_host;
|
|
23
|
+
/** The unsubscribe function to unsubscribe from the scopes store. */
|
|
24
|
+
_unsubscribe;
|
|
25
|
+
/** The current scope state. */
|
|
26
|
+
get value() {
|
|
27
|
+
return getHotkeyManager().scopes.state;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* @param _host - The Lit component that owns this controller.
|
|
31
|
+
*/
|
|
32
|
+
constructor(_host) {
|
|
33
|
+
this._host = _host;
|
|
34
|
+
this._host.addController(this);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Subscribes to the scopes store and updates the host when it changes.
|
|
38
|
+
* Connecting requests an update too, since scopes may have changed while
|
|
39
|
+
* disconnected.
|
|
40
|
+
*/
|
|
41
|
+
hostConnected() {
|
|
42
|
+
this._host.requestUpdate();
|
|
43
|
+
const subscription = getHotkeyManager().scopes.subscribe(() => this._host.requestUpdate());
|
|
44
|
+
this._unsubscribe = () => subscription.unsubscribe();
|
|
45
|
+
}
|
|
46
|
+
/** Unsubscribes from the scopes store. */
|
|
47
|
+
hostDisconnected() {
|
|
48
|
+
this._unsubscribe?.();
|
|
49
|
+
this._unsubscribe = void 0;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { HotkeyScopesStateController };
|
|
@@ -6,4 +6,5 @@ import { HotkeyRecorderController } from "./hotkey-recorder.js";
|
|
|
6
6
|
import { HotkeyRegistrationsController } from "./hotkey-registrations.js";
|
|
7
7
|
import { KeyHoldController } from "./key-hold.js";
|
|
8
8
|
import { HotkeyHintController } from "./hotkey-hint.js";
|
|
9
|
-
|
|
9
|
+
import { HotkeyScopesStateController } from "./hotkey-scopes-state.js";
|
|
10
|
+
export { AppHotkeyControllerOptions, AppHotkeysController, HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyControllerChordOptions, HotkeyControllerCommonOptions, HotkeyControllerOptions, HotkeyControllerOptionsFor, HotkeyControllerSequenceOptions, HotkeyControllerTarget, HotkeyHintController, HotkeyRecorderController, HotkeyRegistrationsController, HotkeyScopesStateController, KeyHoldController };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { HotkeyRecorderController } from "./controllers/hotkey-recorder.js";
|
|
|
8
8
|
import { HotkeyRegistrationsController } from "./controllers/hotkey-registrations.js";
|
|
9
9
|
import { KeyHoldController } from "./controllers/key-hold.js";
|
|
10
10
|
import { HotkeyHintController } from "./controllers/hotkey-hint.js";
|
|
11
|
+
import { HotkeyScopesStateController } from "./controllers/hotkey-scopes-state.js";
|
|
11
12
|
import "./controllers/index.js";
|
|
12
13
|
export * from "@tanstack/hotkeys";
|
|
13
|
-
export { AppHotkeyControllerOptions, AppHotkeysController, HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyControllerChordOptions, HotkeyControllerCommonOptions, HotkeyControllerOptions, HotkeyControllerOptionsFor, HotkeyControllerSequenceOptions, HotkeyControllerTarget, HotkeyHintController, HotkeyRecorderController, HotkeyRegistrationsController, KeyHoldController, hotkey };
|
|
14
|
+
export { AppHotkeyControllerOptions, AppHotkeysController, HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyControllerChordOptions, HotkeyControllerCommonOptions, HotkeyControllerOptions, HotkeyControllerOptionsFor, HotkeyControllerSequenceOptions, HotkeyControllerTarget, HotkeyHintController, HotkeyRecorderController, HotkeyRegistrationsController, HotkeyScopesStateController, KeyHoldController, hotkey };
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,8 @@ import { HotkeyRecorderController } from "./controllers/hotkey-recorder.js";
|
|
|
7
7
|
import { HotkeyRegistrationsController } from "./controllers/hotkey-registrations.js";
|
|
8
8
|
import { KeyHoldController } from "./controllers/key-hold.js";
|
|
9
9
|
import { HotkeyHintController } from "./controllers/hotkey-hint.js";
|
|
10
|
+
import { HotkeyScopesStateController } from "./controllers/hotkey-scopes-state.js";
|
|
10
11
|
|
|
11
12
|
export * from "@tanstack/hotkeys"
|
|
12
13
|
|
|
13
|
-
export { AppHotkeysController, HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyHintController, HotkeyRecorderController, HotkeyRegistrationsController, KeyHoldController, hotkey };
|
|
14
|
+
export { AppHotkeysController, HeldKeyCodesController, HeldKeysController, HotkeyController, 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.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Lit adapter for TanStack Hotkeys",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"keywords": [
|
|
18
18
|
"lit",
|
|
19
19
|
"tanstack",
|
|
20
|
-
"keys"
|
|
20
|
+
"keys",
|
|
21
|
+
"tanstack-intent"
|
|
21
22
|
],
|
|
22
23
|
"type": "module",
|
|
23
24
|
"types": "./dist/index.d.ts",
|
|
@@ -30,10 +31,11 @@
|
|
|
30
31
|
"node": ">=20"
|
|
31
32
|
},
|
|
32
33
|
"files": [
|
|
33
|
-
"dist"
|
|
34
|
+
"dist",
|
|
35
|
+
"skills"
|
|
34
36
|
],
|
|
35
37
|
"dependencies": {
|
|
36
|
-
"@tanstack/hotkeys": "0.
|
|
38
|
+
"@tanstack/hotkeys": "0.18.0"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
|
39
41
|
"lit": "^3.3.3"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lit-hotkeys
|
|
3
|
+
description: >-
|
|
4
|
+
Use when adding or fixing keyboard shortcuts in Lit web components with
|
|
5
|
+
@tanstack/lit-hotkeys: the @hotkey decorator, HotkeyController with an
|
|
6
|
+
options function, element targets, scopes activated from the hotkey manager,
|
|
7
|
+
app hotkey and recorder controllers, held-key controllers, or replacing
|
|
8
|
+
keydown listeners in connectedCallback.
|
|
9
|
+
metadata:
|
|
10
|
+
purpose: >-
|
|
11
|
+
Lit bindings for TanStack Hotkeys: the decorator, reactive controllers,
|
|
12
|
+
manager-activated scopes, and the Lit-specific mistakes to avoid.
|
|
13
|
+
type: framework
|
|
14
|
+
framework: lit
|
|
15
|
+
library: '@tanstack/lit-hotkeys'
|
|
16
|
+
library_version: '0.16.0'
|
|
17
|
+
sources:
|
|
18
|
+
- src/decorators/hotkey.ts
|
|
19
|
+
- src/controllers/hotkey.ts
|
|
20
|
+
- src/controllers/app-hotkeys.ts
|
|
21
|
+
- src/controllers/hotkey-recorder.ts
|
|
22
|
+
- TanStack/hotkeys:docs/framework/lit/quick-start.md
|
|
23
|
+
- TanStack/hotkeys:docs/framework/lit/guides/app-hotkeys.md
|
|
24
|
+
requires:
|
|
25
|
+
- '@tanstack/hotkeys#hotkeys-core'
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
# TanStack Hotkeys in Lit
|
|
29
|
+
|
|
30
|
+
Before starting, load the `@tanstack/hotkeys#hotkeys-core` skill (`npx @tanstack/intent@latest load @tanstack/hotkeys#hotkeys-core`).
|
|
31
|
+
|
|
32
|
+
```ts no-check
|
|
33
|
+
import { LitElement, html } from 'lit'
|
|
34
|
+
import { customElement, state } from 'lit/decorators.js'
|
|
35
|
+
import { HotkeyController, hotkey } from '@tanstack/lit-hotkeys'
|
|
36
|
+
|
|
37
|
+
@customElement('my-editor')
|
|
38
|
+
export class MyEditor extends LitElement {
|
|
39
|
+
@state() private dirty = false
|
|
40
|
+
|
|
41
|
+
@hotkey('Mod+K')
|
|
42
|
+
openSearch() {}
|
|
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
|
+
}
|
|
55
|
+
|
|
56
|
+
save() {}
|
|
57
|
+
|
|
58
|
+
render() {
|
|
59
|
+
return html`<textarea @input=${() => (this.dirty = true)}></textarea>`
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API
|
|
65
|
+
|
|
66
|
+
| API | What it's for |
|
|
67
|
+
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| `@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(...)`. |
|
|
70
|
+
| `provideAppHotkeys(appHotkeys, options?)` | Declare app hotkeys and load the user's custom hotkeys once at startup, since Lit has no provider. |
|
|
71
|
+
| `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
|
+
| `new AppHotkeysController(host)` | Read `appHotkeys`, the cheat sheet `list`, and `customHotkeys`, and change custom hotkeys with `setCustomHotkey`, `resetCustomHotkey`, and `findConflicts`. |
|
|
73
|
+
| `new HotkeyRecorderController(host, options)` | Record a chord or sequence the user presses, for a settings screen. |
|
|
74
|
+
| `new HotkeyRegistrationsController(host)` | Read `.hotkeys`, every registered hotkey with its options, `meta`, and `status` (whether it's enabled and in an active scope). |
|
|
75
|
+
| `new HotkeyScopesStateController(host)` | Read `.value`, the active scopes and the exclusive scope that owns the keyboard. |
|
|
76
|
+
| `new HotkeyHintController(host, hotkey, options?)` | Read `.value`, whether held modifiers should reveal a shortcut's hint. False unless a registration of the hotkey can fire: enabled, in an active scope, and with focus inside its element target. |
|
|
77
|
+
| `new KeyHoldController(host, key)` | Read `.value`, whether one key is held. |
|
|
78
|
+
| `new HeldKeysController(host)` | Read `.value`, every key that's held right now. |
|
|
79
|
+
| `new HeldKeyCodesController(host)` | Read `.value`, which maps each held key to its physical `event.code`. |
|
|
80
|
+
|
|
81
|
+
The package also re-exports `@tanstack/hotkeys`, such as `formatForDisplay`, `matchesKeyboardEvent`, and `defineAppHotkeys`.
|
|
82
|
+
|
|
83
|
+
## Pitfalls
|
|
84
|
+
|
|
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.
|
|
88
|
+
- **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
|
+
|
|
90
|
+
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).
|
|
91
|
+
|
|
92
|
+
## API discovery and docs
|
|
93
|
+
|
|
94
|
+
Signatures: `node_modules/@tanstack/lit-hotkeys/dist/controllers/*.d.ts` and `dist/decorators/*.d.ts`. Guides: `https://tanstack.com/hotkeys/latest/docs/framework/lit/guides/<page>.md`, such as `hotkeys` or `app-hotkeys`.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# App hotkeys in Lit
|
|
2
|
+
|
|
3
|
+
The `@tanstack/hotkeys#app-hotkeys` skill owns the rules. This is the Lit wiring.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
// app-hotkeys.ts
|
|
7
|
+
import { defineAppHotkeys } from '@tanstack/lit-hotkeys'
|
|
8
|
+
|
|
9
|
+
export const appHotkeys = defineAppHotkeys({
|
|
10
|
+
'doc.save': { hotkey: 'Mod+S', meta: { name: 'Save' } },
|
|
11
|
+
'help.shortcuts': { hotkey: 'Mod+/', meta: { name: 'Keyboard shortcuts' } },
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
declare module '@tanstack/lit-hotkeys' {
|
|
15
|
+
interface Register {
|
|
16
|
+
appHotkeys: typeof appHotkeys
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// main.ts, before any element renders
|
|
23
|
+
import { provideAppHotkeys } from '@tanstack/lit-hotkeys'
|
|
24
|
+
import { appHotkeys } from './app-hotkeys'
|
|
25
|
+
|
|
26
|
+
provideAppHotkeys(appHotkeys, {
|
|
27
|
+
customHotkeys: JSON.parse(localStorage.getItem('hotkeys') ?? '{}'),
|
|
28
|
+
onCustomHotkeysChange: (custom) =>
|
|
29
|
+
localStorage.setItem('hotkeys', JSON.stringify(custom)),
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`AppHotkeysController` throws until `provideAppHotkeys` has run. When saved shortcuts come from a server, await them first. Register with `@hotkey(appHotkeys['doc.save'])` or `HotkeyController`; both follow the user's custom hotkey. `AppHotkeysController` has `list` for a cheat sheet, plus `setCustomHotkey`, `findConflicts`, and `resetCustomHotkey`.
|
|
34
|
+
|
|
35
|
+
A settings row records with `HotkeyRecorderController`:
|
|
36
|
+
|
|
37
|
+
```ts no-check
|
|
38
|
+
import { LitElement, html } from 'lit'
|
|
39
|
+
import { property } from 'lit/decorators.js'
|
|
40
|
+
import {
|
|
41
|
+
AppHotkeysController,
|
|
42
|
+
HotkeyRecorderController,
|
|
43
|
+
formatForDisplay,
|
|
44
|
+
} from '@tanstack/lit-hotkeys'
|
|
45
|
+
import type { AppHotkeyId } from '@tanstack/lit-hotkeys'
|
|
46
|
+
|
|
47
|
+
export class ShortcutRow extends LitElement {
|
|
48
|
+
@property() hotkeyId!: AppHotkeyId
|
|
49
|
+
private app = new AppHotkeysController(this)
|
|
50
|
+
private recorder = new HotkeyRecorderController(this, () => ({
|
|
51
|
+
maxSteps: Infinity,
|
|
52
|
+
validate: (hotkey) => {
|
|
53
|
+
const [conflict] = this.app.findConflicts(this.hotkeyId, hotkey)
|
|
54
|
+
return conflict ? `Used by ${conflict.meta?.name ?? conflict.id}` : true
|
|
55
|
+
},
|
|
56
|
+
onRecord: (hotkey) => this.app.setCustomHotkey(this.hotkeyId, hotkey),
|
|
57
|
+
onClear: () => this.app.setCustomHotkey(this.hotkeyId, null),
|
|
58
|
+
}))
|
|
59
|
+
|
|
60
|
+
render() {
|
|
61
|
+
const { meta, hotkey } = this.app.appHotkeys[this.hotkeyId]
|
|
62
|
+
return html`
|
|
63
|
+
${meta?.name}: ${hotkey ? formatForDisplay(hotkey) : 'Unassigned'}
|
|
64
|
+
<button @click=${() => this.recorder.startRecording()}>Change</button>
|
|
65
|
+
<button @click=${() => this.app.resetCustomHotkey(this.hotkeyId)}>
|
|
66
|
+
Reset
|
|
67
|
+
</button>
|
|
68
|
+
`
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|