@tanstack/svelte-hotkeys 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -0
- package/dist/HotkeysCtx.d.ts +22 -0
- package/dist/HotkeysCtx.js +28 -0
- package/dist/HotkeysProvider.svelte +12 -0
- package/dist/HotkeysProvider.svelte.d.ts +4 -0
- package/dist/createHotkey.svelte.d.ts +42 -0
- package/dist/createHotkey.svelte.js +74 -0
- package/dist/createHotkeyRecorder.svelte.d.ts +15 -0
- package/dist/createHotkeyRecorder.svelte.js +100 -0
- package/dist/createHotkeySequence.svelte.d.ts +55 -0
- package/dist/createHotkeySequence.svelte.js +89 -0
- package/dist/getHeldKeyCodesMap.svelte.d.ts +28 -0
- package/dist/getHeldKeyCodesMap.svelte.js +46 -0
- package/dist/getHeldKeys.svelte.d.ts +24 -0
- package/dist/getHeldKeys.svelte.js +42 -0
- package/dist/getIsKeyHeld.svelte.d.ts +44 -0
- package/dist/getIsKeyHeld.svelte.js +69 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/internal.svelte.d.ts +10 -0
- package/dist/internal.svelte.js +12 -0
- package/package.json +64 -0
- package/src/HotkeysCtx.ts +57 -0
- package/src/HotkeysProvider.svelte +12 -0
- package/src/createHotkey.svelte.ts +120 -0
- package/src/createHotkeyRecorder.svelte.ts +118 -0
- package/src/createHotkeySequence.svelte.ts +136 -0
- package/src/getHeldKeyCodesMap.svelte.ts +44 -0
- package/src/getHeldKeys.svelte.ts +40 -0
- package/src/getIsKeyHeld.svelte.ts +68 -0
- package/src/index.ts +17 -0
- package/src/internal.svelte.ts +26 -0
|
@@ -0,0 +1,42 @@
|
|
|
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 _HeldKeysState_tracker, _HeldKeysState_subscribe;
|
|
7
|
+
import { getKeyStateTracker } from '@tanstack/hotkeys';
|
|
8
|
+
import { createStoreSubscriber } from './internal.svelte';
|
|
9
|
+
class HeldKeysState {
|
|
10
|
+
constructor() {
|
|
11
|
+
_HeldKeysState_tracker.set(this, getKeyStateTracker());
|
|
12
|
+
_HeldKeysState_subscribe.set(this, createStoreSubscriber(__classPrivateFieldGet(this, _HeldKeysState_tracker, "f").store));
|
|
13
|
+
}
|
|
14
|
+
get keys() {
|
|
15
|
+
__classPrivateFieldGet(this, _HeldKeysState_subscribe, "f").call(this);
|
|
16
|
+
return __classPrivateFieldGet(this, _HeldKeysState_tracker, "f").store.state.heldKeys;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
_HeldKeysState_tracker = new WeakMap(), _HeldKeysState_subscribe = new WeakMap();
|
|
20
|
+
/**
|
|
21
|
+
* Svelte function that returns reactive access to currently held keyboard keys.
|
|
22
|
+
*
|
|
23
|
+
* This function uses the global KeyStateTracker and updates whenever keys are pressed
|
|
24
|
+
* or released.
|
|
25
|
+
*
|
|
26
|
+
* @returns Object with a reactive `keys` property
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```svelte
|
|
30
|
+
* <script>
|
|
31
|
+
* import { getHeldKeys } from '@tanstack/svelte-hotkeys'
|
|
32
|
+
*
|
|
33
|
+
* const heldKeys = getHeldKeys()
|
|
34
|
+
* </script>
|
|
35
|
+
* <div>
|
|
36
|
+
* Currently pressed: {heldKeys.keys.join(' + ') || 'None'}
|
|
37
|
+
* </div>
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function getHeldKeys() {
|
|
41
|
+
return new HeldKeysState();
|
|
42
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { HeldKey } from '@tanstack/hotkeys';
|
|
2
|
+
export interface SvelteHeldKeyState {
|
|
3
|
+
readonly held: boolean;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Svelte function that returns reactive access to whether a specific key is currently being held.
|
|
7
|
+
*
|
|
8
|
+
* This function uses the global KeyStateTracker and updates whenever keys are pressed
|
|
9
|
+
* or released.
|
|
10
|
+
*
|
|
11
|
+
* @param key - The key to check (e.g., 'Shift', 'Control', 'A')
|
|
12
|
+
* @returns Object with a reactive `held` property
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```svelte
|
|
16
|
+
* <script>
|
|
17
|
+
* import { getIsKeyHeld } from '@tanstack/svelte-hotkeys'
|
|
18
|
+
*
|
|
19
|
+
* const isShiftHeld = getIsKeyHeld('Shift')
|
|
20
|
+
* </script>
|
|
21
|
+
*
|
|
22
|
+
* <div>
|
|
23
|
+
* {isShiftHeld.held ? 'Shift is pressed!' : 'Press Shift'}
|
|
24
|
+
* </div>
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```svelte
|
|
29
|
+
* <script>
|
|
30
|
+
* import { getIsKeyHeld } from '@tanstack/svelte-hotkeys'
|
|
31
|
+
*
|
|
32
|
+
* const isCtrlHeld = getIsKeyHeld('Control')
|
|
33
|
+
* const isShiftHeld = getIsKeyHeld('Shift')
|
|
34
|
+
* const isAltHeld = getIsKeyHeld('Alt')
|
|
35
|
+
* </script>
|
|
36
|
+
*
|
|
37
|
+
* <div>
|
|
38
|
+
* <span style:opacity={isCtrlHeld.held ? 1 : 0.3}>Ctrl</span>
|
|
39
|
+
* <span style:opacity={isShiftHeld.held ? 1 : 0.3}>Shift</span>
|
|
40
|
+
* <span style:opacity={isAltHeld.held ? 1 : 0.3}>Alt</span>
|
|
41
|
+
* </div>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function getIsKeyHeld(key: HeldKey): SvelteHeldKeyState;
|
|
@@ -0,0 +1,69 @@
|
|
|
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 _HeldKeyState_tracker, _HeldKeyState_normalizedKey, _HeldKeyState_subscribe;
|
|
13
|
+
import { getKeyStateTracker } from '@tanstack/hotkeys';
|
|
14
|
+
import { createStoreSubscriber } from './internal.svelte';
|
|
15
|
+
class HeldKeyState {
|
|
16
|
+
constructor(key) {
|
|
17
|
+
_HeldKeyState_tracker.set(this, getKeyStateTracker());
|
|
18
|
+
_HeldKeyState_normalizedKey.set(this, void 0);
|
|
19
|
+
_HeldKeyState_subscribe.set(this, createStoreSubscriber(__classPrivateFieldGet(this, _HeldKeyState_tracker, "f").store));
|
|
20
|
+
__classPrivateFieldSet(this, _HeldKeyState_normalizedKey, key.toLowerCase(), "f");
|
|
21
|
+
}
|
|
22
|
+
get held() {
|
|
23
|
+
__classPrivateFieldGet(this, _HeldKeyState_subscribe, "f").call(this);
|
|
24
|
+
return __classPrivateFieldGet(this, _HeldKeyState_tracker, "f").store.state.heldKeys.some((heldKey) => heldKey.toLowerCase() === __classPrivateFieldGet(this, _HeldKeyState_normalizedKey, "f"));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
_HeldKeyState_tracker = new WeakMap(), _HeldKeyState_normalizedKey = new WeakMap(), _HeldKeyState_subscribe = new WeakMap();
|
|
28
|
+
/**
|
|
29
|
+
* Svelte function that returns reactive access to whether a specific key is currently being held.
|
|
30
|
+
*
|
|
31
|
+
* This function uses the global KeyStateTracker and updates whenever keys are pressed
|
|
32
|
+
* or released.
|
|
33
|
+
*
|
|
34
|
+
* @param key - The key to check (e.g., 'Shift', 'Control', 'A')
|
|
35
|
+
* @returns Object with a reactive `held` property
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```svelte
|
|
39
|
+
* <script>
|
|
40
|
+
* import { getIsKeyHeld } from '@tanstack/svelte-hotkeys'
|
|
41
|
+
*
|
|
42
|
+
* const isShiftHeld = getIsKeyHeld('Shift')
|
|
43
|
+
* </script>
|
|
44
|
+
*
|
|
45
|
+
* <div>
|
|
46
|
+
* {isShiftHeld.held ? 'Shift is pressed!' : 'Press Shift'}
|
|
47
|
+
* </div>
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```svelte
|
|
52
|
+
* <script>
|
|
53
|
+
* import { getIsKeyHeld } from '@tanstack/svelte-hotkeys'
|
|
54
|
+
*
|
|
55
|
+
* const isCtrlHeld = getIsKeyHeld('Control')
|
|
56
|
+
* const isShiftHeld = getIsKeyHeld('Shift')
|
|
57
|
+
* const isAltHeld = getIsKeyHeld('Alt')
|
|
58
|
+
* </script>
|
|
59
|
+
*
|
|
60
|
+
* <div>
|
|
61
|
+
* <span style:opacity={isCtrlHeld.held ? 1 : 0.3}>Ctrl</span>
|
|
62
|
+
* <span style:opacity={isShiftHeld.held ? 1 : 0.3}>Shift</span>
|
|
63
|
+
* <span style:opacity={isAltHeld.held ? 1 : 0.3}>Alt</span>
|
|
64
|
+
* </div>
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function getIsKeyHeld(key) {
|
|
68
|
+
return new HeldKeyState(key);
|
|
69
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from '@tanstack/hotkeys';
|
|
2
|
+
export * from './createHotkey.svelte';
|
|
3
|
+
export * from './createHotkeySequence.svelte';
|
|
4
|
+
export * from './createHotkeyRecorder.svelte';
|
|
5
|
+
export * from './getHeldKeys.svelte';
|
|
6
|
+
export * from './getHeldKeyCodesMap.svelte';
|
|
7
|
+
export * from './getIsKeyHeld.svelte';
|
|
8
|
+
export { default as HotkeysProvider } from './HotkeysProvider.svelte';
|
|
9
|
+
export { DEFAULT_OPTIONS, getDefaultHotkeysOptions, getHotkeysContext, setHotkeysContext, } from './HotkeysCtx';
|
|
10
|
+
export type { HotkeysProviderOptions, HotkeysProviderProps } from './HotkeysCtx';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Re-export everything from the core package
|
|
2
|
+
export * from '@tanstack/hotkeys';
|
|
3
|
+
export * from './createHotkey.svelte';
|
|
4
|
+
export * from './createHotkeySequence.svelte';
|
|
5
|
+
export * from './createHotkeyRecorder.svelte';
|
|
6
|
+
export * from './getHeldKeys.svelte';
|
|
7
|
+
export * from './getHeldKeyCodesMap.svelte';
|
|
8
|
+
export * from './getIsKeyHeld.svelte';
|
|
9
|
+
export { default as HotkeysProvider } from './HotkeysProvider.svelte';
|
|
10
|
+
export { DEFAULT_OPTIONS, getDefaultHotkeysOptions, getHotkeysContext, setHotkeysContext, } from './HotkeysCtx';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type MaybeGetter<T> = T | (() => T);
|
|
2
|
+
interface SubscribableStore<TState> {
|
|
3
|
+
state: TState;
|
|
4
|
+
subscribe: (listener: () => void) => (() => void) | {
|
|
5
|
+
unsubscribe: () => void;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export declare function resolveMaybeGetter<T>(value: MaybeGetter<T>): T;
|
|
9
|
+
export declare function createStoreSubscriber<TState>(store: SubscribableStore<TState>): () => void;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createSubscriber } from 'svelte/reactivity';
|
|
2
|
+
export function resolveMaybeGetter(value) {
|
|
3
|
+
return typeof value === 'function' ? value() : value;
|
|
4
|
+
}
|
|
5
|
+
export function createStoreSubscriber(store) {
|
|
6
|
+
return createSubscriber((update) => {
|
|
7
|
+
const subscription = store.subscribe(update);
|
|
8
|
+
return typeof subscription === 'function'
|
|
9
|
+
? subscription
|
|
10
|
+
: () => subscription.unsubscribe();
|
|
11
|
+
});
|
|
12
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tanstack/svelte-hotkeys",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Svelte adapter for TanStack Hotkeys",
|
|
5
|
+
"author": "Kunal Rao",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/TanStack/hotkeys.git",
|
|
10
|
+
"directory": "packages/svelte-hotkeys"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://tanstack.com/hotkeys",
|
|
13
|
+
"funding": {
|
|
14
|
+
"type": "github",
|
|
15
|
+
"url": "https://github.com/sponsors/kunalrao"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"svelte",
|
|
19
|
+
"tanstack",
|
|
20
|
+
"keys",
|
|
21
|
+
"hotkeys",
|
|
22
|
+
"keyboard",
|
|
23
|
+
"shortcuts"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"clean": "premove ./dist ./coverage",
|
|
27
|
+
"test:eslint": "eslint ./src",
|
|
28
|
+
"test:lib": "vitest --passWithNoTests",
|
|
29
|
+
"test:lib:dev": "pnpm test:lib --watch",
|
|
30
|
+
"test:types": "tsc",
|
|
31
|
+
"test:build": "publint --strict",
|
|
32
|
+
"build": "svelte-package --input ./src --output ./dist"
|
|
33
|
+
},
|
|
34
|
+
"type": "module",
|
|
35
|
+
"types": "dist/index.d.ts",
|
|
36
|
+
"module": "dist/index.js",
|
|
37
|
+
"svelte": "./dist/index.js",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"svelte": "./dist/index.js",
|
|
42
|
+
"import": "./dist/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./package.json": "./package.json"
|
|
45
|
+
},
|
|
46
|
+
"sideEffects": false,
|
|
47
|
+
"files": [
|
|
48
|
+
"dist",
|
|
49
|
+
"src"
|
|
50
|
+
],
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@tanstack/hotkeys": "workspace:*"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@sveltejs/package": "^2.5.7",
|
|
56
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
57
|
+
"@typescript-eslint/parser": "^8.56.1",
|
|
58
|
+
"eslint-plugin-svelte": "^3.15.0",
|
|
59
|
+
"svelte": "^5.53.7"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"svelte": "^5.25.0"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createContext } from 'svelte'
|
|
2
|
+
import type { HotkeyRecorderOptions } from '@tanstack/hotkeys'
|
|
3
|
+
import type { CreateHotkeyOptions } from './createHotkey.svelte'
|
|
4
|
+
import type { Snippet } from 'svelte'
|
|
5
|
+
import type { CreateHotkeySequenceOptions } from './createHotkeySequence.svelte'
|
|
6
|
+
|
|
7
|
+
export interface HotkeysProviderOptions {
|
|
8
|
+
hotkey?: Partial<CreateHotkeyOptions>
|
|
9
|
+
hotkeyRecorder?: Partial<HotkeyRecorderOptions>
|
|
10
|
+
hotkeySequence?: Partial<CreateHotkeySequenceOptions>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface HotkeysProviderProps {
|
|
14
|
+
children: Snippet
|
|
15
|
+
defaultOptions?: HotkeysProviderOptions
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_OPTIONS: HotkeysProviderOptions = {}
|
|
19
|
+
|
|
20
|
+
interface HotkeysContextValue {
|
|
21
|
+
defaultOptions: HotkeysProviderOptions
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const [useHotkeysContext, setHotkeysContextValue] =
|
|
25
|
+
createContext<HotkeysContextValue | null>()
|
|
26
|
+
|
|
27
|
+
export function setHotkeysContext(
|
|
28
|
+
defaultOptions: HotkeysProviderOptions = DEFAULT_OPTIONS,
|
|
29
|
+
): HotkeysContextValue {
|
|
30
|
+
return setHotkeysContextValue({
|
|
31
|
+
get defaultOptions() {
|
|
32
|
+
return defaultOptions
|
|
33
|
+
},
|
|
34
|
+
})!
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function setHotkeysContextSource(
|
|
38
|
+
defaultOptions: () => HotkeysProviderOptions,
|
|
39
|
+
): HotkeysContextValue {
|
|
40
|
+
return setHotkeysContextValue({
|
|
41
|
+
get defaultOptions() {
|
|
42
|
+
return defaultOptions()
|
|
43
|
+
},
|
|
44
|
+
})!
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getHotkeysContext(): HotkeysContextValue | null {
|
|
48
|
+
try {
|
|
49
|
+
return useHotkeysContext()
|
|
50
|
+
} catch {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getDefaultHotkeysOptions(): HotkeysProviderOptions {
|
|
56
|
+
return getHotkeysContext()?.defaultOptions ?? DEFAULT_OPTIONS
|
|
57
|
+
}
|
|
@@ -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,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectPlatform,
|
|
3
|
+
formatHotkey,
|
|
4
|
+
getHotkeyManager,
|
|
5
|
+
rawHotkeyToParsedHotkey,
|
|
6
|
+
} from '@tanstack/hotkeys'
|
|
7
|
+
import { getDefaultHotkeysOptions } from './HotkeysCtx'
|
|
8
|
+
import { resolveMaybeGetter } from './internal.svelte'
|
|
9
|
+
import type {
|
|
10
|
+
Hotkey,
|
|
11
|
+
HotkeyCallback,
|
|
12
|
+
HotkeyOptions,
|
|
13
|
+
RegisterableHotkey,
|
|
14
|
+
} from '@tanstack/hotkeys'
|
|
15
|
+
import type { MaybeGetter } from './internal.svelte'
|
|
16
|
+
import type { Attachment } from 'svelte/attachments'
|
|
17
|
+
|
|
18
|
+
export interface CreateHotkeyOptions extends Omit<HotkeyOptions, 'target'> {
|
|
19
|
+
target?: Document | Window // not html elements, use attachment instead
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeHotkey(
|
|
23
|
+
hotkey: RegisterableHotkey,
|
|
24
|
+
options: CreateHotkeyOptions,
|
|
25
|
+
): Hotkey {
|
|
26
|
+
const platform = options.platform ?? detectPlatform()
|
|
27
|
+
|
|
28
|
+
return typeof hotkey === 'string'
|
|
29
|
+
? hotkey
|
|
30
|
+
: (formatHotkey(rawHotkeyToParsedHotkey(hotkey, platform)) as Hotkey)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function registerHotkey(
|
|
34
|
+
target: HTMLElement | Document | Window,
|
|
35
|
+
hotkey: MaybeGetter<RegisterableHotkey>,
|
|
36
|
+
callback: HotkeyCallback,
|
|
37
|
+
options: MaybeGetter<CreateHotkeyOptions>,
|
|
38
|
+
) {
|
|
39
|
+
const resolvedHotkey = resolveMaybeGetter(hotkey)
|
|
40
|
+
const resolvedOptions = resolveMaybeGetter(options)
|
|
41
|
+
const mergedOptions = {
|
|
42
|
+
...getDefaultHotkeysOptions().hotkey,
|
|
43
|
+
...resolvedOptions,
|
|
44
|
+
} as CreateHotkeyOptions
|
|
45
|
+
|
|
46
|
+
return getHotkeyManager().register(
|
|
47
|
+
normalizeHotkey(resolvedHotkey, mergedOptions),
|
|
48
|
+
callback,
|
|
49
|
+
{
|
|
50
|
+
...mergedOptions,
|
|
51
|
+
target,
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Register a global hotkey for the current component.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```svelte
|
|
61
|
+
* <script lang="ts">
|
|
62
|
+
* import { createHotkey } from '@tanstack/svelte-hotkeys'
|
|
63
|
+
*
|
|
64
|
+
* createHotkey('Mod+S', () => {
|
|
65
|
+
* console.log('Mod+S pressed')
|
|
66
|
+
* })
|
|
67
|
+
* </script>
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function createHotkey(
|
|
71
|
+
hotkey: MaybeGetter<RegisterableHotkey>,
|
|
72
|
+
callback: HotkeyCallback,
|
|
73
|
+
options: MaybeGetter<CreateHotkeyOptions> = {},
|
|
74
|
+
): void {
|
|
75
|
+
$effect(() => {
|
|
76
|
+
if (typeof document === 'undefined') {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const registration = registerHotkey(document, hotkey, callback, options)
|
|
81
|
+
|
|
82
|
+
return () => {
|
|
83
|
+
registration.unregister()
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create an attachment for element-scoped hotkeys.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```svelte
|
|
93
|
+
* <script lang="ts">
|
|
94
|
+
* import { createHotkeyAttachment } from '@tanstack/svelte-hotkeys'
|
|
95
|
+
*
|
|
96
|
+
* let count = $state(0)
|
|
97
|
+
* const saveHotkey = createHotkeyAttachment('Mod+S', () => {
|
|
98
|
+
* count++
|
|
99
|
+
* })
|
|
100
|
+
*
|
|
101
|
+
* </script>
|
|
102
|
+
*
|
|
103
|
+
* <div tabindex="0" {@attach saveHotkey}>
|
|
104
|
+
* Count: {count}
|
|
105
|
+
* </div>
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export function createHotkeyAttachment(
|
|
109
|
+
hotkey: MaybeGetter<RegisterableHotkey>,
|
|
110
|
+
callback: HotkeyCallback,
|
|
111
|
+
options: MaybeGetter<CreateHotkeyOptions> = {},
|
|
112
|
+
): Attachment<HTMLElement> {
|
|
113
|
+
return (element) => {
|
|
114
|
+
const registration = registerHotkey(element, hotkey, callback, options)
|
|
115
|
+
|
|
116
|
+
return () => {
|
|
117
|
+
registration.unregister()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { HotkeyRecorder } from '@tanstack/hotkeys'
|
|
2
|
+
import { onDestroy } from 'svelte'
|
|
3
|
+
import { getDefaultHotkeysOptions } from './HotkeysCtx'
|
|
4
|
+
import { createStoreSubscriber, resolveMaybeGetter } from './internal.svelte'
|
|
5
|
+
import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys'
|
|
6
|
+
import type { MaybeGetter } from './internal.svelte'
|
|
7
|
+
|
|
8
|
+
export interface SvelteHotkeyRecorder {
|
|
9
|
+
/** Whether recording is currently active */
|
|
10
|
+
readonly isRecording: boolean
|
|
11
|
+
/** The currently recorded hotkey (for live preview) */
|
|
12
|
+
readonly recordedHotkey: Hotkey | null
|
|
13
|
+
/** Start recording a new hotkey */
|
|
14
|
+
startRecording: () => void
|
|
15
|
+
/** Stop recording (same as cancel) */
|
|
16
|
+
stopRecording: () => void
|
|
17
|
+
/** Cancel recording without saving */
|
|
18
|
+
cancelRecording: () => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Svelte function for recording keyboard shortcuts.
|
|
23
|
+
*
|
|
24
|
+
* This function provides a thin wrapper around the framework-agnostic `HotkeyRecorder`
|
|
25
|
+
* class, managing all the complexity of capturing keyboard events, converting them
|
|
26
|
+
* to hotkey strings, and handling edge cases like Escape to cancel or Backspace/Delete
|
|
27
|
+
* to clear.
|
|
28
|
+
*
|
|
29
|
+
* @param options - Configuration options for the recorder
|
|
30
|
+
* @returns An object with recording state and control functions
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```svelte
|
|
34
|
+
* <script>
|
|
35
|
+
* import { createHotkeyRecorder } from '@tanstack/svelte-hotkeys'
|
|
36
|
+
*
|
|
37
|
+
* const recorder = createHotkeyRecorder({
|
|
38
|
+
* onRecord: (hotkey) => {
|
|
39
|
+
* console.log('Recorded:', hotkey) // e.g., "Mod+Shift S"
|
|
40
|
+
* },
|
|
41
|
+
* onCancel: () => {
|
|
42
|
+
* console.log('Recording cancelled')
|
|
43
|
+
* },
|
|
44
|
+
* })
|
|
45
|
+
* </script>
|
|
46
|
+
*
|
|
47
|
+
* <div>
|
|
48
|
+
* <button onclick={recorder.startRecording}>
|
|
49
|
+
* {recorder.isRecording ? 'Recording...' : 'Edit Shortcut'}
|
|
50
|
+
* </button>
|
|
51
|
+
* {#if recorder.recordedHotkey}
|
|
52
|
+
* <div>Recording: {recorder.recordedHotkey}</div>
|
|
53
|
+
* {/if}
|
|
54
|
+
* </div>
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
class SvelteHotkeyRecorderState implements SvelteHotkeyRecorder {
|
|
59
|
+
#recorder: HotkeyRecorder
|
|
60
|
+
#subscribe: () => void
|
|
61
|
+
|
|
62
|
+
constructor(options: HotkeyRecorderOptions) {
|
|
63
|
+
this.#recorder = new HotkeyRecorder(options)
|
|
64
|
+
this.#subscribe = createStoreSubscriber(this.#recorder.store)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get isRecording(): boolean {
|
|
68
|
+
this.#subscribe()
|
|
69
|
+
return this.#recorder.store.state.isRecording
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get recordedHotkey(): Hotkey | null {
|
|
73
|
+
this.#subscribe()
|
|
74
|
+
return this.#recorder.store.state.recordedHotkey
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setOptions(options: HotkeyRecorderOptions): void {
|
|
78
|
+
this.#recorder.setOptions(options)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
startRecording(): void {
|
|
82
|
+
this.#recorder.start()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
stopRecording(): void {
|
|
86
|
+
this.#recorder.stop()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
cancelRecording(): void {
|
|
90
|
+
this.#recorder.cancel()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
destroy(): void {
|
|
94
|
+
this.#recorder.destroy()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function createHotkeyRecorder(
|
|
99
|
+
options: MaybeGetter<HotkeyRecorderOptions>,
|
|
100
|
+
): SvelteHotkeyRecorder {
|
|
101
|
+
const recorder = new SvelteHotkeyRecorderState({
|
|
102
|
+
...getDefaultHotkeysOptions().hotkeyRecorder,
|
|
103
|
+
...resolveMaybeGetter(options),
|
|
104
|
+
} as HotkeyRecorderOptions)
|
|
105
|
+
|
|
106
|
+
$effect(() => {
|
|
107
|
+
recorder.setOptions({
|
|
108
|
+
...getDefaultHotkeysOptions().hotkeyRecorder,
|
|
109
|
+
...resolveMaybeGetter(options),
|
|
110
|
+
} as HotkeyRecorderOptions)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
onDestroy(() => {
|
|
114
|
+
recorder.destroy()
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
return recorder
|
|
118
|
+
}
|