diff --git a/README.md b/README.md
index d627591..1c80cd6 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
- 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, React hooks, and devtools (in progress)
+- Batteries included — validation + matching, sequences (Vim-style), key-state tracking, recorder UI helpers, framework adapters, and devtools
### Read the docs →
@@ -57,8 +57,8 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
> - [**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 – needs a contributor!
-> - Vue Hotkeys – needs a contributor!
## Get Involved
diff --git a/docs/config.json b/docs/config.json
index 34bf384..544aa64 100644
--- a/docs/config.json
+++ b/docs/config.json
@@ -49,6 +49,24 @@
"to": "framework/solid/reference/index"
}
]
+ },
+ {
+ "label": "angular",
+ "children": [
+ {
+ "label": "Quick Start",
+ "to": "framework/angular/quick-start"
+ }
+ ]
+ },
+ {
+ "label": "vue",
+ "children": [
+ {
+ "label": "Quick Start",
+ "to": "framework/vue/quick-start"
+ }
+ ]
}
]
},
@@ -130,6 +148,56 @@
"to": "framework/solid/guides/formatting-display"
}
]
+ },
+ {
+ "label": "angular",
+ "children": [
+ {
+ "label": "Hotkeys",
+ "to": "framework/angular/guides/hotkeys"
+ },
+ {
+ "label": "Sequences",
+ "to": "framework/angular/guides/sequences"
+ },
+ {
+ "label": "Hotkey Recording",
+ "to": "framework/angular/guides/hotkey-recording"
+ },
+ {
+ "label": "Key State Tracking",
+ "to": "framework/angular/guides/key-state-tracking"
+ },
+ {
+ "label": "Formatting & Display",
+ "to": "framework/angular/guides/formatting-display"
+ }
+ ]
+ },
+ {
+ "label": "vue",
+ "children": [
+ {
+ "label": "Hotkeys",
+ "to": "framework/vue/guides/hotkeys"
+ },
+ {
+ "label": "Sequences",
+ "to": "framework/vue/guides/sequences"
+ },
+ {
+ "label": "Hotkey Recording",
+ "to": "framework/vue/guides/hotkey-recording"
+ },
+ {
+ "label": "Key State Tracking",
+ "to": "framework/vue/guides/key-state-tracking"
+ },
+ {
+ "label": "Formatting & Display",
+ "to": "framework/vue/guides/formatting-display"
+ }
+ ]
}
]
},
@@ -177,6 +245,15 @@
"to": "framework/angular/reference/index"
}
]
+ },
+ {
+ "label": "vue",
+ "children": [
+ {
+ "label": "Vue Composables",
+ "to": "framework/vue/reference/index"
+ }
+ ]
}
]
},
@@ -278,6 +355,27 @@
"to": "framework/angular/reference/interfaces/HotkeysProviderOptions"
}
]
+ },
+ {
+ "label": "vue",
+ "children": [
+ {
+ "label": "useHotkey",
+ "to": "framework/vue/reference/functions/useHotkey"
+ },
+ {
+ "label": "UseHotkeyOptions",
+ "to": "framework/vue/reference/interfaces/UseHotkeyOptions"
+ },
+ {
+ "label": "provideHotkeysContext",
+ "to": "framework/vue/reference/functions/provideHotkeysContext"
+ },
+ {
+ "label": "HotkeysProviderOptions",
+ "to": "framework/vue/reference/interfaces/HotkeysProviderOptions"
+ }
+ ]
}
]
},
@@ -359,6 +457,19 @@
"to": "framework/angular/reference/interfaces/InjectHotkeySequenceOptions"
}
]
+ },
+ {
+ "label": "vue",
+ "children": [
+ {
+ "label": "useHotkeySequence",
+ "to": "framework/vue/reference/functions/useHotkeySequence"
+ },
+ {
+ "label": "UseHotkeySequenceOptions",
+ "to": "framework/vue/reference/interfaces/UseHotkeySequenceOptions"
+ }
+ ]
}
]
},
@@ -420,6 +531,15 @@
"to": "framework/angular/reference/functions/injectKeyHold"
}
]
+ },
+ {
+ "label": "vue",
+ "children": [
+ {
+ "label": "useKeyHold",
+ "to": "framework/vue/reference/functions/useKeyHold"
+ }
+ ]
}
]
},
@@ -497,6 +617,19 @@
"to": "framework/angular/reference/functions/injectHeldKeyCodes"
}
]
+ },
+ {
+ "label": "vue",
+ "children": [
+ {
+ "label": "useHeldKeys",
+ "to": "framework/vue/reference/functions/useHeldKeys"
+ },
+ {
+ "label": "useHeldKeyCodes",
+ "to": "framework/vue/reference/functions/useHeldKeyCodes"
+ }
+ ]
}
]
},
@@ -570,6 +703,19 @@
"to": "framework/angular/reference/interfaces/AngularHotkeyRecorder"
}
]
+ },
+ {
+ "label": "vue",
+ "children": [
+ {
+ "label": "useHotkeyRecorder",
+ "to": "framework/vue/reference/functions/useHotkeyRecorder"
+ },
+ {
+ "label": "VueHotkeyRecorder",
+ "to": "framework/vue/reference/interfaces/VueHotkeyRecorder"
+ }
+ ]
}
]
},
@@ -710,6 +856,56 @@
"to": "framework/solid/examples/createKeyHold"
}
]
+ },
+ {
+ "label": "angular",
+ "children": [
+ {
+ "label": "injectHotkey",
+ "to": "framework/angular/examples/injectHotkey"
+ },
+ {
+ "label": "injectHotkeySequence",
+ "to": "framework/angular/examples/injectHotkeySequence"
+ },
+ {
+ "label": "injectHotkeyRecorder",
+ "to": "framework/angular/examples/injectHotkeyRecorder"
+ },
+ {
+ "label": "injectHeldKeys",
+ "to": "framework/angular/examples/injectHeldKeys"
+ },
+ {
+ "label": "injectKeyHold",
+ "to": "framework/angular/examples/injectKeyHold"
+ }
+ ]
+ },
+ {
+ "label": "vue",
+ "children": [
+ {
+ "label": "useHotkey",
+ "to": "framework/vue/examples/useHotkey"
+ },
+ {
+ "label": "useHotkeySequence",
+ "to": "framework/vue/examples/useHotkeySequence"
+ },
+ {
+ "label": "useHotkeyRecorder",
+ "to": "framework/vue/examples/useHotkeyRecorder"
+ },
+ {
+ "label": "useHeldKeys",
+ "to": "framework/vue/examples/useHeldKeys"
+ },
+ {
+ "label": "useKeyHold",
+ "to": "framework/vue/examples/useKeyhold"
+ }
+ ]
}
]
}
diff --git a/docs/devtools.md b/docs/devtools.md
index 8402336..e1d0443 100644
--- a/docs/devtools.md
+++ b/docs/devtools.md
@@ -27,6 +27,26 @@ Install the devtools packages for your framework:
npm install @tanstack/react-devtools @tanstack/react-hotkeys-devtools
```
+### Preact
+
+```sh
+npm install @tanstack/preact-devtools @tanstack/preact-hotkeys-devtools
+```
+
+### Solid
+
+```sh
+npm install @tanstack/solid-devtools @tanstack/solid-hotkeys-devtools
+```
+
+### Vue
+
+```sh
+npm install @tanstack/vue-hotkeys-devtools
+```
+
+Angular does not currently ship a dedicated hotkeys devtools adapter.
+
## Setup
### React Setup
@@ -36,32 +56,53 @@ import { TanStackDevtools } from '@tanstack/react-devtools'
import { hotkeysDevtoolsPlugin } from '@tanstack/react-hotkeys-devtools'
function App() {
- return (
-
- {/* Your app content */}
-
-
-
- )
+ return
}
```
-The devtools panel will appear as a tab in the TanStack Devtools UI, alongside any other TanStack devtools plugins you may have installed (e.g., Query devtools, Pacer devtools).
+### Preact Setup
-## Production Builds
+```tsx
+import { TanStackDevtools } from '@tanstack/preact-devtools'
+import { hotkeysDevtoolsPlugin } from '@tanstack/preact-hotkeys-devtools'
-By default, devtools are excluded from production builds to minimize bundle size. The default imports will return no-op implementations in production:
+export function App() {
+ return
+}
+```
+
+### Solid Setup
```tsx
-// This will be a no-op in production builds
-import { hotkeysDevtoolsPlugin } from '@tanstack/react-hotkeys-devtools'
+import { TanStackDevtools } from '@tanstack/solid-devtools'
+import { hotkeysDevtoolsPlugin } from '@tanstack/solid-hotkeys-devtools'
+
+export function App() {
+ return
+}
+```
+
+### Vue Setup
+
+```vue
+
+
+
+
+
+
```
-If you need to include devtools in production builds (e.g., for debugging production issues), use the production-specific imports:
+For React, Preact, and Solid, the Hotkeys panel appears alongside any other TanStack devtools plugins you have installed.
+
+## Production Builds
+
+By default, the framework devtools adapters return no-op implementations in production builds so they do not affect your production bundle behavior.
+
+React additionally exposes a production import when you explicitly want to include the plugin in production:
```tsx
-// This will include full devtools even in production builds
import { hotkeysDevtoolsPlugin } from '@tanstack/react-hotkeys-devtools/production'
```
diff --git a/docs/framework/angular/guides/formatting-display.md b/docs/framework/angular/guides/formatting-display.md
new file mode 100644
index 0000000..f21f7b7
--- /dev/null
+++ b/docs/framework/angular/guides/formatting-display.md
@@ -0,0 +1,88 @@
+---
+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 Angular templates and signals.
+
+## `formatForDisplay`
+
+```ts
+import { formatForDisplay } from '@tanstack/angular-hotkeys'
+
+formatForDisplay('Mod+S')
+formatForDisplay('Mod+Shift+Z')
+```
+
+## `formatWithLabels`
+
+```ts
+import { formatWithLabels } from '@tanstack/angular-hotkeys'
+
+formatWithLabels('Mod+S')
+formatWithLabels('Mod+Shift+Z')
+```
+
+## `formatKeyForDebuggingDisplay`
+
+```ts
+import { formatKeyForDebuggingDisplay } from '@tanstack/angular-hotkeys'
+
+formatKeyForDebuggingDisplay('Meta')
+formatKeyForDebuggingDisplay('Shift')
+```
+
+## Using Formatted Hotkeys in Angular
+
+### Keyboard Shortcut Badges
+
+```ts
+import { Component, input } from '@angular/core'
+import { formatForDisplay } from '@tanstack/angular-hotkeys'
+
+@Component({
+ standalone: true,
+ selector: 'app-shortcut-badge',
+ template: `{{ formatForDisplay(hotkey()) }} `,
+})
+export class ShortcutBadgeComponent {
+ readonly hotkey = input.required()
+ readonly formatForDisplay = formatForDisplay
+}
+```
+
+### Menu Items with Hotkeys
+
+```ts
+import { Component, input } from '@angular/core'
+import { formatForDisplay, injectHotkey } from '@tanstack/angular-hotkeys'
+
+@Component({
+ standalone: true,
+ selector: 'app-menu-item',
+ template: `
+
+ `,
+})
+export class MenuItemComponent {
+ readonly label = input.required()
+ readonly hotkey = input.required()
+ readonly onAction = input.required<() => void>()
+ readonly formatForDisplay = formatForDisplay
+
+ constructor() {
+ injectHotkey(() => this.hotkey(), () => this.onAction()())
+ }
+}
+```
+
+## Validation
+
+```ts
+import { validateHotkey } from '@tanstack/angular-hotkeys'
+
+const result = validateHotkey('Alt+A')
+```
diff --git a/docs/framework/angular/guides/hotkey-recording.md b/docs/framework/angular/guides/hotkey-recording.md
new file mode 100644
index 0000000..3c12a17
--- /dev/null
+++ b/docs/framework/angular/guides/hotkey-recording.md
@@ -0,0 +1,85 @@
+---
+title: Hotkey Recording Guide
+id: hotkey-recording
+---
+
+TanStack Hotkeys provides the `injectHotkeyRecorder` API for building shortcut customization UIs in Angular.
+
+## Basic Usage
+
+```ts
+import { Component } from '@angular/core'
+import {
+ formatForDisplay,
+ injectHotkeyRecorder,
+} from '@tanstack/angular-hotkeys'
+
+@Component({
+ standalone: true,
+ template: `
+
+ {{
+ recorder.isRecording()
+ ? 'Press a key combination...'
+ : recorder.recordedHotkey()
+ ? formatForDisplay(recorder.recordedHotkey()!)
+ : 'Click to record'
+ }}
+
+ @if (recorder.isRecording()) {
+ Cancel
+ }
+ `,
+})
+export class ShortcutRecorderComponent {
+ readonly formatForDisplay = formatForDisplay
+ readonly recorder = injectHotkeyRecorder({
+ onRecord: (hotkey) => {
+ console.log('Recorded:', hotkey)
+ },
+ })
+}
+```
+
+## Return Value
+
+- `isRecording()`: Angular signal getter indicating whether recording is active
+- `recordedHotkey()`: Angular signal getter with 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
+injectHotkeyRecorder({
+ onRecord: (hotkey) => {},
+ onCancel: () => {},
+ onClear: () => {},
+})
+```
+
+## Global Default Options via Provider
+
+```ts
+import { ApplicationConfig } from '@angular/core'
+import { provideHotkeys } from '@tanstack/angular-hotkeys'
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ provideHotkeys({
+ hotkeyRecorder: {
+ onCancel: () => console.log('Recording cancelled'),
+ },
+ }),
+ ],
+}
+```
+
+## 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/angular/guides/hotkeys.md b/docs/framework/angular/guides/hotkeys.md
new file mode 100644
index 0000000..3bbfe19
--- /dev/null
+++ b/docs/framework/angular/guides/hotkeys.md
@@ -0,0 +1,150 @@
+---
+title: Hotkeys Guide
+id: hotkeys
+---
+
+The `injectHotkey` API is the primary way to register keyboard shortcuts in Angular applications. It wraps the singleton `HotkeyManager` with injection-context lifecycle management and Angular signal-friendly reactive options.
+
+## Basic Usage
+
+```ts
+import { Component } from '@angular/core'
+import { injectHotkey } from '@tanstack/angular-hotkeys'
+
+@Component({ standalone: true, template: `` })
+export class AppComponent {
+ constructor() {
+ injectHotkey('Mod+S', () => {
+ saveDocument()
+ })
+ }
+}
+```
+
+The callback receives the original `KeyboardEvent` as the first argument and a `HotkeyCallbackContext` as the second:
+
+```ts
+injectHotkey('Mod+S', (event, context) => {
+ console.log(context.hotkey)
+ console.log(context.parsedHotkey)
+})
+```
+
+## Default Options
+
+`injectHotkey` uses the same core defaults as the framework-agnostic manager:
+
+```ts
+injectHotkey('Mod+S', callback, {
+ enabled: true,
+ preventDefault: true,
+ stopPropagation: true,
+ eventType: 'keydown',
+ requireReset: false,
+ ignoreInputs: undefined,
+ target: document,
+ platform: undefined,
+ conflictBehavior: 'warn',
+})
+```
+
+## Reactive Options
+
+For reactive state, pass an accessor function as the third argument.
+
+### `enabled`
+
+```ts
+import { Component, signal } from '@angular/core'
+import { injectHotkey } from '@tanstack/angular-hotkeys'
+
+@Component({ standalone: true, template: `` })
+export class EditorComponent {
+ readonly isEditing = signal(false)
+
+ constructor() {
+ injectHotkey('Mod+S', () => save(), () => ({
+ enabled: this.isEditing(),
+ }))
+ }
+}
+```
+
+### `target`
+
+```ts
+import { Component, ElementRef, viewChild } from '@angular/core'
+import { injectHotkey } from '@tanstack/angular-hotkeys'
+
+@Component({
+ standalone: true,
+ template: `Panel content
`,
+})
+export class PanelComponent {
+ private readonly panel = viewChild>('panel')
+
+ constructor() {
+ injectHotkey('Escape', () => closePanel(), () => ({
+ target: this.panel()?.nativeElement ?? null,
+ }))
+ }
+}
+```
+
+## Global Default Options via Provider
+
+```ts
+import { ApplicationConfig } from '@angular/core'
+import { provideHotkeys } from '@tanstack/angular-hotkeys'
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ provideHotkeys({
+ hotkey: { preventDefault: false, ignoreInputs: false },
+ }),
+ ],
+}
+```
+
+## Common Options
+
+### `requireReset`
+
+```ts
+injectHotkey('Escape', () => closePanel(), { requireReset: true })
+```
+
+### `ignoreInputs`
+
+```ts
+injectHotkey('K', () => openSearch())
+injectHotkey('Enter', () => submit(), { ignoreInputs: false })
+```
+
+### `conflictBehavior`
+
+```ts
+injectHotkey('Mod+S', () => save(), { conflictBehavior: 'replace' })
+```
+
+### `platform`
+
+```ts
+injectHotkey('Mod+S', () => save(), { platform: 'mac' })
+```
+
+## Automatic Cleanup
+
+Registrations are cleaned up automatically when the owning injection context is destroyed.
+
+## The Hotkey Manager
+
+You can access the underlying manager directly when needed:
+
+```ts
+import { getHotkeyManager } from '@tanstack/angular-hotkeys'
+
+const manager = getHotkeyManager()
+manager.isRegistered('Mod+S')
+manager.getRegistrationCount()
+```
diff --git a/docs/framework/angular/guides/key-state-tracking.md b/docs/framework/angular/guides/key-state-tracking.md
new file mode 100644
index 0000000..835f787
--- /dev/null
+++ b/docs/framework/angular/guides/key-state-tracking.md
@@ -0,0 +1,94 @@
+---
+title: Key State Tracking Guide
+id: key-state-tracking
+---
+
+TanStack Hotkeys provides three Angular APIs for tracking live keyboard state: `injectHeldKeys`, `injectHeldKeyCodes`, and `injectKeyHold`.
+
+## `injectHeldKeys`
+
+```ts
+import { Component } from '@angular/core'
+import { injectHeldKeys } from '@tanstack/angular-hotkeys'
+
+@Component({
+ standalone: true,
+ template: `
+
+ {{ heldKeys().length > 0 ? heldKeys().join(' + ') : 'No keys held' }}
+
+ `,
+})
+export class KeyDisplayComponent {
+ readonly heldKeys = injectHeldKeys()
+}
+```
+
+## `injectHeldKeyCodes`
+
+```ts
+import { injectHeldKeyCodes } from '@tanstack/angular-hotkeys'
+
+readonly heldCodes = injectHeldKeyCodes()
+```
+
+## `injectKeyHold`
+
+```ts
+import { injectKeyHold } from '@tanstack/angular-hotkeys'
+
+readonly isShiftHeld = injectKeyHold('Shift')
+```
+
+## Common Patterns
+
+### Hold-to-Reveal UI
+
+```ts
+import { Component } from '@angular/core'
+import { injectKeyHold } from '@tanstack/angular-hotkeys'
+
+@Component({
+ standalone: true,
+ template: `
+ @if (isShiftHeld()) {
+ Permanently Delete
+ } @else {
+ Move to Trash
+ }
+ `,
+})
+export class FileActionsComponent {
+ readonly isShiftHeld = injectKeyHold('Shift')
+}
+```
+
+### Debugging Key Display
+
+```ts
+import { Component } from '@angular/core'
+import {
+ formatKeyForDebuggingDisplay,
+ injectHeldKeyCodes,
+ injectHeldKeys,
+} from '@tanstack/angular-hotkeys'
+
+@Component({ standalone: true, template: `` })
+export class KeyDebuggerComponent {
+ readonly heldKeys = injectHeldKeys()
+ readonly heldCodes = injectHeldKeyCodes()
+ readonly formatKey = formatKeyForDebuggingDisplay
+}
+```
+
+## Under the Hood
+
+All three APIs subscribe to the singleton `KeyStateTracker`:
+
+```ts
+import { getKeyStateTracker } from '@tanstack/angular-hotkeys'
+
+const tracker = getKeyStateTracker()
+tracker.getHeldKeys()
+tracker.isKeyHeld('Shift')
+```
diff --git a/docs/framework/angular/guides/sequences.md b/docs/framework/angular/guides/sequences.md
new file mode 100644
index 0000000..5542999
--- /dev/null
+++ b/docs/framework/angular/guides/sequences.md
@@ -0,0 +1,100 @@
+---
+title: Sequences Guide
+id: sequences
+---
+
+TanStack Hotkeys supports multi-key sequences in Angular, where keys are pressed one after another rather than simultaneously.
+
+## Basic Usage
+
+```ts
+import { Component } from '@angular/core'
+import { injectHotkeySequence } from '@tanstack/angular-hotkeys'
+
+@Component({ standalone: true, template: `` })
+export class AppComponent {
+ constructor() {
+ injectHotkeySequence(['G', 'G'], () => {
+ window.scrollTo({ top: 0, behavior: 'smooth' })
+ })
+ }
+}
+```
+
+## Sequence Options
+
+```ts
+injectHotkeySequence(['G', 'G'], callback, {
+ timeout: 1000,
+ enabled: true,
+})
+```
+
+### Reactive `enabled`
+
+```ts
+import { Component, signal } from '@angular/core'
+import { injectHotkeySequence } from '@tanstack/angular-hotkeys'
+
+@Component({ standalone: true, template: `` })
+export class VimModeComponent {
+ readonly isVimMode = signal(true)
+
+ constructor() {
+ injectHotkeySequence(['G', 'G'], () => scrollToTop(), () => ({
+ enabled: this.isVimMode(),
+ }))
+ }
+}
+```
+
+## Global Default Options via Provider
+
+```ts
+import { ApplicationConfig } from '@angular/core'
+import { provideHotkeys } from '@tanstack/angular-hotkeys'
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ provideHotkeys({
+ hotkeySequence: { timeout: 1500 },
+ }),
+ ],
+}
+```
+
+## Common Patterns
+
+### Vim-Style Navigation
+
+```ts
+injectHotkeySequence(['G', 'G'], () => scrollToTop())
+injectHotkeySequence(['G', 'Shift+G'], () => scrollToBottom())
+injectHotkeySequence(['D', 'D'], () => deleteLine())
+injectHotkeySequence(['D', 'W'], () => deleteWord())
+injectHotkeySequence(['C', 'I', 'W'], () => changeInnerWord())
+```
+
+### Konami Code
+
+```ts
+injectHotkeySequence(
+ ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'B', 'A'],
+ () => enableEasterEgg(),
+ { timeout: 2000 },
+)
+```
+
+## Under the Hood
+
+`injectHotkeySequence` uses the singleton `SequenceManager`. You can also access it directly:
+
+```ts
+import {
+ createSequenceMatcher,
+ getSequenceManager,
+} from '@tanstack/angular-hotkeys'
+
+const manager = getSequenceManager()
+const matcher = createSequenceMatcher(['G', 'G'], { timeout: 1000 })
+```
diff --git a/docs/framework/angular/quick-start.md b/docs/framework/angular/quick-start.md
new file mode 100644
index 0000000..5147b11
--- /dev/null
+++ b/docs/framework/angular/quick-start.md
@@ -0,0 +1,171 @@
+---
+title: Quick Start
+id: quick-start
+---
+
+## Installation
+
+Don't have TanStack Hotkeys installed yet? See the [Installation](../../installation) page for instructions.
+
+## Your First Hotkey
+
+The `injectHotkey` API is the primary way to register keyboard shortcuts in Angular:
+
+```ts
+import { Component } from '@angular/core'
+import { injectHotkey } from '@tanstack/angular-hotkeys'
+
+@Component({
+ selector: 'app-root',
+ standalone: true,
+ template: `Press Cmd+S (Mac) or Ctrl+S (Windows) to save
`,
+})
+export class AppComponent {
+ constructor() {
+ injectHotkey('Mod+S', () => {
+ saveDocument()
+ })
+ }
+}
+```
+
+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 Hotkeys
+
+```ts
+constructor() {
+ injectHotkey('Mod+S', () => save())
+ injectHotkey('Mod+Z', () => undo())
+ injectHotkey('Mod+Shift+Z', () => redo())
+ injectHotkey('Mod+F', () => openSearch())
+ injectHotkey('Escape', () => closeDialog())
+}
+```
+
+### Scoped Hotkeys with `viewChild`
+
+```ts
+import { Component, ElementRef, viewChild } from '@angular/core'
+import { injectHotkey } from '@tanstack/angular-hotkeys'
+
+@Component({
+ standalone: true,
+ template: `
+
+
Press Escape while focused here to close
+
+ `,
+})
+export class PanelComponent {
+ private readonly panel = viewChild>('panel')
+
+ constructor() {
+ injectHotkey('Escape', () => closePanel(), () => ({
+ target: this.panel()?.nativeElement ?? null,
+ }))
+ }
+}
+```
+
+### Conditional Hotkeys
+
+```ts
+import { Component, signal } from '@angular/core'
+import { injectHotkey } from '@tanstack/angular-hotkeys'
+
+@Component({ standalone: true, template: `` })
+export class ModalComponent {
+ readonly isOpen = signal(true)
+
+ constructor() {
+ injectHotkey('Escape', () => this.isOpen.set(false), () => ({
+ enabled: this.isOpen(),
+ }))
+ }
+}
+```
+
+### Multi-Key Sequences
+
+```ts
+import { injectHotkeySequence } from '@tanstack/angular-hotkeys'
+
+constructor() {
+ injectHotkeySequence(['G', 'G'], () => scrollToTop())
+ injectHotkeySequence(['G', 'Shift+G'], () => scrollToBottom())
+}
+```
+
+### Tracking Held Keys
+
+```ts
+import { Component } from '@angular/core'
+import { injectHeldKeys, injectKeyHold } from '@tanstack/angular-hotkeys'
+
+@Component({
+ standalone: true,
+ template: `
+
+ @if (isShiftHeld()) {
+ Shift mode active
+ }
+ @if (heldKeys().length > 0) {
+ Keys: {{ heldKeys().join('+') }}
+ }
+
+ `,
+})
+export class StatusBarComponent {
+ readonly heldKeys = injectHeldKeys()
+ readonly isShiftHeld = injectKeyHold('Shift')
+}
+```
+
+### Displaying Hotkeys in the UI
+
+```ts
+import { Component } from '@angular/core'
+import { formatForDisplay, injectHotkey } from '@tanstack/angular-hotkeys'
+
+@Component({
+ standalone: true,
+ template: `Save {{ saveLabel }} `,
+})
+export class SaveButtonComponent {
+ readonly saveLabel = formatForDisplay('Mod+S')
+
+ constructor() {
+ injectHotkey('Mod+S', () => save())
+ }
+}
+```
+
+## Default Options Provider
+
+Use `provideHotkeys` to configure default options for your Angular app:
+
+```ts
+import { ApplicationConfig } from '@angular/core'
+import { provideHotkeys } from '@tanstack/angular-hotkeys'
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ provideHotkeys({
+ hotkey: { preventDefault: true },
+ hotkeySequence: { timeout: 1500 },
+ hotkeyRecorder: { onCancel: () => console.log('Recording cancelled') },
+ }),
+ ],
+}
+```
+
+## 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/react/reference/functions/useDefaultHotkeysOptions.md b/docs/framework/react/reference/functions/useDefaultHotkeysOptions.md
index 88f1f99..4d16fb7 100644
--- a/docs/framework/react/reference/functions/useDefaultHotkeysOptions.md
+++ b/docs/framework/react/reference/functions/useDefaultHotkeysOptions.md
@@ -9,7 +9,7 @@ title: useDefaultHotkeysOptions
function useDefaultHotkeysOptions(): HotkeysProviderOptions;
```
-Defined in: [HotkeysProvider.tsx:48](https://github.com/TanStack/hotkeys/blob/main/packages/react-hotkeys/src/HotkeysProvider.tsx#L48)
+Defined in: [HotkeysProvider.tsx:50](https://github.com/TanStack/hotkeys/blob/main/packages/react-hotkeys/src/HotkeysProvider.tsx#L50)
## Returns
diff --git a/docs/framework/react/reference/functions/useHotkeysContext.md b/docs/framework/react/reference/functions/useHotkeysContext.md
index 9db00a6..d5d3d9e 100644
--- a/docs/framework/react/reference/functions/useHotkeysContext.md
+++ b/docs/framework/react/reference/functions/useHotkeysContext.md
@@ -9,7 +9,7 @@ title: useHotkeysContext
function useHotkeysContext(): HotkeysContextValue | null;
```
-Defined in: [HotkeysProvider.tsx:44](https://github.com/TanStack/hotkeys/blob/main/packages/react-hotkeys/src/HotkeysProvider.tsx#L44)
+Defined in: [HotkeysProvider.tsx:45](https://github.com/TanStack/hotkeys/blob/main/packages/react-hotkeys/src/HotkeysProvider.tsx#L45)
## Returns
diff --git a/docs/framework/vue/guides/formatting-display.md b/docs/framework/vue/guides/formatting-display.md
new file mode 100644
index 0000000..820c8b6
--- /dev/null
+++ b/docs/framework/vue/guides/formatting-display.md
@@ -0,0 +1,80 @@
+---
+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 Vue templates and computed UI.
+
+## `formatForDisplay`
+
+```ts
+import { formatForDisplay } from '@tanstack/vue-hotkeys'
+
+formatForDisplay('Mod+S')
+formatForDisplay('Mod+Shift+Z')
+```
+
+## `formatWithLabels`
+
+```ts
+import { formatWithLabels } from '@tanstack/vue-hotkeys'
+
+formatWithLabels('Mod+S')
+formatWithLabels('Mod+Shift+Z')
+```
+
+## `formatKeyForDebuggingDisplay`
+
+```ts
+import { formatKeyForDebuggingDisplay } from '@tanstack/vue-hotkeys'
+
+formatKeyForDebuggingDisplay('Meta')
+formatKeyForDebuggingDisplay('Shift')
+```
+
+## Using Formatted Hotkeys in Vue
+
+### Keyboard Shortcut Badges
+
+```vue
+
+
+
+ {{ formatForDisplay(hotkey) }}
+
+```
+
+### Menu Items with Hotkeys
+
+```vue
+
+
+
+
+
+```
+
+## Validation
+
+```ts
+import { validateHotkey } from '@tanstack/vue-hotkeys'
+
+const result = validateHotkey('Alt+A')
+```
diff --git a/docs/framework/vue/guides/hotkey-recording.md b/docs/framework/vue/guides/hotkey-recording.md
new file mode 100644
index 0000000..f08e93f
--- /dev/null
+++ b/docs/framework/vue/guides/hotkey-recording.md
@@ -0,0 +1,83 @@
+---
+title: Hotkey Recording Guide
+id: hotkey-recording
+---
+
+TanStack Hotkeys provides the `useHotkeyRecorder` composable for building shortcut customization UIs in Vue.
+
+## Basic Usage
+
+```vue
+
+
+
+
+
+ {{
+ recorder.isRecording
+ ? 'Press a key combination...'
+ : recorder.recordedHotkey
+ ? formatForDisplay(recorder.recordedHotkey)
+ : 'Click to record'
+ }}
+
+
+ Cancel
+
+
+
+```
+
+## Return Value
+
+- `isRecording`: reactive ref-like value indicating whether recording is active
+- `recordedHotkey`: reactive ref-like value with 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
+useHotkeyRecorder({
+ onRecord: (hotkey) => {},
+ onCancel: () => {},
+ onClear: () => {},
+})
+```
+
+## Global Default Options via Provider
+
+```vue
+
+
+
+
+
+
+
+```
+
+## 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/vue/guides/hotkeys.md b/docs/framework/vue/guides/hotkeys.md
new file mode 100644
index 0000000..d25215a
--- /dev/null
+++ b/docs/framework/vue/guides/hotkeys.md
@@ -0,0 +1,140 @@
+---
+title: Hotkeys Guide
+id: hotkeys
+---
+
+The `useHotkey` composable is the primary way to register keyboard shortcuts in Vue applications. It wraps the singleton `HotkeyManager` with automatic cleanup, support for template refs, and reactive option syncing.
+
+## Basic Usage
+
+```vue
+
+```
+
+The callback receives the original `KeyboardEvent` as the first argument and a `HotkeyCallbackContext` as the second:
+
+```ts
+useHotkey('Mod+S', (event, context) => {
+ console.log(context.hotkey)
+ console.log(context.parsedHotkey)
+})
+```
+
+## Default Options
+
+`useHotkey` uses the same core defaults as the framework-agnostic manager:
+
+```ts
+useHotkey('Mod+S', callback, {
+ enabled: true,
+ preventDefault: true,
+ stopPropagation: true,
+ eventType: 'keydown',
+ requireReset: false,
+ ignoreInputs: undefined,
+ target: document,
+ platform: undefined,
+ conflictBehavior: 'warn',
+})
+```
+
+## Reactive Options
+
+Vue-specific options can be plain values, refs, or getters.
+
+### `enabled`
+
+```vue
+
+```
+
+### `target`
+
+```vue
+
+
+
+ Panel content
+
+```
+
+## Global Default Options via Provider
+
+```vue
+
+
+
+
+
+
+
+```
+
+## Common Options
+
+### `requireReset`
+
+```ts
+useHotkey('Escape', () => closePanel(), { requireReset: true })
+```
+
+### `ignoreInputs`
+
+```ts
+useHotkey('K', () => openSearch())
+useHotkey('Enter', () => submit(), { ignoreInputs: false })
+```
+
+### `conflictBehavior`
+
+```ts
+useHotkey('Mod+S', () => save(), { conflictBehavior: 'replace' })
+```
+
+### `platform`
+
+```ts
+useHotkey('Mod+S', () => save(), { platform: 'mac' })
+```
+
+## Automatic Cleanup
+
+Hotkeys are automatically unregistered when the owning component unmounts.
+
+## The Hotkey Manager
+
+You can always reach for the underlying manager directly:
+
+```ts
+import { getHotkeyManager } from '@tanstack/vue-hotkeys'
+
+const manager = getHotkeyManager()
+manager.isRegistered('Mod+S')
+manager.getRegistrationCount()
+```
diff --git a/docs/framework/vue/guides/key-state-tracking.md b/docs/framework/vue/guides/key-state-tracking.md
new file mode 100644
index 0000000..3eb99d9
--- /dev/null
+++ b/docs/framework/vue/guides/key-state-tracking.md
@@ -0,0 +1,88 @@
+---
+title: Key State Tracking Guide
+id: key-state-tracking
+---
+
+TanStack Hotkeys provides three Vue composables for tracking live keyboard state: `useHeldKeys`, `useHeldKeyCodes`, and `useKeyHold`.
+
+## `useHeldKeys`
+
+```vue
+
+
+
+ {{ heldKeys.length > 0 ? heldKeys.join(' + ') : 'No keys held' }}
+
+```
+
+## `useHeldKeyCodes`
+
+```vue
+
+```
+
+## `useKeyHold`
+
+```vue
+
+
+
+ Shift
+
+```
+
+## Common Patterns
+
+### Hold-to-Reveal UI
+
+```vue
+
+
+
+ Permanently Delete
+ Move to Trash
+
+```
+
+### Debugging Key Display
+
+```vue
+
+```
+
+## Under the Hood
+
+All three composables subscribe to the singleton `KeyStateTracker`:
+
+```ts
+import { getKeyStateTracker } from '@tanstack/vue-hotkeys'
+
+const tracker = getKeyStateTracker()
+tracker.getHeldKeys()
+tracker.isKeyHeld('Shift')
+```
diff --git a/docs/framework/vue/guides/sequences.md b/docs/framework/vue/guides/sequences.md
new file mode 100644
index 0000000..399a718
--- /dev/null
+++ b/docs/framework/vue/guides/sequences.md
@@ -0,0 +1,93 @@
+---
+title: Sequences Guide
+id: sequences
+---
+
+TanStack Hotkeys supports multi-key sequences in Vue, where keys are pressed one after another rather than simultaneously.
+
+## Basic Usage
+
+```vue
+
+```
+
+## Sequence Options
+
+```ts
+useHotkeySequence(['G', 'G'], callback, {
+ timeout: 1000,
+ enabled: true,
+})
+```
+
+### Reactive `enabled`
+
+```vue
+
+```
+
+## Global Default Options via Provider
+
+```vue
+
+
+
+
+
+
+
+```
+
+## Common Patterns
+
+### Vim-Style Navigation
+
+```ts
+useHotkeySequence(['G', 'G'], () => scrollToTop())
+useHotkeySequence(['G', 'Shift+G'], () => scrollToBottom())
+useHotkeySequence(['D', 'D'], () => deleteLine())
+useHotkeySequence(['D', 'W'], () => deleteWord())
+useHotkeySequence(['C', 'I', 'W'], () => changeInnerWord())
+```
+
+### Konami Code
+
+```ts
+useHotkeySequence(
+ ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'B', 'A'],
+ () => enableEasterEgg(),
+ { timeout: 2000 },
+)
+```
+
+## Under the Hood
+
+`useHotkeySequence` uses the singleton `SequenceManager`. You can also access it directly:
+
+```ts
+import { createSequenceMatcher, getSequenceManager } from '@tanstack/vue-hotkeys'
+
+const manager = getSequenceManager()
+const matcher = createSequenceMatcher(['G', 'G'], { timeout: 1000 })
+```
diff --git a/docs/framework/vue/quick-start.md b/docs/framework/vue/quick-start.md
new file mode 100644
index 0000000..accb4ef
--- /dev/null
+++ b/docs/framework/vue/quick-start.md
@@ -0,0 +1,153 @@
+---
+title: Quick Start
+id: quick-start
+---
+
+## Installation
+
+Don't have TanStack Hotkeys installed yet? See the [Installation](../../installation) page for instructions.
+
+## Your First Hotkey
+
+The `useHotkey` composable is the primary way to register keyboard shortcuts in Vue:
+
+```vue
+
+
+
+ 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 Hotkeys
+
+```vue
+
+```
+
+### Scoped Hotkeys with Template Refs
+
+```vue
+
+
+
+
+
Press Escape while focused here to close
+
+
+```
+
+### Conditional Hotkeys
+
+```vue
+
+```
+
+### Multi-Key Sequences
+
+```vue
+
+```
+
+### Tracking Held Keys
+
+```vue
+
+
+
+
+ Shift mode active
+ Keys: {{ heldKeys.join('+') }}
+
+
+```
+
+### Displaying Hotkeys in the UI
+
+```vue
+
+
+
+
+ Save {{ formatForDisplay('Mod+S') }}
+
+
+```
+
+## Default Options Provider
+
+Wrap part of your app with `HotkeysProvider` to set default options for all Vue composables in that subtree:
+
+```vue
+
+
+
+
+
+
+
+```
+
+## 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/vue/reference/functions/provideHotkeysContext.md b/docs/framework/vue/reference/functions/provideHotkeysContext.md
new file mode 100644
index 0000000..96d9e47
--- /dev/null
+++ b/docs/framework/vue/reference/functions/provideHotkeysContext.md
@@ -0,0 +1,22 @@
+---
+id: provideHotkeysContext
+title: provideHotkeysContext
+---
+
+# Function: provideHotkeysContext()
+
+```ts
+function provideHotkeysContext(defaultOptions?): void;
+```
+
+Defined in: [packages/vue-hotkeys/src/HotkeysProviderContext.ts:22](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/HotkeysProviderContext.ts#L22)
+
+## Parameters
+
+### defaultOptions?
+
+[`HotkeysProviderOptions`](../interfaces/HotkeysProviderOptions.md)
+
+## Returns
+
+`void`
diff --git a/docs/framework/vue/reference/functions/useDefaultHotkeysOptions.md b/docs/framework/vue/reference/functions/useDefaultHotkeysOptions.md
new file mode 100644
index 0000000..9a40772
--- /dev/null
+++ b/docs/framework/vue/reference/functions/useDefaultHotkeysOptions.md
@@ -0,0 +1,16 @@
+---
+id: useDefaultHotkeysOptions
+title: useDefaultHotkeysOptions
+---
+
+# Function: useDefaultHotkeysOptions()
+
+```ts
+function useDefaultHotkeysOptions(): HotkeysProviderOptions;
+```
+
+Defined in: [packages/vue-hotkeys/src/HotkeysProviderContext.ts:34](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/HotkeysProviderContext.ts#L34)
+
+## Returns
+
+[`HotkeysProviderOptions`](../interfaces/HotkeysProviderOptions.md)
diff --git a/docs/framework/vue/reference/functions/useHeldKeyCodes.md b/docs/framework/vue/reference/functions/useHeldKeyCodes.md
new file mode 100644
index 0000000..0c966e3
--- /dev/null
+++ b/docs/framework/vue/reference/functions/useHeldKeyCodes.md
@@ -0,0 +1,42 @@
+---
+id: useHeldKeyCodes
+title: useHeldKeyCodes
+---
+
+# Function: useHeldKeyCodes()
+
+```ts
+function useHeldKeyCodes(): Ref>;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHeldKeyCodes.ts:31](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHeldKeyCodes.ts#L31)
+
+Vue composable that returns a reactive ref mapping 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
+
+`Ref`\<`Record`\<`string`, `string`\>\>
+
+Reactive ref containing record mapping normalized key names to their `event.code` values
+
+## Example
+
+```vue
+
+
+
+
+
+ {{ key }} {{ heldCodes[key] }}
+
+
+
+```
diff --git a/docs/framework/vue/reference/functions/useHeldKeys.md b/docs/framework/vue/reference/functions/useHeldKeys.md
new file mode 100644
index 0000000..ee3e025
--- /dev/null
+++ b/docs/framework/vue/reference/functions/useHeldKeys.md
@@ -0,0 +1,40 @@
+---
+id: useHeldKeys
+title: useHeldKeys
+---
+
+# Function: useHeldKeys()
+
+```ts
+function useHeldKeys(): Ref;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHeldKeys.ts:29](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHeldKeys.ts#L29)
+
+Vue composable that returns a reactive ref of currently held keyboard keys.
+
+This composable uses `useStore` from `@tanstack/vue-store` to subscribe
+to the global KeyStateTracker and updates whenever keys are pressed
+or released.
+
+## Returns
+
+`Ref`\<`string`[]\>
+
+Reactive ref containing array of currently held key names
+
+## Example
+
+```vue
+
+
+
+
+ Currently pressed: {{ heldKeys.join(' + ') || 'None' }}
+
+
+```
diff --git a/docs/framework/vue/reference/functions/useHotkey.md b/docs/framework/vue/reference/functions/useHotkey.md
new file mode 100644
index 0000000..9e24737
--- /dev/null
+++ b/docs/framework/vue/reference/functions/useHotkey.md
@@ -0,0 +1,97 @@
+---
+id: useHotkey
+title: useHotkey
+---
+
+# Function: useHotkey()
+
+```ts
+function useHotkey(
+ hotkey,
+ callback,
+ options): void;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHotkey.ts:100](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkey.ts#L100)
+
+Vue composable for registering a keyboard hotkey.
+
+Uses the singleton HotkeyManager for efficient event handling.
+The callback receives both the keyboard event and a context object
+containing the hotkey string and parsed hotkey.
+
+This composable automatically tracks reactive dependencies and updates
+the registration when options or the callback change.
+
+## Parameters
+
+### hotkey
+
+`MaybeRefOrGetter`\<`RegisterableHotkey`\>
+
+The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object (supports `mod` for cross-platform)
+
+### callback
+
+`HotkeyCallback`
+
+The function to call when the hotkey is pressed
+
+### options
+
+`MaybeRefOrGetter`\<[`UseHotkeyOptions`](../interfaces/UseHotkeyOptions.md)\> = `{}`
+
+Options for the hotkey behavior
+
+## Returns
+
+`void`
+
+## Examples
+
+```vue
+
+```
+
+```vue
+
+```
+
+```vue
+
+
+
+ ...
+
+```
diff --git a/docs/framework/vue/reference/functions/useHotkeyRecorder.md b/docs/framework/vue/reference/functions/useHotkeyRecorder.md
new file mode 100644
index 0000000..ec662e1
--- /dev/null
+++ b/docs/framework/vue/reference/functions/useHotkeyRecorder.md
@@ -0,0 +1,64 @@
+---
+id: useHotkeyRecorder
+title: useHotkeyRecorder
+---
+
+# Function: useHotkeyRecorder()
+
+```ts
+function useHotkeyRecorder(options): VueHotkeyRecorder;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHotkeyRecorder.ts:62](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkeyRecorder.ts#L62)
+
+Vue composable for recording keyboard shortcuts.
+
+This composable 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.
+
+## Parameters
+
+### options
+
+`MaybeRefOrGetter`\<`HotkeyRecorderOptions`\>
+
+Configuration options for the recorder
+
+## Returns
+
+[`VueHotkeyRecorder`](../interfaces/VueHotkeyRecorder.md)
+
+An object with recording state and control functions
+
+## Example
+
+```vue
+
+
+
+
+
+ {{ recorder.isRecording ? 'Recording...' : 'Edit Shortcut' }}
+
+
+ Recording: {{ recorder.recordedHotkey }}
+
+
+
+```
diff --git a/docs/framework/vue/reference/functions/useHotkeySequence.md b/docs/framework/vue/reference/functions/useHotkeySequence.md
new file mode 100644
index 0000000..c17f604
--- /dev/null
+++ b/docs/framework/vue/reference/functions/useHotkeySequence.md
@@ -0,0 +1,71 @@
+---
+id: useHotkeySequence
+title: useHotkeySequence
+---
+
+# Function: useHotkeySequence()
+
+```ts
+function useHotkeySequence(
+ sequence,
+ callback,
+ options): void;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHotkeySequence.ts:66](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkeySequence.ts#L66)
+
+Vue composable for registering a keyboard shortcut sequence (Vim-style).
+
+This composable allows you to register multi-key sequences like 'g g' or 'd d'
+that trigger when the full sequence is pressed within a timeout.
+
+## Parameters
+
+### sequence
+
+`MaybeRefOrGetter`\<`HotkeySequence`\>
+
+Array of hotkey strings that form the sequence
+
+### callback
+
+`HotkeyCallback`
+
+Function to call when the sequence is completed
+
+### options
+
+`MaybeRefOrGetter`\<[`UseHotkeySequenceOptions`](../interfaces/UseHotkeySequenceOptions.md)\> = `{}`
+
+Options for the sequence behavior
+
+## Returns
+
+`void`
+
+## Example
+
+```vue
+
+
+
+ ...
+
+```
diff --git a/docs/framework/vue/reference/functions/useHotkeysContext.md b/docs/framework/vue/reference/functions/useHotkeysContext.md
new file mode 100644
index 0000000..b4728c3
--- /dev/null
+++ b/docs/framework/vue/reference/functions/useHotkeysContext.md
@@ -0,0 +1,16 @@
+---
+id: useHotkeysContext
+title: useHotkeysContext
+---
+
+# Function: useHotkeysContext()
+
+```ts
+function useHotkeysContext(): HotkeysContextValue | null;
+```
+
+Defined in: [packages/vue-hotkeys/src/HotkeysProviderContext.ts:30](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/HotkeysProviderContext.ts#L30)
+
+## Returns
+
+`HotkeysContextValue` \| `null`
diff --git a/docs/framework/vue/reference/functions/useKeyHold.md b/docs/framework/vue/reference/functions/useKeyHold.md
new file mode 100644
index 0000000..84c4f0d
--- /dev/null
+++ b/docs/framework/vue/reference/functions/useKeyHold.md
@@ -0,0 +1,66 @@
+---
+id: useKeyHold
+title: useKeyHold
+---
+
+# Function: useKeyHold()
+
+```ts
+function useKeyHold(key): Ref;
+```
+
+Defined in: [packages/vue-hotkeys/src/useKeyHold.ts:51](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useKeyHold.ts#L51)
+
+Vue composable that returns a reactive ref indicating whether a specific key is currently being held.
+
+This composable uses `useStore` from `@tanstack/vue-store` to subscribe
+to the global KeyStateTracker and uses a selector to determine if
+the specified key is held.
+
+## Parameters
+
+### key
+
+`MaybeRefOrGetter`\<`HeldKey`\>
+
+The key to check (e.g., 'Shift', 'Control', 'A')
+
+## Returns
+
+`Ref`\<`boolean`\>
+
+Reactive ref that is true if the key is currently held down
+
+## Examples
+
+```vue
+
+
+
+
+ {{ isShiftHeld ? 'Shift is pressed!' : 'Press Shift' }}
+
+
+```
+
+```vue
+
+
+
+
+ Ctrl
+ Shift
+ Alt
+
+
+```
diff --git a/docs/framework/vue/reference/index.md b/docs/framework/vue/reference/index.md
new file mode 100644
index 0000000..286b460
--- /dev/null
+++ b/docs/framework/vue/reference/index.md
@@ -0,0 +1,29 @@
+---
+id: "@tanstack/vue-hotkeys"
+title: "@tanstack/vue-hotkeys"
+---
+
+# @tanstack/vue-hotkeys
+
+## Interfaces
+
+- [HotkeysProviderOptions](interfaces/HotkeysProviderOptions.md)
+- [UseHotkeyOptions](interfaces/UseHotkeyOptions.md)
+- [UseHotkeySequenceOptions](interfaces/UseHotkeySequenceOptions.md)
+- [VueHotkeyRecorder](interfaces/VueHotkeyRecorder.md)
+
+## Variables
+
+- [HotkeysProvider](variables/HotkeysProvider.md)
+
+## Functions
+
+- [provideHotkeysContext](functions/provideHotkeysContext.md)
+- [useDefaultHotkeysOptions](functions/useDefaultHotkeysOptions.md)
+- [useHeldKeyCodes](functions/useHeldKeyCodes.md)
+- [useHeldKeys](functions/useHeldKeys.md)
+- [useHotkey](functions/useHotkey.md)
+- [useHotkeyRecorder](functions/useHotkeyRecorder.md)
+- [useHotkeysContext](functions/useHotkeysContext.md)
+- [useHotkeySequence](functions/useHotkeySequence.md)
+- [useKeyHold](functions/useKeyHold.md)
diff --git a/docs/framework/vue/reference/interfaces/HotkeysProviderOptions.md b/docs/framework/vue/reference/interfaces/HotkeysProviderOptions.md
new file mode 100644
index 0000000..7afb463
--- /dev/null
+++ b/docs/framework/vue/reference/interfaces/HotkeysProviderOptions.md
@@ -0,0 +1,38 @@
+---
+id: HotkeysProviderOptions
+title: HotkeysProviderOptions
+---
+
+# Interface: HotkeysProviderOptions
+
+Defined in: [packages/vue-hotkeys/src/HotkeysProviderContext.ts:7](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/HotkeysProviderContext.ts#L7)
+
+## Properties
+
+### hotkey?
+
+```ts
+optional hotkey: Partial;
+```
+
+Defined in: [packages/vue-hotkeys/src/HotkeysProviderContext.ts:8](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/HotkeysProviderContext.ts#L8)
+
+***
+
+### hotkeyRecorder?
+
+```ts
+optional hotkeyRecorder: Partial;
+```
+
+Defined in: [packages/vue-hotkeys/src/HotkeysProviderContext.ts:9](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/HotkeysProviderContext.ts#L9)
+
+***
+
+### hotkeySequence?
+
+```ts
+optional hotkeySequence: Partial;
+```
+
+Defined in: [packages/vue-hotkeys/src/HotkeysProviderContext.ts:10](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/HotkeysProviderContext.ts#L10)
diff --git a/docs/framework/vue/reference/interfaces/UseHotkeyOptions.md b/docs/framework/vue/reference/interfaces/UseHotkeyOptions.md
new file mode 100644
index 0000000..ece91b1
--- /dev/null
+++ b/docs/framework/vue/reference/interfaces/UseHotkeyOptions.md
@@ -0,0 +1,40 @@
+---
+id: UseHotkeyOptions
+title: UseHotkeyOptions
+---
+
+# Interface: UseHotkeyOptions
+
+Defined in: [packages/vue-hotkeys/src/useHotkey.ts:18](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkey.ts#L18)
+
+## Extends
+
+- `Omit`\<`HotkeyOptions`, `"enabled"` \| `"target"`\>
+
+## Properties
+
+### enabled?
+
+```ts
+optional enabled: MaybeRefOrGetter;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHotkey.ts:27](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkey.ts#L27)
+
+Whether the hotkey is active.
+Can be a Ref, a getter function, or a boolean value.
+Defaults to true.
+
+***
+
+### target?
+
+```ts
+optional target: MaybeRefOrGetter;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHotkey.ts:33](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkey.ts#L33)
+
+The DOM element to attach the event listener to.
+Can be a Ref, a getter function, direct DOM element, or null.
+Defaults to document.
diff --git a/docs/framework/vue/reference/interfaces/UseHotkeySequenceOptions.md b/docs/framework/vue/reference/interfaces/UseHotkeySequenceOptions.md
new file mode 100644
index 0000000..1f743bb
--- /dev/null
+++ b/docs/framework/vue/reference/interfaces/UseHotkeySequenceOptions.md
@@ -0,0 +1,40 @@
+---
+id: UseHotkeySequenceOptions
+title: UseHotkeySequenceOptions
+---
+
+# Interface: UseHotkeySequenceOptions
+
+Defined in: [packages/vue-hotkeys/src/useHotkeySequence.ts:12](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkeySequence.ts#L12)
+
+## Extends
+
+- `Omit`\<`SequenceOptions`, `"enabled"` \| `"target"`\>
+
+## Properties
+
+### enabled?
+
+```ts
+optional enabled: MaybeRefOrGetter;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHotkeySequence.ts:21](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkeySequence.ts#L21)
+
+Whether the sequence is active.
+Can be a Ref, a getter function, or a boolean value.
+Defaults to true.
+
+***
+
+### target?
+
+```ts
+optional target: MaybeRefOrGetter;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHotkeySequence.ts:27](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkeySequence.ts#L27)
+
+The DOM element to attach the event listener to.
+Can be a Ref, a getter function, direct DOM element, or null.
+Defaults to document.
diff --git a/docs/framework/vue/reference/interfaces/VueHotkeyRecorder.md b/docs/framework/vue/reference/interfaces/VueHotkeyRecorder.md
new file mode 100644
index 0000000..27c3783
--- /dev/null
+++ b/docs/framework/vue/reference/interfaces/VueHotkeyRecorder.md
@@ -0,0 +1,80 @@
+---
+id: VueHotkeyRecorder
+title: VueHotkeyRecorder
+---
+
+# Interface: VueHotkeyRecorder
+
+Defined in: [packages/vue-hotkeys/src/useHotkeyRecorder.ts:8](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkeyRecorder.ts#L8)
+
+## Properties
+
+### cancelRecording()
+
+```ts
+cancelRecording: () => void;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHotkeyRecorder.ts:18](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkeyRecorder.ts#L18)
+
+Cancel recording without saving
+
+#### Returns
+
+`void`
+
+***
+
+### isRecording
+
+```ts
+isRecording: Ref;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHotkeyRecorder.ts:10](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkeyRecorder.ts#L10)
+
+Whether recording is currently active
+
+***
+
+### recordedHotkey
+
+```ts
+recordedHotkey: Ref;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHotkeyRecorder.ts:12](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkeyRecorder.ts#L12)
+
+The currently recorded hotkey (for live preview)
+
+***
+
+### startRecording()
+
+```ts
+startRecording: () => void;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHotkeyRecorder.ts:14](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkeyRecorder.ts#L14)
+
+Start recording a new hotkey
+
+#### Returns
+
+`void`
+
+***
+
+### stopRecording()
+
+```ts
+stopRecording: () => void;
+```
+
+Defined in: [packages/vue-hotkeys/src/useHotkeyRecorder.ts:16](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/useHotkeyRecorder.ts#L16)
+
+Stop recording (same as cancel)
+
+#### Returns
+
+`void`
diff --git a/docs/framework/vue/reference/variables/HotkeysProvider.md b/docs/framework/vue/reference/variables/HotkeysProvider.md
new file mode 100644
index 0000000..eaef0d1
--- /dev/null
+++ b/docs/framework/vue/reference/variables/HotkeysProvider.md
@@ -0,0 +1,49 @@
+---
+id: HotkeysProvider
+title: HotkeysProvider
+---
+
+# Variable: HotkeysProvider
+
+```ts
+const HotkeysProvider: DefineComponent HotkeysProviderOptions;
+ };
+}>, () =>
+ | VNode[]
+ | undefined, {
+}, {
+}, {
+}, ComponentOptionsMixin, ComponentOptionsMixin, {
+}, string, PublicProps, ToResolvedProps HotkeysProviderOptions;
+ };
+}>, {
+}>, {
+ defaultOptions: HotkeysProviderOptions;
+}, {
+}, {
+}, {
+}, string, ComponentProvideOptions, true, {
+}, any>;
+```
+
+Defined in: [packages/vue-hotkeys/src/HotkeysProvider.tsx:17](https://github.com/TanStack/hotkeys/blob/main/packages/vue-hotkeys/src/HotkeysProvider.tsx#L17)
+
+Vue component that provides default options for hotkeys context.
+
+## Example
+
+```vue
+
+
+
+
+
+```
diff --git a/docs/installation.md b/docs/installation.md
index e50e979..8dfb7f6 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -7,10 +7,11 @@ TanStack Hotkeys is compatible with various front-end frameworks. Install the co
-react: @tanstack/react-hotkeys
+angular: @tanstack/angular-hotkeys
preact: @tanstack/preact-hotkeys
+react: @tanstack/react-hotkeys
solid: @tanstack/solid-hotkeys
-angular: @tanstack/angular-hotkeys
+vue: @tanstack/vue-hotkeys
@@ -23,16 +24,55 @@ Each framework package re-exports everything from the core `@tanstack/hotkeys` p
# React
-## Devtools
+Start with the [Quick Start](./framework/react/quick-start) guide. If you want the integrated devtools panel, also install:
+
+
+
+
+
+# Preact
+
+Start with the [API reference](./framework/preact/reference/index) and [guides](./framework/preact/guides/hotkeys). If you want the integrated devtools panel, also install:
+
+
+
+
+
+# Solid
+
+Start with the [API reference](./framework/solid/reference/index) and [guides](./framework/solid/guides/hotkeys). If you want the integrated devtools panel, also install:
+
+
+
+
+
+# Angular
-Developer tools are available using [TanStack Devtools](https://tanstack.com/devtools/latest). Install the devtools adapter and the Hotkeys devtools plugin as dev dependencies to inspect registered hotkeys and monitor key state.
+Start with the [Quick Start](./framework/angular/quick-start) guide and the Angular-specific [guides](./framework/angular/guides/hotkeys).
+
+Angular currently ships the hotkeys adapter only, so no dedicated Angular devtools package is required.
+
+
+
+
+
+# Vue
+
+Start with the [Quick Start](./framework/vue/quick-start) guide and the Vue-specific [guides](./framework/vue/guides/hotkeys).
+
+If you want the Vue devtools panel component, also install:
+preact: @tanstack/preact-devtools
+preact: @tanstack/preact-hotkeys-devtools
react: @tanstack/react-devtools
react: @tanstack/react-hotkeys-devtools
+solid: @tanstack/solid-devtools
+solid: @tanstack/solid-hotkeys-devtools
+vue: @tanstack/vue-hotkeys-devtools
@@ -40,7 +80,31 @@ react: @tanstack/react-hotkeys-devtools
# React
-See the [devtools](./devtools) documentation for more information on how to set up and use the Hotkeys devtools.
+See the [devtools](./devtools) documentation for setup details.
+
+
+
+
+
+# Preact
+
+See the [devtools](./devtools) documentation for setup details.
+
+
+
+
+
+# Solid
+
+See the [devtools](./devtools) documentation for setup details.
+
+
+
+
+
+# Vue
+
+See the [devtools](./devtools) documentation for setup details.
diff --git a/docs/overview.md b/docs/overview.md
index 22bf14e..8b5553c 100644
--- a/docs/overview.md
+++ b/docs/overview.md
@@ -46,9 +46,9 @@ Surprisingly, in our experience, even AI often struggles to get hotkey managemen
- Platform-aware formatting (e.g., `⌘⇧S` on Mac vs `Ctrl+Shift+S` on Windows) for cheatsheet UIs
- **Framework Adapters**
- - React hooks, Solid primitives, and Angular inject APIs
+ - React and Preact hooks, Solid primitives, Angular inject APIs, and Vue composables
- **Awesome Devtools!**
- See all currently registered hotkeys, held keys, and more in real-time.
-For a complete walkthrough, see the [Quick Start](framework/react/quick-start).
+For a complete walkthrough, see the [React Quick Start](framework/react/quick-start), [Angular Quick Start](framework/angular/quick-start), or [Vue Quick Start](framework/vue/quick-start).
diff --git a/examples/vue/useHeldKeys/eslint.config.js b/examples/vue/useHeldKeys/eslint.config.js
new file mode 100644
index 0000000..92d9bee
--- /dev/null
+++ b/examples/vue/useHeldKeys/eslint.config.js
@@ -0,0 +1,13 @@
+// @ts-check
+
+import rootConfig from '../../../eslint.config.js'
+
+/** @type {import('eslint').Linter.Config[]} */
+const config = [
+ ...rootConfig,
+ {
+ files: ['**/*.{ts,tsx,vue}'],
+ },
+]
+
+export default config
diff --git a/examples/vue/useHeldKeys/index.html b/examples/vue/useHeldKeys/index.html
new file mode 100644
index 0000000..dfe5163
--- /dev/null
+++ b/examples/vue/useHeldKeys/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ useHeldKeys - TanStack Hotkeys Vue Example
+
+
+
+
+
+
diff --git a/examples/vue/useHeldKeys/package.json b/examples/vue/useHeldKeys/package.json
new file mode 100644
index 0000000..086f6e7
--- /dev/null
+++ b/examples/vue/useHeldKeys/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@tanstack/hotkeys-example-vue-use-held-keys",
+ "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/vue-hotkeys": "^0.4.0",
+ "vue": "^3.5.29"
+ },
+ "devDependencies": {
+ "@tanstack/vue-devtools": "^0.2.10",
+ "@tanstack/vue-hotkeys-devtools": "^0.4.0",
+ "@vitejs/plugin-vue": "^6.0.4",
+ "typescript": "5.9.3",
+ "vite": "^7.3.1"
+ }
+}
diff --git a/examples/vue/useHeldKeys/src/App.vue b/examples/vue/useHeldKeys/src/App.vue
new file mode 100644
index 0000000..e90a9c3
--- /dev/null
+++ b/examples/vue/useHeldKeys/src/App.vue
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+ Currently Held Keys
+
+
+
+ +
+
+ {{ formatKeyForDebuggingDisplay(key) }}
+
+ {{
+ formatKeyForDebuggingDisplay(heldCodes[key], {
+ source: 'code',
+ })
+ }}
+
+
+
+
+ Press any keys...
+
+
+ Keys held: {{ heldKeys.length }}
+
+
+
+
+ Usage
+ {{ usageCode }}
+
+
+
+ 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
+
+ Press some key combinations...
+ Clear History
+
+
+
+ Use Cases
+
+ Building a keyboard shortcut recorder
+ Displaying currently held keys to users
+ Debugging keyboard input
+ Creating key combination tutorials
+
+
+
+
+
+
+
+
diff --git a/examples/vue/useHeldKeys/src/index.css b/examples/vue/useHeldKeys/src/index.css
new file mode 100644
index 0000000..5f83d60
--- /dev/null
+++ b/examples/vue/useHeldKeys/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/vue/useHeldKeys/src/index.ts b/examples/vue/useHeldKeys/src/index.ts
new file mode 100644
index 0000000..50a4dab
--- /dev/null
+++ b/examples/vue/useHeldKeys/src/index.ts
@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import './index.css'
+
+createApp(App).mount('#app')
diff --git a/examples/vue/useHeldKeys/src/vue.d.ts b/examples/vue/useHeldKeys/src/vue.d.ts
new file mode 100644
index 0000000..b07a059
--- /dev/null
+++ b/examples/vue/useHeldKeys/src/vue.d.ts
@@ -0,0 +1,6 @@
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
diff --git a/examples/vue/useHeldKeys/tsconfig.json b/examples/vue/useHeldKeys/tsconfig.json
new file mode 100644
index 0000000..b1d7261
--- /dev/null
+++ b/examples/vue/useHeldKeys/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve"
+ },
+ "include": ["src"],
+ "exclude": ["eslint.config.js"]
+}
diff --git a/examples/vue/useHeldKeys/vite.config.ts b/examples/vue/useHeldKeys/vite.config.ts
new file mode 100644
index 0000000..c40aa3c
--- /dev/null
+++ b/examples/vue/useHeldKeys/vite.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+ plugins: [vue()],
+})
diff --git a/examples/vue/useHotkey/eslint.config.js b/examples/vue/useHotkey/eslint.config.js
new file mode 100644
index 0000000..92d9bee
--- /dev/null
+++ b/examples/vue/useHotkey/eslint.config.js
@@ -0,0 +1,13 @@
+// @ts-check
+
+import rootConfig from '../../../eslint.config.js'
+
+/** @type {import('eslint').Linter.Config[]} */
+const config = [
+ ...rootConfig,
+ {
+ files: ['**/*.{ts,tsx,vue}'],
+ },
+]
+
+export default config
diff --git a/examples/vue/useHotkey/index.html b/examples/vue/useHotkey/index.html
new file mode 100644
index 0000000..84e0a78
--- /dev/null
+++ b/examples/vue/useHotkey/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ useHotkey - TanStack Hotkeys Vue Example
+
+
+ You need to enable JavaScript to run this app.
+
+
+
+
diff --git a/examples/vue/useHotkey/package.json b/examples/vue/useHotkey/package.json
new file mode 100644
index 0000000..4e63bbe
--- /dev/null
+++ b/examples/vue/useHotkey/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@tanstack/hotkeys-example-vue-use-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/vue-hotkeys": "^0.4.0",
+ "vue": "^3.5.29"
+ },
+ "devDependencies": {
+ "@tanstack/vue-devtools": "^0.2.10",
+ "@tanstack/vue-hotkeys-devtools": "^0.4.0",
+ "@vitejs/plugin-vue": "^6.0.4",
+ "typescript": "5.9.3",
+ "vite": "^7.3.1"
+ }
+}
diff --git a/examples/vue/useHotkey/src/App.vue b/examples/vue/useHotkey/src/App.vue
new file mode 100644
index 0000000..28df0cf
--- /dev/null
+++ b/examples/vue/useHotkey/src/App.vue
@@ -0,0 +1,671 @@
+
+
+
+
+
+
+
+
+
+ Basic Hotkey
+
+ Press {{ formatForDisplay('Mod+S') }} to trigger
+
+ Save triggered: {{ saveCount }}x
+ {{ basicCode }}
+
+
+
+ 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.
+
+ {{ requireResetCode }}
+
+
+
+ Conditional Hotkey
+
+ {{ formatForDisplay('Mod+E') }} is currently
+ {{ enabled ? 'enabled' : 'disabled' }}
+
+
+ {{ enabled ? 'Disable' : 'Enable' }} Hotkey
+
+ {{ conditionalCode }}
+
+
+
+ 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 }}
+ {{ numberCode }}
+
+
+
+ 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
+
+ {{ navigationCode }}
+
+
+
+ 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
+
+ {{ functionCode }}
+
+
+
+ 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
+
+ {{ multiModifierCode }}
+
+
+
+ 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
+
+ {{ editingCode }}
+
+
+
+ Last triggered: {{ formatForDisplay(lastHotkey) }}
+
+
+ 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
+
Open Modal
+
+
+
Modal Dialog (Scoped)
+
Try these shortcuts while modal is open:
+
+
+ {{ formatForDisplay('Escape') }} — Close modal
+
+
+ {{ formatForDisplay('Mod+Enter') }} — Submit
+
+
+
+ Modal shortcuts: {{ modalShortcutCount }}x
+
+
+ These shortcuts only work when the modal is open and
+ focused. The Escape key here won't conflict with the global
+ Escape handler.
+
+
Close
+
+
+
+
+
+
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.
+
+
+
+
+ {{ scopedCode }}
+
+
+
+
+
+
+
diff --git a/examples/vue/useHotkey/src/index.css b/examples/vue/useHotkey/src/index.css
new file mode 100644
index 0000000..e9f3ca9
--- /dev/null
+++ b/examples/vue/useHotkey/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/vue/useHotkey/src/index.ts b/examples/vue/useHotkey/src/index.ts
new file mode 100644
index 0000000..50a4dab
--- /dev/null
+++ b/examples/vue/useHotkey/src/index.ts
@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import './index.css'
+
+createApp(App).mount('#app')
diff --git a/examples/vue/useHotkey/src/vue.d.ts b/examples/vue/useHotkey/src/vue.d.ts
new file mode 100644
index 0000000..b07a059
--- /dev/null
+++ b/examples/vue/useHotkey/src/vue.d.ts
@@ -0,0 +1,6 @@
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
diff --git a/examples/vue/useHotkey/tsconfig.json b/examples/vue/useHotkey/tsconfig.json
new file mode 100644
index 0000000..b1d7261
--- /dev/null
+++ b/examples/vue/useHotkey/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve"
+ },
+ "include": ["src"],
+ "exclude": ["eslint.config.js"]
+}
diff --git a/examples/vue/useHotkey/vite.config.ts b/examples/vue/useHotkey/vite.config.ts
new file mode 100644
index 0000000..c40aa3c
--- /dev/null
+++ b/examples/vue/useHotkey/vite.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+ plugins: [vue()],
+})
diff --git a/examples/vue/useHotkeyRecorder/eslint.config.js b/examples/vue/useHotkeyRecorder/eslint.config.js
new file mode 100644
index 0000000..92d9bee
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/eslint.config.js
@@ -0,0 +1,13 @@
+// @ts-check
+
+import rootConfig from '../../../eslint.config.js'
+
+/** @type {import('eslint').Linter.Config[]} */
+const config = [
+ ...rootConfig,
+ {
+ files: ['**/*.{ts,tsx,vue}'],
+ },
+]
+
+export default config
diff --git a/examples/vue/useHotkeyRecorder/index.html b/examples/vue/useHotkeyRecorder/index.html
new file mode 100644
index 0000000..a5bc9bf
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ useHotkeyRecorder - TanStack Hotkeys Vue Example
+
+
+
+
+
+
diff --git a/examples/vue/useHotkeyRecorder/package.json b/examples/vue/useHotkeyRecorder/package.json
new file mode 100644
index 0000000..1f08b3e
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@tanstack/hotkeys-example-vue-use-hotkey-recorder",
+ "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/vue-hotkeys": "^0.4.0",
+ "vue": "^3.5.29"
+ },
+ "devDependencies": {
+ "@tanstack/vue-devtools": "^0.2.10",
+ "@tanstack/vue-hotkeys-devtools": "^0.4.0",
+ "@vitejs/plugin-vue": "^6.0.4",
+ "typescript": "5.9.3",
+ "vite": "^7.3.1"
+ }
+}
diff --git a/examples/vue/useHotkeyRecorder/src/App.vue b/examples/vue/useHotkeyRecorder/src/App.vue
new file mode 100644
index 0000000..88eec39
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/src/App.vue
@@ -0,0 +1,283 @@
+
+
+
+
+
+
+
+
+
+
+
+ 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') }}
+
+
+
+
+
+ Recording shortcut... Press any key combination or
+ Escape to cancel. Press Backspace/Delete to clear the shortcut.
+
+
+
+ Usage
+ {{ usageCode }}
+
+
+
+
+
+
+
diff --git a/examples/vue/useHotkeyRecorder/src/ShortcutListItem.vue b/examples/vue/useHotkeyRecorder/src/ShortcutListItem.vue
new file mode 100644
index 0000000..0c0b195
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/src/ShortcutListItem.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+
{{ actionName }}
+
+
+
+
+ +
+ {{ key }}
+
+
+
Press any key combination...
+
+
{{ formatForDisplay(hotkey as Hotkey) }}
+
No shortcut
+
+
+
+
+ Cancel
+
+ Edit
+
+
+
diff --git a/examples/vue/useHotkeyRecorder/src/index.css b/examples/vue/useHotkeyRecorder/src/index.css
new file mode 100644
index 0000000..77dfb2f
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/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/vue/useHotkeyRecorder/src/index.ts b/examples/vue/useHotkeyRecorder/src/index.ts
new file mode 100644
index 0000000..50a4dab
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/src/index.ts
@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import './index.css'
+
+createApp(App).mount('#app')
diff --git a/examples/vue/useHotkeyRecorder/src/vue.d.ts b/examples/vue/useHotkeyRecorder/src/vue.d.ts
new file mode 100644
index 0000000..b07a059
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/src/vue.d.ts
@@ -0,0 +1,6 @@
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
diff --git a/examples/vue/useHotkeyRecorder/tsconfig.json b/examples/vue/useHotkeyRecorder/tsconfig.json
new file mode 100644
index 0000000..b1d7261
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve"
+ },
+ "include": ["src"],
+ "exclude": ["eslint.config.js"]
+}
diff --git a/examples/vue/useHotkeyRecorder/vite.config.ts b/examples/vue/useHotkeyRecorder/vite.config.ts
new file mode 100644
index 0000000..c40aa3c
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/vite.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+ plugins: [vue()],
+})
diff --git a/examples/vue/useHotkeySequence/eslint.config.js b/examples/vue/useHotkeySequence/eslint.config.js
new file mode 100644
index 0000000..92d9bee
--- /dev/null
+++ b/examples/vue/useHotkeySequence/eslint.config.js
@@ -0,0 +1,13 @@
+// @ts-check
+
+import rootConfig from '../../../eslint.config.js'
+
+/** @type {import('eslint').Linter.Config[]} */
+const config = [
+ ...rootConfig,
+ {
+ files: ['**/*.{ts,tsx,vue}'],
+ },
+]
+
+export default config
diff --git a/examples/vue/useHotkeySequence/index.html b/examples/vue/useHotkeySequence/index.html
new file mode 100644
index 0000000..18a2236
--- /dev/null
+++ b/examples/vue/useHotkeySequence/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ useHotkeySequence - TanStack Hotkeys Vue Example
+
+
+
+
+
+
diff --git a/examples/vue/useHotkeySequence/package.json b/examples/vue/useHotkeySequence/package.json
new file mode 100644
index 0000000..79abdb6
--- /dev/null
+++ b/examples/vue/useHotkeySequence/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@tanstack/hotkeys-example-vue-use-hotkey-sequence",
+ "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/vue-hotkeys": "^0.4.0",
+ "vue": "^3.5.29"
+ },
+ "devDependencies": {
+ "@tanstack/vue-devtools": "^0.2.10",
+ "@tanstack/vue-hotkeys-devtools": "^0.4.0",
+ "@vitejs/plugin-vue": "^6.0.4",
+ "typescript": "5.9.3",
+ "vite": "^7.3.1"
+ }
+}
diff --git a/examples/vue/useHotkeySequence/src/App.vue b/examples/vue/useHotkeySequence/src/App.vue
new file mode 100644
index 0000000..f1f6f13
--- /dev/null
+++ b/examples/vue/useHotkeySequence/src/App.vue
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+
+
+ Vim-Style Commands
+
+
+
+ Sequence
+ Action
+
+
+
+
+ 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
+
+
+
+
+
+ Triggered: {{ lastSequence }}
+
+
+
+
+
+ Usage
+ {{ usageCode }}
+
+
+
+ History
+
+ Clear History
+
+
+ Press Escape to clear history
+
+
+
+
+
+
diff --git a/examples/vue/useHotkeySequence/src/index.css b/examples/vue/useHotkeySequence/src/index.css
new file mode 100644
index 0000000..69b749b
--- /dev/null
+++ b/examples/vue/useHotkeySequence/src/index.css
@@ -0,0 +1,159 @@
+* {
+ 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;
+}
+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/vue/useHotkeySequence/src/index.ts b/examples/vue/useHotkeySequence/src/index.ts
new file mode 100644
index 0000000..50a4dab
--- /dev/null
+++ b/examples/vue/useHotkeySequence/src/index.ts
@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import './index.css'
+
+createApp(App).mount('#app')
diff --git a/examples/vue/useHotkeySequence/src/vue.d.ts b/examples/vue/useHotkeySequence/src/vue.d.ts
new file mode 100644
index 0000000..b07a059
--- /dev/null
+++ b/examples/vue/useHotkeySequence/src/vue.d.ts
@@ -0,0 +1,6 @@
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
diff --git a/examples/vue/useHotkeySequence/tsconfig.json b/examples/vue/useHotkeySequence/tsconfig.json
new file mode 100644
index 0000000..b1d7261
--- /dev/null
+++ b/examples/vue/useHotkeySequence/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve"
+ },
+ "include": ["src"],
+ "exclude": ["eslint.config.js"]
+}
diff --git a/examples/vue/useHotkeySequence/vite.config.ts b/examples/vue/useHotkeySequence/vite.config.ts
new file mode 100644
index 0000000..c40aa3c
--- /dev/null
+++ b/examples/vue/useHotkeySequence/vite.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+ plugins: [vue()],
+})
diff --git a/examples/vue/useKeyhold/eslint.config.js b/examples/vue/useKeyhold/eslint.config.js
new file mode 100644
index 0000000..92d9bee
--- /dev/null
+++ b/examples/vue/useKeyhold/eslint.config.js
@@ -0,0 +1,13 @@
+// @ts-check
+
+import rootConfig from '../../../eslint.config.js'
+
+/** @type {import('eslint').Linter.Config[]} */
+const config = [
+ ...rootConfig,
+ {
+ files: ['**/*.{ts,tsx,vue}'],
+ },
+]
+
+export default config
diff --git a/examples/vue/useKeyhold/index.html b/examples/vue/useKeyhold/index.html
new file mode 100644
index 0000000..c99cbf2
--- /dev/null
+++ b/examples/vue/useKeyhold/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ useKeyHold - TanStack Hotkeys Vue Example
+
+
+
+
+
+
diff --git a/examples/vue/useKeyhold/package.json b/examples/vue/useKeyhold/package.json
new file mode 100644
index 0000000..b1c2f4b
--- /dev/null
+++ b/examples/vue/useKeyhold/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@tanstack/hotkeys-example-vue-use-keyhold",
+ "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/vue-hotkeys": "^0.4.0",
+ "vue": "^3.5.29"
+ },
+ "devDependencies": {
+ "@tanstack/vue-devtools": "^0.2.10",
+ "@tanstack/vue-hotkeys-devtools": "^0.4.0",
+ "@vitejs/plugin-vue": "^6.0.4",
+ "typescript": "5.9.3",
+ "vite": "^7.3.1"
+ }
+}
diff --git a/examples/vue/useKeyhold/src/App.vue b/examples/vue/useKeyhold/src/App.vue
new file mode 100644
index 0000000..5011e11
--- /dev/null
+++ b/examples/vue/useKeyhold/src/App.vue
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+ Modifier Key States
+
+
+ Shift
+ {{
+ isShiftHeld ? 'HELD' : 'Released'
+ }}
+
+
+ Control
+ {{
+ isControlHeld ? 'HELD' : 'Released'
+ }}
+
+
+ Alt / Option
+ {{ isAltHeld ? 'HELD' : 'Released' }}
+
+
+ Meta (⌘ / ⊞)
+ {{ isMetaHeld ? 'HELD' : 'Released' }}
+
+
+
+
+
+ Space Bar Demo
+
+ {{ isSpaceHeld ? '🚀 SPACE HELD!' : 'Hold Space Bar' }}
+
+
+
+
+ Usage
+ {{ usageCode }}
+
+
+
+ Conditional UI Example
+ Hold Shift to reveal the secret message:
+
+ {{
+ isShiftHeld
+ ? '🎉 The secret password is: tanstack-hotkeys-rocks!'
+ : '••••••••••••••••••••••••••'
+ }}
+
+
+
+
+ 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/vue/useKeyhold/src/index.css b/examples/vue/useKeyhold/src/index.css
new file mode 100644
index 0000000..0cbb5ae
--- /dev/null
+++ b/examples/vue/useKeyhold/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/vue/useKeyhold/src/index.ts b/examples/vue/useKeyhold/src/index.ts
new file mode 100644
index 0000000..50a4dab
--- /dev/null
+++ b/examples/vue/useKeyhold/src/index.ts
@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import './index.css'
+
+createApp(App).mount('#app')
diff --git a/examples/vue/useKeyhold/src/vue.d.ts b/examples/vue/useKeyhold/src/vue.d.ts
new file mode 100644
index 0000000..b07a059
--- /dev/null
+++ b/examples/vue/useKeyhold/src/vue.d.ts
@@ -0,0 +1,6 @@
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
diff --git a/examples/vue/useKeyhold/tsconfig.json b/examples/vue/useKeyhold/tsconfig.json
new file mode 100644
index 0000000..b1d7261
--- /dev/null
+++ b/examples/vue/useKeyhold/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve"
+ },
+ "include": ["src"],
+ "exclude": ["eslint.config.js"]
+}
diff --git a/examples/vue/useKeyhold/vite.config.ts b/examples/vue/useKeyhold/vite.config.ts
new file mode 100644
index 0000000..c40aa3c
--- /dev/null
+++ b/examples/vue/useKeyhold/vite.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+ plugins: [vue()],
+})
diff --git a/package.json b/package.json
index c57d5e5..bb4ec20 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"type": "git",
"url": "git+https://github.com/TanStack/hotkeys.git"
},
- "packageManager": "pnpm@10.30.3",
+ "packageManager": "pnpm@10.31.0",
"type": "module",
"scripts": {
"build": "nx affected --skip-nx-cache --targets=build --exclude=examples/** && size-limit",
@@ -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",
+ "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",
"dev": "pnpm run watch",
"format": "prettier --experimental-cli --ignore-unknown '**/*' --write",
"generate-docs": "node scripts/generate-docs.ts && pnpm run copy:readme",
@@ -82,6 +82,10 @@
"@tanstack/preact-hotkeys": "workspace:*",
"@tanstack/preact-hotkeys-devtools": "workspace:*",
"@tanstack/react-hotkeys": "workspace:*",
- "@tanstack/react-hotkeys-devtools": "workspace:*"
+ "@tanstack/react-hotkeys-devtools": "workspace:*",
+ "@tanstack/solid-hotkeys": "workspace:*",
+ "@tanstack/solid-hotkeys-devtools": "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 d627591..1c80cd6 100644
--- a/packages/angular-hotkeys/README.md
+++ b/packages/angular-hotkeys/README.md
@@ -44,7 +44,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
- 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, React hooks, and devtools (in progress)
+- Batteries included — validation + matching, sequences (Vim-style), key-state tracking, recorder UI helpers, framework adapters, and devtools
### Read the docs →
@@ -57,8 +57,8 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
> - [**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 – needs a contributor!
-> - Vue Hotkeys – needs a contributor!
## Get Involved
diff --git a/packages/hotkeys-devtools/README.md b/packages/hotkeys-devtools/README.md
index d627591..1c80cd6 100644
--- a/packages/hotkeys-devtools/README.md
+++ b/packages/hotkeys-devtools/README.md
@@ -44,7 +44,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
- 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, React hooks, and devtools (in progress)
+- Batteries included — validation + matching, sequences (Vim-style), key-state tracking, recorder UI helpers, framework adapters, and devtools
### Read the docs →
@@ -57,8 +57,8 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
> - [**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 – needs a contributor!
-> - Vue Hotkeys – needs a contributor!
## Get Involved
diff --git a/packages/hotkeys/README.md b/packages/hotkeys/README.md
index d627591..1c80cd6 100644
--- a/packages/hotkeys/README.md
+++ b/packages/hotkeys/README.md
@@ -44,7 +44,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
- 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, React hooks, and devtools (in progress)
+- Batteries included — validation + matching, sequences (Vim-style), key-state tracking, recorder UI helpers, framework adapters, and devtools
### Read the docs →
@@ -57,8 +57,8 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
> - [**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 – needs a contributor!
-> - Vue Hotkeys – needs a contributor!
## Get Involved
diff --git a/packages/preact-hotkeys-devtools/README.md b/packages/preact-hotkeys-devtools/README.md
index d627591..1c80cd6 100644
--- a/packages/preact-hotkeys-devtools/README.md
+++ b/packages/preact-hotkeys-devtools/README.md
@@ -44,7 +44,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
- 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, React hooks, and devtools (in progress)
+- Batteries included — validation + matching, sequences (Vim-style), key-state tracking, recorder UI helpers, framework adapters, and devtools
### Read the docs →
@@ -57,8 +57,8 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
> - [**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 – needs a contributor!
-> - Vue Hotkeys – needs a contributor!
## Get Involved
diff --git a/packages/preact-hotkeys/README.md b/packages/preact-hotkeys/README.md
index d627591..1c80cd6 100644
--- a/packages/preact-hotkeys/README.md
+++ b/packages/preact-hotkeys/README.md
@@ -44,7 +44,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
- 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, React hooks, and devtools (in progress)
+- Batteries included — validation + matching, sequences (Vim-style), key-state tracking, recorder UI helpers, framework adapters, and devtools
### Read the docs →
@@ -57,8 +57,8 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
> - [**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 – needs a contributor!
-> - Vue Hotkeys – needs a contributor!
## Get Involved
diff --git a/packages/react-hotkeys-devtools/README.md b/packages/react-hotkeys-devtools/README.md
index d627591..1c80cd6 100644
--- a/packages/react-hotkeys-devtools/README.md
+++ b/packages/react-hotkeys-devtools/README.md
@@ -44,7 +44,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
- 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, React hooks, and devtools (in progress)
+- Batteries included — validation + matching, sequences (Vim-style), key-state tracking, recorder UI helpers, framework adapters, and devtools
### Read the docs →
@@ -57,8 +57,8 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
> - [**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 – needs a contributor!
-> - Vue Hotkeys – needs a contributor!
## Get Involved
diff --git a/packages/react-hotkeys/README.md b/packages/react-hotkeys/README.md
index d627591..1c80cd6 100644
--- a/packages/react-hotkeys/README.md
+++ b/packages/react-hotkeys/README.md
@@ -44,7 +44,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
- 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, React hooks, and devtools (in progress)
+- Batteries included — validation + matching, sequences (Vim-style), key-state tracking, recorder UI helpers, framework adapters, and devtools
### Read the docs →
@@ -57,8 +57,8 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec
> - [**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 – needs a contributor!
-> - Vue Hotkeys – needs a contributor!
## Get Involved
diff --git a/packages/react-hotkeys/src/HotkeysProvider.tsx b/packages/react-hotkeys/src/HotkeysProvider.tsx
index ee1bcb1..5d00a5f 100644
--- a/packages/react-hotkeys/src/HotkeysProvider.tsx
+++ b/packages/react-hotkeys/src/HotkeysProvider.tsx
@@ -35,6 +35,7 @@ export function HotkeysProvider({
)
return (
+ // eslint-disable-next-line @eslint-react/no-context-provider -- React 19+ only; we support React >=16.8
{children}
@@ -42,10 +43,12 @@ export function HotkeysProvider({
}
export function useHotkeysContext() {
+ // eslint-disable-next-line @eslint-react/no-use-context -- React 19+ only; we support React >=16.8
return useContext(HotkeysContext)
}
export function useDefaultHotkeysOptions() {
+ // eslint-disable-next-line @eslint-react/no-use-context -- React 19+ only; we support React >=16.8
const context = useContext(HotkeysContext)
return context?.defaultOptions ?? {}
}
diff --git a/packages/solid-hotkeys-devtools/README.md b/packages/solid-hotkeys-devtools/README.md
index e876441..1c80cd6 100644
--- a/packages/solid-hotkeys-devtools/README.md
+++ b/packages/solid-hotkeys-devtools/README.md
@@ -1,46 +1,122 @@
-# @tanstack/solid-hotkeys-devtools
-
-> SolidJS devtools for [TanStack Hotkeys](https://tanstack.com/hotkeys)
-
-## Installation
-
-```bash
-npm install @tanstack/solid-hotkeys-devtools @tanstack/solid-hotkeys
-# or
-bun add @tanstack/solid-hotkeys-devtools @tanstack/solid-hotkeys
-# or
-pnpm add @tanstack/solid-hotkeys-devtools @tanstack/solid-hotkeys
-```
-
-## Usage
-
-```tsx
-import {
- HotkeysDevtoolsPanel,
- hotkeysDevtoolsPlugin,
-} from '@tanstack/solid-hotkeys-devtools'
-import { createHotkey } from '@tanstack/solid-hotkeys'
-
-function App() {
- // Register the devtools plugin
- createHotkey(
- 'Mod+S',
- (event) => {
- event.preventDefault()
- console.log('Save!')
- },
- { plugins: [hotkeysDevtoolsPlugin] },
- )
-
- return (
-
-
- {/* Your app */}
-
- )
-}
-```
-
-## License
-
-MIT
+
+
+
+
+
+
+
+
+
+
+
+
+### [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 – needs a contributor!
+
+## 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
+
+
+
+
+
+
+
+
+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/solid-hotkeys/README.md b/packages/solid-hotkeys/README.md
index ae7844b..1c80cd6 100644
--- a/packages/solid-hotkeys/README.md
+++ b/packages/solid-hotkeys/README.md
@@ -1,305 +1,122 @@
-# @tanstack/solid-hotkeys
-
-> SolidJS adapter for [TanStack Hotkeys](https://tanstack.com/hotkeys) - keyboard shortcuts made easy
-
-[](https://www.npmjs.com/package/@tanstack/solid-hotkeys)
-[](https://opensource.org/licenses/MIT)
-
-## Features
-
-✅ **Type-safe hotkey bindings** - Template strings (`Mod+Shift+S`, `Escape`) or parsed objects
-✅ **Cross-platform** - `Mod` key automatically maps to Cmd on macOS, Ctrl on Windows/Linux
-✅ **Sequence support** - Vim-style multi-key sequences (`g g`, `d d`)
-✅ **Key state tracking** - Track which keys are currently held down
-✅ **Hotkey recording** - Built-in UI helpers for letting users define their own shortcuts
-✅ **SolidJS primitives** - Reactive primitives that work seamlessly with SolidJS
-
-## Installation
-
-```bash
-npm install @tanstack/solid-hotkeys @tanstack/hotkeys
-# or
-bun add @tanstack/solid-hotkeys @tanstack/hotkeys
-# or
-pnpm add @tanstack/solid-hotkeys @tanstack/hotkeys
-```
-
-## Quick Start
-
-```tsx
-import { createHotkey } from '@tanstack/solid-hotkeys'
-
-function App() {
- createHotkey('Mod+S', (event) => {
- event.preventDefault()
- console.log('Save!')
- })
-
- return Press Cmd/Ctrl+S to save
-}
-```
-
-## Usage
-
-### Basic Hotkey
-
-```tsx
-import { createHotkey } from '@tanstack/solid-hotkeys'
-
-function SaveButton() {
- createHotkey('Mod+S', (event, { hotkey }) => {
- event.preventDefault()
- handleSave()
- })
-
- return Save (Cmd/Ctrl+S)
-}
-```
-
-### Conditional Hotkeys
-
-```tsx
-import { createHotkey } from '@tanstack/solid-hotkeys'
-import { Show, createSignal } from 'solid-js'
-
-function Modal(props) {
- // Hotkey only active when modal is open
- createHotkey(
- 'Escape',
- () => props.onClose(),
- () => ({
- enabled: props.isOpen,
- }),
- )
-
- return (
-
- Press Escape to close
-
- )
-}
-```
-
-### Scoped Hotkeys
-
-```tsx
-import { createHotkey } from '@tanstack/solid-hotkeys'
-
-function Editor() {
- let editorRef: HTMLDivElement | undefined
-
- // Hotkey only works when editor is focused
- createHotkey(
- 'Mod+B',
- () => {
- toggleBold()
- },
- { target: editorRef },
- )
-
- return
-}
-```
-
-### Hotkey Sequences (Vim-style)
-
-```tsx
-import { createHotkeySequence } from '@tanstack/solid-hotkeys'
-
-function VimEditor() {
- // 'g g' to go to top
- createHotkeySequence(['G', 'G'], () => {
- scrollToTop()
- })
-
- // 'd d' to delete line
- createHotkeySequence(['D', 'D'], () => {
- deleteLine()
- })
-
- // 'd i w' to delete inner word
- createHotkeySequence(
- ['D', 'I', 'W'],
- () => {
- deleteInnerWord()
- },
- { timeout: 500 },
- )
-
- return Try Vim shortcuts!
-}
-```
-
-### Track Held Keys
-
-```tsx
-import { createHeldKeys, createKeyHold } from '@tanstack/solid-hotkeys'
-import { For } from 'solid-js'
-
-function KeyTracker() {
- const heldKeys = createHeldKeys()
- const shiftHeld = createKeyHold('Shift')
-
- return (
-
-
Shift: {shiftHeld() ? 'Pressed' : 'Not pressed'}
-
- All held keys:
- {(key) => {key} }
-
-
- )
-}
-```
-
-### Hotkey Recorder
-
-```tsx
-import { createHotkeyRecorder } from '@tanstack/solid-hotkeys'
-import { createSignal, Show } from 'solid-js'
-
-function ShortcutSettings() {
- const [shortcut, setShortcut] = createSignal('Mod+S')
-
- const recorder = createHotkeyRecorder({
- onRecord: (hotkey) => {
- setShortcut(hotkey)
- },
- onCancel: () => {
- console.log('Recording cancelled')
- },
- })
-
- return (
-
-
Current shortcut: {shortcut()}
-
- {recorder.isRecording() ? 'Recording...' : 'Edit Shortcut'}
-
-
- Preview: {recorder.recordedHotkey()}
-
-
- )
-}
-```
-
-### Global Configuration
-
-```tsx
-import { HotkeysProvider } from '@tanstack/solid-hotkeys'
-
-function App() {
- return (
-
-
-
- )
-}
-```
-
-## API
-
-### `createHotkey(hotkey, callback, options?)`
-
-Register a keyboard hotkey.
-
-- `hotkey`: String like `"Mod+S"` or `"Escape"`, or accessor function
-- `callback`: Function called when hotkey is pressed
-- `options`: Optional configuration (or accessor function for reactive options)
-
-**Options:**
-
-- `enabled`: Whether the hotkey is active (default: `true`)
-- `preventDefault`: Prevent default browser behavior (default: `false`)
-- `stopPropagation`: Stop event propagation (default: `false`)
-- `target`: DOM element to attach listener to (default: `document`)
-- `platform`: Override platform detection
-
-### `createHotkeySequence(sequence, callback, options?)`
-
-Register a multi-key sequence (Vim-style).
-
-- `sequence`: Array of hotkey strings like `["G", "G"]`, or accessor function
-- `callback`: Function called when sequence completes
-- `options`: Optional configuration (or accessor function)
-
-**Options:**
-
-- `enabled`: Whether sequence detection is active (default: `true`)
-- `timeout`: Max time between keys in ms (default: `1000`)
-- `platform`: Override platform detection
-
-### `createHeldKeys()`
-
-Returns a signal accessor for array of currently held keys.
-
-```tsx
-const heldKeys = createHeldKeys()
-// heldKeys() => ["Shift", "A"]
-```
-
-### `createHeldKeyCodes()`
-
-Returns a signal accessor for map of held keys to their physical key codes.
-
-```tsx
-const heldCodes = createHeldKeyCodes()
-// heldCodes() => { "Shift": "ShiftLeft", "A": "KeyA" }
-```
-
-### `createKeyHold(key)`
-
-Returns a signal accessor that's true when specific key is held.
-
-```tsx
-const isShiftHeld = createKeyHold('Shift')
-// isShiftHeld() => true/false
-```
-
-### `createHotkeyRecorder(options)`
-
-Hotkey recording interface.
-
-**Options:**
-
-- `onRecord`: Callback when hotkey is recorded
-- `onCancel`: Callback when recording is cancelled
-
-**Returns:**
-
-- `isRecording`: Signal accessor for recording state
-- `recordedHotkey`: Signal accessor for current hotkey preview
-- `startRecording`: Function to start recording
-- `stopRecording`: Function to stop recording
-- `cancelRecording`: Function to cancel recording
-
-### `HotkeysProvider`
-
-Optional provider for global configuration.
-
-## Cross-Platform Keys
-
-Use `Mod` for cross-platform modifier:
-
-- `Mod+S` → `Cmd+S` on macOS, `Ctrl+S` on Windows/Linux
-- `Mod+Shift+P` → `Cmd+Shift+P` on macOS, `Ctrl+Shift+P` elsewhere
-
-## Related
-
-- [TanStack Hotkeys](https://tanstack.com/hotkeys) - The core library
-- [@tanstack/react-hotkeys](https://tanstack.com/hotkeys) - React adapter
-
-## License
-
-MIT
+
+
+
+
+
+
+
+
+
+
+
+
+### [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 – needs a contributor!
+
+## 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
+
+
+
+
+
+
+
+
+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/vue-hotkeys-devtools/CHANGELOG.md b/packages/vue-hotkeys-devtools/CHANGELOG.md
new file mode 100644
index 0000000..3e65f08
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/CHANGELOG.md
@@ -0,0 +1,7 @@
+# @tanstack/vue-hotkeys-devtools
+
+## 0.3.0
+
+### Minor Changes
+
+- Initial Vue devtools release
diff --git a/packages/vue-hotkeys-devtools/README.md b/packages/vue-hotkeys-devtools/README.md
new file mode 100644
index 0000000..1c80cd6
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/README.md
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+### [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 – needs a contributor!
+
+## 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
+
+
+
+
+
+
+
+
+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/vue-hotkeys-devtools/eslint.config.js b/packages/vue-hotkeys-devtools/eslint.config.js
new file mode 100644
index 0000000..a3afead
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/eslint.config.js
@@ -0,0 +1,13 @@
+// @ts-check
+
+import rootConfig from '../../eslint.config.js'
+
+/** @type {import('eslint').Linter.Config[]} */
+const config = [
+ ...rootConfig,
+ {
+ files: ['**/*.{ts,tsx,vue}'],
+ },
+]
+
+export default config
diff --git a/packages/vue-hotkeys-devtools/package.json b/packages/vue-hotkeys-devtools/package.json
new file mode 100644
index 0000000..d7af36b
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/package.json
@@ -0,0 +1,70 @@
+{
+ "name": "@tanstack/vue-hotkeys-devtools",
+ "version": "0.4.0",
+ "description": "Vue devtools for TanStack Hotkeys",
+ "author": "Tanner Linsley",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/TanStack/hotkeys.git",
+ "directory": "packages/vue-hotkeys-devtools"
+ },
+ "homepage": "https://tanstack.com/hotkeys",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "keywords": [
+ "vue",
+ "vuejs",
+ "tanstack",
+ "keys",
+ "devtools",
+ "hotkeys",
+ "keyboard"
+ ],
+ "scripts": {
+ "clean": "premove ./build ./dist",
+ "lint": "eslint ./src",
+ "lint:fix": "eslint ./src --fix",
+ "test:eslint": "eslint ./src",
+ "test:lib": "vitest --passWithNoTests",
+ "test:lib:dev": "pnpm test:lib --watch",
+ "test:types": "tsc",
+ "build": "tsdown"
+ },
+ "type": "module",
+ "types": "./dist/index.d.cts",
+ "exports": {
+ ".": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ },
+ "./production": {
+ "import": "./dist/production.js",
+ "require": "./dist/production.cjs"
+ },
+ "./package.json": "./package.json"
+ },
+ "sideEffects": false,
+ "engines": {
+ "node": ">=18"
+ },
+ "files": [
+ "dist/",
+ "src"
+ ],
+ "peerDependencies": {
+ "vue": ">=3.0.0"
+ },
+ "dependencies": {
+ "@tanstack/devtools-utils": "^0.3.2",
+ "@tanstack/hotkeys-devtools": "workspace:*"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^6.0.4",
+ "vue": "^3.5.29"
+ },
+ "main": "./dist/index.cjs",
+ "module": "./dist/index.js"
+}
diff --git a/packages/vue-hotkeys-devtools/src/VueHotkeysDevtools.tsx b/packages/vue-hotkeys-devtools/src/VueHotkeysDevtools.tsx
new file mode 100644
index 0000000..c4ba8b3
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/src/VueHotkeysDevtools.tsx
@@ -0,0 +1,16 @@
+import { createVuePanel } from '@tanstack/devtools-utils/vue'
+import { HotkeysDevtoolsCore } from '@tanstack/hotkeys-devtools'
+import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/vue'
+
+export interface HotkeysDevtoolsVueInit extends DevtoolsPanelProps {}
+
+type DevtoolsPanelConstructor = new (props: DevtoolsPanelProps) => {
+ mount: (el: HTMLElement, theme?: 'dark' | 'light' | 'system') => void
+ unmount: () => void
+}
+
+const [HotkeysDevtoolsPanel, HotkeysDevtoolsPanelNoOp] = createVuePanel(
+ HotkeysDevtoolsCore as unknown as DevtoolsPanelConstructor,
+)
+
+export { HotkeysDevtoolsPanel, HotkeysDevtoolsPanelNoOp }
diff --git a/packages/vue-hotkeys-devtools/src/index.ts b/packages/vue-hotkeys-devtools/src/index.ts
new file mode 100644
index 0000000..336d2da
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/src/index.ts
@@ -0,0 +1,14 @@
+import * as Devtools from './VueHotkeysDevtools'
+import * as plugin from './plugin'
+
+export const HotkeysDevtoolsPanel =
+ process.env.NODE_ENV !== 'development'
+ ? Devtools.HotkeysDevtoolsPanelNoOp
+ : Devtools.HotkeysDevtoolsPanel
+
+export const hotkeysDevtoolsPlugin =
+ process.env.NODE_ENV !== 'development'
+ ? plugin.hotkeysDevtoolsNoOpPlugin
+ : plugin.hotkeysDevtoolsPlugin
+
+export type { HotkeysDevtoolsVueInit } from './VueHotkeysDevtools'
diff --git a/packages/vue-hotkeys-devtools/src/plugin.ts b/packages/vue-hotkeys-devtools/src/plugin.ts
new file mode 100644
index 0000000..3f77088
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/src/plugin.ts
@@ -0,0 +1,9 @@
+import { createVuePlugin } from '@tanstack/devtools-utils/vue'
+import { HotkeysDevtoolsPanel } from './VueHotkeysDevtools'
+
+const [hotkeysDevtoolsPlugin, hotkeysDevtoolsNoOpPlugin] = createVuePlugin(
+ 'TanStack Hotkeys',
+ HotkeysDevtoolsPanel,
+)
+
+export { hotkeysDevtoolsPlugin, hotkeysDevtoolsNoOpPlugin }
diff --git a/packages/vue-hotkeys-devtools/src/production.ts b/packages/vue-hotkeys-devtools/src/production.ts
new file mode 100644
index 0000000..a89c5e2
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/src/production.ts
@@ -0,0 +1,5 @@
+export { HotkeysDevtoolsPanel } from './VueHotkeysDevtools'
+
+export type { HotkeysDevtoolsVueInit } from './VueHotkeysDevtools'
+
+export { hotkeysDevtoolsPlugin } from './plugin'
diff --git a/packages/vue-hotkeys-devtools/src/vue.d.ts b/packages/vue-hotkeys-devtools/src/vue.d.ts
new file mode 100644
index 0000000..b07a059
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/src/vue.d.ts
@@ -0,0 +1,6 @@
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
diff --git a/packages/vue-hotkeys-devtools/tsconfig.json b/packages/vue-hotkeys-devtools/tsconfig.json
new file mode 100644
index 0000000..e716923
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve"
+ },
+ "include": ["src", "vitest.config.ts"],
+ "exclude": ["eslint.config.js"]
+}
diff --git a/packages/vue-hotkeys-devtools/tsdown.config.ts b/packages/vue-hotkeys-devtools/tsdown.config.ts
new file mode 100644
index 0000000..fd0188f
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/tsdown.config.ts
@@ -0,0 +1,16 @@
+import { defineConfig } from 'tsdown'
+
+export default defineConfig({
+ entry: ['./src/index.ts', './src/production.ts'],
+ format: ['esm', 'cjs'],
+ unbundle: true,
+ dts: true,
+ sourcemap: true,
+ clean: true,
+ minify: false,
+ fixedExtension: false,
+ exports: true,
+ publint: {
+ strict: true,
+ },
+})
diff --git a/packages/vue-hotkeys-devtools/vitest.config.ts b/packages/vue-hotkeys-devtools/vitest.config.ts
new file mode 100644
index 0000000..61ea681
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/vitest.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from 'vitest/config'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+ plugins: [vue()],
+ test: {
+ environment: 'happy-dom',
+ globals: true,
+ },
+})
diff --git a/packages/vue-hotkeys/README.md b/packages/vue-hotkeys/README.md
new file mode 100644
index 0000000..1c80cd6
--- /dev/null
+++ b/packages/vue-hotkeys/README.md
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+### [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 – needs a contributor!
+
+## 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
+
+
+
+
+
+
+
+
+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/vue-hotkeys/eslint.config.js b/packages/vue-hotkeys/eslint.config.js
new file mode 100644
index 0000000..a3afead
--- /dev/null
+++ b/packages/vue-hotkeys/eslint.config.js
@@ -0,0 +1,13 @@
+// @ts-check
+
+import rootConfig from '../../eslint.config.js'
+
+/** @type {import('eslint').Linter.Config[]} */
+const config = [
+ ...rootConfig,
+ {
+ files: ['**/*.{ts,tsx,vue}'],
+ },
+]
+
+export default config
diff --git a/packages/vue-hotkeys/package.json b/packages/vue-hotkeys/package.json
new file mode 100644
index 0000000..df022e2
--- /dev/null
+++ b/packages/vue-hotkeys/package.json
@@ -0,0 +1,66 @@
+{
+ "name": "@tanstack/vue-hotkeys",
+ "version": "0.4.0",
+ "description": "Vue adapter for TanStack Hotkeys",
+ "author": "Tanner Linsley",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/TanStack/hotkeys.git",
+ "directory": "packages/vue-hotkeys"
+ },
+ "homepage": "https://tanstack.com/hotkeys",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "keywords": [
+ "vue",
+ "vuejs",
+ "tanstack",
+ "hotkeys",
+ "keyboard",
+ "shortcuts"
+ ],
+ "scripts": {
+ "clean": "premove ./build ./dist",
+ "lint": "eslint ./src",
+ "lint:fix": "eslint ./src --fix",
+ "test:eslint": "eslint ./src",
+ "test:lib": "vitest --passWithNoTests",
+ "test:lib:dev": "pnpm test:lib --watch",
+ "test:types": "tsc",
+ "build": "tsdown"
+ },
+ "type": "module",
+ "main": "./dist/index.cjs",
+ "module": "./dist/index.js",
+ "types": "./dist/index.d.cts",
+ "exports": {
+ ".": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ },
+ "./package.json": "./package.json"
+ },
+ "sideEffects": false,
+ "engines": {
+ "node": ">=18"
+ },
+ "files": [
+ "dist",
+ "src"
+ ],
+ "dependencies": {
+ "@tanstack/hotkeys": "workspace:*",
+ "@tanstack/vue-store": "^0.9.2"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^6.0.4",
+ "@vue/test-utils": "^2.4.6",
+ "vue": "^3.5.29"
+ },
+ "peerDependencies": {
+ "vue": ">=3.0.0"
+ }
+}
diff --git a/packages/vue-hotkeys/src/HotkeysProvider.tsx b/packages/vue-hotkeys/src/HotkeysProvider.tsx
new file mode 100644
index 0000000..7e37c70
--- /dev/null
+++ b/packages/vue-hotkeys/src/HotkeysProvider.tsx
@@ -0,0 +1,29 @@
+import { defineComponent } from 'vue'
+import { provideHotkeysContext } from './HotkeysProviderContext'
+import type { HotkeysProviderOptions } from './HotkeysProviderContext'
+
+/**
+ * Vue component that provides default options for hotkeys context.
+ *
+ * @example
+ * ```vue
+ *
+ *
+ *
+ *
+ *
+ * ```
+ */
+export const HotkeysProvider = defineComponent({
+ name: 'HotkeysProvider',
+ props: {
+ defaultOptions: {
+ type: Object as () => HotkeysProviderOptions,
+ default: undefined,
+ },
+ },
+ setup(props, { slots }) {
+ provideHotkeysContext(props.defaultOptions)
+ return () => slots.default?.()
+ },
+})
diff --git a/packages/vue-hotkeys/src/HotkeysProviderContext.ts b/packages/vue-hotkeys/src/HotkeysProviderContext.ts
new file mode 100644
index 0000000..10f1aff
--- /dev/null
+++ b/packages/vue-hotkeys/src/HotkeysProviderContext.ts
@@ -0,0 +1,37 @@
+import { inject, provide } from 'vue'
+import type { InjectionKey } from 'vue'
+import type { HotkeyRecorderOptions } from '@tanstack/hotkeys'
+import type { UseHotkeyOptions } from './useHotkey'
+import type { UseHotkeySequenceOptions } from './useHotkeySequence'
+
+export interface HotkeysProviderOptions {
+ hotkey?: Partial
+ hotkeyRecorder?: Partial
+ hotkeySequence?: Partial
+}
+
+interface HotkeysContextValue {
+ defaultOptions: HotkeysProviderOptions
+}
+
+const HotkeysContext: InjectionKey =
+ Symbol('HotkeysContext')
+
+const DEFAULT_OPTIONS: HotkeysProviderOptions = {}
+
+export function provideHotkeysContext(defaultOptions?: HotkeysProviderOptions) {
+ const contextValue: HotkeysContextValue = {
+ defaultOptions: defaultOptions ?? DEFAULT_OPTIONS,
+ }
+
+ provide(HotkeysContext, contextValue)
+}
+
+export function useHotkeysContext() {
+ return inject(HotkeysContext, null)
+}
+
+export function useDefaultHotkeysOptions() {
+ const context = inject(HotkeysContext, null)
+ return context?.defaultOptions ?? {}
+}
diff --git a/packages/vue-hotkeys/src/index.ts b/packages/vue-hotkeys/src/index.ts
new file mode 100644
index 0000000..78cd36d
--- /dev/null
+++ b/packages/vue-hotkeys/src/index.ts
@@ -0,0 +1,14 @@
+// Re-export everything from the core package
+export * from '@tanstack/hotkeys'
+
+// Provider
+export * from './HotkeysProvider'
+export * from './HotkeysProviderContext'
+
+// Vue-specific composables
+export * from './useHotkey'
+export * from './useHeldKeys'
+export * from './useHeldKeyCodes'
+export * from './useKeyHold'
+export * from './useHotkeySequence'
+export * from './useHotkeyRecorder'
diff --git a/packages/vue-hotkeys/src/useHeldKeyCodes.ts b/packages/vue-hotkeys/src/useHeldKeyCodes.ts
new file mode 100644
index 0000000..b182ec0
--- /dev/null
+++ b/packages/vue-hotkeys/src/useHeldKeyCodes.ts
@@ -0,0 +1,34 @@
+import { useStore } from '@tanstack/vue-store'
+import { getKeyStateTracker } from '@tanstack/hotkeys'
+import type { Ref } from 'vue'
+
+/**
+ * Vue composable that returns a reactive ref mapping 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 Reactive ref containing record mapping normalized key names to their `event.code` values
+ *
+ * @example
+ * ```vue
+ *
+ *
+ *
+ *
+ *
+ * {{ key }} {{ heldCodes[key] }}
+ *
+ *
+ *
+ * ```
+ */
+export function useHeldKeyCodes(): Ref> {
+ const tracker = getKeyStateTracker()
+ return useStore(tracker.store, (state) => state.heldCodes)
+}
diff --git a/packages/vue-hotkeys/src/useHeldKeys.ts b/packages/vue-hotkeys/src/useHeldKeys.ts
new file mode 100644
index 0000000..c93ae30
--- /dev/null
+++ b/packages/vue-hotkeys/src/useHeldKeys.ts
@@ -0,0 +1,32 @@
+import { useStore } from '@tanstack/vue-store'
+import { getKeyStateTracker } from '@tanstack/hotkeys'
+import type { Ref } from 'vue'
+
+/**
+ * Vue composable that returns a reactive ref of currently held keyboard keys.
+ *
+ * This composable uses `useStore` from `@tanstack/vue-store` to subscribe
+ * to the global KeyStateTracker and updates whenever keys are pressed
+ * or released.
+ *
+ * @returns Reactive ref containing array of currently held key names
+ *
+ * @example
+ * ```vue
+ *
+ *
+ *
+ *
+ * Currently pressed: {{ heldKeys.join(' + ') || 'None' }}
+ *
+ *
+ * ```
+ */
+export function useHeldKeys(): Ref> {
+ const tracker = getKeyStateTracker()
+ return useStore(tracker.store, (state) => state.heldKeys)
+}
diff --git a/packages/vue-hotkeys/src/useHotkey.ts b/packages/vue-hotkeys/src/useHotkey.ts
new file mode 100644
index 0000000..58c3acb
--- /dev/null
+++ b/packages/vue-hotkeys/src/useHotkey.ts
@@ -0,0 +1,197 @@
+import { onUnmounted, unref, watch } from 'vue'
+import {
+ detectPlatform,
+ formatHotkey,
+ getHotkeyManager,
+ rawHotkeyToParsedHotkey,
+} from '@tanstack/hotkeys'
+import { useDefaultHotkeysOptions } from './HotkeysProviderContext'
+import type {
+ Hotkey,
+ HotkeyCallback,
+ HotkeyOptions,
+ HotkeyRegistrationHandle,
+ RegisterableHotkey,
+} from '@tanstack/hotkeys'
+import type { MaybeRefOrGetter } from 'vue'
+
+export interface UseHotkeyOptions extends Omit<
+ HotkeyOptions,
+ 'enabled' | 'target'
+> {
+ /**
+ * Whether the hotkey is active.
+ * Can be a Ref, a getter function, or a boolean value.
+ * Defaults to true.
+ */
+ enabled?: MaybeRefOrGetter
+ /**
+ * The DOM element to attach the event listener to.
+ * Can be a Ref, a getter function, direct DOM element, or null.
+ * Defaults to document.
+ */
+ target?: MaybeRefOrGetter
+}
+
+/**
+ * Vue composable for registering a keyboard hotkey.
+ *
+ * Uses the singleton HotkeyManager for efficient event handling.
+ * The callback receives both the keyboard event and a context object
+ * containing the hotkey string and parsed hotkey.
+ *
+ * This composable automatically tracks reactive dependencies and updates
+ * the registration when options or the callback change.
+ *
+ * @param hotkey - The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object (supports `mod` for cross-platform)
+ * @param callback - The function to call when the hotkey is pressed
+ * @param options - Options for the hotkey behavior
+ *
+ * @example
+ * ```vue
+ *
+ * ```
+ *
+ * @example
+ * ```vue
+ *
+ * ```
+ *
+ * @example
+ * ```vue
+ *
+ *
+ *
+ * ...
+ *
+ * ```
+ */
+export function useHotkey(
+ hotkey: MaybeRefOrGetter,
+ callback: HotkeyCallback,
+ options: MaybeRefOrGetter = {},
+): void {
+ const defaultOptions = useDefaultHotkeysOptions()
+ const manager = getHotkeyManager()
+
+ let registration: HotkeyRegistrationHandle | null = null
+
+ // Watch for changes to reactive dependencies
+ const stopWatcher = watch(
+ () => {
+ const resolvedHotkey = resolveMaybeRefOrGetter(hotkey)
+ const resolvedOptions = resolveMaybeRefOrGetter(options)
+ const mergedOptions = {
+ ...defaultOptions.hotkey,
+ ...resolvedOptions,
+ } as UseHotkeyOptions
+ const resolvedEnabled =
+ mergedOptions.enabled === undefined
+ ? undefined
+ : resolveMaybeRefOrGetter(mergedOptions.enabled)
+ const resolvedTarget =
+ mergedOptions.target === undefined
+ ? undefined
+ : resolveMaybeRefOrGetter(mergedOptions.target)
+
+ return {
+ resolvedHotkey,
+ mergedOptions,
+ resolvedEnabled,
+ resolvedTarget,
+ }
+ },
+ ({ resolvedHotkey, mergedOptions, resolvedEnabled, resolvedTarget }) => {
+ // Normalize to hotkey string
+ const platform = mergedOptions.platform ?? detectPlatform()
+ const hotkeyString: Hotkey =
+ typeof resolvedHotkey === 'string'
+ ? resolvedHotkey
+ : (formatHotkey(
+ rawHotkeyToParsedHotkey(resolvedHotkey as any, platform),
+ ) as Hotkey)
+
+ // Resolve target
+ const finalTarget =
+ resolvedTarget ?? (typeof document !== 'undefined' ? document : null)
+
+ if (!finalTarget) {
+ return
+ }
+
+ // Unregister previous registration if it exists
+ if (registration?.isActive) {
+ registration.unregister()
+ registration = null
+ }
+
+ // Extract options without target (target is handled separately)
+ const {
+ target: _target,
+ enabled: _enabled,
+ ...restOptions
+ } = mergedOptions
+ const optionsWithoutTarget = {
+ ...restOptions,
+ ...(resolvedEnabled === undefined ? {} : { enabled: resolvedEnabled }),
+ }
+
+ // Register the hotkey
+ registration = manager.register(hotkeyString, callback, {
+ ...optionsWithoutTarget,
+ target: finalTarget,
+ })
+
+ // Update callback and options
+ if (registration.isActive) {
+ registration.callback = callback
+ registration.setOptions(optionsWithoutTarget)
+ }
+ },
+ { immediate: true },
+ )
+
+ // Cleanup on unmount
+ onUnmounted(() => {
+ stopWatcher()
+ if (registration?.isActive) {
+ registration.unregister()
+ registration = null
+ }
+ })
+}
+
+function resolveMaybeRefOrGetter(value: MaybeRefOrGetter): T {
+ return typeof value === 'function' ? (value as () => T)() : unref(value)
+}
diff --git a/packages/vue-hotkeys/src/useHotkeyRecorder.ts b/packages/vue-hotkeys/src/useHotkeyRecorder.ts
new file mode 100644
index 0000000..9a9cade
--- /dev/null
+++ b/packages/vue-hotkeys/src/useHotkeyRecorder.ts
@@ -0,0 +1,112 @@
+import { onUnmounted, unref, watch } from 'vue'
+import { useStore } from '@tanstack/vue-store'
+import { HotkeyRecorder } from '@tanstack/hotkeys'
+import { useDefaultHotkeysOptions } from './HotkeysProviderContext'
+import type { MaybeRefOrGetter, Ref } from 'vue'
+import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys'
+
+export interface VueHotkeyRecorder {
+ /** Whether recording is currently active */
+ isRecording: Ref
+ /** The currently recorded hotkey (for live preview) */
+ recordedHotkey: Ref
+ /** Start recording a new hotkey */
+ startRecording: () => void
+ /** Stop recording (same as cancel) */
+ stopRecording: () => void
+ /** Cancel recording without saving */
+ cancelRecording: () => void
+}
+
+/**
+ * Vue composable for recording keyboard shortcuts.
+ *
+ * This composable 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
+ * ```vue
+ *
+ *
+ *
+ *
+ *
+ * {{ recorder.isRecording ? 'Recording...' : 'Edit Shortcut' }}
+ *
+ *
+ * Recording: {{ recorder.recordedHotkey }}
+ *
+ *
+ *
+ * ```
+ */
+export function useHotkeyRecorder(
+ options: MaybeRefOrGetter,
+): VueHotkeyRecorder {
+ const defaultOptions = useDefaultHotkeysOptions()
+ const recorder = new HotkeyRecorder(
+ resolveRecorderOptions(options, defaultOptions),
+ )
+
+ // Subscribe to recorder state using useStore
+ const isRecording = useStore(recorder.store, (state) => state.isRecording)
+ const recordedHotkey = useStore(
+ recorder.store,
+ (state) => state.recordedHotkey,
+ )
+
+ const stopWatcher = watch(
+ () => resolveRecorderOptions(options, defaultOptions),
+ (resolvedOptions) => {
+ recorder.setOptions(resolvedOptions)
+ },
+ { immediate: true },
+ )
+
+ // Cleanup on unmount
+ onUnmounted(() => {
+ stopWatcher()
+ recorder.destroy()
+ })
+
+ return {
+ isRecording: isRecording as Ref,
+ recordedHotkey: recordedHotkey as Ref,
+ startRecording: () => recorder.start(),
+ stopRecording: () => recorder.stop(),
+ cancelRecording: () => recorder.cancel(),
+ }
+}
+
+function resolveRecorderOptions(
+ options: MaybeRefOrGetter,
+ defaultOptions: ReturnType,
+): HotkeyRecorderOptions {
+ return {
+ ...defaultOptions.hotkeyRecorder,
+ ...resolveMaybeRefOrGetter(options),
+ } as HotkeyRecorderOptions
+}
+
+function resolveMaybeRefOrGetter(value: MaybeRefOrGetter): T {
+ return typeof value === 'function' ? (value as () => T)() : unref(value)
+}
diff --git a/packages/vue-hotkeys/src/useHotkeySequence.ts b/packages/vue-hotkeys/src/useHotkeySequence.ts
new file mode 100644
index 0000000..16375e4
--- /dev/null
+++ b/packages/vue-hotkeys/src/useHotkeySequence.ts
@@ -0,0 +1,158 @@
+import { onUnmounted, unref, watch } from 'vue'
+import { getSequenceManager } from '@tanstack/hotkeys'
+import { useDefaultHotkeysOptions } from './HotkeysProviderContext'
+import type { MaybeRefOrGetter } from 'vue'
+import type {
+ HotkeyCallback,
+ HotkeySequence,
+ SequenceOptions,
+ SequenceRegistrationHandle,
+} from '@tanstack/hotkeys'
+
+export interface UseHotkeySequenceOptions extends Omit<
+ SequenceOptions,
+ 'enabled' | 'target'
+> {
+ /**
+ * Whether the sequence is active.
+ * Can be a Ref, a getter function, or a boolean value.
+ * Defaults to true.
+ */
+ enabled?: MaybeRefOrGetter
+ /**
+ * The DOM element to attach the event listener to.
+ * Can be a Ref, a getter function, direct DOM element, or null.
+ * Defaults to document.
+ */
+ target?: MaybeRefOrGetter
+}
+
+/**
+ * Vue composable for registering a keyboard shortcut sequence (Vim-style).
+ *
+ * This composable allows you to register multi-key sequences like 'g g' or 'd d'
+ * that trigger when the full sequence is pressed within a timeout.
+ *
+ * @param sequence - Array of hotkey strings that form the sequence
+ * @param callback - Function to call when the sequence is completed
+ * @param options - Options for the sequence behavior
+ *
+ * @example
+ * ```vue
+ *
+ *
+ *
+ * ...
+ *
+ * ```
+ */
+export function useHotkeySequence(
+ sequence: MaybeRefOrGetter,
+ callback: HotkeyCallback,
+ options: MaybeRefOrGetter = {},
+): void {
+ const defaultOptions = useDefaultHotkeysOptions()
+ const manager = getSequenceManager()
+
+ let registration: SequenceRegistrationHandle | null = null
+
+ // Watch for changes to reactive dependencies
+ const stopWatcher = watch(
+ () => {
+ const resolvedSequence = resolveMaybeRefOrGetter(sequence)
+ const resolvedOptions = resolveMaybeRefOrGetter(options)
+ const mergedOptions = {
+ ...defaultOptions.hotkeySequence,
+ ...resolvedOptions,
+ } as UseHotkeySequenceOptions
+ const resolvedEnabled =
+ mergedOptions.enabled === undefined
+ ? undefined
+ : resolveMaybeRefOrGetter(mergedOptions.enabled)
+ const resolvedTarget =
+ mergedOptions.target === undefined
+ ? undefined
+ : resolveMaybeRefOrGetter(mergedOptions.target)
+
+ return {
+ resolvedSequence,
+ mergedOptions,
+ resolvedEnabled,
+ resolvedTarget,
+ }
+ },
+ ({ resolvedSequence, mergedOptions, resolvedEnabled, resolvedTarget }) => {
+ if (resolvedEnabled === false || resolvedSequence.length === 0) {
+ return
+ }
+
+ // Resolve target
+ const finalTarget =
+ resolvedTarget ?? (typeof document !== 'undefined' ? document : null)
+
+ if (!finalTarget) {
+ return
+ }
+
+ // Unregister previous registration if it exists
+ if (registration?.isActive) {
+ registration.unregister()
+ registration = null
+ }
+
+ // Extract options without target (target is handled separately)
+ const {
+ target: _target,
+ enabled: _enabled,
+ ...restOptions
+ } = mergedOptions
+ const optionsWithoutTarget = {
+ ...restOptions,
+ ...(resolvedEnabled === undefined ? {} : { enabled: resolvedEnabled }),
+ }
+
+ // Register the sequence
+ registration = manager.register(resolvedSequence, callback, {
+ ...optionsWithoutTarget,
+ target: finalTarget,
+ })
+
+ // Update callback and options
+ if (registration.isActive) {
+ registration.callback = callback
+ registration.setOptions(optionsWithoutTarget)
+ }
+ },
+ { immediate: true },
+ )
+
+ // Cleanup on unmount
+ onUnmounted(() => {
+ stopWatcher()
+ if (registration?.isActive) {
+ registration.unregister()
+ registration = null
+ }
+ })
+}
+
+function resolveMaybeRefOrGetter(value: MaybeRefOrGetter): T {
+ return typeof value === 'function' ? (value as () => T)() : unref(value)
+}
diff --git a/packages/vue-hotkeys/src/useKeyHold.ts b/packages/vue-hotkeys/src/useKeyHold.ts
new file mode 100644
index 0000000..89aacca
--- /dev/null
+++ b/packages/vue-hotkeys/src/useKeyHold.ts
@@ -0,0 +1,65 @@
+import { useStore } from '@tanstack/vue-store'
+import { getKeyStateTracker } from '@tanstack/hotkeys'
+import { unref } from 'vue'
+import type { MaybeRefOrGetter, Ref } from 'vue'
+import type { HeldKey } from '@tanstack/hotkeys'
+
+/**
+ * Vue composable that returns a reactive ref indicating whether a specific key is currently being held.
+ *
+ * This composable uses `useStore` from `@tanstack/vue-store` to subscribe
+ * to the global KeyStateTracker and uses a selector to determine if
+ * the specified key is held.
+ *
+ * @param key - The key to check (e.g., 'Shift', 'Control', 'A')
+ * @returns Reactive ref that is true if the key is currently held down
+ *
+ * @example
+ * ```vue
+ *
+ *
+ *
+ *
+ * {{ isShiftHeld ? 'Shift is pressed!' : 'Press Shift' }}
+ *
+ *
+ * ```
+ *
+ * @example
+ * ```vue
+ *
+ *
+ *
+ *
+ * Ctrl
+ * Shift
+ * Alt
+ *
+ *
+ * ```
+ */
+export function useKeyHold(key: MaybeRefOrGetter): Ref {
+ const tracker = getKeyStateTracker()
+
+ const isHeld = useStore(tracker.store, (state) => {
+ const keyValue = unref(key)
+ const normalizedKey = (
+ typeof keyValue === 'string' ? keyValue : String(keyValue)
+ ).toLowerCase()
+ return state.heldKeys.some(
+ (heldKey) => heldKey.toLowerCase() === normalizedKey,
+ )
+ })
+
+ return isHeld as Ref
+}
diff --git a/packages/vue-hotkeys/src/vue.d.ts b/packages/vue-hotkeys/src/vue.d.ts
new file mode 100644
index 0000000..b07a059
--- /dev/null
+++ b/packages/vue-hotkeys/src/vue.d.ts
@@ -0,0 +1,6 @@
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
diff --git a/packages/vue-hotkeys/tests/vue-hotkeys.test.ts b/packages/vue-hotkeys/tests/vue-hotkeys.test.ts
new file mode 100644
index 0000000..262a9df
--- /dev/null
+++ b/packages/vue-hotkeys/tests/vue-hotkeys.test.ts
@@ -0,0 +1,149 @@
+import { defineComponent, h, nextTick, ref } from 'vue'
+import { mount } from '@vue/test-utils'
+import { HotkeyManager } from '@tanstack/hotkeys'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import { useHotkey } from '../src/useHotkey'
+import {
+ useHotkeyRecorder,
+ type VueHotkeyRecorder,
+} from '../src/useHotkeyRecorder'
+
+describe('vue hotkeys adapters', () => {
+ beforeEach(() => {
+ HotkeyManager.resetInstance()
+ })
+
+ afterEach(() => {
+ HotkeyManager.resetInstance()
+ })
+
+ it('syncs a ref passed to the enabled option', async () => {
+ const callback = vi.fn()
+ const enabled = ref(true)
+
+ const wrapper = mount(
+ defineComponent({
+ setup() {
+ useHotkey('Mod+S', callback, {
+ enabled,
+ platform: 'mac',
+ })
+
+ return () => h('div')
+ },
+ }),
+ )
+
+ document.dispatchEvent(
+ new KeyboardEvent('keydown', {
+ key: 's',
+ metaKey: true,
+ bubbles: true,
+ }),
+ )
+ expect(callback).toHaveBeenCalledTimes(1)
+
+ enabled.value = false
+ await nextTick()
+
+ document.dispatchEvent(
+ new KeyboardEvent('keydown', {
+ key: 's',
+ metaKey: true,
+ bubbles: true,
+ }),
+ )
+ expect(callback).toHaveBeenCalledTimes(1)
+
+ enabled.value = true
+ await nextTick()
+
+ document.dispatchEvent(
+ new KeyboardEvent('keydown', {
+ key: 's',
+ metaKey: true,
+ bubbles: true,
+ }),
+ )
+ expect(callback).toHaveBeenCalledTimes(2)
+
+ wrapper.unmount()
+ })
+
+ it('registers once a ref target is attached', async () => {
+ const callback = vi.fn()
+ const target = ref(null)
+
+ const wrapper = mount(
+ defineComponent({
+ setup() {
+ useHotkey('Mod+S', callback, {
+ target,
+ platform: 'mac',
+ })
+
+ return () => h('div', { ref: target, tabIndex: 0 })
+ },
+ }),
+ )
+
+ await nextTick()
+
+ wrapper.element.dispatchEvent(
+ new KeyboardEvent('keydown', {
+ key: 's',
+ metaKey: true,
+ bubbles: true,
+ }),
+ )
+
+ expect(callback).toHaveBeenCalledTimes(1)
+
+ wrapper.unmount()
+ })
+
+ it('syncs recorder options from a getter', async () => {
+ const onRecord = vi.fn()
+ const firstOnCancel = vi.fn()
+ const secondOnCancel = vi.fn()
+ const onCancel = ref(firstOnCancel)
+ let recorder!: VueHotkeyRecorder
+
+ const wrapper = mount(
+ defineComponent({
+ setup() {
+ recorder = useHotkeyRecorder(() => ({
+ onRecord,
+ onCancel: onCancel.value,
+ }))
+
+ return () => h('div')
+ },
+ }),
+ )
+
+ recorder.startRecording()
+ document.dispatchEvent(
+ new KeyboardEvent('keydown', {
+ key: 'Escape',
+ bubbles: true,
+ }),
+ )
+ expect(firstOnCancel).toHaveBeenCalledTimes(1)
+ expect(secondOnCancel).not.toHaveBeenCalled()
+
+ onCancel.value = secondOnCancel
+ await nextTick()
+
+ recorder.startRecording()
+ document.dispatchEvent(
+ new KeyboardEvent('keydown', {
+ key: 'Escape',
+ bubbles: true,
+ }),
+ )
+ expect(secondOnCancel).toHaveBeenCalledTimes(1)
+
+ wrapper.unmount()
+ })
+})
diff --git a/packages/vue-hotkeys/tsconfig.docs.json b/packages/vue-hotkeys/tsconfig.docs.json
new file mode 100644
index 0000000..214e95c
--- /dev/null
+++ b/packages/vue-hotkeys/tsconfig.docs.json
@@ -0,0 +1,5 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src"],
+ "exclude": ["tests", "vitest.config.ts"]
+}
diff --git a/packages/vue-hotkeys/tsconfig.json b/packages/vue-hotkeys/tsconfig.json
new file mode 100644
index 0000000..d24b3d3
--- /dev/null
+++ b/packages/vue-hotkeys/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "preserve"
+ },
+ "include": ["src", "src/**/*.vue", "vitest.config.ts", "tests"],
+ "exclude": ["eslint.config.js"]
+}
diff --git a/packages/vue-hotkeys/tsdown.config.ts b/packages/vue-hotkeys/tsdown.config.ts
new file mode 100644
index 0000000..71071cb
--- /dev/null
+++ b/packages/vue-hotkeys/tsdown.config.ts
@@ -0,0 +1,16 @@
+import { defineConfig } from 'tsdown'
+
+export default defineConfig({
+ entry: ['./src/index.ts'],
+ format: ['esm', 'cjs'],
+ unbundle: true,
+ dts: true,
+ sourcemap: true,
+ clean: true,
+ minify: false,
+ fixedExtension: false,
+ exports: true,
+ publint: {
+ strict: true,
+ },
+})
diff --git a/packages/vue-hotkeys/vitest.config.ts b/packages/vue-hotkeys/vitest.config.ts
new file mode 100644
index 0000000..61ea681
--- /dev/null
+++ b/packages/vue-hotkeys/vitest.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from 'vitest/config'
+import vue from '@vitejs/plugin-vue'
+
+export default defineConfig({
+ plugins: [vue()],
+ test: {
+ environment: 'happy-dom',
+ globals: true,
+ },
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1277775..5042696 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -730,7 +730,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.2
- version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)
+ version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)(vue@3.5.29(typescript@5.9.3))
'@tanstack/solid-devtools':
specifier: 0.7.30
version: 0.7.30(csstype@3.2.3)(solid-js@1.9.11)
@@ -755,7 +755,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.2
- version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)
+ version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)(vue@3.5.29(typescript@5.9.3))
'@tanstack/solid-devtools':
specifier: 0.7.30
version: 0.7.30(csstype@3.2.3)(solid-js@1.9.11)
@@ -786,7 +786,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.2
- version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)
+ version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)(vue@3.5.29(typescript@5.9.3))
'@tanstack/solid-devtools':
specifier: 0.7.30
version: 0.7.30(csstype@3.2.3)(solid-js@1.9.11)
@@ -811,7 +811,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.2
- version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)
+ version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)(vue@3.5.29(typescript@5.9.3))
'@tanstack/solid-devtools':
specifier: 0.7.30
version: 0.7.30(csstype@3.2.3)(solid-js@1.9.11)
@@ -836,7 +836,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.2
- version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)
+ version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)(vue@3.5.29(typescript@5.9.3))
'@tanstack/solid-devtools':
specifier: 0.7.30
version: 0.7.30(csstype@3.2.3)(solid-js@1.9.11)
@@ -857,6 +857,131 @@ 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/vue/useHeldKeys:
+ dependencies:
+ '@tanstack/vue-hotkeys':
+ specifier: ^0.4.0
+ version: link:../../../packages/vue-hotkeys
+ vue:
+ specifier: ^3.5.29
+ version: 3.5.29(typescript@5.9.3)
+ devDependencies:
+ '@tanstack/vue-devtools':
+ specifier: ^0.2.10
+ version: 0.2.10(csstype@3.2.3)(solid-js@1.9.11)
+ '@tanstack/vue-hotkeys-devtools':
+ specifier: ^0.4.0
+ version: link:../../../packages/vue-hotkeys-devtools
+ '@vitejs/plugin-vue':
+ specifier: ^6.0.4
+ version: 6.0.4(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))(vue@3.5.29(typescript@5.9.3))
+ 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/useHotkey:
+ dependencies:
+ '@tanstack/vue-hotkeys':
+ specifier: ^0.4.0
+ version: link:../../../packages/vue-hotkeys
+ vue:
+ specifier: ^3.5.29
+ version: 3.5.29(typescript@5.9.3)
+ devDependencies:
+ '@tanstack/vue-devtools':
+ specifier: ^0.2.10
+ version: 0.2.10(csstype@3.2.3)(solid-js@1.9.11)
+ '@tanstack/vue-hotkeys-devtools':
+ specifier: ^0.4.0
+ version: link:../../../packages/vue-hotkeys-devtools
+ '@vitejs/plugin-vue':
+ specifier: ^6.0.4
+ version: 6.0.4(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))(vue@3.5.29(typescript@5.9.3))
+ 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/useHotkeyRecorder:
+ dependencies:
+ '@tanstack/vue-hotkeys':
+ specifier: ^0.4.0
+ version: link:../../../packages/vue-hotkeys
+ vue:
+ specifier: ^3.5.29
+ version: 3.5.29(typescript@5.9.3)
+ devDependencies:
+ '@tanstack/vue-devtools':
+ specifier: ^0.2.10
+ version: 0.2.10(csstype@3.2.3)(solid-js@1.9.11)
+ '@tanstack/vue-hotkeys-devtools':
+ specifier: ^0.4.0
+ version: link:../../../packages/vue-hotkeys-devtools
+ '@vitejs/plugin-vue':
+ specifier: ^6.0.4
+ version: 6.0.4(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))(vue@3.5.29(typescript@5.9.3))
+ 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/useHotkeySequence:
+ dependencies:
+ '@tanstack/vue-hotkeys':
+ specifier: ^0.4.0
+ version: link:../../../packages/vue-hotkeys
+ vue:
+ specifier: ^3.5.29
+ version: 3.5.29(typescript@5.9.3)
+ devDependencies:
+ '@tanstack/vue-devtools':
+ specifier: ^0.2.10
+ version: 0.2.10(csstype@3.2.3)(solid-js@1.9.11)
+ '@tanstack/vue-hotkeys-devtools':
+ specifier: ^0.4.0
+ version: link:../../../packages/vue-hotkeys-devtools
+ '@vitejs/plugin-vue':
+ specifier: ^6.0.4
+ version: 6.0.4(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))(vue@3.5.29(typescript@5.9.3))
+ 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/useKeyhold:
+ dependencies:
+ '@tanstack/vue-hotkeys':
+ specifier: ^0.4.0
+ version: link:../../../packages/vue-hotkeys
+ vue:
+ specifier: ^3.5.29
+ version: 3.5.29(typescript@5.9.3)
+ devDependencies:
+ '@tanstack/vue-devtools':
+ specifier: ^0.2.10
+ version: 0.2.10(csstype@3.2.3)(solid-js@1.9.11)
+ '@tanstack/vue-hotkeys-devtools':
+ specifier: ^0.4.0
+ version: link:../../../packages/vue-hotkeys-devtools
+ '@vitejs/plugin-vue':
+ specifier: ^6.0.4
+ version: 6.0.4(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))(vue@3.5.29(typescript@5.9.3))
+ 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)
+
packages/angular-hotkeys:
dependencies:
'@tanstack/angular-store':
@@ -892,7 +1017,7 @@ importers:
version: 0.5.0(csstype@3.2.3)(solid-js@1.9.11)
'@tanstack/devtools-utils':
specifier: ^0.3.2
- version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)
+ version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)(vue@3.5.29(typescript@5.9.3))
'@tanstack/hotkeys':
specifier: workspace:*
version: link:../hotkeys
@@ -933,7 +1058,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.2
- version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)
+ version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)(vue@3.5.29(typescript@5.9.3))
'@tanstack/hotkeys-devtools':
specifier: workspace:*
version: link:../hotkeys-devtools
@@ -983,7 +1108,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.2
- version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)
+ version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)(vue@3.5.29(typescript@5.9.3))
'@tanstack/hotkeys-devtools':
specifier: workspace:*
version: link:../hotkeys-devtools
@@ -1039,7 +1164,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.2
- version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)
+ version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)(vue@3.5.29(typescript@5.9.3))
'@tanstack/hotkeys-devtools':
specifier: workspace:*
version: link:../hotkeys-devtools
@@ -1051,6 +1176,41 @@ 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/vue-hotkeys:
+ dependencies:
+ '@tanstack/hotkeys':
+ specifier: workspace:*
+ version: link:../hotkeys
+ '@tanstack/vue-store':
+ specifier: ^0.9.2
+ version: 0.9.2(vue@3.5.29(typescript@5.9.3))
+ devDependencies:
+ '@vitejs/plugin-vue':
+ specifier: ^6.0.4
+ version: 6.0.4(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))(vue@3.5.29(typescript@5.9.3))
+ '@vue/test-utils':
+ specifier: ^2.4.6
+ version: 2.4.6
+ vue:
+ specifier: ^3.5.29
+ version: 3.5.29(typescript@5.9.3)
+
+ packages/vue-hotkeys-devtools:
+ dependencies:
+ '@tanstack/devtools-utils':
+ specifier: ^0.3.2
+ version: 0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)(vue@3.5.29(typescript@5.9.3))
+ '@tanstack/hotkeys-devtools':
+ specifier: workspace:*
+ version: link:../hotkeys-devtools
+ devDependencies:
+ '@vitejs/plugin-vue':
+ specifier: ^6.0.4
+ version: 6.0.4(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))(vue@3.5.29(typescript@5.9.3))
+ vue:
+ specifier: ^3.5.29
+ version: 3.5.29(typescript@5.9.3)
+
packages:
'@adobe/css-tools@4.4.4':
@@ -2374,6 +2534,10 @@ packages:
'@types/node':
optional: true
+ '@isaacs/cliui@8.0.2':
+ resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+ engines: {node: '>=12'}
+
'@isaacs/cliui@9.0.0':
resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==}
engines: {node: '>=18'}
@@ -2865,6 +3029,9 @@ packages:
cpu: [x64]
os: [win32]
+ '@one-ini/wasm@0.1.1':
+ resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
+
'@oxc-project/types@0.113.0':
resolution: {integrity: sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA==}
@@ -3101,6 +3268,10 @@ packages:
resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==}
engines: {node: '>=20.0.0'}
+ '@pkgjs/parseargs@0.11.0':
+ resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+ engines: {node: '>=14'}
+
'@preact/preset-vite@2.10.3':
resolution: {integrity: sha512-1SiS+vFItpkNdBs7q585PSAIln0wBeBdcpJYbzPs1qipsb/FssnkUioNXuRsb8ZnU8YEQHr+3v8+/mzWSnTQmg==}
peerDependencies:
@@ -3307,6 +3478,9 @@ packages:
cpu: [x64]
os: [win32]
+ '@rolldown/pluginutils@1.0.0-rc.2':
+ resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==}
+
'@rolldown/pluginutils@1.0.0-rc.3':
resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==}
@@ -3692,6 +3866,19 @@ packages:
resolution: {integrity: sha512-wVT2YfKDSpd+4f7fk6UaPIP3a2J7LSovlyVuFF1PH2yQb7gjqehod5zdFiwFyEXgvI9XGuFvvs1OehkKNYcr6A==}
engines: {node: '>=18'}
+ '@tanstack/vue-devtools@0.2.10':
+ resolution: {integrity: sha512-1DgbO8EhfpW1klCxUFU/YU7LsKxzjrdViwkzgBd61IMY1bWBiLs+MxUx+zg8YIrboh0Jowc+LPKcKHPKaroPlQ==}
+ engines: {node: '>=18'}
+
+ '@tanstack/vue-store@0.9.2':
+ resolution: {integrity: sha512-Cz2QvwWg/vDziuCnP5TVjmZroshM8rI+Yaifqiv6A5jK8eaMxD5r86SJLUtznnOEGmqpTOPdp3ryvWzuPVTZrA==}
+ peerDependencies:
+ '@vue/composition-api': ^1.2.1
+ vue: ^2.5.0 || ^3.0.0
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+
'@testing-library/dom@10.4.1':
resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
engines: {node: '>=18'}
@@ -4032,6 +4219,13 @@ packages:
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+ '@vitejs/plugin-vue@6.0.4':
+ resolution: {integrity: sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+ vue: ^3.2.25
+
'@vitest/expect@4.0.18':
resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==}
@@ -4061,6 +4255,38 @@ packages:
'@vitest/utils@4.0.18':
resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
+ '@vue/compiler-core@3.5.29':
+ resolution: {integrity: sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==}
+
+ '@vue/compiler-dom@3.5.29':
+ resolution: {integrity: sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==}
+
+ '@vue/compiler-sfc@3.5.29':
+ resolution: {integrity: sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==}
+
+ '@vue/compiler-ssr@3.5.29':
+ resolution: {integrity: sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==}
+
+ '@vue/reactivity@3.5.29':
+ resolution: {integrity: sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==}
+
+ '@vue/runtime-core@3.5.29':
+ resolution: {integrity: sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==}
+
+ '@vue/runtime-dom@3.5.29':
+ resolution: {integrity: sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==}
+
+ '@vue/server-renderer@3.5.29':
+ resolution: {integrity: sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==}
+ peerDependencies:
+ vue: 3.5.29
+
+ '@vue/shared@3.5.29':
+ resolution: {integrity: sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==}
+
+ '@vue/test-utils@2.4.6':
+ resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==}
+
'@webassemblyjs/ast@1.14.1':
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
@@ -4123,6 +4349,10 @@ packages:
resolution: {integrity: sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==}
hasBin: true
+ abbrev@2.0.0:
+ resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
abbrev@4.0.0:
resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -4573,6 +4803,10 @@ packages:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
+ commander@10.0.1:
+ resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
+ engines: {node: '>=14'}
+
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -4594,6 +4828,9 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ config-chain@1.1.13:
+ resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
+
connect-history-api-fallback@2.0.0:
resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==}
engines: {node: '>=0.8'}
@@ -4859,6 +5096,14 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
+ eastasianwidth@0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+
+ editorconfig@1.0.4:
+ resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==}
+ engines: {node: '>=14'}
+ hasBin: true
+
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
@@ -4876,6 +5121,9 @@ packages:
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
emojis-list@3.0.0:
resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
engines: {node: '>= 4'}
@@ -5318,6 +5566,10 @@ packages:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
+ foreground-child@3.3.1:
+ resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
+ engines: {node: '>=14'}
+
form-data@4.0.5:
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'}
@@ -5414,6 +5666,11 @@ packages:
glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
+ glob@10.5.0:
+ resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+ hasBin: true
+
glob@13.0.6:
resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
engines: {node: 18 || 20 || >=22}
@@ -5633,6 +5890,9 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ ini@1.3.8:
+ resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+
ini@6.0.0:
resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -5875,6 +6135,9 @@ packages:
resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
engines: {node: '>=8'}
+ jackspeak@3.4.3:
+ resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
jackspeak@4.2.3:
resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==}
engines: {node: 20 || >=22}
@@ -5905,6 +6168,15 @@ packages:
jose@6.2.0:
resolution: {integrity: sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ==}
+ js-beautify@1.15.4:
+ resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ js-cookie@3.0.5:
+ resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
+ engines: {node: '>=14'}
+
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -6113,6 +6385,9 @@ packages:
resolution: {integrity: sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==}
engines: {node: '>=8.0'}
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
lru-cache@11.2.6:
resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==}
engines: {node: 20 || >=22}
@@ -6262,6 +6537,10 @@ packages:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
+ minimatch@9.0.1:
+ resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -6408,6 +6687,11 @@ packages:
node-releases@2.0.27:
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+ nopt@7.2.1:
+ resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ hasBin: true
+
nopt@9.0.0:
resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -6582,6 +6866,9 @@ packages:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
+ package-json-from-dist@1.0.1:
+ resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+
package-manager-detector@0.2.11:
resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==}
@@ -6642,6 +6929,10 @@ packages:
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+ path-scurry@1.11.1:
+ resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+ engines: {node: '>=16 || 14 >=14.18'}
+
path-scurry@2.0.2:
resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
engines: {node: 18 || 20 || >=22}
@@ -6798,6 +7089,9 @@ packages:
resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
engines: {node: '>=10'}
+ proto-list@1.2.4:
+ resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
+
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
@@ -7415,6 +7709,10 @@ packages:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
+ string-width@5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+
string-width@7.2.0:
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
engines: {node: '>=18'}
@@ -7864,12 +8162,34 @@ packages:
resolution: {integrity: sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==}
engines: {node: '>=0.10.0'}
+ vue-component-type-helpers@2.2.12:
+ resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==}
+
+ vue-demi@0.14.10:
+ resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
+ engines: {node: '>=12'}
+ hasBin: true
+ peerDependencies:
+ '@vue/composition-api': ^1.0.0-rc.1
+ vue: ^3.0.0-0 || ^2.6.0
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+
vue-eslint-parser@10.4.0:
resolution: {integrity: sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ vue@3.5.29:
+ resolution: {integrity: sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
walk-up-path@4.0.0:
resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==}
engines: {node: 20 || >=22}
@@ -8010,6 +8330,10 @@ packages:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
+ wrap-ansi@8.1.0:
+ resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+ engines: {node: '>=12'}
+
wrap-ansi@9.0.2:
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
engines: {node: '>=18'}
@@ -8233,7 +8557,7 @@ snapshots:
dependencies:
'@ampproject/remapping': 2.3.0
'@angular-devkit/architect': 0.2102.1(chokidar@5.0.0)
- '@angular-devkit/build-webpack': 0.2102.1(chokidar@5.0.0)(webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)))(webpack@5.105.2(esbuild@0.27.3))
+ '@angular-devkit/build-webpack': 0.2102.1(chokidar@5.0.0)(webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2))(webpack@5.105.2(esbuild@0.27.3))
'@angular-devkit/core': 21.2.1(chokidar@5.0.0)
'@angular/build': 21.2.1(@angular/compiler-cli@21.2.1(@angular/compiler@21.2.1)(typescript@5.9.3))(@angular/compiler@21.2.1)(@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)))(@types/node@25.3.5)(chokidar@5.0.0)(jiti@2.6.1)(karma@6.4.4)(less@4.4.2)(postcss@8.5.6)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.0.18(@types/node@25.3.5)(happy-dom@20.8.3)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2)
'@angular/compiler-cli': 21.2.1(@angular/compiler@21.2.1)(typescript@5.9.3)
@@ -8247,35 +8571,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
@@ -8283,10 +8607,10 @@ snapshots:
tslib: 2.8.1
typescript: 5.9.3
webpack: 5.105.2(esbuild@0.27.3)
- 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-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.2)
+ webpack-dev-server: 5.2.3(tslib@2.8.1)(webpack@5.105.2)
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))
@@ -8315,12 +8639,12 @@ snapshots:
- webpack-cli
- yaml
- '@angular-devkit/build-webpack@0.2102.1(chokidar@5.0.0)(webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)))(webpack@5.105.2(esbuild@0.27.3))':
+ '@angular-devkit/build-webpack@0.2102.1(chokidar@5.0.0)(webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2))(webpack@5.105.2(esbuild@0.27.3))':
dependencies:
'@angular-devkit/architect': 0.2102.1(chokidar@5.0.0)
rxjs: 7.8.2
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-dev-server: 5.2.3(tslib@2.8.1)(webpack@5.105.2)
transitivePeerDependencies:
- chokidar
@@ -9764,6 +10088,15 @@ snapshots:
optionalDependencies:
'@types/node': 25.3.5
+ '@isaacs/cliui@8.0.2':
+ dependencies:
+ string-width: 5.1.2
+ string-width-cjs: string-width@4.2.3
+ strip-ansi: 7.1.2
+ strip-ansi-cjs: strip-ansi@6.0.1
+ wrap-ansi: 8.1.0
+ wrap-ansi-cjs: wrap-ansi@7.0.0
+
'@isaacs/cliui@9.0.0': {}
'@isaacs/fs-minipass@4.0.1':
@@ -10110,7 +10443,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
@@ -10218,6 +10551,8 @@ snapshots:
'@nx/nx-win32-x64-msvc@22.5.4':
optional: true
+ '@one-ini/wasm@0.1.1': {}
+
'@oxc-project/types@0.113.0': {}
'@oxc-project/types@0.115.0': {}
@@ -10435,6 +10770,9 @@ snapshots:
tslib: 2.8.1
tsyringe: 4.10.0
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
'@preact/preset-vite@2.10.3(@babel/core@7.29.0)(preact@10.28.4)(rollup@4.57.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))':
dependencies:
'@babel/core': 7.29.0
@@ -10566,6 +10904,8 @@ snapshots:
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.7':
optional: true
+ '@rolldown/pluginutils@1.0.0-rc.2': {}
+
'@rolldown/pluginutils@1.0.0-rc.3': {}
'@rolldown/pluginutils@1.0.0-rc.4': {}
@@ -10831,7 +11171,7 @@ snapshots:
transitivePeerDependencies:
- csstype
- '@tanstack/devtools-utils@0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)':
+ '@tanstack/devtools-utils@0.3.2(@types/react@19.2.14)(csstype@3.2.3)(preact@10.28.4)(react@19.2.4)(solid-js@1.9.11)(vue@3.5.29(typescript@5.9.3))':
dependencies:
'@tanstack/devtools-ui': 0.5.0(csstype@3.2.3)(solid-js@1.9.11)
optionalDependencies:
@@ -10839,6 +11179,7 @@ snapshots:
preact: 10.28.4
react: 19.2.4
solid-js: 1.9.11
+ vue: 3.5.29(typescript@5.9.3)
transitivePeerDependencies:
- csstype
@@ -10933,6 +11274,21 @@ snapshots:
transitivePeerDependencies:
- typescript
+ '@tanstack/vue-devtools@0.2.10(csstype@3.2.3)(solid-js@1.9.11)':
+ dependencies:
+ '@tanstack/devtools': 0.10.11(csstype@3.2.3)(solid-js@1.9.11)
+ transitivePeerDependencies:
+ - bufferutil
+ - csstype
+ - solid-js
+ - utf-8-validate
+
+ '@tanstack/vue-store@0.9.2(vue@3.5.29(typescript@5.9.3))':
+ dependencies:
+ '@tanstack/store': 0.9.2
+ vue: 3.5.29(typescript@5.9.3)
+ vue-demi: 0.14.10(vue@3.5.29(typescript@5.9.3))
+
'@testing-library/dom@10.4.1':
dependencies:
'@babel/code-frame': 7.29.0
@@ -11308,6 +11664,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@vitejs/plugin-vue@6.0.4(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))(vue@3.5.29(typescript@5.9.3))':
+ dependencies:
+ '@rolldown/pluginutils': 1.0.0-rc.2
+ 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)
+ vue: 3.5.29(typescript@5.9.3)
+
'@vitest/expect@4.0.18':
dependencies:
'@standard-schema/spec': 1.1.0
@@ -11347,6 +11709,65 @@ snapshots:
'@vitest/pretty-format': 4.0.18
tinyrainbow: 3.0.3
+ '@vue/compiler-core@3.5.29':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@vue/shared': 3.5.29
+ entities: 7.0.1
+ estree-walker: 2.0.2
+ source-map-js: 1.2.1
+
+ '@vue/compiler-dom@3.5.29':
+ dependencies:
+ '@vue/compiler-core': 3.5.29
+ '@vue/shared': 3.5.29
+
+ '@vue/compiler-sfc@3.5.29':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@vue/compiler-core': 3.5.29
+ '@vue/compiler-dom': 3.5.29
+ '@vue/compiler-ssr': 3.5.29
+ '@vue/shared': 3.5.29
+ estree-walker: 2.0.2
+ magic-string: 0.30.21
+ postcss: 8.5.6
+ source-map-js: 1.2.1
+
+ '@vue/compiler-ssr@3.5.29':
+ dependencies:
+ '@vue/compiler-dom': 3.5.29
+ '@vue/shared': 3.5.29
+
+ '@vue/reactivity@3.5.29':
+ dependencies:
+ '@vue/shared': 3.5.29
+
+ '@vue/runtime-core@3.5.29':
+ dependencies:
+ '@vue/reactivity': 3.5.29
+ '@vue/shared': 3.5.29
+
+ '@vue/runtime-dom@3.5.29':
+ dependencies:
+ '@vue/reactivity': 3.5.29
+ '@vue/runtime-core': 3.5.29
+ '@vue/shared': 3.5.29
+ csstype: 3.2.3
+
+ '@vue/server-renderer@3.5.29(vue@3.5.29(typescript@5.9.3))':
+ dependencies:
+ '@vue/compiler-ssr': 3.5.29
+ '@vue/shared': 3.5.29
+ vue: 3.5.29(typescript@5.9.3)
+
+ '@vue/shared@3.5.29': {}
+
+ '@vue/test-utils@2.4.6':
+ dependencies:
+ js-beautify: 1.15.4
+ vue-component-type-helpers: 2.2.12
+
'@webassemblyjs/ast@1.14.1':
dependencies:
'@webassemblyjs/helper-numbers': 1.13.2
@@ -11438,6 +11859,8 @@ snapshots:
dependencies:
argparse: 2.0.1
+ abbrev@2.0.0: {}
+
abbrev@4.0.0: {}
accepts@1.3.8:
@@ -11604,7 +12027,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
@@ -11942,6 +12365,8 @@ snapshots:
dependencies:
delayed-stream: 1.0.0
+ commander@10.0.1: {}
+
commander@2.20.3: {}
comment-parser@1.4.5: {}
@@ -11966,6 +12391,11 @@ snapshots:
concat-map@0.0.1: {}
+ config-chain@1.1.13:
+ dependencies:
+ ini: 1.3.8
+ proto-list: 1.2.4
+
connect-history-api-fallback@2.0.0: {}
connect@3.7.0:
@@ -11999,7 +12429,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
@@ -12034,7 +12464,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
@@ -12217,6 +12647,15 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
+ eastasianwidth@0.2.0: {}
+
+ editorconfig@1.0.4:
+ dependencies:
+ '@one-ini/wasm': 0.1.1
+ commander: 10.0.1
+ minimatch: 9.0.1
+ semver: 7.7.4
+
ee-first@1.1.1: {}
ejs@3.1.10:
@@ -12229,6 +12668,8 @@ snapshots:
emoji-regex@8.0.0: {}
+ emoji-regex@9.2.2: {}
+
emojis-list@3.0.0: {}
empathic@2.0.0: {}
@@ -12859,6 +13300,11 @@ snapshots:
dependencies:
is-callable: 1.2.7
+ foreground-child@3.3.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ signal-exit: 4.1.0
+
form-data@4.0.5:
dependencies:
asynckit: 0.4.0
@@ -12952,6 +13398,15 @@ snapshots:
glob-to-regexp@0.4.1: {}
+ glob@10.5.0:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 3.4.3
+ minimatch: 9.0.5
+ minipass: 7.1.3
+ package-json-from-dist: 1.0.1
+ path-scurry: 1.11.1
+
glob@13.0.6:
dependencies:
minimatch: 10.2.4
@@ -13182,6 +13637,8 @@ snapshots:
inherits@2.0.4: {}
+ ini@1.3.8: {}
+
ini@6.0.0: {}
internal-slot@1.1.0:
@@ -13400,6 +13857,12 @@ snapshots:
html-escaper: 2.0.2
istanbul-lib-report: 3.0.1
+ jackspeak@3.4.3:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+
jackspeak@4.2.3:
dependencies:
'@isaacs/cliui': 9.0.0
@@ -13431,6 +13894,16 @@ snapshots:
jose@6.2.0: {}
+ js-beautify@1.15.4:
+ dependencies:
+ config-chain: 1.1.13
+ editorconfig: 1.0.4
+ glob: 10.5.0
+ js-cookie: 3.0.5
+ nopt: 7.2.1
+
+ js-cookie@3.0.5: {}
+
js-tokens@4.0.0: {}
js-yaml@3.14.2:
@@ -13563,7 +14036,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:
@@ -13588,7 +14061,7 @@ 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:
@@ -13687,6 +14160,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ lru-cache@10.4.3: {}
+
lru-cache@11.2.6: {}
lru-cache@5.1.1:
@@ -13809,7 +14284,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
@@ -13833,6 +14308,10 @@ snapshots:
dependencies:
brace-expansion: 2.0.2
+ minimatch@9.0.1:
+ dependencies:
+ brace-expansion: 2.0.2
+
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.2
@@ -13976,6 +14455,10 @@ snapshots:
node-releases@2.0.27: {}
+ nopt@7.2.1:
+ dependencies:
+ abbrev: 2.0.0
+
nopt@9.0.0:
dependencies:
abbrev: 4.0.0
@@ -14243,6 +14726,8 @@ snapshots:
p-try@2.2.0: {}
+ package-json-from-dist@1.0.1: {}
+
package-manager-detector@0.2.11:
dependencies:
quansync: 0.2.11
@@ -14321,6 +14806,11 @@ snapshots:
path-parse@1.0.7: {}
+ path-scurry@1.11.1:
+ dependencies:
+ lru-cache: 10.4.3
+ minipass: 7.1.3
+
path-scurry@2.0.2:
dependencies:
lru-cache: 11.2.6
@@ -14359,7 +14849,7 @@ 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-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
@@ -14448,6 +14938,8 @@ snapshots:
err-code: 2.0.3
retry: 0.12.0
+ proto-list@1.2.4: {}
+
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
@@ -14766,7 +15258,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:
@@ -15089,7 +15581,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
@@ -15182,6 +15674,12 @@ snapshots:
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
+ string-width@5.1.2:
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.1.2
+
string-width@7.2.0:
dependencies:
emoji-regex: 10.6.0
@@ -15605,6 +16103,12 @@ snapshots:
void-elements@2.0.1: {}
+ vue-component-type-helpers@2.2.12: {}
+
+ vue-demi@0.14.10(vue@3.5.29(typescript@5.9.3)):
+ dependencies:
+ vue: 3.5.29(typescript@5.9.3)
+
vue-eslint-parser@10.4.0(eslint@9.39.2(jiti@2.6.1)):
dependencies:
debug: 4.4.3
@@ -15617,6 +16121,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ vue@3.5.29(typescript@5.9.3):
+ dependencies:
+ '@vue/compiler-dom': 3.5.29
+ '@vue/compiler-sfc': 3.5.29
+ '@vue/runtime-dom': 3.5.29
+ '@vue/server-renderer': 3.5.29(vue@3.5.29(typescript@5.9.3))
+ '@vue/shared': 3.5.29
+ optionalDependencies:
+ typescript: 5.9.3
+
walk-up-path@4.0.0: {}
watchpack@2.5.1:
@@ -15637,7 +16151,7 @@ snapshots:
webidl-conversions@3.0.1: {}
- webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)):
+ webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.105.2):
dependencies:
colorette: 2.0.20
memfs: 4.56.10(tslib@2.8.1)
@@ -15650,7 +16164,7 @@ snapshots:
transitivePeerDependencies:
- tslib
- webpack-dev-server@5.2.3(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):
dependencies:
'@types/bonjour': 3.5.13
'@types/connect-history-api-fallback': 1.5.4
@@ -15678,7 +16192,7 @@ snapshots:
serve-index: 1.9.2
sockjs: 0.3.24
spdy: 4.0.2
- webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3))
+ webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.2)
ws: 8.19.0
optionalDependencies:
webpack: 5.105.2(esbuild@0.27.3)
@@ -15697,7 +16211,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)
@@ -15813,6 +16327,12 @@ snapshots:
string-width: 4.2.3
strip-ansi: 6.0.1
+ wrap-ansi@8.1.0:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 5.1.2
+ strip-ansi: 7.1.2
+
wrap-ansi@9.0.2:
dependencies:
ansi-styles: 6.2.3
diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts
index 20a1a28..0c9ebdf 100644
--- a/scripts/generate-docs.ts
+++ b/scripts/generate-docs.ts
@@ -6,6 +6,18 @@ const __dirname = fileURLToPath(new URL('.', import.meta.url))
await generateReferenceDocs({
packages: [
+ {
+ name: 'angular-hotkeys',
+ entryPoints: [
+ resolve(__dirname, '../packages/angular-hotkeys/src/index.ts'),
+ ],
+ tsconfig: resolve(
+ __dirname,
+ '../packages/angular-hotkeys/tsconfig.docs.json',
+ ),
+ outputDir: resolve(__dirname, '../docs/framework/angular/reference'),
+ exclude: ['packages/hotkeys/**/*'],
+ },
{
name: 'hotkeys',
entryPoints: [resolve(__dirname, '../packages/hotkeys/src/index.ts')],
@@ -49,15 +61,13 @@ await generateReferenceDocs({
exclude: ['packages/hotkeys/**/*'],
},
{
- name: 'angular-hotkeys',
- entryPoints: [
- resolve(__dirname, '../packages/angular-hotkeys/src/index.ts'),
- ],
+ name: 'vue-hotkeys',
+ entryPoints: [resolve(__dirname, '../packages/vue-hotkeys/src/index.ts')],
tsconfig: resolve(
__dirname,
- '../packages/angular-hotkeys/tsconfig.docs.json',
+ '../packages/vue-hotkeys/tsconfig.docs.json',
),
- outputDir: resolve(__dirname, '../docs/framework/angular/reference'),
+ outputDir: resolve(__dirname, '../docs/framework/vue/reference'),
exclude: ['packages/hotkeys/**/*'],
},
],