@tanstack/preact-hotkeys 0.13.0 → 0.14.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/dist/HotkeysProvider.d.ts +40 -9
- package/dist/HotkeysProvider.js +38 -4
- package/dist/_appHotkeysStore.js +17 -0
- package/dist/index.d.ts +6 -8
- package/dist/index.js +6 -8
- package/dist/useAppHotkeys.d.ts +71 -0
- package/dist/useAppHotkeys.js +79 -0
- package/dist/useHotkey.d.ts +45 -51
- package/dist/useHotkey.js +7 -119
- package/dist/useHotkeyHint.d.ts +6 -4
- package/dist/useHotkeyHint.js +9 -4
- package/dist/useHotkeyRecorder.d.ts +34 -30
- package/dist/useHotkeyRecorder.js +7 -41
- package/dist/useHotkeyRegistrations.d.ts +24 -17
- package/dist/useHotkeyRegistrations.js +24 -16
- package/dist/useHotkeys.d.ts +17 -26
- package/dist/useHotkeys.js +38 -79
- package/dist/utils.js +8 -1
- package/package.json +4 -4
- package/dist/useHotkeySequence.d.ts +0 -56
- package/dist/useHotkeySequence.js +0 -119
- package/dist/useHotkeySequenceRecorder.d.ts +0 -16
- package/dist/useHotkeySequenceRecorder.js +0 -38
- package/dist/useHotkeySequences.d.ts +0 -60
- package/dist/useHotkeySequences.js +0 -125
|
@@ -1,54 +1,58 @@
|
|
|
1
|
-
import { Hotkey,
|
|
1
|
+
import { Hotkey, HotkeyChord, HotkeyChordRecorderOptions, HotkeySequence, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
2
2
|
//#region src/useHotkeyRecorder.d.ts
|
|
3
|
-
export interface PreactHotkeyRecorder {
|
|
3
|
+
export interface PreactHotkeyRecorder<THotkey extends Hotkey = Hotkey> {
|
|
4
4
|
/** Whether recording is currently active */
|
|
5
5
|
isRecording: boolean;
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/** Chords recorded so far in a multi-step recording (for live preview) */
|
|
7
|
+
steps: HotkeySequence;
|
|
8
|
+
/** The last committed hotkey */
|
|
9
|
+
recordedHotkey: THotkey | null;
|
|
8
10
|
/** Start recording a new hotkey */
|
|
9
11
|
startRecording: () => void;
|
|
10
|
-
/** Stop recording
|
|
12
|
+
/** Stop recording without calling onCancel */
|
|
11
13
|
stopRecording: () => void;
|
|
12
|
-
/** Cancel recording
|
|
14
|
+
/** Cancel recording and call onCancel */
|
|
13
15
|
cancelRecording: () => void;
|
|
16
|
+
/** Commit the recorded steps now (multi-step recordings) */
|
|
17
|
+
commitRecording: () => void;
|
|
14
18
|
}
|
|
15
19
|
/**
|
|
16
20
|
* Preact hook for recording keyboard shortcuts.
|
|
17
21
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
+
* With the default `maxSteps: 1`, the first valid chord is recorded and
|
|
23
|
+
* `onRecord` receives a chord such as `'Mod+S'`. With a larger `maxSteps`, each
|
|
24
|
+
* chord adds a step; the recording commits on Enter (unless
|
|
25
|
+
* `commitKeys: 'none'`), after `idleTimeoutMs`, or when it reaches `maxSteps`,
|
|
26
|
+
* and `onRecord` receives a chord (one step) or a sequence.
|
|
27
|
+
*
|
|
28
|
+
* Escape cancels. Backspace/Delete removes the last step, or calls `onClear`
|
|
29
|
+
* when nothing is recorded.
|
|
22
30
|
*
|
|
23
31
|
* @param options - Configuration options for the recorder
|
|
24
|
-
* @returns
|
|
32
|
+
* @returns The recorder state and controls
|
|
25
33
|
*
|
|
26
34
|
* @example
|
|
27
35
|
* ```tsx
|
|
28
|
-
* function
|
|
36
|
+
* function ShortcutInput() {
|
|
29
37
|
* const [shortcut, setShortcut] = useState<Hotkey>('Mod+S')
|
|
30
|
-
*
|
|
31
|
-
* const recorder = useHotkeyRecorder({
|
|
32
|
-
* onRecord: (hotkey) => {
|
|
33
|
-
* setShortcut(hotkey)
|
|
34
|
-
* },
|
|
35
|
-
* onCancel: () => {
|
|
36
|
-
* console.log('Recording cancelled')
|
|
37
|
-
* },
|
|
38
|
-
* })
|
|
38
|
+
* const recorder = useHotkeyRecorder({ onRecord: setShortcut })
|
|
39
39
|
*
|
|
40
40
|
* return (
|
|
41
|
-
* <
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* </button>
|
|
45
|
-
* {recorder.recordedHotkey && (
|
|
46
|
-
* <div>Recording: {recorder.recordedHotkey}</div>
|
|
47
|
-
* )}
|
|
48
|
-
* </div>
|
|
41
|
+
* <button onClick={recorder.startRecording}>
|
|
42
|
+
* {recorder.isRecording ? 'Press keys…' : formatForDisplay(shortcut)}
|
|
43
|
+
* </button>
|
|
49
44
|
* )
|
|
50
45
|
* }
|
|
51
46
|
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* const recorder = useHotkeyRecorder({
|
|
51
|
+
* maxSteps: 3,
|
|
52
|
+
* onRecord: (hotkey) => setShortcut(hotkey), // 'Mod+S' or ['G', 'H']
|
|
53
|
+
* })
|
|
54
|
+
* ```
|
|
52
55
|
*/
|
|
53
|
-
export declare function useHotkeyRecorder(options:
|
|
56
|
+
export declare function useHotkeyRecorder(options: HotkeyChordRecorderOptions): PreactHotkeyRecorder<HotkeyChord>;
|
|
57
|
+
export declare function useHotkeyRecorder(options: HotkeySequenceRecorderOptions): PreactHotkeyRecorder;
|
|
54
58
|
//#endregion
|
|
@@ -4,44 +4,6 @@ import { useEffect, useRef } from "preact/hooks";
|
|
|
4
4
|
import { useSelector } from "@tanstack/preact-store";
|
|
5
5
|
|
|
6
6
|
//#region src/useHotkeyRecorder.ts
|
|
7
|
-
/**
|
|
8
|
-
* Preact hook for recording keyboard shortcuts.
|
|
9
|
-
*
|
|
10
|
-
* This hook provides a thin wrapper around the framework-agnostic `HotkeyRecorder`
|
|
11
|
-
* class, managing all the complexity of capturing keyboard events, converting them
|
|
12
|
-
* to hotkey strings, and handling edge cases like Escape to cancel or Backspace/Delete
|
|
13
|
-
* to clear.
|
|
14
|
-
*
|
|
15
|
-
* @param options - Configuration options for the recorder
|
|
16
|
-
* @returns An object with recording state and control functions
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```tsx
|
|
20
|
-
* function ShortcutSettings() {
|
|
21
|
-
* const [shortcut, setShortcut] = useState<Hotkey>('Mod+S')
|
|
22
|
-
*
|
|
23
|
-
* const recorder = useHotkeyRecorder({
|
|
24
|
-
* onRecord: (hotkey) => {
|
|
25
|
-
* setShortcut(hotkey)
|
|
26
|
-
* },
|
|
27
|
-
* onCancel: () => {
|
|
28
|
-
* console.log('Recording cancelled')
|
|
29
|
-
* },
|
|
30
|
-
* })
|
|
31
|
-
*
|
|
32
|
-
* return (
|
|
33
|
-
* <div>
|
|
34
|
-
* <button onClick={recorder.startRecording}>
|
|
35
|
-
* {recorder.isRecording ? 'Recording...' : 'Edit Shortcut'}
|
|
36
|
-
* </button>
|
|
37
|
-
* {recorder.recordedHotkey && (
|
|
38
|
-
* <div>Recording: {recorder.recordedHotkey}</div>
|
|
39
|
-
* )}
|
|
40
|
-
* </div>
|
|
41
|
-
* )
|
|
42
|
-
* }
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
7
|
function useHotkeyRecorder(options) {
|
|
46
8
|
const mergedOptions = {
|
|
47
9
|
...useDefaultHotkeysOptions().hotkeyRecorder,
|
|
@@ -50,8 +12,10 @@ function useHotkeyRecorder(options) {
|
|
|
50
12
|
const recorderRef = useRef(null);
|
|
51
13
|
if (!recorderRef.current) recorderRef.current = new HotkeyRecorder(mergedOptions);
|
|
52
14
|
recorderRef.current.setOptions(mergedOptions);
|
|
53
|
-
const
|
|
54
|
-
const
|
|
15
|
+
const recorder = recorderRef.current;
|
|
16
|
+
const isRecording = useSelector(recorder.store, (state) => state.isRecording);
|
|
17
|
+
const steps = useSelector(recorder.store, (state) => state.steps);
|
|
18
|
+
const recordedHotkey = useSelector(recorder.store, (state) => state.recordedHotkey);
|
|
55
19
|
useEffect(() => {
|
|
56
20
|
return () => {
|
|
57
21
|
recorderRef.current?.destroy();
|
|
@@ -59,10 +23,12 @@ function useHotkeyRecorder(options) {
|
|
|
59
23
|
}, []);
|
|
60
24
|
return {
|
|
61
25
|
isRecording,
|
|
26
|
+
steps,
|
|
62
27
|
recordedHotkey,
|
|
63
28
|
startRecording: () => recorderRef.current?.start(),
|
|
64
29
|
stopRecording: () => recorderRef.current?.stop(),
|
|
65
|
-
cancelRecording: () => recorderRef.current?.cancel()
|
|
30
|
+
cancelRecording: () => recorderRef.current?.cancel(),
|
|
31
|
+
commitRecording: () => recorderRef.current?.commit()
|
|
66
32
|
};
|
|
67
33
|
}
|
|
68
34
|
|
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
import { HotkeyRegistrationView
|
|
1
|
+
import { HotkeyRegistrationView } from "@tanstack/hotkeys";
|
|
2
2
|
//#region src/useHotkeyRegistrations.d.ts
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Preact hook that reactively reads all hotkey and sequence registrations
|
|
14
|
-
* from the singleton managers.
|
|
4
|
+
* Preact hook that reactively reads every hotkey registration, chords and
|
|
5
|
+
* sequences, from the singleton HotkeyManager. Narrow each view with `kind`.
|
|
6
|
+
*
|
|
7
|
+
* This hook does NOT require the HotkeysProvider. It re-renders when
|
|
8
|
+
* registrations change, including trigger counts and sequence progress.
|
|
9
|
+
*
|
|
10
|
+
* @returns All registrations, without callbacks
|
|
15
11
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* function ShortcutPalette() {
|
|
15
|
+
* const registrations = useHotkeyRegistrations()
|
|
19
16
|
*
|
|
20
|
-
*
|
|
17
|
+
* return (
|
|
18
|
+
* <ul>
|
|
19
|
+
* {registrations.map((reg) => (
|
|
20
|
+
* <li key={reg.id}>
|
|
21
|
+
* {reg.options.meta?.name}: {formatForDisplay(reg.hotkey)}
|
|
22
|
+
* </li>
|
|
23
|
+
* ))}
|
|
24
|
+
* </ul>
|
|
25
|
+
* )
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
21
28
|
*/
|
|
22
|
-
export declare function useHotkeyRegistrations():
|
|
29
|
+
export declare function useHotkeyRegistrations(): Array<HotkeyRegistrationView>;
|
|
23
30
|
//#endregion
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
import { getHotkeyManager,
|
|
2
|
-
import { useMemo } from "preact/hooks";
|
|
1
|
+
import { getHotkeyManager, toHotkeyRegistrationView } from "@tanstack/hotkeys";
|
|
3
2
|
import { useSelector } from "@tanstack/preact-store";
|
|
4
3
|
|
|
5
4
|
//#region src/useHotkeyRegistrations.ts
|
|
6
5
|
/**
|
|
7
|
-
* Preact hook that reactively reads
|
|
8
|
-
* from the singleton
|
|
6
|
+
* Preact hook that reactively reads every hotkey registration, chords and
|
|
7
|
+
* sequences, from the singleton HotkeyManager. Narrow each view with `kind`.
|
|
9
8
|
*
|
|
10
|
-
* This
|
|
11
|
-
*
|
|
12
|
-
* re-renders when registrations change.
|
|
9
|
+
* This hook does NOT require the HotkeysProvider. It re-renders when
|
|
10
|
+
* registrations change, including trigger counts and sequence progress.
|
|
13
11
|
*
|
|
14
|
-
* @returns
|
|
12
|
+
* @returns All registrations, without callbacks
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* function ShortcutPalette() {
|
|
17
|
+
* const registrations = useHotkeyRegistrations()
|
|
18
|
+
*
|
|
19
|
+
* return (
|
|
20
|
+
* <ul>
|
|
21
|
+
* {registrations.map((reg) => (
|
|
22
|
+
* <li key={reg.id}>
|
|
23
|
+
* {reg.options.meta?.name}: {formatForDisplay(reg.hotkey)}
|
|
24
|
+
* </li>
|
|
25
|
+
* ))}
|
|
26
|
+
* </ul>
|
|
27
|
+
* )
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
15
30
|
*/
|
|
16
31
|
function useHotkeyRegistrations() {
|
|
17
|
-
|
|
18
|
-
const sequenceManager = getSequenceManager();
|
|
19
|
-
const hotkeyState = useSelector(hotkeyManager.registrations);
|
|
20
|
-
const sequenceState = useSelector(sequenceManager.registrations);
|
|
21
|
-
return {
|
|
22
|
-
hotkeys: useMemo(() => Array.from(hotkeyState.values()).map(toHotkeyRegistrationView), [hotkeyState]),
|
|
23
|
-
sequences: useMemo(() => Array.from(sequenceState.values()), [sequenceState])
|
|
24
|
-
};
|
|
32
|
+
return useSelector(getHotkeyManager().registrations, (state) => Array.from(state.values()).map(toHotkeyRegistrationView));
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
//#endregion
|
package/dist/useHotkeys.d.ts
CHANGED
|
@@ -1,41 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { UseHotkeyCommonOptions, UseHotkeyTarget } from "./useHotkey.js";
|
|
2
|
+
import { AdapterHotkeyDefinition } from "@tanstack/hotkeys/adapter";
|
|
3
3
|
//#region src/useHotkeys.d.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* One hotkey for `useHotkeys`. Chords accept `requireReset`; sequences accept
|
|
6
|
+
* `timeout`; hotkeys of unknown kind accept only shared options.
|
|
6
7
|
*/
|
|
7
|
-
export
|
|
8
|
-
/** The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object */
|
|
9
|
-
hotkey: RegisterableHotkey;
|
|
10
|
-
/** The function to call when the hotkey is pressed */
|
|
11
|
-
callback: HotkeyCallback;
|
|
12
|
-
/** Per-hotkey options (merged on top of commonOptions) */
|
|
13
|
-
options?: UseHotkeyOptions;
|
|
14
|
-
}
|
|
8
|
+
export type UseHotkeyDefinition = AdapterHotkeyDefinition<UseHotkeyTarget>;
|
|
15
9
|
/**
|
|
16
|
-
* Preact hook for registering
|
|
10
|
+
* Preact hook for registering several hotkeys at once: chords, sequences, and
|
|
11
|
+
* app hotkeys. Accepts a dynamic array, so it is safe to use with
|
|
12
|
+
* variable-length lists without violating the rules of hooks.
|
|
17
13
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
14
|
+
* Options merge in this order: provider defaults, app hotkey declaration,
|
|
15
|
+
* commonOptions, per-hotkey options. Each option applies only to the kinds
|
|
16
|
+
* that support it. Callbacks and options sync on every render.
|
|
21
17
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* Per-row `enabled: false` keeps that registration (visible in devtools) and
|
|
19
|
+
* only suppresses firing. Unbound app hotkeys and empty sequences register nothing.
|
|
24
20
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* @param hotkeys - Array of hotkey definitions to register
|
|
28
|
-
* @param commonOptions - Shared options applied to all hotkeys (overridden by per-definition options).
|
|
29
|
-
* Per-row `enabled: false` still registers that hotkey: `HotkeyManager` suppresses execution only (the row
|
|
30
|
-
* stays in the store and appears in TanStack Hotkeys devtools). Toggling `enabled` updates the existing handle
|
|
31
|
-
* via `setOptions` (no unregister/re-register churn).
|
|
21
|
+
* @param hotkeys - Hotkeys to register
|
|
22
|
+
* @param commonOptions - Options applied to every hotkey (overridden by per-hotkey options)
|
|
32
23
|
*
|
|
33
24
|
* @example
|
|
34
25
|
* ```tsx
|
|
35
26
|
* function Editor() {
|
|
36
27
|
* useHotkeys([
|
|
37
28
|
* { hotkey: 'Mod+S', callback: () => save() },
|
|
38
|
-
* { hotkey: '
|
|
29
|
+
* { hotkey: ['G', 'G'], callback: () => scrollToTop(), options: { timeout: 500 } },
|
|
39
30
|
* { hotkey: 'Escape', callback: () => close() },
|
|
40
31
|
* ])
|
|
41
32
|
* }
|
|
@@ -56,5 +47,5 @@ export interface UseHotkeyDefinition {
|
|
|
56
47
|
* }
|
|
57
48
|
* ```
|
|
58
49
|
*/
|
|
59
|
-
export declare function useHotkeys(hotkeys:
|
|
50
|
+
export declare function useHotkeys(hotkeys: ReadonlyArray<UseHotkeyDefinition>, commonOptions?: UseHotkeyCommonOptions): void;
|
|
60
51
|
//#endregion
|
package/dist/useHotkeys.js
CHANGED
|
@@ -1,33 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { useHotkeysContext } from "./HotkeysProvider.js";
|
|
2
|
+
import { useAppHotkeysStoreState } from "./_appHotkeysStore.js";
|
|
3
|
+
import { resolveTarget } from "./utils.js";
|
|
4
4
|
import { useEffect, useRef } from "preact/hooks";
|
|
5
|
+
import { createHotkeyBindings, prepareHotkeyDefinitions } from "@tanstack/hotkeys/adapter";
|
|
5
6
|
|
|
6
7
|
//#region src/useHotkeys.ts
|
|
7
8
|
/**
|
|
8
|
-
* Preact hook for registering
|
|
9
|
+
* Preact hook for registering several hotkeys at once: chords, sequences, and
|
|
10
|
+
* app hotkeys. Accepts a dynamic array, so it is safe to use with
|
|
11
|
+
* variable-length lists without violating the rules of hooks.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
+
* Options merge in this order: provider defaults, app hotkey declaration,
|
|
14
|
+
* commonOptions, per-hotkey options. Each option applies only to the kinds
|
|
15
|
+
* that support it. Callbacks and options sync on every render.
|
|
13
16
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
17
|
+
* Per-row `enabled: false` keeps that registration (visible in devtools) and
|
|
18
|
+
* only suppresses firing. Unbound app hotkeys and empty sequences register nothing.
|
|
16
19
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* @param hotkeys - Array of hotkey definitions to register
|
|
20
|
-
* @param commonOptions - Shared options applied to all hotkeys (overridden by per-definition options).
|
|
21
|
-
* Per-row `enabled: false` still registers that hotkey: `HotkeyManager` suppresses execution only (the row
|
|
22
|
-
* stays in the store and appears in TanStack Hotkeys devtools). Toggling `enabled` updates the existing handle
|
|
23
|
-
* via `setOptions` (no unregister/re-register churn).
|
|
20
|
+
* @param hotkeys - Hotkeys to register
|
|
21
|
+
* @param commonOptions - Options applied to every hotkey (overridden by per-hotkey options)
|
|
24
22
|
*
|
|
25
23
|
* @example
|
|
26
24
|
* ```tsx
|
|
27
25
|
* function Editor() {
|
|
28
26
|
* useHotkeys([
|
|
29
27
|
* { hotkey: 'Mod+S', callback: () => save() },
|
|
30
|
-
* { hotkey: '
|
|
28
|
+
* { hotkey: ['G', 'G'], callback: () => scrollToTop(), options: { timeout: 500 } },
|
|
31
29
|
* { hotkey: 'Escape', callback: () => close() },
|
|
32
30
|
* ])
|
|
33
31
|
* }
|
|
@@ -49,72 +47,33 @@ import { useEffect, useRef } from "preact/hooks";
|
|
|
49
47
|
* ```
|
|
50
48
|
*/
|
|
51
49
|
function useHotkeys(hotkeys, commonOptions = {}) {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
50
|
+
const defaults = useHotkeysContext()?.defaultOptions.hotkey;
|
|
51
|
+
const appHotkeysStore = useAppHotkeysStoreState();
|
|
52
|
+
const latestRef = useRef({
|
|
53
|
+
hotkeys,
|
|
54
|
+
commonOptions,
|
|
55
|
+
defaults,
|
|
56
|
+
appHotkeysStore
|
|
57
|
+
});
|
|
58
|
+
latestRef.current = {
|
|
59
|
+
hotkeys,
|
|
60
|
+
commonOptions,
|
|
61
|
+
defaults,
|
|
62
|
+
appHotkeysStore
|
|
63
|
+
};
|
|
64
|
+
const bindingsRef = useRef(null);
|
|
67
65
|
useEffect(() => {
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const mergedOptions = {
|
|
75
|
-
...defaultOptionsRef.current,
|
|
76
|
-
...commonOptionsRef.current,
|
|
77
|
-
...def.options
|
|
78
|
-
};
|
|
79
|
-
const resolvedTarget = isRef(mergedOptions.target) ? mergedOptions.target.current : mergedOptions.target ?? (typeof document !== "undefined" ? document : null);
|
|
80
|
-
if (!resolvedTarget) continue;
|
|
81
|
-
const registrationKey = `${i}:${hotkeyStr}`;
|
|
82
|
-
rows.push({
|
|
83
|
-
registrationKey,
|
|
84
|
-
def,
|
|
85
|
-
hotkeyStr,
|
|
86
|
-
mergedOptions,
|
|
87
|
-
resolvedTarget
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
const nextKeys = new Set(rows.map((r) => r.registrationKey));
|
|
91
|
-
for (const [key, record] of prevRegistrations) if (!nextKeys.has(key) && record.handle.isActive) record.handle.unregister();
|
|
92
|
-
for (const row of rows) {
|
|
93
|
-
const { registrationKey, def, hotkeyStr, mergedOptions, resolvedTarget } = row;
|
|
94
|
-
const existing = prevRegistrations.get(registrationKey);
|
|
95
|
-
if (existing?.handle.isActive && existing.target === resolvedTarget) {
|
|
96
|
-
existing.handle.callback = def.callback;
|
|
97
|
-
const { target: _target, ...optionsWithoutTarget } = mergedOptions;
|
|
98
|
-
existing.handle.setOptions(optionsWithoutTarget);
|
|
99
|
-
nextRegistrations.set(registrationKey, existing);
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (existing?.handle.isActive) existing.handle.unregister();
|
|
103
|
-
const handle = managerRef.current.register(hotkeyStr, def.callback, {
|
|
104
|
-
...mergedOptions,
|
|
105
|
-
target: resolvedTarget
|
|
106
|
-
});
|
|
107
|
-
nextRegistrations.set(registrationKey, {
|
|
108
|
-
handle,
|
|
109
|
-
target: resolvedTarget
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
registrationsRef.current = nextRegistrations;
|
|
66
|
+
bindingsRef.current ??= createHotkeyBindings();
|
|
67
|
+
const { hotkeys, ...context } = latestRef.current;
|
|
68
|
+
bindingsRef.current.update(prepareHotkeyDefinitions(hotkeys, {
|
|
69
|
+
...context,
|
|
70
|
+
resolveTarget
|
|
71
|
+
}));
|
|
113
72
|
});
|
|
114
73
|
useEffect(() => {
|
|
115
74
|
return () => {
|
|
116
|
-
|
|
117
|
-
|
|
75
|
+
bindingsRef.current?.destroy();
|
|
76
|
+
bindingsRef.current = null;
|
|
118
77
|
};
|
|
119
78
|
}, []);
|
|
120
79
|
}
|
package/dist/utils.js
CHANGED
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
function isRef(value) {
|
|
6
6
|
return value !== null && typeof value === "object" && "current" in value;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolves a hook target. A ref that is still null skips registration;
|
|
10
|
+
* a null target means document.
|
|
11
|
+
*/
|
|
12
|
+
function resolveTarget(target) {
|
|
13
|
+
return isRef(target) ? target.current : target ?? void 0;
|
|
14
|
+
}
|
|
8
15
|
|
|
9
16
|
//#endregion
|
|
10
|
-
export {
|
|
17
|
+
export { resolveTarget };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/preact-hotkeys",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Preact adapter for TanStack Hotkeys",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"dist"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@tanstack/hotkeys": "0.
|
|
37
|
-
"@tanstack/preact-store": "^0.13.
|
|
36
|
+
"@tanstack/hotkeys": "0.12.0",
|
|
37
|
+
"@tanstack/preact-store": "^0.13.5"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@preact/preset-vite": "^2.10.6",
|
|
41
41
|
"@testing-library/preact": "^3.2.4",
|
|
42
|
-
"preact": "^
|
|
42
|
+
"preact": "^11.0.1"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"preact": ">=10.0.0"
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { HotkeyCallback, HotkeySequence, SequenceOptions } from "@tanstack/hotkeys";
|
|
2
|
-
import { RefObject } from "preact";
|
|
3
|
-
//#region src/useHotkeySequence.d.ts
|
|
4
|
-
export interface UseHotkeySequenceOptions extends Omit<SequenceOptions, 'target'> {
|
|
5
|
-
/**
|
|
6
|
-
* The DOM element to attach the event listener to.
|
|
7
|
-
* Can be a Preact ref, direct DOM element, or null.
|
|
8
|
-
* Defaults to document.
|
|
9
|
-
*/
|
|
10
|
-
target?: RefObject<HTMLElement | null> | HTMLElement | Document | Window | null;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Preact hook for registering a keyboard shortcut sequence (Vim-style).
|
|
14
|
-
*
|
|
15
|
-
* This hook allows you to register multi-key sequences like 'g g' or 'd d'
|
|
16
|
-
* that trigger when the full sequence is pressed within a timeout.
|
|
17
|
-
*
|
|
18
|
-
* Each step may include modifiers. You can chain the same modifier across
|
|
19
|
-
* steps (e.g. `Shift+R` then `Shift+T`). Modifier-only keydown events (Shift,
|
|
20
|
-
* Control, Alt, or Meta pressed alone) are ignored while matching—they do not
|
|
21
|
-
* advance the sequence or reset progress.
|
|
22
|
-
*
|
|
23
|
-
* @param sequence - Array of hotkey strings that form the sequence
|
|
24
|
-
* @param callback - Function to call when the sequence is completed
|
|
25
|
-
* @param options - Options for the sequence behavior. `enabled: false` keeps the registration (visible in devtools)
|
|
26
|
-
* and only suppresses firing; the hook updates the existing handle instead of unregistering.
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```tsx
|
|
30
|
-
* function VimEditor() {
|
|
31
|
-
* // 'g g' to go to top
|
|
32
|
-
* useHotkeySequence(['G', 'G'], () => {
|
|
33
|
-
* scrollToTop()
|
|
34
|
-
* })
|
|
35
|
-
*
|
|
36
|
-
* // 'd d' to delete line
|
|
37
|
-
* useHotkeySequence(['D', 'D'], () => {
|
|
38
|
-
* deleteLine()
|
|
39
|
-
* })
|
|
40
|
-
*
|
|
41
|
-
* // 'd i w' to delete inner word
|
|
42
|
-
* useHotkeySequence(['D', 'I', 'W'], () => {
|
|
43
|
-
* deleteInnerWord()
|
|
44
|
-
* }, { timeout: 500 })
|
|
45
|
-
*
|
|
46
|
-
* // Same modifier on consecutive steps (bare Shift between chords is ignored)
|
|
47
|
-
* useHotkeySequence(['Shift+R', 'Shift+T'], () => {
|
|
48
|
-
* nextAction()
|
|
49
|
-
* })
|
|
50
|
-
*
|
|
51
|
-
* return <div>...</div>
|
|
52
|
-
* }
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
|
-
export declare function useHotkeySequence(sequence: HotkeySequence, callback: HotkeyCallback, options?: UseHotkeySequenceOptions): void;
|
|
56
|
-
//#endregion
|