@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
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="./media/header_hotkeys.png" alt="TanStack Hotkeys" />
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<br />
|
|
6
|
+
|
|
7
|
+
<div align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@tanstack/hotkeys" target="\_parent">
|
|
9
|
+
<img alt="" src="https://img.shields.io/npm/dm/@tanstack/hotkeys.svg" alt="npm downloads" />
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://github.com/TanStack/hotkeys" target="\_parent">
|
|
12
|
+
<img alt="" src="https://img.shields.io/github/stars/TanStack/hotkeys.svg?style=social&label=Star" alt="GitHub stars" />
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://bundlephobia.com/result?p=@tanstack/react-hotkeys@latest" target="\_parent">
|
|
15
|
+
<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/react-hotkeys@latest" alt="Bundle size" />
|
|
16
|
+
</a>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div align="center">
|
|
20
|
+
<a href="#badge">
|
|
21
|
+
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
|
|
22
|
+
</a>
|
|
23
|
+
<a href="#badge">
|
|
24
|
+
<img src="https://img.shields.io/github/v/release/tanstack/hotkeys" alt="Release"/>
|
|
25
|
+
</a>
|
|
26
|
+
<a href="https://twitter.com/tan_stack">
|
|
27
|
+
<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
|
|
28
|
+
</a>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div align="center">
|
|
32
|
+
|
|
33
|
+
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
|
|
34
|
+
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
# TanStack Hotkeys
|
|
38
|
+
|
|
39
|
+
> [!NOTE]
|
|
40
|
+
> TanStack Hotkeys is pre-alpha (prototyping phase). We are actively developing the library and are open to feedback and contributions.
|
|
41
|
+
|
|
42
|
+
Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objects, a cross-platform `Mod` key, a singleton Hotkey Manager, and utilities for cheatsheet UIs—built to stay SSR-friendly.
|
|
43
|
+
|
|
44
|
+
- Type-safe bindings — template strings (`Mod+Shift+S`, `Escape`) or parsed objects for full control
|
|
45
|
+
- Flexible options — `keydown`/`keyup`, `preventDefault`, `stopPropagation`, conditional enabled, `requireReset`
|
|
46
|
+
- Cross-platform Mod — maps to Cmd on macOS and Ctrl on Windows/Linux
|
|
47
|
+
- Batteries included — validation + matching, sequences (Vim-style), key-state tracking, recorder UI helpers, framework adapters, and devtools
|
|
48
|
+
|
|
49
|
+
### <a href="https://tanstack.com/hotkeys">Read the docs →</a>
|
|
50
|
+
|
|
51
|
+
<br />
|
|
52
|
+
|
|
53
|
+
> [!NOTE]
|
|
54
|
+
> You may know **TanStack Hotkeys** by our adapter names, too!
|
|
55
|
+
>
|
|
56
|
+
> - [**React Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/react/react-hotkeys)
|
|
57
|
+
> - [**Preact Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/preact/preact-hotkeys)
|
|
58
|
+
> - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference)
|
|
59
|
+
> - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference)
|
|
60
|
+
> - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference)
|
|
61
|
+
> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference)
|
|
62
|
+
|
|
63
|
+
## Get Involved
|
|
64
|
+
|
|
65
|
+
- We welcome issues and pull requests!
|
|
66
|
+
- Participate in [GitHub discussions](https://github.com/TanStack/hotkeys/discussions)
|
|
67
|
+
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
|
|
68
|
+
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions
|
|
69
|
+
|
|
70
|
+
## Partners
|
|
71
|
+
|
|
72
|
+
<div align="center">
|
|
73
|
+
|
|
74
|
+
<table align="center">
|
|
75
|
+
<tr>
|
|
76
|
+
<td>
|
|
77
|
+
<a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
|
|
78
|
+
<picture>
|
|
79
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-D643Zkrv.svg" height="40" />
|
|
80
|
+
<source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-CIzGLYU_.svg" height="40" />
|
|
81
|
+
<img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
|
|
82
|
+
</picture>
|
|
83
|
+
</a>
|
|
84
|
+
</td>
|
|
85
|
+
<td>
|
|
86
|
+
<a href="https://www.cloudflare.com?utm_source=tanstack">
|
|
87
|
+
<picture>
|
|
88
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-Co-Tyjbl.svg" height="60" />
|
|
89
|
+
<source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-6Ojsn8yh.svg" height="60" />
|
|
90
|
+
<img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
|
|
91
|
+
</picture>
|
|
92
|
+
</a>
|
|
93
|
+
</td>
|
|
94
|
+
</tr>
|
|
95
|
+
</table>
|
|
96
|
+
|
|
97
|
+
<div align="center">
|
|
98
|
+
<img src="media/partner_logo.svg" alt="Keys & you?" height="65">
|
|
99
|
+
<p>
|
|
100
|
+
We're looking for TanStack Hotkeys Partners to join our mission! Partner with us to push the boundaries of TanStack Hotkeys and build amazing things together.
|
|
101
|
+
</p>
|
|
102
|
+
<a href="mailto:partners@tanstack.com?subject=TanStack Hotkeys Partnership"><b>LET'S CHAT</b></a>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
## Explore the TanStack Ecosystem
|
|
108
|
+
|
|
109
|
+
- <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
|
|
110
|
+
- <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
|
|
111
|
+
- <a href="https://github.com/tanstack/devtools"><b>TanStack DevTools</b></a> – Unified devtools panel
|
|
112
|
+
- <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
|
|
113
|
+
- <a href="https://github.com/tanstack/hotkeys"><b>TanStack Hotkeys</b></a> – Type‑safe keyboard shortcuts
|
|
114
|
+
- <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
|
|
115
|
+
- <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
|
|
116
|
+
- <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
|
|
117
|
+
- <a href="https://github.com/tanstack/start"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
|
|
118
|
+
- <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
|
|
119
|
+
- <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
|
|
120
|
+
- <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering
|
|
121
|
+
|
|
122
|
+
… and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { HotkeyRecorderOptions } from '@tanstack/hotkeys';
|
|
2
|
+
import type { CreateHotkeyOptions } from './createHotkey.svelte';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { CreateHotkeySequenceOptions } from './createHotkeySequence.svelte';
|
|
5
|
+
export interface HotkeysProviderOptions {
|
|
6
|
+
hotkey?: Partial<CreateHotkeyOptions>;
|
|
7
|
+
hotkeyRecorder?: Partial<HotkeyRecorderOptions>;
|
|
8
|
+
hotkeySequence?: Partial<CreateHotkeySequenceOptions>;
|
|
9
|
+
}
|
|
10
|
+
export interface HotkeysProviderProps {
|
|
11
|
+
children: Snippet;
|
|
12
|
+
defaultOptions?: HotkeysProviderOptions;
|
|
13
|
+
}
|
|
14
|
+
export declare const DEFAULT_OPTIONS: HotkeysProviderOptions;
|
|
15
|
+
interface HotkeysContextValue {
|
|
16
|
+
defaultOptions: HotkeysProviderOptions;
|
|
17
|
+
}
|
|
18
|
+
export declare function setHotkeysContext(defaultOptions?: HotkeysProviderOptions): HotkeysContextValue;
|
|
19
|
+
export declare function setHotkeysContextSource(defaultOptions: () => HotkeysProviderOptions): HotkeysContextValue;
|
|
20
|
+
export declare function getHotkeysContext(): HotkeysContextValue | null;
|
|
21
|
+
export declare function getDefaultHotkeysOptions(): HotkeysProviderOptions;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createContext } from 'svelte';
|
|
2
|
+
export const DEFAULT_OPTIONS = {};
|
|
3
|
+
const [useHotkeysContext, setHotkeysContextValue] = createContext();
|
|
4
|
+
export function setHotkeysContext(defaultOptions = DEFAULT_OPTIONS) {
|
|
5
|
+
return setHotkeysContextValue({
|
|
6
|
+
get defaultOptions() {
|
|
7
|
+
return defaultOptions;
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export function setHotkeysContextSource(defaultOptions) {
|
|
12
|
+
return setHotkeysContextValue({
|
|
13
|
+
get defaultOptions() {
|
|
14
|
+
return defaultOptions();
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function getHotkeysContext() {
|
|
19
|
+
try {
|
|
20
|
+
return useHotkeysContext();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function getDefaultHotkeysOptions() {
|
|
27
|
+
return getHotkeysContext()?.defaultOptions ?? DEFAULT_OPTIONS;
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { DEFAULT_OPTIONS, setHotkeysContextSource } from './HotkeysCtx'
|
|
3
|
+
|
|
4
|
+
import type { HotkeysProviderProps } from './HotkeysCtx'
|
|
5
|
+
|
|
6
|
+
let { children, defaultOptions = DEFAULT_OPTIONS }: HotkeysProviderProps =
|
|
7
|
+
$props()
|
|
8
|
+
|
|
9
|
+
setHotkeysContextSource(() => defaultOptions)
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
{@render children()}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { HotkeyCallback, HotkeyOptions, RegisterableHotkey } from '@tanstack/hotkeys';
|
|
2
|
+
import type { MaybeGetter } from './internal.svelte';
|
|
3
|
+
import type { Attachment } from 'svelte/attachments';
|
|
4
|
+
export interface CreateHotkeyOptions extends Omit<HotkeyOptions, 'target'> {
|
|
5
|
+
target?: Document | Window;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Register a global hotkey for the current component.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```svelte
|
|
12
|
+
* <script lang="ts">
|
|
13
|
+
* import { createHotkey } from '@tanstack/svelte-hotkeys'
|
|
14
|
+
*
|
|
15
|
+
* createHotkey('Mod+S', () => {
|
|
16
|
+
* console.log('Mod+S pressed')
|
|
17
|
+
* })
|
|
18
|
+
* </script>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function createHotkey(hotkey: MaybeGetter<RegisterableHotkey>, callback: HotkeyCallback, options?: MaybeGetter<CreateHotkeyOptions>): void;
|
|
22
|
+
/**
|
|
23
|
+
* Create an attachment for element-scoped hotkeys.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```svelte
|
|
27
|
+
* <script lang="ts">
|
|
28
|
+
* import { createHotkeyAttachment } from '@tanstack/svelte-hotkeys'
|
|
29
|
+
*
|
|
30
|
+
* let count = $state(0)
|
|
31
|
+
* const saveHotkey = createHotkeyAttachment('Mod+S', () => {
|
|
32
|
+
* count++
|
|
33
|
+
* })
|
|
34
|
+
*
|
|
35
|
+
* </script>
|
|
36
|
+
*
|
|
37
|
+
* <div tabindex="0" {@attach saveHotkey}>
|
|
38
|
+
* Count: {count}
|
|
39
|
+
* </div>
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function createHotkeyAttachment(hotkey: MaybeGetter<RegisterableHotkey>, callback: HotkeyCallback, options?: MaybeGetter<CreateHotkeyOptions>): Attachment<HTMLElement>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { detectPlatform, formatHotkey, getHotkeyManager, rawHotkeyToParsedHotkey, } from '@tanstack/hotkeys';
|
|
2
|
+
import { getDefaultHotkeysOptions } from './HotkeysCtx';
|
|
3
|
+
import { resolveMaybeGetter } from './internal.svelte';
|
|
4
|
+
function normalizeHotkey(hotkey, options) {
|
|
5
|
+
const platform = options.platform ?? detectPlatform();
|
|
6
|
+
return typeof hotkey === 'string'
|
|
7
|
+
? hotkey
|
|
8
|
+
: formatHotkey(rawHotkeyToParsedHotkey(hotkey, platform));
|
|
9
|
+
}
|
|
10
|
+
function registerHotkey(target, hotkey, callback, options) {
|
|
11
|
+
const resolvedHotkey = resolveMaybeGetter(hotkey);
|
|
12
|
+
const resolvedOptions = resolveMaybeGetter(options);
|
|
13
|
+
const mergedOptions = {
|
|
14
|
+
...getDefaultHotkeysOptions().hotkey,
|
|
15
|
+
...resolvedOptions,
|
|
16
|
+
};
|
|
17
|
+
return getHotkeyManager().register(normalizeHotkey(resolvedHotkey, mergedOptions), callback, {
|
|
18
|
+
...mergedOptions,
|
|
19
|
+
target,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Register a global hotkey for the current component.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```svelte
|
|
27
|
+
* <script lang="ts">
|
|
28
|
+
* import { createHotkey } from '@tanstack/svelte-hotkeys'
|
|
29
|
+
*
|
|
30
|
+
* createHotkey('Mod+S', () => {
|
|
31
|
+
* console.log('Mod+S pressed')
|
|
32
|
+
* })
|
|
33
|
+
* </script>
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function createHotkey(hotkey, callback, options = {}) {
|
|
37
|
+
$effect(() => {
|
|
38
|
+
if (typeof document === 'undefined') {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const registration = registerHotkey(document, hotkey, callback, options);
|
|
42
|
+
return () => {
|
|
43
|
+
registration.unregister();
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create an attachment for element-scoped hotkeys.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```svelte
|
|
52
|
+
* <script lang="ts">
|
|
53
|
+
* import { createHotkeyAttachment } from '@tanstack/svelte-hotkeys'
|
|
54
|
+
*
|
|
55
|
+
* let count = $state(0)
|
|
56
|
+
* const saveHotkey = createHotkeyAttachment('Mod+S', () => {
|
|
57
|
+
* count++
|
|
58
|
+
* })
|
|
59
|
+
*
|
|
60
|
+
* </script>
|
|
61
|
+
*
|
|
62
|
+
* <div tabindex="0" {@attach saveHotkey}>
|
|
63
|
+
* Count: {count}
|
|
64
|
+
* </div>
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function createHotkeyAttachment(hotkey, callback, options = {}) {
|
|
68
|
+
return (element) => {
|
|
69
|
+
const registration = registerHotkey(element, hotkey, callback, options);
|
|
70
|
+
return () => {
|
|
71
|
+
registration.unregister();
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys';
|
|
2
|
+
import type { MaybeGetter } from './internal.svelte';
|
|
3
|
+
export interface SvelteHotkeyRecorder {
|
|
4
|
+
/** Whether recording is currently active */
|
|
5
|
+
readonly isRecording: boolean;
|
|
6
|
+
/** The currently recorded hotkey (for live preview) */
|
|
7
|
+
readonly recordedHotkey: Hotkey | null;
|
|
8
|
+
/** Start recording a new hotkey */
|
|
9
|
+
startRecording: () => void;
|
|
10
|
+
/** Stop recording (same as cancel) */
|
|
11
|
+
stopRecording: () => void;
|
|
12
|
+
/** Cancel recording without saving */
|
|
13
|
+
cancelRecording: () => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function createHotkeyRecorder(options: MaybeGetter<HotkeyRecorderOptions>): SvelteHotkeyRecorder;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _SvelteHotkeyRecorderState_recorder, _SvelteHotkeyRecorderState_subscribe;
|
|
13
|
+
import { HotkeyRecorder } from '@tanstack/hotkeys';
|
|
14
|
+
import { onDestroy } from 'svelte';
|
|
15
|
+
import { getDefaultHotkeysOptions } from './HotkeysCtx';
|
|
16
|
+
import { createStoreSubscriber, resolveMaybeGetter } from './internal.svelte';
|
|
17
|
+
/**
|
|
18
|
+
* Svelte function for recording keyboard shortcuts.
|
|
19
|
+
*
|
|
20
|
+
* This function provides a thin wrapper around the framework-agnostic `HotkeyRecorder`
|
|
21
|
+
* class, managing all the complexity of capturing keyboard events, converting them
|
|
22
|
+
* to hotkey strings, and handling edge cases like Escape to cancel or Backspace/Delete
|
|
23
|
+
* to clear.
|
|
24
|
+
*
|
|
25
|
+
* @param options - Configuration options for the recorder
|
|
26
|
+
* @returns An object with recording state and control functions
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```svelte
|
|
30
|
+
* <script>
|
|
31
|
+
* import { createHotkeyRecorder } from '@tanstack/svelte-hotkeys'
|
|
32
|
+
*
|
|
33
|
+
* const recorder = createHotkeyRecorder({
|
|
34
|
+
* onRecord: (hotkey) => {
|
|
35
|
+
* console.log('Recorded:', hotkey) // e.g., "Mod+Shift S"
|
|
36
|
+
* },
|
|
37
|
+
* onCancel: () => {
|
|
38
|
+
* console.log('Recording cancelled')
|
|
39
|
+
* },
|
|
40
|
+
* })
|
|
41
|
+
* </script>
|
|
42
|
+
*
|
|
43
|
+
* <div>
|
|
44
|
+
* <button onclick={recorder.startRecording}>
|
|
45
|
+
* {recorder.isRecording ? 'Recording...' : 'Edit Shortcut'}
|
|
46
|
+
* </button>
|
|
47
|
+
* {#if recorder.recordedHotkey}
|
|
48
|
+
* <div>Recording: {recorder.recordedHotkey}</div>
|
|
49
|
+
* {/if}
|
|
50
|
+
* </div>
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
class SvelteHotkeyRecorderState {
|
|
54
|
+
constructor(options) {
|
|
55
|
+
_SvelteHotkeyRecorderState_recorder.set(this, void 0);
|
|
56
|
+
_SvelteHotkeyRecorderState_subscribe.set(this, void 0);
|
|
57
|
+
__classPrivateFieldSet(this, _SvelteHotkeyRecorderState_recorder, new HotkeyRecorder(options), "f");
|
|
58
|
+
__classPrivateFieldSet(this, _SvelteHotkeyRecorderState_subscribe, createStoreSubscriber(__classPrivateFieldGet(this, _SvelteHotkeyRecorderState_recorder, "f").store), "f");
|
|
59
|
+
}
|
|
60
|
+
get isRecording() {
|
|
61
|
+
__classPrivateFieldGet(this, _SvelteHotkeyRecorderState_subscribe, "f").call(this);
|
|
62
|
+
return __classPrivateFieldGet(this, _SvelteHotkeyRecorderState_recorder, "f").store.state.isRecording;
|
|
63
|
+
}
|
|
64
|
+
get recordedHotkey() {
|
|
65
|
+
__classPrivateFieldGet(this, _SvelteHotkeyRecorderState_subscribe, "f").call(this);
|
|
66
|
+
return __classPrivateFieldGet(this, _SvelteHotkeyRecorderState_recorder, "f").store.state.recordedHotkey;
|
|
67
|
+
}
|
|
68
|
+
setOptions(options) {
|
|
69
|
+
__classPrivateFieldGet(this, _SvelteHotkeyRecorderState_recorder, "f").setOptions(options);
|
|
70
|
+
}
|
|
71
|
+
startRecording() {
|
|
72
|
+
__classPrivateFieldGet(this, _SvelteHotkeyRecorderState_recorder, "f").start();
|
|
73
|
+
}
|
|
74
|
+
stopRecording() {
|
|
75
|
+
__classPrivateFieldGet(this, _SvelteHotkeyRecorderState_recorder, "f").stop();
|
|
76
|
+
}
|
|
77
|
+
cancelRecording() {
|
|
78
|
+
__classPrivateFieldGet(this, _SvelteHotkeyRecorderState_recorder, "f").cancel();
|
|
79
|
+
}
|
|
80
|
+
destroy() {
|
|
81
|
+
__classPrivateFieldGet(this, _SvelteHotkeyRecorderState_recorder, "f").destroy();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
_SvelteHotkeyRecorderState_recorder = new WeakMap(), _SvelteHotkeyRecorderState_subscribe = new WeakMap();
|
|
85
|
+
export function createHotkeyRecorder(options) {
|
|
86
|
+
const recorder = new SvelteHotkeyRecorderState({
|
|
87
|
+
...getDefaultHotkeysOptions().hotkeyRecorder,
|
|
88
|
+
...resolveMaybeGetter(options),
|
|
89
|
+
});
|
|
90
|
+
$effect(() => {
|
|
91
|
+
recorder.setOptions({
|
|
92
|
+
...getDefaultHotkeysOptions().hotkeyRecorder,
|
|
93
|
+
...resolveMaybeGetter(options),
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
onDestroy(() => {
|
|
97
|
+
recorder.destroy();
|
|
98
|
+
});
|
|
99
|
+
return recorder;
|
|
100
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
* @example
|
|
11
|
+
* ```svelte
|
|
12
|
+
* <script lang="ts">
|
|
13
|
+
* import { createHotkeySequence } from '@tanstack/svelte-hotkeys'
|
|
14
|
+
*
|
|
15
|
+
* // Scroll to top when 'G G' is pressed
|
|
16
|
+
* createHotkeySequence(['G', 'G'], () => {
|
|
17
|
+
* scrollToTop()
|
|
18
|
+
* })
|
|
19
|
+
*
|
|
20
|
+
* // Delete line when 'D D' is pressed
|
|
21
|
+
* createHotkeySequence(['D', 'D'], () => {
|
|
22
|
+
* deleteLine()
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* // Delete inner word when 'D I W' is pressed
|
|
26
|
+
* createHotkeySequence(['D', 'I', 'W'], () => {
|
|
27
|
+
* deleteInnerWord()
|
|
28
|
+
* }, { timeout: 500 })
|
|
29
|
+
* </script>
|
|
30
|
+
*
|
|
31
|
+
* <div>
|
|
32
|
+
* ....
|
|
33
|
+
* </div>
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function createHotkeySequence(sequence: MaybeGetter<HotkeySequence>, callback: HotkeyCallback, options?: MaybeGetter<CreateHotkeySequenceOptions>): void;
|
|
37
|
+
/**
|
|
38
|
+
* Create an attachment for element-scoped keyboard sequences.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```svelte
|
|
42
|
+
* <script lang="ts">
|
|
43
|
+
* import { createHotkeySequenceAttachment } from '@tanstack/svelte-hotkeys'
|
|
44
|
+
*
|
|
45
|
+
* const vimKeys = createHotkeySequenceAttachment(['G', 'G'], () => {
|
|
46
|
+
* scrollToTop()
|
|
47
|
+
* })
|
|
48
|
+
* </script>
|
|
49
|
+
*
|
|
50
|
+
* <div tabindex="0" {@attach vimKeys}>
|
|
51
|
+
* Focus here and press g then g
|
|
52
|
+
* </div>
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function createHotkeySequenceAttachment(sequence: MaybeGetter<HotkeySequence>, callback: HotkeyCallback, options?: MaybeGetter<CreateHotkeySequenceOptions>): Attachment<HTMLElement>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { getSequenceManager } from '@tanstack/hotkeys';
|
|
2
|
+
import { getDefaultHotkeysOptions } from './HotkeysCtx';
|
|
3
|
+
import { resolveMaybeGetter } from './internal.svelte';
|
|
4
|
+
function registerHotkeySequence(target, sequence, callback, options) {
|
|
5
|
+
const resolvedSequence = resolveMaybeGetter(sequence);
|
|
6
|
+
const resolvedOptions = resolveMaybeGetter(options);
|
|
7
|
+
const mergedOptions = {
|
|
8
|
+
...getDefaultHotkeysOptions().hotkeySequence,
|
|
9
|
+
...resolvedOptions,
|
|
10
|
+
};
|
|
11
|
+
return getSequenceManager().register(resolvedSequence, callback, {
|
|
12
|
+
...mergedOptions,
|
|
13
|
+
target,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Register a global keyboard shortcut sequence for the current component.
|
|
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
|
+
* </script>
|
|
39
|
+
*
|
|
40
|
+
* <div>
|
|
41
|
+
* ....
|
|
42
|
+
* </div>
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function createHotkeySequence(sequence, callback, options = {}) {
|
|
46
|
+
$effect(() => {
|
|
47
|
+
if (typeof document === 'undefined') {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const resolvedSequence = resolveMaybeGetter(sequence);
|
|
51
|
+
if (resolvedSequence.length === 0) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const registration = registerHotkeySequence(document, resolvedSequence, callback, options);
|
|
55
|
+
return () => {
|
|
56
|
+
registration.unregister();
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create an attachment for element-scoped keyboard sequences.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```svelte
|
|
65
|
+
* <script lang="ts">
|
|
66
|
+
* import { createHotkeySequenceAttachment } from '@tanstack/svelte-hotkeys'
|
|
67
|
+
*
|
|
68
|
+
* const vimKeys = createHotkeySequenceAttachment(['G', 'G'], () => {
|
|
69
|
+
* scrollToTop()
|
|
70
|
+
* })
|
|
71
|
+
* </script>
|
|
72
|
+
*
|
|
73
|
+
* <div tabindex="0" {@attach vimKeys}>
|
|
74
|
+
* Focus here and press g then g
|
|
75
|
+
* </div>
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function createHotkeySequenceAttachment(sequence, callback, options = {}) {
|
|
79
|
+
return (element) => {
|
|
80
|
+
const resolvedSequence = resolveMaybeGetter(sequence);
|
|
81
|
+
if (resolvedSequence.length === 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const registration = registerHotkeySequence(element, resolvedSequence, callback, options);
|
|
85
|
+
return () => {
|
|
86
|
+
registration.unregister();
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface SvelteHeldKeyCodesMap {
|
|
2
|
+
readonly codes: Record<string, string>;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Svelte function that returns reactive access to the map of currently held key names
|
|
6
|
+
* to their physical `event.code` values.
|
|
7
|
+
*
|
|
8
|
+
* This is useful for debugging which physical key was pressed (e.g. distinguishing
|
|
9
|
+
* left vs right Shift via "ShiftLeft" / "ShiftRight").
|
|
10
|
+
*
|
|
11
|
+
* @returns Object with a reactive `codes` property
|
|
12
|
+
*
|
|
13
|
+
* ```svelte
|
|
14
|
+
* <script>
|
|
15
|
+
* import { getHeldKeyCodesMap } from '@tanstack/svelte-hotkeys'
|
|
16
|
+
* const heldKeyCodesMap = getHeldKeyCodesMap()
|
|
17
|
+
* </script>
|
|
18
|
+
*
|
|
19
|
+
* <div>
|
|
20
|
+
* {#each Object.entries(heldKeyCodesMap.codes) as [key, code]}
|
|
21
|
+
* <kbd>
|
|
22
|
+
* {key} <small>{code}</small>
|
|
23
|
+
* </kbd>
|
|
24
|
+
* {/each}
|
|
25
|
+
* </div>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function getHeldKeyCodesMap(): SvelteHeldKeyCodesMap;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var _HeldKeyCodesMapState_tracker, _HeldKeyCodesMapState_subscribe;
|
|
7
|
+
import { getKeyStateTracker } from '@tanstack/hotkeys';
|
|
8
|
+
import { createStoreSubscriber } from './internal.svelte';
|
|
9
|
+
class HeldKeyCodesMapState {
|
|
10
|
+
constructor() {
|
|
11
|
+
_HeldKeyCodesMapState_tracker.set(this, getKeyStateTracker());
|
|
12
|
+
_HeldKeyCodesMapState_subscribe.set(this, createStoreSubscriber(__classPrivateFieldGet(this, _HeldKeyCodesMapState_tracker, "f").store));
|
|
13
|
+
}
|
|
14
|
+
get codes() {
|
|
15
|
+
__classPrivateFieldGet(this, _HeldKeyCodesMapState_subscribe, "f").call(this);
|
|
16
|
+
return __classPrivateFieldGet(this, _HeldKeyCodesMapState_tracker, "f").store.state.heldCodes;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
_HeldKeyCodesMapState_tracker = new WeakMap(), _HeldKeyCodesMapState_subscribe = new WeakMap();
|
|
20
|
+
/**
|
|
21
|
+
* Svelte function that returns reactive access to the map of currently held key names
|
|
22
|
+
* to their physical `event.code` values.
|
|
23
|
+
*
|
|
24
|
+
* This is useful for debugging which physical key was pressed (e.g. distinguishing
|
|
25
|
+
* left vs right Shift via "ShiftLeft" / "ShiftRight").
|
|
26
|
+
*
|
|
27
|
+
* @returns Object with a reactive `codes` property
|
|
28
|
+
*
|
|
29
|
+
* ```svelte
|
|
30
|
+
* <script>
|
|
31
|
+
* import { getHeldKeyCodesMap } from '@tanstack/svelte-hotkeys'
|
|
32
|
+
* const heldKeyCodesMap = getHeldKeyCodesMap()
|
|
33
|
+
* </script>
|
|
34
|
+
*
|
|
35
|
+
* <div>
|
|
36
|
+
* {#each Object.entries(heldKeyCodesMap.codes) as [key, code]}
|
|
37
|
+
* <kbd>
|
|
38
|
+
* {key} <small>{code}</small>
|
|
39
|
+
* </kbd>
|
|
40
|
+
* {/each}
|
|
41
|
+
* </div>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function getHeldKeyCodesMap() {
|
|
45
|
+
return new HeldKeyCodesMapState();
|
|
46
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface SvelteHeldKeys {
|
|
2
|
+
readonly keys: Array<string>;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Svelte function that returns reactive access to currently held keyboard keys.
|
|
6
|
+
*
|
|
7
|
+
* This function uses the global KeyStateTracker and updates whenever keys are pressed
|
|
8
|
+
* or released.
|
|
9
|
+
*
|
|
10
|
+
* @returns Object with a reactive `keys` property
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```svelte
|
|
14
|
+
* <script>
|
|
15
|
+
* import { getHeldKeys } from '@tanstack/svelte-hotkeys'
|
|
16
|
+
*
|
|
17
|
+
* const heldKeys = getHeldKeys()
|
|
18
|
+
* </script>
|
|
19
|
+
* <div>
|
|
20
|
+
* Currently pressed: {heldKeys.keys.join(' + ') || 'None'}
|
|
21
|
+
* </div>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function getHeldKeys(): SvelteHeldKeys;
|