diff --git a/README.md b/README.md index 1c80cd6..8c67395 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/docs/config.json b/docs/config.json index 544aa64..bf75e84 100644 --- a/docs/config.json +++ b/docs/config.json @@ -67,6 +67,15 @@ "to": "framework/vue/quick-start" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "Quick Start", + "to": "framework/svelte/quick-start" + } + ] } ] }, @@ -198,6 +207,31 @@ "to": "framework/vue/guides/formatting-display" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "Hotkeys", + "to": "framework/svelte/guides/hotkeys" + }, + { + "label": "Sequences", + "to": "framework/svelte/guides/sequences" + }, + { + "label": "Hotkey Recording", + "to": "framework/svelte/guides/hotkey-recording" + }, + { + "label": "Key State Tracking", + "to": "framework/svelte/guides/key-state-tracking" + }, + { + "label": "Formatting & Display", + "to": "framework/svelte/guides/formatting-display" + } + ] } ] }, @@ -254,6 +288,15 @@ "to": "framework/vue/reference/index" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "Svelte APIs", + "to": "framework/svelte/reference/index" + } + ] } ] }, @@ -376,6 +419,51 @@ "to": "framework/vue/reference/interfaces/HotkeysProviderOptions" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "createHotkey", + "to": "framework/svelte/reference/functions/createHotkey" + }, + { + "label": "createHotkeyAttachment", + "to": "framework/svelte/reference/functions/createHotkeyAttachment" + }, + { + "label": "CreateHotkeyOptions", + "to": "framework/svelte/reference/interfaces/CreateHotkeyOptions" + }, + { + "label": "HotkeysProvider", + "to": "framework/svelte/reference/variables/HotkeysProvider" + }, + { + "label": "HotkeysProviderOptions", + "to": "framework/svelte/reference/interfaces/HotkeysProviderOptions" + }, + { + "label": "HotkeysProviderProps", + "to": "framework/svelte/reference/interfaces/HotkeysProviderProps" + }, + { + "label": "DEFAULT_OPTIONS", + "to": "framework/svelte/reference/variables/DEFAULT_OPTIONS" + }, + { + "label": "getDefaultHotkeysOptions", + "to": "framework/svelte/reference/functions/getDefaultHotkeysOptions" + }, + { + "label": "getHotkeysContext", + "to": "framework/svelte/reference/functions/getHotkeysContext" + }, + { + "label": "setHotkeysContext", + "to": "framework/svelte/reference/functions/setHotkeysContext" + } + ] } ] }, @@ -470,6 +558,23 @@ "to": "framework/vue/reference/interfaces/UseHotkeySequenceOptions" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "createHotkeySequence", + "to": "framework/svelte/reference/functions/createHotkeySequence" + }, + { + "label": "createHotkeySequenceAttachment", + "to": "framework/svelte/reference/functions/createHotkeySequenceAttachment" + }, + { + "label": "CreateHotkeySequenceOptions", + "to": "framework/svelte/reference/interfaces/CreateHotkeySequenceOptions" + } + ] } ] }, @@ -540,6 +645,19 @@ "to": "framework/vue/reference/functions/useKeyHold" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "getIsKeyHeld", + "to": "framework/svelte/reference/functions/getIsKeyHeld" + }, + { + "label": "SvelteHeldKeyState", + "to": "framework/svelte/reference/interfaces/SvelteHeldKeyState" + } + ] } ] }, @@ -630,6 +748,27 @@ "to": "framework/vue/reference/functions/useHeldKeyCodes" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "getHeldKeys", + "to": "framework/svelte/reference/functions/getHeldKeys" + }, + { + "label": "SvelteHeldKeys", + "to": "framework/svelte/reference/interfaces/SvelteHeldKeys" + }, + { + "label": "getHeldKeyCodesMap", + "to": "framework/svelte/reference/functions/getHeldKeyCodesMap" + }, + { + "label": "SvelteHeldKeyCodesMap", + "to": "framework/svelte/reference/interfaces/SvelteHeldKeyCodesMap" + } + ] } ] }, @@ -716,6 +855,19 @@ "to": "framework/vue/reference/interfaces/VueHotkeyRecorder" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "createHotkeyRecorder", + "to": "framework/svelte/reference/functions/createHotkeyRecorder" + }, + { + "label": "SvelteHotkeyRecorder", + "to": "framework/svelte/reference/interfaces/SvelteHotkeyRecorder" + } + ] } ] }, @@ -906,6 +1058,31 @@ "to": "framework/vue/examples/useKeyhold" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "createHotkey", + "to": "framework/svelte/examples/create-hotkey" + }, + { + "label": "createHotkeySequence", + "to": "framework/svelte/examples/create-hotkey-sequence" + }, + { + "label": "createHotkeyRecorder", + "to": "framework/svelte/examples/create-hotkey-recorder" + }, + { + "label": "getHeldKeys", + "to": "framework/svelte/examples/get-held-keys" + }, + { + "label": "getIsKeyHeld", + "to": "framework/svelte/examples/get-is-key-held" + } + ] } ] } diff --git a/docs/framework/svelte/guides/formatting-display.md b/docs/framework/svelte/guides/formatting-display.md new file mode 100644 index 0000000..f13d857 --- /dev/null +++ b/docs/framework/svelte/guides/formatting-display.md @@ -0,0 +1,70 @@ +--- +title: Formatting & Display Guide +id: formatting-display +--- + +TanStack Hotkeys includes utilities for turning hotkey strings into display-friendly labels. These utilities are framework-agnostic, but they pair naturally with Svelte templates and reactive UI. + +## `formatForDisplay` + +```ts +import { formatForDisplay } from '@tanstack/svelte-hotkeys' + +formatForDisplay('Mod+S') +formatForDisplay('Mod+Shift+Z') +``` + +## `formatWithLabels` + +```ts +import { formatWithLabels } from '@tanstack/svelte-hotkeys' + +formatWithLabels('Mod+S') +formatWithLabels('Mod+Shift+Z') +``` + +## `formatKeyForDebuggingDisplay` + +```ts +import { formatKeyForDebuggingDisplay } from '@tanstack/svelte-hotkeys' + +formatKeyForDebuggingDisplay('Meta') +formatKeyForDebuggingDisplay('Shift') +``` + +## Using Formatted Hotkeys in Svelte + +### Keyboard Shortcut Badges + +```svelte + + +{formatForDisplay(hotkey)} +``` + +### Menu Items with Hotkeys + +```svelte + + + +``` + +## Validation + +```ts +import { validateHotkey } from '@tanstack/svelte-hotkeys' + +const result = validateHotkey('Alt+A') +``` diff --git a/docs/framework/svelte/guides/hotkey-recording.md b/docs/framework/svelte/guides/hotkey-recording.md new file mode 100644 index 0000000..a143859 --- /dev/null +++ b/docs/framework/svelte/guides/hotkey-recording.md @@ -0,0 +1,78 @@ +--- +title: Hotkey Recording Guide +id: hotkey-recording +--- + +TanStack Hotkeys provides the `createHotkeyRecorder` function for building shortcut customization UIs in Svelte. + +## Basic Usage + +```svelte + + +
+ + {#if recorder.isRecording} + + {/if} +
+``` + +## Return Value + +- `isRecording`: whether recording is active +- `recordedHotkey`: the most recently recorded hotkey +- `startRecording()`: start listening for key presses +- `stopRecording()`: stop listening and keep the current recording +- `cancelRecording()`: stop listening and discard the in-progress recording + +## Options + +```ts +createHotkeyRecorder({ + onRecord: (hotkey) => {}, + onCancel: () => {}, + onClear: () => {}, +}) +``` + +Options can also be reactive: + +```svelte + +``` + +## Recording Behavior + +- Modifier-only presses do not complete a recording. +- Modifier plus key combinations record the full shortcut. +- Escape cancels recording. +- Backspace and Delete clear the shortcut. +- Recorded values are normalized to portable `Mod` format. diff --git a/docs/framework/svelte/guides/hotkeys.md b/docs/framework/svelte/guides/hotkeys.md new file mode 100644 index 0000000..88c4a7c --- /dev/null +++ b/docs/framework/svelte/guides/hotkeys.md @@ -0,0 +1,138 @@ +--- +title: Hotkeys Guide +id: hotkeys +--- + +Use `createHotkey` for global shortcuts and `createHotkeyAttachment` for element-scoped shortcuts. This keeps the common global case simple while making scoped behavior feel native to Svelte 5. + +## Global hotkeys + +```svelte + +``` + +The callback receives the original `KeyboardEvent` as the first argument and a `HotkeyCallbackContext` as the second: + +```ts +createHotkey('Mod+S', (event, context) => { + console.log(context.hotkey) + console.log(context.parsedHotkey) +}) +``` + +## Scoped hotkeys + +Use attachments instead of capturing an element ref just to pass it back into the API. + +```svelte + + +
Panel content
+``` + +## Reactive inputs + +Hotkeys can take plain values for static registrations or getter functions when the hotkey or options depend on reactive state. + +### Reactive `enabled` + +```svelte + +``` + +### Reactive hotkey values + +```svelte + +``` + +## Default options + +Set defaults explicitly with `setHotkeysContext` when a subtree needs shared behavior: + +```svelte + +``` + +## Common Options + +### `requireReset` + +```ts +createHotkey('Escape', () => closePanel(), { requireReset: true }) +``` + +### `ignoreInputs` + +```ts +createHotkey('K', () => openSearch()) +createHotkey('Enter', () => submit(), { ignoreInputs: false }) +``` + +### `conflictBehavior` + +```ts +createHotkey('Mod+S', () => save(), { conflictBehavior: 'replace' }) +``` + +### `platform` + +```ts +createHotkey('Mod+S', () => save(), { platform: 'mac' }) +``` + +## Automatic Cleanup + +Global hotkeys are automatically unregistered when the owning component unmounts. Attachment-based hotkeys clean themselves up when the attached element is removed or when reactive inputs change. + +## The Hotkey Manager + +You can always reach for the underlying manager directly: + +```ts +import { getHotkeyManager } from '@tanstack/svelte-hotkeys' + +const manager = getHotkeyManager() +manager.isRegistered('Mod+S') +manager.getRegistrationCount() +``` diff --git a/docs/framework/svelte/guides/key-state-tracking.md b/docs/framework/svelte/guides/key-state-tracking.md new file mode 100644 index 0000000..0aedcc0 --- /dev/null +++ b/docs/framework/svelte/guides/key-state-tracking.md @@ -0,0 +1,98 @@ +--- +title: Key State Tracking Guide +id: key-state-tracking +--- + +TanStack Hotkeys provides three Svelte functions for tracking live keyboard state: `getHeldKeys`, `getHeldKeyCodesMap`, and `getIsKeyHeld`. + +## `getHeldKeys` + +```svelte + + +
{heldKeys.keys.length > 0 ? heldKeys.keys.join(' + ') : 'No keys held'}
+``` + +## `getHeldKeyCodesMap` + +```svelte + + +
{JSON.stringify(heldCodes.codes, null, 2)}
+``` + +## `getIsKeyHeld` + +```svelte + + +Shift +``` + +## Common Patterns + +### Hold-to-Reveal UI + +```svelte + + +{#if isShiftHeld.held} + +{:else} + +{/if} +``` + +### Debugging Key Display + +```svelte + + +
+ {#each heldKeys.keys as key} + + {formatKeyForDebuggingDisplay(key)}: + {heldCodes.codes[key] + ? formatKeyForDebuggingDisplay(heldCodes.codes[key], { source: 'code' }) + : 'unknown'} + + {/each} +
+``` + +## Under the Hood + +All three functions subscribe to the singleton `KeyStateTracker`: + +```ts +import { getKeyStateTracker } from '@tanstack/svelte-hotkeys' + +const tracker = getKeyStateTracker() +tracker.getHeldKeys() +tracker.isKeyHeld('Shift') +``` diff --git a/docs/framework/svelte/guides/sequences.md b/docs/framework/svelte/guides/sequences.md new file mode 100644 index 0000000..2211644 --- /dev/null +++ b/docs/framework/svelte/guides/sequences.md @@ -0,0 +1,109 @@ +--- +title: Sequences Guide +id: sequences +--- + +TanStack Hotkeys supports multi-key sequences in Svelte, where keys are pressed one after another rather than simultaneously. + +## Global sequences + +```svelte + +``` + +## Scoped sequences + +Use `createHotkeySequenceAttachment` when a sequence should only be active while a specific element owns focus. + +```svelte + + +
+ Focus here, then press g then g +
+``` + +## Sequence options + +```ts +createHotkeySequence(['G', 'G'], callback, { + timeout: 1000, + enabled: true, +}) +``` + +### Reactive `enabled` + +```svelte + +``` + +## Default options + +```svelte + +``` + +## Common Patterns + +### Vim-Style Navigation + +```ts +createHotkeySequence(['G', 'G'], () => scrollToTop()) +createHotkeySequence(['G', 'Shift+G'], () => scrollToBottom()) +createHotkeySequence(['D', 'D'], () => deleteLine()) +createHotkeySequence(['D', 'W'], () => deleteWord()) +createHotkeySequence(['C', 'I', 'W'], () => changeInnerWord()) +``` + +### Konami Code + +```ts +createHotkeySequence( + ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'B', 'A'], + () => enableEasterEgg(), + { timeout: 2000 }, +) +``` + +## Under the Hood + +`createHotkeySequence` uses the singleton `SequenceManager`. You can also access it directly: + +```ts +import { + createSequenceMatcher, + getSequenceManager, +} from '@tanstack/svelte-hotkeys' + +const manager = getSequenceManager() +const matcher = createSequenceMatcher(['G', 'G'], { timeout: 1000 }) +``` diff --git a/docs/framework/svelte/quick-start.md b/docs/framework/svelte/quick-start.md new file mode 100644 index 0000000..44290bc --- /dev/null +++ b/docs/framework/svelte/quick-start.md @@ -0,0 +1,170 @@ +--- +title: Quick Start +id: quick-start +--- + +## Installation + +Don't have TanStack Hotkeys installed yet? See the [Installation](../../installation) page for instructions. + +## Your First Hotkey + +Use `createHotkey` for global shortcuts and attachments for element-scoped shortcuts. + +```svelte + + +
Press Cmd+S (Mac) or Ctrl+S (Windows) to save
+``` + +The `Mod` modifier automatically resolves to `Meta` (Command) on macOS and `Control` on Windows/Linux, so your shortcuts work across platforms without extra logic. + +## Common Patterns + +### Multiple global hotkeys + +```svelte + +``` + +### Scoped hotkeys with attachments + +```svelte + + +
+

Press Escape while focused here to close

+
+``` + +### Reactive options + +```svelte + +``` + +### Scoped sequences + +```svelte + + +
+ Focus here, then press g then g +
+``` + +### Tracking held keys + +```svelte + + +
+ {#if isShiftHeld.held}Shift mode active{/if} + {#if heldKeys.keys.length > 0} + Keys: {heldKeys.keys.join('+')} + {/if} +
+``` + +### Recording shortcuts + +```svelte + + + +``` + +### Displaying hotkeys in the UI + +```svelte + + + +``` + +## Default options + +Use `setHotkeysContext` when you want defaults for a subtree. This is an advanced API and usually belongs near the root of the part of the app that owns the hotkeys. + +```svelte + +``` + +## Next Steps + +- [Hotkeys Guide](./guides/hotkeys) +- [Sequences Guide](./guides/sequences) +- [Hotkey Recording Guide](./guides/hotkey-recording) +- [Key State Tracking Guide](./guides/key-state-tracking) +- [Formatting & Display Guide](./guides/formatting-display) diff --git a/docs/framework/svelte/reference/functions/createHotkey.md b/docs/framework/svelte/reference/functions/createHotkey.md new file mode 100644 index 0000000..323583c --- /dev/null +++ b/docs/framework/svelte/reference/functions/createHotkey.md @@ -0,0 +1,47 @@ +--- +id: createHotkey +title: createHotkey +--- + +# Function: createHotkey() + +```ts +function createHotkey( + hotkey, + callback, + options): void; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:70](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L70) + +Register a global hotkey for the current component. + +## Parameters + +### hotkey + +`MaybeGetter`\<`RegisterableHotkey`\> + +### callback + +`HotkeyCallback` + +### options + +`MaybeGetter`\<[`CreateHotkeyOptions`](../interfaces/CreateHotkeyOptions.md)\> = `{}` + +## Returns + +`void` + +## Example + +```svelte + +``` diff --git a/docs/framework/svelte/reference/functions/createHotkeyAttachment.md b/docs/framework/svelte/reference/functions/createHotkeyAttachment.md new file mode 100644 index 0000000..2b27ca3 --- /dev/null +++ b/docs/framework/svelte/reference/functions/createHotkeyAttachment.md @@ -0,0 +1,53 @@ +--- +id: createHotkeyAttachment +title: createHotkeyAttachment +--- + +# Function: createHotkeyAttachment() + +```ts +function createHotkeyAttachment( + hotkey, + callback, +options): Attachment; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:108](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L108) + +Create an attachment for element-scoped hotkeys. + +## Parameters + +### hotkey + +`MaybeGetter`\<`RegisterableHotkey`\> + +### callback + +`HotkeyCallback` + +### options + +`MaybeGetter`\<[`CreateHotkeyOptions`](../interfaces/CreateHotkeyOptions.md)\> = `{}` + +## Returns + +`Attachment`\<`HTMLElement`\> + +## Example + +```svelte + + +
+ Count: {count} +
+``` diff --git a/docs/framework/svelte/reference/functions/createHotkeyRecorder.md b/docs/framework/svelte/reference/functions/createHotkeyRecorder.md new file mode 100644 index 0000000..41e3db2 --- /dev/null +++ b/docs/framework/svelte/reference/functions/createHotkeyRecorder.md @@ -0,0 +1,22 @@ +--- +id: createHotkeyRecorder +title: createHotkeyRecorder +--- + +# Function: createHotkeyRecorder() + +```ts +function createHotkeyRecorder(options): SvelteHotkeyRecorder; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:98](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L98) + +## Parameters + +### options + +`MaybeGetter`\<`HotkeyRecorderOptions`\> + +## Returns + +[`SvelteHotkeyRecorder`](../interfaces/SvelteHotkeyRecorder.md) diff --git a/docs/framework/svelte/reference/functions/createHotkeySequence.md b/docs/framework/svelte/reference/functions/createHotkeySequence.md new file mode 100644 index 0000000..0fb3c55 --- /dev/null +++ b/docs/framework/svelte/reference/functions/createHotkeySequence.md @@ -0,0 +1,62 @@ +--- +id: createHotkeySequence +title: createHotkeySequence +--- + +# Function: createHotkeySequence() + +```ts +function createHotkeySequence( + sequence, + callback, + options): void; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:67](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L67) + +Register a global keyboard shortcut sequence for the current component. + +## Parameters + +### sequence + +`MaybeGetter`\<`HotkeySequence`\> + +### callback + +`HotkeyCallback` + +### options + +`MaybeGetter`\<[`CreateHotkeySequenceOptions`](../interfaces/CreateHotkeySequenceOptions.md)\> = `{}` + +## Returns + +`void` + +## Example + +```svelte + + +
+ .... +
+``` diff --git a/docs/framework/svelte/reference/functions/createHotkeySequenceAttachment.md b/docs/framework/svelte/reference/functions/createHotkeySequenceAttachment.md new file mode 100644 index 0000000..69a28c8 --- /dev/null +++ b/docs/framework/svelte/reference/functions/createHotkeySequenceAttachment.md @@ -0,0 +1,51 @@ +--- +id: createHotkeySequenceAttachment +title: createHotkeySequenceAttachment +--- + +# Function: createHotkeySequenceAttachment() + +```ts +function createHotkeySequenceAttachment( + sequence, + callback, +options): Attachment; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:113](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L113) + +Create an attachment for element-scoped keyboard sequences. + +## Parameters + +### sequence + +`MaybeGetter`\<`HotkeySequence`\> + +### callback + +`HotkeyCallback` + +### options + +`MaybeGetter`\<[`CreateHotkeySequenceOptions`](../interfaces/CreateHotkeySequenceOptions.md)\> = `{}` + +## Returns + +`Attachment`\<`HTMLElement`\> + +## Example + +```svelte + + +
+ Focus here and press g then g +
+``` diff --git a/docs/framework/svelte/reference/functions/getDefaultHotkeysOptions.md b/docs/framework/svelte/reference/functions/getDefaultHotkeysOptions.md new file mode 100644 index 0000000..e3db423 --- /dev/null +++ b/docs/framework/svelte/reference/functions/getDefaultHotkeysOptions.md @@ -0,0 +1,16 @@ +--- +id: getDefaultHotkeysOptions +title: getDefaultHotkeysOptions +--- + +# Function: getDefaultHotkeysOptions() + +```ts +function getDefaultHotkeysOptions(): HotkeysProviderOptions; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:55](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L55) + +## Returns + +[`HotkeysProviderOptions`](../interfaces/HotkeysProviderOptions.md) diff --git a/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md b/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md new file mode 100644 index 0000000..11767fc --- /dev/null +++ b/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md @@ -0,0 +1,39 @@ +--- +id: getHeldKeyCodesMap +title: getHeldKeyCodesMap +--- + +# Function: getHeldKeyCodesMap() + +```ts +function getHeldKeyCodesMap(): SvelteHeldKeyCodesMap; +``` + +Defined in: [packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts:42](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts#L42) + +Svelte function that returns reactive access to the map of currently held key names +to their physical `event.code` values. + +This is useful for debugging which physical key was pressed (e.g. distinguishing +left vs right Shift via "ShiftLeft" / "ShiftRight"). + +## Returns + +[`SvelteHeldKeyCodesMap`](../interfaces/SvelteHeldKeyCodesMap.md) + +Object with a reactive `codes` property + +```svelte + + +
+ {#each Object.entries(heldKeyCodesMap.codes) as [key, code]} + + {key} {code} + + {/each} +
+``` diff --git a/docs/framework/svelte/reference/functions/getHeldKeys.md b/docs/framework/svelte/reference/functions/getHeldKeys.md new file mode 100644 index 0000000..a50cd94 --- /dev/null +++ b/docs/framework/svelte/reference/functions/getHeldKeys.md @@ -0,0 +1,36 @@ +--- +id: getHeldKeys +title: getHeldKeys +--- + +# Function: getHeldKeys() + +```ts +function getHeldKeys(): SvelteHeldKeys; +``` + +Defined in: [packages/svelte-hotkeys/src/getHeldKeys.svelte.ts:38](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts#L38) + +Svelte function that returns reactive access to currently held keyboard keys. + +This function uses the global KeyStateTracker and updates whenever keys are pressed +or released. + +## Returns + +[`SvelteHeldKeys`](../interfaces/SvelteHeldKeys.md) + +Object with a reactive `keys` property + +## Example + +```svelte + +
+ Currently pressed: {heldKeys.keys.join(' + ') || 'None'} +
+``` diff --git a/docs/framework/svelte/reference/functions/getHotkeysContext.md b/docs/framework/svelte/reference/functions/getHotkeysContext.md new file mode 100644 index 0000000..bcefd64 --- /dev/null +++ b/docs/framework/svelte/reference/functions/getHotkeysContext.md @@ -0,0 +1,16 @@ +--- +id: getHotkeysContext +title: getHotkeysContext +--- + +# Function: getHotkeysContext() + +```ts +function getHotkeysContext(): HotkeysContextValue | null; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:47](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L47) + +## Returns + +`HotkeysContextValue` \| `null` diff --git a/docs/framework/svelte/reference/functions/getIsKeyHeld.md b/docs/framework/svelte/reference/functions/getIsKeyHeld.md new file mode 100644 index 0000000..5bf281a --- /dev/null +++ b/docs/framework/svelte/reference/functions/getIsKeyHeld.md @@ -0,0 +1,61 @@ +--- +id: getIsKeyHeld +title: getIsKeyHeld +--- + +# Function: getIsKeyHeld() + +```ts +function getIsKeyHeld(key): SvelteHeldKeyState; +``` + +Defined in: [packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts:66](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts#L66) + +Svelte function that returns reactive access to whether a specific key is currently being held. + +This function uses the global KeyStateTracker and updates whenever keys are pressed +or released. + +## Parameters + +### key + +`HeldKey` + +The key to check (e.g., 'Shift', 'Control', 'A') + +## Returns + +[`SvelteHeldKeyState`](../interfaces/SvelteHeldKeyState.md) + +Object with a reactive `held` property + +## Examples + +```svelte + + +
+ {isShiftHeld.held ? 'Shift is pressed!' : 'Press Shift'} +
+``` + +```svelte + + +
+ Ctrl + Shift + Alt +
+``` diff --git a/docs/framework/svelte/reference/functions/setHotkeysContext.md b/docs/framework/svelte/reference/functions/setHotkeysContext.md new file mode 100644 index 0000000..a778858 --- /dev/null +++ b/docs/framework/svelte/reference/functions/setHotkeysContext.md @@ -0,0 +1,22 @@ +--- +id: setHotkeysContext +title: setHotkeysContext +--- + +# Function: setHotkeysContext() + +```ts +function setHotkeysContext(defaultOptions): HotkeysContextValue; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:27](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L27) + +## Parameters + +### defaultOptions + +[`HotkeysProviderOptions`](../interfaces/HotkeysProviderOptions.md) = `DEFAULT_OPTIONS` + +## Returns + +`HotkeysContextValue` diff --git a/docs/framework/svelte/reference/index.md b/docs/framework/svelte/reference/index.md new file mode 100644 index 0000000..d9e09c8 --- /dev/null +++ b/docs/framework/svelte/reference/index.md @@ -0,0 +1,40 @@ +--- +id: "@tanstack/svelte-hotkeys" +title: "@tanstack/svelte-hotkeys" +--- + +# @tanstack/svelte-hotkeys + +## Interfaces + +- [CreateHotkeyOptions](interfaces/CreateHotkeyOptions.md) +- [CreateHotkeySequenceOptions](interfaces/CreateHotkeySequenceOptions.md) +- [HotkeysProviderOptions](interfaces/HotkeysProviderOptions.md) +- [HotkeysProviderProps](interfaces/HotkeysProviderProps.md) +- [SvelteHeldKeyCodesMap](interfaces/SvelteHeldKeyCodesMap.md) +- [SvelteHeldKeys](interfaces/SvelteHeldKeys.md) +- [SvelteHeldKeyState](interfaces/SvelteHeldKeyState.md) +- [SvelteHotkeyRecorder](interfaces/SvelteHotkeyRecorder.md) + +## Type Aliases + +- [HotkeysProvider](type-aliases/HotkeysProvider.md) + +## Variables + +- [DEFAULT\_OPTIONS](variables/DEFAULT_OPTIONS.md) +- [HotkeysProvider](variables/HotkeysProvider.md) + +## Functions + +- [createHotkey](functions/createHotkey.md) +- [createHotkeyAttachment](functions/createHotkeyAttachment.md) +- [createHotkeyRecorder](functions/createHotkeyRecorder.md) +- [createHotkeySequence](functions/createHotkeySequence.md) +- [createHotkeySequenceAttachment](functions/createHotkeySequenceAttachment.md) +- [getDefaultHotkeysOptions](functions/getDefaultHotkeysOptions.md) +- [getHeldKeyCodesMap](functions/getHeldKeyCodesMap.md) +- [getHeldKeys](functions/getHeldKeys.md) +- [getHotkeysContext](functions/getHotkeysContext.md) +- [getIsKeyHeld](functions/getIsKeyHeld.md) +- [setHotkeysContext](functions/setHotkeysContext.md) diff --git a/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md b/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md new file mode 100644 index 0000000..142abc9 --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md @@ -0,0 +1,22 @@ +--- +id: CreateHotkeyOptions +title: CreateHotkeyOptions +--- + +# Interface: CreateHotkeyOptions + +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:18](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L18) + +## Extends + +- `Omit`\<`HotkeyOptions`, `"target"`\> + +## Properties + +### target? + +```ts +optional target: Document | Window; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:19](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L19) diff --git a/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md b/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md new file mode 100644 index 0000000..e25631a --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md @@ -0,0 +1,22 @@ +--- +id: CreateHotkeySequenceOptions +title: CreateHotkeySequenceOptions +--- + +# Interface: CreateHotkeySequenceOptions + +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:12](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L12) + +## Extends + +- `Omit`\<`SequenceOptions`, `"target"`\> + +## Properties + +### target? + +```ts +optional target: Document | Window; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:16](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L16) diff --git a/docs/framework/svelte/reference/interfaces/HotkeysProviderOptions.md b/docs/framework/svelte/reference/interfaces/HotkeysProviderOptions.md new file mode 100644 index 0000000..4716f04 --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/HotkeysProviderOptions.md @@ -0,0 +1,38 @@ +--- +id: HotkeysProviderOptions +title: HotkeysProviderOptions +--- + +# Interface: HotkeysProviderOptions + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:7](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L7) + +## Properties + +### hotkey? + +```ts +optional hotkey: Partial; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:8](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L8) + +*** + +### hotkeyRecorder? + +```ts +optional hotkeyRecorder: Partial; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:9](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L9) + +*** + +### hotkeySequence? + +```ts +optional hotkeySequence: Partial; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:10](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L10) diff --git a/docs/framework/svelte/reference/interfaces/HotkeysProviderProps.md b/docs/framework/svelte/reference/interfaces/HotkeysProviderProps.md new file mode 100644 index 0000000..6a02782 --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/HotkeysProviderProps.md @@ -0,0 +1,28 @@ +--- +id: HotkeysProviderProps +title: HotkeysProviderProps +--- + +# Interface: HotkeysProviderProps + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:13](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L13) + +## Properties + +### children + +```ts +children: Snippet; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:14](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L14) + +*** + +### defaultOptions? + +```ts +optional defaultOptions: HotkeysProviderOptions; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:15](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L15) diff --git a/docs/framework/svelte/reference/interfaces/SvelteHeldKeyCodesMap.md b/docs/framework/svelte/reference/interfaces/SvelteHeldKeyCodesMap.md new file mode 100644 index 0000000..dfa7564 --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/SvelteHeldKeyCodesMap.md @@ -0,0 +1,18 @@ +--- +id: SvelteHeldKeyCodesMap +title: SvelteHeldKeyCodesMap +--- + +# Interface: SvelteHeldKeyCodesMap + +Defined in: [packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts:4](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts#L4) + +## Properties + +### codes + +```ts +readonly codes: Record; +``` + +Defined in: [packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts:5](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts#L5) diff --git a/docs/framework/svelte/reference/interfaces/SvelteHeldKeyState.md b/docs/framework/svelte/reference/interfaces/SvelteHeldKeyState.md new file mode 100644 index 0000000..31ecd19 --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/SvelteHeldKeyState.md @@ -0,0 +1,18 @@ +--- +id: SvelteHeldKeyState +title: SvelteHeldKeyState +--- + +# Interface: SvelteHeldKeyState + +Defined in: [packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts:5](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts#L5) + +## Properties + +### held + +```ts +readonly held: boolean; +``` + +Defined in: [packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts:6](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts#L6) diff --git a/docs/framework/svelte/reference/interfaces/SvelteHeldKeys.md b/docs/framework/svelte/reference/interfaces/SvelteHeldKeys.md new file mode 100644 index 0000000..259e130 --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/SvelteHeldKeys.md @@ -0,0 +1,18 @@ +--- +id: SvelteHeldKeys +title: SvelteHeldKeys +--- + +# Interface: SvelteHeldKeys + +Defined in: [packages/svelte-hotkeys/src/getHeldKeys.svelte.ts:4](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts#L4) + +## Properties + +### keys + +```ts +readonly keys: string[]; +``` + +Defined in: [packages/svelte-hotkeys/src/getHeldKeys.svelte.ts:5](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts#L5) diff --git a/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md b/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md new file mode 100644 index 0000000..a2e7f44 --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md @@ -0,0 +1,80 @@ +--- +id: SvelteHotkeyRecorder +title: SvelteHotkeyRecorder +--- + +# Interface: SvelteHotkeyRecorder + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:8](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L8) + +## Properties + +### cancelRecording() + +```ts +cancelRecording: () => void; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:18](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L18) + +Cancel recording without saving + +#### Returns + +`void` + +*** + +### isRecording + +```ts +readonly isRecording: boolean; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:10](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L10) + +Whether recording is currently active + +*** + +### recordedHotkey + +```ts +readonly recordedHotkey: Hotkey | null; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:12](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L12) + +The currently recorded hotkey (for live preview) + +*** + +### startRecording() + +```ts +startRecording: () => void; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:14](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L14) + +Start recording a new hotkey + +#### Returns + +`void` + +*** + +### stopRecording() + +```ts +stopRecording: () => void; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:16](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L16) + +Stop recording (same as cancel) + +#### Returns + +`void` diff --git a/docs/framework/svelte/reference/type-aliases/HotkeysProvider.md b/docs/framework/svelte/reference/type-aliases/HotkeysProvider.md new file mode 100644 index 0000000..a35a88d --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/HotkeysProvider.md @@ -0,0 +1,12 @@ +--- +id: HotkeysProvider +title: HotkeysProvider +--- + +# Type Alias: HotkeysProvider + +```ts +type HotkeysProvider = SvelteComponent; +``` + +Defined in: node\_modules/.pnpm/svelte@5.53.7/node\_modules/svelte/types/index.d.ts:3195 diff --git a/docs/framework/svelte/reference/variables/DEFAULT_OPTIONS.md b/docs/framework/svelte/reference/variables/DEFAULT_OPTIONS.md new file mode 100644 index 0000000..0d1171c --- /dev/null +++ b/docs/framework/svelte/reference/variables/DEFAULT_OPTIONS.md @@ -0,0 +1,12 @@ +--- +id: DEFAULT_OPTIONS +title: DEFAULT_OPTIONS +--- + +# Variable: DEFAULT\_OPTIONS + +```ts +const DEFAULT_OPTIONS: HotkeysProviderOptions = {}; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:18](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L18) diff --git a/docs/framework/svelte/reference/variables/HotkeysProvider.md b/docs/framework/svelte/reference/variables/HotkeysProvider.md new file mode 100644 index 0000000..bc71bdd --- /dev/null +++ b/docs/framework/svelte/reference/variables/HotkeysProvider.md @@ -0,0 +1,12 @@ +--- +id: HotkeysProvider +title: HotkeysProvider +--- + +# Variable: HotkeysProvider + +```ts +const HotkeysProvider: LegacyComponentType; +``` + +Defined in: node\_modules/.pnpm/svelte@5.53.7/node\_modules/svelte/types/index.d.ts:3195 diff --git a/examples/svelte/create-hotkey-recorder/.gitignore b/examples/svelte/create-hotkey-recorder/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/examples/svelte/create-hotkey-recorder/.npmrc b/examples/svelte/create-hotkey-recorder/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/examples/svelte/create-hotkey-recorder/index.html b/examples/svelte/create-hotkey-recorder/index.html new file mode 100644 index 0000000..1d26e1a --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/index.html @@ -0,0 +1,14 @@ + + + + + + + createHotkeyRecorder - TanStack Hotkeys Svelte Example + + + +
+ + + diff --git a/examples/svelte/create-hotkey-recorder/package.json b/examples/svelte/create-hotkey-recorder/package.json new file mode 100644 index 0000000..ce7942a --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/package.json @@ -0,0 +1,17 @@ +{ + "name": "@tanstack/hotkeys-example-svelte-create-hotkey-recorder", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3069" + }, + "dependencies": { + "@tanstack/svelte-hotkeys": "0.4.1", + "svelte": "^5.53.7" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "typescript": "5.9.3", + "vite": "^7.3.1" + } +} diff --git a/examples/svelte/create-hotkey-recorder/src/App.svelte b/examples/svelte/create-hotkey-recorder/src/App.svelte new file mode 100644 index 0000000..8c0d819 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/App.svelte @@ -0,0 +1,262 @@ + + +
+
+

Keyboard Shortcuts Settings

+

+ Customize your keyboard shortcuts. Click "Edit" to record a new shortcut, + or press Escape to cancel. +

+
+ +
+
+

Shortcuts

+
+ {#each Object.entries(DEFAULT_SHORTCUT_ACTIONS) as [actionId, action]} + handleEdit(actionId)} + onCancel={handleCancel} + /> + {/each} +
+
+ +
+

Demo Actions

+

Try your shortcuts! Actions will trigger when you press them.

+
+
+
Save
+
{saveCount}
+ {formatForDisplay(shortcuts.save || 'Mod+K')} +
+
+
Open
+
{openCount}
+ {formatForDisplay(shortcuts.open || 'Mod+E')} +
+
+
New
+
{newCount}
+ {formatForDisplay(shortcuts.new || 'Mod+G')} +
+
+
Close
+
{closeCount}
+ {formatForDisplay(shortcuts.close || 'Mod+Shift+K')} +
+
+
Undo
+
{undoCount}
+ {formatForDisplay(shortcuts.undo || 'Mod+Shift+E')} +
+
+
Redo
+
{redoCount}
+ {formatForDisplay(shortcuts.redo || 'Mod+Shift+G')} +
+
+
+ + {#if recorder.isRecording} +
+ Recording shortcut... Press any key combination or Escape + to cancel. Press Backspace/Delete to clear the shortcut. +
+ {/if} + +
+

Usage

+
{`import {
+  createHotkey,
+  createHotkeyRecorder,
+  formatForDisplay,
+} from '@tanstack/svelte-hotkeys'
+
+let shortcuts = $state({
+  save: 'Mod+K',
+  open: 'Mod+E',
+})
+
+const recorder = createHotkeyRecorder({
+  onRecord: (hotkey) => {
+    shortcuts = { ...shortcuts, save: hotkey }
+  },
+})
+
+// Register shortcuts dynamically
+createHotkey(
+  () => shortcuts.save,
+  () => handleSave(),
+  () => ({ enabled: !recorder.isRecording })
+)
+
+// In template:
+// {formatForDisplay(shortcuts.save)}`}
+
+
+
diff --git a/examples/svelte/create-hotkey-recorder/src/Root.svelte b/examples/svelte/create-hotkey-recorder/src/Root.svelte new file mode 100644 index 0000000..12e94e9 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/Root.svelte @@ -0,0 +1,5 @@ + + + diff --git a/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte b/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte new file mode 100644 index 0000000..c34268d --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte @@ -0,0 +1,51 @@ + + +
+
+
{actionName}
+
+ {#if isRecording} +
+ {#if heldKeys.keys.length > 0} +
+ {#each heldKeys.keys as key, index} + {#if index > 0} + + + {/if} + {key} + {/each} +
+ {:else} + Press any key combination... + {/if} +
+ {:else if hotkey} + {formatForDisplay(hotkey as Hotkey)} + {:else} + No shortcut + {/if} +
+
+
+ {#if isRecording} + + {:else} + + {/if} +
+
diff --git a/examples/svelte/create-hotkey-recorder/src/index.css b/examples/svelte/create-hotkey-recorder/src/index.css new file mode 100644 index 0000000..77dfb2f --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/index.css @@ -0,0 +1,256 @@ +* { + box-sizing: border-box; +} +body { + margin: 0; + font-family: + system-ui, + -apple-system, + sans-serif; + background: #f5f5f5; + color: #333; +} +.app { + max-width: 900px; + margin: 0 auto; + padding: 20px; +} +header { + text-align: center; + margin-bottom: 40px; +} +header h1 { + margin: 0 0 10px; + color: #0066cc; +} +header p { + color: #666; + margin: 0; + max-width: 600px; + margin: 0 auto; +} +.demo-section { + background: white; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} +.demo-section h2 { + margin: 0 0 16px; + font-size: 20px; +} +.demo-section p { + margin: 0 0 16px; +} +kbd { + background: linear-gradient(180deg, #f8f8f8 0%, #e8e8e8 100%); + border: 1px solid #ccc; + border-bottom-width: 2px; + border-radius: 4px; + padding: 2px 8px; + font-family: monospace; + font-size: 13px; +} +button { + background: #0066cc; + color: white; + border: none; + padding: 8px 16px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + transition: background 0.2s; +} +button:hover { + background: #0052a3; +} +button:active { + background: #004080; +} +.cancel-button { + background: #dc3545; +} +.cancel-button:hover { + background: #c82333; +} +.edit-button { + background: #28a745; +} +.edit-button:hover { + background: #218838; +} +.code-block { + background: #1e1e1e; + color: #d4d4d4; + padding: 16px; + border-radius: 8px; + overflow-x: auto; + font-size: 13px; + line-height: 1.5; + margin-top: 16px; +} +.info-box { + background: #e3f2fd; + border-radius: 8px; + padding: 12px 16px; + margin: 20px 0; +} +.recording-notice { + background: #fff3cd; + border: 2px solid #ffc107; + animation: pulse 2s ease-in-out infinite; +} +@keyframes pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.8; + } +} + +/* Shortcuts List */ +.shortcuts-list { + display: flex; + flex-direction: column; + gap: 12px; +} +.shortcut-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + background: #f8f9fa; + border: 2px solid transparent; + border-radius: 8px; + transition: all 0.2s; +} +.shortcut-item:hover { + background: #f0f0f0; +} +.shortcut-item.recording { + background: #fff3cd; + border-color: #ffc107; + box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.2); + animation: recordingPulse 1.5s ease-in-out infinite; +} +@keyframes recordingPulse { + 0%, + 100% { + box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.2); + } + 50% { + box-shadow: 0 0 0 6px rgba(255, 193, 7, 0.1); + } +} +.shortcut-item-content { + display: flex; + align-items: center; + gap: 24px; + flex: 1; +} +.shortcut-action { + font-weight: 500; + min-width: 80px; + font-size: 15px; +} +.shortcut-hotkey { + display: flex; + align-items: center; + min-height: 32px; +} +.shortcut-hotkey kbd { + font-size: 14px; +} +.no-shortcut { + color: #999; + font-style: italic; + font-size: 14px; +} +.shortcut-actions { + display: flex; + gap: 8px; +} + +/* Recording Indicator */ +.recording-indicator { + display: flex; + align-items: center; + gap: 8px; +} +.recording-text { + color: #856404; + font-style: italic; + font-size: 14px; +} +.held-hotkeys { + display: flex; + align-items: center; + gap: 4px; +} +.held-hotkeys .plus { + color: #856404; + font-size: 16px; + margin: 0 4px; +} +.held-hotkeys kbd { + background: #ffc107; + border-color: #ff9800; + color: #856404; + font-weight: 600; +} + +/* Demo Stats */ +.demo-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 16px; + margin-top: 20px; +} +.stat-item { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px; + background: #f8f9fa; + border-radius: 8px; + gap: 8px; +} +.stat-label { + font-size: 13px; + color: #666; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.stat-value { + font-size: 32px; + font-weight: bold; + color: #0066cc; +} +.stat-item kbd { + margin-top: 4px; +} + +/* Responsive */ +@media (max-width: 600px) { + .shortcut-item { + flex-direction: column; + align-items: flex-start; + gap: 12px; + } + .shortcut-item-content { + width: 100%; + flex-direction: column; + align-items: flex-start; + gap: 8px; + } + .shortcut-actions { + width: 100%; + justify-content: flex-end; + } + .demo-stats { + grid-template-columns: repeat(2, 1fr); + } +} diff --git a/examples/svelte/create-hotkey-recorder/src/main.ts b/examples/svelte/create-hotkey-recorder/src/main.ts new file mode 100644 index 0000000..9357900 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/main.ts @@ -0,0 +1,5 @@ +import { mount } from 'svelte' +import Root from './Root.svelte' +import './index.css' + +mount(Root, { target: document.getElementById('app')! }) diff --git a/examples/svelte/create-hotkey-recorder/static/robots.txt b/examples/svelte/create-hotkey-recorder/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/examples/svelte/create-hotkey-recorder/svelte.config.js b/examples/svelte/create-hotkey-recorder/svelte.config.js new file mode 100644 index 0000000..b30b657 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/svelte.config.js @@ -0,0 +1,11 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import('svelte').Config} */ +const config = { + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, + }, +} + +export default config diff --git a/examples/svelte/create-hotkey-recorder/tsconfig.json b/examples/svelte/create-hotkey-recorder/tsconfig.json new file mode 100644 index 0000000..912a030 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["eslint.config.js"] +} diff --git a/examples/svelte/create-hotkey-recorder/vite.config.ts b/examples/svelte/create-hotkey-recorder/vite.config.ts new file mode 100644 index 0000000..951a9ba --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/svelte/create-hotkey-sequence/.gitignore b/examples/svelte/create-hotkey-sequence/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/examples/svelte/create-hotkey-sequence/.npmrc b/examples/svelte/create-hotkey-sequence/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/examples/svelte/create-hotkey-sequence/README.md b/examples/svelte/create-hotkey-sequence/README.md new file mode 100644 index 0000000..57b7713 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/README.md @@ -0,0 +1,42 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project +npx sv create my-app +``` + +To recreate this project with the same configuration: + +```sh +# recreate this project +pnpm dlx sv create --template minimal --types ts --install pnpm create-hotkey +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/examples/svelte/create-hotkey-sequence/index.html b/examples/svelte/create-hotkey-sequence/index.html new file mode 100644 index 0000000..3e93ebb --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/index.html @@ -0,0 +1,14 @@ + + + + + + + createHotkeySequence - TanStack Hotkeys Svelte Example + + + +
+ + + diff --git a/examples/svelte/create-hotkey-sequence/package.json b/examples/svelte/create-hotkey-sequence/package.json new file mode 100644 index 0000000..436c6bb --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/package.json @@ -0,0 +1,17 @@ +{ + "name": "@tanstack/hotkeys-example-svelte-create-hotkey-sequence", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3069" + }, + "dependencies": { + "@tanstack/svelte-hotkeys": "0.4.1", + "svelte": "^5.53.7" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "typescript": "5.9.3", + "vite": "^7.3.1" + } +} diff --git a/examples/svelte/create-hotkey-sequence/src/App.svelte b/examples/svelte/create-hotkey-sequence/src/App.svelte new file mode 100644 index 0000000..f28fcd1 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/src/App.svelte @@ -0,0 +1,200 @@ + + +
+
+

createHotkeySequence

+

+ Register multi-key sequences (like Vim commands). Keys must be pressed + within the timeout window (default: 1000ms). +

+
+ +
+
+

Vim-Style Commands

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SequenceAction
+ g g + Go to top
+ G (Shift+G) + Go to bottom
+ d d + Delete line
+ y y + Yank (copy) line
+ d w + Delete word
+ c i w + Change inner word
+
+ +
+

Fun Sequences

+
+
+

Konami Code (Partial)

+

+ +

+ Use arrow keys within 1.5 seconds +
+
+

Side to Side

+

+ +

+ Arrow keys within 1.5 seconds +
+
+

Spell It Out

+

+ h e l l o +

+ Type "hello" quickly +
+
+
+ + {#if lastSequence} +
+ Triggered: + {lastSequence} +
+ {/if} + +
+

Input handling

+

+ Sequences are not detected when typing in text inputs, textareas, + selects, or contenteditable elements. Button-type inputs ( + type="button", submit, reset) + still receive sequences. Focus the input below and try g + g or h + e + l + l + o — nothing will trigger. Click outside to try again. +

+ +
+ +
+

Usage

+
{`import { createHotkeySequence } from '@tanstack/svelte-hotkeys'
+
+`}
+
+ + {#if history.length > 0} +
+

History

+
    + {#each history as item} +
  • {item}
  • + {/each} +
+ +
+ {/if} + +

+ Press Escape to clear history +

+
+
diff --git a/examples/svelte/create-hotkey-sequence/src/Root.svelte b/examples/svelte/create-hotkey-sequence/src/Root.svelte new file mode 100644 index 0000000..12e94e9 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/src/Root.svelte @@ -0,0 +1,5 @@ + + + diff --git a/examples/svelte/create-hotkey-sequence/src/index.css b/examples/svelte/create-hotkey-sequence/src/index.css new file mode 100644 index 0000000..8ee4387 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/src/index.css @@ -0,0 +1,162 @@ +* { + box-sizing: border-box; +} +body { + margin: 0; + font-family: + system-ui, + -apple-system, + sans-serif; + background: #f5f5f5; + color: #333; +} +.app { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} +header { + text-align: center; + margin-bottom: 40px; +} +header h1 { + margin: 0 0 10px; + color: #0066cc; +} +header p { + color: #666; + margin: 0; + max-width: 500px; + margin: 0 auto; +} + +.demo-section { + background: white; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} +.demo-section h2 { + margin: 0 0 16px; + font-size: 20px; +} +.demo-section p { + margin: 0 0 12px; +} +kbd { + background: linear-gradient(180deg, #f8f8f8 0%, #e8e8e8 100%); + border: 1px solid #ccc; + border-bottom-width: 2px; + border-radius: 4px; + padding: 2px 8px; + font-family: monospace; + font-size: 13px; + margin-right: 4px; +} +.sequence-table { + width: 100%; + border-collapse: collapse; +} +.sequence-table th, +.sequence-table td { + padding: 12px; + text-align: left; + border-bottom: 1px solid #eee; +} +.sequence-table th { + font-weight: 600; + color: #666; + font-size: 14px; +} +.fun-sequences { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; +} +.sequence-card { + background: #f8f9fa; + border-radius: 8px; + padding: 16px; + text-align: center; +} +.sequence-card h3 { + margin: 0 0 12px; + font-size: 16px; +} +.sequence-card p { + margin: 0 0 8px; +} +.hint { + font-size: 12px; + color: #888; + font-style: italic; +} +.info-box { + background: #e3f2fd; + border-radius: 8px; + padding: 16px 20px; + margin-bottom: 24px; + font-size: 18px; +} +.info-box.success { + background: #e8f5e9; + color: #2e7d32; +} +.code-block { + background: #1e1e1e; + color: #d4d4d4; + padding: 16px; + border-radius: 8px; + overflow-x: auto; + font-size: 13px; + line-height: 1.5; +} +.history-list { + list-style: none; + padding: 0; + margin: 0 0 16px; +} +.history-list li { + padding: 10px 14px; + background: #f0f0f0; + border-radius: 6px; + margin-bottom: 6px; + font-family: monospace; + font-size: 14px; +} +button { + background: #0066cc; + color: white; + border: none; + padding: 10px 20px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; +} +button:hover { + background: #0052a3; +} + +.counter { + font-size: 18px; + font-weight: bold; + color: #0066cc; + margin: 12px 0; +} + +.demo-input { + width: 100%; + max-width: 400px; + padding: 12px 16px; + font-size: 14px; + border: 1px solid #ddd; + border-radius: 6px; + margin-top: 8px; +} + +.demo-input:focus { + outline: 2px solid #0066cc; + outline-offset: 2px; + border-color: #0066cc; +} diff --git a/examples/svelte/create-hotkey-sequence/src/main.ts b/examples/svelte/create-hotkey-sequence/src/main.ts new file mode 100644 index 0000000..9357900 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/src/main.ts @@ -0,0 +1,5 @@ +import { mount } from 'svelte' +import Root from './Root.svelte' +import './index.css' + +mount(Root, { target: document.getElementById('app')! }) diff --git a/examples/svelte/create-hotkey-sequence/static/robots.txt b/examples/svelte/create-hotkey-sequence/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/examples/svelte/create-hotkey-sequence/svelte.config.js b/examples/svelte/create-hotkey-sequence/svelte.config.js new file mode 100644 index 0000000..b30b657 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/svelte.config.js @@ -0,0 +1,11 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import('svelte').Config} */ +const config = { + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, + }, +} + +export default config diff --git a/examples/svelte/create-hotkey-sequence/tsconfig.json b/examples/svelte/create-hotkey-sequence/tsconfig.json new file mode 100644 index 0000000..912a030 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["eslint.config.js"] +} diff --git a/examples/svelte/create-hotkey-sequence/vite.config.ts b/examples/svelte/create-hotkey-sequence/vite.config.ts new file mode 100644 index 0000000..951a9ba --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/svelte/create-hotkey/.gitignore b/examples/svelte/create-hotkey/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/examples/svelte/create-hotkey/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/examples/svelte/create-hotkey/.npmrc b/examples/svelte/create-hotkey/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/examples/svelte/create-hotkey/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/examples/svelte/create-hotkey/index.html b/examples/svelte/create-hotkey/index.html new file mode 100644 index 0000000..42f0210 --- /dev/null +++ b/examples/svelte/create-hotkey/index.html @@ -0,0 +1,14 @@ + + + + + + + createHotkey - TanStack Hotkeys Svelte Example + + + +
+ + + diff --git a/examples/svelte/create-hotkey/package.json b/examples/svelte/create-hotkey/package.json new file mode 100644 index 0000000..9f52691 --- /dev/null +++ b/examples/svelte/create-hotkey/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/hotkeys-example-svelte-create-hotkey", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3069", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/svelte-hotkeys": "0.4.1", + "svelte": "^5.53.7" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "typescript": "5.9.3", + "vite": "^7.3.1" + } +} diff --git a/examples/svelte/create-hotkey/src/App.svelte b/examples/svelte/create-hotkey/src/App.svelte new file mode 100644 index 0000000..c31ccde --- /dev/null +++ b/examples/svelte/create-hotkey/src/App.svelte @@ -0,0 +1,549 @@ + + +
+
+

createHotkey

+

+ Register keyboard shortcuts with callback context containing the hotkey + and parsed hotkey information. +

+
+ +
+
+

Basic Hotkey

+

+ Press {formatForDisplay('Mod+S')} to trigger +

+
Save triggered: {saveCount}x
+
{`createHotkey('Mod+S', (_event, { hotkey, parsedHotkey }) => {
+  console.log('Hotkey:', hotkey)
+  console.log('Parsed:', parsedHotkey)
+})`}
+
+ +
+

With requireReset

+

+ Hold {formatForDisplay('Mod+K')} — only increments once until you + release all keys +

+
Increment: {incrementCount}
+

+ This prevents repeated triggering while holding the keys down. Release + all keys to allow re-triggering. +

+
{`createHotkey(
+  'Mod+K',
+  (event, { hotkey }) => {
+    count++
+  },
+  { requireReset: true }
+)`}
+
+ +
+

Conditional Hotkey

+

+ {formatForDisplay('Mod+E')} is currently + {enabled ? 'enabled' : 'disabled'} +

+ +
{`let enabled = $state(true)
+
+createHotkey(
+  'Mod+E',
+  (event, { hotkey }) => {
+    alert('Triggered!')
+  },
+  () => ({ enabled })
+)`}
+
+ +
+

Number Key Combinations

+

Common for tab/section switching:

+
+
{formatForDisplay('Mod+1')} → Tab 1
+
{formatForDisplay('Mod+2')} → Tab 2
+
{formatForDisplay('Mod+3')} → Tab 3
+
{formatForDisplay('Mod+4')} → Tab 4
+
{formatForDisplay('Mod+5')} → Tab 5
+
+
Active Tab: {activeTab}
+
{`createHotkey('Mod+1', () => activeTab = 1)
+createHotkey('Mod+2', () => activeTab = 2)`}
+
+ +
+

Navigation Key Combinations

+

Selection and navigation shortcuts:

+
+
{formatForDisplay('Shift+ArrowUp')} — Select up
+
+ {formatForDisplay('Shift+ArrowDown')} — Select down +
+
+ {formatForDisplay('Alt+ArrowLeft')} — Navigate back +
+
+ {formatForDisplay('Alt+ArrowRight')} — Navigate forward +
+
{formatForDisplay('Mod+Home')} — Go to start
+
{formatForDisplay('Mod+End')} — Go to end
+
+ {formatForDisplay('Control+PageUp')} — Previous page +
+
+ {formatForDisplay('Control+PageDown')} — Next page +
+
+
Navigation triggered: {navigationCount}x
+
+ +
+

Function Key Combinations

+

System and application shortcuts:

+
+
{formatForDisplay('Alt+F4')} — Close window
+
{formatForDisplay('Control+F5')} — Hard refresh
+
{formatForDisplay('Mod+F1')} — Help
+
{formatForDisplay('Shift+F10')} — Context menu
+
{formatForDisplay('F12')} — DevTools
+
+
Function keys triggered: {functionKeyCount}x
+
+ +
+

Multi-Modifier Combinations

+

Complex shortcuts with multiple modifiers:

+
+
{formatForDisplay('Mod+Shift+S')} — Save As
+
{formatForDisplay('Mod+Shift+Z')} — Redo
+
+ {formatForDisplay('Control+Alt+A')} — Special action +
+
+ {formatForDisplay('Control+Shift+N')} — New incognito +
+
{formatForDisplay('Mod+Alt+T')} — Toggle theme
+
+ {formatForDisplay('Control+Alt+Shift+X')} — Triple modifier +
+
+
+ Multi-modifier triggered: {multiModifierCount}x +
+
+ +
+

Editing Key Combinations

+

Text editing and form shortcuts:

+
+
{formatForDisplay('Mod+Enter')} — Submit form
+
{formatForDisplay('Shift+Enter')} — New line
+
+ {formatForDisplay('Mod+Backspace')} — Delete word +
+
+ {formatForDisplay('Mod+Delete')} — Delete forward +
+
{formatForDisplay('Control+Tab')} — Next tab
+
{formatForDisplay('Shift+Tab')} — Previous field
+
{formatForDisplay('Mod+Space')} — Toggle
+
+
Editing keys triggered: {editingKeyCount}x
+
+ + {#if lastHotkey} +
+ Last triggered: + {formatForDisplay(lastHotkey)} +
+ {/if} + +

+ Press Escape to reset all counters +

+ +
+

Scoped Keyboard Shortcuts

+

+ Shortcuts can be scoped to specific DOM elements using the + target option. This allows different shortcuts to work in different + parts of your application. +

+ +
+ +
+

Sidebar (Scoped Area)

+

Click here to focus, then try:

+
+
+ {formatForDisplay('Mod+B')} — Trigger sidebar action +
+
{formatForDisplay('Mod+N')} — New item
+
+
+ Sidebar shortcuts: {sidebarShortcutCount}x +
+

+ These shortcuts only work when this sidebar area is focused or + contains focus. +

+
+ +
+

Modal Dialog

+ + {#if modalOpen} + + + {/if} +
+ +
+

Text Editor (Scoped)

+

Focus the editor below and try:

+
+
+ {formatForDisplay('Mod+S')} — Save editor content +
+
{formatForDisplay('Mod+/')} — Add comment
+
{formatForDisplay('Mod+K')} — Clear editor
+
+ +
Editor shortcuts: {editorShortcutCount}x
+

+ These shortcuts only work when the editor is focused. Notice that {formatForDisplay('Mod+S')} + here doesn't conflict with the global + {formatForDisplay('Mod+S')} + shortcut. +

+
+
+ +
{`const sidebarHotkey = createHotkeyAttachment(
+  'Mod+B',
+  () => console.log('Sidebar shortcut!'),
+)
+
+const modalEscape = createHotkeyAttachment(
+  'Escape',
+  () => (modalOpen = false),
+  () => ({ enabled: modalOpen }),
+)
+
+const editorSave = createHotkeyAttachment('Mod+S', () => {
+  saveEditorContent()
+})`}
+
+
+
diff --git a/examples/svelte/create-hotkey/src/Root.svelte b/examples/svelte/create-hotkey/src/Root.svelte new file mode 100644 index 0000000..12e94e9 --- /dev/null +++ b/examples/svelte/create-hotkey/src/Root.svelte @@ -0,0 +1,5 @@ + + + diff --git a/examples/svelte/create-hotkey/src/index.css b/examples/svelte/create-hotkey/src/index.css new file mode 100644 index 0000000..e9f3ca9 --- /dev/null +++ b/examples/svelte/create-hotkey/src/index.css @@ -0,0 +1,212 @@ +* { + box-sizing: border-box; +} +body { + margin: 0; + font-family: + system-ui, + -apple-system, + sans-serif; + background: #f5f5f5; + color: #333; +} +.app { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} +header { + text-align: center; + margin-bottom: 40px; +} +header h1 { + margin: 0 0 10px; + color: #0066cc; +} +header p { + color: #666; + margin: 0; +} +.demo-section { + background: white; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} +.demo-section h2 { + margin: 0 0 12px; + font-size: 20px; +} +.demo-section p { + margin: 0 0 12px; +} +kbd { + background: linear-gradient(180deg, #f8f8f8 0%, #e8e8e8 100%); + border: 1px solid #ccc; + border-bottom-width: 2px; + border-radius: 4px; + padding: 2px 8px; + font-family: monospace; + font-size: 13px; +} +.counter { + font-size: 28px; + font-weight: bold; + color: #0066cc; + margin: 16px 0; +} +.hint { + font-size: 13px; + color: #888; + font-style: italic; +} +.info-box { + background: #e3f2fd; + border-radius: 8px; + padding: 12px 16px; + margin: 20px 0; +} +button { + background: #0066cc; + color: white; + border: none; + padding: 10px 20px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; +} +button:hover { + background: #0052a3; +} +.code-block { + background: #1e1e1e; + color: #d4d4d4; + padding: 16px; + border-radius: 8px; + overflow-x: auto; + font-size: 13px; + line-height: 1.5; + margin-top: 16px; +} +.hotkey-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 12px; + margin: 16px 0; +} +.hotkey-grid > div { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: #f8f9fa; + border-radius: 6px; + font-size: 14px; +} +.hotkey-grid kbd { + flex-shrink: 0; +} + +/* Scoped shortcuts section */ +.scoped-section { + margin-top: 40px; +} + +.scoped-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 24px; + margin: 24px 0; +} + +.scoped-area { + background: #f8f9fa; + border: 2px dashed #0066cc; + border-radius: 8px; + padding: 20px; + position: relative; +} + +.scoped-area:focus-within { + border-color: #0052a3; + border-style: solid; + background: #f0f7ff; + box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1); +} + +.scoped-area h3 { + margin: 0 0 12px; + font-size: 18px; + color: #0066cc; +} + +.scoped-area .hotkey-list { + margin: 12px 0; +} + +.scoped-area .hotkey-list > div { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 0; + font-size: 14px; +} + +.scoped-editor { + width: 100%; + margin: 12px 0; + padding: 12px; + border: 1px solid #ddd; + border-radius: 6px; + font-family: 'Courier New', monospace; + font-size: 14px; + resize: vertical; + min-height: 120px; +} + +.scoped-editor:focus { + outline: 2px solid #0066cc; + outline-offset: 2px; + border-color: #0066cc; +} + +/* Modal styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal-content { + background: white; + border-radius: 12px; + padding: 24px; + max-width: 500px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); +} + +.modal-content:focus { + outline: 3px solid #0066cc; + outline-offset: 2px; +} + +.modal-content h3 { + margin: 0 0 16px; + font-size: 20px; + color: #0066cc; +} + +.modal-content button { + margin-top: 16px; +} diff --git a/examples/svelte/create-hotkey/src/main.ts b/examples/svelte/create-hotkey/src/main.ts new file mode 100644 index 0000000..9357900 --- /dev/null +++ b/examples/svelte/create-hotkey/src/main.ts @@ -0,0 +1,5 @@ +import { mount } from 'svelte' +import Root from './Root.svelte' +import './index.css' + +mount(Root, { target: document.getElementById('app')! }) diff --git a/examples/svelte/create-hotkey/static/robots.txt b/examples/svelte/create-hotkey/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/examples/svelte/create-hotkey/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/examples/svelte/create-hotkey/svelte.config.js b/examples/svelte/create-hotkey/svelte.config.js new file mode 100644 index 0000000..b30b657 --- /dev/null +++ b/examples/svelte/create-hotkey/svelte.config.js @@ -0,0 +1,11 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import('svelte').Config} */ +const config = { + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, + }, +} + +export default config diff --git a/examples/svelte/create-hotkey/tsconfig.json b/examples/svelte/create-hotkey/tsconfig.json new file mode 100644 index 0000000..912a030 --- /dev/null +++ b/examples/svelte/create-hotkey/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["eslint.config.js"] +} diff --git a/examples/svelte/create-hotkey/vite.config.ts b/examples/svelte/create-hotkey/vite.config.ts new file mode 100644 index 0000000..951a9ba --- /dev/null +++ b/examples/svelte/create-hotkey/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/svelte/get-held-keys/.gitignore b/examples/svelte/get-held-keys/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/examples/svelte/get-held-keys/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/examples/svelte/get-held-keys/.npmrc b/examples/svelte/get-held-keys/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/examples/svelte/get-held-keys/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/examples/svelte/get-held-keys/index.html b/examples/svelte/get-held-keys/index.html new file mode 100644 index 0000000..de0f445 --- /dev/null +++ b/examples/svelte/get-held-keys/index.html @@ -0,0 +1,14 @@ + + + + + + + getHeldKeys - TanStack Hotkeys Svelte Example + + + +
+ + + diff --git a/examples/svelte/get-held-keys/package.json b/examples/svelte/get-held-keys/package.json new file mode 100644 index 0000000..e6c1329 --- /dev/null +++ b/examples/svelte/get-held-keys/package.json @@ -0,0 +1,17 @@ +{ + "name": "@tanstack/hotkeys-example-svelte-get-held-keys", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3069" + }, + "dependencies": { + "@tanstack/svelte-hotkeys": "0.4.1", + "svelte": "^5.53.7" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "typescript": "5.9.3", + "vite": "^7.3.1" + } +} diff --git a/examples/svelte/get-held-keys/src/App.svelte b/examples/svelte/get-held-keys/src/App.svelte new file mode 100644 index 0000000..5e2901e --- /dev/null +++ b/examples/svelte/get-held-keys/src/App.svelte @@ -0,0 +1,111 @@ + + +
+
+

getHeldKeys

+

+ Returns an array of all currently pressed keys. Useful for displaying key + combinations or building custom shortcut recording. +

+
+ +
+
+

Currently Held Keys

+
+ {#if heldKeys.keys.length > 0} + {#each heldKeys.keys as key, index} + {@const code = heldKeyCodesMap.codes[key]} + {#if index > 0} + + + {/if} + + {formatKeyForDebuggingDisplay(key)} + {#if code && code !== key} + + {formatKeyForDebuggingDisplay(code, { + source: 'code', + })} + + {/if} + + {/each} + {:else} + Press any keys... + {/if} +
+
+ Keys held: {heldKeys.keys.length} +
+
+ +
+

Usage

+
{`import { getHeldKeys } from '@tanstack/svelte-hotkeys'
+
+const heldKeys = getHeldKeys()
+
+// In template:
+// Currently pressed: {heldKeys.keys.join(' + ') || 'None'}`}
+
+ +
+

Try These Combinations

+
    +
  • + Hold Shift + Control + A +
  • +
  • Press multiple letter keys at once
  • +
  • Hold modifiers and watch them appear
  • +
  • Release keys one by one
  • +
+
+ +
+

Recent Combinations

+ {#if history.length > 0} +
    + {#each history as combo} +
  • {combo}
  • + {/each} +
+ + {:else} +

Press some key combinations...

+ {/if} +
+ +
+

Use Cases

+
    +
  • Building a keyboard shortcut recorder
  • +
  • Displaying currently held keys to users
  • +
  • Debugging keyboard input
  • +
  • Creating key combination tutorials
  • +
+
+
+
diff --git a/examples/svelte/get-held-keys/src/Root.svelte b/examples/svelte/get-held-keys/src/Root.svelte new file mode 100644 index 0000000..12e94e9 --- /dev/null +++ b/examples/svelte/get-held-keys/src/Root.svelte @@ -0,0 +1,5 @@ + + + diff --git a/examples/svelte/get-held-keys/src/index.css b/examples/svelte/get-held-keys/src/index.css new file mode 100644 index 0000000..5f83d60 --- /dev/null +++ b/examples/svelte/get-held-keys/src/index.css @@ -0,0 +1,131 @@ +* { + box-sizing: border-box; +} +body { + margin: 0; + font-family: + system-ui, + -apple-system, + sans-serif; + background: #f5f5f5; + color: #333; +} +.app { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} +header { + text-align: center; + margin-bottom: 40px; +} +header h1 { + margin: 0 0 10px; + color: #0066cc; +} +header p { + color: #666; + margin: 0; + max-width: 500px; + margin: 0 auto; +} +.demo-section { + background: white; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} +.demo-section h2 { + margin: 0 0 16px; + font-size: 20px; +} +.demo-section ul { + margin: 0; + padding-left: 20px; +} +.demo-section li { + margin-bottom: 8px; +} +kbd { + background: linear-gradient(180deg, #f8f8f8 0%, #e8e8e8 100%); + border: 1px solid #ccc; + border-bottom-width: 2px; + border-radius: 4px; + padding: 2px 8px; + font-family: monospace; + font-size: 13px; +} +kbd.large { + font-size: 24px; + padding: 8px 16px; + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 2px; +} +kbd.large .code-label { + display: block; + font-size: 11px; + color: #888; + font-weight: normal; +} +.key-display { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + min-height: 80px; + flex-wrap: wrap; + background: #f8f9fa; + border-radius: 8px; + padding: 20px; +} +.key-display .plus { + font-size: 24px; + color: #666; +} +.placeholder { + color: #999; + font-style: italic; +} +.stats { + text-align: center; + margin-top: 16px; + font-size: 16px; + color: #666; +} +.code-block { + background: #1e1e1e; + color: #d4d4d4; + padding: 16px; + border-radius: 8px; + overflow-x: auto; + font-size: 13px; + line-height: 1.5; +} +.history-list { + list-style: none; + padding: 0; + margin: 0 0 16px; +} +.history-list li { + padding: 8px 12px; + background: #f0f0f0; + border-radius: 4px; + margin-bottom: 4px; + font-family: monospace; + font-size: 14px; +} +button { + background: #0066cc; + color: white; + border: none; + padding: 10px 20px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; +} +button:hover { + background: #0052a3; +} diff --git a/examples/svelte/get-held-keys/src/main.ts b/examples/svelte/get-held-keys/src/main.ts new file mode 100644 index 0000000..9357900 --- /dev/null +++ b/examples/svelte/get-held-keys/src/main.ts @@ -0,0 +1,5 @@ +import { mount } from 'svelte' +import Root from './Root.svelte' +import './index.css' + +mount(Root, { target: document.getElementById('app')! }) diff --git a/examples/svelte/get-held-keys/static/robots.txt b/examples/svelte/get-held-keys/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/examples/svelte/get-held-keys/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/examples/svelte/get-held-keys/svelte.config.js b/examples/svelte/get-held-keys/svelte.config.js new file mode 100644 index 0000000..b30b657 --- /dev/null +++ b/examples/svelte/get-held-keys/svelte.config.js @@ -0,0 +1,11 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import('svelte').Config} */ +const config = { + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, + }, +} + +export default config diff --git a/examples/svelte/get-held-keys/tsconfig.json b/examples/svelte/get-held-keys/tsconfig.json new file mode 100644 index 0000000..912a030 --- /dev/null +++ b/examples/svelte/get-held-keys/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["eslint.config.js"] +} diff --git a/examples/svelte/get-held-keys/vite.config.ts b/examples/svelte/get-held-keys/vite.config.ts new file mode 100644 index 0000000..951a9ba --- /dev/null +++ b/examples/svelte/get-held-keys/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/svelte/get-is-key-held/.gitignore b/examples/svelte/get-is-key-held/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/examples/svelte/get-is-key-held/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/examples/svelte/get-is-key-held/.npmrc b/examples/svelte/get-is-key-held/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/examples/svelte/get-is-key-held/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/examples/svelte/get-is-key-held/index.html b/examples/svelte/get-is-key-held/index.html new file mode 100644 index 0000000..83c4b3b --- /dev/null +++ b/examples/svelte/get-is-key-held/index.html @@ -0,0 +1,14 @@ + + + + + + + getIsKeyHeld - TanStack Hotkeys Svelte Example + + + +
+ + + diff --git a/examples/svelte/get-is-key-held/package.json b/examples/svelte/get-is-key-held/package.json new file mode 100644 index 0000000..e13b44e --- /dev/null +++ b/examples/svelte/get-is-key-held/package.json @@ -0,0 +1,17 @@ +{ + "name": "@tanstack/hotkeys-example-svelte-get-is-key-held", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3069" + }, + "dependencies": { + "@tanstack/svelte-hotkeys": "0.4.1", + "svelte": "^5.53.7" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "typescript": "5.9.3", + "vite": "^7.3.1" + } +} diff --git a/examples/svelte/get-is-key-held/src/App.svelte b/examples/svelte/get-is-key-held/src/App.svelte new file mode 100644 index 0000000..19a4c43 --- /dev/null +++ b/examples/svelte/get-is-key-held/src/App.svelte @@ -0,0 +1,92 @@ + + +
+
+

getIsKeyHeld

+

+ Returns a boolean indicating if a specific key is currently held. + Optimized to only re-render when that specific key changes. +

+
+ +
+
+

Modifier Key States

+
+
+ Shift + + {isShiftHeld.held ? 'HELD' : 'Released'} + +
+
+ Control + + {isControlHeld.held ? 'HELD' : 'Released'} + +
+
+ Alt / Option + {isAltHeld.held ? 'HELD' : 'Released'} +
+
+ Meta (⌘ / ⊞) + {isMetaHeld.held ? 'HELD' : 'Released'} +
+
+
+ +
+

Space Bar Demo

+
+ {isSpaceHeld.held ? '🚀 SPACE HELD!' : 'Hold Space Bar'} +
+
+ +
+

Usage

+
{`import { getIsKeyHeld } from '@tanstack/svelte-hotkeys'
+
+const isShiftHeld = getIsKeyHeld('Shift')
+
+// In template:
+// 
+// {isShiftHeld.held ? 'Shift is pressed!' : 'Press Shift'} +//
`}
+
+ +
+

Conditional UI Example

+

+ Hold Shift to reveal the secret message: +

+
+ {#if isShiftHeld.held} + 🎉 The secret password is: tanstack-hotkeys-rocks! + {:else} + •••••••••••••••••••••••••• + {/if} +
+
+ +
+

Use Cases

+
    +
  • Show different UI based on modifier state
  • +
  • Enable "power user" mode while holding a key
  • +
  • Hold-to-reveal sensitive information
  • +
  • Drag-and-drop with modifier behaviors
  • +
  • Show additional options on hover + modifier
  • +
+
+
+
diff --git a/examples/svelte/get-is-key-held/src/Root.svelte b/examples/svelte/get-is-key-held/src/Root.svelte new file mode 100644 index 0000000..12e94e9 --- /dev/null +++ b/examples/svelte/get-is-key-held/src/Root.svelte @@ -0,0 +1,5 @@ + + + diff --git a/examples/svelte/get-is-key-held/src/index.css b/examples/svelte/get-is-key-held/src/index.css new file mode 100644 index 0000000..0cbb5ae --- /dev/null +++ b/examples/svelte/get-is-key-held/src/index.css @@ -0,0 +1,127 @@ +* { + box-sizing: border-box; +} +body { + margin: 0; + font-family: + system-ui, + -apple-system, + sans-serif; + background: #f5f5f5; + color: #333; +} +.app { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} +header { + text-align: center; + margin-bottom: 40px; +} +header h1 { + margin: 0 0 10px; + color: #0066cc; +} +header p { + color: #666; + margin: 0; + max-width: 500px; + margin: 0 auto; +} +.demo-section { + background: white; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} +.demo-section h2 { + margin: 0 0 16px; + font-size: 20px; +} +.demo-section p { + margin: 0 0 12px; +} +.demo-section ul { + margin: 0; + padding-left: 20px; +} +.demo-section li { + margin-bottom: 8px; +} +kbd { + background: linear-gradient(180deg, #f8f8f8 0%, #e8e8e8 100%); + border: 1px solid #ccc; + border-bottom-width: 2px; + border-radius: 4px; + padding: 2px 8px; + font-family: monospace; + font-size: 13px; +} +.modifier-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; +} +.modifier-indicator { + background: #f0f0f0; + border: 2px solid #ddd; + border-radius: 12px; + padding: 20px; + text-align: center; + transition: all 0.15s ease; +} +.modifier-indicator.active { + background: #4caf50; + border-color: #388e3c; + color: white; + transform: scale(1.02); +} +.modifier-indicator .key-name { + display: block; + font-weight: bold; + font-size: 18px; + margin-bottom: 8px; +} +.modifier-indicator .status { + font-size: 14px; + opacity: 0.8; +} +.space-indicator { + background: #f0f0f0; + border: 3px solid #ddd; + border-radius: 16px; + padding: 40px; + text-align: center; + font-size: 24px; + transition: all 0.15s ease; +} +.space-indicator.active { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-color: #5a67d8; + color: white; + transform: scale(1.02); +} +.secret-box { + background: #f0f0f0; + border-radius: 8px; + padding: 20px; + text-align: center; + font-family: monospace; + font-size: 16px; + transition: all 0.3s ease; +} +.secret-box.revealed { + background: #e8f5e9; + color: #2e7d32; +} +.code-block { + background: #1e1e1e; + color: #d4d4d4; + padding: 16px; + border-radius: 8px; + overflow-x: auto; + font-size: 13px; + line-height: 1.5; +} diff --git a/examples/svelte/get-is-key-held/src/main.ts b/examples/svelte/get-is-key-held/src/main.ts new file mode 100644 index 0000000..9357900 --- /dev/null +++ b/examples/svelte/get-is-key-held/src/main.ts @@ -0,0 +1,5 @@ +import { mount } from 'svelte' +import Root from './Root.svelte' +import './index.css' + +mount(Root, { target: document.getElementById('app')! }) diff --git a/examples/svelte/get-is-key-held/static/robots.txt b/examples/svelte/get-is-key-held/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/examples/svelte/get-is-key-held/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/examples/svelte/get-is-key-held/svelte.config.js b/examples/svelte/get-is-key-held/svelte.config.js new file mode 100644 index 0000000..b30b657 --- /dev/null +++ b/examples/svelte/get-is-key-held/svelte.config.js @@ -0,0 +1,11 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import('svelte').Config} */ +const config = { + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, + }, +} + +export default config diff --git a/examples/svelte/get-is-key-held/tsconfig.json b/examples/svelte/get-is-key-held/tsconfig.json new file mode 100644 index 0000000..912a030 --- /dev/null +++ b/examples/svelte/get-is-key-held/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["eslint.config.js"] +} diff --git a/examples/svelte/get-is-key-held/vite.config.ts b/examples/svelte/get-is-key-held/vite.config.ts new file mode 100644 index 0000000..951a9ba --- /dev/null +++ b/examples/svelte/get-is-key-held/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/package.json b/package.json index bb4ec20..4376185 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "clean": "find . -name 'dist' -type d -prune -exec rm -rf {} +", "clean:node_modules": "find . -name 'node_modules' -type d -prune -exec rm -rf {} +", "clean:all": "pnpm run clean && pnpm run clean:node_modules", - "copy:readme": "cp README.md packages/hotkeys/README.md && cp README.md packages/hotkeys-devtools/README.md && cp README.md packages/angular-hotkeys/README.md && cp README.md packages/react-hotkeys/README.md && cp README.md packages/react-hotkeys-devtools/README.md && cp README.md packages/preact-hotkeys/README.md && cp README.md packages/preact-hotkeys-devtools/README.md && cp README.md packages/solid-hotkeys/README.md && cp README.md packages/solid-hotkeys-devtools/README.md && cp README.md packages/vue-hotkeys/README.md && cp README.md packages/vue-hotkeys-devtools/README.md", + "copy:readme": "cp README.md packages/hotkeys/README.md && cp README.md packages/hotkeys-devtools/README.md && cp README.md packages/angular-hotkeys/README.md && cp README.md packages/react-hotkeys/README.md && cp README.md packages/react-hotkeys-devtools/README.md && cp README.md packages/preact-hotkeys/README.md && cp README.md packages/preact-hotkeys-devtools/README.md && cp README.md packages/solid-hotkeys/README.md && cp README.md packages/solid-hotkeys-devtools/README.md && cp README.md packages/vue-hotkeys/README.md && cp README.md packages/vue-hotkeys-devtools/README.md && cp README.md packages/svelte-hotkeys/README.md", "dev": "pnpm run watch", "format": "prettier --experimental-cli --ignore-unknown '**/*' --write", "generate-docs": "node scripts/generate-docs.ts && pnpm run copy:readme", @@ -85,6 +85,7 @@ "@tanstack/react-hotkeys-devtools": "workspace:*", "@tanstack/solid-hotkeys": "workspace:*", "@tanstack/solid-hotkeys-devtools": "workspace:*", + "@tanstack/svelte-hotkeys": "workspace:*", "@tanstack/vue-hotkeys": "workspace:*", "@tanstack/vue-hotkeys-devtools": "workspace:*" } diff --git a/packages/angular-hotkeys/README.md b/packages/angular-hotkeys/README.md index 1c80cd6..8c67395 100644 --- a/packages/angular-hotkeys/README.md +++ b/packages/angular-hotkeys/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/hotkeys-devtools/README.md b/packages/hotkeys-devtools/README.md index 1c80cd6..8c67395 100644 --- a/packages/hotkeys-devtools/README.md +++ b/packages/hotkeys-devtools/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/hotkeys/README.md b/packages/hotkeys/README.md index 1c80cd6..8c67395 100644 --- a/packages/hotkeys/README.md +++ b/packages/hotkeys/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/preact-hotkeys-devtools/README.md b/packages/preact-hotkeys-devtools/README.md index 1c80cd6..8c67395 100644 --- a/packages/preact-hotkeys-devtools/README.md +++ b/packages/preact-hotkeys-devtools/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/preact-hotkeys/README.md b/packages/preact-hotkeys/README.md index 1c80cd6..8c67395 100644 --- a/packages/preact-hotkeys/README.md +++ b/packages/preact-hotkeys/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/react-hotkeys-devtools/README.md b/packages/react-hotkeys-devtools/README.md index 1c80cd6..8c67395 100644 --- a/packages/react-hotkeys-devtools/README.md +++ b/packages/react-hotkeys-devtools/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/react-hotkeys/README.md b/packages/react-hotkeys/README.md index 1c80cd6..8c67395 100644 --- a/packages/react-hotkeys/README.md +++ b/packages/react-hotkeys/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/solid-hotkeys-devtools/README.md b/packages/solid-hotkeys-devtools/README.md index 1c80cd6..8c67395 100644 --- a/packages/solid-hotkeys-devtools/README.md +++ b/packages/solid-hotkeys-devtools/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/solid-hotkeys/README.md b/packages/solid-hotkeys/README.md index 1c80cd6..8c67395 100644 --- a/packages/solid-hotkeys/README.md +++ b/packages/solid-hotkeys/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/svelte-hotkeys/.gitignore b/packages/svelte-hotkeys/.gitignore new file mode 100644 index 0000000..2fd4600 --- /dev/null +++ b/packages/svelte-hotkeys/.gitignore @@ -0,0 +1,8 @@ +.svelte-kit +node_modules +dist +build +.env +.env.* +!.env.example +!.env.test \ No newline at end of file diff --git a/packages/svelte-hotkeys/README.md b/packages/svelte-hotkeys/README.md new file mode 100644 index 0000000..8c67395 --- /dev/null +++ b/packages/svelte-hotkeys/README.md @@ -0,0 +1,122 @@ +
+ TanStack Hotkeys +
+ +
+ + + + + +
+ +### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/) + +
+ +# TanStack Hotkeys + +> [!NOTE] +> TanStack Hotkeys is pre-alpha (prototyping phase). We are actively developing the library and are open to feedback and contributions. + +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. + +- Type-safe bindings — template strings (`Mod+Shift+S`, `Escape`) or parsed objects for full control +- Flexible options — `keydown`/`keyup`, `preventDefault`, `stopPropagation`, conditional enabled, `requireReset` +- Cross-platform Mod — maps to Cmd on macOS and Ctrl on Windows/Linux +- Batteries included — validation + matching, sequences (Vim-style), key-state tracking, recorder UI helpers, framework adapters, and devtools + +### Read the docs → + +
+ +> [!NOTE] +> You may know **TanStack Hotkeys** by our adapter names, too! +> +> - [**React Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/react/react-hotkeys) +> - [**Preact Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/preact/preact-hotkeys) +> - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) +> - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) +> - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) + +## Get Involved + +- We welcome issues and pull requests! +- Participate in [GitHub discussions](https://github.com/TanStack/hotkeys/discussions) +- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ) +- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions + +## Partners + +
+ + + + + + +
+ + + + + CodeRabbit + + + + + + + + Cloudflare + + +
+ +
+Keys & you? +

+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. +

+LET'S CHAT +
+ +
+ +## Explore the TanStack Ecosystem + +- TanStack Config – Tooling for JS/TS packages +- TanStack DB – Reactive sync client store +- TanStack DevTools – Unified devtools panel +- TanStack Form – Type‑safe form state +- TanStack Hotkeys – Type‑safe keyboard shortcuts +- TanStack Query – Async state & caching +- TanStack Ranger – Range & slider primitives +- TanStack Router – Type‑safe routing, caching & URL state +- TanStack Start – Full‑stack SSR & streaming +- TanStack Store – Reactive data store +- TanStack Table – Headless datagrids +- TanStack Virtual – Virtualized rendering + +… and more at TanStack.com » diff --git a/packages/svelte-hotkeys/eslint.config.js b/packages/svelte-hotkeys/eslint.config.js new file mode 100644 index 0000000..6e628b9 --- /dev/null +++ b/packages/svelte-hotkeys/eslint.config.js @@ -0,0 +1,31 @@ +// @ts-check + +import tsParser from '@typescript-eslint/parser' +import pluginSvelte from 'eslint-plugin-svelte' +import rootConfig from '../../eslint.config.js' +import svelteConfig from './svelte.config.js' + +/** @type {import('eslint').Linter.Config[]} */ +const config = [ + ...rootConfig, + ...pluginSvelte.configs['recommended'], + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + languageOptions: { + parserOptions: { + parser: tsParser, + extraFileExtensions: ['.svelte'], + svelteConfig, + }, + }, + }, + { + rules: { + 'svelte/block-lang': ['error', { script: ['ts'] }], + 'svelte/no-svelte-internal': 'error', + 'svelte/valid-compile': 'off', + }, + }, +] + +export default config diff --git a/packages/svelte-hotkeys/package.json b/packages/svelte-hotkeys/package.json new file mode 100644 index 0000000..1e130fc --- /dev/null +++ b/packages/svelte-hotkeys/package.json @@ -0,0 +1,64 @@ +{ + "name": "@tanstack/svelte-hotkeys", + "version": "0.4.1", + "description": "Svelte adapter for TanStack Hotkeys", + "author": "Kunal Rao", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/TanStack/hotkeys.git", + "directory": "packages/svelte-hotkeys" + }, + "homepage": "https://tanstack.com/hotkeys", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kunalrao" + }, + "keywords": [ + "svelte", + "tanstack", + "keys", + "hotkeys", + "keyboard", + "shortcuts" + ], + "scripts": { + "clean": "premove ./dist ./coverage", + "test:eslint": "eslint ./src", + "test:lib": "vitest --passWithNoTests", + "test:lib:dev": "pnpm test:lib --watch", + "test:types": "tsc", + "test:build": "publint --strict", + "build": "svelte-package --input ./src --output ./dist" + }, + "type": "module", + "types": "dist/index.d.ts", + "module": "dist/index.js", + "svelte": "./dist/index.js", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "svelte": "./dist/index.js", + "import": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "sideEffects": false, + "files": [ + "dist", + "src" + ], + "dependencies": { + "@tanstack/hotkeys": "workspace:*" + }, + "devDependencies": { + "@sveltejs/package": "^2.5.7", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@typescript-eslint/parser": "^8.56.1", + "eslint-plugin-svelte": "^3.15.0", + "svelte": "^5.53.7" + }, + "peerDependencies": { + "svelte": "^5.25.0" + } +} diff --git a/packages/svelte-hotkeys/src/HotkeysCtx.ts b/packages/svelte-hotkeys/src/HotkeysCtx.ts new file mode 100644 index 0000000..686e1c9 --- /dev/null +++ b/packages/svelte-hotkeys/src/HotkeysCtx.ts @@ -0,0 +1,57 @@ +import { createContext } from 'svelte' +import type { HotkeyRecorderOptions } from '@tanstack/hotkeys' +import type { CreateHotkeyOptions } from './createHotkey.svelte' +import type { Snippet } from 'svelte' +import type { CreateHotkeySequenceOptions } from './createHotkeySequence.svelte' + +export interface HotkeysProviderOptions { + hotkey?: Partial + hotkeyRecorder?: Partial + hotkeySequence?: Partial +} + +export interface HotkeysProviderProps { + children: Snippet + defaultOptions?: HotkeysProviderOptions +} + +export const DEFAULT_OPTIONS: HotkeysProviderOptions = {} + +interface HotkeysContextValue { + defaultOptions: HotkeysProviderOptions +} + +const [useHotkeysContext, setHotkeysContextValue] = + createContext() + +export function setHotkeysContext( + defaultOptions: HotkeysProviderOptions = DEFAULT_OPTIONS, +): HotkeysContextValue { + return setHotkeysContextValue({ + get defaultOptions() { + return defaultOptions + }, + })! +} + +export function setHotkeysContextSource( + defaultOptions: () => HotkeysProviderOptions, +): HotkeysContextValue { + return setHotkeysContextValue({ + get defaultOptions() { + return defaultOptions() + }, + })! +} + +export function getHotkeysContext(): HotkeysContextValue | null { + try { + return useHotkeysContext() + } catch { + return null + } +} + +export function getDefaultHotkeysOptions(): HotkeysProviderOptions { + return getHotkeysContext()?.defaultOptions ?? DEFAULT_OPTIONS +} diff --git a/packages/svelte-hotkeys/src/HotkeysProvider.svelte b/packages/svelte-hotkeys/src/HotkeysProvider.svelte new file mode 100644 index 0000000..a431589 --- /dev/null +++ b/packages/svelte-hotkeys/src/HotkeysProvider.svelte @@ -0,0 +1,12 @@ + + +{@render children()} diff --git a/packages/svelte-hotkeys/src/createHotkey.svelte.ts b/packages/svelte-hotkeys/src/createHotkey.svelte.ts new file mode 100644 index 0000000..be32cad --- /dev/null +++ b/packages/svelte-hotkeys/src/createHotkey.svelte.ts @@ -0,0 +1,120 @@ +import { + detectPlatform, + formatHotkey, + getHotkeyManager, + rawHotkeyToParsedHotkey, +} from '@tanstack/hotkeys' +import { getDefaultHotkeysOptions } from './HotkeysCtx' +import { resolveMaybeGetter } from './internal.svelte' +import type { + Hotkey, + HotkeyCallback, + HotkeyOptions, + RegisterableHotkey, +} from '@tanstack/hotkeys' +import type { MaybeGetter } from './internal.svelte' +import type { Attachment } from 'svelte/attachments' + +export interface CreateHotkeyOptions extends Omit { + target?: Document | Window // not html elements, use attachment instead +} + +function normalizeHotkey( + hotkey: RegisterableHotkey, + options: CreateHotkeyOptions, +): Hotkey { + const platform = options.platform ?? detectPlatform() + + return typeof hotkey === 'string' + ? hotkey + : (formatHotkey(rawHotkeyToParsedHotkey(hotkey, platform)) as Hotkey) +} + +function registerHotkey( + target: HTMLElement | Document | Window, + hotkey: MaybeGetter, + callback: HotkeyCallback, + options: MaybeGetter, +) { + const resolvedHotkey = resolveMaybeGetter(hotkey) + const resolvedOptions = resolveMaybeGetter(options) + const mergedOptions = { + ...getDefaultHotkeysOptions().hotkey, + ...resolvedOptions, + } as CreateHotkeyOptions + + return getHotkeyManager().register( + normalizeHotkey(resolvedHotkey, mergedOptions), + callback, + { + ...mergedOptions, + target, + }, + ) +} + +/** + * Register a global hotkey for the current component. + * + * @example + * ```svelte + * + * ``` + */ +export function createHotkey( + hotkey: MaybeGetter, + callback: HotkeyCallback, + options: MaybeGetter = {}, +): void { + $effect(() => { + if (typeof document === 'undefined') { + return + } + + const registration = registerHotkey(document, hotkey, callback, options) + + return () => { + registration.unregister() + } + }) +} + +/** + * Create an attachment for element-scoped hotkeys. + * + * @example + * ```svelte + * + * + *
+ * Count: {count} + *
+ * ``` + */ +export function createHotkeyAttachment( + hotkey: MaybeGetter, + callback: HotkeyCallback, + options: MaybeGetter = {}, +): Attachment { + return (element) => { + const registration = registerHotkey(element, hotkey, callback, options) + + return () => { + registration.unregister() + } + } +} diff --git a/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts b/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts new file mode 100644 index 0000000..f490a3e --- /dev/null +++ b/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts @@ -0,0 +1,118 @@ +import { HotkeyRecorder } from '@tanstack/hotkeys' +import { onDestroy } from 'svelte' +import { getDefaultHotkeysOptions } from './HotkeysCtx' +import { createStoreSubscriber, resolveMaybeGetter } from './internal.svelte' +import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys' +import type { MaybeGetter } from './internal.svelte' + +export interface SvelteHotkeyRecorder { + /** Whether recording is currently active */ + readonly isRecording: boolean + /** The currently recorded hotkey (for live preview) */ + readonly recordedHotkey: Hotkey | null + /** Start recording a new hotkey */ + startRecording: () => void + /** Stop recording (same as cancel) */ + stopRecording: () => void + /** Cancel recording without saving */ + cancelRecording: () => void +} + +/** + * Svelte function for recording keyboard shortcuts. + * + * This function provides a thin wrapper around the framework-agnostic `HotkeyRecorder` + * class, managing all the complexity of capturing keyboard events, converting them + * to hotkey strings, and handling edge cases like Escape to cancel or Backspace/Delete + * to clear. + * + * @param options - Configuration options for the recorder + * @returns An object with recording state and control functions + * + * @example + * ```svelte + * + * + *
+ * + * {#if recorder.recordedHotkey} + *
Recording: {recorder.recordedHotkey}
+ * {/if} + *
+ * ``` + */ + +class SvelteHotkeyRecorderState implements SvelteHotkeyRecorder { + #recorder: HotkeyRecorder + #subscribe: () => void + + constructor(options: HotkeyRecorderOptions) { + this.#recorder = new HotkeyRecorder(options) + this.#subscribe = createStoreSubscriber(this.#recorder.store) + } + + get isRecording(): boolean { + this.#subscribe() + return this.#recorder.store.state.isRecording + } + + get recordedHotkey(): Hotkey | null { + this.#subscribe() + return this.#recorder.store.state.recordedHotkey + } + + setOptions(options: HotkeyRecorderOptions): void { + this.#recorder.setOptions(options) + } + + startRecording(): void { + this.#recorder.start() + } + + stopRecording(): void { + this.#recorder.stop() + } + + cancelRecording(): void { + this.#recorder.cancel() + } + + destroy(): void { + this.#recorder.destroy() + } +} + +export function createHotkeyRecorder( + options: MaybeGetter, +): SvelteHotkeyRecorder { + const recorder = new SvelteHotkeyRecorderState({ + ...getDefaultHotkeysOptions().hotkeyRecorder, + ...resolveMaybeGetter(options), + } as HotkeyRecorderOptions) + + $effect(() => { + recorder.setOptions({ + ...getDefaultHotkeysOptions().hotkeyRecorder, + ...resolveMaybeGetter(options), + } as HotkeyRecorderOptions) + }) + + onDestroy(() => { + recorder.destroy() + }) + + return recorder +} diff --git a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts new file mode 100644 index 0000000..2fc8dbe --- /dev/null +++ b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts @@ -0,0 +1,136 @@ +import { getSequenceManager } from '@tanstack/hotkeys' +import { getDefaultHotkeysOptions } from './HotkeysCtx' +import { resolveMaybeGetter } from './internal.svelte' +import type { + HotkeyCallback, + HotkeySequence, + SequenceOptions, +} from '@tanstack/hotkeys' +import type { MaybeGetter } from './internal.svelte' +import type { Attachment } from 'svelte/attachments' + +export interface CreateHotkeySequenceOptions extends Omit< + SequenceOptions, + 'target' +> { + target?: Document | Window // not html elements, use attachment instead +} + +function registerHotkeySequence( + target: HTMLElement | Document | Window, + sequence: MaybeGetter, + callback: HotkeyCallback, + options: MaybeGetter, +) { + const resolvedSequence = resolveMaybeGetter(sequence) + const resolvedOptions = resolveMaybeGetter(options) + const mergedOptions = { + ...getDefaultHotkeysOptions().hotkeySequence, + ...resolvedOptions, + } as CreateHotkeySequenceOptions + + return getSequenceManager().register(resolvedSequence, callback, { + ...mergedOptions, + target, + }) +} + +/** + * Register a global keyboard shortcut sequence for the current component. + * + * @example + * ```svelte + * + * + *
+ * .... + *
+ * ``` + */ +export function createHotkeySequence( + sequence: MaybeGetter, + callback: HotkeyCallback, + options: MaybeGetter = {}, +): void { + $effect(() => { + if (typeof document === 'undefined') { + return + } + + const resolvedSequence = resolveMaybeGetter(sequence) + if (resolvedSequence.length === 0) { + return + } + + const registration = registerHotkeySequence( + document, + resolvedSequence, + callback, + options, + ) + + return () => { + registration.unregister() + } + }) +} + +/** + * Create an attachment for element-scoped keyboard sequences. + * + * @example + * ```svelte + * + * + *
+ * Focus here and press g then g + *
+ * ``` + */ +export function createHotkeySequenceAttachment( + sequence: MaybeGetter, + callback: HotkeyCallback, + options: MaybeGetter = {}, +): Attachment { + return (element) => { + const resolvedSequence = resolveMaybeGetter(sequence) + + if (resolvedSequence.length === 0) { + return + } + + const registration = registerHotkeySequence( + element, + resolvedSequence, + callback, + options, + ) + + return () => { + registration.unregister() + } + } +} diff --git a/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts new file mode 100644 index 0000000..1d59981 --- /dev/null +++ b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts @@ -0,0 +1,44 @@ +import { getKeyStateTracker } from '@tanstack/hotkeys' +import { createStoreSubscriber } from './internal.svelte' + +export interface SvelteHeldKeyCodesMap { + readonly codes: Record +} + +class HeldKeyCodesMapState implements SvelteHeldKeyCodesMap { + #tracker = getKeyStateTracker() + #subscribe = createStoreSubscriber(this.#tracker.store) + + get codes(): Record { + this.#subscribe() + return this.#tracker.store.state.heldCodes + } +} + +/** + * Svelte function that returns reactive access to the map of currently held key names + * to their physical `event.code` values. + * + * This is useful for debugging which physical key was pressed (e.g. distinguishing + * left vs right Shift via "ShiftLeft" / "ShiftRight"). + * + * @returns Object with a reactive `codes` property + * + * ```svelte + * + * + *
+ * {#each Object.entries(heldKeyCodesMap.codes) as [key, code]} + * + * {key} {code} + * + * {/each} + *
+ * ``` + */ +export function getHeldKeyCodesMap(): SvelteHeldKeyCodesMap { + return new HeldKeyCodesMapState() +} diff --git a/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts new file mode 100644 index 0000000..eddc222 --- /dev/null +++ b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts @@ -0,0 +1,40 @@ +import { getKeyStateTracker } from '@tanstack/hotkeys' +import { createStoreSubscriber } from './internal.svelte' + +export interface SvelteHeldKeys { + readonly keys: Array +} + +class HeldKeysState implements SvelteHeldKeys { + #tracker = getKeyStateTracker() + #subscribe = createStoreSubscriber(this.#tracker.store) + + get keys(): Array { + this.#subscribe() + return this.#tracker.store.state.heldKeys + } +} + +/** + * Svelte function that returns reactive access to currently held keyboard keys. + * + * This function uses the global KeyStateTracker and updates whenever keys are pressed + * or released. + * + * @returns Object with a reactive `keys` property + * + * @example + * ```svelte + * + *
+ * Currently pressed: {heldKeys.keys.join(' + ') || 'None'} + *
+ * ``` + */ +export function getHeldKeys(): SvelteHeldKeys { + return new HeldKeysState() +} diff --git a/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts new file mode 100644 index 0000000..797681c --- /dev/null +++ b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts @@ -0,0 +1,68 @@ +import { getKeyStateTracker } from '@tanstack/hotkeys' +import { createStoreSubscriber } from './internal.svelte' +import type { HeldKey } from '@tanstack/hotkeys' + +export interface SvelteHeldKeyState { + readonly held: boolean +} + +class HeldKeyState implements SvelteHeldKeyState { + #tracker = getKeyStateTracker() + #normalizedKey: string + #subscribe = createStoreSubscriber(this.#tracker.store) + + constructor(key: HeldKey) { + this.#normalizedKey = key.toLowerCase() + } + + get held(): boolean { + this.#subscribe() + return this.#tracker.store.state.heldKeys.some( + (heldKey) => heldKey.toLowerCase() === this.#normalizedKey, + ) + } +} + +/** + * Svelte function that returns reactive access to whether a specific key is currently being held. + * + * This function uses the global KeyStateTracker and updates whenever keys are pressed + * or released. + * + * @param key - The key to check (e.g., 'Shift', 'Control', 'A') + * @returns Object with a reactive `held` property + * + * @example + * ```svelte + * + * + *
+ * {isShiftHeld.held ? 'Shift is pressed!' : 'Press Shift'} + *
+ * ``` + * + * @example + * ```svelte + * + * + *
+ * Ctrl + * Shift + * Alt + *
+ * ``` + */ + +export function getIsKeyHeld(key: HeldKey): SvelteHeldKeyState { + return new HeldKeyState(key) +} diff --git a/packages/svelte-hotkeys/src/index.ts b/packages/svelte-hotkeys/src/index.ts new file mode 100644 index 0000000..0d62f94 --- /dev/null +++ b/packages/svelte-hotkeys/src/index.ts @@ -0,0 +1,17 @@ +// Re-export everything from the core package +export * from '@tanstack/hotkeys' + +export * from './createHotkey.svelte' +export * from './createHotkeySequence.svelte' +export * from './createHotkeyRecorder.svelte' +export * from './getHeldKeys.svelte' +export * from './getHeldKeyCodesMap.svelte' +export * from './getIsKeyHeld.svelte' +export { default as HotkeysProvider } from './HotkeysProvider.svelte' +export { + DEFAULT_OPTIONS, + getDefaultHotkeysOptions, + getHotkeysContext, + setHotkeysContext, +} from './HotkeysCtx' +export type { HotkeysProviderOptions, HotkeysProviderProps } from './HotkeysCtx' diff --git a/packages/svelte-hotkeys/src/internal.svelte.ts b/packages/svelte-hotkeys/src/internal.svelte.ts new file mode 100644 index 0000000..058b925 --- /dev/null +++ b/packages/svelte-hotkeys/src/internal.svelte.ts @@ -0,0 +1,26 @@ +import { createSubscriber } from 'svelte/reactivity' + +export type MaybeGetter = T | (() => T) + +interface SubscribableStore { + state: TState + subscribe: ( + listener: () => void, + ) => (() => void) | { unsubscribe: () => void } +} + +export function resolveMaybeGetter(value: MaybeGetter): T { + return typeof value === 'function' ? (value as () => T)() : value +} + +export function createStoreSubscriber( + store: SubscribableStore, +): () => void { + return createSubscriber((update) => { + const subscription = store.subscribe(update) + + return typeof subscription === 'function' + ? subscription + : () => subscription.unsubscribe() + }) +} diff --git a/packages/svelte-hotkeys/svelte.config.js b/packages/svelte-hotkeys/svelte.config.js new file mode 100644 index 0000000..076d2dc --- /dev/null +++ b/packages/svelte-hotkeys/svelte.config.js @@ -0,0 +1,10 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +const config = { + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, + }, +} + +export default config diff --git a/packages/svelte-hotkeys/tests/createHotkey.test.ts b/packages/svelte-hotkeys/tests/createHotkey.test.ts new file mode 100644 index 0000000..f35fd2f --- /dev/null +++ b/packages/svelte-hotkeys/tests/createHotkey.test.ts @@ -0,0 +1,9 @@ +import { describe, expect, it } from 'vitest' +import { getDefaultHotkeysOptions, getHotkeysContext } from '../src/HotkeysCtx' + +describe('HotkeysCtx', () => { + it('falls back cleanly when no parent context exists', () => { + expect(getHotkeysContext()).toBeNull() + expect(getDefaultHotkeysOptions()).toEqual({}) + }) +}) diff --git a/packages/svelte-hotkeys/tsconfig.docs.json b/packages/svelte-hotkeys/tsconfig.docs.json new file mode 100644 index 0000000..08866d6 --- /dev/null +++ b/packages/svelte-hotkeys/tsconfig.docs.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "paths": { + "@tanstack/hotkeys": ["../hotkeys/src"] + } + }, + "include": ["src"] +} diff --git a/packages/svelte-hotkeys/tsconfig.json b/packages/svelte-hotkeys/tsconfig.json new file mode 100644 index 0000000..07b3300 --- /dev/null +++ b/packages/svelte-hotkeys/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*.js", + "src/**/*.ts", + "src/**/*.svelte", + "src/**/*.svelte.ts", + "svelte.config.js", + "vitest.config.ts" + ], + "exclude": ["eslint.config.js"] +} diff --git a/packages/svelte-hotkeys/vite.config.ts b/packages/svelte-hotkeys/vite.config.ts new file mode 100644 index 0000000..3d696d6 --- /dev/null +++ b/packages/svelte-hotkeys/vite.config.ts @@ -0,0 +1,6 @@ +import { svelte } from '@sveltejs/vite-plugin-svelte' +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/packages/svelte-hotkeys/vitest.config.ts b/packages/svelte-hotkeys/vitest.config.ts new file mode 100644 index 0000000..9d327db --- /dev/null +++ b/packages/svelte-hotkeys/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], + test: { + environment: 'happy-dom', + globals: true, + }, +}) diff --git a/packages/vue-hotkeys-devtools/README.md b/packages/vue-hotkeys-devtools/README.md index 1c80cd6..8c67395 100644 --- a/packages/vue-hotkeys-devtools/README.md +++ b/packages/vue-hotkeys-devtools/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/vue-hotkeys/README.md b/packages/vue-hotkeys/README.md index 1c80cd6..8c67395 100644 --- a/packages/vue-hotkeys/README.md +++ b/packages/vue-hotkeys/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 652fda8..48e96f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,7 +58,7 @@ importers: version: 3.8.1 prettier-plugin-svelte: specifier: ^3.5.1 - version: 3.5.1(prettier@3.8.1)(svelte@5.50.3) + version: 3.5.1(prettier@3.8.1)(svelte@5.53.7) publint: specifier: ^0.3.18 version: 0.3.18 @@ -857,6 +857,101 @@ importers: specifier: ^2.11.10 version: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + examples/svelte/create-hotkey: + dependencies: + '@tanstack/svelte-hotkeys': + specifier: 0.4.1 + version: link:../../../packages/svelte-hotkeys + svelte: + specifier: ^5.53.7 + version: 5.53.7 + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.4 + version: 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) + + examples/svelte/create-hotkey-recorder: + dependencies: + '@tanstack/svelte-hotkeys': + specifier: 0.4.1 + version: link:../../../packages/svelte-hotkeys + svelte: + specifier: ^5.53.7 + version: 5.53.7 + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.4 + version: 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) + + examples/svelte/create-hotkey-sequence: + dependencies: + '@tanstack/svelte-hotkeys': + specifier: 0.4.1 + version: link:../../../packages/svelte-hotkeys + svelte: + specifier: ^5.53.7 + version: 5.53.7 + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.4 + version: 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) + + examples/svelte/get-held-keys: + dependencies: + '@tanstack/svelte-hotkeys': + specifier: 0.4.1 + version: link:../../../packages/svelte-hotkeys + svelte: + specifier: ^5.53.7 + version: 5.53.7 + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.4 + version: 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) + + examples/svelte/get-is-key-held: + dependencies: + '@tanstack/svelte-hotkeys': + specifier: 0.4.1 + version: link:../../../packages/svelte-hotkeys + svelte: + specifier: ^5.53.7 + version: 5.53.7 + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.4 + version: 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) + examples/vue/useHeldKeys: dependencies: '@tanstack/vue-hotkeys': @@ -1176,6 +1271,28 @@ importers: specifier: ^2.11.10 version: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + packages/svelte-hotkeys: + dependencies: + '@tanstack/hotkeys': + specifier: workspace:* + version: link:../hotkeys + devDependencies: + '@sveltejs/package': + specifier: ^2.5.7 + version: 2.5.7(svelte@5.53.7)(typescript@5.9.3) + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.4 + version: 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + '@typescript-eslint/parser': + specifier: ^8.56.1 + version: 8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-svelte: + specifier: ^3.15.0 + version: 3.15.0(eslint@9.39.2(jiti@2.6.1))(svelte@5.53.7) + svelte: + specifier: ^5.53.7 + version: 5.53.7 + packages/vue-hotkeys: dependencies: '@tanstack/hotkeys': @@ -3761,6 +3878,28 @@ packages: peerDependencies: acorn: ^8.9.0 + '@sveltejs/package@2.5.7': + resolution: {integrity: sha512-qqD9xa9H7TDiGFrF6rz7AirOR8k15qDK/9i4MIE8te4vWsv5GEogPks61rrZcLy+yWph+aI6pIj2MdoK3YI8AQ==} + engines: {node: ^16.14 || >=18} + hasBin: true + peerDependencies: + svelte: ^3.44.0 || ^4.0.0 || ^5.0.0-next.1 + + '@sveltejs/vite-plugin-svelte-inspector@5.0.2': + resolution: {integrity: sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^6.0.0-next.0 + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + + '@sveltejs/vite-plugin-svelte@6.2.4': + resolution: {integrity: sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + '@svitejs/changesets-changelog-github-compact@1.2.0': resolution: {integrity: sha512-08eKiDAjj4zLug1taXSIJ0kGL5cawjVCyJkBb6EWSg5fEPX6L+Wtr0CH2If4j5KYylz85iaZiFlUItvgJvll5g==} engines: {node: ^14.13.1 || ^16.0.0 || >=18} @@ -4036,6 +4175,9 @@ packages: '@types/sockjs@0.3.36': resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -4060,22 +4202,45 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/parser@8.56.1': + resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.55.0': resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.56.1': + resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/scope-manager@8.55.0': resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.56.1': + resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.55.0': resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/tsconfig-utils@8.56.1': + resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/type-utils@8.55.0': resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4087,12 +4252,22 @@ packages: resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.56.1': + resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.55.0': resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/typescript-estree@8.56.1': + resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@8.55.0': resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4104,6 +4279,10 @@ packages: resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.56.1': + resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} cpu: [arm] @@ -4473,6 +4652,10 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.1: + resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} + engines: {node: '>= 0.4'} + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -4967,6 +5150,9 @@ packages: supports-color: optional: true + dedent-js@1.0.1: + resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} @@ -4974,6 +5160,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + default-browser-id@5.0.1: resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} engines: {node: '>=18'} @@ -5035,8 +5225,8 @@ packages: detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - devalue@5.6.2: - resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==} + devalue@5.6.3: + resolution: {integrity: sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==} di@0.0.1: resolution: {integrity: sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==} @@ -5347,6 +5537,16 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' + eslint-plugin-svelte@3.15.0: + resolution: {integrity: sha512-QKB7zqfuB8aChOfBTComgDptMf2yxiJx7FE04nneCmtQzgTHvY8UJkuh8J2Rz7KB9FFV9aTHX6r7rdYGvG8T9Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.1 || ^9.0.0 || ^10.0.0 + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + eslint-plugin-unused-imports@4.4.1: resolution: {integrity: sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==} peerDependencies: @@ -5372,6 +5572,10 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@9.39.2: resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5687,6 +5891,10 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + globals@17.3.0: resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==} engines: {node: '>=18'} @@ -6268,6 +6476,10 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + knip@5.86.0: resolution: {integrity: sha512-tGpRCbP+L+VysXnAp1bHTLQ0k/SdC3M3oX18+Cpiqax1qdS25iuCPzpK8LVmAKARZv0Ijri81Wq09Rzk0JTl+Q==} engines: {node: '>=18.18.0'} @@ -6276,6 +6488,9 @@ packages: '@types/node': '>=18' typescript: '>=5.0.4 <7' + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} + kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} @@ -6312,6 +6527,10 @@ packages: webpack: optional: true + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -6981,6 +7200,18 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss-load-config@3.1.4: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + postcss-loader@8.2.0: resolution: {integrity: sha512-tHX+RkpsXVcc7st4dSdDGliI+r4aAQDuv+v3vFYHixb6YgjreG5AG4SEB0kDK8u2s6htqEEpKlkhSBUTvWKYnA==} engines: {node: '>= 18.12.0'} @@ -7027,6 +7258,12 @@ packages: peerDependencies: postcss: ^8.4.31 + postcss-scss@4.0.9: + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + postcss-selector-parser@7.1.1: resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} @@ -7396,6 +7633,9 @@ packages: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + select-hose@2.0.0: resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} @@ -7763,8 +8003,23 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte@5.50.3: - resolution: {integrity: sha512-5JCO8P/cFlwyfi1LeZ9uppMZvuaHWygyZmqxyKOIqbV3PoHKaddvV1C6njL/InpDXplNYZnAVEbn8mLslycBxQ==} + svelte-eslint-parser@1.4.1: + resolution: {integrity: sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.24.0} + peerDependencies: + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + + svelte2tsx@0.7.51: + resolution: {integrity: sha512-YbVMQi5LtQkVGOMdATTY8v3SMtkNjzYtrVDGaN3Bv+0LQ47tGXu/Oc8ryTkcYuEJWTZFJ8G2+2I8ORcQVGt9Ag==} + peerDependencies: + svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 + typescript: ^4.9.4 || ^5.0.0 + + svelte@5.53.7: + resolution: {integrity: sha512-uxck1KI7JWtlfP3H6HOWi/94soAl23jsGJkBzN2BAWcQng0+lTrRNhxActFqORgnO9BHVd1hKJhG+ljRuIUWfQ==} engines: {node: '>=18'} tapable@2.3.0: @@ -8383,6 +8638,10 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} @@ -8571,35 +8830,35 @@ snapshots: '@babel/preset-env': 7.29.0(@babel/core@7.29.0) '@babel/runtime': 7.28.6 '@discoveryjs/json-ext': 0.6.3 - '@ngtools/webpack': 21.2.1(@angular/compiler-cli@21.2.1(@angular/compiler@21.2.1)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2) + '@ngtools/webpack': 21.2.1(@angular/compiler-cli@21.2.1(@angular/compiler@21.2.1)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)) ansi-colors: 4.1.3 autoprefixer: 10.4.27(postcss@8.5.6) - babel-loader: 10.0.0(@babel/core@7.29.0)(webpack@5.105.2) + babel-loader: 10.0.0(@babel/core@7.29.0)(webpack@5.105.2(esbuild@0.27.3)) browserslist: 4.28.1 - copy-webpack-plugin: 14.0.0(webpack@5.105.2) - css-loader: 7.1.3(webpack@5.105.2) + copy-webpack-plugin: 14.0.0(webpack@5.105.2(esbuild@0.27.3)) + css-loader: 7.1.3(webpack@5.105.2(esbuild@0.27.3)) esbuild-wasm: 0.27.3 http-proxy-middleware: 3.0.5 istanbul-lib-instrument: 6.0.3 jsonc-parser: 3.3.1 karma-source-map-support: 1.4.0 less: 4.4.2 - less-loader: 12.3.1(less@4.4.2)(webpack@5.105.2) - license-webpack-plugin: 4.0.2(webpack@5.105.2) + less-loader: 12.3.1(less@4.4.2)(webpack@5.105.2(esbuild@0.27.3)) + license-webpack-plugin: 4.0.2(webpack@5.105.2(esbuild@0.27.3)) loader-utils: 3.3.1 - mini-css-extract-plugin: 2.10.0(webpack@5.105.2) + mini-css-extract-plugin: 2.10.0(webpack@5.105.2(esbuild@0.27.3)) open: 11.0.0 ora: 9.3.0 picomatch: 4.0.3 piscina: 5.1.4 postcss: 8.5.6 - postcss-loader: 8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.2) + postcss-loader: 8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)) resolve-url-loader: 5.0.0 rxjs: 7.8.2 sass: 1.97.3 - sass-loader: 16.0.7(sass@1.97.3)(webpack@5.105.2) + sass-loader: 16.0.7(sass@1.97.3)(webpack@5.105.2(esbuild@0.27.3)) semver: 7.7.4 - source-map-loader: 5.0.0(webpack@5.105.2) + source-map-loader: 5.0.0(webpack@5.105.2(esbuild@0.27.3)) source-map-support: 0.5.21 terser: 5.46.0 tinyglobby: 0.2.15 @@ -8610,7 +8869,7 @@ snapshots: webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) webpack-dev-server: 5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(webpack@5.105.2) + webpack-subresource-integrity: 5.1.0(webpack@5.105.2(esbuild@0.27.3)) optionalDependencies: '@angular/core': 21.2.1(@angular/compiler@21.2.1)(rxjs@7.8.2)(zone.js@0.16.1) '@angular/platform-browser': 21.2.1(@angular/common@21.2.1(@angular/core@21.2.1(@angular/compiler@21.2.1)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.1(@angular/compiler@21.2.1)(rxjs@7.8.2)(zone.js@0.16.1)) @@ -10443,7 +10702,7 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@ngtools/webpack@21.2.1(@angular/compiler-cli@21.2.1(@angular/compiler@21.2.1)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2)': + '@ngtools/webpack@21.2.1(@angular/compiler-cli@21.2.1(@angular/compiler@21.2.1)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3))': dependencies: '@angular/compiler-cli': 21.2.1(@angular/compiler@21.2.1)(typescript@5.9.3) typescript: 5.9.3 @@ -11135,6 +11394,34 @@ snapshots: dependencies: acorn: 8.15.0 + '@sveltejs/package@2.5.7(svelte@5.53.7)(typescript@5.9.3)': + dependencies: + chokidar: 5.0.0 + kleur: 4.1.5 + sade: 1.8.1 + semver: 7.7.4 + svelte: 5.53.7 + svelte2tsx: 0.7.51(svelte@5.53.7)(typescript@5.9.3) + transitivePeerDependencies: + - typescript + + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))': + dependencies: + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + obug: 2.1.1 + svelte: 5.53.7 + vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) + + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + deepmerge: 4.3.1 + magic-string: 0.30.21 + obug: 2.1.1 + svelte: 5.53.7 + vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) + vitefu: 1.1.1(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) + '@svitejs/changesets-changelog-github-compact@1.2.0(encoding@0.1.13)': dependencies: '@changesets/get-github-info': 0.6.0(encoding@0.1.13) @@ -11490,6 +11777,8 @@ snapshots: dependencies: '@types/node': 25.3.5 + '@types/trusted-types@2.0.7': {} + '@types/unist@3.0.3': {} '@types/whatwg-mimetype@3.0.2': {} @@ -11526,6 +11815,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + eslint: 9.39.2(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/project-service@8.55.0(typescript@5.9.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) @@ -11535,15 +11836,33 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.55.0': dependencies: '@typescript-eslint/types': 8.55.0 '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/scope-manager@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + '@typescript-eslint/tsconfig-utils@8.55.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@typescript-eslint/type-utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.55.0 @@ -11558,6 +11877,8 @@ snapshots: '@typescript-eslint/types@8.55.0': {} + '@typescript-eslint/types@8.56.1': {} + '@typescript-eslint/typescript-estree@8.55.0(typescript@5.9.3)': dependencies: '@typescript-eslint/project-service': 8.55.0(typescript@5.9.3) @@ -11573,6 +11894,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) @@ -11589,6 +11925,11 @@ snapshots: '@typescript-eslint/types': 8.55.0 eslint-visitor-keys: 4.2.1 + '@typescript-eslint/visitor-keys@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + eslint-visitor-keys: 5.0.1 + '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -11975,6 +12316,8 @@ snapshots: dependencies: dequal: 2.0.3 + aria-query@5.3.1: {} + aria-query@5.3.2: {} array-buffer-byte-length@1.0.2: @@ -12027,7 +12370,7 @@ snapshots: axobject-query@4.1.0: {} - babel-loader@10.0.0(@babel/core@7.29.0)(webpack@5.105.2): + babel-loader@10.0.0(@babel/core@7.29.0)(webpack@5.105.2(esbuild@0.27.3)): dependencies: '@babel/core': 7.29.0 find-up: 5.0.0 @@ -12429,7 +12772,7 @@ snapshots: dependencies: is-what: 3.14.1 - copy-webpack-plugin@14.0.0(webpack@5.105.2): + copy-webpack-plugin@14.0.0(webpack@5.105.2(esbuild@0.27.3)): dependencies: glob-parent: 6.0.2 normalize-path: 3.0.0 @@ -12464,7 +12807,7 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - css-loader@7.1.3(webpack@5.105.2): + css-loader@7.1.3(webpack@5.105.2(esbuild@0.27.3)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -12519,6 +12862,8 @@ snapshots: dependencies: ms: 2.1.3 + dedent-js@1.0.1: {} + deep-equal@2.2.3: dependencies: array-buffer-byte-length: 1.0.2 @@ -12542,6 +12887,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge@4.3.1: {} + default-browser-id@5.0.1: {} default-browser@5.5.0: @@ -12588,7 +12935,7 @@ snapshots: detect-node@2.1.0: {} - devalue@5.6.2: {} + devalue@5.6.3: {} di@0.0.1: {} @@ -12999,6 +13346,24 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-svelte@3.15.0(eslint@9.39.2(jiti@2.6.1))(svelte@5.53.7): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@jridgewell/sourcemap-codec': 1.5.5 + eslint: 9.39.2(jiti@2.6.1) + esutils: 2.0.3 + globals: 16.5.0 + known-css-properties: 0.37.0 + postcss: 8.5.6 + postcss-load-config: 3.1.4(postcss@8.5.6) + postcss-safe-parser: 7.0.1(postcss@8.5.6) + semver: 7.7.4 + svelte-eslint-parser: 1.4.1(svelte@5.53.7) + optionalDependencies: + svelte: 5.53.7 + transitivePeerDependencies: + - ts-node + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)): dependencies: eslint: 9.39.2(jiti@2.6.1) @@ -13019,6 +13384,8 @@ snapshots: eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.1: {} + eslint@9.39.2(jiti@2.6.1): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) @@ -13426,6 +13793,8 @@ snapshots: globals@15.15.0: {} + globals@16.5.0: {} + globals@17.3.0: {} globby@11.1.0: @@ -14011,6 +14380,8 @@ snapshots: kind-of@6.0.3: {} + kleur@4.1.5: {} + knip@5.86.0(@types/node@25.3.5)(typescript@5.9.3): dependencies: '@nodelib/fs.walk': 1.2.8 @@ -14029,6 +14400,8 @@ snapshots: yaml: 2.8.2 zod: 4.3.6 + known-css-properties@0.37.0: {} + kolorist@1.8.0: {} launch-editor@2.13.0: @@ -14036,7 +14409,7 @@ snapshots: picocolors: 1.1.1 shell-quote: 1.8.3 - less-loader@12.3.1(less@4.4.2)(webpack@5.105.2): + less-loader@12.3.1(less@4.4.2)(webpack@5.105.2(esbuild@0.27.3)): dependencies: less: 4.4.2 optionalDependencies: @@ -14061,12 +14434,14 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - license-webpack-plugin@4.0.2(webpack@5.105.2): + license-webpack-plugin@4.0.2(webpack@5.105.2(esbuild@0.27.3)): dependencies: webpack-sources: 3.3.4 optionalDependencies: webpack: 5.105.2(esbuild@0.27.3) + lilconfig@2.1.0: {} + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -14284,7 +14659,7 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.10.0(webpack@5.105.2): + mini-css-extract-plugin@2.10.0(webpack@5.105.2(esbuild@0.27.3)): dependencies: schema-utils: 4.3.3 tapable: 2.3.0 @@ -14849,7 +15224,14 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.2): + postcss-load-config@3.1.4(postcss@8.5.6): + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.2 + optionalDependencies: + postcss: 8.5.6 + + postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)): dependencies: cosmiconfig: 9.0.0(typescript@5.9.3) jiti: 2.6.1 @@ -14887,6 +15269,10 @@ snapshots: dependencies: postcss: 8.5.6 + postcss-scss@4.0.9(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 @@ -14908,10 +15294,10 @@ snapshots: premove@4.0.0: {} - prettier-plugin-svelte@3.5.1(prettier@3.8.1)(svelte@5.50.3): + prettier-plugin-svelte@3.5.1(prettier@3.8.1)(svelte@5.53.7): dependencies: prettier: 3.8.1 - svelte: 5.50.3 + svelte: 5.53.7 prettier@2.8.8: {} @@ -15258,7 +15644,7 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@16.0.7(sass@1.97.3)(webpack@5.105.2): + sass-loader@16.0.7(sass@1.97.3)(webpack@5.105.2(esbuild@0.27.3)): dependencies: neo-async: 2.6.2 optionalDependencies: @@ -15285,6 +15671,8 @@ snapshots: ajv-formats: 2.1.1(ajv@8.18.0) ajv-keywords: 5.1.0(ajv@8.18.0) + scule@1.3.0: {} + select-hose@2.0.0: {} selfsigned@5.5.0: @@ -15581,7 +15969,7 @@ snapshots: source-map-js@1.2.1: {} - source-map-loader@5.0.0(webpack@5.105.2): + source-map-loader@5.0.0(webpack@5.105.2(esbuild@0.27.3)): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 @@ -15727,17 +16115,36 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte@5.50.3: + svelte-eslint-parser@1.4.1(svelte@5.53.7): + dependencies: + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + postcss: 8.5.6 + postcss-scss: 4.0.9(postcss@8.5.6) + postcss-selector-parser: 7.1.1 + optionalDependencies: + svelte: 5.53.7 + + svelte2tsx@0.7.51(svelte@5.53.7)(typescript@5.9.3): + dependencies: + dedent-js: 1.0.1 + scule: 1.3.0 + svelte: 5.53.7 + typescript: 5.9.3 + + svelte@5.53.7: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) '@types/estree': 1.0.8 + '@types/trusted-types': 2.0.7 acorn: 8.15.0 - aria-query: 5.3.2 + aria-query: 5.3.1 axobject-query: 4.1.0 clsx: 2.1.1 - devalue: 5.6.2 + devalue: 5.6.3 esm-env: 1.2.2 esrap: 2.2.3 is-reference: 3.0.3 @@ -16211,7 +16618,7 @@ snapshots: webpack-sources@3.3.4: {} - webpack-subresource-integrity@5.1.0(webpack@5.105.2): + webpack-subresource-integrity@5.1.0(webpack@5.105.2(esbuild@0.27.3)): dependencies: typed-assert: 1.0.9 webpack: 5.105.2(esbuild@0.27.3) @@ -16358,6 +16765,8 @@ snapshots: yallist@5.0.0: {} + yaml@1.10.2: {} + yaml@2.8.2: {} yargs-parser@20.2.9: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5add109..2113f35 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,3 +5,5 @@ preferWorkspacePackages: true packages: - 'examples/**/*' - 'packages/*' +onlyBuiltDependencies: + - esbuild diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 0c9ebdf..8047ed2 100644 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -70,6 +70,18 @@ await generateReferenceDocs({ outputDir: resolve(__dirname, '../docs/framework/vue/reference'), exclude: ['packages/hotkeys/**/*'], }, + { + name: 'svelte-hotkeys', + entryPoints: [ + resolve(__dirname, '../packages/svelte-hotkeys/src/index.ts'), + ], + tsconfig: resolve( + __dirname, + '../packages/svelte-hotkeys/tsconfig.docs.json', + ), + outputDir: resolve(__dirname, '../docs/framework/svelte/reference'), + exclude: ['packages/hotkeys/**/*'], + }, ], }) diff --git a/vitest.workspace.ts b/vitest.workspace.ts index 5d00262..0e0f245 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -12,6 +12,8 @@ export default defineConfig({ './packages/solid-hotkeys-devtools/vitest.config.ts', './packages/solid-hotkeys/vitest.config.ts', './packages/angular-hotkeys/vitest.config.ts', + './packages/vue-hotkeys/vitest.config.ts', + './packages/svelte-hotkeys/vitest.config.ts', ], }, })