From 5bdef40b7be8f69cc0a1e24dd6cf67bf927b9c71 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 20 Sep 2026 21:45:05 -0500 Subject: [PATCH 1/3] feat: add physical hotkeys, recorder validation, hints, and display options --- .../physical-recording-hints-display.md | 44 + docs/config.json | 188 ++++- docs/devtools.md | 28 +- .../angular/guides/formatting-display.md | 127 +-- .../angular/guides/hotkey-recording.md | 48 +- docs/framework/angular/guides/hotkeys.md | 85 +- .../angular/guides/key-state-tracking.md | 20 +- .../angular/guides/sequence-recording.md | 55 +- docs/framework/angular/guides/sequences.md | 34 +- docs/framework/angular/quick-start.md | 20 +- .../reference/functions/injectHotkeyHint.md | 30 + docs/framework/angular/reference/index.md | 1 + .../interfaces/InjectHotkeyOptions.md | 2 +- .../interfaces/InjectHotkeySequenceOptions.md | 2 +- .../lit/guides/formatting-display.md | 274 ++----- docs/framework/lit/guides/hotkey-recording.md | 65 +- docs/framework/lit/guides/hotkeys.md | 88 +- .../lit/guides/key-state-tracking.md | 23 +- .../lit/guides/sequence-recording.md | 28 +- docs/framework/lit/guides/sequences.md | 34 +- docs/framework/lit/quick-start.md | 32 +- .../reference/classes/HotkeyHintController.md | 129 +++ docs/framework/lit/reference/index.md | 1 + .../preact/guides/formatting-display.md | 228 ++---- .../preact/guides/hotkey-recording.md | 72 +- docs/framework/preact/guides/hotkeys.md | 94 ++- .../preact/guides/key-state-tracking.md | 32 +- .../preact/guides/sequence-recording.md | 24 +- docs/framework/preact/guides/sequences.md | 48 +- .../reference/functions/useHotkeyHint.md | 30 + .../functions/useHotkeyRegistrations.md | 2 +- docs/framework/preact/reference/index.md | 1 + .../interfaces/HotkeyRegistrationsResult.md | 6 +- .../reference/interfaces/UseHotkeyOptions.md | 2 +- .../interfaces/UseHotkeySequenceOptions.md | 2 +- .../react/guides/formatting-display.md | 244 ++---- .../react/guides/hotkey-recording.md | 76 +- docs/framework/react/guides/hotkeys.md | 94 ++- .../react/guides/key-state-tracking.md | 40 +- .../react/guides/sequence-recording.md | 24 +- docs/framework/react/guides/sequences.md | 50 +- docs/framework/react/quick-start.md | 24 +- .../reference/functions/useHotkeyHint.md | 30 + docs/framework/react/reference/index.md | 1 + .../reference/interfaces/UseHotkeyOptions.md | 2 +- .../interfaces/UseHotkeySequenceOptions.md | 2 +- .../solid/guides/formatting-display.md | 180 ++-- .../solid/guides/hotkey-recording.md | 68 +- docs/framework/solid/guides/hotkeys.md | 93 ++- .../solid/guides/key-state-tracking.md | 34 +- .../solid/guides/sequence-recording.md | 55 +- docs/framework/solid/guides/sequences.md | 50 +- .../reference/functions/createHotkeyHint.md | 36 + docs/framework/solid/reference/index.md | 1 + .../interfaces/CreateHotkeyOptions.md | 2 +- .../interfaces/CreateHotkeySequenceOptions.md | 2 +- .../svelte/guides/formatting-display.md | 107 ++- .../svelte/guides/hotkey-recording.md | 46 +- docs/framework/svelte/guides/hotkeys.md | 70 +- .../svelte/guides/key-state-tracking.md | 20 +- .../svelte/guides/sequence-recording.md | 48 +- docs/framework/svelte/guides/sequences.md | 26 +- docs/framework/svelte/quick-start.md | 6 +- .../functions/createHotkeyRecorder.md | 2 +- .../functions/createHotkeySequenceRecorder.md | 2 +- .../reference/functions/getHotkeyHint.md | 30 + docs/framework/svelte/reference/index.md | 2 + .../reference/interfaces/SvelteHotkeyHint.md | 16 + .../vue/guides/formatting-display.md | 117 ++- docs/framework/vue/guides/hotkey-recording.md | 48 +- docs/framework/vue/guides/hotkeys.md | 74 +- .../vue/guides/key-state-tracking.md | 20 +- .../vue/guides/sequence-recording.md | 75 +- docs/framework/vue/guides/sequences.md | 38 +- docs/framework/vue/quick-start.md | 20 +- .../vue/reference/functions/useHotkeyHint.md | 30 + docs/framework/vue/reference/index.md | 1 + docs/installation.md | 4 +- docs/overview.md | 59 +- docs/reference/classes/HotkeyManager.md | 20 +- docs/reference/classes/HotkeyRecorder.md | 16 +- .../classes/HotkeySequenceRecorder.md | 26 +- docs/reference/classes/KeyStateTracker.md | 14 +- docs/reference/classes/SequenceManager.md | 18 +- docs/reference/functions/areHotkeysEqual.md | 35 + docs/reference/functions/assertValidHotkey.md | 2 +- docs/reference/functions/checkHotkey.md | 2 +- .../functions/createHotkeyHandler.md | 4 +- .../functions/createMultiHotkeyHandler.md | 4 +- .../functions/createSequenceMatcher.md | 2 +- docs/reference/functions/detectPlatform.md | 2 +- .../functions/findHotkeyConflicts.md | 28 + docs/reference/functions/formatForDisplay.md | 130 ++- docs/reference/functions/formatHotkey.md | 4 +- .../functions/formatHotkeySequence.md | 5 +- docs/reference/functions/formatWithLabels.md | 4 +- docs/reference/functions/getHotkeyManager.md | 2 +- .../reference/functions/getKeyStateTracker.md | 2 +- .../reference/functions/getSequenceManager.md | 2 +- docs/reference/functions/hasNonModifierKey.md | 4 +- docs/reference/functions/isModifierKey.md | 2 +- docs/reference/functions/isSingleLetterKey.md | 2 +- .../functions/matchesHeldModifiers.md | 43 + .../functions/matchesKeyboardEvent.md | 7 +- docs/reference/functions/normalizeHotkey.md | 2 +- .../functions/normalizeHotkeyFromEvent.md | 2 +- .../functions/normalizeHotkeyFromParsed.md | 4 +- docs/reference/functions/normalizeKeyName.md | 2 +- .../functions/normalizeRegisterableHotkey.md | 4 +- docs/reference/functions/parseHotkey.md | 6 +- .../reference/functions/parseKeyboardEvent.md | 12 +- .../functions/parseRegisterableHotkey.md | 27 + .../functions/rawHotkeyToParsedHotkey.md | 4 +- docs/reference/functions/resolveModifier.md | 2 +- .../functions/toHotkeyRegistrationView.md | 2 +- docs/reference/functions/validateHotkey.md | 2 +- docs/reference/index.md | 33 +- .../interfaces/CreateHotkeyHandlerOptions.md | 8 +- .../interfaces/FormatDisplayOptions.md | 70 +- .../interfaces/HeldModifierOptions.md | 30 + .../interfaces/HotkeyCallbackContext.md | 6 +- .../interfaces/HotkeyConflictOptions.md | 112 +++ docs/reference/interfaces/HotkeyMeta.md | 20 +- docs/reference/interfaces/HotkeyOptions.md | 26 +- .../interfaces/HotkeyRecorderOptions.md | 114 ++- .../interfaces/HotkeyRecorderState.md | 6 +- .../HotkeyRecorderValidationContext.md | 26 + .../interfaces/HotkeyRegistration.md | 44 +- .../interfaces/HotkeyRegistrationHandle.md | 12 +- .../interfaces/HotkeyRegistrationView.md | 18 +- .../HotkeySequenceRecorderOptions.md | 122 ++- .../interfaces/HotkeySequenceRecorderState.md | 8 +- ...HotkeySequenceRecorderValidationContext.md | 26 + .../interfaces/KeyboardEventMatch.md | 58 ++ .../interfaces/NormalizedKeyboardEvent.md | 98 +++ docs/reference/interfaces/ParsedHotkey.md | 96 --- docs/reference/interfaces/ParsedModifiers.md | 68 ++ docs/reference/interfaces/RawHotkey.md | 98 --- docs/reference/interfaces/RawModifiers.md | 68 ++ docs/reference/interfaces/RecorderOptions.md | 69 ++ .../reference/interfaces/RecorderRejection.md | 56 ++ docs/reference/interfaces/SequenceOptions.md | 26 +- .../interfaces/SequenceRegistration.md | 108 +++ .../interfaces/SequenceRegistrationHandle.md | 12 +- .../interfaces/SequenceRegistrationView.md | 18 +- docs/reference/interfaces/ValidationResult.md | 8 +- .../type-aliases/CanonicalModifier.md | 2 +- .../type-aliases/ConflictBehavior.md | 2 +- docs/reference/type-aliases/DisplayHotkey.md | 13 + docs/reference/type-aliases/EditingKey.md | 2 +- .../type-aliases/FourModifierHotkey.md | 21 + docs/reference/type-aliases/FunctionKey.md | 18 +- docs/reference/type-aliases/Hotkey.md | 2 +- docs/reference/type-aliases/HotkeyCallback.md | 2 +- docs/reference/type-aliases/HotkeyConflict.md | 18 + docs/reference/type-aliases/HotkeySequence.md | 4 +- .../HotkeySequenceRecorderCommitKeys.md | 2 +- docs/reference/type-aliases/IndividualKey.md | 2 +- docs/reference/type-aliases/Key.md | 6 +- docs/reference/type-aliases/LetterKey.md | 2 +- docs/reference/type-aliases/LogicalKey.md | 14 + docs/reference/type-aliases/Modifier.md | 2 +- .../type-aliases/MultiHotkeyHandler.md | 10 + docs/reference/type-aliases/NamedKey.md | 14 + docs/reference/type-aliases/NavigationKey.md | 2 +- .../type-aliases/NonPunctuationKey.md | 18 + docs/reference/type-aliases/NumberKey.md | 2 +- docs/reference/type-aliases/ParsedHotkey.md | 29 + docs/reference/type-aliases/PhysicalKey.md | 14 + .../reference/type-aliases/PhysicalKeyCode.md | 50 ++ docs/reference/type-aliases/PunctuationKey.md | 41 +- docs/reference/type-aliases/RawHotkey.md | 12 + .../reference/type-aliases/RecorderKeyMode.md | 12 + .../type-aliases/RegisterableHotkey.md | 2 +- .../type-aliases/SingleModifierHotkey.md | 25 + docs/reference/type-aliases/Target.md | 10 + .../type-aliases/ThreeModifierHotkey.md | 23 + .../type-aliases/TwoModifierHotkey.md | 26 + docs/reference/variables/ALL_KEYS.md | 57 +- .../variables/DEFAULT_SEQUENCE_TIMEOUT.md | 2 +- docs/reference/variables/EDITING_KEYS.md | 2 +- docs/reference/variables/FUNCTION_KEYS.md | 4 +- .../variables/KEY_DISPLAY_SYMBOLS.md | 2 +- docs/reference/variables/LETTER_KEYS.md | 2 +- .../variables/LINUX_MODIFIER_LABELS.md | 2 +- .../variables/MAC_MODIFIER_LABELS.md | 2 +- .../variables/MAC_MODIFIER_SYMBOLS.md | 2 +- docs/reference/variables/MODIFIER_ALIASES.md | 2 +- docs/reference/variables/MODIFIER_KEYS.md | 2 +- docs/reference/variables/MODIFIER_ORDER.md | 2 +- docs/reference/variables/NAVIGATION_KEYS.md | 2 +- docs/reference/variables/NUMBER_KEYS.md | 2 +- .../variables/PUNCTUATION_CODE_MAP.md | 2 +- docs/reference/variables/PUNCTUATION_KEYS.md | 2 +- .../PUNCTUATION_KEY_DISPLAY_LABELS.md | 2 +- .../variables/WINDOWS_MODIFIER_LABELS.md | 2 +- .../injectHotkey/src/app/app.component.html | 10 +- .../injectHotkey/src/app/app.component.ts | 4 +- examples/angular/kitchen-sink/README.md | 15 + examples/angular/kitchen-sink/angular.json | 115 +++ examples/angular/kitchen-sink/package.json | 38 + .../kitchen-sink/src/app/app.component.ts | 648 +++++++++++++++ .../kitchen-sink/src/app/app.config.ts | 10 + examples/angular/kitchen-sink/src/index.html | 14 + examples/angular/kitchen-sink/src/main.ts | 5 + examples/angular/kitchen-sink/src/styles.css | 156 ++++ examples/angular/kitchen-sink/tsconfig.json | 28 + examples/lit/hotkey/src/app.ts | 14 +- examples/lit/kitchen-sink/README.md | 15 + examples/lit/kitchen-sink/index.html | 12 + examples/lit/kitchen-sink/package.json | 21 + examples/lit/kitchen-sink/src/app.ts | 771 ++++++++++++++++++ examples/lit/kitchen-sink/src/styles.css | 156 ++++ examples/lit/kitchen-sink/tsconfig.json | 27 + examples/preact/kitchen-sink/README.md | 15 + examples/preact/kitchen-sink/eslint.config.js | 11 + examples/preact/kitchen-sink/index.html | 14 + examples/preact/kitchen-sink/package.json | 24 + examples/preact/kitchen-sink/src/index.tsx | 706 ++++++++++++++++ examples/preact/kitchen-sink/src/styles.css | 156 ++++ examples/preact/kitchen-sink/tsconfig.json | 21 + examples/preact/kitchen-sink/vite.config.ts | 6 + examples/preact/useHotkey/src/index.tsx | 14 +- examples/react/kitchen-sink/README.md | 29 + examples/react/kitchen-sink/eslint.config.js | 11 + examples/react/kitchen-sink/index.html | 12 + examples/react/kitchen-sink/package.json | 28 + examples/react/kitchen-sink/src/index.tsx | 729 +++++++++++++++++ examples/react/kitchen-sink/src/styles.css | 156 ++++ examples/react/kitchen-sink/tsconfig.json | 20 + examples/react/kitchen-sink/vite.config.ts | 6 + examples/react/useHotkey/src/index.tsx | 21 +- .../react/useHotkeyRecorder/src/index.tsx | 7 +- .../react/useHotkeySequence/src/index.tsx | 13 +- examples/solid/createHotkey/src/index.tsx | 14 +- examples/solid/kitchen-sink/README.md | 15 + examples/solid/kitchen-sink/index.html | 14 + examples/solid/kitchen-sink/package.json | 25 + examples/solid/kitchen-sink/src/index.tsx | 729 +++++++++++++++++ examples/solid/kitchen-sink/src/styles.css | 156 ++++ examples/solid/kitchen-sink/tsconfig.json | 21 + examples/solid/kitchen-sink/vite.config.ts | 6 + examples/svelte/create-hotkey/src/App.svelte | 14 +- examples/svelte/kitchen-sink/.gitignore | 23 + examples/svelte/kitchen-sink/.npmrc | 1 + examples/svelte/kitchen-sink/README.md | 15 + examples/svelte/kitchen-sink/eslint.config.js | 16 + examples/svelte/kitchen-sink/index.html | 14 + examples/svelte/kitchen-sink/package.json | 22 + examples/svelte/kitchen-sink/src/App.svelte | 131 +++ .../svelte/kitchen-sink/src/Editor.svelte | 57 ++ .../svelte/kitchen-sink/src/EditorPane.svelte | 41 + .../svelte/kitchen-sink/src/Formatting.svelte | 45 + examples/svelte/kitchen-sink/src/Hint.svelte | 9 + .../svelte/kitchen-sink/src/Recording.svelte | 127 +++ examples/svelte/kitchen-sink/src/Root.svelte | 8 + .../svelte/kitchen-sink/src/Sequences.svelte | 65 ++ .../svelte/kitchen-sink/src/Tickets.svelte | 38 + examples/svelte/kitchen-sink/src/main.ts | 5 + examples/svelte/kitchen-sink/src/styles.css | 156 ++++ examples/svelte/kitchen-sink/svelte.config.js | 11 + examples/svelte/kitchen-sink/tsconfig.json | 9 + examples/svelte/kitchen-sink/vite.config.ts | 6 + examples/vanilla/formatForDisplay/src/main.ts | 286 ++++--- .../vanilla/formatForDisplay/src/styles.css | 263 ++++-- examples/vue/kitchen-sink/README.md | 15 + examples/vue/kitchen-sink/eslint.config.js | 27 + examples/vue/kitchen-sink/index.html | 14 + examples/vue/kitchen-sink/package.json | 26 + examples/vue/kitchen-sink/src/App.vue | 151 ++++ examples/vue/kitchen-sink/src/Editor.vue | 57 ++ examples/vue/kitchen-sink/src/EditorPane.vue | 40 + examples/vue/kitchen-sink/src/Formatting.vue | 64 ++ examples/vue/kitchen-sink/src/Hint.vue | 13 + examples/vue/kitchen-sink/src/Recording.vue | 141 ++++ examples/vue/kitchen-sink/src/Sequences.vue | 68 ++ examples/vue/kitchen-sink/src/Tickets.vue | 43 + examples/vue/kitchen-sink/src/index.ts | 13 + examples/vue/kitchen-sink/src/styles.css | 156 ++++ examples/vue/kitchen-sink/src/vue.d.ts | 6 + examples/vue/kitchen-sink/tsconfig.json | 9 + examples/vue/kitchen-sink/vite.config.ts | 6 + examples/vue/useHotkey/src/App.vue | 14 +- package.json | 2 +- packages/angular-hotkeys/src/index.ts | 1 + .../angular-hotkeys/src/injectHotkeyHint.ts | 29 + .../tests/injectHotkeyHint.test.ts | 31 + .../src/components/DetailsPanel.tsx | 4 +- .../{manager.utils.ts => _event-target.ts} | 74 -- packages/hotkeys/src/_keyboard-event.ts | 101 +++ packages/hotkeys/src/_match.ts | 96 +++ packages/hotkeys/src/_named-keys.ts | 62 ++ packages/hotkeys/src/_recorder-chord.ts | 76 ++ packages/hotkeys/src/_recording-guard.ts | 63 ++ packages/hotkeys/src/_registration.ts | 79 ++ packages/hotkeys/src/conflicts.ts | 125 +++ packages/hotkeys/src/constants.ts | 244 ++---- packages/hotkeys/src/display-labels.ts | 123 +++ packages/hotkeys/src/format.ts | 163 ++-- packages/hotkeys/src/hint.ts | 42 + packages/hotkeys/src/hotkey-manager.ts | 135 ++- packages/hotkeys/src/hotkey-recorder.ts | 106 ++- .../hotkeys/src/hotkey-sequence-recorder.ts | 138 +++- .../src/{hotkey.ts => hotkey.types.ts} | 281 ++----- packages/hotkeys/src/index.ts | 16 +- packages/hotkeys/src/key-state-tracker.ts | 60 +- packages/hotkeys/src/key.types.ts | 268 ++++++ packages/hotkeys/src/match.ts | 131 +-- packages/hotkeys/src/parse.ts | 93 ++- packages/hotkeys/src/platform.ts | 66 ++ packages/hotkeys/src/recorder-chord.ts | 25 - packages/hotkeys/src/recorder-options.ts | 34 + packages/hotkeys/src/sequence-manager.ts | 119 ++- packages/hotkeys/src/validate.ts | 50 +- packages/hotkeys/tests/constants.test.ts | 2 +- packages/hotkeys/tests/format.test.ts | 152 +++- packages/hotkeys/tests/hotkey-manager.test.ts | 69 ++ .../hotkeys/tests/hotkey-recorder.test.ts | 232 +++--- .../tests/hotkey-sequence-recorder.test.ts | 415 +++++----- packages/hotkeys/tests/key-coverage.test.ts | 85 ++ .../hotkeys/tests/key-state-tracker.test.ts | 7 + packages/hotkeys/tests/match.test.ts | 108 ++- packages/hotkeys/tests/parse.test.ts | 44 +- .../hotkeys/tests/parsed-identity.test.ts | 119 +++ packages/hotkeys/tests/physical-key.test.ts | 46 ++ packages/hotkeys/tests/public-api.test.ts | 35 + .../tests/recording-and-display.test.ts | 419 ++++++++++ ...est.ts => registration-and-target.test.ts} | 8 +- .../hotkeys/tests/review-regressions.test.ts | 180 ++++ .../tests/sequence-regressions.test.ts | 293 +++++++ .../src/controllers/hotkey-hint.ts | 56 ++ packages/lit-hotkeys/src/controllers/index.ts | 1 + .../lit-hotkeys/tests/hotkey-hint.spec.ts | 37 + packages/preact-hotkeys/src/index.ts | 1 + packages/preact-hotkeys/src/useHotkey.ts | 19 +- packages/preact-hotkeys/src/useHotkeyHint.ts | 19 + .../src/useHotkeyRegistrations.ts | 16 +- .../preact-hotkeys/src/useHotkeySequence.ts | 24 +- .../preact-hotkeys/src/useHotkeySequences.ts | 22 +- packages/preact-hotkeys/src/useHotkeys.ts | 22 +- .../tests/useHotkeyHint.test.tsx | 32 + .../tests/useHotkeyRegistrations.test.tsx | 44 + packages/react-hotkeys/src/index.ts | 1 + packages/react-hotkeys/src/useHotkey.ts | 19 +- packages/react-hotkeys/src/useHotkeyHint.ts | 19 + .../react-hotkeys/src/useHotkeySequence.ts | 24 +- .../react-hotkeys/src/useHotkeySequences.ts | 22 +- packages/react-hotkeys/src/useHotkeys.ts | 23 +- .../tests/useHotkeyHint.test.tsx | 99 +++ .../solid-hotkeys/src/createHotkeyHint.ts | 28 + packages/solid-hotkeys/src/index.ts | 1 + .../tests/createHotkeyHint.test.tsx | 26 + .../src/createHotkeyRecorder.svelte.ts | 9 +- .../createHotkeySequenceRecorder.svelte.ts | 12 +- .../src/getHotkeyHint.svelte.ts | 51 ++ packages/svelte-hotkeys/src/index.ts | 1 + .../tests/RecorderHarness.svelte | 17 + .../tests/getHotkeyHint.test.ts | 43 + .../tests/hint-harness.svelte.ts | 16 + .../svelte-hotkeys/tests/recorders.test.ts | 45 + packages/svelte-hotkeys/vitest.config.ts | 1 + packages/vue-hotkeys/src/index.ts | 1 + packages/vue-hotkeys/src/useHotkeyHint.ts | 25 + packages/vue-hotkeys/tests/hints.test.ts | 35 + pnpm-lock.yaml | 318 +++++++- 365 files changed, 16286 insertions(+), 3433 deletions(-) create mode 100644 .changeset/physical-recording-hints-display.md create mode 100644 docs/framework/angular/reference/functions/injectHotkeyHint.md create mode 100644 docs/framework/lit/reference/classes/HotkeyHintController.md create mode 100644 docs/framework/preact/reference/functions/useHotkeyHint.md create mode 100644 docs/framework/react/reference/functions/useHotkeyHint.md create mode 100644 docs/framework/solid/reference/functions/createHotkeyHint.md create mode 100644 docs/framework/svelte/reference/functions/getHotkeyHint.md create mode 100644 docs/framework/svelte/reference/interfaces/SvelteHotkeyHint.md create mode 100644 docs/framework/vue/reference/functions/useHotkeyHint.md create mode 100644 docs/reference/functions/areHotkeysEqual.md create mode 100644 docs/reference/functions/findHotkeyConflicts.md create mode 100644 docs/reference/functions/matchesHeldModifiers.md create mode 100644 docs/reference/functions/parseRegisterableHotkey.md create mode 100644 docs/reference/interfaces/HeldModifierOptions.md create mode 100644 docs/reference/interfaces/HotkeyConflictOptions.md create mode 100644 docs/reference/interfaces/HotkeyRecorderValidationContext.md create mode 100644 docs/reference/interfaces/HotkeySequenceRecorderValidationContext.md create mode 100644 docs/reference/interfaces/KeyboardEventMatch.md create mode 100644 docs/reference/interfaces/NormalizedKeyboardEvent.md delete mode 100644 docs/reference/interfaces/ParsedHotkey.md create mode 100644 docs/reference/interfaces/ParsedModifiers.md delete mode 100644 docs/reference/interfaces/RawHotkey.md create mode 100644 docs/reference/interfaces/RawModifiers.md create mode 100644 docs/reference/interfaces/RecorderOptions.md create mode 100644 docs/reference/interfaces/RecorderRejection.md create mode 100644 docs/reference/interfaces/SequenceRegistration.md create mode 100644 docs/reference/type-aliases/DisplayHotkey.md create mode 100644 docs/reference/type-aliases/FourModifierHotkey.md create mode 100644 docs/reference/type-aliases/HotkeyConflict.md create mode 100644 docs/reference/type-aliases/LogicalKey.md create mode 100644 docs/reference/type-aliases/MultiHotkeyHandler.md create mode 100644 docs/reference/type-aliases/NamedKey.md create mode 100644 docs/reference/type-aliases/NonPunctuationKey.md create mode 100644 docs/reference/type-aliases/ParsedHotkey.md create mode 100644 docs/reference/type-aliases/PhysicalKey.md create mode 100644 docs/reference/type-aliases/PhysicalKeyCode.md create mode 100644 docs/reference/type-aliases/RawHotkey.md create mode 100644 docs/reference/type-aliases/RecorderKeyMode.md create mode 100644 docs/reference/type-aliases/SingleModifierHotkey.md create mode 100644 docs/reference/type-aliases/Target.md create mode 100644 docs/reference/type-aliases/ThreeModifierHotkey.md create mode 100644 docs/reference/type-aliases/TwoModifierHotkey.md create mode 100644 examples/angular/kitchen-sink/README.md create mode 100644 examples/angular/kitchen-sink/angular.json create mode 100644 examples/angular/kitchen-sink/package.json create mode 100644 examples/angular/kitchen-sink/src/app/app.component.ts create mode 100644 examples/angular/kitchen-sink/src/app/app.config.ts create mode 100644 examples/angular/kitchen-sink/src/index.html create mode 100644 examples/angular/kitchen-sink/src/main.ts create mode 100644 examples/angular/kitchen-sink/src/styles.css create mode 100644 examples/angular/kitchen-sink/tsconfig.json create mode 100644 examples/lit/kitchen-sink/README.md create mode 100644 examples/lit/kitchen-sink/index.html create mode 100644 examples/lit/kitchen-sink/package.json create mode 100644 examples/lit/kitchen-sink/src/app.ts create mode 100644 examples/lit/kitchen-sink/src/styles.css create mode 100644 examples/lit/kitchen-sink/tsconfig.json create mode 100644 examples/preact/kitchen-sink/README.md create mode 100644 examples/preact/kitchen-sink/eslint.config.js create mode 100644 examples/preact/kitchen-sink/index.html create mode 100644 examples/preact/kitchen-sink/package.json create mode 100644 examples/preact/kitchen-sink/src/index.tsx create mode 100644 examples/preact/kitchen-sink/src/styles.css create mode 100644 examples/preact/kitchen-sink/tsconfig.json create mode 100644 examples/preact/kitchen-sink/vite.config.ts create mode 100644 examples/react/kitchen-sink/README.md create mode 100644 examples/react/kitchen-sink/eslint.config.js create mode 100644 examples/react/kitchen-sink/index.html create mode 100644 examples/react/kitchen-sink/package.json create mode 100644 examples/react/kitchen-sink/src/index.tsx create mode 100644 examples/react/kitchen-sink/src/styles.css create mode 100644 examples/react/kitchen-sink/tsconfig.json create mode 100644 examples/react/kitchen-sink/vite.config.ts create mode 100644 examples/solid/kitchen-sink/README.md create mode 100644 examples/solid/kitchen-sink/index.html create mode 100644 examples/solid/kitchen-sink/package.json create mode 100644 examples/solid/kitchen-sink/src/index.tsx create mode 100644 examples/solid/kitchen-sink/src/styles.css create mode 100644 examples/solid/kitchen-sink/tsconfig.json create mode 100644 examples/solid/kitchen-sink/vite.config.ts create mode 100644 examples/svelte/kitchen-sink/.gitignore create mode 100644 examples/svelte/kitchen-sink/.npmrc create mode 100644 examples/svelte/kitchen-sink/README.md create mode 100644 examples/svelte/kitchen-sink/eslint.config.js create mode 100644 examples/svelte/kitchen-sink/index.html create mode 100644 examples/svelte/kitchen-sink/package.json create mode 100644 examples/svelte/kitchen-sink/src/App.svelte create mode 100644 examples/svelte/kitchen-sink/src/Editor.svelte create mode 100644 examples/svelte/kitchen-sink/src/EditorPane.svelte create mode 100644 examples/svelte/kitchen-sink/src/Formatting.svelte create mode 100644 examples/svelte/kitchen-sink/src/Hint.svelte create mode 100644 examples/svelte/kitchen-sink/src/Recording.svelte create mode 100644 examples/svelte/kitchen-sink/src/Root.svelte create mode 100644 examples/svelte/kitchen-sink/src/Sequences.svelte create mode 100644 examples/svelte/kitchen-sink/src/Tickets.svelte create mode 100644 examples/svelte/kitchen-sink/src/main.ts create mode 100644 examples/svelte/kitchen-sink/src/styles.css create mode 100644 examples/svelte/kitchen-sink/svelte.config.js create mode 100644 examples/svelte/kitchen-sink/tsconfig.json create mode 100644 examples/svelte/kitchen-sink/vite.config.ts create mode 100644 examples/vue/kitchen-sink/README.md create mode 100644 examples/vue/kitchen-sink/eslint.config.js create mode 100644 examples/vue/kitchen-sink/index.html create mode 100644 examples/vue/kitchen-sink/package.json create mode 100644 examples/vue/kitchen-sink/src/App.vue create mode 100644 examples/vue/kitchen-sink/src/Editor.vue create mode 100644 examples/vue/kitchen-sink/src/EditorPane.vue create mode 100644 examples/vue/kitchen-sink/src/Formatting.vue create mode 100644 examples/vue/kitchen-sink/src/Hint.vue create mode 100644 examples/vue/kitchen-sink/src/Recording.vue create mode 100644 examples/vue/kitchen-sink/src/Sequences.vue create mode 100644 examples/vue/kitchen-sink/src/Tickets.vue create mode 100644 examples/vue/kitchen-sink/src/index.ts create mode 100644 examples/vue/kitchen-sink/src/styles.css create mode 100644 examples/vue/kitchen-sink/src/vue.d.ts create mode 100644 examples/vue/kitchen-sink/tsconfig.json create mode 100644 examples/vue/kitchen-sink/vite.config.ts create mode 100644 packages/angular-hotkeys/src/injectHotkeyHint.ts create mode 100644 packages/angular-hotkeys/tests/injectHotkeyHint.test.ts rename packages/hotkeys/src/{manager.utils.ts => _event-target.ts} (66%) create mode 100644 packages/hotkeys/src/_keyboard-event.ts create mode 100644 packages/hotkeys/src/_match.ts create mode 100644 packages/hotkeys/src/_named-keys.ts create mode 100644 packages/hotkeys/src/_recorder-chord.ts create mode 100644 packages/hotkeys/src/_recording-guard.ts create mode 100644 packages/hotkeys/src/_registration.ts create mode 100644 packages/hotkeys/src/conflicts.ts create mode 100644 packages/hotkeys/src/display-labels.ts create mode 100644 packages/hotkeys/src/hint.ts rename packages/hotkeys/src/{hotkey.ts => hotkey.types.ts} (58%) create mode 100644 packages/hotkeys/src/key.types.ts create mode 100644 packages/hotkeys/src/platform.ts delete mode 100644 packages/hotkeys/src/recorder-chord.ts create mode 100644 packages/hotkeys/src/recorder-options.ts create mode 100644 packages/hotkeys/tests/key-coverage.test.ts create mode 100644 packages/hotkeys/tests/parsed-identity.test.ts create mode 100644 packages/hotkeys/tests/physical-key.test.ts create mode 100644 packages/hotkeys/tests/public-api.test.ts create mode 100644 packages/hotkeys/tests/recording-and-display.test.ts rename packages/hotkeys/tests/{manager.utils.test.ts => registration-and-target.test.ts} (98%) create mode 100644 packages/hotkeys/tests/review-regressions.test.ts create mode 100644 packages/hotkeys/tests/sequence-regressions.test.ts create mode 100644 packages/lit-hotkeys/src/controllers/hotkey-hint.ts create mode 100644 packages/lit-hotkeys/tests/hotkey-hint.spec.ts create mode 100644 packages/preact-hotkeys/src/useHotkeyHint.ts create mode 100644 packages/preact-hotkeys/tests/useHotkeyHint.test.tsx create mode 100644 packages/preact-hotkeys/tests/useHotkeyRegistrations.test.tsx create mode 100644 packages/react-hotkeys/src/useHotkeyHint.ts create mode 100644 packages/react-hotkeys/tests/useHotkeyHint.test.tsx create mode 100644 packages/solid-hotkeys/src/createHotkeyHint.ts create mode 100644 packages/solid-hotkeys/tests/createHotkeyHint.test.tsx create mode 100644 packages/svelte-hotkeys/src/getHotkeyHint.svelte.ts create mode 100644 packages/svelte-hotkeys/tests/RecorderHarness.svelte create mode 100644 packages/svelte-hotkeys/tests/getHotkeyHint.test.ts create mode 100644 packages/svelte-hotkeys/tests/hint-harness.svelte.ts create mode 100644 packages/svelte-hotkeys/tests/recorders.test.ts create mode 100644 packages/vue-hotkeys/src/useHotkeyHint.ts create mode 100644 packages/vue-hotkeys/tests/hints.test.ts diff --git a/.changeset/physical-recording-hints-display.md b/.changeset/physical-recording-hints-display.md new file mode 100644 index 00000000..0d7be06c --- /dev/null +++ b/.changeset/physical-recording-hints-display.md @@ -0,0 +1,44 @@ +--- +'@tanstack/angular-hotkeys': minor +'@tanstack/hotkeys': minor +'@tanstack/hotkeys-devtools': minor +'@tanstack/lit-hotkeys': minor +'@tanstack/preact-hotkeys': minor +'@tanstack/preact-hotkeys-devtools': minor +'@tanstack/react-hotkeys': minor +'@tanstack/react-hotkeys-devtools': minor +'@tanstack/solid-hotkeys': minor +'@tanstack/solid-hotkeys-devtools': minor +'@tanstack/svelte-hotkeys': minor +'@tanstack/vue-hotkeys': minor +'@tanstack/vue-hotkeys-devtools': minor +--- + +- breaking: `RawHotkey` and `ParsedHotkey` are now unions containing either `key` or `code`. Use type intersections instead of extending them with interfaces. +- breaking: Recorders default to physical codes. Set `recordBy: 'key'` to record logical keys. +- breaking: Clearing a recording calls only `onClear`, without calling `onRecord` with an empty value. +- feat: Support typed physical bindings such as `Mod+[KeyS]` and `{ code: 'KeyS', mod: true }`. +- feat: Support F1–F24, additional named keys, and more punctuation shortcuts. +- feat: Add a platform option to `parseKeyboardEvent`. +- feat: Add recorder `recordBy`, `validate`, `detectConflicts`, and `onReject` options. +- feat: Add `findHotkeyConflicts`, including sequence-prefix checks. +- feat: Add `HotkeyMeta.group`. +- feat: Add `matchesHeldModifiers` and shortcut hint helpers for every framework adapter. +- feat: Add `formatForDisplay` support for parsed bindings, `parts`, separate modifier/key symbols, `keyLabels`, and a resolved `layoutMap`. +- fix: Improve shortcut matching across keyboard layouts, macOS Option keys, shifted punctuation, and numpad keys. +- fix: Prefer exact matches over physical-key fallbacks in hotkeys and sequences. +- fix: Handle literal plus shortcuts such as `Mod++` and Unicode key names correctly. +- fix: Avoid triggering shortcuts during IME composition and AltGraph character entry. +- fix: Clear held-key and `requireReset` state correctly when released keys produce different characters. +- fix: Prevent recording keystrokes, repeats, and releases from triggering registered shortcuts. +- fix: Display readable physical-key labels, such as `S` instead of `KeyS`. +- fix: Recognize equivalent hotkey aliases in registration lookups and conflict checks, including sequences. +- fix: Avoid unnecessary registration updates and handle target changes correctly in React and Preact. +- fix: Reject bracketed physical codes in logical `key` fields. +- fix: Reset shortcuts when keys are released inside inputs or while disabled. +- fix: Respect recorder `ignoreInputs` inside shadow roots. +- fix: Preserve Shift when recording logical AltGraph shortcuts. +- fix: Ignore modifier and composition events in standalone sequence matching. +- fix: Ignore repeated keydowns when matching sequences. +- fix: Prevent continuous rerenders when reading Preact hotkey registrations. +- fix: Allow Svelte recorder controls to be passed directly to event handlers. diff --git a/docs/config.json b/docs/config.json index 8c4ce859..cebf6ddb 100644 --- a/docs/config.json +++ b/docs/config.json @@ -935,6 +935,14 @@ { "label": "IndividualKey", "to": "reference/type-aliases/IndividualKey" + }, + { + "label": "matchesHeldModifiers", + "to": "reference/functions/matchesHeldModifiers" + }, + { + "label": "HeldModifierOptions", + "to": "reference/interfaces/HeldModifierOptions" } ], "frameworks": [ @@ -952,6 +960,10 @@ { "label": "useHeldKeyCodes", "to": "framework/react/reference/functions/useHeldKeyCodes" + }, + { + "label": "useHotkeyHint", + "to": "framework/react/reference/functions/useHotkeyHint" } ] }, @@ -969,6 +981,10 @@ { "label": "useHeldKeyCodes", "to": "framework/preact/reference/functions/useHeldKeyCodes" + }, + { + "label": "useHotkeyHint", + "to": "framework/preact/reference/functions/useHotkeyHint" } ] }, @@ -986,6 +1002,10 @@ { "label": "createHeldKeyCodes", "to": "framework/solid/reference/functions/createHeldKeyCodes" + }, + { + "label": "createHotkeyHint", + "to": "framework/solid/reference/functions/createHotkeyHint" } ] }, @@ -1003,6 +1023,10 @@ { "label": "injectHeldKeyCodes", "to": "framework/angular/reference/functions/injectHeldKeyCodes" + }, + { + "label": "injectHotkeyHint", + "to": "framework/angular/reference/functions/injectHotkeyHint" } ] }, @@ -1020,6 +1044,10 @@ { "label": "useHeldKeyCodes", "to": "framework/vue/reference/functions/useHeldKeyCodes" + }, + { + "label": "useHotkeyHint", + "to": "framework/vue/reference/functions/useHotkeyHint" } ] }, @@ -1037,6 +1065,10 @@ { "label": "HeldKeyCodesController", "to": "framework/lit/reference/classes/HeldKeyCodesController" + }, + { + "label": "HotkeyHintController", + "to": "framework/lit/reference/classes/HotkeyHintController" } ] }, @@ -1066,6 +1098,10 @@ { "label": "SvelteHeldKeyCodesMap", "to": "framework/svelte/reference/interfaces/SvelteHeldKeyCodesMap" + }, + { + "label": "getHotkeyHint", + "to": "framework/svelte/reference/functions/getHotkeyHint" } ] } @@ -1087,6 +1123,34 @@ { "label": "HotkeyRecorderState", "to": "reference/interfaces/HotkeyRecorderState" + }, + { + "label": "findHotkeyConflicts", + "to": "reference/functions/findHotkeyConflicts" + }, + { + "label": "HotkeyConflictOptions", + "to": "reference/interfaces/HotkeyConflictOptions" + }, + { + "label": "HotkeyConflict", + "to": "reference/type-aliases/HotkeyConflict" + }, + { + "label": "RecorderRejection", + "to": "reference/interfaces/RecorderRejection" + }, + { + "label": "RecorderKeyMode", + "to": "reference/type-aliases/RecorderKeyMode" + }, + { + "label": "RecorderOptions", + "to": "reference/interfaces/RecorderOptions" + }, + { + "label": "HotkeyRecorderValidationContext", + "to": "reference/interfaces/HotkeyRecorderValidationContext" } ], "frameworks": [ @@ -1199,6 +1263,10 @@ { "label": "HotkeySequenceRecorderCommitKeys", "to": "reference/type-aliases/HotkeySequenceRecorderCommitKeys" + }, + { + "label": "HotkeySequenceRecorderValidationContext", + "to": "reference/interfaces/HotkeySequenceRecorderValidationContext" } ], "frameworks": [ @@ -1363,21 +1431,105 @@ { "label": "MODIFIER_KEYS", "to": "reference/variables/MODIFIER_KEYS" + }, + { + "label": "parseRegisterableHotkey", + "to": "reference/functions/parseRegisterableHotkey" + }, + { + "label": "areHotkeysEqual", + "to": "reference/functions/areHotkeysEqual" + }, + { + "label": "PhysicalKey", + "to": "reference/type-aliases/PhysicalKey" + }, + { + "label": "Hotkey", + "to": "reference/type-aliases/Hotkey" + }, + { + "label": "RegisterableHotkey", + "to": "reference/type-aliases/RegisterableHotkey" + }, + { + "label": "RawHotkey", + "to": "reference/type-aliases/RawHotkey" + }, + { + "label": "ParsedHotkey", + "to": "reference/type-aliases/ParsedHotkey" + }, + { + "label": "LogicalKey", + "to": "reference/type-aliases/LogicalKey" + }, + { + "label": "PhysicalKeyCode", + "to": "reference/type-aliases/PhysicalKeyCode" + }, + { + "label": "DisplayHotkey", + "to": "reference/type-aliases/DisplayHotkey" + }, + { + "label": "formatHotkeySequence", + "to": "reference/functions/formatHotkeySequence" + }, + { + "label": "parseHotkey", + "to": "reference/functions/parseHotkey" + }, + { + "label": "rawHotkeyToParsedHotkey", + "to": "reference/functions/rawHotkeyToParsedHotkey" + }, + { + "label": "parseKeyboardEvent", + "to": "reference/functions/parseKeyboardEvent" + }, + { + "label": "matchesKeyboardEvent", + "to": "reference/functions/matchesKeyboardEvent" + }, + { + "label": "validateHotkey", + "to": "reference/functions/validateHotkey" + }, + { + "label": "checkHotkey", + "to": "reference/functions/checkHotkey" + }, + { + "label": "assertValidHotkey", + "to": "reference/functions/assertValidHotkey" + }, + { + "label": "ValidationResult", + "to": "reference/interfaces/ValidationResult" } ] }, { "label": "Examples", - "children": [ - { - "label": "formatForDisplay", - "to": "framework/vanilla/examples/formatForDisplay" - } - ], + "children": [], "frameworks": [ + { + "label": "vanilla", + "children": [ + { + "label": "formatForDisplay", + "to": "framework/vanilla/examples/formatForDisplay" + } + ] + }, { "label": "react", "children": [ + { + "label": "Kitchen Sink", + "to": "framework/react/examples/kitchen-sink" + }, { "label": "useHotkey", "to": "framework/react/examples/useHotkey" @@ -1415,6 +1567,10 @@ { "label": "preact", "children": [ + { + "label": "Kitchen Sink", + "to": "framework/preact/examples/kitchen-sink" + }, { "label": "useHotkey", "to": "framework/preact/examples/useHotkey" @@ -1452,6 +1608,10 @@ { "label": "solid", "children": [ + { + "label": "Kitchen Sink", + "to": "framework/solid/examples/kitchen-sink" + }, { "label": "createHotkey", "to": "framework/solid/examples/createHotkey" @@ -1489,6 +1649,10 @@ { "label": "angular", "children": [ + { + "label": "Kitchen Sink", + "to": "framework/angular/examples/kitchen-sink" + }, { "label": "injectHotkey", "to": "framework/angular/examples/injectHotkey" @@ -1526,6 +1690,10 @@ { "label": "vue", "children": [ + { + "label": "Kitchen Sink", + "to": "framework/vue/examples/kitchen-sink" + }, { "label": "useHotkey", "to": "framework/vue/examples/useHotkey" @@ -1563,6 +1731,10 @@ { "label": "lit", "children": [ + { + "label": "Kitchen Sink", + "to": "framework/lit/examples/kitchen-sink" + }, { "label": "hotkey", "to": "framework/lit/examples/hotkey" @@ -1592,6 +1764,10 @@ { "label": "svelte", "children": [ + { + "label": "Kitchen Sink", + "to": "framework/svelte/examples/kitchen-sink" + }, { "label": "createHotkey", "to": "framework/svelte/examples/create-hotkey" diff --git a/docs/devtools.md b/docs/devtools.md index a6a2d90f..2a3aabb3 100644 --- a/docs/devtools.md +++ b/docs/devtools.md @@ -3,19 +3,19 @@ title: Devtools id: devtools --- -TanStack Hotkeys provides devtools for debugging and monitoring all your registered hotkeys in real-time. The devtools integrate seamlessly within the [TanStack Devtools](https://tanstack.com/devtools) multi-panel UI. +TanStack Hotkeys ships devtools for debugging and monitoring your registered hotkeys in real time, as a panel inside the [TanStack Devtools](https://tanstack.com/devtools) multi-panel UI. > [!NOTE] -> By default, the TanStack Devtools and TanStack Hotkeys Devtools will only be included in development mode. This helps keep your production bundle size minimal. If you need to include devtools in production builds (e.g., for debugging production issues), you can use the alternative "production" imports. +> By default, TanStack Devtools and the Hotkeys devtools are only included in development mode, so they add nothing to your production bundle. If you need devtools in a production build (say, to debug a production-only issue), use the alternative "production" imports. ## Features -The Hotkeys devtools panel provides: +The Hotkeys devtools panel lets you: -- **Registered Hotkeys List** - View all currently registered hotkeys with their options and status -- **Held Keys Display** - See which keys are currently being held down in real-time -- **Trigger Hotkeys** - Programmatically trigger hotkey callbacks for testing without pressing keys -- **Registration Details** - Inspect individual hotkey registrations including their target, event type, and conflict behavior +- View all currently registered hotkeys with their options and status +- See which keys are held down in real time +- Trigger hotkey callbacks for testing, without pressing the keys +- Inspect individual registrations, including their target, event type, and conflict behavior ## Installation @@ -49,7 +49,7 @@ Angular and Lit do not currently ship a dedicated hotkeys devtools adapter. ## Setup -### React Setup +### React setup ```tsx import { TanStackDevtools } from '@tanstack/react-devtools' @@ -60,7 +60,7 @@ function App() { } ``` -### Preact Setup +### Preact setup ```tsx import { TanStackDevtools } from '@tanstack/preact-devtools' @@ -71,7 +71,7 @@ export function App() { } ``` -### Solid Setup +### Solid setup ```tsx import { TanStackDevtools } from '@tanstack/solid-devtools' @@ -82,7 +82,7 @@ export function App() { } ``` -### Vue Setup +### Vue setup ```vue +`useSymbols` accepts a boolean or independent `modifiers` and `keys` settings. Omitted fields default to true. Modifier symbols apply on macOS; Windows and Linux retain modifier labels. -{formatForDisplay(hotkey)} +```ts +formatForDisplay('Shift+[ArrowUp]', { + platform: 'mac', + useSymbols: { modifiers: false, keys: true }, + parts: true, +}) // ['Shift', '↑'] + +formatForDisplay('Mod+[KeyS]', { + platform: 'mac', useSymbols: false, +}) // 'Cmd+S' + +formatForDisplay('Control++', { + platform: 'windows', separatorToken: ' · ', +}) // 'Ctrl · +' ``` -### Menu Items with Hotkeys +An empty separator joins labels directly. `undefined` or `null` uses the platform default. `formatWithLabels(binding, options)` is the shorthand for `formatForDisplay` with `useSymbols: false`. -```svelte - +```ts +const layoutMap = new Map([['KeyQ', 'a']]) - +formatForDisplay('Mod+[KeyQ]', { platform: 'mac', layoutMap }) // '⌘ A' +formatForDisplay('Mod+Q', { platform: 'mac', layoutMap }) // '⌘ Q' ``` -## Validation +Any object with `get(code): string | undefined` works, including a browser `KeyboardLayoutMap`. Formatting stays synchronous. Your app owns loading, errors, and refreshing the map: render fallback labels while loading, then pass the resolved map on the next render. The library never requests it. Logical bindings ignore `layoutMap`. + +Use `keyLabels` for explicit labels keyed by physical code or normalized logical key: ```ts -import { validateHotkey } from '@tanstack/svelte-hotkeys' +formatForDisplay('Mod+[KeyQ]', { + platform: 'mac', layoutMap, keyLabels: { KeyQ: 'Action' }, +}) // '⌘ Action' +``` + +The precedence is `keyLabels`, then a layout entry, then the fallback label. Layout entries receive normal letter casing and key symbols; missing or empty entries fall back. Explicit labels are final. All of these options affect display only. + +## Parse and store bindings + +`parseHotkey` returns either a logical `key` or a physical `code`, plus resolved modifier flags. Narrow the union before inspecting the identity: -const result = validateHotkey('Alt+A') +```ts +import { parseHotkey, normalizeHotkeyFromParsed } from '@tanstack/svelte-hotkeys' + +const parsed = parseHotkey('Mod+[KeyS]', 'mac') +if (parsed.code !== undefined) { + console.log(parsed.code) // 'KeyS'; parsed.key is undefined +} +const stored = normalizeHotkeyFromParsed(parsed, 'mac') // 'Mod+[KeyS]' +formatForDisplay(stored, { platform: 'windows' }) // 'Ctrl+S' ``` + +Parsed modifiers are already resolved. To display a portable `Mod` binding on another platform, serialize with the original platform first, as above. `normalizeRegisterableHotkey` accepts strings or raw objects and preserves the logical/physical distinction. Do not store display labels or put a bracketed code in a logical `key` field. + +Use `validateHotkey` when accepting strings from an external source. It returns `valid`, `errors`, and `warnings`; it does not guarantee that a browser or operating system will deliver the shortcut. Recorder validation and live conflict checks are covered in the [recording guide](./hotkey-recording.md#validation-and-conflicts). + +Try these options together in the [vanilla formatter playground](../../../framework/vanilla/examples/formatForDisplay). diff --git a/docs/framework/svelte/guides/hotkey-recording.md b/docs/framework/svelte/guides/hotkey-recording.md index 93069ad1..34fa87fb 100644 --- a/docs/framework/svelte/guides/hotkey-recording.md +++ b/docs/framework/svelte/guides/hotkey-recording.md @@ -5,7 +5,9 @@ id: hotkey-recording TanStack Hotkeys provides the `createHotkeyRecorder` function for building shortcut customization UIs in Svelte. -## Basic Usage +Recorders default to physical codes: recording a shortcut stores a string such as `Mod+[KeyS]`. Pass it directly to your hotkey registration and use `formatForDisplay` for the label. Set `recordBy: 'key'` when you intentionally want the produced character instead. + +## Basic usage ```svelte ``` +### Changing a binding + +Pass a new logical or physical binding through your framework's normal state mechanism. A recorder result such as `Alt+[KeyS]` can be passed directly to the same registration API. Keep an initial binding in application state if you want a reset button; the library does not need a separate preferences store. + ## Default options Set defaults explicitly with `setHotkeysContext` when a subtree needs shared behavior: @@ -96,7 +117,7 @@ Set defaults explicitly with `setHotkeysContext` when a subtree needs shared beh ``` -## Common Options +## Common options ### `requireReset` @@ -123,13 +144,13 @@ createHotkey('Mod+S', () => save(), { conflictBehavior: 'replace' }) createHotkey('Mod+S', () => save(), { platform: 'mac' }) ``` -## Automatic Cleanup +## Automatic cleanup Global hotkeys are automatically unregistered when the owning component unmounts. Attachment-based hotkeys clean themselves up when the attached element is removed or when reactive inputs change. -## Registering Multiple Hotkeys +## Registering multiple hotkeys -When you need to register several hotkeys at once — or a dynamic, variable-length list — use `createHotkeys` (plural) for global shortcuts and `createHotkeysAttachment` for element-scoped shortcuts: +When you need to register several hotkeys at once, or a dynamic, variable-length list, use `createHotkeys` (plural) for global shortcuts and `createHotkeysAttachment` for element-scoped shortcuts: ```svelte ``` -### Common Options with Per-Hotkey Overrides +### Common options with per-hotkey overrides Pass shared options as the second argument. Per-definition options override the common ones: @@ -157,7 +178,7 @@ createHotkeys( ) ``` -### Dynamic Hotkey Lists +### Dynamic hotkey lists Pass a getter for reactive arrays: @@ -176,7 +197,7 @@ Pass a getter for reactive arrays: ``` -### Scoped Multi-Hotkeys +### Scoped multi-hotkeys Use `createHotkeysAttachment` to scope multiple hotkeys to a specific element: @@ -193,9 +214,9 @@ Use `createHotkeysAttachment` to scope multiple hotkeys to a specific element:
Editor content
``` -## Metadata (name & description) +## Metadata (name, description, and group) -Every hotkey registration can carry a `meta` object with a `name` and `description`. This metadata is informational only -- it does not affect hotkey behavior -- but it flows through to registrations and devtools, making it easy to build shortcut palettes and help screens. +Every hotkey registration can carry a `meta` object with a `name`, `description`, and `group`. Metadata never affects hotkey behavior, but it flows through to registrations and devtools, so you can build shortcut palettes and help screens from it. ```ts createHotkey('Mod+S', () => save(), { @@ -203,13 +224,12 @@ createHotkey('Mod+S', () => save(), { }) ``` -The `meta` option is typed as `HotkeyMeta`, which ships with `name` and `description` fields. You can extend it with additional properties using TypeScript declaration merging: +The `meta` option is typed as `HotkeyMeta`, which ships with `name`, `description`, and `group` fields. You can extend it with additional properties using TypeScript declaration merging: ```ts declare module '@tanstack/hotkeys' { interface HotkeyMeta { icon?: string - group?: string } } @@ -218,13 +238,15 @@ createHotkey('Mod+S', () => save(), { }) ``` -## Introspecting Registrations +Group is descriptive metadata, not an execution scope. A shortcuts panel can group live registration views directly. Disabled registrations remain listed; unmounted registrations disappear. + +## Introspecting registrations Use the `getHotkeyRegistrations` function to get a live view of all hotkey and sequence registrations. This is useful for building shortcut palettes, help dialogs, or devtools. ```svelte @@ -234,12 +256,12 @@ Use the `getHotkeyRegistrations` function to get a live view of all hotkey and s