@tanstack/preact-hotkeys 0.13.1 → 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 +2 -2
- 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,22 +1,53 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { HotkeyRecorderOptions, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
1
|
+
import { UseHotkeyCommonOptions } from "./useHotkey.js";
|
|
2
|
+
import { AppHotkeys, AppHotkeysStore, CustomHotkeys, HotkeyChordRecorderOptions, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
4
3
|
import { ComponentChildren } from "preact";
|
|
5
4
|
//#region src/HotkeysProvider.d.ts
|
|
6
5
|
export interface HotkeysProviderOptions {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Defaults for every hotkey registered by `useHotkey` and `useHotkeys`.
|
|
8
|
+
* `requireReset` applies to chords and `timeout` applies to sequences.
|
|
9
|
+
*/
|
|
10
|
+
hotkey?: UseHotkeyCommonOptions;
|
|
11
|
+
/** Defaults for every `useHotkeyRecorder`. */
|
|
12
|
+
hotkeyRecorder?: Partial<Omit<HotkeyChordRecorderOptions & HotkeySequenceRecorderOptions, 'maxSteps' | 'onRecord' | 'validate'>>;
|
|
11
13
|
}
|
|
12
14
|
interface HotkeysContextValue {
|
|
15
|
+
appHotkeysStore: AppHotkeysStore | null;
|
|
13
16
|
defaultOptions: HotkeysProviderOptions;
|
|
14
17
|
}
|
|
15
|
-
export interface HotkeysProviderProps {
|
|
18
|
+
export interface HotkeysProviderProps<T extends AppHotkeys = AppHotkeys> {
|
|
16
19
|
children: ComponentChildren;
|
|
20
|
+
/**
|
|
21
|
+
* Every app hotkey, from `defineAppHotkeys`. Listed by `useAppHotkeyList`
|
|
22
|
+
* whether or not their components have mounted.
|
|
23
|
+
*/
|
|
24
|
+
appHotkeys?: T;
|
|
25
|
+
/**
|
|
26
|
+
* Starting custom hotkeys, such as saved data. Read once; the provider owns
|
|
27
|
+
* the state afterwards. Invalid entries are ignored with a warning.
|
|
28
|
+
*/
|
|
29
|
+
customHotkeys?: CustomHotkeys<T> | null;
|
|
30
|
+
/** Called with the custom hotkeys after every change, ready to save. */
|
|
31
|
+
onCustomHotkeysChange?: (customHotkeys: CustomHotkeys<T>) => void;
|
|
17
32
|
defaultOptions?: HotkeysProviderOptions;
|
|
18
33
|
}
|
|
19
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Provides default options, app hotkeys, and the user's custom hotkeys.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* <HotkeysProvider
|
|
40
|
+
* appHotkeys={appHotkeys}
|
|
41
|
+
* customHotkeys={JSON.parse(localStorage.getItem('hotkeys') ?? '{}')}
|
|
42
|
+
* onCustomHotkeysChange={(custom) =>
|
|
43
|
+
* localStorage.setItem('hotkeys', JSON.stringify(custom))
|
|
44
|
+
* }
|
|
45
|
+
* >
|
|
46
|
+
* <App />
|
|
47
|
+
* </HotkeysProvider>
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function HotkeysProvider<T extends AppHotkeys = AppHotkeys>({ children, appHotkeys, customHotkeys, onCustomHotkeysChange, defaultOptions }: HotkeysProviderProps<T>): import("preact").JSX.Element;
|
|
20
51
|
export declare function useHotkeysContext(): HotkeysContextValue | null;
|
|
21
52
|
export declare function useDefaultHotkeysOptions(): HotkeysProviderOptions;
|
|
22
53
|
//#endregion
|
package/dist/HotkeysProvider.js
CHANGED
|
@@ -1,12 +1,46 @@
|
|
|
1
|
+
import { AppHotkeysStore, publishAppHotkeysStore } from "@tanstack/hotkeys";
|
|
1
2
|
import { createContext } from "preact";
|
|
2
|
-
import { useContext, useMemo } from "preact/hooks";
|
|
3
|
+
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
|
3
4
|
import { jsx } from "preact/jsx-runtime";
|
|
4
5
|
|
|
5
6
|
//#region src/HotkeysProvider.tsx
|
|
6
7
|
const HotkeysContext = createContext(null);
|
|
7
8
|
const DEFAULT_OPTIONS = {};
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Provides default options, app hotkeys, and the user's custom hotkeys.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <HotkeysProvider
|
|
15
|
+
* appHotkeys={appHotkeys}
|
|
16
|
+
* customHotkeys={JSON.parse(localStorage.getItem('hotkeys') ?? '{}')}
|
|
17
|
+
* onCustomHotkeysChange={(custom) =>
|
|
18
|
+
* localStorage.setItem('hotkeys', JSON.stringify(custom))
|
|
19
|
+
* }
|
|
20
|
+
* >
|
|
21
|
+
* <App />
|
|
22
|
+
* </HotkeysProvider>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function HotkeysProvider({ children, appHotkeys, customHotkeys, onCustomHotkeysChange, defaultOptions = DEFAULT_OPTIONS }) {
|
|
26
|
+
const [store] = useState(() => appHotkeys ? new AppHotkeysStore(appHotkeys, {
|
|
27
|
+
customHotkeys,
|
|
28
|
+
onCustomHotkeysChange
|
|
29
|
+
}) : null);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
store?.setOptions({ onCustomHotkeysChange });
|
|
32
|
+
}, [store, onCustomHotkeysChange]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (appHotkeys) store?.setAppHotkeys(appHotkeys);
|
|
35
|
+
}, [store, appHotkeys]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!store) return;
|
|
38
|
+
return publishAppHotkeysStore(store);
|
|
39
|
+
}, [store]);
|
|
40
|
+
const contextValue = useMemo(() => ({
|
|
41
|
+
appHotkeysStore: store,
|
|
42
|
+
defaultOptions
|
|
43
|
+
}), [store, defaultOptions]);
|
|
10
44
|
return /* @__PURE__ */ jsx(HotkeysContext.Provider, {
|
|
11
45
|
value: contextValue,
|
|
12
46
|
children
|
|
@@ -16,7 +50,7 @@ function useHotkeysContext() {
|
|
|
16
50
|
return useContext(HotkeysContext);
|
|
17
51
|
}
|
|
18
52
|
function useDefaultHotkeysOptions() {
|
|
19
|
-
return useContext(HotkeysContext)?.defaultOptions ??
|
|
53
|
+
return useContext(HotkeysContext)?.defaultOptions ?? DEFAULT_OPTIONS;
|
|
20
54
|
}
|
|
21
55
|
|
|
22
56
|
//#endregion
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useHotkeysContext } from "./HotkeysProvider.js";
|
|
2
|
+
import { Store, useSelector } from "@tanstack/preact-store";
|
|
3
|
+
|
|
4
|
+
//#region src/_appHotkeysStore.ts
|
|
5
|
+
const NO_APP_HOTKEYS = new Store(null);
|
|
6
|
+
/**
|
|
7
|
+
* The provider's app hotkeys store, re-rendering when app hotkeys or custom
|
|
8
|
+
* hotkeys change. Null outside a provider with `appHotkeys`.
|
|
9
|
+
*/
|
|
10
|
+
function useAppHotkeysStoreState() {
|
|
11
|
+
const store = useHotkeysContext()?.appHotkeysStore ?? null;
|
|
12
|
+
useSelector(store?.store ?? NO_APP_HOTKEYS, (state) => state);
|
|
13
|
+
return store;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { useAppHotkeysStoreState };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { UseHotkeyOptions, useHotkey } from "./useHotkey.js";
|
|
2
|
-
import { UseHotkeySequenceOptions, useHotkeySequence } from "./useHotkeySequence.js";
|
|
1
|
+
import { UseAppHotkeyOptions, UseHotkeyChordOptions, UseHotkeyCommonOptions, UseHotkeyOptions, UseHotkeyOptionsFor, UseHotkeySequenceOptions, UseHotkeyTarget, useHotkey } from "./useHotkey.js";
|
|
3
2
|
import { HotkeysProvider, HotkeysProviderOptions, HotkeysProviderProps, useDefaultHotkeysOptions, useHotkeysContext } from "./HotkeysProvider.js";
|
|
4
3
|
import { UseHotkeyDefinition, useHotkeys } from "./useHotkeys.js";
|
|
4
|
+
import { PreactHotkeyRecorder, useHotkeyRecorder } from "./useHotkeyRecorder.js";
|
|
5
|
+
import { useHotkeyRegistrations } from "./useHotkeyRegistrations.js";
|
|
6
|
+
import { useHotkeyHint } from "./useHotkeyHint.js";
|
|
7
|
+
import { UseCustomHotkeysResult, useAppHotkeyList, useAppHotkeys, useCustomHotkeys } from "./useAppHotkeys.js";
|
|
5
8
|
import { useHeldKeys } from "./useHeldKeys.js";
|
|
6
9
|
import { useHeldKeyCodes } from "./useHeldKeyCodes.js";
|
|
7
10
|
import { useKeyHold } from "./useKeyHold.js";
|
|
8
|
-
import { UseHotkeySequenceDefinition, useHotkeySequences } from "./useHotkeySequences.js";
|
|
9
|
-
import { PreactHotkeyRecorder, useHotkeyRecorder } from "./useHotkeyRecorder.js";
|
|
10
|
-
import { HotkeyRegistrationsResult, useHotkeyRegistrations } from "./useHotkeyRegistrations.js";
|
|
11
|
-
import { PreactHotkeySequenceRecorder, useHotkeySequenceRecorder } from "./useHotkeySequenceRecorder.js";
|
|
12
|
-
import { useHotkeyHint } from "./useHotkeyHint.js";
|
|
13
11
|
export * from "@tanstack/hotkeys";
|
|
14
|
-
export {
|
|
12
|
+
export { HotkeysProvider, HotkeysProviderOptions, HotkeysProviderProps, PreactHotkeyRecorder, UseAppHotkeyOptions, UseCustomHotkeysResult, UseHotkeyChordOptions, UseHotkeyCommonOptions, UseHotkeyDefinition, UseHotkeyOptions, UseHotkeyOptionsFor, UseHotkeySequenceOptions, UseHotkeyTarget, useAppHotkeyList, useAppHotkeys, useCustomHotkeys, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations, useHotkeys, useHotkeysContext, useKeyHold };
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { HotkeysProvider, useDefaultHotkeysOptions, useHotkeysContext } from "./HotkeysProvider.js";
|
|
2
|
-
import { useHotkey } from "./useHotkey.js";
|
|
3
2
|
import { useHotkeys } from "./useHotkeys.js";
|
|
4
|
-
import {
|
|
5
|
-
import { useHeldKeyCodes } from "./useHeldKeyCodes.js";
|
|
6
|
-
import { useKeyHold } from "./useKeyHold.js";
|
|
7
|
-
import { useHotkeySequence } from "./useHotkeySequence.js";
|
|
8
|
-
import { useHotkeySequences } from "./useHotkeySequences.js";
|
|
3
|
+
import { useHotkey } from "./useHotkey.js";
|
|
9
4
|
import { useHotkeyRecorder } from "./useHotkeyRecorder.js";
|
|
10
5
|
import { useHotkeyRegistrations } from "./useHotkeyRegistrations.js";
|
|
11
|
-
import { useHotkeySequenceRecorder } from "./useHotkeySequenceRecorder.js";
|
|
12
6
|
import { useHotkeyHint } from "./useHotkeyHint.js";
|
|
7
|
+
import { useAppHotkeyList, useAppHotkeys, useCustomHotkeys } from "./useAppHotkeys.js";
|
|
8
|
+
import { useHeldKeys } from "./useHeldKeys.js";
|
|
9
|
+
import { useHeldKeyCodes } from "./useHeldKeyCodes.js";
|
|
10
|
+
import { useKeyHold } from "./useKeyHold.js";
|
|
13
11
|
|
|
14
12
|
export * from "@tanstack/hotkeys"
|
|
15
13
|
|
|
16
|
-
export { HotkeysProvider, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations,
|
|
14
|
+
export { HotkeysProvider, useAppHotkeyList, useAppHotkeys, useCustomHotkeys, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations, useHotkeys, useHotkeysContext, useKeyHold };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { AppHotkey, AppHotkeyListItem, CustomHotkeys, Hotkey, RegisteredAppHotkeys } from "@tanstack/hotkeys";
|
|
2
|
+
//#region src/useAppHotkeys.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Returns the effective app hotkeys from `HotkeysProvider`, typed by
|
|
5
|
+
* `Register`: each declaration with the user's custom hotkey applied.
|
|
6
|
+
* Pass an entry to `useHotkey` or `useHotkeys` to register it.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const hotkeys = useAppHotkeys()
|
|
11
|
+
* useHotkey(hotkeys['doc.save'], () => save())
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function useAppHotkeys(): RegisteredAppHotkeys;
|
|
15
|
+
/**
|
|
16
|
+
* Lists every app hotkey with its live status, in declaration order, for
|
|
17
|
+
* cheat sheets and settings screens. App hotkeys whose components have not
|
|
18
|
+
* mounted are `'inactive'`; those without a hotkey are `'unbound'`.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* function CheatSheet() {
|
|
23
|
+
* const items = useAppHotkeyList()
|
|
24
|
+
* return (
|
|
25
|
+
* <ul>
|
|
26
|
+
* {items.map(({ appHotkey, status }) => (
|
|
27
|
+
* <li key={appHotkey.id} data-status={status}>
|
|
28
|
+
* {appHotkey.meta?.name}:{' '}
|
|
29
|
+
* {appHotkey.hotkey ? formatForDisplay(appHotkey.hotkey) : 'Unassigned'}
|
|
30
|
+
* </li>
|
|
31
|
+
* ))}
|
|
32
|
+
* </ul>
|
|
33
|
+
* )
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function useAppHotkeyList(): Array<AppHotkeyListItem<RegisteredAppHotkeys[keyof RegisteredAppHotkeys]>>;
|
|
38
|
+
/** Custom hotkey state and actions returned by `useCustomHotkeys`. */
|
|
39
|
+
export interface UseCustomHotkeysResult {
|
|
40
|
+
/** The user's hotkeys that differ from the defaults. */
|
|
41
|
+
customHotkeys: CustomHotkeys;
|
|
42
|
+
/**
|
|
43
|
+
* Sets the user's hotkey for an app hotkey. `null` unbinds it.
|
|
44
|
+
* Returns conflicting app hotkeys; when any exist, nothing changes.
|
|
45
|
+
*/
|
|
46
|
+
setCustomHotkey: (id: keyof RegisteredAppHotkeys & string, hotkey: Hotkey | null) => Array<AppHotkey>;
|
|
47
|
+
/** Restores the default hotkey for an app hotkey. */
|
|
48
|
+
resetCustomHotkey: (id: keyof RegisteredAppHotkeys & string) => void;
|
|
49
|
+
/** Restores every default hotkey. */
|
|
50
|
+
resetCustomHotkeys: () => void;
|
|
51
|
+
/** Other app hotkeys that would collide if `id` used `hotkey`. */
|
|
52
|
+
findConflicts: (id: keyof RegisteredAppHotkeys & string, hotkey: Hotkey) => Array<AppHotkey>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reads and changes the user's custom hotkeys. Changes apply to every
|
|
56
|
+
* registered app hotkey immediately and are passed to the provider's
|
|
57
|
+
* `onCustomHotkeysChange`, ready to save.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```tsx
|
|
61
|
+
* const { setCustomHotkey, findConflicts } = useCustomHotkeys()
|
|
62
|
+
* const recorder = useHotkeyRecorder({
|
|
63
|
+
* maxSteps: 3,
|
|
64
|
+
* validate: (hotkey) =>
|
|
65
|
+
* findConflicts('doc.save', hotkey).length === 0 || 'Already in use',
|
|
66
|
+
* onRecord: (hotkey) => setCustomHotkey('doc.save', hotkey),
|
|
67
|
+
* })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function useCustomHotkeys(): UseCustomHotkeysResult;
|
|
71
|
+
//#endregion
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useAppHotkeysStoreState } from "./_appHotkeysStore.js";
|
|
2
|
+
import { useHotkeyRegistrations } from "./useHotkeyRegistrations.js";
|
|
3
|
+
|
|
4
|
+
//#region src/useAppHotkeys.ts
|
|
5
|
+
function useRequiredStore(hook) {
|
|
6
|
+
const store = useAppHotkeysStoreState();
|
|
7
|
+
if (!store) throw new Error(`${hook} requires a HotkeysProvider with an appHotkeys prop`);
|
|
8
|
+
return store;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Returns the effective app hotkeys from `HotkeysProvider`, typed by
|
|
12
|
+
* `Register`: each declaration with the user's custom hotkey applied.
|
|
13
|
+
* Pass an entry to `useHotkey` or `useHotkeys` to register it.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* const hotkeys = useAppHotkeys()
|
|
18
|
+
* useHotkey(hotkeys['doc.save'], () => save())
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function useAppHotkeys() {
|
|
22
|
+
return useRequiredStore("useAppHotkeys").appHotkeys;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Lists every app hotkey with its live status, in declaration order, for
|
|
26
|
+
* cheat sheets and settings screens. App hotkeys whose components have not
|
|
27
|
+
* mounted are `'inactive'`; those without a hotkey are `'unbound'`.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* function CheatSheet() {
|
|
32
|
+
* const items = useAppHotkeyList()
|
|
33
|
+
* return (
|
|
34
|
+
* <ul>
|
|
35
|
+
* {items.map(({ appHotkey, status }) => (
|
|
36
|
+
* <li key={appHotkey.id} data-status={status}>
|
|
37
|
+
* {appHotkey.meta?.name}:{' '}
|
|
38
|
+
* {appHotkey.hotkey ? formatForDisplay(appHotkey.hotkey) : 'Unassigned'}
|
|
39
|
+
* </li>
|
|
40
|
+
* ))}
|
|
41
|
+
* </ul>
|
|
42
|
+
* )
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
function useAppHotkeyList() {
|
|
47
|
+
const store = useRequiredStore("useAppHotkeyList");
|
|
48
|
+
const registrations = useHotkeyRegistrations();
|
|
49
|
+
return store.getList(registrations);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Reads and changes the user's custom hotkeys. Changes apply to every
|
|
53
|
+
* registered app hotkey immediately and are passed to the provider's
|
|
54
|
+
* `onCustomHotkeysChange`, ready to save.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```tsx
|
|
58
|
+
* const { setCustomHotkey, findConflicts } = useCustomHotkeys()
|
|
59
|
+
* const recorder = useHotkeyRecorder({
|
|
60
|
+
* maxSteps: 3,
|
|
61
|
+
* validate: (hotkey) =>
|
|
62
|
+
* findConflicts('doc.save', hotkey).length === 0 || 'Already in use',
|
|
63
|
+
* onRecord: (hotkey) => setCustomHotkey('doc.save', hotkey),
|
|
64
|
+
* })
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
function useCustomHotkeys() {
|
|
68
|
+
const store = useRequiredStore("useCustomHotkeys");
|
|
69
|
+
return {
|
|
70
|
+
customHotkeys: store.customHotkeys,
|
|
71
|
+
setCustomHotkey: (id, hotkey) => store.setCustomHotkey(id, hotkey),
|
|
72
|
+
resetCustomHotkey: (id) => store.resetCustomHotkey(id),
|
|
73
|
+
resetCustomHotkeys: () => store.resetCustomHotkeys(),
|
|
74
|
+
findConflicts: (id, hotkey) => store.findConflicts(id, hotkey)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
export { useAppHotkeyList, useAppHotkeys, useCustomHotkeys };
|
package/dist/useHotkey.d.ts
CHANGED
|
@@ -1,72 +1,66 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AppHotkey, HotkeyCallback, RegisterableHotkey } from "@tanstack/hotkeys";
|
|
2
2
|
import { RefObject } from "preact";
|
|
3
|
+
import { AdapterAppHotkeyOptions, AdapterChordOptions, AdapterCommonOptions, AdapterHotkeyOptions, AdapterOptionsFor, AdapterSequenceOptions } from "@tanstack/hotkeys/adapter";
|
|
3
4
|
//#region src/useHotkey.d.ts
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
/** A Preact ref, element, document, or window to listen on. */
|
|
6
|
+
export type UseHotkeyTarget = RefObject<HTMLElement | null> | HTMLElement | Document | Window | null;
|
|
7
|
+
/** Options shared by chords and sequences. */
|
|
8
|
+
export type UseHotkeyOptions = AdapterHotkeyOptions<UseHotkeyTarget>;
|
|
9
|
+
/** Options for a chord such as `'Mod+S'`. */
|
|
10
|
+
export type UseHotkeyChordOptions = AdapterChordOptions<UseHotkeyTarget>;
|
|
11
|
+
/** Options for a sequence such as `['G', 'G']`. */
|
|
12
|
+
export type UseHotkeySequenceOptions = AdapterSequenceOptions<UseHotkeyTarget>;
|
|
13
|
+
/** Options applied to every hotkey, such as provider defaults. Each applies to the kinds that support it. */
|
|
14
|
+
export type UseHotkeyCommonOptions = AdapterCommonOptions<UseHotkeyTarget>;
|
|
15
|
+
/** The options type for a hotkey: chord options for chords, sequence options for sequences. */
|
|
16
|
+
export type UseHotkeyOptionsFor<T extends RegisterableHotkey> = AdapterOptionsFor<T, UseHotkeyTarget>;
|
|
12
17
|
/**
|
|
13
|
-
*
|
|
18
|
+
* Options for an app hotkey at the call site. `meta` comes from the declaration.
|
|
19
|
+
* Kind-specific options follow the declared default: `requireReset` for a chord,
|
|
20
|
+
* `timeout` for a sequence, both for an app hotkey without a default. If the
|
|
21
|
+
* user rebinds it to the other kind, options for the old kind no longer apply.
|
|
22
|
+
*/
|
|
23
|
+
export type UseAppHotkeyOptions<T extends AppHotkey = AppHotkey> = AdapterAppHotkeyOptions<T, UseHotkeyTarget>;
|
|
24
|
+
/**
|
|
25
|
+
* Preact hook for registering a hotkey: a chord such as `'Mod+S'`, a sequence
|
|
26
|
+
* such as `['G', 'G']`, or an app hotkey from `defineAppHotkeys`.
|
|
14
27
|
*
|
|
15
|
-
* Uses the singleton HotkeyManager for efficient event handling.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
28
|
+
* Uses the singleton HotkeyManager for efficient event handling. The callback
|
|
29
|
+
* and options sync on every render, so callbacks that reference Preact state
|
|
30
|
+
* always see the latest values. `enabled: false` keeps the registration
|
|
31
|
+
* (visible in devtools) and only suppresses firing.
|
|
18
32
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
33
|
+
* Chords accept `requireReset`; sequences accept `timeout`. A one-step
|
|
34
|
+
* sequence registers as its chord. An app hotkey uses its effective hotkey
|
|
35
|
+
* from `HotkeysProvider` (including the user's custom hotkey) and its declared
|
|
36
|
+
* meta and options, and registers nothing while unbound.
|
|
23
37
|
*
|
|
24
|
-
*
|
|
38
|
+
* Options merge in this order: provider defaults, app hotkey declaration,
|
|
39
|
+
* call-site options.
|
|
40
|
+
*
|
|
41
|
+
* @param hotkey - A chord string, RawHotkey object, sequence, or app hotkey
|
|
25
42
|
* @param callback - The function to call when the hotkey is pressed
|
|
26
|
-
* @param options - Options for the hotkey behavior
|
|
27
|
-
* and only suppresses firing; the hook updates the existing handle instead of unregistering.
|
|
43
|
+
* @param options - Options for the hotkey behavior
|
|
28
44
|
*
|
|
29
45
|
* @example
|
|
30
46
|
* ```tsx
|
|
31
|
-
* function
|
|
47
|
+
* function Editor() {
|
|
32
48
|
* const [count, setCount] = useState(0)
|
|
33
49
|
*
|
|
34
|
-
*
|
|
35
|
-
* useHotkey('
|
|
36
|
-
*
|
|
37
|
-
* handleSave()
|
|
38
|
-
* })
|
|
39
|
-
*
|
|
40
|
-
* return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
|
|
41
|
-
* }
|
|
42
|
-
* ```
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```tsx
|
|
46
|
-
* function Modal({ isOpen, onClose }) {
|
|
47
|
-
* // enabled option is synced on every render
|
|
48
|
-
* useHotkey('Escape', () => {
|
|
49
|
-
* onClose()
|
|
50
|
-
* }, { enabled: isOpen })
|
|
51
|
-
*
|
|
52
|
-
* if (!isOpen) return null
|
|
53
|
-
* return <div className="modal">...</div>
|
|
50
|
+
* useHotkey('Mod+S', () => save(count))
|
|
51
|
+
* useHotkey(['G', 'G'], () => scrollToTop(), { timeout: 500 })
|
|
52
|
+
* useHotkey('Escape', () => close(), { enabled: isOpen })
|
|
54
53
|
* }
|
|
55
54
|
* ```
|
|
56
55
|
*
|
|
57
56
|
* @example
|
|
58
57
|
* ```tsx
|
|
59
|
-
* function
|
|
60
|
-
* const
|
|
61
|
-
*
|
|
62
|
-
* // Scoped to a specific element
|
|
63
|
-
* useHotkey('Mod+S', () => {
|
|
64
|
-
* save()
|
|
65
|
-
* }, { target: editorRef })
|
|
66
|
-
*
|
|
67
|
-
* return <div ref={editorRef}>...</div>
|
|
58
|
+
* function Toolbar() {
|
|
59
|
+
* const hotkeys = useAppHotkeys()
|
|
60
|
+
* useHotkey(hotkeys['doc.save'], () => save())
|
|
68
61
|
* }
|
|
69
62
|
* ```
|
|
70
63
|
*/
|
|
71
|
-
export declare function useHotkey(hotkey:
|
|
64
|
+
export declare function useHotkey<T extends AppHotkey>(hotkey: T, callback: HotkeyCallback, options?: UseAppHotkeyOptions<T>): void;
|
|
65
|
+
export declare function useHotkey<T extends RegisterableHotkey>(hotkey: T, callback: HotkeyCallback, options?: UseHotkeyOptionsFor<T>): void;
|
|
72
66
|
//#endregion
|
package/dist/useHotkey.js
CHANGED
|
@@ -1,124 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isRef } from "./utils.js";
|
|
3
|
-
import { detectPlatform, getHotkeyManager, normalizeRegisterableHotkey } from "@tanstack/hotkeys";
|
|
4
|
-
import { useEffect, useRef } from "preact/hooks";
|
|
1
|
+
import { useHotkeys } from "./useHotkeys.js";
|
|
5
2
|
|
|
6
3
|
//#region src/useHotkey.ts
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
* This hook syncs the callback and options on every render to avoid
|
|
15
|
-
* stale closures. This means
|
|
16
|
-
* callbacks that reference Preact state will always have access to
|
|
17
|
-
* the latest values.
|
|
18
|
-
*
|
|
19
|
-
* @param hotkey - The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object (supports `mod` for cross-platform)
|
|
20
|
-
* @param callback - The function to call when the hotkey is pressed
|
|
21
|
-
* @param options - Options for the hotkey behavior. `enabled: false` keeps the registration (visible in devtools)
|
|
22
|
-
* and only suppresses firing; the hook updates the existing handle instead of unregistering.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```tsx
|
|
26
|
-
* function SaveButton() {
|
|
27
|
-
* const [count, setCount] = useState(0)
|
|
28
|
-
*
|
|
29
|
-
* // Callback always has access to latest count value
|
|
30
|
-
* useHotkey('Mod+S', (event, { hotkey }) => {
|
|
31
|
-
* console.log(`Save triggered, count is ${count}`)
|
|
32
|
-
* handleSave()
|
|
33
|
-
* })
|
|
34
|
-
*
|
|
35
|
-
* return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
|
|
36
|
-
* }
|
|
37
|
-
* ```
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```tsx
|
|
41
|
-
* function Modal({ isOpen, onClose }) {
|
|
42
|
-
* // enabled option is synced on every render
|
|
43
|
-
* useHotkey('Escape', () => {
|
|
44
|
-
* onClose()
|
|
45
|
-
* }, { enabled: isOpen })
|
|
46
|
-
*
|
|
47
|
-
* if (!isOpen) return null
|
|
48
|
-
* return <div className="modal">...</div>
|
|
49
|
-
* }
|
|
50
|
-
* ```
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```tsx
|
|
54
|
-
* function Editor() {
|
|
55
|
-
* const editorRef = useRef<HTMLDivElement>(null)
|
|
56
|
-
*
|
|
57
|
-
* // Scoped to a specific element
|
|
58
|
-
* useHotkey('Mod+S', () => {
|
|
59
|
-
* save()
|
|
60
|
-
* }, { target: editorRef })
|
|
61
|
-
*
|
|
62
|
-
* return <div ref={editorRef}>...</div>
|
|
63
|
-
* }
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
function useHotkey(hotkey, callback, options = {}) {
|
|
67
|
-
const mergedOptions = {
|
|
68
|
-
...useDefaultHotkeysOptions().hotkey,
|
|
69
|
-
...options
|
|
70
|
-
};
|
|
71
|
-
const manager = getHotkeyManager();
|
|
72
|
-
const registrationRef = useRef(null);
|
|
73
|
-
const callbackRef = useRef(callback);
|
|
74
|
-
const optionsRef = useRef(mergedOptions);
|
|
75
|
-
const managerRef = useRef(manager);
|
|
76
|
-
callbackRef.current = callback;
|
|
77
|
-
optionsRef.current = mergedOptions;
|
|
78
|
-
managerRef.current = manager;
|
|
79
|
-
const prevTargetRef = useRef(null);
|
|
80
|
-
const prevHotkeyRef = useRef(null);
|
|
81
|
-
const platform = mergedOptions.platform ?? detectPlatform();
|
|
82
|
-
const hotkeyString = normalizeRegisterableHotkey(hotkey, platform);
|
|
83
|
-
const { target: _target, ...optionsWithoutTarget } = mergedOptions;
|
|
84
|
-
useEffect(() => {
|
|
85
|
-
const resolvedTarget = isRef(optionsRef.current.target) ? optionsRef.current.target.current : optionsRef.current.target ?? (typeof document !== "undefined" ? document : null);
|
|
86
|
-
if (!resolvedTarget) {
|
|
87
|
-
if (registrationRef.current?.isActive) {
|
|
88
|
-
registrationRef.current.unregister();
|
|
89
|
-
registrationRef.current = null;
|
|
90
|
-
}
|
|
91
|
-
prevTargetRef.current = null;
|
|
92
|
-
prevHotkeyRef.current = null;
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
const targetChanged = prevTargetRef.current !== null && prevTargetRef.current !== resolvedTarget;
|
|
96
|
-
const hotkeyChanged = prevHotkeyRef.current !== null && prevHotkeyRef.current !== hotkeyString;
|
|
97
|
-
if (registrationRef.current?.isActive && (targetChanged || hotkeyChanged)) {
|
|
98
|
-
registrationRef.current.unregister();
|
|
99
|
-
registrationRef.current = null;
|
|
100
|
-
}
|
|
101
|
-
if (!registrationRef.current || !registrationRef.current.isActive) registrationRef.current = managerRef.current.register(hotkeyString, callbackRef.current, {
|
|
102
|
-
...optionsRef.current,
|
|
103
|
-
target: resolvedTarget
|
|
104
|
-
});
|
|
105
|
-
prevTargetRef.current = resolvedTarget;
|
|
106
|
-
prevHotkeyRef.current = hotkeyString;
|
|
107
|
-
});
|
|
108
|
-
useEffect(() => {
|
|
109
|
-
return () => {
|
|
110
|
-
if (registrationRef.current?.isActive) {
|
|
111
|
-
registrationRef.current.unregister();
|
|
112
|
-
registrationRef.current = null;
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
}, []);
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
if (registrationRef.current?.isActive) {
|
|
118
|
-
registrationRef.current.callback = callback;
|
|
119
|
-
registrationRef.current.setOptions(optionsWithoutTarget);
|
|
120
|
-
}
|
|
121
|
-
});
|
|
4
|
+
function useHotkey(hotkey, callback, options) {
|
|
5
|
+
useHotkeys([{
|
|
6
|
+
hotkey,
|
|
7
|
+
callback,
|
|
8
|
+
options
|
|
9
|
+
}]);
|
|
122
10
|
}
|
|
123
11
|
|
|
124
12
|
//#endregion
|
package/dist/useHotkeyHint.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { HeldModifierOptions, RegisterableHotkey } from "@tanstack/hotkeys";
|
|
1
|
+
import { AppHotkey, HeldModifierOptions, RegisterableHotkey } from "@tanstack/hotkeys";
|
|
2
2
|
//#region src/useHotkeyHint.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Returns a boolean indicating whether held modifiers reveal this shortcut.
|
|
5
5
|
* Only visibility changes from held-key updates cause a rerender.
|
|
6
6
|
* Uses the nonempty-subset/AltGr rules of `matchesHeldModifiers`; `exact` requires
|
|
7
|
-
* every modifier.
|
|
8
|
-
*
|
|
7
|
+
* every modifier. A sequence reveals its hint from its first step, and an app
|
|
8
|
+
* hotkey uses its effective hotkey (false while unbound). Combine with the
|
|
9
|
+
* action's enabled state before showing a badge. This helper does not register
|
|
10
|
+
* a shortcut or check whether its target is focused.
|
|
9
11
|
*/
|
|
10
|
-
export declare function useHotkeyHint(hotkey: RegisterableHotkey, options?: HeldModifierOptions): boolean;
|
|
12
|
+
export declare function useHotkeyHint(hotkey: RegisterableHotkey | AppHotkey, options?: HeldModifierOptions): boolean;
|
|
11
13
|
//#endregion
|
package/dist/useHotkeyHint.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useAppHotkeysStoreState } from "./_appHotkeysStore.js";
|
|
2
|
+
import { getKeyStateTracker, isAppHotkey, matchesHeldModifiers, resolveAppHotkey } from "@tanstack/hotkeys";
|
|
2
3
|
import { useSelector } from "@tanstack/preact-store";
|
|
3
4
|
|
|
4
5
|
//#region src/useHotkeyHint.ts
|
|
@@ -6,11 +7,15 @@ import { useSelector } from "@tanstack/preact-store";
|
|
|
6
7
|
* Returns a boolean indicating whether held modifiers reveal this shortcut.
|
|
7
8
|
* Only visibility changes from held-key updates cause a rerender.
|
|
8
9
|
* Uses the nonempty-subset/AltGr rules of `matchesHeldModifiers`; `exact` requires
|
|
9
|
-
* every modifier.
|
|
10
|
-
*
|
|
10
|
+
* every modifier. A sequence reveals its hint from its first step, and an app
|
|
11
|
+
* hotkey uses its effective hotkey (false while unbound). Combine with the
|
|
12
|
+
* action's enabled state before showing a badge. This helper does not register
|
|
13
|
+
* a shortcut or check whether its target is focused.
|
|
11
14
|
*/
|
|
12
15
|
function useHotkeyHint(hotkey, options = {}) {
|
|
13
|
-
|
|
16
|
+
const store = useAppHotkeysStoreState();
|
|
17
|
+
const resolved = isAppHotkey(hotkey) ? resolveAppHotkey(hotkey, store).hotkey : hotkey;
|
|
18
|
+
return useSelector(getKeyStateTracker().store, (state) => resolved !== null && matchesHeldModifiers(resolved, state.heldKeys, options));
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
//#endregion
|