@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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectPlatform,
|
|
3
|
+
formatHotkey,
|
|
4
|
+
getHotkeyManager,
|
|
5
|
+
rawHotkeyToParsedHotkey,
|
|
6
|
+
} from '@tanstack/hotkeys'
|
|
7
|
+
import { HOTKEY_DEFAULT_OPTIONS } from '../constants'
|
|
8
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit'
|
|
9
|
+
import type {
|
|
10
|
+
Hotkey,
|
|
11
|
+
HotkeyCallback,
|
|
12
|
+
HotkeyManager,
|
|
13
|
+
HotkeyOptions,
|
|
14
|
+
HotkeyRegistrationHandle,
|
|
15
|
+
RegisterableHotkey,
|
|
16
|
+
} from '@tanstack/hotkeys'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A Lit ReactiveController that registers a keyboard hotkey when the host
|
|
20
|
+
* element is connected and unregisters it when the host is disconnected.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* class MyElement extends LitElement {
|
|
25
|
+
* private hotkey = new HotkeyController(this, 'Mod+S', () => this.save())
|
|
26
|
+
*
|
|
27
|
+
* constructor() {
|
|
28
|
+
* super()
|
|
29
|
+
* this.addController(this.hotkey)
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class HotkeyController implements ReactiveController {
|
|
35
|
+
/** The hotkey registration handle. */
|
|
36
|
+
private _registration: HotkeyRegistrationHandle | undefined
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param _host - The Lit component that owns this controller (use `this` and pass it to `addController()`).
|
|
40
|
+
* @param _hotkey - The key or key combo to listen for (e.g. `'Mod+S'` or a raw hotkey object).
|
|
41
|
+
* @param _callback - Function to run when the hotkey is pressed; called with the host as `this`.
|
|
42
|
+
* @param _options - Optional registration options (target, platform, enabled, etc.).
|
|
43
|
+
*/
|
|
44
|
+
constructor(
|
|
45
|
+
private _host: ReactiveControllerHost,
|
|
46
|
+
private _hotkey: RegisterableHotkey,
|
|
47
|
+
private _callback: HotkeyCallback,
|
|
48
|
+
private _options: HotkeyOptions = HOTKEY_DEFAULT_OPTIONS,
|
|
49
|
+
) {}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Registers the hotkey with the global manager when the host is connected to the DOM.
|
|
53
|
+
* Skips registration if no target is available (e.g. no document or options.target is null).
|
|
54
|
+
*/
|
|
55
|
+
public hostConnected(): void {
|
|
56
|
+
const manager: HotkeyManager = getHotkeyManager()
|
|
57
|
+
|
|
58
|
+
const platform = this._options.platform ?? detectPlatform()
|
|
59
|
+
const hotkeyString: Hotkey =
|
|
60
|
+
typeof this._hotkey === 'string'
|
|
61
|
+
? this._hotkey
|
|
62
|
+
: (formatHotkey(
|
|
63
|
+
rawHotkeyToParsedHotkey(this._hotkey, platform),
|
|
64
|
+
) as Hotkey)
|
|
65
|
+
|
|
66
|
+
const hasExplicitTarget = 'target' in this._options
|
|
67
|
+
const resolvedTarget = hasExplicitTarget
|
|
68
|
+
? (this._options.target ?? null)
|
|
69
|
+
: typeof document !== 'undefined'
|
|
70
|
+
? document
|
|
71
|
+
: null
|
|
72
|
+
|
|
73
|
+
if (!resolvedTarget) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { target: _target, ...optionsWithoutTarget } = this._options
|
|
78
|
+
|
|
79
|
+
const boundCallback: HotkeyCallback = this._callback.bind(
|
|
80
|
+
this._host as unknown as object,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
this._registration = manager.register(hotkeyString, boundCallback, {
|
|
84
|
+
...optionsWithoutTarget,
|
|
85
|
+
target: resolvedTarget,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Unregisters the hotkey when the host is disconnected from the DOM. */
|
|
90
|
+
public hostDisconnected(): void {
|
|
91
|
+
if (this._registration?.isActive) {
|
|
92
|
+
this._registration.unregister()
|
|
93
|
+
}
|
|
94
|
+
this._registration = undefined
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { getKeyStateTracker } from '@tanstack/hotkeys'
|
|
2
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit'
|
|
3
|
+
import type { HeldKey, KeyStateTracker } from '@tanstack/hotkeys'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A Lit ReactiveController that tracks whether a specific key is currently held.
|
|
7
|
+
*
|
|
8
|
+
* Subscribes to the global KeyStateTracker and triggers host updates
|
|
9
|
+
* when the held state of the specified key changes.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* class MyElement extends LitElement {
|
|
14
|
+
* private shiftHold = new KeyHoldController(this, 'Shift')
|
|
15
|
+
*
|
|
16
|
+
* render() {
|
|
17
|
+
* return html`
|
|
18
|
+
* <div style="opacity: ${this.shiftHold.value ? 1 : 0.5}">
|
|
19
|
+
* ${this.shiftHold.value ? 'Shift is pressed!' : 'Press Shift'}
|
|
20
|
+
* </div>
|
|
21
|
+
* `
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* class ModifierIndicators extends LitElement {
|
|
29
|
+
* private ctrl = new KeyHoldController(this, 'Control')
|
|
30
|
+
* private shift = new KeyHoldController(this, 'Shift')
|
|
31
|
+
* private alt = new KeyHoldController(this, 'Alt')
|
|
32
|
+
*
|
|
33
|
+
* render() {
|
|
34
|
+
* return html`
|
|
35
|
+
* <span style="opacity: ${this.ctrl.value ? 1 : 0.3}">Ctrl</span>
|
|
36
|
+
* <span style="opacity: ${this.shift.value ? 1 : 0.3}">Shift</span>
|
|
37
|
+
* <span style="opacity: ${this.alt.value ? 1 : 0.3}">Alt</span>
|
|
38
|
+
* `
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export class KeyHoldController implements ReactiveController {
|
|
44
|
+
/** The unsubscribe function to unsubscribe from the tracker store. */
|
|
45
|
+
private _unsubscribe: (() => void) | undefined
|
|
46
|
+
/** Whether the tracked key is currently held down. */
|
|
47
|
+
private _value = false
|
|
48
|
+
|
|
49
|
+
/** Whether the tracked key is currently held down. */
|
|
50
|
+
public get value(): boolean {
|
|
51
|
+
return this._value
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param host - The Lit component that owns this controller.
|
|
56
|
+
* @param key - The key to track (e.g. 'Shift', 'Control', 'A').
|
|
57
|
+
*/
|
|
58
|
+
constructor(
|
|
59
|
+
private _host: ReactiveControllerHost,
|
|
60
|
+
private _key: HeldKey,
|
|
61
|
+
) {
|
|
62
|
+
this._host.addController(this)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public hostConnected(): void {
|
|
66
|
+
const tracker: KeyStateTracker = getKeyStateTracker()
|
|
67
|
+
const normalizedKey: string = this._key.toLowerCase()
|
|
68
|
+
|
|
69
|
+
const subscription = tracker.store.subscribe(() => {
|
|
70
|
+
const isHeld: boolean = tracker.store.state.heldKeys.some(
|
|
71
|
+
(heldKey: string) => heldKey.toLowerCase() === normalizedKey,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if (isHeld !== this._value) {
|
|
75
|
+
this._value = isHeld
|
|
76
|
+
this._host.requestUpdate()
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
this._unsubscribe = () => subscription.unsubscribe()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public hostDisconnected(): void {
|
|
84
|
+
this._unsubscribe?.()
|
|
85
|
+
this._unsubscribe = undefined
|
|
86
|
+
this._value = false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { HotkeySequenceController } from '../controllers/hotkey-sequence'
|
|
2
|
+
import { HOTKEY_SEQUENCE_DEFAULT_OPTIONS } from '../constants'
|
|
3
|
+
import type { LitElement } from 'lit'
|
|
4
|
+
import type {
|
|
5
|
+
HotkeyCallback,
|
|
6
|
+
HotkeySequence,
|
|
7
|
+
SequenceOptions,
|
|
8
|
+
} from '@tanstack/hotkeys'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Decorator that registers a keyboard sequence (e.g. Vim-style) on the element
|
|
12
|
+
* when it connects and unregisters when it disconnects. Uses
|
|
13
|
+
* {@link HotkeySequenceController} under the hood.
|
|
14
|
+
*
|
|
15
|
+
* @param sequence - The key sequence to listen for (e.g. `['G', 'G']` for "g g").
|
|
16
|
+
* @param options - Optional sequence options (target, timeout, enabled, etc.).
|
|
17
|
+
* @returns A method decorator for use on LitElement methods.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* class MyElement extends LitElement {
|
|
22
|
+
* @hotkeySequence(['G', 'G'])
|
|
23
|
+
* goToTop() { window.scrollTo(0, 0) }
|
|
24
|
+
*
|
|
25
|
+
* @hotkeySequence(['D', 'D'], { timeout: 500 })
|
|
26
|
+
* deleteLine() { this.deleteCurrentLine() }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function hotkeySequence(
|
|
31
|
+
sequence: HotkeySequence,
|
|
32
|
+
options: SequenceOptions = HOTKEY_SEQUENCE_DEFAULT_OPTIONS,
|
|
33
|
+
) {
|
|
34
|
+
return function <T extends HotkeyCallback>(
|
|
35
|
+
proto: LitElement,
|
|
36
|
+
methodName: string,
|
|
37
|
+
descriptor: TypedPropertyDescriptor<T>,
|
|
38
|
+
) {
|
|
39
|
+
const originalConnected = proto.connectedCallback
|
|
40
|
+
const controllerKey = Symbol(`@hotkeySequence:${methodName}`)
|
|
41
|
+
|
|
42
|
+
proto.connectedCallback = function () {
|
|
43
|
+
originalConnected.call(this)
|
|
44
|
+
|
|
45
|
+
const host = this as unknown as {
|
|
46
|
+
[key: symbol]: HotkeySequenceController | undefined
|
|
47
|
+
addController: (c: unknown) => void
|
|
48
|
+
[key: string]: unknown
|
|
49
|
+
}
|
|
50
|
+
if (!host[controllerKey]) {
|
|
51
|
+
const callback: HotkeyCallback = descriptor.value
|
|
52
|
+
? descriptor.value.bind(this)
|
|
53
|
+
: (host[methodName] as HotkeyCallback).bind(this)
|
|
54
|
+
|
|
55
|
+
if (typeof callback !== 'function') {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`@hotkeySequence decorator can only be applied to functions`,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const controller: HotkeySequenceController =
|
|
62
|
+
new HotkeySequenceController(this, sequence, callback, options)
|
|
63
|
+
host[controllerKey] = controller
|
|
64
|
+
this.addController(controller)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { HotkeyController } from '../controllers/hotkey'
|
|
2
|
+
import { HOTKEY_DEFAULT_OPTIONS } from '../constants'
|
|
3
|
+
import type { LitElement } from 'lit'
|
|
4
|
+
import type {
|
|
5
|
+
HotkeyCallback,
|
|
6
|
+
HotkeyOptions,
|
|
7
|
+
RegisterableHotkey,
|
|
8
|
+
} from '@tanstack/hotkeys'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Decorator that registers a keyboard hotkey on the element when it connects
|
|
12
|
+
* and unregisters when it disconnects. Uses {@link HotkeyController} under the hood.
|
|
13
|
+
*
|
|
14
|
+
* @param hotkey - The key or key combo to listen for (e.g. `'Mod+S'` or a raw hotkey object).
|
|
15
|
+
* @param options - Optional registration options (target, platform, enabled, etc.).
|
|
16
|
+
* @returns A method decorator for use on LitElement methods.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* class MyElement extends LitElement {
|
|
21
|
+
* @hotkey('Mod+S')
|
|
22
|
+
* save() { this.doSave() }
|
|
23
|
+
*
|
|
24
|
+
* @hotkey('Escape')
|
|
25
|
+
* close() { this.dismiss() }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function hotkey(
|
|
30
|
+
hotkey: RegisterableHotkey,
|
|
31
|
+
options: HotkeyOptions = HOTKEY_DEFAULT_OPTIONS,
|
|
32
|
+
) {
|
|
33
|
+
return function <T extends HotkeyCallback>(
|
|
34
|
+
proto: LitElement,
|
|
35
|
+
propertyKey: string,
|
|
36
|
+
descriptor?: TypedPropertyDescriptor<T>,
|
|
37
|
+
) {
|
|
38
|
+
const originalConnected = proto.connectedCallback
|
|
39
|
+
const controllerKey = Symbol(`@hotkey:${propertyKey}`)
|
|
40
|
+
|
|
41
|
+
proto.connectedCallback = function () {
|
|
42
|
+
originalConnected.call(this)
|
|
43
|
+
|
|
44
|
+
const host = this as unknown as {
|
|
45
|
+
[key: symbol]: HotkeyController | undefined
|
|
46
|
+
addController: (c: unknown) => void
|
|
47
|
+
[key: string]: unknown
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!host[controllerKey]) {
|
|
51
|
+
const callback: HotkeyCallback = descriptor?.value
|
|
52
|
+
? (descriptor.value.bind(this) as HotkeyCallback)
|
|
53
|
+
: (host[propertyKey] as HotkeyCallback)
|
|
54
|
+
|
|
55
|
+
if (typeof callback !== 'function') {
|
|
56
|
+
throw new Error(`@hotkey decorator can only be applied to functions`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const controller: HotkeyController = new HotkeyController(
|
|
60
|
+
this,
|
|
61
|
+
hotkey,
|
|
62
|
+
callback,
|
|
63
|
+
options,
|
|
64
|
+
)
|
|
65
|
+
host[controllerKey] = controller
|
|
66
|
+
this.addController(controller)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|