@tanstack/octane-hotkeys 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/octane-hotkeys",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Octane adapter for TanStack Hotkeys",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"keywords": [
|
|
18
18
|
"octane",
|
|
19
19
|
"tanstack",
|
|
20
|
-
"hotkeys"
|
|
20
|
+
"hotkeys",
|
|
21
|
+
"tanstack-intent"
|
|
21
22
|
],
|
|
22
23
|
"type": "module",
|
|
23
24
|
"types": "./dist/index.d.ts",
|
|
@@ -30,10 +31,11 @@
|
|
|
30
31
|
"node": ">=22.22.2"
|
|
31
32
|
},
|
|
32
33
|
"files": [
|
|
33
|
-
"dist"
|
|
34
|
+
"dist",
|
|
35
|
+
"skills"
|
|
34
36
|
],
|
|
35
37
|
"dependencies": {
|
|
36
|
-
"@tanstack/hotkeys": "0.
|
|
38
|
+
"@tanstack/hotkeys": "0.17.0",
|
|
37
39
|
"@tanstack/octane-store": "^0.13.0"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: octane-hotkeys
|
|
3
|
+
description: >-
|
|
4
|
+
Use when adding or fixing keyboard shortcuts in an Octane app with
|
|
5
|
+
@tanstack/octane-hotkeys: useHotkey and useHotkeys in .tsrx components,
|
|
6
|
+
element state as targets, HotkeysProvider defaults and dialog scopes, app
|
|
7
|
+
hotkey hooks, recorders, held-key and hint hooks, or replacing keydown
|
|
8
|
+
listeners.
|
|
9
|
+
metadata:
|
|
10
|
+
purpose: >-
|
|
11
|
+
Octane bindings for TanStack Hotkeys: hooks in compiler-enabled .tsrx
|
|
12
|
+
components, element targets, providers, and the Octane-specific mistakes to
|
|
13
|
+
avoid.
|
|
14
|
+
type: framework
|
|
15
|
+
framework: octane
|
|
16
|
+
library: '@tanstack/octane-hotkeys'
|
|
17
|
+
library_version: '0.5.0'
|
|
18
|
+
sources:
|
|
19
|
+
- src/useHotkey.ts
|
|
20
|
+
- src/useHotkeys.ts
|
|
21
|
+
- src/HotkeysProvider.ts
|
|
22
|
+
- src/useAppHotkeys.ts
|
|
23
|
+
- TanStack/hotkeys:docs/framework/octane/quick-start.md
|
|
24
|
+
- TanStack/hotkeys:docs/framework/octane/guides/hotkeys-provider-and-scopes.md
|
|
25
|
+
requires:
|
|
26
|
+
- '@tanstack/hotkeys#hotkeys-core'
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# TanStack Hotkeys in Octane
|
|
30
|
+
|
|
31
|
+
Before starting, load the `@tanstack/hotkeys#hotkeys-core` skill (`npx @tanstack/intent@latest load @tanstack/hotkeys#hotkeys-core`).
|
|
32
|
+
|
|
33
|
+
```tsrx
|
|
34
|
+
import { useState } from 'octane'
|
|
35
|
+
import { formatForDisplay, useHotkey } from '@tanstack/octane-hotkeys'
|
|
36
|
+
|
|
37
|
+
export function Panel({ onSave }: { onSave: () => void }) @{
|
|
38
|
+
const [panel, setPanel] = useState<HTMLDivElement | null>(null)
|
|
39
|
+
|
|
40
|
+
useHotkey('Mod+S', () => onSave(), { meta: { name: 'Save' } })
|
|
41
|
+
useHotkey('Escape', () => closePanel(), { target: panel })
|
|
42
|
+
|
|
43
|
+
<div ref={setPanel} tabIndex={0}>
|
|
44
|
+
Save <kbd>{formatForDisplay('Mod+S')}</kbd>
|
|
45
|
+
</div>
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
| API | What it's for |
|
|
52
|
+
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| `useHotkey(hotkey, callback, options?)` | Register one chord, sequence, or app hotkey while the component is mounted. |
|
|
54
|
+
| `useHotkeys(definitions, commonOptions?)` | Register several hotkeys, or a list built from data, in one call. |
|
|
55
|
+
| `<HotkeysProvider>` | Set default options, a scope (`exclusive` for dialogs), `enabled`, and app hotkeys for the components inside it. |
|
|
56
|
+
| `useHotkeysContext()` | Read the nearest provider's merged defaults and enabled state, or `null` outside a provider. |
|
|
57
|
+
| `useDefaultHotkeysOptions()` | Read the default options merged from every provider above. |
|
|
58
|
+
| `useAppHotkeys()` | Get the declared app hotkeys with the user's custom hotkeys applied, to pass to the register API. |
|
|
59
|
+
| `useAppHotkeyList()` | List every app hotkey with its status, including ones whose components aren't mounted, for a cheat sheet or settings screen. |
|
|
60
|
+
| `useCustomHotkeys()` | Read, change, and reset the user's custom hotkeys, and check a new one for conflicts. |
|
|
61
|
+
| `useHotkeyRecorder(options)` | Record a chord or sequence the user presses, for a settings screen. |
|
|
62
|
+
| `useHotkeyRegistrations()` | List every registered hotkey with its options and `meta`, for a cheat sheet of what's mounted. |
|
|
63
|
+
| `useHotkeyHint(hotkey, options?)` | Get whether held modifiers should reveal a shortcut's hint, such as labels that appear while Mod is held. |
|
|
64
|
+
| `useKeyHold(key)` | Get whether one key is held, such as Shift for a range-select mode. |
|
|
65
|
+
| `useHeldKeys()` | Get every key that's held right now. |
|
|
66
|
+
| `useHeldKeyCodes()` | Map each held key to its physical `event.code`. |
|
|
67
|
+
|
|
68
|
+
The package also re-exports `@tanstack/hotkeys`, such as `formatForDisplay`, `matchesKeyboardEvent`, and `defineAppHotkeys`.
|
|
69
|
+
|
|
70
|
+
No provider is required. Use Octane 0.12 or newer, with hooks in compiler-enabled `.tsrx` components.
|
|
71
|
+
|
|
72
|
+
## Pitfalls
|
|
73
|
+
|
|
74
|
+
- **Targets are elements, not ref objects.** Keep the element in state with a callback ref, as above; a `null` target waits for the element.
|
|
75
|
+
- **Register a dialog's hotkeys in a child of its provider.** A hook uses the nearest `HotkeysProvider` above its component, so a dialog that renders `<HotkeysProvider scope={...} exclusive>` and registers `Escape` itself puts it in the suspended outer scope. Give each open dialog its own scope name, such as `` `dialog-${useId()}` ``.
|
|
76
|
+
- **Hooks are tracked by call site.** Use `useHotkeys(items.map(...))` for lists, not `useHotkey` in a loop.
|
|
77
|
+
- **Call the hooks directly.** A `useShortcut(keys: string)` wrapper has to cast strings to hotkeys, which turns off type checking and drops sequences, options, and app hotkeys.
|
|
78
|
+
|
|
79
|
+
When declaring app hotkeys, building a cheat sheet, or letting users rebind shortcuts, load `@tanstack/hotkeys#app-hotkeys` and read [app hotkeys in Octane](references/app-hotkeys.md).
|
|
80
|
+
|
|
81
|
+
## API discovery and docs
|
|
82
|
+
|
|
83
|
+
Signatures: `node_modules/@tanstack/octane-hotkeys/dist/*.d.ts`. Guides: `https://tanstack.com/hotkeys/latest/docs/framework/octane/guides/<page>.md`, such as `hotkeys`, `hotkeys-provider-and-scopes`, or `app-hotkeys`.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# App hotkeys in Octane
|
|
2
|
+
|
|
3
|
+
The `@tanstack/hotkeys#app-hotkeys` skill owns the rules. This is the Octane wiring.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
// app-hotkeys.ts
|
|
7
|
+
import { defineAppHotkeys } from '@tanstack/octane-hotkeys'
|
|
8
|
+
|
|
9
|
+
export const appHotkeys = defineAppHotkeys({
|
|
10
|
+
'doc.save': { hotkey: 'Mod+S', meta: { name: 'Save' } },
|
|
11
|
+
'help.shortcuts': { hotkey: 'Mod+/', meta: { name: 'Keyboard shortcuts' } },
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
declare module '@tanstack/octane-hotkeys' {
|
|
15
|
+
interface Register {
|
|
16
|
+
appHotkeys: typeof appHotkeys
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```tsrx
|
|
22
|
+
import { HotkeysProvider } from '@tanstack/octane-hotkeys'
|
|
23
|
+
import type { CustomHotkeys } from '@tanstack/octane-hotkeys'
|
|
24
|
+
import { appHotkeys } from './app-hotkeys'
|
|
25
|
+
import { App } from './App.tsrx'
|
|
26
|
+
|
|
27
|
+
const saved: CustomHotkeys = JSON.parse(localStorage.getItem('hotkeys') ?? '{}')
|
|
28
|
+
|
|
29
|
+
export function Root() @{
|
|
30
|
+
<HotkeysProvider
|
|
31
|
+
appHotkeys={appHotkeys}
|
|
32
|
+
customHotkeys={saved}
|
|
33
|
+
onCustomHotkeysChange={(custom) => localStorage.setItem('hotkeys', JSON.stringify(custom))}
|
|
34
|
+
>
|
|
35
|
+
<App />
|
|
36
|
+
</HotkeysProvider>
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Register with `useHotkey(appHotkeys['doc.save'], save)`, using the import or `useAppHotkeys()`. Build a cheat sheet from `useAppHotkeyList()`.
|
|
41
|
+
|
|
42
|
+
A settings row records with `useHotkeyRecorder` and saves through `useCustomHotkeys`:
|
|
43
|
+
|
|
44
|
+
```tsrx
|
|
45
|
+
import { formatForDisplay, useAppHotkeys, useCustomHotkeys, useHotkeyRecorder } from '@tanstack/octane-hotkeys'
|
|
46
|
+
import type { AppHotkeyId } from '@tanstack/octane-hotkeys'
|
|
47
|
+
|
|
48
|
+
export function ShortcutRow({ id }: { id: AppHotkeyId }) @{
|
|
49
|
+
const appHotkey = useAppHotkeys()[id]
|
|
50
|
+
const { findConflicts, setCustomHotkey, resetCustomHotkey } = useCustomHotkeys()
|
|
51
|
+
const recorder = useHotkeyRecorder({
|
|
52
|
+
maxSteps: Infinity,
|
|
53
|
+
validate: (hotkey) => {
|
|
54
|
+
const [conflict] = findConflicts(id, hotkey)
|
|
55
|
+
return conflict ? `Used by ${conflict.meta?.name ?? conflict.id}` : true
|
|
56
|
+
},
|
|
57
|
+
onRecord: (hotkey) => setCustomHotkey(id, hotkey),
|
|
58
|
+
onClear: () => setCustomHotkey(id, null),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
<div>
|
|
62
|
+
{appHotkey.meta?.name}: {appHotkey.hotkey ? formatForDisplay(appHotkey.hotkey) : 'Unassigned'}
|
|
63
|
+
<button onClick={recorder.startRecording}>Change</button>
|
|
64
|
+
<button onClick={() => resetCustomHotkey(id)}>Reset</button>
|
|
65
|
+
</div>
|
|
66
|
+
}
|
|
67
|
+
```
|