Hooks
useHotkeys
Keyboard shortcuts with sequences and modifiers.
Global keyboard shortcuts with support for sequences: press «g», then «d» — and you are on the dashboard.
Example
- Нажмите сочетание — сюда попадёт срабатывание
import { useHotkeys } from "@toimetdev/pathlogs-hooks";
useHotkeys([
{ keys: "g d", label: "Dashboard", handler: () => router.push("/dashboard") },
{ keys: "g m", label: "My tasks", handler: () => router.push("/my") },
{ keys: "d", label: "Mark as done", handler: markDone },
{ keys: "mod+k", label: "Search", allowInInput: true, handler: openPalette },
]);Key notation
Chords are separated by spaces; modifiers inside a chord, by a plus:
"k" // a plain key
"g d" // a sequence: g, then d
"mod+k" // Ctrl on Windows/Linux, ⌘ on macOS
"mod+shift+p" // several modifiers
"?" // shift is implied
"escape" // esc, up, down, left, right, enter, space, delete| Prop | Type | Default | Description |
|---|---|---|---|
mod | modifier | — | Ctrl and ⌘ in one notation. Telling ctrl and cmd apart is rarely needed — what an app almost always means is «the system modifier». |
shift | modifier | — | Required only when written explicitly. «?» is typed with shift on most layouts, and demanding shift: false would break that notation. |
alt | modifier | — | Also known as option on macOS. |
modmodifierCtrl and ⌘ in one notation. Telling ctrl and cmd apart is rarely needed — what an app almost always means is «the system modifier».
shiftmodifierRequired only when written explicitly. «?» is typed with shift on most layouts, and demanding shift: false would break that notation.
altmodifierAlso known as option on macOS.
Options
| Prop | Type | Default | Description |
|---|---|---|---|
keys* | string | — | The key notation. |
handler* | (e: KeyboardEvent) => void | — | What to do. preventDefault is called for you. |
label | string | — | The caption for the help screen. Entries without one are treated as internal and stay out of the help. |
group | string | — | A section in the help: «Navigation», «Board». |
allowInInput | boolean | false | Also fires while focus is in a text field. For mod+k and escape. |
enabled | boolean | true | Temporarily disable an entry without removing it from the list. |
keys*stringThe key notation.
handler*(e: KeyboardEvent) => voidWhat to do. preventDefault is called for you.
labelstringThe caption for the help screen. Entries without one are treated as internal and stay out of the help.
groupstringA section in the help: «Navigation», «Board».
allowInInputbooleandefaults to: false
Also fires while focus is in a text field. For mod+k and escape.
enabledbooleandefaults to: true
Temporarily disable an entry without removing it from the list.
The hook's second argument holds shared options: enabled switches off the whole set, timeout sets the window for the second key (1200ms by default), and target lets you listen on a specific element rather than window.
Text inputs
Ordinary keys inside a text field belong to the field, not to the application. Entries with allowInInput are the exception.
The help screen
The same array is handed to the HotkeysHelp component — it shows the help screen on «?» and calls useHotkeys itself:
import { HotkeysHelp } from "@toimetdev/pathlogs-core";
const hotkeys = [
{ keys: "g d", label: "Dashboard", group: "Navigation", handler: goDashboard },
];
// no separate useHotkeys call needed — HotkeysHelp does it for you
<HotkeysHelp hotkeys={hotkeys} hint="«g» is the leader: press g, then a second key." />One list for both handling and help — there is nothing to drift apart.
The matcher, separately
Parsing and matching know nothing about the DOM and are covered by tests:
import {
parseHotkey, // "g d" → [{ key: "g" }, { key: "d" }]
chordFromEvent, // KeyboardEvent → a chord
chordMatches, // do two chords match
createHotkeyMatcher // the state machine for sequences
} from "@toimetdev/pathlogs-hooks";The matcher stores no buffer of presses, only an index inside an unfinished sequence: a buffer would have to be cleared on a timer, while an index need only be compared against the time of the last press. That is why a «g» pressed a minute ago does not turn a stray «d» into a navigation.