@tanstack/svelte-hotkeys 0.6.0 → 0.7.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/createHotkey.svelte.js +46 -12
- package/dist/createHotkeySequence.svelte.js +57 -16
- package/dist/createHotkeySequences.svelte.d.ts +49 -0
- package/dist/createHotkeySequences.svelte.js +189 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
- package/src/createHotkey.svelte.ts +66 -14
- package/src/createHotkeySequence.svelte.ts +70 -20
- package/src/createHotkeySequences.svelte.ts +274 -0
- package/src/index.ts +1 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { detectPlatform, formatHotkey, getHotkeyManager, rawHotkeyToParsedHotkey, } from '@tanstack/hotkeys';
|
|
2
|
+
import { onDestroy } from 'svelte';
|
|
2
3
|
import { getDefaultHotkeysOptions } from './HotkeysCtx';
|
|
3
4
|
import { resolveMaybeGetter } from './internal.svelte';
|
|
4
5
|
function normalizeHotkey(hotkey, options) {
|
|
@@ -7,14 +8,8 @@ function normalizeHotkey(hotkey, options) {
|
|
|
7
8
|
? hotkey
|
|
8
9
|
: formatHotkey(rawHotkeyToParsedHotkey(hotkey, platform));
|
|
9
10
|
}
|
|
10
|
-
function registerHotkey(target, hotkey, callback,
|
|
11
|
-
|
|
12
|
-
const resolvedOptions = resolveMaybeGetter(options);
|
|
13
|
-
const mergedOptions = {
|
|
14
|
-
...getDefaultHotkeysOptions().hotkey,
|
|
15
|
-
...resolvedOptions,
|
|
16
|
-
};
|
|
17
|
-
return getHotkeyManager().register(normalizeHotkey(resolvedHotkey, mergedOptions), callback, {
|
|
11
|
+
function registerHotkey(target, hotkey, callback, mergedOptions) {
|
|
12
|
+
return getHotkeyManager().register(normalizeHotkey(hotkey, mergedOptions), callback, {
|
|
18
13
|
...mergedOptions,
|
|
19
14
|
target,
|
|
20
15
|
});
|
|
@@ -34,14 +29,37 @@ function registerHotkey(target, hotkey, callback, options) {
|
|
|
34
29
|
* ```
|
|
35
30
|
*/
|
|
36
31
|
export function createHotkey(hotkey, callback, options = {}) {
|
|
32
|
+
let registration = null;
|
|
33
|
+
let lastHotkeyStr = null;
|
|
37
34
|
$effect(() => {
|
|
38
35
|
if (typeof document === 'undefined') {
|
|
39
36
|
return;
|
|
40
37
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
const resolvedHotkey = resolveMaybeGetter(hotkey);
|
|
39
|
+
const resolvedOptions = resolveMaybeGetter(options);
|
|
40
|
+
const mergedOptions = {
|
|
41
|
+
...getDefaultHotkeysOptions().hotkey,
|
|
42
|
+
...resolvedOptions,
|
|
44
43
|
};
|
|
44
|
+
const hotkeyStr = normalizeHotkey(resolvedHotkey, mergedOptions);
|
|
45
|
+
const { target: _t, ...optionsWithoutTarget } = mergedOptions;
|
|
46
|
+
if (registration?.isActive && lastHotkeyStr === hotkeyStr) {
|
|
47
|
+
registration.callback = callback;
|
|
48
|
+
registration.setOptions(optionsWithoutTarget);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (registration?.isActive) {
|
|
52
|
+
registration.unregister();
|
|
53
|
+
registration = null;
|
|
54
|
+
}
|
|
55
|
+
registration = registerHotkey(document, resolvedHotkey, callback, mergedOptions);
|
|
56
|
+
lastHotkeyStr = hotkeyStr;
|
|
57
|
+
});
|
|
58
|
+
onDestroy(() => {
|
|
59
|
+
if (registration?.isActive) {
|
|
60
|
+
registration.unregister();
|
|
61
|
+
registration = null;
|
|
62
|
+
}
|
|
45
63
|
});
|
|
46
64
|
}
|
|
47
65
|
/**
|
|
@@ -67,11 +85,27 @@ export function createHotkey(hotkey, callback, options = {}) {
|
|
|
67
85
|
export function createHotkeyAttachment(hotkey, callback, options = {}) {
|
|
68
86
|
return (element) => {
|
|
69
87
|
let registration = null;
|
|
88
|
+
let lastHotkeyStr = null;
|
|
70
89
|
$effect(() => {
|
|
90
|
+
const resolvedHotkey = resolveMaybeGetter(hotkey);
|
|
91
|
+
const resolvedOptions = resolveMaybeGetter(options);
|
|
92
|
+
const mergedOptions = {
|
|
93
|
+
...getDefaultHotkeysOptions().hotkey,
|
|
94
|
+
...resolvedOptions,
|
|
95
|
+
};
|
|
96
|
+
const hotkeyStr = normalizeHotkey(resolvedHotkey, mergedOptions);
|
|
97
|
+
const { target: _t, ...optionsWithoutTarget } = mergedOptions;
|
|
98
|
+
if (registration?.isActive && lastHotkeyStr === hotkeyStr) {
|
|
99
|
+
registration.callback = callback;
|
|
100
|
+
registration.setOptions(optionsWithoutTarget);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
71
103
|
if (registration?.isActive) {
|
|
72
104
|
registration.unregister();
|
|
105
|
+
registration = null;
|
|
73
106
|
}
|
|
74
|
-
registration = registerHotkey(element,
|
|
107
|
+
registration = registerHotkey(element, resolvedHotkey, callback, mergedOptions);
|
|
108
|
+
lastHotkeyStr = hotkeyStr;
|
|
75
109
|
});
|
|
76
110
|
return () => {
|
|
77
111
|
if (registration?.isActive) {
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import { getSequenceManager } from '@tanstack/hotkeys';
|
|
1
|
+
import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys';
|
|
2
|
+
import { onDestroy } from 'svelte';
|
|
2
3
|
import { getDefaultHotkeysOptions } from './HotkeysCtx';
|
|
3
4
|
import { resolveMaybeGetter } from './internal.svelte';
|
|
4
|
-
function registerHotkeySequence(target, sequence, callback,
|
|
5
|
-
|
|
6
|
-
const resolvedOptions = resolveMaybeGetter(options);
|
|
7
|
-
const mergedOptions = {
|
|
8
|
-
...getDefaultHotkeysOptions().hotkeySequence,
|
|
9
|
-
...resolvedOptions,
|
|
10
|
-
};
|
|
11
|
-
return getSequenceManager().register(resolvedSequence, callback, {
|
|
5
|
+
function registerHotkeySequence(target, sequence, callback, mergedOptions) {
|
|
6
|
+
return getSequenceManager().register(sequence, callback, {
|
|
12
7
|
...mergedOptions,
|
|
13
8
|
target,
|
|
14
9
|
});
|
|
@@ -53,18 +48,45 @@ function registerHotkeySequence(target, sequence, callback, options) {
|
|
|
53
48
|
* ```
|
|
54
49
|
*/
|
|
55
50
|
export function createHotkeySequence(sequence, callback, options = {}) {
|
|
51
|
+
let registration = null;
|
|
52
|
+
let lastSequenceKey = null;
|
|
56
53
|
$effect(() => {
|
|
57
54
|
if (typeof document === 'undefined') {
|
|
58
55
|
return;
|
|
59
56
|
}
|
|
60
57
|
const resolvedSequence = resolveMaybeGetter(sequence);
|
|
58
|
+
const resolvedOptions = resolveMaybeGetter(options);
|
|
59
|
+
const mergedOptions = {
|
|
60
|
+
...getDefaultHotkeysOptions().hotkeySequence,
|
|
61
|
+
...resolvedOptions,
|
|
62
|
+
};
|
|
63
|
+
const { target: _t, ...optionsWithoutTarget } = mergedOptions;
|
|
61
64
|
if (resolvedSequence.length === 0) {
|
|
65
|
+
if (registration?.isActive) {
|
|
66
|
+
registration.unregister();
|
|
67
|
+
registration = null;
|
|
68
|
+
}
|
|
69
|
+
lastSequenceKey = null;
|
|
62
70
|
return;
|
|
63
71
|
}
|
|
64
|
-
const
|
|
65
|
-
|
|
72
|
+
const sequenceKey = formatHotkeySequence(resolvedSequence);
|
|
73
|
+
if (registration?.isActive && lastSequenceKey === sequenceKey) {
|
|
74
|
+
registration.callback = callback;
|
|
75
|
+
registration.setOptions(optionsWithoutTarget);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (registration?.isActive) {
|
|
66
79
|
registration.unregister();
|
|
67
|
-
|
|
80
|
+
registration = null;
|
|
81
|
+
}
|
|
82
|
+
registration = registerHotkeySequence(document, resolvedSequence, callback, mergedOptions);
|
|
83
|
+
lastSequenceKey = sequenceKey;
|
|
84
|
+
});
|
|
85
|
+
onDestroy(() => {
|
|
86
|
+
if (registration?.isActive) {
|
|
87
|
+
registration.unregister();
|
|
88
|
+
registration = null;
|
|
89
|
+
}
|
|
68
90
|
});
|
|
69
91
|
}
|
|
70
92
|
/**
|
|
@@ -88,16 +110,35 @@ export function createHotkeySequence(sequence, callback, options = {}) {
|
|
|
88
110
|
export function createHotkeySequenceAttachment(sequence, callback, options = {}) {
|
|
89
111
|
return (element) => {
|
|
90
112
|
let registration = null;
|
|
113
|
+
let lastSequenceKey = null;
|
|
91
114
|
$effect(() => {
|
|
92
115
|
const resolvedSequence = resolveMaybeGetter(sequence);
|
|
116
|
+
const resolvedOptions = resolveMaybeGetter(options);
|
|
117
|
+
const mergedOptions = {
|
|
118
|
+
...getDefaultHotkeysOptions().hotkeySequence,
|
|
119
|
+
...resolvedOptions,
|
|
120
|
+
};
|
|
121
|
+
const { target: _t, ...optionsWithoutTarget } = mergedOptions;
|
|
122
|
+
if (resolvedSequence.length === 0) {
|
|
123
|
+
if (registration?.isActive) {
|
|
124
|
+
registration.unregister();
|
|
125
|
+
registration = null;
|
|
126
|
+
}
|
|
127
|
+
lastSequenceKey = null;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const sequenceKey = formatHotkeySequence(resolvedSequence);
|
|
131
|
+
if (registration?.isActive && lastSequenceKey === sequenceKey) {
|
|
132
|
+
registration.callback = callback;
|
|
133
|
+
registration.setOptions(optionsWithoutTarget);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
93
136
|
if (registration?.isActive) {
|
|
94
137
|
registration.unregister();
|
|
95
138
|
registration = null;
|
|
96
139
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
registration = registerHotkeySequence(element, resolvedSequence, callback, options);
|
|
140
|
+
registration = registerHotkeySequence(element, resolvedSequence, callback, mergedOptions);
|
|
141
|
+
lastSequenceKey = sequenceKey;
|
|
101
142
|
});
|
|
102
143
|
return () => {
|
|
103
144
|
if (registration?.isActive) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { HotkeyCallback, HotkeySequence } from '@tanstack/hotkeys';
|
|
2
|
+
import type { CreateHotkeySequenceOptions } from './createHotkeySequence.svelte';
|
|
3
|
+
import type { MaybeGetter } from './internal.svelte';
|
|
4
|
+
import type { Attachment } from 'svelte/attachments';
|
|
5
|
+
/**
|
|
6
|
+
* A single sequence definition for use with `createHotkeySequences`.
|
|
7
|
+
*/
|
|
8
|
+
export interface CreateHotkeySequenceDefinition {
|
|
9
|
+
/** Array of hotkey strings that form the sequence */
|
|
10
|
+
sequence: MaybeGetter<HotkeySequence>;
|
|
11
|
+
/** The function to call when the sequence is completed */
|
|
12
|
+
callback: HotkeyCallback;
|
|
13
|
+
/** Per-sequence options (merged on top of commonOptions) */
|
|
14
|
+
options?: MaybeGetter<CreateHotkeySequenceOptions>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Register multiple global keyboard shortcut sequences for the current component.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```svelte
|
|
21
|
+
* <script lang="ts">
|
|
22
|
+
* import { createHotkeySequences } from '@tanstack/svelte-hotkeys'
|
|
23
|
+
*
|
|
24
|
+
* createHotkeySequences([
|
|
25
|
+
* { sequence: ['G', 'G'], callback: () => scrollToTop() },
|
|
26
|
+
* { sequence: ['D', 'D'], callback: () => deleteLine() },
|
|
27
|
+
* ])
|
|
28
|
+
* </script>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function createHotkeySequences(definitions: MaybeGetter<Array<CreateHotkeySequenceDefinition>>, commonOptions?: MaybeGetter<CreateHotkeySequenceOptions>): void;
|
|
32
|
+
/**
|
|
33
|
+
* Create an attachment for element-scoped multi-sequence registration.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```svelte
|
|
37
|
+
* <script lang="ts">
|
|
38
|
+
* import { createHotkeySequencesAttachment } from '@tanstack/svelte-hotkeys'
|
|
39
|
+
*
|
|
40
|
+
* const vim = createHotkeySequencesAttachment([
|
|
41
|
+
* { sequence: ['G', 'G'], callback: () => scrollToTop() },
|
|
42
|
+
* { sequence: ['D', 'D'], callback: () => deleteLine() },
|
|
43
|
+
* ])
|
|
44
|
+
* </script>
|
|
45
|
+
*
|
|
46
|
+
* <div tabindex="0" {@attach vim}>Editor</div>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function createHotkeySequencesAttachment(definitions: MaybeGetter<Array<CreateHotkeySequenceDefinition>>, commonOptions?: MaybeGetter<CreateHotkeySequenceOptions>): Attachment<HTMLElement>;
|
|
@@ -0,0 +1,189 @@
|
|
|
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
|
+
function cleanupRegistrations(registrations) {
|
|
7
|
+
for (const { handle } of registrations.values()) {
|
|
8
|
+
if (handle.isActive) {
|
|
9
|
+
handle.unregister();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
registrations.clear();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Register multiple global keyboard shortcut sequences for the current component.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```svelte
|
|
19
|
+
* <script lang="ts">
|
|
20
|
+
* import { createHotkeySequences } from '@tanstack/svelte-hotkeys'
|
|
21
|
+
*
|
|
22
|
+
* createHotkeySequences([
|
|
23
|
+
* { sequence: ['G', 'G'], callback: () => scrollToTop() },
|
|
24
|
+
* { sequence: ['D', 'D'], callback: () => deleteLine() },
|
|
25
|
+
* ])
|
|
26
|
+
* </script>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function createHotkeySequences(definitions, commonOptions = {}) {
|
|
30
|
+
const registrations = new SvelteMap();
|
|
31
|
+
onDestroy(() => {
|
|
32
|
+
cleanupRegistrations(registrations);
|
|
33
|
+
});
|
|
34
|
+
$effect(() => {
|
|
35
|
+
if (typeof document === 'undefined') {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const resolvedDefinitions = resolveMaybeGetter(definitions);
|
|
39
|
+
const resolvedCommonOptions = resolveMaybeGetter(commonOptions);
|
|
40
|
+
const nextKeys = new SvelteSet();
|
|
41
|
+
const prepared = [];
|
|
42
|
+
for (let i = 0; i < resolvedDefinitions.length; i++) {
|
|
43
|
+
const def = resolvedDefinitions[i];
|
|
44
|
+
const resolvedSequence = resolveMaybeGetter(def.sequence);
|
|
45
|
+
if (resolvedSequence.length === 0) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const resolvedDefOptions = def.options
|
|
49
|
+
? resolveMaybeGetter(def.options)
|
|
50
|
+
: {};
|
|
51
|
+
const mergedOptions = {
|
|
52
|
+
...getDefaultHotkeysOptions().hotkeySequence,
|
|
53
|
+
...resolvedCommonOptions,
|
|
54
|
+
...resolvedDefOptions,
|
|
55
|
+
};
|
|
56
|
+
const resolvedTarget = mergedOptions.target ??
|
|
57
|
+
(typeof document !== 'undefined' ? document : null);
|
|
58
|
+
if (!resolvedTarget) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const sequenceString = formatHotkeySequence(resolvedSequence);
|
|
62
|
+
const registrationKey = `${i}:${sequenceString}`;
|
|
63
|
+
nextKeys.add(registrationKey);
|
|
64
|
+
prepared.push({
|
|
65
|
+
registrationKey,
|
|
66
|
+
def,
|
|
67
|
+
mergedOptions,
|
|
68
|
+
sequenceString,
|
|
69
|
+
resolvedSequence,
|
|
70
|
+
resolvedTarget,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
for (const [key, record] of [...registrations.entries()]) {
|
|
74
|
+
if (!nextKeys.has(key)) {
|
|
75
|
+
if (record.handle.isActive) {
|
|
76
|
+
record.handle.unregister();
|
|
77
|
+
}
|
|
78
|
+
registrations.delete(key);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const p of prepared) {
|
|
82
|
+
const existing = registrations.get(p.registrationKey);
|
|
83
|
+
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions;
|
|
84
|
+
if (existing?.handle.isActive && existing.target === p.resolvedTarget) {
|
|
85
|
+
existing.handle.callback = p.def.callback;
|
|
86
|
+
existing.handle.setOptions(optionsWithoutTarget);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (existing?.handle.isActive) {
|
|
90
|
+
existing.handle.unregister();
|
|
91
|
+
}
|
|
92
|
+
if (existing) {
|
|
93
|
+
registrations.delete(p.registrationKey);
|
|
94
|
+
}
|
|
95
|
+
const handle = getSequenceManager().register(p.resolvedSequence, p.def.callback, {
|
|
96
|
+
...optionsWithoutTarget,
|
|
97
|
+
target: p.resolvedTarget,
|
|
98
|
+
});
|
|
99
|
+
registrations.set(p.registrationKey, {
|
|
100
|
+
handle,
|
|
101
|
+
target: p.resolvedTarget,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create an attachment for element-scoped multi-sequence registration.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```svelte
|
|
111
|
+
* <script lang="ts">
|
|
112
|
+
* import { createHotkeySequencesAttachment } from '@tanstack/svelte-hotkeys'
|
|
113
|
+
*
|
|
114
|
+
* const vim = createHotkeySequencesAttachment([
|
|
115
|
+
* { sequence: ['G', 'G'], callback: () => scrollToTop() },
|
|
116
|
+
* { sequence: ['D', 'D'], callback: () => deleteLine() },
|
|
117
|
+
* ])
|
|
118
|
+
* </script>
|
|
119
|
+
*
|
|
120
|
+
* <div tabindex="0" {@attach vim}>Editor</div>
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export function createHotkeySequencesAttachment(definitions, commonOptions = {}) {
|
|
124
|
+
return (element) => {
|
|
125
|
+
const registrations = new SvelteMap();
|
|
126
|
+
$effect(() => {
|
|
127
|
+
const resolvedDefinitions = resolveMaybeGetter(definitions);
|
|
128
|
+
const resolvedCommonOptions = resolveMaybeGetter(commonOptions);
|
|
129
|
+
const nextKeys = new SvelteSet();
|
|
130
|
+
const prepared = [];
|
|
131
|
+
for (let i = 0; i < resolvedDefinitions.length; i++) {
|
|
132
|
+
const def = resolvedDefinitions[i];
|
|
133
|
+
const resolvedSequence = resolveMaybeGetter(def.sequence);
|
|
134
|
+
if (resolvedSequence.length === 0) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const resolvedDefOptions = def.options
|
|
138
|
+
? resolveMaybeGetter(def.options)
|
|
139
|
+
: {};
|
|
140
|
+
const mergedOptions = {
|
|
141
|
+
...getDefaultHotkeysOptions().hotkeySequence,
|
|
142
|
+
...resolvedCommonOptions,
|
|
143
|
+
...resolvedDefOptions,
|
|
144
|
+
};
|
|
145
|
+
const sequenceString = formatHotkeySequence(resolvedSequence);
|
|
146
|
+
const registrationKey = `${i}:${sequenceString}`;
|
|
147
|
+
nextKeys.add(registrationKey);
|
|
148
|
+
prepared.push({
|
|
149
|
+
registrationKey,
|
|
150
|
+
def,
|
|
151
|
+
mergedOptions,
|
|
152
|
+
sequenceString,
|
|
153
|
+
resolvedSequence,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
for (const [key, record] of [...registrations.entries()]) {
|
|
157
|
+
if (!nextKeys.has(key)) {
|
|
158
|
+
if (record.handle.isActive) {
|
|
159
|
+
record.handle.unregister();
|
|
160
|
+
}
|
|
161
|
+
registrations.delete(key);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
for (const p of prepared) {
|
|
165
|
+
const existing = registrations.get(p.registrationKey);
|
|
166
|
+
const { target: _target, ...optionsWithoutTarget } = p.mergedOptions;
|
|
167
|
+
if (existing?.handle.isActive && existing.target === element) {
|
|
168
|
+
existing.handle.callback = p.def.callback;
|
|
169
|
+
existing.handle.setOptions(optionsWithoutTarget);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (existing?.handle.isActive) {
|
|
173
|
+
existing.handle.unregister();
|
|
174
|
+
}
|
|
175
|
+
if (existing) {
|
|
176
|
+
registrations.delete(p.registrationKey);
|
|
177
|
+
}
|
|
178
|
+
const handle = getSequenceManager().register(p.resolvedSequence, p.def.callback, {
|
|
179
|
+
...optionsWithoutTarget,
|
|
180
|
+
target: element,
|
|
181
|
+
});
|
|
182
|
+
registrations.set(p.registrationKey, { handle, target: element });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
return () => {
|
|
186
|
+
cleanupRegistrations(registrations);
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from '@tanstack/hotkeys';
|
|
|
2
2
|
export * from './createHotkey.svelte';
|
|
3
3
|
export * from './createHotkeys.svelte';
|
|
4
4
|
export * from './createHotkeySequence.svelte';
|
|
5
|
+
export * from './createHotkeySequences.svelte';
|
|
5
6
|
export * from './createHotkeyRecorder.svelte';
|
|
6
7
|
export * from './createHotkeySequenceRecorder.svelte';
|
|
7
8
|
export * from './getHeldKeys.svelte';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from '@tanstack/hotkeys';
|
|
|
3
3
|
export * from './createHotkey.svelte';
|
|
4
4
|
export * from './createHotkeys.svelte';
|
|
5
5
|
export * from './createHotkeySequence.svelte';
|
|
6
|
+
export * from './createHotkeySequences.svelte';
|
|
6
7
|
export * from './createHotkeyRecorder.svelte';
|
|
7
8
|
export * from './createHotkeySequenceRecorder.svelte';
|
|
8
9
|
export * from './getHeldKeys.svelte';
|
package/package.json
CHANGED
|
@@ -4,12 +4,14 @@ import {
|
|
|
4
4
|
getHotkeyManager,
|
|
5
5
|
rawHotkeyToParsedHotkey,
|
|
6
6
|
} from '@tanstack/hotkeys'
|
|
7
|
+
import { onDestroy } from 'svelte'
|
|
7
8
|
import { getDefaultHotkeysOptions } from './HotkeysCtx'
|
|
8
9
|
import { resolveMaybeGetter } from './internal.svelte'
|
|
9
10
|
import type {
|
|
10
11
|
Hotkey,
|
|
11
12
|
HotkeyCallback,
|
|
12
13
|
HotkeyOptions,
|
|
14
|
+
HotkeyRegistrationHandle,
|
|
13
15
|
RegisterableHotkey,
|
|
14
16
|
} from '@tanstack/hotkeys'
|
|
15
17
|
import type { MaybeGetter } from './internal.svelte'
|
|
@@ -32,19 +34,12 @@ function normalizeHotkey(
|
|
|
32
34
|
|
|
33
35
|
function registerHotkey(
|
|
34
36
|
target: HTMLElement | Document | Window,
|
|
35
|
-
hotkey:
|
|
37
|
+
hotkey: RegisterableHotkey,
|
|
36
38
|
callback: HotkeyCallback,
|
|
37
|
-
|
|
39
|
+
mergedOptions: CreateHotkeyOptions,
|
|
38
40
|
) {
|
|
39
|
-
const resolvedHotkey = resolveMaybeGetter(hotkey)
|
|
40
|
-
const resolvedOptions = resolveMaybeGetter(options)
|
|
41
|
-
const mergedOptions = {
|
|
42
|
-
...getDefaultHotkeysOptions().hotkey,
|
|
43
|
-
...resolvedOptions,
|
|
44
|
-
} as CreateHotkeyOptions
|
|
45
|
-
|
|
46
41
|
return getHotkeyManager().register(
|
|
47
|
-
normalizeHotkey(
|
|
42
|
+
normalizeHotkey(hotkey, mergedOptions),
|
|
48
43
|
callback,
|
|
49
44
|
{
|
|
50
45
|
...mergedOptions,
|
|
@@ -72,15 +67,48 @@ export function createHotkey(
|
|
|
72
67
|
callback: HotkeyCallback,
|
|
73
68
|
options: MaybeGetter<CreateHotkeyOptions> = {},
|
|
74
69
|
): void {
|
|
70
|
+
let registration: HotkeyRegistrationHandle | null = null
|
|
71
|
+
let lastHotkeyStr: Hotkey | null = null
|
|
72
|
+
|
|
75
73
|
$effect(() => {
|
|
76
74
|
if (typeof document === 'undefined') {
|
|
77
75
|
return
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
const
|
|
78
|
+
const resolvedHotkey = resolveMaybeGetter(hotkey)
|
|
79
|
+
const resolvedOptions = resolveMaybeGetter(options)
|
|
80
|
+
const mergedOptions = {
|
|
81
|
+
...getDefaultHotkeysOptions().hotkey,
|
|
82
|
+
...resolvedOptions,
|
|
83
|
+
} as CreateHotkeyOptions
|
|
81
84
|
|
|
82
|
-
|
|
85
|
+
const hotkeyStr = normalizeHotkey(resolvedHotkey, mergedOptions)
|
|
86
|
+
const { target: _t, ...optionsWithoutTarget } = mergedOptions
|
|
87
|
+
|
|
88
|
+
if (registration?.isActive && lastHotkeyStr === hotkeyStr) {
|
|
89
|
+
registration.callback = callback
|
|
90
|
+
registration.setOptions(optionsWithoutTarget)
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (registration?.isActive) {
|
|
83
95
|
registration.unregister()
|
|
96
|
+
registration = null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
registration = registerHotkey(
|
|
100
|
+
document,
|
|
101
|
+
resolvedHotkey,
|
|
102
|
+
callback,
|
|
103
|
+
mergedOptions,
|
|
104
|
+
)
|
|
105
|
+
lastHotkeyStr = hotkeyStr
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
onDestroy(() => {
|
|
109
|
+
if (registration?.isActive) {
|
|
110
|
+
registration.unregister()
|
|
111
|
+
registration = null
|
|
84
112
|
}
|
|
85
113
|
})
|
|
86
114
|
}
|
|
@@ -111,14 +139,38 @@ export function createHotkeyAttachment(
|
|
|
111
139
|
options: MaybeGetter<CreateHotkeyOptions> = {},
|
|
112
140
|
): Attachment<HTMLElement> {
|
|
113
141
|
return (element) => {
|
|
114
|
-
let registration:
|
|
142
|
+
let registration: HotkeyRegistrationHandle | null = null
|
|
143
|
+
let lastHotkeyStr: Hotkey | null = null
|
|
115
144
|
|
|
116
145
|
$effect(() => {
|
|
146
|
+
const resolvedHotkey = resolveMaybeGetter(hotkey)
|
|
147
|
+
const resolvedOptions = resolveMaybeGetter(options)
|
|
148
|
+
const mergedOptions = {
|
|
149
|
+
...getDefaultHotkeysOptions().hotkey,
|
|
150
|
+
...resolvedOptions,
|
|
151
|
+
} as CreateHotkeyOptions
|
|
152
|
+
|
|
153
|
+
const hotkeyStr = normalizeHotkey(resolvedHotkey, mergedOptions)
|
|
154
|
+
const { target: _t, ...optionsWithoutTarget } = mergedOptions
|
|
155
|
+
|
|
156
|
+
if (registration?.isActive && lastHotkeyStr === hotkeyStr) {
|
|
157
|
+
registration.callback = callback
|
|
158
|
+
registration.setOptions(optionsWithoutTarget)
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
117
162
|
if (registration?.isActive) {
|
|
118
163
|
registration.unregister()
|
|
164
|
+
registration = null
|
|
119
165
|
}
|
|
120
166
|
|
|
121
|
-
registration = registerHotkey(
|
|
167
|
+
registration = registerHotkey(
|
|
168
|
+
element,
|
|
169
|
+
resolvedHotkey,
|
|
170
|
+
callback,
|
|
171
|
+
mergedOptions,
|
|
172
|
+
)
|
|
173
|
+
lastHotkeyStr = hotkeyStr
|
|
122
174
|
})
|
|
123
175
|
|
|
124
176
|
return () => {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { getSequenceManager } from '@tanstack/hotkeys'
|
|
1
|
+
import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys'
|
|
2
|
+
import { onDestroy } from 'svelte'
|
|
2
3
|
import { getDefaultHotkeysOptions } from './HotkeysCtx'
|
|
3
4
|
import { resolveMaybeGetter } from './internal.svelte'
|
|
4
5
|
import type {
|
|
5
6
|
HotkeyCallback,
|
|
6
7
|
HotkeySequence,
|
|
7
8
|
SequenceOptions,
|
|
9
|
+
SequenceRegistrationHandle,
|
|
8
10
|
} from '@tanstack/hotkeys'
|
|
9
11
|
import type { MaybeGetter } from './internal.svelte'
|
|
10
12
|
import type { Attachment } from 'svelte/attachments'
|
|
@@ -18,18 +20,11 @@ export interface CreateHotkeySequenceOptions extends Omit<
|
|
|
18
20
|
|
|
19
21
|
function registerHotkeySequence(
|
|
20
22
|
target: HTMLElement | Document | Window,
|
|
21
|
-
sequence:
|
|
23
|
+
sequence: HotkeySequence,
|
|
22
24
|
callback: HotkeyCallback,
|
|
23
|
-
|
|
25
|
+
mergedOptions: CreateHotkeySequenceOptions,
|
|
24
26
|
) {
|
|
25
|
-
|
|
26
|
-
const resolvedOptions = resolveMaybeGetter(options)
|
|
27
|
-
const mergedOptions = {
|
|
28
|
-
...getDefaultHotkeysOptions().hotkeySequence,
|
|
29
|
-
...resolvedOptions,
|
|
30
|
-
} as CreateHotkeySequenceOptions
|
|
31
|
-
|
|
32
|
-
return getSequenceManager().register(resolvedSequence, callback, {
|
|
27
|
+
return getSequenceManager().register(sequence, callback, {
|
|
33
28
|
...mergedOptions,
|
|
34
29
|
target,
|
|
35
30
|
})
|
|
@@ -79,25 +74,58 @@ export function createHotkeySequence(
|
|
|
79
74
|
callback: HotkeyCallback,
|
|
80
75
|
options: MaybeGetter<CreateHotkeySequenceOptions> = {},
|
|
81
76
|
): void {
|
|
77
|
+
let registration: SequenceRegistrationHandle | null = null
|
|
78
|
+
let lastSequenceKey: string | null = null
|
|
79
|
+
|
|
82
80
|
$effect(() => {
|
|
83
81
|
if (typeof document === 'undefined') {
|
|
84
82
|
return
|
|
85
83
|
}
|
|
86
84
|
|
|
87
85
|
const resolvedSequence = resolveMaybeGetter(sequence)
|
|
86
|
+
const resolvedOptions = resolveMaybeGetter(options)
|
|
87
|
+
const mergedOptions = {
|
|
88
|
+
...getDefaultHotkeysOptions().hotkeySequence,
|
|
89
|
+
...resolvedOptions,
|
|
90
|
+
} as CreateHotkeySequenceOptions
|
|
91
|
+
|
|
92
|
+
const { target: _t, ...optionsWithoutTarget } = mergedOptions
|
|
93
|
+
|
|
88
94
|
if (resolvedSequence.length === 0) {
|
|
95
|
+
if (registration?.isActive) {
|
|
96
|
+
registration.unregister()
|
|
97
|
+
registration = null
|
|
98
|
+
}
|
|
99
|
+
lastSequenceKey = null
|
|
89
100
|
return
|
|
90
101
|
}
|
|
91
102
|
|
|
92
|
-
const
|
|
103
|
+
const sequenceKey = formatHotkeySequence(resolvedSequence)
|
|
104
|
+
|
|
105
|
+
if (registration?.isActive && lastSequenceKey === sequenceKey) {
|
|
106
|
+
registration.callback = callback
|
|
107
|
+
registration.setOptions(optionsWithoutTarget)
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (registration?.isActive) {
|
|
112
|
+
registration.unregister()
|
|
113
|
+
registration = null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
registration = registerHotkeySequence(
|
|
93
117
|
document,
|
|
94
118
|
resolvedSequence,
|
|
95
119
|
callback,
|
|
96
|
-
|
|
120
|
+
mergedOptions,
|
|
97
121
|
)
|
|
122
|
+
lastSequenceKey = sequenceKey
|
|
123
|
+
})
|
|
98
124
|
|
|
99
|
-
|
|
125
|
+
onDestroy(() => {
|
|
126
|
+
if (registration?.isActive) {
|
|
100
127
|
registration.unregister()
|
|
128
|
+
registration = null
|
|
101
129
|
}
|
|
102
130
|
})
|
|
103
131
|
}
|
|
@@ -126,26 +154,48 @@ export function createHotkeySequenceAttachment(
|
|
|
126
154
|
options: MaybeGetter<CreateHotkeySequenceOptions> = {},
|
|
127
155
|
): Attachment<HTMLElement> {
|
|
128
156
|
return (element) => {
|
|
129
|
-
let registration:
|
|
157
|
+
let registration: SequenceRegistrationHandle | null = null
|
|
158
|
+
let lastSequenceKey: string | null = null
|
|
130
159
|
|
|
131
160
|
$effect(() => {
|
|
132
161
|
const resolvedSequence = resolveMaybeGetter(sequence)
|
|
162
|
+
const resolvedOptions = resolveMaybeGetter(options)
|
|
163
|
+
const mergedOptions = {
|
|
164
|
+
...getDefaultHotkeysOptions().hotkeySequence,
|
|
165
|
+
...resolvedOptions,
|
|
166
|
+
} as CreateHotkeySequenceOptions
|
|
133
167
|
|
|
134
|
-
|
|
135
|
-
registration.unregister()
|
|
136
|
-
registration = null
|
|
137
|
-
}
|
|
168
|
+
const { target: _t, ...optionsWithoutTarget } = mergedOptions
|
|
138
169
|
|
|
139
170
|
if (resolvedSequence.length === 0) {
|
|
171
|
+
if (registration?.isActive) {
|
|
172
|
+
registration.unregister()
|
|
173
|
+
registration = null
|
|
174
|
+
}
|
|
175
|
+
lastSequenceKey = null
|
|
140
176
|
return
|
|
141
177
|
}
|
|
142
178
|
|
|
179
|
+
const sequenceKey = formatHotkeySequence(resolvedSequence)
|
|
180
|
+
|
|
181
|
+
if (registration?.isActive && lastSequenceKey === sequenceKey) {
|
|
182
|
+
registration.callback = callback
|
|
183
|
+
registration.setOptions(optionsWithoutTarget)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (registration?.isActive) {
|
|
188
|
+
registration.unregister()
|
|
189
|
+
registration = null
|
|
190
|
+
}
|
|
191
|
+
|
|
143
192
|
registration = registerHotkeySequence(
|
|
144
193
|
element,
|
|
145
194
|
resolvedSequence,
|
|
146
195
|
callback,
|
|
147
|
-
|
|
196
|
+
mergedOptions,
|
|
148
197
|
)
|
|
198
|
+
lastSequenceKey = sequenceKey
|
|
149
199
|
})
|
|
150
200
|
|
|
151
201
|
return () => {
|
|
@@ -0,0 +1,274 @@
|
|
|
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
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from '@tanstack/hotkeys'
|
|
|
4
4
|
export * from './createHotkey.svelte'
|
|
5
5
|
export * from './createHotkeys.svelte'
|
|
6
6
|
export * from './createHotkeySequence.svelte'
|
|
7
|
+
export * from './createHotkeySequences.svelte'
|
|
7
8
|
export * from './createHotkeyRecorder.svelte'
|
|
8
9
|
export * from './createHotkeySequenceRecorder.svelte'
|
|
9
10
|
export * from './getHeldKeys.svelte'
|