@tanstack/svelte-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 +122 -0
- package/dist/HotkeysCtx.d.ts +22 -0
- package/dist/HotkeysCtx.js +28 -0
- package/dist/HotkeysProvider.svelte +12 -0
- package/dist/HotkeysProvider.svelte.d.ts +4 -0
- package/dist/createHotkey.svelte.d.ts +42 -0
- package/dist/createHotkey.svelte.js +74 -0
- package/dist/createHotkeyRecorder.svelte.d.ts +15 -0
- package/dist/createHotkeyRecorder.svelte.js +100 -0
- package/dist/createHotkeySequence.svelte.d.ts +55 -0
- package/dist/createHotkeySequence.svelte.js +89 -0
- package/dist/getHeldKeyCodesMap.svelte.d.ts +28 -0
- package/dist/getHeldKeyCodesMap.svelte.js +46 -0
- package/dist/getHeldKeys.svelte.d.ts +24 -0
- package/dist/getHeldKeys.svelte.js +42 -0
- package/dist/getIsKeyHeld.svelte.d.ts +44 -0
- package/dist/getIsKeyHeld.svelte.js +69 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/internal.svelte.d.ts +10 -0
- package/dist/internal.svelte.js +12 -0
- package/package.json +64 -0
- package/src/HotkeysCtx.ts +57 -0
- package/src/HotkeysProvider.svelte +12 -0
- package/src/createHotkey.svelte.ts +120 -0
- package/src/createHotkeyRecorder.svelte.ts +118 -0
- package/src/createHotkeySequence.svelte.ts +136 -0
- package/src/getHeldKeyCodesMap.svelte.ts +44 -0
- package/src/getHeldKeys.svelte.ts +40 -0
- package/src/getIsKeyHeld.svelte.ts +68 -0
- package/src/index.ts +17 -0
- package/src/internal.svelte.ts +26 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { getSequenceManager } from '@tanstack/hotkeys'
|
|
2
|
+
import { getDefaultHotkeysOptions } from './HotkeysCtx'
|
|
3
|
+
import { resolveMaybeGetter } from './internal.svelte'
|
|
4
|
+
import type {
|
|
5
|
+
HotkeyCallback,
|
|
6
|
+
HotkeySequence,
|
|
7
|
+
SequenceOptions,
|
|
8
|
+
} from '@tanstack/hotkeys'
|
|
9
|
+
import type { MaybeGetter } from './internal.svelte'
|
|
10
|
+
import type { Attachment } from 'svelte/attachments'
|
|
11
|
+
|
|
12
|
+
export interface CreateHotkeySequenceOptions extends Omit<
|
|
13
|
+
SequenceOptions,
|
|
14
|
+
'target'
|
|
15
|
+
> {
|
|
16
|
+
target?: Document | Window // not html elements, use attachment instead
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function registerHotkeySequence(
|
|
20
|
+
target: HTMLElement | Document | Window,
|
|
21
|
+
sequence: MaybeGetter<HotkeySequence>,
|
|
22
|
+
callback: HotkeyCallback,
|
|
23
|
+
options: MaybeGetter<CreateHotkeySequenceOptions>,
|
|
24
|
+
) {
|
|
25
|
+
const resolvedSequence = resolveMaybeGetter(sequence)
|
|
26
|
+
const resolvedOptions = resolveMaybeGetter(options)
|
|
27
|
+
const mergedOptions = {
|
|
28
|
+
...getDefaultHotkeysOptions().hotkeySequence,
|
|
29
|
+
...resolvedOptions,
|
|
30
|
+
} as CreateHotkeySequenceOptions
|
|
31
|
+
|
|
32
|
+
return getSequenceManager().register(resolvedSequence, callback, {
|
|
33
|
+
...mergedOptions,
|
|
34
|
+
target,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Register a global keyboard shortcut sequence for the current component.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```svelte
|
|
43
|
+
* <script lang="ts">
|
|
44
|
+
* import { createHotkeySequence } from '@tanstack/svelte-hotkeys'
|
|
45
|
+
*
|
|
46
|
+
* // Scroll to top when 'G G' is pressed
|
|
47
|
+
* createHotkeySequence(['G', 'G'], () => {
|
|
48
|
+
* scrollToTop()
|
|
49
|
+
* })
|
|
50
|
+
*
|
|
51
|
+
* // Delete line when 'D D' is pressed
|
|
52
|
+
* createHotkeySequence(['D', 'D'], () => {
|
|
53
|
+
* deleteLine()
|
|
54
|
+
* })
|
|
55
|
+
*
|
|
56
|
+
* // Delete inner word when 'D I W' is pressed
|
|
57
|
+
* createHotkeySequence(['D', 'I', 'W'], () => {
|
|
58
|
+
* deleteInnerWord()
|
|
59
|
+
* }, { timeout: 500 })
|
|
60
|
+
* </script>
|
|
61
|
+
*
|
|
62
|
+
* <div>
|
|
63
|
+
* ....
|
|
64
|
+
* </div>
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function createHotkeySequence(
|
|
68
|
+
sequence: MaybeGetter<HotkeySequence>,
|
|
69
|
+
callback: HotkeyCallback,
|
|
70
|
+
options: MaybeGetter<CreateHotkeySequenceOptions> = {},
|
|
71
|
+
): void {
|
|
72
|
+
$effect(() => {
|
|
73
|
+
if (typeof document === 'undefined') {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const resolvedSequence = resolveMaybeGetter(sequence)
|
|
78
|
+
if (resolvedSequence.length === 0) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const registration = registerHotkeySequence(
|
|
83
|
+
document,
|
|
84
|
+
resolvedSequence,
|
|
85
|
+
callback,
|
|
86
|
+
options,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
registration.unregister()
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create an attachment for element-scoped keyboard sequences.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```svelte
|
|
100
|
+
* <script lang="ts">
|
|
101
|
+
* import { createHotkeySequenceAttachment } from '@tanstack/svelte-hotkeys'
|
|
102
|
+
*
|
|
103
|
+
* const vimKeys = createHotkeySequenceAttachment(['G', 'G'], () => {
|
|
104
|
+
* scrollToTop()
|
|
105
|
+
* })
|
|
106
|
+
* </script>
|
|
107
|
+
*
|
|
108
|
+
* <div tabindex="0" {@attach vimKeys}>
|
|
109
|
+
* Focus here and press g then g
|
|
110
|
+
* </div>
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function createHotkeySequenceAttachment(
|
|
114
|
+
sequence: MaybeGetter<HotkeySequence>,
|
|
115
|
+
callback: HotkeyCallback,
|
|
116
|
+
options: MaybeGetter<CreateHotkeySequenceOptions> = {},
|
|
117
|
+
): Attachment<HTMLElement> {
|
|
118
|
+
return (element) => {
|
|
119
|
+
const resolvedSequence = resolveMaybeGetter(sequence)
|
|
120
|
+
|
|
121
|
+
if (resolvedSequence.length === 0) {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const registration = registerHotkeySequence(
|
|
126
|
+
element,
|
|
127
|
+
resolvedSequence,
|
|
128
|
+
callback,
|
|
129
|
+
options,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return () => {
|
|
133
|
+
registration.unregister()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getKeyStateTracker } from '@tanstack/hotkeys'
|
|
2
|
+
import { createStoreSubscriber } from './internal.svelte'
|
|
3
|
+
|
|
4
|
+
export interface SvelteHeldKeyCodesMap {
|
|
5
|
+
readonly codes: Record<string, string>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class HeldKeyCodesMapState implements SvelteHeldKeyCodesMap {
|
|
9
|
+
#tracker = getKeyStateTracker()
|
|
10
|
+
#subscribe = createStoreSubscriber(this.#tracker.store)
|
|
11
|
+
|
|
12
|
+
get codes(): Record<string, string> {
|
|
13
|
+
this.#subscribe()
|
|
14
|
+
return this.#tracker.store.state.heldCodes
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Svelte function that returns reactive access to the map of currently held key names
|
|
20
|
+
* to their physical `event.code` values.
|
|
21
|
+
*
|
|
22
|
+
* This is useful for debugging which physical key was pressed (e.g. distinguishing
|
|
23
|
+
* left vs right Shift via "ShiftLeft" / "ShiftRight").
|
|
24
|
+
*
|
|
25
|
+
* @returns Object with a reactive `codes` property
|
|
26
|
+
*
|
|
27
|
+
* ```svelte
|
|
28
|
+
* <script>
|
|
29
|
+
* import { getHeldKeyCodesMap } from '@tanstack/svelte-hotkeys'
|
|
30
|
+
* const heldKeyCodesMap = getHeldKeyCodesMap()
|
|
31
|
+
* </script>
|
|
32
|
+
*
|
|
33
|
+
* <div>
|
|
34
|
+
* {#each Object.entries(heldKeyCodesMap.codes) as [key, code]}
|
|
35
|
+
* <kbd>
|
|
36
|
+
* {key} <small>{code}</small>
|
|
37
|
+
* </kbd>
|
|
38
|
+
* {/each}
|
|
39
|
+
* </div>
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function getHeldKeyCodesMap(): SvelteHeldKeyCodesMap {
|
|
43
|
+
return new HeldKeyCodesMapState()
|
|
44
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getKeyStateTracker } from '@tanstack/hotkeys'
|
|
2
|
+
import { createStoreSubscriber } from './internal.svelte'
|
|
3
|
+
|
|
4
|
+
export interface SvelteHeldKeys {
|
|
5
|
+
readonly keys: Array<string>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class HeldKeysState implements SvelteHeldKeys {
|
|
9
|
+
#tracker = getKeyStateTracker()
|
|
10
|
+
#subscribe = createStoreSubscriber(this.#tracker.store)
|
|
11
|
+
|
|
12
|
+
get keys(): Array<string> {
|
|
13
|
+
this.#subscribe()
|
|
14
|
+
return this.#tracker.store.state.heldKeys
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Svelte function that returns reactive access to currently held keyboard keys.
|
|
20
|
+
*
|
|
21
|
+
* This function uses the global KeyStateTracker and updates whenever keys are pressed
|
|
22
|
+
* or released.
|
|
23
|
+
*
|
|
24
|
+
* @returns Object with a reactive `keys` property
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```svelte
|
|
28
|
+
* <script>
|
|
29
|
+
* import { getHeldKeys } from '@tanstack/svelte-hotkeys'
|
|
30
|
+
*
|
|
31
|
+
* const heldKeys = getHeldKeys()
|
|
32
|
+
* </script>
|
|
33
|
+
* <div>
|
|
34
|
+
* Currently pressed: {heldKeys.keys.join(' + ') || 'None'}
|
|
35
|
+
* </div>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function getHeldKeys(): SvelteHeldKeys {
|
|
39
|
+
return new HeldKeysState()
|
|
40
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { getKeyStateTracker } from '@tanstack/hotkeys'
|
|
2
|
+
import { createStoreSubscriber } from './internal.svelte'
|
|
3
|
+
import type { HeldKey } from '@tanstack/hotkeys'
|
|
4
|
+
|
|
5
|
+
export interface SvelteHeldKeyState {
|
|
6
|
+
readonly held: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class HeldKeyState implements SvelteHeldKeyState {
|
|
10
|
+
#tracker = getKeyStateTracker()
|
|
11
|
+
#normalizedKey: string
|
|
12
|
+
#subscribe = createStoreSubscriber(this.#tracker.store)
|
|
13
|
+
|
|
14
|
+
constructor(key: HeldKey) {
|
|
15
|
+
this.#normalizedKey = key.toLowerCase()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get held(): boolean {
|
|
19
|
+
this.#subscribe()
|
|
20
|
+
return this.#tracker.store.state.heldKeys.some(
|
|
21
|
+
(heldKey) => heldKey.toLowerCase() === this.#normalizedKey,
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Svelte function that returns reactive access to whether a specific key is currently being held.
|
|
28
|
+
*
|
|
29
|
+
* This function uses the global KeyStateTracker and updates whenever keys are pressed
|
|
30
|
+
* or released.
|
|
31
|
+
*
|
|
32
|
+
* @param key - The key to check (e.g., 'Shift', 'Control', 'A')
|
|
33
|
+
* @returns Object with a reactive `held` property
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```svelte
|
|
37
|
+
* <script>
|
|
38
|
+
* import { getIsKeyHeld } from '@tanstack/svelte-hotkeys'
|
|
39
|
+
*
|
|
40
|
+
* const isShiftHeld = getIsKeyHeld('Shift')
|
|
41
|
+
* </script>
|
|
42
|
+
*
|
|
43
|
+
* <div>
|
|
44
|
+
* {isShiftHeld.held ? 'Shift is pressed!' : 'Press Shift'}
|
|
45
|
+
* </div>
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```svelte
|
|
50
|
+
* <script>
|
|
51
|
+
* import { getIsKeyHeld } from '@tanstack/svelte-hotkeys'
|
|
52
|
+
*
|
|
53
|
+
* const isCtrlHeld = getIsKeyHeld('Control')
|
|
54
|
+
* const isShiftHeld = getIsKeyHeld('Shift')
|
|
55
|
+
* const isAltHeld = getIsKeyHeld('Alt')
|
|
56
|
+
* </script>
|
|
57
|
+
*
|
|
58
|
+
* <div>
|
|
59
|
+
* <span style:opacity={isCtrlHeld.held ? 1 : 0.3}>Ctrl</span>
|
|
60
|
+
* <span style:opacity={isShiftHeld.held ? 1 : 0.3}>Shift</span>
|
|
61
|
+
* <span style:opacity={isAltHeld.held ? 1 : 0.3}>Alt</span>
|
|
62
|
+
* </div>
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
export function getIsKeyHeld(key: HeldKey): SvelteHeldKeyState {
|
|
67
|
+
return new HeldKeyState(key)
|
|
68
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Re-export everything from the core package
|
|
2
|
+
export * from '@tanstack/hotkeys'
|
|
3
|
+
|
|
4
|
+
export * from './createHotkey.svelte'
|
|
5
|
+
export * from './createHotkeySequence.svelte'
|
|
6
|
+
export * from './createHotkeyRecorder.svelte'
|
|
7
|
+
export * from './getHeldKeys.svelte'
|
|
8
|
+
export * from './getHeldKeyCodesMap.svelte'
|
|
9
|
+
export * from './getIsKeyHeld.svelte'
|
|
10
|
+
export { default as HotkeysProvider } from './HotkeysProvider.svelte'
|
|
11
|
+
export {
|
|
12
|
+
DEFAULT_OPTIONS,
|
|
13
|
+
getDefaultHotkeysOptions,
|
|
14
|
+
getHotkeysContext,
|
|
15
|
+
setHotkeysContext,
|
|
16
|
+
} from './HotkeysCtx'
|
|
17
|
+
export type { HotkeysProviderOptions, HotkeysProviderProps } from './HotkeysCtx'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createSubscriber } from 'svelte/reactivity'
|
|
2
|
+
|
|
3
|
+
export type MaybeGetter<T> = T | (() => T)
|
|
4
|
+
|
|
5
|
+
interface SubscribableStore<TState> {
|
|
6
|
+
state: TState
|
|
7
|
+
subscribe: (
|
|
8
|
+
listener: () => void,
|
|
9
|
+
) => (() => void) | { unsubscribe: () => void }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function resolveMaybeGetter<T>(value: MaybeGetter<T>): T {
|
|
13
|
+
return typeof value === 'function' ? (value as () => T)() : value
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createStoreSubscriber<TState>(
|
|
17
|
+
store: SubscribableStore<TState>,
|
|
18
|
+
): () => void {
|
|
19
|
+
return createSubscriber((update) => {
|
|
20
|
+
const subscription = store.subscribe(update)
|
|
21
|
+
|
|
22
|
+
return typeof subscription === 'function'
|
|
23
|
+
? subscription
|
|
24
|
+
: () => subscription.unsubscribe()
|
|
25
|
+
})
|
|
26
|
+
}
|