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: ` + + @if (recorder.isRecording()) { + + } + `, +}) +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()) { + + } @else { + + } + `, +}) +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: ``, +}) +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 + + + +``` + +### 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 + + + +``` + +## 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 + + + +``` + +## 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 + + + +``` + +## `useHeldKeyCodes` + +```vue + +``` + +## `useKeyHold` + +```vue + + + +``` + +## Common Patterns + +### Hold-to-Reveal UI + +```vue + + + +``` + +### 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 + + + +``` + +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 + + + +``` + +### Conditional Hotkeys + +```vue + +``` + +### Multi-Key Sequences + +```vue + +``` + +### Tracking Held Keys + +```vue + + + +``` + +### Displaying Hotkeys in the UI + +```vue + + + +``` + +## 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 + + + +``` 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 + + + +``` 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 + + + +``` 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 + + + +``` + +```vue + + + +``` 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 @@ + + + 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 + + + +
+ + + 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 @@ + + +