@tanstack/svelte-hotkeys 0.12.1 → 0.14.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.
@@ -1,65 +0,0 @@
1
- import type { HotkeyCallback, HotkeySequence, SequenceOptions } from '@tanstack/hotkeys';
2
- import type { MaybeGetter } from './internal.svelte';
3
- import type { Attachment } from 'svelte/attachments';
4
- export interface CreateHotkeySequenceOptions extends Omit<SequenceOptions, 'target'> {
5
- target?: Document | Window;
6
- }
7
- /**
8
- * Register a global keyboard shortcut sequence for the current component.
9
- *
10
- * Each step may include modifiers. You can chain the same modifier across
11
- * steps (e.g. `Shift+R` then `Shift+T`). Modifier-only keydown events (Shift,
12
- * Control, Alt, or Meta pressed alone) are ignored while matching—they do not
13
- * advance the sequence or reset progress.
14
- *
15
- * @example
16
- * ```svelte
17
- * <script lang="ts">
18
- * import { createHotkeySequence } from '@tanstack/svelte-hotkeys'
19
- *
20
- * // Scroll to top when 'G G' is pressed
21
- * createHotkeySequence(['G', 'G'], () => {
22
- * scrollToTop()
23
- * })
24
- *
25
- * // Delete line when 'D D' is pressed
26
- * createHotkeySequence(['D', 'D'], () => {
27
- * deleteLine()
28
- * })
29
- *
30
- * // Delete inner word when 'D I W' is pressed
31
- * createHotkeySequence(['D', 'I', 'W'], () => {
32
- * deleteInnerWord()
33
- * }, { timeout: 500 })
34
- *
35
- * // Same modifier on consecutive steps (bare Shift between chords is ignored)
36
- * createHotkeySequence(['Shift+R', 'Shift+T'], () => {
37
- * nextAction()
38
- * })
39
- * </script>
40
- *
41
- * <div>
42
- * ....
43
- * </div>
44
- * ```
45
- */
46
- export declare function createHotkeySequence(sequence: MaybeGetter<HotkeySequence>, callback: HotkeyCallback, options?: MaybeGetter<CreateHotkeySequenceOptions>): void;
47
- /**
48
- * Create an attachment for element-scoped keyboard sequences.
49
- *
50
- * @example
51
- * ```svelte
52
- * <script lang="ts">
53
- * import { createHotkeySequenceAttachment } from '@tanstack/svelte-hotkeys'
54
- *
55
- * const vimKeys = createHotkeySequenceAttachment(['G', 'G'], () => {
56
- * scrollToTop()
57
- * })
58
- * </script>
59
- *
60
- * <div tabindex="0" {@attach vimKeys}>
61
- * Focus here and press g then g
62
- * </div>
63
- * ```
64
- */
65
- export declare function createHotkeySequenceAttachment(sequence: MaybeGetter<HotkeySequence>, callback: HotkeyCallback, options?: MaybeGetter<CreateHotkeySequenceOptions>): Attachment<HTMLElement>;
@@ -1,149 +0,0 @@
1
- import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys';
2
- import { onDestroy } from 'svelte';
3
- import { getDefaultHotkeysOptions } from './HotkeysCtx';
4
- import { resolveMaybeGetter } from './internal.svelte';
5
- function registerHotkeySequence(target, sequence, callback, mergedOptions) {
6
- return getSequenceManager().register(sequence, callback, {
7
- ...mergedOptions,
8
- target,
9
- });
10
- }
11
- /**
12
- * Register a global keyboard shortcut sequence for the current component.
13
- *
14
- * Each step may include modifiers. You can chain the same modifier across
15
- * steps (e.g. `Shift+R` then `Shift+T`). Modifier-only keydown events (Shift,
16
- * Control, Alt, or Meta pressed alone) are ignored while matching—they do not
17
- * advance the sequence or reset progress.
18
- *
19
- * @example
20
- * ```svelte
21
- * <script lang="ts">
22
- * import { createHotkeySequence } from '@tanstack/svelte-hotkeys'
23
- *
24
- * // Scroll to top when 'G G' is pressed
25
- * createHotkeySequence(['G', 'G'], () => {
26
- * scrollToTop()
27
- * })
28
- *
29
- * // Delete line when 'D D' is pressed
30
- * createHotkeySequence(['D', 'D'], () => {
31
- * deleteLine()
32
- * })
33
- *
34
- * // Delete inner word when 'D I W' is pressed
35
- * createHotkeySequence(['D', 'I', 'W'], () => {
36
- * deleteInnerWord()
37
- * }, { timeout: 500 })
38
- *
39
- * // Same modifier on consecutive steps (bare Shift between chords is ignored)
40
- * createHotkeySequence(['Shift+R', 'Shift+T'], () => {
41
- * nextAction()
42
- * })
43
- * </script>
44
- *
45
- * <div>
46
- * ....
47
- * </div>
48
- * ```
49
- */
50
- export function createHotkeySequence(sequence, callback, options = {}) {
51
- let registration = null;
52
- let lastSequenceKey = null;
53
- $effect(() => {
54
- if (typeof document === 'undefined') {
55
- return;
56
- }
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;
64
- if (resolvedSequence.length === 0) {
65
- if (registration?.isActive) {
66
- registration.unregister();
67
- registration = null;
68
- }
69
- lastSequenceKey = null;
70
- return;
71
- }
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) {
79
- registration.unregister();
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
- }
90
- });
91
- }
92
- /**
93
- * Create an attachment for element-scoped keyboard sequences.
94
- *
95
- * @example
96
- * ```svelte
97
- * <script lang="ts">
98
- * import { createHotkeySequenceAttachment } from '@tanstack/svelte-hotkeys'
99
- *
100
- * const vimKeys = createHotkeySequenceAttachment(['G', 'G'], () => {
101
- * scrollToTop()
102
- * })
103
- * </script>
104
- *
105
- * <div tabindex="0" {@attach vimKeys}>
106
- * Focus here and press g then g
107
- * </div>
108
- * ```
109
- */
110
- export function createHotkeySequenceAttachment(sequence, callback, options = {}) {
111
- return (element) => {
112
- let registration = null;
113
- let lastSequenceKey = null;
114
- $effect(() => {
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
- }
136
- if (registration?.isActive) {
137
- registration.unregister();
138
- registration = null;
139
- }
140
- registration = registerHotkeySequence(element, resolvedSequence, callback, mergedOptions);
141
- lastSequenceKey = sequenceKey;
142
- });
143
- return () => {
144
- if (registration?.isActive) {
145
- registration.unregister();
146
- }
147
- };
148
- };
149
- }
@@ -1,15 +0,0 @@
1
- import type { HotkeySequence, HotkeySequenceRecorderOptions } from '@tanstack/hotkeys';
2
- import type { MaybeGetter } from './internal.svelte';
3
- export interface SvelteHotkeySequenceRecorder {
4
- readonly isRecording: boolean;
5
- readonly steps: HotkeySequence;
6
- readonly recordedSequence: HotkeySequence | null;
7
- startRecording: () => void;
8
- stopRecording: () => void;
9
- cancelRecording: () => void;
10
- commitRecording: () => void;
11
- }
12
- /**
13
- * Svelte helper for recording multi-chord sequences (Vim-style shortcuts).
14
- */
15
- export declare function createHotkeySequenceRecorder(options: MaybeGetter<HotkeySequenceRecorderOptions>): SvelteHotkeySequenceRecorder;
@@ -1,65 +0,0 @@
1
- import { HotkeySequenceRecorder } from '@tanstack/hotkeys';
2
- import { onDestroy } from 'svelte';
3
- import { getDefaultHotkeysOptions } from './HotkeysCtx';
4
- import { createStoreSubscriber, resolveMaybeGetter } from './internal.svelte';
5
- class SvelteHotkeySequenceRecorderState {
6
- #recorder;
7
- #subscribe;
8
- constructor(options) {
9
- this.#recorder = new HotkeySequenceRecorder(options);
10
- this.#subscribe = createStoreSubscriber(this.#recorder.store);
11
- }
12
- get isRecording() {
13
- this.#subscribe();
14
- return this.#recorder.store.state.isRecording;
15
- }
16
- get steps() {
17
- this.#subscribe();
18
- return this.#recorder.store.state.steps;
19
- }
20
- get recordedSequence() {
21
- this.#subscribe();
22
- return this.#recorder.store.state.recordedSequence;
23
- }
24
- setOptions(options) {
25
- this.#recorder.setOptions(options);
26
- }
27
- // Keep the recorder receiver when used directly as an event handler.
28
- startRecording = () => {
29
- this.#recorder.start();
30
- };
31
- // Keep the recorder receiver when used directly as an event handler.
32
- stopRecording = () => {
33
- this.#recorder.stop();
34
- };
35
- // Keep the recorder receiver when used directly as an event handler.
36
- cancelRecording = () => {
37
- this.#recorder.cancel();
38
- };
39
- // Keep the recorder receiver when used directly as an event handler.
40
- commitRecording = () => {
41
- this.#recorder.commit();
42
- };
43
- destroy() {
44
- this.#recorder.destroy();
45
- }
46
- }
47
- /**
48
- * Svelte helper for recording multi-chord sequences (Vim-style shortcuts).
49
- */
50
- export function createHotkeySequenceRecorder(options) {
51
- const recorder = new SvelteHotkeySequenceRecorderState({
52
- ...getDefaultHotkeysOptions().hotkeySequenceRecorder,
53
- ...resolveMaybeGetter(options),
54
- });
55
- $effect(() => {
56
- recorder.setOptions({
57
- ...getDefaultHotkeysOptions().hotkeySequenceRecorder,
58
- ...resolveMaybeGetter(options),
59
- });
60
- });
61
- onDestroy(() => {
62
- recorder.destroy();
63
- });
64
- return recorder;
65
- }
@@ -1,49 +0,0 @@
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>;
@@ -1,189 +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
- 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
- }