@tanstack/svelte-hotkeys 0.10.0 → 0.12.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/README.md +15 -1
- package/dist/createHotkeyRecorder.svelte.js +22 -32
- package/dist/createHotkeySequenceRecorder.svelte.js +28 -37
- package/dist/getHeldKeyCodesMap.svelte.js +4 -13
- package/dist/getHeldKeys.svelte.js +4 -13
- package/dist/getHotkeyHint.svelte.d.ts +13 -0
- package/dist/getHotkeyHint.svelte.js +36 -0
- package/dist/getHotkeyRegistrations.svelte.js +8 -17
- package/dist/getIsKeyHeld.svelte.js +6 -19
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +12 -10
- package/src/HotkeysCtx.ts +0 -61
- package/src/HotkeysProvider.svelte +0 -12
- package/src/createHotkey.svelte.ts +0 -179
- package/src/createHotkeyRecorder.svelte.ts +0 -118
- package/src/createHotkeySequence.svelte.ts +0 -207
- package/src/createHotkeySequenceRecorder.svelte.ts +0 -93
- package/src/createHotkeySequences.svelte.ts +0 -274
- package/src/createHotkeys.svelte.ts +0 -276
- package/src/getHeldKeyCodesMap.svelte.ts +0 -44
- package/src/getHeldKeys.svelte.ts +0 -40
- package/src/getHotkeyRegistrations.svelte.ts +0 -60
- package/src/getIsKeyHeld.svelte.ts +0 -68
- package/src/index.ts +0 -21
- package/src/internal.svelte.ts +0 -26
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'
|
|
2
|
-
import { onDestroy } from 'svelte'
|
|
3
|
-
import { SvelteMap, SvelteSet } from 'svelte/reactivity'
|
|
4
|
-
import { getDefaultHotkeysOptions } from './HotkeysCtx'
|
|
5
|
-
import { resolveMaybeGetter } from './internal.svelte'
|
|
6
|
-
import type {
|
|
7
|
-
HotkeyCallback,
|
|
8
|
-
HotkeySequence,
|
|
9
|
-
SequenceRegistrationHandle,
|
|
10
|
-
} from '@tanstack/hotkeys'
|
|
11
|
-
import type { CreateHotkeySequenceOptions } from './createHotkeySequence.svelte'
|
|
12
|
-
import type { MaybeGetter } from './internal.svelte'
|
|
13
|
-
import type { Attachment } from 'svelte/attachments'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* A single sequence definition for use with `createHotkeySequences`.
|
|
17
|
-
*/
|
|
18
|
-
export interface CreateHotkeySequenceDefinition {
|
|
19
|
-
/** Array of hotkey strings that form the sequence */
|
|
20
|
-
sequence: MaybeGetter<HotkeySequence>
|
|
21
|
-
/** The function to call when the sequence is completed */
|
|
22
|
-
callback: HotkeyCallback
|
|
23
|
-
/** Per-sequence options (merged on top of commonOptions) */
|
|
24
|
-
options?: MaybeGetter<CreateHotkeySequenceOptions>
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
type RegistrationRecord = {
|
|
28
|
-
handle: SequenceRegistrationHandle
|
|
29
|
-
target: Document | HTMLElement | Window
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function cleanupRegistrations(
|
|
33
|
-
registrations:
|
|
34
|
-
| Map<string, RegistrationRecord>
|
|
35
|
-
| SvelteMap<string, RegistrationRecord>,
|
|
36
|
-
) {
|
|
37
|
-
for (const { handle } of registrations.values()) {
|
|
38
|
-
if (handle.isActive) {
|
|
39
|
-
handle.unregister()
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
registrations.clear()
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Register multiple global keyboard shortcut sequences for the current component.
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* ```svelte
|
|
50
|
-
* <script lang="ts">
|
|
51
|
-
* import { createHotkeySequences } from '@tanstack/svelte-hotkeys'
|
|
52
|
-
*
|
|
53
|
-
* createHotkeySequences([
|
|
54
|
-
* { sequence: ['G', 'G'], callback: () => scrollToTop() },
|
|
55
|
-
* { sequence: ['D', 'D'], callback: () => deleteLine() },
|
|
56
|
-
* ])
|
|
57
|
-
* </script>
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
|
-
export function createHotkeySequences(
|
|
61
|
-
definitions: MaybeGetter<Array<CreateHotkeySequenceDefinition>>,
|
|
62
|
-
commonOptions: MaybeGetter<CreateHotkeySequenceOptions> = {},
|
|
63
|
-
): void {
|
|
64
|
-
const registrations = new SvelteMap<string, RegistrationRecord>()
|
|
65
|
-
|
|
66
|
-
onDestroy(() => {
|
|
67
|
-
cleanupRegistrations(registrations)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
$effect(() => {
|
|
71
|
-
if (typeof document === 'undefined') {
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const resolvedDefinitions = resolveMaybeGetter(definitions)
|
|
76
|
-
const resolvedCommonOptions = resolveMaybeGetter(commonOptions)
|
|
77
|
-
const nextKeys = new SvelteSet<string>()
|
|
78
|
-
const prepared: Array<{
|
|
79
|
-
registrationKey: string
|
|
80
|
-
def: CreateHotkeySequenceDefinition
|
|
81
|
-
mergedOptions: CreateHotkeySequenceOptions
|
|
82
|
-
sequenceString: string
|
|
83
|
-
resolvedSequence: HotkeySequence
|
|
84
|
-
resolvedTarget: Document | HTMLElement | Window
|
|
85
|
-
}> = []
|
|
86
|
-
|
|
87
|
-
for (let i = 0; i < resolvedDefinitions.length; i++) {
|
|
88
|
-
const def = resolvedDefinitions[i]!
|
|
89
|
-
const resolvedSequence = resolveMaybeGetter(def.sequence)
|
|
90
|
-
if (resolvedSequence.length === 0) {
|
|
91
|
-
continue
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const resolvedDefOptions = def.options
|
|
95
|
-
? resolveMaybeGetter(def.options)
|
|
96
|
-
: {}
|
|
97
|
-
|
|
98
|
-
const mergedOptions = {
|
|
99
|
-
...getDefaultHotkeysOptions().hotkeySequence,
|
|
100
|
-
...resolvedCommonOptions,
|
|
101
|
-
...resolvedDefOptions,
|
|
102
|
-
} as CreateHotkeySequenceOptions
|
|
103
|
-
|
|
104
|
-
const resolvedTarget =
|
|
105
|
-
mergedOptions.target ??
|
|
106
|
-
(typeof document !== 'undefined' ? document : null)
|
|
107
|
-
|
|
108
|
-
if (!resolvedTarget) {
|
|
109
|
-
continue
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const sequenceString = formatHotkeySequence(resolvedSequence)
|
|
113
|
-
const registrationKey = `${i}:${sequenceString}`
|
|
114
|
-
nextKeys.add(registrationKey)
|
|
115
|
-
prepared.push({
|
|
116
|
-
registrationKey,
|
|
117
|
-
def,
|
|
118
|
-
mergedOptions,
|
|
119
|
-
sequenceString,
|
|
120
|
-
resolvedSequence,
|
|
121
|
-
resolvedTarget,
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
for (const [key, record] of [...registrations.entries()]) {
|
|
126
|
-
if (!nextKeys.has(key)) {
|
|
127
|
-
if (record.handle.isActive) {
|
|
128
|
-
record.handle.unregister()
|
|
129
|
-
}
|
|
130
|
-
registrations.delete(key)
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
for (const p of prepared) {
|
|
135
|
-
const existing = registrations.get(p.registrationKey)
|
|
136
|
-
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions
|
|
137
|
-
|
|
138
|
-
if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
|
|
139
|
-
existing.handle.callback = p.def.callback
|
|
140
|
-
existing.handle.setOptions(optionsWithoutTarget)
|
|
141
|
-
continue
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (existing?.handle.isActive) {
|
|
145
|
-
existing.handle.unregister()
|
|
146
|
-
}
|
|
147
|
-
if (existing) {
|
|
148
|
-
registrations.delete(p.registrationKey)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const handle = getSequenceManager().register(
|
|
152
|
-
p.resolvedSequence,
|
|
153
|
-
p.def.callback,
|
|
154
|
-
{
|
|
155
|
-
...optionsWithoutTarget,
|
|
156
|
-
target: p.resolvedTarget,
|
|
157
|
-
},
|
|
158
|
-
)
|
|
159
|
-
registrations.set(p.registrationKey, {
|
|
160
|
-
handle,
|
|
161
|
-
target: p.resolvedTarget,
|
|
162
|
-
})
|
|
163
|
-
}
|
|
164
|
-
})
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Create an attachment for element-scoped multi-sequence registration.
|
|
169
|
-
*
|
|
170
|
-
* @example
|
|
171
|
-
* ```svelte
|
|
172
|
-
* <script lang="ts">
|
|
173
|
-
* import { createHotkeySequencesAttachment } from '@tanstack/svelte-hotkeys'
|
|
174
|
-
*
|
|
175
|
-
* const vim = createHotkeySequencesAttachment([
|
|
176
|
-
* { sequence: ['G', 'G'], callback: () => scrollToTop() },
|
|
177
|
-
* { sequence: ['D', 'D'], callback: () => deleteLine() },
|
|
178
|
-
* ])
|
|
179
|
-
* </script>
|
|
180
|
-
*
|
|
181
|
-
* <div tabindex="0" {@attach vim}>Editor</div>
|
|
182
|
-
* ```
|
|
183
|
-
*/
|
|
184
|
-
export function createHotkeySequencesAttachment(
|
|
185
|
-
definitions: MaybeGetter<Array<CreateHotkeySequenceDefinition>>,
|
|
186
|
-
commonOptions: MaybeGetter<CreateHotkeySequenceOptions> = {},
|
|
187
|
-
): Attachment<HTMLElement> {
|
|
188
|
-
return (element) => {
|
|
189
|
-
const registrations = new SvelteMap<string, RegistrationRecord>()
|
|
190
|
-
|
|
191
|
-
$effect(() => {
|
|
192
|
-
const resolvedDefinitions = resolveMaybeGetter(definitions)
|
|
193
|
-
const resolvedCommonOptions = resolveMaybeGetter(commonOptions)
|
|
194
|
-
const nextKeys = new SvelteSet<string>()
|
|
195
|
-
const prepared: Array<{
|
|
196
|
-
registrationKey: string
|
|
197
|
-
def: CreateHotkeySequenceDefinition
|
|
198
|
-
mergedOptions: CreateHotkeySequenceOptions
|
|
199
|
-
sequenceString: string
|
|
200
|
-
resolvedSequence: HotkeySequence
|
|
201
|
-
}> = []
|
|
202
|
-
|
|
203
|
-
for (let i = 0; i < resolvedDefinitions.length; i++) {
|
|
204
|
-
const def = resolvedDefinitions[i]!
|
|
205
|
-
const resolvedSequence = resolveMaybeGetter(def.sequence)
|
|
206
|
-
if (resolvedSequence.length === 0) {
|
|
207
|
-
continue
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const resolvedDefOptions = def.options
|
|
211
|
-
? resolveMaybeGetter(def.options)
|
|
212
|
-
: {}
|
|
213
|
-
|
|
214
|
-
const mergedOptions = {
|
|
215
|
-
...getDefaultHotkeysOptions().hotkeySequence,
|
|
216
|
-
...resolvedCommonOptions,
|
|
217
|
-
...resolvedDefOptions,
|
|
218
|
-
} as CreateHotkeySequenceOptions
|
|
219
|
-
|
|
220
|
-
const sequenceString = formatHotkeySequence(resolvedSequence)
|
|
221
|
-
const registrationKey = `${i}:${sequenceString}`
|
|
222
|
-
nextKeys.add(registrationKey)
|
|
223
|
-
prepared.push({
|
|
224
|
-
registrationKey,
|
|
225
|
-
def,
|
|
226
|
-
mergedOptions,
|
|
227
|
-
sequenceString,
|
|
228
|
-
resolvedSequence,
|
|
229
|
-
})
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
for (const [key, record] of [...registrations.entries()]) {
|
|
233
|
-
if (!nextKeys.has(key)) {
|
|
234
|
-
if (record.handle.isActive) {
|
|
235
|
-
record.handle.unregister()
|
|
236
|
-
}
|
|
237
|
-
registrations.delete(key)
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
for (const p of prepared) {
|
|
242
|
-
const existing = registrations.get(p.registrationKey)
|
|
243
|
-
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions
|
|
244
|
-
|
|
245
|
-
if (existing?.handle.isActive && existing.target === element) {
|
|
246
|
-
existing.handle.callback = p.def.callback
|
|
247
|
-
existing.handle.setOptions(optionsWithoutTarget)
|
|
248
|
-
continue
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (existing?.handle.isActive) {
|
|
252
|
-
existing.handle.unregister()
|
|
253
|
-
}
|
|
254
|
-
if (existing) {
|
|
255
|
-
registrations.delete(p.registrationKey)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const handle = getSequenceManager().register(
|
|
259
|
-
p.resolvedSequence,
|
|
260
|
-
p.def.callback,
|
|
261
|
-
{
|
|
262
|
-
...optionsWithoutTarget,
|
|
263
|
-
target: element,
|
|
264
|
-
},
|
|
265
|
-
)
|
|
266
|
-
registrations.set(p.registrationKey, { handle, target: element })
|
|
267
|
-
}
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
return () => {
|
|
271
|
-
cleanupRegistrations(registrations)
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
detectPlatform,
|
|
3
|
-
getHotkeyManager,
|
|
4
|
-
normalizeRegisterableHotkey,
|
|
5
|
-
} from '@tanstack/hotkeys'
|
|
6
|
-
import { onDestroy } from 'svelte'
|
|
7
|
-
import { SvelteMap, SvelteSet } from 'svelte/reactivity'
|
|
8
|
-
import { getDefaultHotkeysOptions } from './HotkeysCtx'
|
|
9
|
-
import { resolveMaybeGetter } from './internal.svelte'
|
|
10
|
-
import type {
|
|
11
|
-
Hotkey,
|
|
12
|
-
HotkeyCallback,
|
|
13
|
-
HotkeyRegistrationHandle,
|
|
14
|
-
RegisterableHotkey,
|
|
15
|
-
} from '@tanstack/hotkeys'
|
|
16
|
-
import type { CreateHotkeyOptions } from './createHotkey.svelte'
|
|
17
|
-
import type { MaybeGetter } from './internal.svelte'
|
|
18
|
-
import type { Attachment } from 'svelte/attachments'
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* A single hotkey definition for use with `createHotkeys`.
|
|
22
|
-
*/
|
|
23
|
-
export interface CreateHotkeyDefinition {
|
|
24
|
-
/** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */
|
|
25
|
-
hotkey: MaybeGetter<RegisterableHotkey>
|
|
26
|
-
/** The function to call when the hotkey is pressed */
|
|
27
|
-
callback: HotkeyCallback
|
|
28
|
-
/** Per-hotkey options (merged on top of commonOptions) */
|
|
29
|
-
options?: MaybeGetter<CreateHotkeyOptions>
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type RegistrationRecord = {
|
|
33
|
-
handle: HotkeyRegistrationHandle
|
|
34
|
-
target: Document | HTMLElement | Window
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function cleanupRegistrations(
|
|
38
|
-
registrations:
|
|
39
|
-
| Map<string, RegistrationRecord>
|
|
40
|
-
| SvelteMap<string, RegistrationRecord>,
|
|
41
|
-
) {
|
|
42
|
-
for (const { handle } of registrations.values()) {
|
|
43
|
-
if (handle.isActive) {
|
|
44
|
-
handle.unregister()
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
registrations.clear()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Register multiple global hotkeys for the current component.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```svelte
|
|
55
|
-
* <script lang="ts">
|
|
56
|
-
* import { createHotkeys } from '@tanstack/svelte-hotkeys'
|
|
57
|
-
*
|
|
58
|
-
* createHotkeys([
|
|
59
|
-
* { hotkey: 'Mod+S', callback: () => save() },
|
|
60
|
-
* { hotkey: 'Mod+Z', callback: () => undo() },
|
|
61
|
-
* { hotkey: 'Escape', callback: () => close() },
|
|
62
|
-
* ])
|
|
63
|
-
* </script>
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
export function createHotkeys(
|
|
67
|
-
hotkeys: MaybeGetter<Array<CreateHotkeyDefinition>>,
|
|
68
|
-
commonOptions: MaybeGetter<CreateHotkeyOptions> = {},
|
|
69
|
-
): void {
|
|
70
|
-
const registrations = new SvelteMap<string, RegistrationRecord>()
|
|
71
|
-
|
|
72
|
-
onDestroy(() => {
|
|
73
|
-
cleanupRegistrations(registrations)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
$effect(() => {
|
|
77
|
-
if (typeof document === 'undefined') {
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const resolvedHotkeys = resolveMaybeGetter(hotkeys)
|
|
82
|
-
const resolvedCommonOptions = resolveMaybeGetter(commonOptions)
|
|
83
|
-
const nextKeys = new SvelteSet<string>()
|
|
84
|
-
const prepared: Array<{
|
|
85
|
-
registrationKey: string
|
|
86
|
-
def: CreateHotkeyDefinition
|
|
87
|
-
mergedOptions: CreateHotkeyOptions
|
|
88
|
-
hotkeyString: Hotkey
|
|
89
|
-
resolvedTarget: Document | HTMLElement | Window
|
|
90
|
-
}> = []
|
|
91
|
-
|
|
92
|
-
for (let i = 0; i < resolvedHotkeys.length; i++) {
|
|
93
|
-
const def = resolvedHotkeys[i]!
|
|
94
|
-
const resolvedHotkey = resolveMaybeGetter(def.hotkey)
|
|
95
|
-
const resolvedDefOptions = def.options
|
|
96
|
-
? resolveMaybeGetter(def.options)
|
|
97
|
-
: {}
|
|
98
|
-
|
|
99
|
-
const mergedOptions = {
|
|
100
|
-
...getDefaultHotkeysOptions().hotkey,
|
|
101
|
-
...resolvedCommonOptions,
|
|
102
|
-
...resolvedDefOptions,
|
|
103
|
-
} as CreateHotkeyOptions
|
|
104
|
-
|
|
105
|
-
const resolvedTarget =
|
|
106
|
-
mergedOptions.target ??
|
|
107
|
-
(typeof document !== 'undefined' ? document : null)
|
|
108
|
-
|
|
109
|
-
if (!resolvedTarget) {
|
|
110
|
-
continue
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const hotkeyString = normalizeRegisterableHotkey(
|
|
114
|
-
resolvedHotkey,
|
|
115
|
-
mergedOptions.platform ?? detectPlatform(),
|
|
116
|
-
)
|
|
117
|
-
const registrationKey = `${i}:${hotkeyString}`
|
|
118
|
-
nextKeys.add(registrationKey)
|
|
119
|
-
prepared.push({
|
|
120
|
-
registrationKey,
|
|
121
|
-
def,
|
|
122
|
-
mergedOptions,
|
|
123
|
-
hotkeyString,
|
|
124
|
-
resolvedTarget,
|
|
125
|
-
})
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
for (const [key, record] of [...registrations.entries()]) {
|
|
129
|
-
if (!nextKeys.has(key)) {
|
|
130
|
-
if (record.handle.isActive) {
|
|
131
|
-
record.handle.unregister()
|
|
132
|
-
}
|
|
133
|
-
registrations.delete(key)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
for (const p of prepared) {
|
|
138
|
-
const existing = registrations.get(p.registrationKey)
|
|
139
|
-
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions
|
|
140
|
-
|
|
141
|
-
if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
|
|
142
|
-
existing.handle.callback = p.def.callback
|
|
143
|
-
existing.handle.setOptions(optionsWithoutTarget)
|
|
144
|
-
continue
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (existing?.handle.isActive) {
|
|
148
|
-
existing.handle.unregister()
|
|
149
|
-
}
|
|
150
|
-
if (existing) {
|
|
151
|
-
registrations.delete(p.registrationKey)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const handle = getHotkeyManager().register(
|
|
155
|
-
p.hotkeyString,
|
|
156
|
-
p.def.callback,
|
|
157
|
-
{
|
|
158
|
-
...optionsWithoutTarget,
|
|
159
|
-
target: p.resolvedTarget,
|
|
160
|
-
},
|
|
161
|
-
)
|
|
162
|
-
registrations.set(p.registrationKey, {
|
|
163
|
-
handle,
|
|
164
|
-
target: p.resolvedTarget,
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Create an attachment for element-scoped multi-hotkey registration.
|
|
172
|
-
*
|
|
173
|
-
* @example
|
|
174
|
-
* ```svelte
|
|
175
|
-
* <script lang="ts">
|
|
176
|
-
* import { createHotkeysAttachment } from '@tanstack/svelte-hotkeys'
|
|
177
|
-
*
|
|
178
|
-
* const editorKeys = createHotkeysAttachment([
|
|
179
|
-
* { hotkey: 'Mod+S', callback: () => save() },
|
|
180
|
-
* { hotkey: 'Mod+Z', callback: () => undo() },
|
|
181
|
-
* ])
|
|
182
|
-
* </script>
|
|
183
|
-
*
|
|
184
|
-
* <div tabindex="0" {@attach editorKeys}>
|
|
185
|
-
* Editor content
|
|
186
|
-
* </div>
|
|
187
|
-
* ```
|
|
188
|
-
*/
|
|
189
|
-
export function createHotkeysAttachment(
|
|
190
|
-
hotkeys: MaybeGetter<Array<CreateHotkeyDefinition>>,
|
|
191
|
-
commonOptions: MaybeGetter<CreateHotkeyOptions> = {},
|
|
192
|
-
): Attachment<HTMLElement> {
|
|
193
|
-
return (element) => {
|
|
194
|
-
const registrations = new SvelteMap<string, RegistrationRecord>()
|
|
195
|
-
|
|
196
|
-
$effect(() => {
|
|
197
|
-
const resolvedHotkeys = resolveMaybeGetter(hotkeys)
|
|
198
|
-
const resolvedCommonOptions = resolveMaybeGetter(commonOptions)
|
|
199
|
-
const nextKeys = new SvelteSet<string>()
|
|
200
|
-
const prepared: Array<{
|
|
201
|
-
registrationKey: string
|
|
202
|
-
def: CreateHotkeyDefinition
|
|
203
|
-
mergedOptions: CreateHotkeyOptions
|
|
204
|
-
hotkeyString: Hotkey
|
|
205
|
-
}> = []
|
|
206
|
-
|
|
207
|
-
for (let i = 0; i < resolvedHotkeys.length; i++) {
|
|
208
|
-
const def = resolvedHotkeys[i]!
|
|
209
|
-
const resolvedHotkey = resolveMaybeGetter(def.hotkey)
|
|
210
|
-
const resolvedDefOptions = def.options
|
|
211
|
-
? resolveMaybeGetter(def.options)
|
|
212
|
-
: {}
|
|
213
|
-
|
|
214
|
-
const mergedOptions = {
|
|
215
|
-
...getDefaultHotkeysOptions().hotkey,
|
|
216
|
-
...resolvedCommonOptions,
|
|
217
|
-
...resolvedDefOptions,
|
|
218
|
-
} as CreateHotkeyOptions
|
|
219
|
-
|
|
220
|
-
const hotkeyString = normalizeRegisterableHotkey(
|
|
221
|
-
resolvedHotkey,
|
|
222
|
-
mergedOptions.platform ?? detectPlatform(),
|
|
223
|
-
)
|
|
224
|
-
const registrationKey = `${i}:${hotkeyString}`
|
|
225
|
-
nextKeys.add(registrationKey)
|
|
226
|
-
prepared.push({
|
|
227
|
-
registrationKey,
|
|
228
|
-
def,
|
|
229
|
-
mergedOptions,
|
|
230
|
-
hotkeyString,
|
|
231
|
-
})
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
for (const [key, record] of [...registrations.entries()]) {
|
|
235
|
-
if (!nextKeys.has(key)) {
|
|
236
|
-
if (record.handle.isActive) {
|
|
237
|
-
record.handle.unregister()
|
|
238
|
-
}
|
|
239
|
-
registrations.delete(key)
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
for (const p of prepared) {
|
|
244
|
-
const existing = registrations.get(p.registrationKey)
|
|
245
|
-
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions
|
|
246
|
-
|
|
247
|
-
if (existing?.handle.isActive) {
|
|
248
|
-
existing.handle.callback = p.def.callback
|
|
249
|
-
existing.handle.setOptions(optionsWithoutTarget)
|
|
250
|
-
continue
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (existing?.handle.isActive) {
|
|
254
|
-
existing.handle.unregister()
|
|
255
|
-
}
|
|
256
|
-
if (existing) {
|
|
257
|
-
registrations.delete(p.registrationKey)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const handle = getHotkeyManager().register(
|
|
261
|
-
p.hotkeyString,
|
|
262
|
-
p.def.callback,
|
|
263
|
-
{
|
|
264
|
-
...optionsWithoutTarget,
|
|
265
|
-
target: element,
|
|
266
|
-
},
|
|
267
|
-
)
|
|
268
|
-
registrations.set(p.registrationKey, { handle, target: element })
|
|
269
|
-
}
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
return () => {
|
|
273
|
-
cleanupRegistrations(registrations)
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getHotkeyManager,
|
|
3
|
-
getSequenceManager,
|
|
4
|
-
toHotkeyRegistrationView,
|
|
5
|
-
} from '@tanstack/hotkeys'
|
|
6
|
-
import { createStoreSubscriber } from './internal.svelte'
|
|
7
|
-
import type {
|
|
8
|
-
HotkeyRegistrationView,
|
|
9
|
-
SequenceRegistrationView,
|
|
10
|
-
} from '@tanstack/hotkeys'
|
|
11
|
-
|
|
12
|
-
export interface SvelteHotkeyRegistrations {
|
|
13
|
-
readonly hotkeys: Array<HotkeyRegistrationView>
|
|
14
|
-
readonly sequences: Array<SequenceRegistrationView>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
class HotkeyRegistrationsState implements SvelteHotkeyRegistrations {
|
|
18
|
-
#hotkeyManager = getHotkeyManager()
|
|
19
|
-
#sequenceManager = getSequenceManager()
|
|
20
|
-
#subscribeHotkeys = createStoreSubscriber(this.#hotkeyManager.registrations)
|
|
21
|
-
#subscribeSequences = createStoreSubscriber(
|
|
22
|
-
this.#sequenceManager.registrations,
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
get hotkeys(): Array<HotkeyRegistrationView> {
|
|
26
|
-
this.#subscribeHotkeys()
|
|
27
|
-
return Array.from(this.#hotkeyManager.registrations.state.values()).map(
|
|
28
|
-
toHotkeyRegistrationView,
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
get sequences(): Array<SequenceRegistrationView> {
|
|
33
|
-
this.#subscribeSequences()
|
|
34
|
-
return Array.from(this.#sequenceManager.registrations.state.values())
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Svelte function that returns reactive access to all hotkey and sequence
|
|
40
|
-
* registrations from the singleton managers.
|
|
41
|
-
*
|
|
42
|
-
* This is a standalone function that does NOT require the HotkeysProvider.
|
|
43
|
-
*
|
|
44
|
-
* @returns Object with reactive `hotkeys` and `sequences` properties
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```svelte
|
|
48
|
-
* <script>
|
|
49
|
-
* import { getHotkeyRegistrations } from '@tanstack/svelte-hotkeys'
|
|
50
|
-
*
|
|
51
|
-
* const registrations = getHotkeyRegistrations()
|
|
52
|
-
* </script>
|
|
53
|
-
* {#each registrations.hotkeys as reg}
|
|
54
|
-
* <div>{reg.options.meta?.name} — {reg.triggerCount} triggers</div>
|
|
55
|
-
* {/each}
|
|
56
|
-
* ```
|
|
57
|
-
*/
|
|
58
|
-
export function getHotkeyRegistrations(): SvelteHotkeyRegistrations {
|
|
59
|
-
return new HotkeyRegistrationsState()
|
|
60
|
-
}
|