diff --git a/.changeset/all-poets-write.md b/.changeset/all-poets-write.md
new file mode 100644
index 0000000..1a7d713
--- /dev/null
+++ b/.changeset/all-poets-write.md
@@ -0,0 +1,6 @@
+---
+'@tanstack/vue-hotkeys': minor
+'@tanstack/vue-hotkeys-devtools': minor
+---
+
+- Initial Vue adapter release
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..25ae292
--- /dev/null
+++ b/examples/vue/useHeldKeys/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@tanstack/hotkeys-example-vue-use-held-keys",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --port=3076",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@tanstack/vue-hotkeys": "^0.3.0",
+ "vue": "^3.5.14"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.2.3",
+ "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..7632285
--- /dev/null
+++ b/examples/vue/useHeldKeys/src/App.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
useHeldKeys
+
Returns an array of all currently pressed keys.
+
+
+
+
+ {{ formatKeyForDebuggingDisplay(key) }}
+
+ {{ formatKeyForDebuggingDisplay(heldCodes[key], { source: 'code' }) }}
+
+
+ +
+
+ Press any keys...
+
+
+
Keys held: {{ heldKeys.length }}
+
+
Usage
+
import { useHeldKeys } from '@tanstack/vue-hotkeys'
+
+const heldKeys = useHeldKeys()
+
+// heldKeys is a reactive ref containing array of key names
+
+
diff --git a/examples/vue/useHeldKeys/src/index.ts b/examples/vue/useHeldKeys/src/index.ts
new file mode 100644
index 0000000..01433bc
--- /dev/null
+++ b/examples/vue/useHeldKeys/src/index.ts
@@ -0,0 +1,4 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+
+createApp(App).mount('#app')
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..58d1f27
--- /dev/null
+++ b/examples/vue/useHotkey/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@tanstack/hotkeys-example-vue-use-hotkey",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --port=3075",
+ "build": "vite build",
+ "preview": "vite preview",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "test:types": "tsc"
+ },
+ "dependencies": {
+ "@tanstack/vue-hotkeys": "^0.3.0",
+ "vue": "^3.5.14"
+ },
+ "devDependencies": {
+ "@tanstack/vue-hotkeys-devtools": "^0.3.0",
+ "@vitejs/plugin-vue": "^5.2.3",
+ "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..07d15ca
--- /dev/null
+++ b/examples/vue/useHotkey/src/App.vue
@@ -0,0 +1,212 @@
+
+
+
+
+
+ useHotkey
+
+ Register keyboard shortcuts with the useHotkey composable. Supports scoped
+ shortcuts, conditional enabling, and cross-platform Mod key.
+
+
+
+
+
+ Last Hotkey Pressed
+
+ {{ lastHotkey ? formatForDisplay(lastHotkey) : 'None yet' }}
+
+
+
+
+ Basic Hotkeys
+
+
+ {{ formatForDisplay('Mod+S') }}
+ Save (triggered {{ saveCount }} times)
+
+
+ {{ formatForDisplay('Mod+K') }}
+ Increment (triggered {{ incrementCount }} times)
+
+
+ {{ formatForDisplay('Mod+E') }}
+ Alert ({{ enabled ? 'enabled' : 'disabled' }})
+
+ {{ enabled ? 'Disable' : 'Enable' }}
+
+
+
+
+
+
+ Tab Switching
+
+
+ Tab {{ tab }} {{ formatForDisplay(`Mod+${tab}`) }}
+
+
+
+
Content for Tab 1
+
Content for Tab 2
+
Content for Tab 3
+
+
+
+
+ Scoped Hotkeys
+
+
+
+
+
+
Modal
+
Open Modal
+
+
+
Press Escape to close
+
Triggered {{ modalShortcutCount }} times
+
Close
+
+
+
+
+
+
+
Code Editor
+
Focus and press {{ formatForDisplay('Mod+/') }} to comment
+
+
Comment shortcut used {{ editorShortcutCount }} times
+
+
+
+
+
diff --git a/examples/vue/useHotkey/src/index.css b/examples/vue/useHotkey/src/index.css
new file mode 100644
index 0000000..bd5b809
--- /dev/null
+++ b/examples/vue/useHotkey/src/index.css
@@ -0,0 +1,203 @@
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ background: #0f1419;
+ color: #e0e0e0;
+}
+
+.app {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem;
+}
+
+header {
+ text-align: center;
+ margin-bottom: 3rem;
+}
+
+h1 {
+ font-size: 3rem;
+ margin: 0 0 1rem;
+ background: linear-gradient(135deg, #00d4aa 0%, #00a3ff 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+header p {
+ font-size: 1.2rem;
+ opacity: 0.7;
+}
+
+.demo-section {
+ background: #1a1f2e;
+ border-radius: 8px;
+ padding: 2rem;
+ margin-bottom: 2rem;
+}
+
+.demo-section h2 {
+ margin-top: 0;
+ color: #00d4aa;
+}
+
+.hotkey-display {
+ font-size: 2rem;
+ text-align: center;
+ padding: 2rem;
+ background: #0f1419;
+ border-radius: 8px;
+ font-family: monospace;
+}
+
+.hotkey-list {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.hotkey-item {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 1rem;
+ background: #0f1419;
+ border-radius: 8px;
+}
+
+.hotkey-item kbd {
+ min-width: 100px;
+}
+
+.hotkey-item button {
+ margin-left: auto;
+}
+
+kbd {
+ background: #2a3f5f;
+ border: 1px solid #3a5f8f;
+ border-radius: 4px;
+ padding: 0.25rem 0.5rem;
+ font-family: monospace;
+ font-size: 0.9em;
+}
+
+button {
+ background: #00a3ff;
+ color: white;
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 1rem;
+}
+
+button:hover {
+ background: #0088dd;
+}
+
+.tabs {
+ display: flex;
+ gap: 0.5rem;
+ margin-bottom: 1rem;
+}
+
+.tabs button {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+}
+
+.tabs button.active {
+ background: #00d4aa;
+}
+
+.tab-content {
+ padding: 2rem;
+ background: #0f1419;
+ border-radius: 8px;
+ min-height: 100px;
+}
+
+.scoped-demo {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+ margin-bottom: 2rem;
+}
+
+.scoped-box {
+ padding: 1.5rem;
+ background: #0f1419;
+ border-radius: 8px;
+ border: 2px solid transparent;
+}
+
+.scoped-box:focus {
+ outline: none;
+ border-color: #00d4aa;
+}
+
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.8);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.modal {
+ background: #1a1f2e;
+ padding: 2rem;
+ border-radius: 8px;
+ border: 2px solid #00d4aa;
+ min-width: 300px;
+}
+
+.modal:focus {
+ outline: none;
+}
+
+.editor-demo {
+ margin-top: 2rem;
+}
+
+.code-editor {
+ width: 100%;
+ min-height: 200px;
+ background: #0f1419;
+ color: #e0e0e0;
+ border: 2px solid transparent;
+ border-radius: 8px;
+ padding: 1rem;
+ font-family: monospace;
+ font-size: 1rem;
+ resize: vertical;
+}
+
+.code-editor:focus {
+ outline: none;
+ border-color: #00d4aa;
+}
+
+.editor-stats {
+ margin-top: 0.5rem;
+ opacity: 0.7;
+ font-size: 0.9rem;
+}
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/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..459f07b
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@tanstack/hotkeys-example-vue-use-hotkey-recorder",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --port=3079",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@tanstack/vue-hotkeys": "^0.3.0",
+ "vue": "^3.5.14"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.2.3",
+ "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..36ef9c0
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/src/App.vue
@@ -0,0 +1,109 @@
+
+
+
+
+
useHotkeyRecorder
+
Record keyboard shortcuts from user input.
+
+
+
+ Recording... Press keys now
+
+
+ {{ formatForDisplay(recorder.recordedHotkey) }}
+
+
+ Press Escape to cancel or
+ Backspace to clear
+
+
+
+
Custom Shortcuts
+
+
+
+
{{ actionId }}
+
+ Triggered: {{ key === 'save' ? saveCount : key === 'open' ? openCount : newCount }} times
+
+
+
+ {{ formatForDisplay(shortcuts[key] as Hotkey) }}
+
+
Not set
+
+ Record
+
+
+
+
+
Usage
+
import { useHotkeyRecorder } from '@tanstack/vue-hotkeys'
+
+const recorder = useHotkeyRecorder({
+ onRecord: (hotkey) => {
+ console.log('Recorded:', hotkey)
+ }
+})
+
+// recorder.isRecording - reactive boolean
+// recorder.recordedHotkey - reactive Hotkey | null
+// recorder.startRecording() - start recording
+// recorder.stopRecording() - stop recording
+// recorder.cancelRecording() - cancel recording
+
+
diff --git a/examples/vue/useHotkeyRecorder/src/index.ts b/examples/vue/useHotkeyRecorder/src/index.ts
new file mode 100644
index 0000000..01433bc
--- /dev/null
+++ b/examples/vue/useHotkeyRecorder/src/index.ts
@@ -0,0 +1,4 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+
+createApp(App).mount('#app')
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..73f273f
--- /dev/null
+++ b/examples/vue/useHotkeySequence/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@tanstack/hotkeys-example-vue-use-hotkey-sequence",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --port=3078",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@tanstack/vue-hotkeys": "^0.3.0",
+ "vue": "^3.5.14"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.2.3",
+ "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..0734a14
--- /dev/null
+++ b/examples/vue/useHotkeySequence/src/App.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
useHotkeySequence
+
Register multi-key sequences (like Vim commands).
+
+
+ {{ lastSequence }}
+
+
+
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
+
+
+
+
+
History
+
+
Try typing a sequence...
+
+
+
+
Press Escape to clear history
+
+
Usage
+
import { useHotkeySequence } from '@tanstack/vue-hotkeys'
+
+useHotkeySequence(['G', 'G'], () => {
+ console.log('Go to top!')
+})
+
+
+
+
diff --git a/examples/vue/useHotkeySequence/src/index.ts b/examples/vue/useHotkeySequence/src/index.ts
new file mode 100644
index 0000000..01433bc
--- /dev/null
+++ b/examples/vue/useHotkeySequence/src/index.ts
@@ -0,0 +1,4 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+
+createApp(App).mount('#app')
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..12aae13
--- /dev/null
+++ b/examples/vue/useKeyhold/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@tanstack/hotkeys-example-vue-use-keyhold",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --port=3077",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@tanstack/vue-hotkeys": "^0.3.0",
+ "vue": "^3.5.14"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.2.3",
+ "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..8fba920
--- /dev/null
+++ b/examples/vue/useKeyhold/src/App.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
useKeyHold
+
Returns a boolean indicating if a specific key is currently held.
+
+
Modifier Key States
+
+
+
Shift
+
{{ isShiftHeld ? 'HELD' : 'Released' }}
+
+
+
+
Control
+
{{ isControlHeld ? 'HELD' : 'Released' }}
+
+
+
+
Alt
+
{{ isAltHeld ? 'HELD' : 'Released' }}
+
+
+
+
Meta (⌘/⊞)
+
{{ isMetaHeld ? 'HELD' : 'Released' }}
+
+
+
+
Space Bar Demo
+
+ {{ isSpaceHeld ? '🚀 SPACE HELD!' : 'Hold Space Bar' }}
+
+
+
Usage
+
import { useKeyHold } from '@tanstack/vue-hotkeys'
+
+const isShiftHeld = useKeyHold('Shift')
+
+// isShiftHeld is a reactive ref (boolean)
+
+
diff --git a/examples/vue/useKeyhold/src/index.ts b/examples/vue/useKeyhold/src/index.ts
new file mode 100644
index 0000000..01433bc
--- /dev/null
+++ b/examples/vue/useKeyhold/src/index.ts
@@ -0,0 +1,4 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+
+createApp(App).mount('#app')
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/packages/react-hotkeys/src/HotkeysProvider.tsx b/packages/react-hotkeys/src/HotkeysProvider.tsx
index ee1bcb1..8e1a493 100644
--- a/packages/react-hotkeys/src/HotkeysProvider.tsx
+++ b/packages/react-hotkeys/src/HotkeysProvider.tsx
@@ -1,4 +1,4 @@
-import React, { createContext, useContext, useMemo } from 'react'
+import React, { createContext, use, useMemo } from 'react'
import type { ReactNode } from 'react'
import type { HotkeyRecorderOptions } from '@tanstack/hotkeys'
import type { UseHotkeyOptions } from './useHotkey'
@@ -35,17 +35,17 @@ export function HotkeysProvider({
)
return (
-
+
{children}
-
+
)
}
export function useHotkeysContext() {
- return useContext(HotkeysContext)
+ return use(HotkeysContext)
}
export function useDefaultHotkeysOptions() {
- const context = useContext(HotkeysContext)
+ const context = use(HotkeysContext)
return context?.defaultOptions ?? {}
}
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..adfb0cd
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/README.md
@@ -0,0 +1,32 @@
+# TanStack Hotkeys Devtools for Vue
+
+Developer tools for TanStack Hotkeys Vue adapter.
+
+## Installation
+
+```bash
+pnpm add @tanstack/vue-hotkeys-devtools
+# or
+npm install @tanstack/vue-hotkeys-devtools
+# or
+yarn add @tanstack/vue-hotkeys-devtools
+```
+
+## Usage
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+## License
+
+MIT © [Tanner Linsley](https://github.com/tannerlinsley)
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..9149efe
--- /dev/null
+++ b/packages/vue-hotkeys-devtools/package.json
@@ -0,0 +1,66 @@
+{
+ "name": "@tanstack/vue-hotkeys-devtools",
+ "version": "0.3.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"
+ },
+ "./package.json": "./package.json"
+ },
+ "sideEffects": false,
+ "engines": {
+ "node": ">=18"
+ },
+ "files": [
+ "dist/",
+ "src"
+ ],
+ "peerDependencies": {
+ "vue": ">=3.0.0"
+ },
+ "dependencies": {
+ "@tanstack/devtools-utils": "^0.3.0",
+ "@tanstack/hotkeys-devtools": "workspace:*"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.2.3",
+ "vue": "^3.5.14"
+ },
+ "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..e868f75
--- /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({
+ name: 'TanStack Hotkeys',
+ Component: 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..71071cb
--- /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'],
+ 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..cc39b3c
--- /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: 'jsdom',
+ globals: true,
+ },
+})
diff --git a/packages/vue-hotkeys/README.md b/packages/vue-hotkeys/README.md
new file mode 100644
index 0000000..c7d8e70
--- /dev/null
+++ b/packages/vue-hotkeys/README.md
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
+
+
+
+# TanStack Hotkeys for Vue
+
+> [!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 Vue. 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, Vue composables, and devtools (in progress)
+
+### Read the docs →
+
+## Installation
+
+```bash
+pnpm add @tanstack/vue-hotkeys
+# or
+npm install @tanstack/vue-hotkeys
+# or
+yarn add @tanstack/vue-hotkeys
+```
+
+## Quick Start
+
+```vue
+
+
+
+
+
Count: {{ count }}
+
+ Focus me and press Mod+K
+
+
+
+```
+
+## Features
+
+### Composables
+
+- `useHotkey` — Register a keyboard shortcut
+- `useHotkeySequence` — Register multi-key sequences (Vim-style like 'g g')
+- `useHeldKeys` — Track currently pressed keys
+- `useKeyHold` — Check if a specific key is held
+- `useHotkeyRecorder` — Record hotkeys from user input
+- `HotkeysProvider` — Provide default options to all hotkeys
+
+### Advanced Usage
+
+```vue
+
+
+
+
+
Pressed keys: {{ heldKeys.join(' + ') }}
+
+ {{ recorder.isRecording ? 'Recording...' : 'Record Shortcut' }}
+
+
+
+```
+
+## License
+
+MIT © [Tanner Linsley](https://github.com/tannerlinsley)
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..a8d66d7
--- /dev/null
+++ b/packages/vue-hotkeys/package.json
@@ -0,0 +1,66 @@
+{
+ "name": "@tanstack/vue-hotkeys",
+ "version": "0.3.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.1"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.2.3",
+ "@vue/test-utils": "^2.4.6",
+ "vue": "^3.5.14"
+ },
+ "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..a34db47
--- /dev/null
+++ b/packages/vue-hotkeys/src/useHotkey.ts
@@ -0,0 +1,173 @@
+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 {
+ /**
+ * 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
+ | HTMLElement
+ | Document
+ | Window
+ | null
+}
+
+/**
+ * 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 = unref(hotkey)
+ const resolvedOptions = unref(options)
+ const mergedOptions = {
+ ...defaultOptions.hotkey,
+ ...resolvedOptions,
+ } as UseHotkeyOptions
+
+ return { resolvedHotkey, mergedOptions }
+ },
+ ({ resolvedHotkey, mergedOptions }) => {
+ // 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
+ let targetValue = 'target' in mergedOptions ? mergedOptions.target : null
+ if (typeof targetValue === 'function') {
+ targetValue = targetValue()
+ } else {
+ targetValue = unref(targetValue)
+ }
+ const resolvedTarget = targetValue ?? (typeof document !== 'undefined' ? document : null)
+
+ if (!resolvedTarget) {
+ 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, ...optionsWithoutTarget } = mergedOptions
+
+ // Register the hotkey
+ registration = manager.register(hotkeyString, callback, {
+ ...optionsWithoutTarget,
+ target: resolvedTarget,
+ })
+
+ // 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
+ }
+ })
+}
diff --git a/packages/vue-hotkeys/src/useHotkeyRecorder.ts b/packages/vue-hotkeys/src/useHotkeyRecorder.ts
new file mode 100644
index 0000000..b23f49d
--- /dev/null
+++ b/packages/vue-hotkeys/src/useHotkeyRecorder.ts
@@ -0,0 +1,96 @@
+import { onUnmounted, unref } 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()
+
+ // Merge default options with provided options
+ const resolvedOptions = unref(options)
+ const mergedOptions = {
+ ...defaultOptions.hotkeyRecorder,
+ ...resolvedOptions,
+ } as HotkeyRecorderOptions
+
+ // Create recorder instance
+ const recorder = new HotkeyRecorder(mergedOptions)
+
+ // Subscribe to recorder state using useStore
+ const isRecording = useStore(recorder.store, (state) => state.isRecording)
+ const recordedHotkey = useStore(
+ recorder.store,
+ (state) => state.recordedHotkey,
+ )
+
+ // Cleanup on unmount
+ onUnmounted(() => {
+ recorder.destroy()
+ })
+
+ return {
+ isRecording: isRecording as Ref,
+ recordedHotkey: recordedHotkey as Ref,
+ startRecording: () => recorder.start(),
+ stopRecording: () => recorder.stop(),
+ cancelRecording: () => recorder.cancel(),
+ }
+}
diff --git a/packages/vue-hotkeys/src/useHotkeySequence.ts b/packages/vue-hotkeys/src/useHotkeySequence.ts
new file mode 100644
index 0000000..8ec68aa
--- /dev/null
+++ b/packages/vue-hotkeys/src/useHotkeySequence.ts
@@ -0,0 +1,136 @@
+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,
+} from '@tanstack/hotkeys'
+
+export interface UseHotkeySequenceOptions extends Omit<
+ SequenceOptions,
+ 'target'
+> {
+ /**
+ * 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
+ | HTMLElement
+ | Document
+ | Window
+ | null
+}
+
+/**
+ * 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: any = null
+
+ // Watch for changes to reactive dependencies
+ const stopWatcher = watch(
+ () => {
+ const resolvedSequence = unref(sequence)
+ const resolvedOptions = unref(options)
+ const mergedOptions = {
+ ...defaultOptions.hotkeySequence,
+ ...resolvedOptions,
+ } as UseHotkeySequenceOptions
+
+ return { resolvedSequence, mergedOptions }
+ },
+ ({ resolvedSequence, mergedOptions }) => {
+ if (resolvedSequence.length === 0) {
+ return
+ }
+
+ // Resolve target
+ let targetValue = 'target' in mergedOptions ? mergedOptions.target : null
+ if (typeof targetValue === 'function') {
+ targetValue = targetValue()
+ } else {
+ targetValue = unref(targetValue)
+ }
+ const resolvedTarget = targetValue ?? (typeof document !== 'undefined' ? document : null)
+
+ if (!resolvedTarget) {
+ 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, ...optionsWithoutTarget } = mergedOptions
+
+ // Register the sequence
+ registration = manager.register(resolvedSequence as any, callback, {
+ ...optionsWithoutTarget,
+ target: resolvedTarget,
+ })
+
+ // 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
+ }
+ })
+}
diff --git a/packages/vue-hotkeys/src/useKeyHold.ts b/packages/vue-hotkeys/src/useKeyHold.ts
new file mode 100644
index 0000000..46897db
--- /dev/null
+++ b/packages/vue-hotkeys/src/useKeyHold.ts
@@ -0,0 +1,61 @@
+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/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..cc39b3c
--- /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: 'jsdom',
+ globals: true,
+ },
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a663c03..0cbc556 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -380,7 +380,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.0
- version: 0.3.0(@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.0(@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.28(typescript@5.9.3))
'@tanstack/solid-devtools':
specifier: 0.7.26
version: 0.7.26(csstype@3.2.3)(solid-js@1.9.11)
@@ -405,7 +405,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.0
- version: 0.3.0(@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.0(@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.28(typescript@5.9.3))
'@tanstack/solid-devtools':
specifier: 0.7.26
version: 0.7.26(csstype@3.2.3)(solid-js@1.9.11)
@@ -436,7 +436,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.0
- version: 0.3.0(@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.0(@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.28(typescript@5.9.3))
'@tanstack/solid-devtools':
specifier: 0.7.26
version: 0.7.26(csstype@3.2.3)(solid-js@1.9.11)
@@ -461,7 +461,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.0
- version: 0.3.0(@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.0(@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.28(typescript@5.9.3))
'@tanstack/solid-devtools':
specifier: 0.7.26
version: 0.7.26(csstype@3.2.3)(solid-js@1.9.11)
@@ -486,7 +486,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.0
- version: 0.3.0(@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.0(@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.28(typescript@5.9.3))
'@tanstack/solid-devtools':
specifier: 0.7.26
version: 0.7.26(csstype@3.2.3)(solid-js@1.9.11)
@@ -507,6 +507,104 @@ 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.0)(jiti@2.6.1)(yaml@2.8.2))
+ examples/vue/useHeldKeys:
+ dependencies:
+ '@tanstack/vue-hotkeys':
+ specifier: ^0.3.0
+ version: link:../../../packages/vue-hotkeys
+ vue:
+ specifier: ^3.5.14
+ version: 3.5.28(typescript@5.9.3)
+ devDependencies:
+ '@vitejs/plugin-vue':
+ specifier: ^5.2.3
+ version: 5.2.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))(vue@3.5.28(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.0)(jiti@2.6.1)(yaml@2.8.2)
+
+ examples/vue/useHotkey:
+ dependencies:
+ '@tanstack/vue-hotkeys':
+ specifier: ^0.3.0
+ version: link:../../../packages/vue-hotkeys
+ vue:
+ specifier: ^3.5.14
+ version: 3.5.28(typescript@5.9.3)
+ devDependencies:
+ '@tanstack/vue-hotkeys-devtools':
+ specifier: ^0.3.0
+ version: link:../../../packages/vue-hotkeys-devtools
+ '@vitejs/plugin-vue':
+ specifier: ^5.2.3
+ version: 5.2.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))(vue@3.5.28(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.0)(jiti@2.6.1)(yaml@2.8.2)
+
+ examples/vue/useHotkeyRecorder:
+ dependencies:
+ '@tanstack/vue-hotkeys':
+ specifier: ^0.3.0
+ version: link:../../../packages/vue-hotkeys
+ vue:
+ specifier: ^3.5.14
+ version: 3.5.28(typescript@5.9.3)
+ devDependencies:
+ '@vitejs/plugin-vue':
+ specifier: ^5.2.3
+ version: 5.2.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))(vue@3.5.28(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.0)(jiti@2.6.1)(yaml@2.8.2)
+
+ examples/vue/useHotkeySequence:
+ dependencies:
+ '@tanstack/vue-hotkeys':
+ specifier: ^0.3.0
+ version: link:../../../packages/vue-hotkeys
+ vue:
+ specifier: ^3.5.14
+ version: 3.5.28(typescript@5.9.3)
+ devDependencies:
+ '@vitejs/plugin-vue':
+ specifier: ^5.2.3
+ version: 5.2.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))(vue@3.5.28(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.0)(jiti@2.6.1)(yaml@2.8.2)
+
+ examples/vue/useKeyhold:
+ dependencies:
+ '@tanstack/vue-hotkeys':
+ specifier: ^0.3.0
+ version: link:../../../packages/vue-hotkeys
+ vue:
+ specifier: ^3.5.14
+ version: 3.5.28(typescript@5.9.3)
+ devDependencies:
+ '@vitejs/plugin-vue':
+ specifier: ^5.2.3
+ version: 5.2.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))(vue@3.5.28(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.0)(jiti@2.6.1)(yaml@2.8.2)
+
packages/hotkeys:
dependencies:
'@tanstack/store':
@@ -520,7 +618,7 @@ importers:
version: 0.4.4(csstype@3.2.3)(solid-js@1.9.11)
'@tanstack/devtools-utils':
specifier: ^0.3.0
- version: 0.3.0(@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.0(@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.28(typescript@5.9.3))
'@tanstack/hotkeys':
specifier: workspace:*
version: link:../hotkeys
@@ -561,7 +659,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.0
- version: 0.3.0(@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.0(@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.28(typescript@5.9.3))
'@tanstack/hotkeys-devtools':
specifier: workspace:*
version: link:../hotkeys-devtools
@@ -611,7 +709,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.0
- version: 0.3.0(@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.0(@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.28(typescript@5.9.3))
'@tanstack/hotkeys-devtools':
specifier: workspace:*
version: link:../hotkeys-devtools
@@ -667,7 +765,7 @@ importers:
dependencies:
'@tanstack/devtools-utils':
specifier: ^0.3.0
- version: 0.3.0(@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.0(@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.28(typescript@5.9.3))
'@tanstack/hotkeys-devtools':
specifier: workspace:*
version: link:../hotkeys-devtools
@@ -679,6 +777,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.0)(jiti@2.6.1)(yaml@2.8.2))
+ packages/vue-hotkeys:
+ dependencies:
+ '@tanstack/hotkeys':
+ specifier: workspace:*
+ version: link:../hotkeys
+ '@tanstack/vue-store':
+ specifier: ^0.9.1
+ version: 0.9.1(vue@3.5.28(typescript@5.9.3))
+ devDependencies:
+ '@vitejs/plugin-vue':
+ specifier: ^5.2.3
+ version: 5.2.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))
+ '@vue/test-utils':
+ specifier: ^2.4.6
+ version: 2.4.6
+ vue:
+ specifier: ^3.5.14
+ version: 3.5.28(typescript@5.9.3)
+
+ packages/vue-hotkeys-devtools:
+ dependencies:
+ '@tanstack/devtools-utils':
+ specifier: ^0.3.0
+ version: 0.3.0(@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.28(typescript@5.9.3))
+ '@tanstack/hotkeys-devtools':
+ specifier: workspace:*
+ version: link:../hotkeys-devtools
+ devDependencies:
+ '@vitejs/plugin-vue':
+ specifier: ^5.2.3
+ version: 5.2.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))
+ vue:
+ specifier: ^3.5.14
+ version: 3.5.28(typescript@5.9.3)
+
packages:
'@adobe/css-tools@4.4.4':
@@ -1198,6 +1331,10 @@ packages:
resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==}
engines: {node: 20 || >=22}
+ '@isaacs/cliui@8.0.2':
+ resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+ engines: {node: '>=12'}
+
'@jest/diff-sequences@30.0.1':
resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
@@ -1307,6 +1444,9 @@ packages:
cpu: [x64]
os: [win32]
+ '@one-ini/wasm@0.1.1':
+ resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
+
'@oxc-project/types@0.112.0':
resolution: {integrity: sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ==}
@@ -1418,6 +1558,10 @@ packages:
cpu: [x64]
os: [win32]
+ '@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:
@@ -1881,6 +2025,15 @@ packages:
resolution: {integrity: sha512-wVT2YfKDSpd+4f7fk6UaPIP3a2J7LSovlyVuFF1PH2yQb7gjqehod5zdFiwFyEXgvI9XGuFvvs1OehkKNYcr6A==}
engines: {node: '>=18'}
+ '@tanstack/vue-store@0.9.1':
+ resolution: {integrity: sha512-mXXZzPWom656MExX2gG1fqopJhToDbqGEl98WtJ5/hyouQHtQXiAgtsPNLzUcVcwU9okM/OCWv7QAgXf6C5ziQ==}
+ 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'}
@@ -2150,6 +2303,13 @@ packages:
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+ '@vitejs/plugin-vue@5.2.4':
+ resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ peerDependencies:
+ vite: ^5.0.0 || ^6.0.0
+ vue: ^3.2.25
+
'@vitest/expect@4.0.18':
resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==}
@@ -2179,6 +2339,38 @@ packages:
'@vitest/utils@4.0.18':
resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
+ '@vue/compiler-core@3.5.28':
+ resolution: {integrity: sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==}
+
+ '@vue/compiler-dom@3.5.28':
+ resolution: {integrity: sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==}
+
+ '@vue/compiler-sfc@3.5.28':
+ resolution: {integrity: sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==}
+
+ '@vue/compiler-ssr@3.5.28':
+ resolution: {integrity: sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==}
+
+ '@vue/reactivity@3.5.28':
+ resolution: {integrity: sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==}
+
+ '@vue/runtime-core@3.5.28':
+ resolution: {integrity: sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==}
+
+ '@vue/runtime-dom@3.5.28':
+ resolution: {integrity: sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==}
+
+ '@vue/server-renderer@3.5.28':
+ resolution: {integrity: sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==}
+ peerDependencies:
+ vue: 3.5.28
+
+ '@vue/shared@3.5.28':
+ resolution: {integrity: sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==}
+
+ '@vue/test-utils@2.4.6':
+ resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==}
+
'@yarnpkg/lockfile@1.1.0':
resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==}
@@ -2190,6 +2382,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}
+
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -2211,6 +2407,10 @@ packages:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
+ ansi-regex@6.2.2:
+ resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
+ engines: {node: '>=12'}
+
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@@ -2219,6 +2419,10 @@ packages:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
engines: {node: '>=10'}
+ ansi-styles@6.2.3:
+ resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
+ engines: {node: '>=12'}
+
ansis@4.2.0:
resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}
engines: {node: '>=14'}
@@ -2424,6 +2628,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'}
+
comment-parser@1.4.5:
resolution: {integrity: sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==}
engines: {node: '>= 12.0.0'}
@@ -2434,6 +2642,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==}
+
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
@@ -2554,6 +2765,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
+
ejs@3.1.10:
resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}
engines: {node: '>=0.10.0'}
@@ -2565,6 +2784,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==}
+
empathic@2.0.0:
resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
engines: {node: '>=14'}
@@ -2894,6 +3116,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'}
@@ -2955,6 +3181,11 @@ packages:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
+ 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
+
globals@14.0.0:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
@@ -3077,6 +3308,9 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ ini@1.3.8:
+ resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+
internal-slot@1.1.0:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
@@ -3201,6 +3435,9 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ jackspeak@3.4.3:
+ resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
jake@10.9.4:
resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==}
engines: {node: '>=10'}
@@ -3214,6 +3451,15 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
+ 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==}
@@ -3300,6 +3546,9 @@ packages:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -3375,6 +3624,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'}
@@ -3382,6 +3635,10 @@ packages:
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+ minipass@7.1.3:
+ resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@@ -3428,6 +3685,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
+
npm-run-path@4.0.1:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
engines: {node: '>=8'}
@@ -3519,6 +3781,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==}
@@ -3546,6 +3811,10 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
+ path-scurry@1.11.1:
+ resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+ engines: {node: '>=16 || 14 >=14.18'}
+
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
@@ -3612,6 +3881,9 @@ packages:
resolution: {integrity: sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+ proto-list@1.2.4:
+ resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
+
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
@@ -3921,6 +4193,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_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
@@ -3928,6 +4204,10 @@ packages:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
+ strip-ansi@7.1.2:
+ resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
+ engines: {node: '>=12'}
+
strip-bom@3.0.0:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'}
@@ -4218,12 +4498,34 @@ packages:
jsdom:
optional: true
+ 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.28:
+ resolution: {integrity: sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==}
+ 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}
@@ -4280,6 +4582,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'}
+
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@@ -4959,6 +5265,15 @@ snapshots:
dependencies:
'@isaacs/balanced-match': 4.0.1
+ '@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
+
'@jest/diff-sequences@30.0.1': {}
'@jest/get-type@30.1.0': {}
@@ -5064,6 +5379,8 @@ snapshots:
'@nx/nx-win32-x64-msvc@22.5.2':
optional: true
+ '@one-ini/wasm@0.1.1': {}
+
'@oxc-project/types@0.112.0': {}
'@oxc-resolver/binding-android-arm-eabi@11.18.0':
@@ -5128,6 +5445,9 @@ snapshots:
'@oxc-resolver/binding-win32-x64-msvc@11.18.0':
optional: true
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
'@preact/preset-vite@2.10.3(@babel/core@7.29.0)(preact@10.28.4)(rollup@4.58.0)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))':
dependencies:
'@babel/core': 7.29.0
@@ -5430,7 +5750,7 @@ snapshots:
transitivePeerDependencies:
- csstype
- '@tanstack/devtools-utils@0.3.0(@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.0(@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.28(typescript@5.9.3))':
dependencies:
'@tanstack/devtools-ui': 0.4.4(csstype@3.2.3)(solid-js@1.9.11)
optionalDependencies:
@@ -5438,6 +5758,7 @@ snapshots:
preact: 10.28.4
react: 19.2.4
solid-js: 1.9.11
+ vue: 3.5.28(typescript@5.9.3)
transitivePeerDependencies:
- csstype
@@ -5548,6 +5869,12 @@ snapshots:
transitivePeerDependencies:
- typescript
+ '@tanstack/vue-store@0.9.1(vue@3.5.28(typescript@5.9.3))':
+ dependencies:
+ '@tanstack/store': 0.9.1
+ vue: 3.5.28(typescript@5.9.3)
+ vue-demi: 0.14.10(vue@3.5.28(typescript@5.9.3))
+
'@testing-library/dom@10.4.1':
dependencies:
'@babel/code-frame': 7.29.0
@@ -5831,6 +6158,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@vitejs/plugin-vue@5.2.4(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3))':
+ dependencies:
+ vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)
+ vue: 3.5.28(typescript@5.9.3)
+
'@vitest/expect@4.0.18':
dependencies:
'@standard-schema/spec': 1.1.0
@@ -5870,6 +6202,65 @@ snapshots:
'@vitest/pretty-format': 4.0.18
tinyrainbow: 3.0.3
+ '@vue/compiler-core@3.5.28':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@vue/shared': 3.5.28
+ entities: 7.0.1
+ estree-walker: 2.0.2
+ source-map-js: 1.2.1
+
+ '@vue/compiler-dom@3.5.28':
+ dependencies:
+ '@vue/compiler-core': 3.5.28
+ '@vue/shared': 3.5.28
+
+ '@vue/compiler-sfc@3.5.28':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@vue/compiler-core': 3.5.28
+ '@vue/compiler-dom': 3.5.28
+ '@vue/compiler-ssr': 3.5.28
+ '@vue/shared': 3.5.28
+ 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.28':
+ dependencies:
+ '@vue/compiler-dom': 3.5.28
+ '@vue/shared': 3.5.28
+
+ '@vue/reactivity@3.5.28':
+ dependencies:
+ '@vue/shared': 3.5.28
+
+ '@vue/runtime-core@3.5.28':
+ dependencies:
+ '@vue/reactivity': 3.5.28
+ '@vue/shared': 3.5.28
+
+ '@vue/runtime-dom@3.5.28':
+ dependencies:
+ '@vue/reactivity': 3.5.28
+ '@vue/runtime-core': 3.5.28
+ '@vue/shared': 3.5.28
+ csstype: 3.2.3
+
+ '@vue/server-renderer@3.5.28(vue@3.5.28(typescript@5.9.3))':
+ dependencies:
+ '@vue/compiler-ssr': 3.5.28
+ '@vue/shared': 3.5.28
+ vue: 3.5.28(typescript@5.9.3)
+
+ '@vue/shared@3.5.28': {}
+
+ '@vue/test-utils@2.4.6':
+ dependencies:
+ js-beautify: 1.15.4
+ vue-component-type-helpers: 2.2.12
+
'@yarnpkg/lockfile@1.1.0': {}
'@yarnpkg/parsers@3.0.2':
@@ -5881,6 +6272,8 @@ snapshots:
dependencies:
argparse: 2.0.1
+ abbrev@2.0.0: {}
+
acorn-jsx@5.3.2(acorn@8.16.0):
dependencies:
acorn: 8.16.0
@@ -5898,12 +6291,16 @@ snapshots:
ansi-regex@5.0.1: {}
+ ansi-regex@6.2.2: {}
+
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
ansi-styles@5.2.0: {}
+ ansi-styles@6.2.3: {}
+
ansis@4.2.0: {}
argparse@1.0.10:
@@ -6114,12 +6511,19 @@ snapshots:
dependencies:
delayed-stream: 1.0.0
+ commander@10.0.1: {}
+
comment-parser@1.4.5: {}
compare-versions@6.1.1: {}
concat-map@0.0.1: {}
+ config-chain@1.1.13:
+ dependencies:
+ ini: 1.3.8
+ proto-list: 1.2.4
+
convert-source-map@2.0.0: {}
cross-spawn@7.0.6:
@@ -6243,6 +6647,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
+
ejs@3.1.10:
dependencies:
jake: 10.9.4
@@ -6251,6 +6664,8 @@ snapshots:
emoji-regex@8.0.0: {}
+ emoji-regex@9.2.2: {}
+
empathic@2.0.0: {}
encoding-sniffer@0.2.1:
@@ -6697,6 +7112,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
@@ -6768,6 +7188,15 @@ snapshots:
dependencies:
is-glob: 4.0.3
+ 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
+
globals@14.0.0: {}
globals@15.15.0: {}
@@ -6875,6 +7304,8 @@ snapshots:
inherits@2.0.4: {}
+ ini@1.3.8: {}
+
internal-slot@1.1.0:
dependencies:
es-errors: 1.3.0
@@ -6992,6 +7423,12 @@ snapshots:
isexe@2.0.0: {}
+ jackspeak@3.4.3:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+
jake@10.9.4:
dependencies:
async: 3.2.6
@@ -7007,6 +7444,16 @@ snapshots:
jiti@2.6.1: {}
+ 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:
@@ -7089,6 +7536,8 @@ snapshots:
chalk: 4.1.2
is-unicode-supported: 0.1.0
+ lru-cache@10.4.3: {}
+
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@@ -7158,12 +7607,18 @@ 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
minimist@1.2.8: {}
+ minipass@7.1.3: {}
+
mri@1.2.0: {}
ms@2.1.3: {}
@@ -7193,6 +7648,10 @@ snapshots:
node-releases@2.0.27: {}
+ nopt@7.2.1:
+ dependencies:
+ abbrev: 2.0.0
+
npm-run-path@4.0.1:
dependencies:
path-key: 3.1.1
@@ -7356,6 +7815,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
@@ -7383,6 +7844,11 @@ snapshots:
path-key@3.1.1: {}
+ path-scurry@1.11.1:
+ dependencies:
+ lru-cache: 10.4.3
+ minipass: 7.1.3
+
path-type@4.0.0: {}
pathe@2.0.3: {}
@@ -7430,6 +7896,8 @@ snapshots:
ansi-styles: 5.2.0
react-is: 18.3.1
+ proto-list@1.2.4: {}
+
proxy-from-env@1.1.0: {}
publint@0.3.17:
@@ -7759,6 +8227,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_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
@@ -7767,6 +8241,10 @@ snapshots:
dependencies:
ansi-regex: 5.0.1
+ strip-ansi@7.1.2:
+ dependencies:
+ ansi-regex: 6.2.2
+
strip-bom@3.0.0: {}
strip-indent@3.0.0:
@@ -8053,6 +8531,12 @@ snapshots:
- tsx
- yaml
+ vue-component-type-helpers@2.2.12: {}
+
+ vue-demi@0.14.10(vue@3.5.28(typescript@5.9.3)):
+ dependencies:
+ vue: 3.5.28(typescript@5.9.3)
+
vue-eslint-parser@10.4.0(eslint@9.39.3(jiti@2.6.1)):
dependencies:
debug: 4.4.3
@@ -8065,6 +8549,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ vue@3.5.28(typescript@5.9.3):
+ dependencies:
+ '@vue/compiler-dom': 3.5.28
+ '@vue/compiler-sfc': 3.5.28
+ '@vue/runtime-dom': 3.5.28
+ '@vue/server-renderer': 3.5.28(vue@3.5.28(typescript@5.9.3))
+ '@vue/shared': 3.5.28
+ optionalDependencies:
+ typescript: 5.9.3
+
walk-up-path@4.0.0: {}
wcwidth@1.0.1:
@@ -8128,6 +8622,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
+
wrappy@1.0.2: {}
ws@8.19.0: {}