@tanstack/octane-hotkeys 0.0.0-stage → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tanner Linsley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,140 @@
1
- # Temporary Holding Version
1
+ <div align="center">
2
+ <picture>
3
+ <source
4
+ media="(prefers-color-scheme: dark)"
5
+ srcset="https://tanstack.com/api/readme/hotkeys.png?theme=dark"
6
+ />
7
+ <source
8
+ media="(prefers-color-scheme: light)"
9
+ srcset="https://tanstack.com/api/readme/hotkeys.png"
10
+ />
11
+ <img
12
+ src="https://tanstack.com/api/readme/hotkeys.png"
13
+ alt="TanStack Hotkeys"
14
+ width="900"
15
+ />
16
+ </picture>
17
+ </div>
2
18
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
19
+ <br />
20
+
21
+ <div align="center">
22
+ <a href="https://www.npmjs.com/package/@tanstack/hotkeys" target="\_parent">
23
+ <img alt="" src="https://img.shields.io/npm/dm/@tanstack/hotkeys.svg" alt="npm downloads" />
24
+ </a>
25
+ <a href="https://github.com/TanStack/hotkeys" target="\_parent">
26
+ <img alt="" src="https://img.shields.io/github/stars/TanStack/hotkeys.svg?style=social&label=Star" alt="GitHub stars" />
27
+ </a>
28
+ <a href="https://bundlephobia.com/result?p=@tanstack/react-hotkeys@latest" target="\_parent">
29
+ <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/react-hotkeys@latest" alt="Bundle size" />
30
+ </a>
31
+ </div>
32
+
33
+ <div align="center">
34
+ <a href="#badge">
35
+ <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
36
+ </a>
37
+ <a href="#badge">
38
+ <img src="https://img.shields.io/github/v/release/tanstack/hotkeys" alt="Release"/>
39
+ </a>
40
+ <a href="https://twitter.com/tan_stack">
41
+ <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
42
+ </a>
43
+ </div>
44
+
45
+ <div align="center">
46
+
47
+ ### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
48
+
49
+ </div>
50
+
51
+ # TanStack Hotkeys
52
+
53
+ > [!NOTE]
54
+ > TanStack Hotkeys is alpha. We are actively developing the library and are open to feedback and contributions.
55
+
56
+ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objects, a cross-platform `Mod` key, a singleton Hotkey Manager, and utilities for cheatsheet UIs—built to stay SSR-friendly.
57
+
58
+ - Type-safe bindings — template strings (`Mod+Shift+S`, `Escape`) or parsed objects for full control
59
+ - Flexible options — `keydown`/`keyup`, `preventDefault`, `stopPropagation`, conditional enabled, `requireReset`
60
+ - Cross-platform Mod — maps to Cmd on macOS and Ctrl on Windows/Linux
61
+ - Batteries included — validation + matching, sequences (Vim-style), key-state tracking, recorder UI helpers, framework adapters, and devtools
62
+
63
+ ### <a href="https://tanstack.com/hotkeys">Read the docs →</a>
64
+
65
+ <br />
66
+
67
+ > [!NOTE]
68
+ > You may know **TanStack Hotkeys** by our adapter names, too!
69
+ >
70
+ > - [**Alpine Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/alpine/quick-start)
71
+ > - [**Ember Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/ember/quick-start)
72
+ > - [**Octane Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/octane/quick-start)
73
+ > - [**React Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/react/react-hotkeys)
74
+ > - [**Preact Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/preact/preact-hotkeys)
75
+ > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference)
76
+ > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference)
77
+ > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference)
78
+ > - [**Lit Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/lit/reference)
79
+ > - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference)
80
+
81
+ ## Get Involved
82
+
83
+ - We welcome issues and pull requests!
84
+ - Participate in [GitHub discussions](https://github.com/TanStack/hotkeys/discussions)
85
+ - Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
86
+ - See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions
87
+
88
+ ## Partners
89
+
90
+ <div align="center">
91
+
92
+ <table align="center">
93
+ <tr>
94
+ <td>
95
+ <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
96
+ <picture>
97
+ <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-D643Zkrv.svg" height="40" />
98
+ <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-CIzGLYU_.svg" height="40" />
99
+ <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
100
+ </picture>
101
+ </a>
102
+ </td>
103
+ <td>
104
+ <a href="https://www.cloudflare.com?utm_source=tanstack">
105
+ <picture>
106
+ <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-Co-Tyjbl.svg" height="60" />
107
+ <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-6Ojsn8yh.svg" height="60" />
108
+ <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
109
+ </picture>
110
+ </a>
111
+ </td>
112
+ </tr>
113
+ </table>
114
+
115
+ <div align="center">
116
+ <img src="media/partner_logo.svg" alt="Keys & you?" height="65">
117
+ <p>
118
+ We're looking for TanStack Hotkeys Partners to join our mission! Partner with us to push the boundaries of TanStack Hotkeys and build amazing things together.
119
+ </p>
120
+ <a href="mailto:partners@tanstack.com?subject=TanStack Hotkeys Partnership"><b>LET'S CHAT</b></a>
121
+ </div>
122
+
123
+ </div>
124
+
125
+ ## Explore the TanStack Ecosystem
126
+
127
+ - <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
128
+ - <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
129
+ - <a href="https://github.com/tanstack/devtools"><b>TanStack DevTools</b></a> – Unified devtools panel
130
+ - <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
131
+ - <a href="https://github.com/tanstack/hotkeys"><b>TanStack Hotkeys</b></a> – Type‑safe keyboard shortcuts
132
+ - <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
133
+ - <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
134
+ - <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
135
+ - <a href="https://github.com/tanstack/start"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
136
+ - <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
137
+ - <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
138
+ - <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering
139
+
140
+ … and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>
@@ -0,0 +1,25 @@
1
+ import { HotkeyOptions, HotkeyRecorderOptions, HotkeySequenceRecorderOptions, SequenceOptions } from "@tanstack/hotkeys";
2
+ //#region src/HotkeysProvider.d.ts
3
+ export interface DefaultHotkeysOptions {
4
+ hotkey?: HotkeyOptions;
5
+ hotkeySequence?: SequenceOptions;
6
+ hotkeyRecorder?: Partial<HotkeyRecorderOptions>;
7
+ hotkeySequenceRecorder?: Partial<HotkeySequenceRecorderOptions>;
8
+ }
9
+ export type HotkeysProviderOptions = DefaultHotkeysOptions;
10
+ export interface HotkeysProviderProps {
11
+ children?: unknown;
12
+ defaultOptions?: HotkeysProviderOptions;
13
+ }
14
+ /** Reads the nearest provider, or null outside a provider. */
15
+ export declare function useHotkeysContext(): {
16
+ defaultOptions: DefaultHotkeysOptions;
17
+ } | null;
18
+ /** Reads the nearest provider's defaults, or an empty object. */
19
+ export declare function useDefaultHotkeysOptions(): DefaultHotkeysOptions;
20
+ /** Sets registration and recorder defaults for descendant components. */
21
+ export declare function HotkeysProvider({ children, defaultOptions }: HotkeysProviderProps): import("octane").ElementDescriptor<{
22
+ value: DefaultHotkeysOptions;
23
+ children: unknown;
24
+ }>;
25
+ //#endregion
@@ -0,0 +1,23 @@
1
+ import { createContext, createElement, useContext } from "octane";
2
+
3
+ //#region src/HotkeysProvider.ts
4
+ const HotkeysContext = createContext(null);
5
+ /** Reads the nearest provider, or null outside a provider. */
6
+ function useHotkeysContext() {
7
+ const defaultOptions = useContext(HotkeysContext);
8
+ return defaultOptions === null ? null : { defaultOptions };
9
+ }
10
+ /** Reads the nearest provider's defaults, or an empty object. */
11
+ function useDefaultHotkeysOptions() {
12
+ return useContext(HotkeysContext) ?? {};
13
+ }
14
+ /** Sets registration and recorder defaults for descendant components. */
15
+ function HotkeysProvider({ children, defaultOptions = {} }) {
16
+ return createElement(HotkeysContext.Provider, {
17
+ value: defaultOptions,
18
+ children
19
+ });
20
+ }
21
+
22
+ //#endregion
23
+ export { HotkeysProvider, useDefaultHotkeysOptions, useHotkeysContext };
@@ -0,0 +1,16 @@
1
+ import { DefaultHotkeysOptions, HotkeysProvider, HotkeysProviderOptions, HotkeysProviderProps, useDefaultHotkeysOptions, useHotkeysContext } from "./HotkeysProvider.js";
2
+ import { useHotkeys } from "./useHotkeys.js";
3
+ import { useHotkey } from "./useHotkey.js";
4
+ import { useHotkeySequences } from "./useHotkeySequences.js";
5
+ import { useHotkeySequence } from "./useHotkeySequence.js";
6
+ import { useHeldKeys } from "./useHeldKeys.js";
7
+ import { useHeldKeyCodes } from "./useHeldKeyCodes.js";
8
+ import { useKeyHold } from "./useKeyHold.js";
9
+ import { useHotkeyHint } from "./useHotkeyHint.js";
10
+ import { HotkeyRegistrationsResult, OctaneHotkeyRecorder, OctaneHotkeySequenceRecorder } from "./types.js";
11
+ import { useHotkeyRegistrations } from "./useHotkeyRegistrations.js";
12
+ import { useHotkeyRecorder } from "./useHotkeyRecorder.js";
13
+ import { useHotkeySequenceRecorder } from "./useHotkeySequenceRecorder.js";
14
+ import { HotkeyDefinition, HotkeySequenceDefinition } from "@tanstack/hotkeys/adapter";
15
+ export * from "@tanstack/hotkeys";
16
+ export { type DefaultHotkeysOptions, type HotkeyDefinition, HotkeyRegistrationsResult, type HotkeySequenceDefinition, HotkeysProvider, type HotkeysProviderOptions, type HotkeysProviderProps, OctaneHotkeyRecorder, OctaneHotkeySequenceRecorder, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations, useHotkeySequence, useHotkeySequenceRecorder, useHotkeySequences, useHotkeys, useHotkeysContext, useKeyHold };
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import { HotkeysProvider, useDefaultHotkeysOptions, useHotkeysContext } from "./HotkeysProvider.js";
2
+ import { useHotkeys } from "./useHotkeys.js";
3
+ import { useHotkey } from "./useHotkey.js";
4
+ import { useHotkeySequences } from "./useHotkeySequences.js";
5
+ import { useHotkeySequence } from "./useHotkeySequence.js";
6
+ import { useHeldKeys } from "./useHeldKeys.js";
7
+ import { useHeldKeyCodes } from "./useHeldKeyCodes.js";
8
+ import { useKeyHold } from "./useKeyHold.js";
9
+ import { useHotkeyHint } from "./useHotkeyHint.js";
10
+ import { useHotkeyRegistrations } from "./useHotkeyRegistrations.js";
11
+ import { useHotkeyRecorder } from "./useHotkeyRecorder.js";
12
+ import { useHotkeySequenceRecorder } from "./useHotkeySequenceRecorder.js";
13
+
14
+ export * from "@tanstack/hotkeys"
15
+
16
+ export { HotkeysProvider, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations, useHotkeySequence, useHotkeySequenceRecorder, useHotkeySequences, useHotkeys, useHotkeysContext, useKeyHold };
@@ -0,0 +1,35 @@
1
+ import { useSelector } from "@tanstack/octane-store";
2
+
3
+ //#region src/internal.ts
4
+ const subSlotCache = /* @__PURE__ */ new Map();
5
+ const bareTagCache = /* @__PURE__ */ new Map();
6
+ function subSlot(slot, tag) {
7
+ if (slot === void 0) {
8
+ let bare = bareTagCache.get(tag);
9
+ if (bare === void 0) {
10
+ bare = Symbol.for(`@tanstack/octane-hotkeys:${tag}`);
11
+ bareTagCache.set(tag, bare);
12
+ }
13
+ return bare;
14
+ }
15
+ let byTag = subSlotCache.get(slot);
16
+ if (byTag === void 0) {
17
+ byTag = /* @__PURE__ */ new Map();
18
+ subSlotCache.set(slot, byTag);
19
+ }
20
+ let child = byTag.get(tag);
21
+ if (child === void 0) {
22
+ child = Symbol.for(`${slot.description ?? ""}:${tag}`);
23
+ byTag.set(tag, child);
24
+ }
25
+ return child;
26
+ }
27
+ function splitSlot(args) {
28
+ const tail = args[args.length - 1];
29
+ const slot = typeof tail === "symbol" ? tail : void 0;
30
+ return [slot === void 0 ? args : args.slice(0, -1), slot];
31
+ }
32
+ const select = useSelector;
33
+
34
+ //#endregion
35
+ export { select, splitSlot, subSlot };
@@ -0,0 +1,38 @@
1
+ import { Hotkey, HotkeyRegistrationView, HotkeySequence, SequenceRegistrationView } from "@tanstack/hotkeys";
2
+ //#region src/types.d.ts
3
+ /** Recording state and controls owned by the containing component. */
4
+ export interface OctaneHotkeyRecorder {
5
+ /** Whether the recorder is listening for a shortcut. */
6
+ isRecording: boolean;
7
+ /** The last recorded binding, including brackets for physical codes. */
8
+ recordedHotkey: Hotkey | null;
9
+ /** Start a new recording session. */
10
+ startRecording: () => void;
11
+ /** Stop and clear recorder state without calling onRecord or onCancel. */
12
+ stopRecording: () => void;
13
+ /** Stop, clear recorder state, and call onCancel. */
14
+ cancelRecording: () => void;
15
+ }
16
+ /** Sequence recording state and controls. */
17
+ export interface OctaneHotkeySequenceRecorder {
18
+ /** Whether the recorder is listening for chords. */
19
+ isRecording: boolean;
20
+ /** Chords captured in the current session. */
21
+ steps: HotkeySequence;
22
+ /** The last committed sequence. */
23
+ recordedSequence: HotkeySequence | null;
24
+ /** Start a new recording session. */
25
+ startRecording: () => void;
26
+ /** Stop without committing or calling onCancel. */
27
+ stopRecording: () => void;
28
+ /** Discard the current session and call onCancel. */
29
+ cancelRecording: () => void;
30
+ /** Commit the current steps. Does nothing when no steps are recorded. */
31
+ commitRecording: () => void;
32
+ }
33
+ /** Live registration views from the shared hotkey and sequence managers. */
34
+ export interface HotkeyRegistrationsResult {
35
+ hotkeys: Array<HotkeyRegistrationView>;
36
+ sequences: Array<SequenceRegistrationView>;
37
+ }
38
+ //#endregion
@@ -0,0 +1,4 @@
1
+ //#region src/useHeldKeyCodes.d.ts
2
+ /** Reads held physical key codes from the shared key tracker. */
3
+ export declare function useHeldKeyCodes(): Record<string, string>;
4
+ //#endregion
@@ -0,0 +1,10 @@
1
+ import { select, subSlot } from "./internal.js";
2
+ import { getKeyStateTracker } from "@tanstack/hotkeys";
3
+
4
+ //#region src/useHeldKeyCodes.ts
5
+ function useHeldKeyCodes(slot) {
6
+ return select(getKeyStateTracker().store, (state) => state.heldCodes, void 0, subSlot(slot, "held-codes"));
7
+ }
8
+
9
+ //#endregion
10
+ export { useHeldKeyCodes };
@@ -0,0 +1,4 @@
1
+ //#region src/useHeldKeys.d.ts
2
+ /** Reads held logical keys from the shared key tracker. */
3
+ export declare function useHeldKeys(): Array<string>;
4
+ //#endregion
@@ -0,0 +1,10 @@
1
+ import { select, subSlot } from "./internal.js";
2
+ import { getKeyStateTracker } from "@tanstack/hotkeys";
3
+
4
+ //#region src/useHeldKeys.ts
5
+ function useHeldKeys(slot) {
6
+ return select(getKeyStateTracker().store, (state) => state.heldKeys, void 0, subSlot(slot, "held-keys"));
7
+ }
8
+
9
+ //#endregion
10
+ export { useHeldKeys };
@@ -0,0 +1,5 @@
1
+ import { HotkeyCallback, HotkeyOptions, RegisterableHotkey } from "@tanstack/hotkeys";
2
+ //#region src/useHotkey.d.ts
3
+ /** Registers one shortcut. Callback and options are refreshed after every commit. */
4
+ export declare function useHotkey(hotkey: RegisterableHotkey, callback: HotkeyCallback, options?: HotkeyOptions): void;
5
+ //#endregion
@@ -0,0 +1,14 @@
1
+ import { splitSlot, subSlot } from "./internal.js";
2
+ import { useHotkeys } from "./useHotkeys.js";
3
+
4
+ //#region src/useHotkey.ts
5
+ function useHotkey(hotkey, callback, ...rest) {
6
+ const [args, slot] = splitSlot(rest);
7
+ useHotkeys([{
8
+ hotkey,
9
+ callback
10
+ }], args[0], subSlot(slot, "hotkey"));
11
+ }
12
+
13
+ //#endregion
14
+ export { useHotkey };
@@ -0,0 +1,5 @@
1
+ import { HeldModifierOptions, RegisterableHotkey } from "@tanstack/hotkeys";
2
+ //#region src/useHotkeyHint.d.ts
3
+ /** Reports whether held modifiers reveal a shortcut hint. */
4
+ export declare function useHotkeyHint(hotkey: RegisterableHotkey, options?: HeldModifierOptions): boolean;
5
+ //#endregion
@@ -0,0 +1,11 @@
1
+ import { select, splitSlot, subSlot } from "./internal.js";
2
+ import { getKeyStateTracker, matchesHeldModifiers } from "@tanstack/hotkeys";
3
+
4
+ //#region src/useHotkeyHint.ts
5
+ function useHotkeyHint(hotkey, ...rest) {
6
+ const [args, slot] = splitSlot(rest);
7
+ return select(getKeyStateTracker().store, (state) => matchesHeldModifiers(hotkey, state.heldKeys, args[0]), void 0, subSlot(slot, "hint"));
8
+ }
9
+
10
+ //#endregion
11
+ export { useHotkeyHint };
@@ -0,0 +1,6 @@
1
+ import { OctaneHotkeyRecorder } from "./types.js";
2
+ import { HotkeyRecorderOptions } from "@tanstack/hotkeys";
3
+ //#region src/useHotkeyRecorder.d.ts
4
+ /** Records a shortcut and destroys the recorder on unmount. */
5
+ export declare function useHotkeyRecorder(options: HotkeyRecorderOptions): OctaneHotkeyRecorder;
6
+ //#endregion
@@ -0,0 +1,28 @@
1
+ import { useDefaultHotkeysOptions } from "./HotkeysProvider.js";
2
+ import { select, subSlot } from "./internal.js";
3
+ import { HotkeyRecorder } from "@tanstack/hotkeys";
4
+ import { useLayoutEffect, useRef } from "octane";
5
+
6
+ //#region src/useHotkeyRecorder.ts
7
+ function useHotkeyRecorder(options, slot) {
8
+ const merged = {
9
+ ...useDefaultHotkeysOptions().hotkeyRecorder,
10
+ ...options
11
+ };
12
+ const ref = useRef(null, subSlot(slot, "recorder"));
13
+ ref.current ??= new HotkeyRecorder(merged);
14
+ const recorder = ref.current;
15
+ useLayoutEffect(() => {
16
+ recorder.setOptions(merged);
17
+ }, null, subSlot(slot, "options"));
18
+ useLayoutEffect(() => () => recorder.destroy(), [], subSlot(slot, "cleanup"));
19
+ return {
20
+ ...select(recorder.store, (value) => value, void 0, subSlot(slot, "state")),
21
+ startRecording: () => recorder.start(),
22
+ stopRecording: () => recorder.stop(),
23
+ cancelRecording: () => recorder.cancel()
24
+ };
25
+ }
26
+
27
+ //#endregion
28
+ export { useHotkeyRecorder };
@@ -0,0 +1,5 @@
1
+ import { HotkeyRegistrationsResult } from "./types.js";
2
+ //#region src/useHotkeyRegistrations.d.ts
3
+ /** Reads public registration snapshots from both managers. */
4
+ export declare function useHotkeyRegistrations(): HotkeyRegistrationsResult;
5
+ //#endregion
@@ -0,0 +1,13 @@
1
+ import { select, subSlot } from "./internal.js";
2
+ import { getHotkeyManager, getSequenceManager, toHotkeyRegistrationView } from "@tanstack/hotkeys";
3
+
4
+ //#region src/useHotkeyRegistrations.ts
5
+ function useHotkeyRegistrations(slot) {
6
+ return {
7
+ hotkeys: select(getHotkeyManager().registrations, (state) => Array.from(state.values()).map(toHotkeyRegistrationView), void 0, subSlot(slot, "hotkeys")),
8
+ sequences: select(getSequenceManager().registrations, (state) => Array.from(state.values()), void 0, subSlot(slot, "sequences"))
9
+ };
10
+ }
11
+
12
+ //#endregion
13
+ export { useHotkeyRegistrations };
@@ -0,0 +1,5 @@
1
+ import { HotkeyCallback, HotkeySequence, SequenceOptions } from "@tanstack/hotkeys";
2
+ //#region src/useHotkeySequence.d.ts
3
+ /** Registers one multi-chord shortcut. Empty sequences do not register. */
4
+ export declare function useHotkeySequence(sequence: HotkeySequence, callback: HotkeyCallback, options?: SequenceOptions): void;
5
+ //#endregion
@@ -0,0 +1,14 @@
1
+ import { splitSlot, subSlot } from "./internal.js";
2
+ import { useHotkeySequences } from "./useHotkeySequences.js";
3
+
4
+ //#region src/useHotkeySequence.ts
5
+ function useHotkeySequence(sequence, callback, ...rest) {
6
+ const [args, slot] = splitSlot(rest);
7
+ useHotkeySequences([{
8
+ sequence,
9
+ callback
10
+ }], args[0], subSlot(slot, "sequence"));
11
+ }
12
+
13
+ //#endregion
14
+ export { useHotkeySequence };
@@ -0,0 +1,6 @@
1
+ import { OctaneHotkeySequenceRecorder } from "./types.js";
2
+ import { HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
3
+ //#region src/useHotkeySequenceRecorder.d.ts
4
+ /** Records a sequence, including live steps and explicit commit/cancel controls. */
5
+ export declare function useHotkeySequenceRecorder(options: HotkeySequenceRecorderOptions): OctaneHotkeySequenceRecorder;
6
+ //#endregion
@@ -0,0 +1,29 @@
1
+ import { useDefaultHotkeysOptions } from "./HotkeysProvider.js";
2
+ import { select, subSlot } from "./internal.js";
3
+ import { HotkeySequenceRecorder } from "@tanstack/hotkeys";
4
+ import { useLayoutEffect, useRef } from "octane";
5
+
6
+ //#region src/useHotkeySequenceRecorder.ts
7
+ function useHotkeySequenceRecorder(options, slot) {
8
+ const merged = {
9
+ ...useDefaultHotkeysOptions().hotkeySequenceRecorder,
10
+ ...options
11
+ };
12
+ const ref = useRef(null, subSlot(slot, "recorder"));
13
+ ref.current ??= new HotkeySequenceRecorder(merged);
14
+ const recorder = ref.current;
15
+ useLayoutEffect(() => {
16
+ recorder.setOptions(merged);
17
+ }, null, subSlot(slot, "options"));
18
+ useLayoutEffect(() => () => recorder.destroy(), [], subSlot(slot, "cleanup"));
19
+ return {
20
+ ...select(recorder.store, (value) => value, void 0, subSlot(slot, "state")),
21
+ startRecording: () => recorder.start(),
22
+ stopRecording: () => recorder.stop(),
23
+ cancelRecording: () => recorder.cancel(),
24
+ commitRecording: () => recorder.commit()
25
+ };
26
+ }
27
+
28
+ //#endregion
29
+ export { useHotkeySequenceRecorder };
@@ -0,0 +1,6 @@
1
+ import { SequenceOptions } from "@tanstack/hotkeys";
2
+ import { HotkeySequenceDefinition } from "@tanstack/hotkeys/adapter";
3
+ //#region src/useHotkeySequences.d.ts
4
+ /** Registers a changing list of sequences and releases it on unmount. */
5
+ export declare function useHotkeySequences(definitions: Array<HotkeySequenceDefinition>, options?: SequenceOptions): void;
6
+ //#endregion
@@ -0,0 +1,23 @@
1
+ import { useDefaultHotkeysOptions } from "./HotkeysProvider.js";
2
+ import { splitSlot, subSlot } from "./internal.js";
3
+ import { useLayoutEffect, useRef } from "octane";
4
+ import { createHotkeySequenceBindings } from "@tanstack/hotkeys/adapter";
5
+
6
+ //#region src/useHotkeySequences.ts
7
+ function useHotkeySequences(definitions, ...rest) {
8
+ const [args, slot] = splitSlot(rest);
9
+ const options = {
10
+ ...useDefaultHotkeysOptions().hotkeySequence,
11
+ ...args[0]
12
+ };
13
+ const ref = useRef(null, subSlot(slot, "bindings"));
14
+ ref.current ??= createHotkeySequenceBindings();
15
+ const bindings = ref.current;
16
+ useLayoutEffect(() => {
17
+ bindings.update(definitions, options);
18
+ }, null, subSlot(slot, "update"));
19
+ useLayoutEffect(() => () => bindings.destroy(), [], subSlot(slot, "cleanup"));
20
+ }
21
+
22
+ //#endregion
23
+ export { useHotkeySequences };
@@ -0,0 +1,6 @@
1
+ import { HotkeyOptions } from "@tanstack/hotkeys";
2
+ import { HotkeyDefinition } from "@tanstack/hotkeys/adapter";
3
+ //#region src/useHotkeys.d.ts
4
+ /** Registers a changing list of shortcuts after commit and releases it on unmount. */
5
+ export declare function useHotkeys(definitions: Array<HotkeyDefinition>, options?: HotkeyOptions): void;
6
+ //#endregion
@@ -0,0 +1,23 @@
1
+ import { useDefaultHotkeysOptions } from "./HotkeysProvider.js";
2
+ import { splitSlot, subSlot } from "./internal.js";
3
+ import { useLayoutEffect, useRef } from "octane";
4
+ import { createHotkeyBindings } from "@tanstack/hotkeys/adapter";
5
+
6
+ //#region src/useHotkeys.ts
7
+ function useHotkeys(definitions, ...rest) {
8
+ const [args, slot] = splitSlot(rest);
9
+ const options = {
10
+ ...useDefaultHotkeysOptions().hotkey,
11
+ ...args[0]
12
+ };
13
+ const ref = useRef(null, subSlot(slot, "bindings"));
14
+ ref.current ??= createHotkeyBindings();
15
+ const bindings = ref.current;
16
+ useLayoutEffect(() => {
17
+ bindings.update(definitions, options);
18
+ }, null, subSlot(slot, "update"));
19
+ useLayoutEffect(() => () => bindings.destroy(), [], subSlot(slot, "cleanup"));
20
+ }
21
+
22
+ //#endregion
23
+ export { useHotkeys };
@@ -0,0 +1,5 @@
1
+ import { IndividualKey } from "@tanstack/hotkeys";
2
+ //#region src/useKeyHold.d.ts
3
+ /** Reports whether a logical key is held. */
4
+ export declare function useKeyHold(key: IndividualKey): boolean;
5
+ //#endregion
@@ -0,0 +1,10 @@
1
+ import { select, subSlot } from "./internal.js";
2
+ import { getKeyStateTracker } from "@tanstack/hotkeys";
3
+
4
+ //#region src/useKeyHold.ts
5
+ function useKeyHold(key, slot) {
6
+ return select(getKeyStateTracker().store, (state) => state.heldKeys.includes(key), void 0, subSlot(slot, "key-hold"));
7
+ }
8
+
9
+ //#endregion
10
+ export { useKeyHold };
package/package.json CHANGED
@@ -1,6 +1,65 @@
1
1
  {
2
2
  "name": "@tanstack/octane-hotkeys",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Octane adapter for TanStack Hotkeys",
5
+ "author": "Tanner Linsley",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/TanStack/hotkeys.git",
10
+ "directory": "packages/octane-hotkeys"
11
+ },
12
+ "homepage": "https://tanstack.com/hotkeys",
13
+ "funding": {
14
+ "type": "github",
15
+ "url": "https://github.com/sponsors/tannerlinsley"
16
+ },
17
+ "keywords": [
18
+ "octane",
19
+ "tanstack",
20
+ "hotkeys"
21
+ ],
22
+ "type": "module",
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": "./dist/index.js",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "sideEffects": false,
29
+ "engines": {
30
+ "node": ">=22.22.2"
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "dependencies": {
36
+ "@tanstack/hotkeys": "0.11.0",
37
+ "@tanstack/octane-store": "^0.12.3"
38
+ },
39
+ "devDependencies": {
40
+ "@tsrx/typescript-plugin": "^0.3.138",
41
+ "octane": "0.1.36",
42
+ "vite": "^8.3.2"
43
+ },
44
+ "peerDependencies": {
45
+ "octane": ">=0.1.36 <0.2.0"
46
+ },
47
+ "octane": {
48
+ "hookSlots": {
49
+ "manual": [
50
+ "dist"
51
+ ]
52
+ }
53
+ },
54
+ "scripts": {
55
+ "clean": "premove ./build ./dist",
56
+ "lint": "eslint ./src",
57
+ "lint:fix": "eslint ./src --fix",
58
+ "test:eslint": "eslint ./src",
59
+ "test:lib": "vitest run && vitest run --config vitest.ssr.config.ts",
60
+ "test:lib:dev": "vitest --watch",
61
+ "test:types": "tsrx-tsc --noEmit",
62
+ "build": "tsdown",
63
+ "test:build": "publint --strict && node ../../scripts/verify-package.ts"
64
+ }
6
65
  }