@tanstack/preact-hotkeys 0.17.1 → 0.19.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 +4 -2
- package/dist/HotkeysProvider.js +12 -5
- package/dist/_appHotkeysStore.js +3 -2
- package/dist/_useSelector.js +54 -0
- package/dist/useHeldKeyCodes.js +1 -1
- package/dist/useHeldKeys.d.ts +1 -2
- package/dist/useHeldKeys.js +2 -3
- package/dist/useHotkey.d.ts +4 -3
- package/dist/useHotkeyHint.d.ts +3 -2
- package/dist/useHotkeyHint.js +3 -2
- package/dist/useHotkeyRecorder.js +3 -5
- package/dist/useHotkeyRegistrations.js +1 -1
- package/dist/useHotkeyScopesState.js +1 -1
- package/dist/useKeyHold.d.ts +1 -2
- package/dist/useKeyHold.js +2 -3
- package/dist/utils.js +3 -3
- package/package.json +2 -3
- package/skills/preact-hotkeys/SKILL.md +1 -1
|
@@ -27,8 +27,10 @@ export interface HotkeysProviderProps<T extends AppHotkeys = AppHotkeys> extends
|
|
|
27
27
|
*/
|
|
28
28
|
appHotkeys?: T;
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
* the
|
|
30
|
+
* The user's saved custom hotkeys. When the value changes, such as after
|
|
31
|
+
* saved data loads or the user signs in, it replaces the current custom
|
|
32
|
+
* hotkeys. `undefined` leaves them unchanged; `null` clears them. Invalid
|
|
33
|
+
* entries are ignored with a warning.
|
|
32
34
|
*/
|
|
33
35
|
customHotkeys?: CustomHotkeys<T> | null;
|
|
34
36
|
/** Called with the custom hotkeys after every change, ready to save. */
|
package/dist/HotkeysProvider.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { publishAppHotkeysStore } from "@tanstack/hotkeys";
|
|
2
2
|
import { createContext } from "preact";
|
|
3
3
|
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
|
4
|
-
import { activateHotkeysProviderScope, getHotkeysProviderScopeKey, mergeHotkeysProviderDefaults } from "@tanstack/hotkeys/adapter";
|
|
4
|
+
import { activateHotkeysProviderScope, createAppHotkeysStore, getHotkeysProviderScopeKey, mergeHotkeysProviderDefaults } from "@tanstack/hotkeys/adapter";
|
|
5
5
|
import { jsx } from "preact/jsx-runtime";
|
|
6
6
|
|
|
7
7
|
//#region src/HotkeysProvider.tsx
|
|
@@ -45,7 +45,7 @@ const NO_PARENT = {
|
|
|
45
45
|
*/
|
|
46
46
|
function HotkeysProvider({ children, appHotkeys, customHotkeys, onCustomHotkeysChange, defaultOptions = DEFAULT_OPTIONS, scope, exclusive = false, enabled = true }) {
|
|
47
47
|
const parent = useContext(HotkeysContext) ?? NO_PARENT;
|
|
48
|
-
const [store] = useState(() => appHotkeys ?
|
|
48
|
+
const [store] = useState(() => appHotkeys ? createAppHotkeysStore(appHotkeys, {
|
|
49
49
|
customHotkeys,
|
|
50
50
|
onCustomHotkeysChange
|
|
51
51
|
}) : null);
|
|
@@ -60,8 +60,15 @@ function HotkeysProvider({ children, appHotkeys, customHotkeys, onCustomHotkeysC
|
|
|
60
60
|
return scopeKey ? activateHotkeysProviderScope(scopeKey) : void 0;
|
|
61
61
|
}, [scopeKey]);
|
|
62
62
|
useEffect(() => {
|
|
63
|
-
store?.setOptions({
|
|
64
|
-
|
|
63
|
+
store?.setOptions({
|
|
64
|
+
customHotkeys,
|
|
65
|
+
onCustomHotkeysChange
|
|
66
|
+
});
|
|
67
|
+
}, [
|
|
68
|
+
store,
|
|
69
|
+
customHotkeys,
|
|
70
|
+
onCustomHotkeysChange
|
|
71
|
+
]);
|
|
65
72
|
useEffect(() => {
|
|
66
73
|
if (appHotkeys) store?.setAppHotkeys(appHotkeys);
|
|
67
74
|
}, [store, appHotkeys]);
|
package/dist/_appHotkeysStore.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useHotkeysContext } from "./HotkeysProvider.js";
|
|
2
|
-
import {
|
|
2
|
+
import { useSelector } from "./_useSelector.js";
|
|
3
|
+
import { createStore } from "@tanstack/hotkeys/adapter";
|
|
3
4
|
|
|
4
5
|
//#region src/_appHotkeysStore.ts
|
|
5
|
-
const NO_APP_HOTKEYS =
|
|
6
|
+
const NO_APP_HOTKEYS = createStore(null);
|
|
6
7
|
/**
|
|
7
8
|
* The provider's app hotkeys store, re-rendering when app hotkeys or custom
|
|
8
9
|
* hotkeys change. Null outside a provider with `appHotkeys`.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
|
|
2
|
+
|
|
3
|
+
//#region src/_useSelector.ts
|
|
4
|
+
/**
|
|
5
|
+
* Subscribes to a store and returns the selected value, re-rendering when it
|
|
6
|
+
* changes (by `Object.is`). The selector runs again only when the store's
|
|
7
|
+
* state or the selector itself changes. Preact 10 has no
|
|
8
|
+
* `useSyncExternalStore` outside `preact/compat`, so this follows its approach.
|
|
9
|
+
*/
|
|
10
|
+
function useSelector(store, selector) {
|
|
11
|
+
const select = useMemo(() => {
|
|
12
|
+
let memo;
|
|
13
|
+
return (state) => {
|
|
14
|
+
if (!memo || !Object.is(memo.state, state)) memo = {
|
|
15
|
+
state,
|
|
16
|
+
selected: selector(state)
|
|
17
|
+
};
|
|
18
|
+
return memo.selected;
|
|
19
|
+
};
|
|
20
|
+
}, [selector]);
|
|
21
|
+
const selected = useRef(void 0);
|
|
22
|
+
const getSelection = () => {
|
|
23
|
+
const next = select(store.get());
|
|
24
|
+
if (!Object.is(selected.current, next)) selected.current = next;
|
|
25
|
+
return selected.current;
|
|
26
|
+
};
|
|
27
|
+
const value = getSelection();
|
|
28
|
+
const [{ instance }, forceUpdate] = useState({ instance: {
|
|
29
|
+
value,
|
|
30
|
+
getSelection
|
|
31
|
+
} });
|
|
32
|
+
const changed = () => {
|
|
33
|
+
try {
|
|
34
|
+
return !Object.is(instance.value, instance.getSelection());
|
|
35
|
+
} catch {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
useLayoutEffect(() => {
|
|
40
|
+
instance.value = value;
|
|
41
|
+
instance.getSelection = getSelection;
|
|
42
|
+
if (changed()) forceUpdate({ instance });
|
|
43
|
+
});
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (changed()) forceUpdate({ instance });
|
|
46
|
+
return store.subscribe(() => {
|
|
47
|
+
if (changed()) forceUpdate({ instance });
|
|
48
|
+
}).unsubscribe;
|
|
49
|
+
}, [instance, store]);
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { useSelector };
|
package/dist/useHeldKeyCodes.js
CHANGED
package/dist/useHeldKeys.d.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Preact hook that returns an array of currently held keyboard keys.
|
|
4
4
|
*
|
|
5
|
-
* This hook
|
|
6
|
-
* to the global KeyStateTracker and updates whenever keys are pressed
|
|
5
|
+
* This hook subscribes to the global KeyStateTracker and updates whenever keys are pressed
|
|
7
6
|
* or released.
|
|
8
7
|
*
|
|
9
8
|
* @returns Array of currently held key names
|
package/dist/useHeldKeys.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
import { useSelector } from "./_useSelector.js";
|
|
1
2
|
import { getKeyStateTracker } from "@tanstack/hotkeys";
|
|
2
|
-
import { useSelector } from "@tanstack/preact-store";
|
|
3
3
|
|
|
4
4
|
//#region src/useHeldKeys.ts
|
|
5
5
|
/**
|
|
6
6
|
* Preact hook that returns an array of currently held keyboard keys.
|
|
7
7
|
*
|
|
8
|
-
* This hook
|
|
9
|
-
* to the global KeyStateTracker and updates whenever keys are pressed
|
|
8
|
+
* This hook subscribes to the global KeyStateTracker and updates whenever keys are pressed
|
|
10
9
|
* or released.
|
|
11
10
|
*
|
|
12
11
|
* @returns Array of currently held key names
|
package/dist/useHotkey.d.ts
CHANGED
|
@@ -38,7 +38,8 @@ export type UseAppHotkeyOptions<T extends AppHotkey = AppHotkey> = AdapterAppHot
|
|
|
38
38
|
* Options merge in this order: provider defaults, app hotkey declaration,
|
|
39
39
|
* call-site options.
|
|
40
40
|
*
|
|
41
|
-
* @param hotkey - A chord string, RawHotkeyChord object, sequence, or app hotkey
|
|
41
|
+
* @param hotkey - A chord string, RawHotkeyChord object, sequence, or app hotkey.
|
|
42
|
+
* `undefined`, `null`, or `''` registers nothing, for an optional shortcut.
|
|
42
43
|
* @param callback - The function to call when the hotkey is pressed
|
|
43
44
|
* @param options - Options for the hotkey behavior
|
|
44
45
|
*
|
|
@@ -61,6 +62,6 @@ export type UseAppHotkeyOptions<T extends AppHotkey = AppHotkey> = AdapterAppHot
|
|
|
61
62
|
* }
|
|
62
63
|
* ```
|
|
63
64
|
*/
|
|
64
|
-
export declare function useHotkey<T extends AppHotkey>(hotkey: T, callback: HotkeyCallback, options?: UseAppHotkeyOptions<T>): void;
|
|
65
|
-
export declare function useHotkey<const T extends RegisterableHotkey>(hotkey: T, callback: HotkeyCallback<HotkeyCallbackContextFor<T>>, options?: UseHotkeyOptionsFor<T>): void;
|
|
65
|
+
export declare function useHotkey<T extends AppHotkey>(hotkey: T | null | undefined, callback: HotkeyCallback, options?: UseAppHotkeyOptions<T>): void;
|
|
66
|
+
export declare function useHotkey<const T extends RegisterableHotkey>(hotkey: T | null | undefined, callback: HotkeyCallback<HotkeyCallbackContextFor<T>>, options?: UseHotkeyOptionsFor<T>): void;
|
|
66
67
|
//#endregion
|
package/dist/useHotkeyHint.d.ts
CHANGED
|
@@ -4,10 +4,11 @@ import { AppHotkey, HeldModifierOptions, RegisterableHotkey } from "@tanstack/ho
|
|
|
4
4
|
* Returns a boolean indicating whether held modifiers reveal this shortcut.
|
|
5
5
|
* The hint shows only while the hotkey can fire: some registration of it is
|
|
6
6
|
* enabled and in an active scope. An app hotkey matches its registrations by
|
|
7
|
-
* id and uses its effective hotkey (false while unbound).
|
|
7
|
+
* id and uses its effective hotkey (false while unbound). No hotkey
|
|
8
|
+
* (`undefined`, `null`, or `''`) returns false.
|
|
8
9
|
* Uses the nonempty-subset/AltGr rules of `matchesHeldModifiers`; `exact`
|
|
9
10
|
* requires every modifier. A sequence reveals its hint from its first step.
|
|
10
11
|
* Only visibility changes cause a rerender. This helper does not register a shortcut. A hotkey on an element counts only while focus is inside it.
|
|
11
12
|
*/
|
|
12
|
-
export declare function useHotkeyHint(hotkey: RegisterableHotkey | AppHotkey, options?: HeldModifierOptions): boolean;
|
|
13
|
+
export declare function useHotkeyHint(hotkey: RegisterableHotkey | AppHotkey | null | undefined, options?: HeldModifierOptions): boolean;
|
|
13
14
|
//#endregion
|
package/dist/useHotkeyHint.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { useSelector } from "./_useSelector.js";
|
|
1
2
|
import { useAppHotkeysStoreState } from "./_appHotkeysStore.js";
|
|
2
3
|
import { getHotkeyManager, getKeyStateTracker, isAppHotkey, matchesHeldModifiers, resolveAppHotkey } from "@tanstack/hotkeys";
|
|
3
|
-
import { useSelector } from "@tanstack/preact-store";
|
|
4
4
|
|
|
5
5
|
//#region src/useHotkeyHint.ts
|
|
6
6
|
/**
|
|
7
7
|
* Returns a boolean indicating whether held modifiers reveal this shortcut.
|
|
8
8
|
* The hint shows only while the hotkey can fire: some registration of it is
|
|
9
9
|
* enabled and in an active scope. An app hotkey matches its registrations by
|
|
10
|
-
* id and uses its effective hotkey (false while unbound).
|
|
10
|
+
* id and uses its effective hotkey (false while unbound). No hotkey
|
|
11
|
+
* (`undefined`, `null`, or `''`) returns false.
|
|
11
12
|
* Uses the nonempty-subset/AltGr rules of `matchesHeldModifiers`; `exact`
|
|
12
13
|
* requires every modifier. A sequence reveals its hint from its first step.
|
|
13
14
|
* Only visibility changes cause a rerender. This helper does not register a shortcut. A hotkey on an element counts only while focus is inside it.
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { useDefaultHotkeysOptions } from "./HotkeysProvider.js";
|
|
2
|
+
import { useSelector } from "./_useSelector.js";
|
|
2
3
|
import { HotkeyRecorder } from "@tanstack/hotkeys";
|
|
3
4
|
import { useEffect, useRef } from "preact/hooks";
|
|
4
|
-
import {
|
|
5
|
+
import { withDefaults } from "@tanstack/hotkeys/adapter";
|
|
5
6
|
|
|
6
7
|
//#region src/useHotkeyRecorder.ts
|
|
7
8
|
function useHotkeyRecorder(options) {
|
|
8
|
-
const mergedOptions =
|
|
9
|
-
...useDefaultHotkeysOptions().hotkeyRecorder,
|
|
10
|
-
...options
|
|
11
|
-
};
|
|
9
|
+
const mergedOptions = withDefaults(useDefaultHotkeysOptions().hotkeyRecorder, options);
|
|
12
10
|
const recorderRef = useRef(null);
|
|
13
11
|
if (!recorderRef.current) recorderRef.current = new HotkeyRecorder(mergedOptions);
|
|
14
12
|
recorderRef.current.setOptions(mergedOptions);
|
package/dist/useKeyHold.d.ts
CHANGED
|
@@ -3,8 +3,7 @@ import { IndividualKey } from "@tanstack/hotkeys";
|
|
|
3
3
|
/**
|
|
4
4
|
* Preact hook that returns whether a specific key is currently being held.
|
|
5
5
|
*
|
|
6
|
-
* This hook uses
|
|
7
|
-
* to the global KeyStateTracker and uses a selector to determine if
|
|
6
|
+
* This hook subscribes to the global KeyStateTracker and uses a selector to determine if
|
|
8
7
|
* the specified key is held.
|
|
9
8
|
*
|
|
10
9
|
* @param key - The key to check (e.g., 'Shift', 'Control', 'A')
|
package/dist/useKeyHold.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
import { useSelector } from "./_useSelector.js";
|
|
1
2
|
import { getKeyStateTracker } from "@tanstack/hotkeys";
|
|
2
|
-
import { useSelector } from "@tanstack/preact-store";
|
|
3
3
|
|
|
4
4
|
//#region src/useKeyHold.ts
|
|
5
5
|
/**
|
|
6
6
|
* Preact hook that returns whether a specific key is currently being held.
|
|
7
7
|
*
|
|
8
|
-
* This hook uses
|
|
9
|
-
* to the global KeyStateTracker and uses a selector to determine if
|
|
8
|
+
* This hook subscribes to the global KeyStateTracker and uses a selector to determine if
|
|
10
9
|
* the specified key is held.
|
|
11
10
|
*
|
|
12
11
|
* @param key - The key to check (e.g., 'Shift', 'Control', 'A')
|
package/dist/utils.js
CHANGED
|
@@ -6,11 +6,11 @@ function isRef(value) {
|
|
|
6
6
|
return value !== null && typeof value === "object" && "current" in value;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* Resolves a hook target. A ref that is still null skips
|
|
10
|
-
*
|
|
9
|
+
* Resolves a hook target. A null target, or a ref that is still null, skips
|
|
10
|
+
* registration. Omit the target to listen on the document.
|
|
11
11
|
*/
|
|
12
12
|
function resolveTarget(target) {
|
|
13
|
-
return isRef(target) ? target.current : target
|
|
13
|
+
return isRef(target) ? target.current : target;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/preact-hotkeys",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Preact adapter for TanStack Hotkeys",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,8 +35,7 @@
|
|
|
35
35
|
"skills"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@tanstack/hotkeys": "0.
|
|
39
|
-
"@tanstack/preact-store": "^0.13.5"
|
|
38
|
+
"@tanstack/hotkeys": "0.21.0"
|
|
40
39
|
},
|
|
41
40
|
"devDependencies": {
|
|
42
41
|
"@preact/preset-vite": "^2.10.6",
|
|
@@ -101,7 +101,7 @@ A hook uses the nearest `HotkeysProvider` above its component. Registering `Esca
|
|
|
101
101
|
|
|
102
102
|
## Pitfalls
|
|
103
103
|
|
|
104
|
-
- **Pass the ref, not `ref.current`.** `target: listRef.current` is `null` on the first render, and a `null` target
|
|
104
|
+
- **Pass the ref, not `ref.current`.** `target: listRef.current` is `null` on the first render, and a `null` target registers nothing, so the hotkey doesn't work until the component renders again. Pass `target: listRef`; a ref that is still empty waits for its element.
|
|
105
105
|
- **No dependency arrays or `useCallback`.** The hooks read the hotkey, callback, and options on every render, so inline callbacks always see current state.
|
|
106
106
|
- **Call the hooks directly.** A `useShortcut(keys: string)` wrapper has to cast strings to hotkeys, which turns off type checking and drops sequences, options, app hotkeys, and the typed callback context.
|
|
107
107
|
- **In tests,** set `<HotkeysProvider defaultOptions={{ hotkey: { platform: 'mac' } }}>`.
|