@tanstack/lit-hotkeys 0.0.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.
- package/README.md +123 -0
- package/dist/constants.cjs +27 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/constants.js +25 -0
- package/dist/constants.js.map +1 -0
- package/dist/controllers/held-key-codes.cjs +61 -0
- package/dist/controllers/held-key-codes.cjs.map +1 -0
- package/dist/controllers/held-key-codes.d.cts +45 -0
- package/dist/controllers/held-key-codes.d.ts +45 -0
- package/dist/controllers/held-key-codes.js +61 -0
- package/dist/controllers/held-key-codes.js.map +1 -0
- package/dist/controllers/held-keys.cjs +58 -0
- package/dist/controllers/held-keys.cjs.map +1 -0
- package/dist/controllers/held-keys.d.cts +44 -0
- package/dist/controllers/held-keys.d.ts +44 -0
- package/dist/controllers/held-keys.js +58 -0
- package/dist/controllers/held-keys.js.map +1 -0
- package/dist/controllers/hotkey-recorder.cjs +104 -0
- package/dist/controllers/hotkey-recorder.cjs.map +1 -0
- package/dist/controllers/hotkey-recorder.d.cts +75 -0
- package/dist/controllers/hotkey-recorder.d.ts +75 -0
- package/dist/controllers/hotkey-recorder.js +104 -0
- package/dist/controllers/hotkey-recorder.js.map +1 -0
- package/dist/controllers/hotkey-sequence-recorder.cjs +117 -0
- package/dist/controllers/hotkey-sequence-recorder.cjs.map +1 -0
- package/dist/controllers/hotkey-sequence-recorder.d.cts +83 -0
- package/dist/controllers/hotkey-sequence-recorder.d.ts +83 -0
- package/dist/controllers/hotkey-sequence-recorder.js +117 -0
- package/dist/controllers/hotkey-sequence-recorder.js.map +1 -0
- package/dist/controllers/hotkey-sequence.cjs +58 -0
- package/dist/controllers/hotkey-sequence.cjs.map +1 -0
- package/dist/controllers/hotkey-sequence.d.cts +45 -0
- package/dist/controllers/hotkey-sequence.d.ts +45 -0
- package/dist/controllers/hotkey-sequence.js +58 -0
- package/dist/controllers/hotkey-sequence.js.map +1 -0
- package/dist/controllers/hotkey.cjs +60 -0
- package/dist/controllers/hotkey.cjs.map +1 -0
- package/dist/controllers/hotkey.d.cts +45 -0
- package/dist/controllers/hotkey.d.ts +45 -0
- package/dist/controllers/hotkey.js +60 -0
- package/dist/controllers/hotkey.js.map +1 -0
- package/dist/controllers/index.d.cts +7 -0
- package/dist/controllers/index.d.ts +7 -0
- package/dist/controllers/key-hold.cjs +78 -0
- package/dist/controllers/key-hold.cjs.map +1 -0
- package/dist/controllers/key-hold.d.cts +62 -0
- package/dist/controllers/key-hold.d.ts +62 -0
- package/dist/controllers/key-hold.js +78 -0
- package/dist/controllers/key-hold.js.map +1 -0
- package/dist/decorators/hotkey-sequence.cjs +45 -0
- package/dist/decorators/hotkey-sequence.cjs.map +1 -0
- package/dist/decorators/hotkey-sequence.d.cts +28 -0
- package/dist/decorators/hotkey-sequence.d.ts +28 -0
- package/dist/decorators/hotkey-sequence.js +45 -0
- package/dist/decorators/hotkey-sequence.js.map +1 -0
- package/dist/decorators/hotkey.cjs +44 -0
- package/dist/decorators/hotkey.cjs.map +1 -0
- package/dist/decorators/hotkey.d.cts +27 -0
- package/dist/decorators/hotkey.d.ts +27 -0
- package/dist/decorators/hotkey.js +44 -0
- package/dist/decorators/hotkey.js.map +1 -0
- package/dist/decorators/index.d.cts +2 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/index.cjs +27 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +13 -0
- package/package.json +60 -0
- package/src/constants.ts +23 -0
- package/src/controllers/held-key-codes.ts +67 -0
- package/src/controllers/held-keys.ts +64 -0
- package/src/controllers/hotkey-recorder.ts +120 -0
- package/src/controllers/hotkey-sequence-recorder.ts +141 -0
- package/src/controllers/hotkey-sequence.ts +82 -0
- package/src/controllers/hotkey.ts +96 -0
- package/src/controllers/index.ts +7 -0
- package/src/controllers/key-hold.ts +88 -0
- package/src/decorators/hotkey-sequence.ts +68 -0
- package/src/decorators/hotkey.ts +70 -0
- package/src/decorators/index.ts +2 -0
- package/src/index.ts +6 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_hotkey_sequence = require('./controllers/hotkey-sequence.cjs');
|
|
3
|
+
const require_hotkey_sequence$1 = require('./decorators/hotkey-sequence.cjs');
|
|
4
|
+
const require_hotkey = require('./controllers/hotkey.cjs');
|
|
5
|
+
const require_hotkey$1 = require('./decorators/hotkey.cjs');
|
|
6
|
+
const require_held_key_codes = require('./controllers/held-key-codes.cjs');
|
|
7
|
+
const require_held_keys = require('./controllers/held-keys.cjs');
|
|
8
|
+
const require_hotkey_recorder = require('./controllers/hotkey-recorder.cjs');
|
|
9
|
+
const require_hotkey_sequence_recorder = require('./controllers/hotkey-sequence-recorder.cjs');
|
|
10
|
+
const require_key_hold = require('./controllers/key-hold.cjs');
|
|
11
|
+
|
|
12
|
+
exports.HeldKeyCodesController = require_held_key_codes.HeldKeyCodesController;
|
|
13
|
+
exports.HeldKeysController = require_held_keys.HeldKeysController;
|
|
14
|
+
exports.HotkeyController = require_hotkey.HotkeyController;
|
|
15
|
+
exports.HotkeyRecorderController = require_hotkey_recorder.HotkeyRecorderController;
|
|
16
|
+
exports.HotkeySequenceController = require_hotkey_sequence.HotkeySequenceController;
|
|
17
|
+
exports.HotkeySequenceRecorderController = require_hotkey_sequence_recorder.HotkeySequenceRecorderController;
|
|
18
|
+
exports.KeyHoldController = require_key_hold.KeyHoldController;
|
|
19
|
+
exports.hotkey = require_hotkey$1.hotkey;
|
|
20
|
+
exports.hotkeySequence = require_hotkey_sequence$1.hotkeySequence;
|
|
21
|
+
var _tanstack_hotkeys = require("@tanstack/hotkeys");
|
|
22
|
+
Object.keys(_tanstack_hotkeys).forEach(function (k) {
|
|
23
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return _tanstack_hotkeys[k]; }
|
|
26
|
+
});
|
|
27
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { hotkeySequence } from "./decorators/hotkey-sequence.cjs";
|
|
2
|
+
import { hotkey } from "./decorators/hotkey.cjs";
|
|
3
|
+
import { HeldKeyCodesController } from "./controllers/held-key-codes.cjs";
|
|
4
|
+
import { HeldKeysController } from "./controllers/held-keys.cjs";
|
|
5
|
+
import { HotkeyController } from "./controllers/hotkey.cjs";
|
|
6
|
+
import { HotkeyRecorderController } from "./controllers/hotkey-recorder.cjs";
|
|
7
|
+
import { HotkeySequenceController } from "./controllers/hotkey-sequence.cjs";
|
|
8
|
+
import { HotkeySequenceRecorderController } from "./controllers/hotkey-sequence-recorder.cjs";
|
|
9
|
+
import { KeyHoldController } from "./controllers/key-hold.cjs";
|
|
10
|
+
export * from "@tanstack/hotkeys";
|
|
11
|
+
export { HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyRecorderController, HotkeySequenceController, HotkeySequenceRecorderController, KeyHoldController, hotkey, hotkeySequence };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { hotkeySequence } from "./decorators/hotkey-sequence.js";
|
|
2
|
+
import { hotkey } from "./decorators/hotkey.js";
|
|
3
|
+
import { HeldKeyCodesController } from "./controllers/held-key-codes.js";
|
|
4
|
+
import { HeldKeysController } from "./controllers/held-keys.js";
|
|
5
|
+
import { HotkeyController } from "./controllers/hotkey.js";
|
|
6
|
+
import { HotkeyRecorderController } from "./controllers/hotkey-recorder.js";
|
|
7
|
+
import { HotkeySequenceController } from "./controllers/hotkey-sequence.js";
|
|
8
|
+
import { HotkeySequenceRecorderController } from "./controllers/hotkey-sequence-recorder.js";
|
|
9
|
+
import { KeyHoldController } from "./controllers/key-hold.js";
|
|
10
|
+
export * from "@tanstack/hotkeys";
|
|
11
|
+
export { HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyRecorderController, HotkeySequenceController, HotkeySequenceRecorderController, KeyHoldController, hotkey, hotkeySequence };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { HotkeySequenceController } from "./controllers/hotkey-sequence.js";
|
|
2
|
+
import { hotkeySequence } from "./decorators/hotkey-sequence.js";
|
|
3
|
+
import { HotkeyController } from "./controllers/hotkey.js";
|
|
4
|
+
import { hotkey } from "./decorators/hotkey.js";
|
|
5
|
+
import { HeldKeyCodesController } from "./controllers/held-key-codes.js";
|
|
6
|
+
import { HeldKeysController } from "./controllers/held-keys.js";
|
|
7
|
+
import { HotkeyRecorderController } from "./controllers/hotkey-recorder.js";
|
|
8
|
+
import { HotkeySequenceRecorderController } from "./controllers/hotkey-sequence-recorder.js";
|
|
9
|
+
import { KeyHoldController } from "./controllers/key-hold.js";
|
|
10
|
+
|
|
11
|
+
export * from "@tanstack/hotkeys"
|
|
12
|
+
|
|
13
|
+
export { HeldKeyCodesController, HeldKeysController, HotkeyController, HotkeyRecorderController, HotkeySequenceController, HotkeySequenceRecorderController, KeyHoldController, hotkey, hotkeySequence };
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tanstack/lit-hotkeys",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Lit adapter for TanStack Hotkeys",
|
|
5
|
+
"author": "Tanner Linsley",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/TanStack/hotkeys.git",
|
|
10
|
+
"directory": "packages/lit-hotkeys"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://tanstack.com/hotkeys",
|
|
13
|
+
"funding": {
|
|
14
|
+
"type": "github",
|
|
15
|
+
"url": "https://github.com/sponsors/tannerlinsley"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"lit",
|
|
19
|
+
"tanstack",
|
|
20
|
+
"keys"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"clean": "premove ./build ./dist",
|
|
24
|
+
"lint": "eslint ./src",
|
|
25
|
+
"lint:fix": "eslint ./src --fix",
|
|
26
|
+
"test:eslint": "eslint ./src",
|
|
27
|
+
"test:lib": "vitest --passWithNoTests",
|
|
28
|
+
"test:lib:dev": "pnpm test:lib --watch",
|
|
29
|
+
"test:types": "tsc",
|
|
30
|
+
"build": "tsdown"
|
|
31
|
+
},
|
|
32
|
+
"type": "module",
|
|
33
|
+
"main": "./dist/index.cjs",
|
|
34
|
+
"module": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.cts",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"import": "./dist/index.js",
|
|
39
|
+
"require": "./dist/index.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"dist",
|
|
49
|
+
"src"
|
|
50
|
+
],
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@tanstack/hotkeys": "workspace:*"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"lit": "^3.3.2"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"lit": ">=3.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HotkeyOptions, SequenceOptions } from '@tanstack/hotkeys'
|
|
2
|
+
|
|
3
|
+
export const HOTKEY_DEFAULT_OPTIONS: HotkeyOptions = {
|
|
4
|
+
enabled: true,
|
|
5
|
+
preventDefault: true,
|
|
6
|
+
stopPropagation: true,
|
|
7
|
+
eventType: 'keydown',
|
|
8
|
+
requireReset: false,
|
|
9
|
+
ignoreInputs: undefined, // smart default: false for Mod+S, true for single keys
|
|
10
|
+
platform: undefined, // auto-detected
|
|
11
|
+
conflictBehavior: 'warn',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const HOTKEY_SEQUENCE_DEFAULT_OPTIONS: SequenceOptions = {
|
|
15
|
+
enabled: true,
|
|
16
|
+
timeout: 1000,
|
|
17
|
+
preventDefault: true,
|
|
18
|
+
stopPropagation: true,
|
|
19
|
+
eventType: 'keydown',
|
|
20
|
+
ignoreInputs: undefined, // smart default: false for Mod+S, true for single keys
|
|
21
|
+
platform: undefined, // auto-detected
|
|
22
|
+
conflictBehavior: 'warn',
|
|
23
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getKeyStateTracker } from '@tanstack/hotkeys'
|
|
2
|
+
import type { KeyStateTracker } from '@tanstack/hotkeys'
|
|
3
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A Lit ReactiveController that tracks all currently held key names to their physical `event.code` values.
|
|
7
|
+
*
|
|
8
|
+
* Subscribes to the global KeyStateTracker and triggers host updates
|
|
9
|
+
* whenever keys are pressed or released.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* class KeyDisplay extends LitElement {
|
|
14
|
+
* private heldKeyCodes = new HeldKeyCodesController(this)
|
|
15
|
+
*
|
|
16
|
+
* render() {
|
|
17
|
+
* const heldCodes = Object.entries(this.heldKeyCodes.value).map(([key, code]) => `${key}: ${code}`).join(' + ')
|
|
18
|
+
* return html`
|
|
19
|
+
* <div>
|
|
20
|
+
* Currently pressed: ${heldCodes || 'None'}
|
|
21
|
+
* </div>
|
|
22
|
+
* `
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class HeldKeyCodesController implements ReactiveController {
|
|
28
|
+
/** The unsubscribe function to unsubscribe from the tracker store. */
|
|
29
|
+
private _unsubscribe: (() => void) | undefined
|
|
30
|
+
/** The currently held key names to their physical `event.code` values. */
|
|
31
|
+
private _value: Record<string, string> = {}
|
|
32
|
+
|
|
33
|
+
/** Map of currently held key names to their physical `event.code` values. */
|
|
34
|
+
public get value(): Record<string, string> {
|
|
35
|
+
return this._value
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param _host - The Lit component that owns this controller.
|
|
40
|
+
*/
|
|
41
|
+
constructor(private _host: ReactiveControllerHost) {
|
|
42
|
+
this._host.addController(this)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Subscribes to the tracker store and updates the internal state when changes occur. */
|
|
46
|
+
public hostConnected(): void {
|
|
47
|
+
const tracker: KeyStateTracker = getKeyStateTracker()
|
|
48
|
+
this._value = tracker.store.state.heldCodes
|
|
49
|
+
this._host.requestUpdate()
|
|
50
|
+
|
|
51
|
+
const subscription = tracker.store.subscribe(() => {
|
|
52
|
+
const { heldCodes } = tracker.store.state
|
|
53
|
+
|
|
54
|
+
this._value = heldCodes
|
|
55
|
+
this._host.requestUpdate()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
this._unsubscribe = () => subscription.unsubscribe()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Unsubscribes from the tracker store and stops tracking the held key codes. */
|
|
62
|
+
public hostDisconnected(): void {
|
|
63
|
+
this._unsubscribe?.()
|
|
64
|
+
this._unsubscribe = undefined
|
|
65
|
+
this._value = {}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { getKeyStateTracker } from '@tanstack/hotkeys'
|
|
2
|
+
import type { KeyStateTracker } from '@tanstack/hotkeys'
|
|
3
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A Lit ReactiveController that tracks all currently held keyboard keys.
|
|
7
|
+
*
|
|
8
|
+
* Subscribes to the global KeyStateTracker and triggers host updates
|
|
9
|
+
* whenever keys are pressed or released.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* class KeyDisplay extends LitElement {
|
|
14
|
+
* private heldKeys = new HeldKeysController(this)
|
|
15
|
+
*
|
|
16
|
+
* render() {
|
|
17
|
+
* return html`
|
|
18
|
+
* <div>
|
|
19
|
+
* Currently pressed: ${this.heldKeys.value.join(' + ') || 'None'}
|
|
20
|
+
* </div>
|
|
21
|
+
* `
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export class HeldKeysController implements ReactiveController {
|
|
27
|
+
/** The unsubscribe function to unsubscribe from the tracker store. */
|
|
28
|
+
private _unsubscribe: (() => void) | undefined
|
|
29
|
+
/** The currently held key names. */
|
|
30
|
+
private _value: Array<string> = []
|
|
31
|
+
|
|
32
|
+
/** Array of currently held key names. */
|
|
33
|
+
public get value(): Array<string> {
|
|
34
|
+
return this._value
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param _host - The Lit component that owns this controller.
|
|
39
|
+
*/
|
|
40
|
+
constructor(private _host: ReactiveControllerHost) {
|
|
41
|
+
this._host.addController(this)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Subscribes to the tracker store and updates the internal state when changes occur. */
|
|
45
|
+
public hostConnected(): void {
|
|
46
|
+
const tracker: KeyStateTracker = getKeyStateTracker()
|
|
47
|
+
|
|
48
|
+
const subscription = tracker.store.subscribe(() => {
|
|
49
|
+
const { heldKeys } = tracker.store.state
|
|
50
|
+
|
|
51
|
+
this._value = heldKeys
|
|
52
|
+
this._host.requestUpdate()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
this._unsubscribe = () => subscription.unsubscribe()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Unsubscribes from the tracker store and stops tracking the held keys. */
|
|
59
|
+
public hostDisconnected(): void {
|
|
60
|
+
this._unsubscribe?.()
|
|
61
|
+
this._unsubscribe = undefined
|
|
62
|
+
this._value = []
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { HotkeyRecorder } from '@tanstack/hotkeys'
|
|
2
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit'
|
|
3
|
+
import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A Lit ReactiveController that records keyboard shortcuts.
|
|
7
|
+
*
|
|
8
|
+
* Wraps the framework-agnostic `HotkeyRecorder` class, managing all the
|
|
9
|
+
* complexity of capturing keyboard events, converting them to hotkey strings,
|
|
10
|
+
* and handling edge cases like Escape to cancel or Backspace/Delete to clear.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* class ShortcutSettings extends LitElement {
|
|
15
|
+
* private recorder = new HotkeyRecorderController(this, {
|
|
16
|
+
* onRecord: (hotkey) => {
|
|
17
|
+
* this.shortcut = hotkey
|
|
18
|
+
* this.requestUpdate()
|
|
19
|
+
* },
|
|
20
|
+
* onCancel: () => {
|
|
21
|
+
* console.log('Recording cancelled')
|
|
22
|
+
* },
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* private shortcut: Hotkey | null = null
|
|
26
|
+
*
|
|
27
|
+
* render() {
|
|
28
|
+
* return html`
|
|
29
|
+
* <button @click=${() => this.recorder.startRecording()}>
|
|
30
|
+
* ${this.recorder.isRecording ? 'Recording...' : 'Edit Shortcut'}
|
|
31
|
+
* </button>
|
|
32
|
+
* ${this.recorder.recordedHotkey
|
|
33
|
+
* ? html`<div>Recording: ${this.recorder.recordedHotkey}</div>`
|
|
34
|
+
* : nothing}
|
|
35
|
+
* `
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export class HotkeyRecorderController implements ReactiveController {
|
|
41
|
+
/** The recorder instance. */
|
|
42
|
+
private _recorder: HotkeyRecorder
|
|
43
|
+
/** The unsubscribe function to unsubscribe from the recorder store. */
|
|
44
|
+
private _unsubscribe: (() => void) | undefined
|
|
45
|
+
/** Whether recording is currently active. */
|
|
46
|
+
private _isRecording = false
|
|
47
|
+
/** The currently recorded hotkey (for live preview). */
|
|
48
|
+
private _recordedHotkey: Hotkey | null = null
|
|
49
|
+
|
|
50
|
+
/** Whether recording is currently active. */
|
|
51
|
+
public get isRecording(): boolean {
|
|
52
|
+
return this._isRecording
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The currently recorded hotkey (for live preview). */
|
|
56
|
+
public get recordedHotkey(): Hotkey | null {
|
|
57
|
+
return this._recordedHotkey
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param _host - The Lit component that owns this controller.
|
|
62
|
+
* @param _options - Configuration options for the recorder.
|
|
63
|
+
*/
|
|
64
|
+
constructor(
|
|
65
|
+
private _host: ReactiveControllerHost,
|
|
66
|
+
private _options: HotkeyRecorderOptions,
|
|
67
|
+
) {
|
|
68
|
+
this._recorder = new HotkeyRecorder(_options)
|
|
69
|
+
this._host.addController(this)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Subscribes to the recorder store and updates the internal state when changes occur. */
|
|
73
|
+
public hostConnected(): void {
|
|
74
|
+
const subscription = this._recorder.store.subscribe(() => {
|
|
75
|
+
const { isRecording, recordedHotkey } = this._recorder.store.state
|
|
76
|
+
|
|
77
|
+
const hasChanged: boolean =
|
|
78
|
+
isRecording !== this._isRecording ||
|
|
79
|
+
recordedHotkey !== this._recordedHotkey
|
|
80
|
+
|
|
81
|
+
if (hasChanged) {
|
|
82
|
+
this._isRecording = isRecording
|
|
83
|
+
this._recordedHotkey = recordedHotkey
|
|
84
|
+
this._host.requestUpdate()
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
this._unsubscribe = () => subscription.unsubscribe()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Unsubscribes from the recorder store and destroys the recorder instance to prevent memory leaks. */
|
|
92
|
+
public hostDisconnected(): void {
|
|
93
|
+
this._unsubscribe?.()
|
|
94
|
+
this._unsubscribe = undefined
|
|
95
|
+
this._recorder.stop()
|
|
96
|
+
this._isRecording = false
|
|
97
|
+
this._recordedHotkey = null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Updates the recorder options (e.g. callbacks). */
|
|
101
|
+
public setOptions(options: Partial<HotkeyRecorderOptions>): void {
|
|
102
|
+
this._options = { ...this._options, ...options }
|
|
103
|
+
this._recorder.setOptions(this._options)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Start recording a new hotkey. */
|
|
107
|
+
public startRecording(): void {
|
|
108
|
+
this._recorder.start()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Stop recording (same as cancel but without calling onCancel). */
|
|
112
|
+
public stopRecording(): void {
|
|
113
|
+
this._recorder.stop()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Cancel recording without saving. */
|
|
117
|
+
public cancelRecording(): void {
|
|
118
|
+
this._recorder.cancel()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { HotkeySequenceRecorder } from '@tanstack/hotkeys'
|
|
2
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit'
|
|
3
|
+
import type {
|
|
4
|
+
HotkeySequence,
|
|
5
|
+
HotkeySequenceRecorderOptions,
|
|
6
|
+
} from '@tanstack/hotkeys'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A Lit ReactiveController that records multi-chord sequences (Vim-style shortcuts).
|
|
10
|
+
*
|
|
11
|
+
* Wraps the framework-agnostic `HotkeySequenceRecorder` class, managing store
|
|
12
|
+
* subscriptions and host update lifecycle automatically.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* class ShortcutSettings extends LitElement {
|
|
17
|
+
* private recorder = new HotkeySequenceRecorderController(this, {
|
|
18
|
+
* onRecord: (sequence) => {
|
|
19
|
+
* this.sequence = sequence
|
|
20
|
+
* this.requestUpdate()
|
|
21
|
+
* },
|
|
22
|
+
* onCancel: () => {
|
|
23
|
+
* console.log('Recording cancelled')
|
|
24
|
+
* },
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* private sequence: HotkeySequence | null = null
|
|
28
|
+
*
|
|
29
|
+
* render() {
|
|
30
|
+
* return html`
|
|
31
|
+
* <button @click=${() => this.recorder.startRecording()}>
|
|
32
|
+
* ${this.recorder.isRecording ? 'Recording...' : 'Edit Sequence'}
|
|
33
|
+
* </button>
|
|
34
|
+
* ${this.recorder.steps.length
|
|
35
|
+
* ? html`<div>Steps: ${this.recorder.steps.join(' → ')}</div>`
|
|
36
|
+
* : nothing}
|
|
37
|
+
* ${this.recorder.recordedSequence
|
|
38
|
+
* ? html`<div>Recorded: ${this.recorder.recordedSequence.join(' → ')}</div>`
|
|
39
|
+
* : nothing}
|
|
40
|
+
* `
|
|
41
|
+
* }
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export class HotkeySequenceRecorderController implements ReactiveController {
|
|
46
|
+
/** The recorder instance. */
|
|
47
|
+
private _recorder: HotkeySequenceRecorder
|
|
48
|
+
/** The unsubscribe function. */
|
|
49
|
+
private _unsubscribe: (() => void) | undefined
|
|
50
|
+
/** Whether recording is currently active. */
|
|
51
|
+
private _isRecording = false
|
|
52
|
+
/** Chords captured in the current session. */
|
|
53
|
+
private _steps: HotkeySequence = []
|
|
54
|
+
/** Last committed sequence, or null if none. */
|
|
55
|
+
private _recordedSequence: HotkeySequence | null = null
|
|
56
|
+
|
|
57
|
+
/** Whether recording is currently active. */
|
|
58
|
+
public get isRecording(): boolean {
|
|
59
|
+
return this._isRecording
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Chords captured in the current session. */
|
|
63
|
+
public get steps(): HotkeySequence {
|
|
64
|
+
return this._steps
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Last committed sequence, or null if none. */
|
|
68
|
+
public get recordedSequence(): HotkeySequence | null {
|
|
69
|
+
return this._recordedSequence
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param _host - The Lit component that owns this controller.
|
|
74
|
+
* @param _options - Configuration options for the sequence recorder.
|
|
75
|
+
*/
|
|
76
|
+
constructor(
|
|
77
|
+
private _host: ReactiveControllerHost,
|
|
78
|
+
private _options: HotkeySequenceRecorderOptions,
|
|
79
|
+
) {
|
|
80
|
+
this._recorder = new HotkeySequenceRecorder(_options)
|
|
81
|
+
this._host.addController(this)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Subscribes to the recorder store and updates internal state when changes occur. */
|
|
85
|
+
public hostConnected(): void {
|
|
86
|
+
const subscription = this._recorder.store.subscribe(() => {
|
|
87
|
+
const { isRecording, steps, recordedSequence } =
|
|
88
|
+
this._recorder.store.state
|
|
89
|
+
|
|
90
|
+
const hasChanged: boolean =
|
|
91
|
+
isRecording !== this._isRecording ||
|
|
92
|
+
steps !== this._steps ||
|
|
93
|
+
recordedSequence !== this._recordedSequence
|
|
94
|
+
|
|
95
|
+
if (hasChanged) {
|
|
96
|
+
this._isRecording = isRecording
|
|
97
|
+
this._steps = steps
|
|
98
|
+
this._recordedSequence = recordedSequence
|
|
99
|
+
this._host.requestUpdate()
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
this._unsubscribe = () => subscription.unsubscribe()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Unsubscribes from the recorder store and destroys the recorder instance to prevent memory leaks. */
|
|
107
|
+
public hostDisconnected(): void {
|
|
108
|
+
this._unsubscribe?.()
|
|
109
|
+
this._unsubscribe = undefined
|
|
110
|
+
this._recorder.stop()
|
|
111
|
+
this._isRecording = false
|
|
112
|
+
this._steps = []
|
|
113
|
+
this._recordedSequence = null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Updates the recorder options. */
|
|
117
|
+
public setOptions(options: Partial<HotkeySequenceRecorderOptions>): void {
|
|
118
|
+
this._options = { ...this._options, ...options }
|
|
119
|
+
this._recorder.setOptions(this._options)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Start recording a new sequence. */
|
|
123
|
+
public startRecording(): void {
|
|
124
|
+
this._recorder.start()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Stop recording (same as cancel but without calling onCancel). */
|
|
128
|
+
public stopRecording(): void {
|
|
129
|
+
this._recorder.stop()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Cancel recording without saving. */
|
|
133
|
+
public cancelRecording(): void {
|
|
134
|
+
this._recorder.cancel()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Commit current steps as a sequence (no-op if empty). */
|
|
138
|
+
public commitRecording(): void {
|
|
139
|
+
this._recorder.commit()
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { getSequenceManager } from '@tanstack/hotkeys'
|
|
2
|
+
import { HOTKEY_SEQUENCE_DEFAULT_OPTIONS } from '../constants'
|
|
3
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit'
|
|
4
|
+
import type {
|
|
5
|
+
HotkeyCallback,
|
|
6
|
+
HotkeySequence,
|
|
7
|
+
SequenceManager,
|
|
8
|
+
SequenceOptions,
|
|
9
|
+
SequenceRegistrationHandle,
|
|
10
|
+
} from '@tanstack/hotkeys'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A Lit ReactiveController that registers a keyboard sequence (e.g. Vim-style)
|
|
14
|
+
* when the host element is connected and unregisters it when the host is disconnected.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* class MyElement extends LitElement {
|
|
19
|
+
* private seq = new HotkeySequenceController(this, ['G', 'G'], () => this.goToTop())
|
|
20
|
+
*
|
|
21
|
+
* constructor() {
|
|
22
|
+
* super()
|
|
23
|
+
* this.addController(this.seq)
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export class HotkeySequenceController implements ReactiveController {
|
|
29
|
+
/** The sequence registration handle. */
|
|
30
|
+
private _registration: SequenceRegistrationHandle | undefined
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param _host - The Lit component that owns this controller (use `this` and pass it to `addController()`).
|
|
34
|
+
* @param _sequence - The key sequence to listen for (e.g. `['G', 'G']`).
|
|
35
|
+
* @param _callback - Function to run when the sequence is completed; called with the host as `this`.
|
|
36
|
+
* @param _options - Optional sequence options (target, timeout, enabled, etc.).
|
|
37
|
+
*/
|
|
38
|
+
constructor(
|
|
39
|
+
private _host: ReactiveControllerHost,
|
|
40
|
+
private _sequence: HotkeySequence,
|
|
41
|
+
private _callback: HotkeyCallback,
|
|
42
|
+
private _options: SequenceOptions = HOTKEY_SEQUENCE_DEFAULT_OPTIONS,
|
|
43
|
+
) {}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Registers the sequence with the global sequence manager when the host is connected to the DOM.
|
|
47
|
+
* Skips registration if disabled, sequence is empty, or no target is available.
|
|
48
|
+
*/
|
|
49
|
+
public hostConnected(): void {
|
|
50
|
+
const { target: _target, ...optionsWithoutTarget } = this._options
|
|
51
|
+
|
|
52
|
+
const manager: SequenceManager = getSequenceManager()
|
|
53
|
+
|
|
54
|
+
const hasExplicitTarget = 'target' in this._options
|
|
55
|
+
const resolvedTarget = hasExplicitTarget
|
|
56
|
+
? (this._options.target ?? null)
|
|
57
|
+
: typeof document !== 'undefined'
|
|
58
|
+
? document
|
|
59
|
+
: null
|
|
60
|
+
|
|
61
|
+
if (!resolvedTarget) {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const boundCallback: HotkeyCallback = this._callback.bind(
|
|
66
|
+
this._host as unknown as object,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
this._registration = manager.register(this._sequence, boundCallback, {
|
|
70
|
+
...optionsWithoutTarget,
|
|
71
|
+
target: resolvedTarget,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Unregisters the sequence when the host is disconnected from the DOM. */
|
|
76
|
+
public hostDisconnected(): void {
|
|
77
|
+
if (this._registration?.isActive) {
|
|
78
|
+
this._registration.unregister()
|
|
79
|
+
}
|
|
80
|
+
this._registration = undefined
|
|
81
|
+
}
|
|
82
|
+
}
|