@tanstack/solid-hotkeys 0.13.0 → 0.14.1
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 +39 -8
- package/dist/HotkeysProvider.js +36 -5
- package/dist/appHotkeys.d.ts +42 -0
- package/dist/appHotkeys.js +64 -0
- package/dist/createHotkey.d.ts +33 -60
- package/dist/createHotkey.js +6 -113
- package/dist/createHotkeyHint.d.ts +6 -7
- package/dist/createHotkeyHint.js +13 -7
- package/dist/createHotkeyRecorder.d.ts +22 -41
- package/dist/createHotkeyRecorder.js +9 -52
- package/dist/createHotkeyRegistrations.d.ts +14 -17
- package/dist/createHotkeyRegistrations.js +14 -13
- package/dist/createHotkeys.d.ts +16 -43
- package/dist/createHotkeys.js +22 -92
- package/dist/defaultOptions.js +14 -1
- package/dist/index.d.ts +6 -8
- package/dist/index.js +6 -8
- package/package.json +2 -2
- package/dist/createHotkeySequence.d.ts +0 -53
- package/dist/createHotkeySequence.js +0 -106
- package/dist/createHotkeySequenceRecorder.d.ts +0 -16
- package/dist/createHotkeySequenceRecorder.js +0 -43
- package/dist/createHotkeySequences.d.ts +0 -54
- package/dist/createHotkeySequences.js +0 -111
|
@@ -1,21 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { HotkeyRecorderOptions, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
1
|
+
import { CreateHotkeyCommonOptions } from "./createHotkey.js";
|
|
2
|
+
import { AppHotkeys, AppHotkeysStore, CustomHotkeys, HotkeyChordRecorderOptions, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
4
3
|
import { JSX, ParentComponent } from "solid-js";
|
|
5
4
|
//#region src/HotkeysProvider.d.ts
|
|
6
5
|
export interface HotkeysProviderOptions {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Defaults for every hotkey registered by `createHotkey` and `createHotkeys`.
|
|
8
|
+
* `requireReset` applies to chords and `timeout` applies to sequences.
|
|
9
|
+
*/
|
|
10
|
+
hotkey?: CreateHotkeyCommonOptions;
|
|
11
|
+
/** Defaults for every `createHotkeyRecorder`. */
|
|
12
|
+
hotkeyRecorder?: Partial<Omit<HotkeyChordRecorderOptions & HotkeySequenceRecorderOptions, 'maxSteps' | 'onRecord' | 'validate'>>;
|
|
11
13
|
}
|
|
12
14
|
interface HotkeysContextValue {
|
|
13
|
-
|
|
15
|
+
readonly appHotkeysStore: AppHotkeysStore | null;
|
|
16
|
+
readonly defaultOptions: HotkeysProviderOptions;
|
|
14
17
|
}
|
|
15
18
|
export interface HotkeysProviderProps {
|
|
16
19
|
children: JSX.Element;
|
|
20
|
+
/**
|
|
21
|
+
* Every app hotkey, from `defineAppHotkeys`. Read when the provider is
|
|
22
|
+
* created; listed by `createAppHotkeyList` whether or not they are registered.
|
|
23
|
+
*/
|
|
24
|
+
appHotkeys?: AppHotkeys;
|
|
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 | null;
|
|
30
|
+
/** Called with the custom hotkeys after every change, ready to save. */
|
|
31
|
+
onCustomHotkeysChange?: (customHotkeys: CustomHotkeys) => void;
|
|
17
32
|
defaultOptions?: HotkeysProviderOptions;
|
|
18
33
|
}
|
|
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
|
+
*/
|
|
19
50
|
export declare const HotkeysProvider: ParentComponent<HotkeysProviderProps>;
|
|
20
51
|
export declare function useHotkeysContext(): HotkeysContextValue | null;
|
|
21
52
|
export declare function useDefaultHotkeysOptions(): HotkeysProviderOptions;
|
package/dist/HotkeysProvider.js
CHANGED
|
@@ -1,14 +1,45 @@
|
|
|
1
|
+
import { AppHotkeysStore, publishAppHotkeysStore } from "@tanstack/hotkeys";
|
|
1
2
|
import { createComponent } from "solid-js/web";
|
|
2
|
-
import { createContext, useContext } from "solid-js";
|
|
3
|
+
import { createContext, createEffect, onCleanup, onMount, useContext } from "solid-js";
|
|
3
4
|
|
|
4
5
|
//#region src/HotkeysProvider.tsx
|
|
5
6
|
const HotkeysContext = createContext(null);
|
|
6
7
|
const DEFAULT_OPTIONS = {};
|
|
8
|
+
/**
|
|
9
|
+
* Provides default options, app hotkeys, and the user's custom hotkeys.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <HotkeysProvider
|
|
14
|
+
* appHotkeys={appHotkeys}
|
|
15
|
+
* customHotkeys={JSON.parse(localStorage.getItem('hotkeys') ?? '{}')}
|
|
16
|
+
* onCustomHotkeysChange={(custom) =>
|
|
17
|
+
* localStorage.setItem('hotkeys', JSON.stringify(custom))
|
|
18
|
+
* }
|
|
19
|
+
* >
|
|
20
|
+
* <App />
|
|
21
|
+
* </HotkeysProvider>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
7
24
|
const HotkeysProvider = (props) => {
|
|
8
|
-
|
|
9
|
-
|
|
25
|
+
const store = props.appHotkeys ? new AppHotkeysStore(props.appHotkeys, {
|
|
26
|
+
customHotkeys: props.customHotkeys,
|
|
27
|
+
onCustomHotkeysChange: (custom) => props.onCustomHotkeysChange?.(custom)
|
|
28
|
+
}) : null;
|
|
29
|
+
createEffect(() => {
|
|
30
|
+
if (props.appHotkeys) store?.setAppHotkeys(props.appHotkeys);
|
|
31
|
+
});
|
|
32
|
+
onMount(() => {
|
|
33
|
+
if (store) onCleanup(publishAppHotkeysStore(store));
|
|
34
|
+
});
|
|
35
|
+
const contextValue = {
|
|
36
|
+
appHotkeysStore: store,
|
|
37
|
+
get defaultOptions() {
|
|
10
38
|
return props.defaultOptions ?? DEFAULT_OPTIONS;
|
|
11
|
-
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return createComponent(HotkeysContext.Provider, {
|
|
42
|
+
value: contextValue,
|
|
12
43
|
get children() {
|
|
13
44
|
return props.children;
|
|
14
45
|
}
|
|
@@ -18,7 +49,7 @@ function useHotkeysContext() {
|
|
|
18
49
|
return useContext(HotkeysContext);
|
|
19
50
|
}
|
|
20
51
|
function useDefaultHotkeysOptions() {
|
|
21
|
-
return useContext(HotkeysContext)?.defaultOptions ??
|
|
52
|
+
return useContext(HotkeysContext)?.defaultOptions ?? DEFAULT_OPTIONS;
|
|
22
53
|
}
|
|
23
54
|
|
|
24
55
|
//#endregion
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AppHotkey, AppHotkeyListItem, CustomHotkeys, Hotkey, RegisteredAppHotkeys } from "@tanstack/hotkeys";
|
|
2
|
+
//#region src/appHotkeys.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Returns an accessor for the effective app hotkeys from `HotkeysProvider`,
|
|
5
|
+
* typed by `Register`: each declaration with the user's custom hotkey applied.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const hotkeys = useAppHotkeys()
|
|
10
|
+
* createHotkey(() => hotkeys()['doc.save'], () => save())
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function useAppHotkeys(): () => RegisteredAppHotkeys;
|
|
14
|
+
/**
|
|
15
|
+
* Lists every app hotkey with its live status, in declaration order, for
|
|
16
|
+
* cheat sheets and settings screens. App hotkeys whose components have not
|
|
17
|
+
* mounted are `'inactive'`; those without a hotkey are `'unbound'`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createAppHotkeyList(): () => Array<AppHotkeyListItem<RegisteredAppHotkeys[keyof RegisteredAppHotkeys]>>;
|
|
20
|
+
/** Custom hotkey state and actions returned by `useCustomHotkeys`. */
|
|
21
|
+
export interface UseCustomHotkeysResult {
|
|
22
|
+
/** The user's hotkeys that differ from the defaults. */
|
|
23
|
+
customHotkeys: () => CustomHotkeys;
|
|
24
|
+
/**
|
|
25
|
+
* Sets the user's hotkey for an app hotkey. `null` unbinds it.
|
|
26
|
+
* Returns conflicting app hotkeys; when any exist, nothing changes.
|
|
27
|
+
*/
|
|
28
|
+
setCustomHotkey: (id: keyof RegisteredAppHotkeys & string, hotkey: Hotkey | null) => Array<AppHotkey>;
|
|
29
|
+
/** Restores the default hotkey for an app hotkey. */
|
|
30
|
+
resetCustomHotkey: (id: keyof RegisteredAppHotkeys & string) => void;
|
|
31
|
+
/** Restores every default hotkey. */
|
|
32
|
+
resetCustomHotkeys: () => void;
|
|
33
|
+
/** Other app hotkeys that would collide if `id` used `hotkey`. */
|
|
34
|
+
findConflicts: (id: keyof RegisteredAppHotkeys & string, hotkey: Hotkey) => Array<AppHotkey>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Reads and changes the user's custom hotkeys. Changes apply to every
|
|
38
|
+
* registered app hotkey immediately and are passed to the provider's
|
|
39
|
+
* `onCustomHotkeysChange`, ready to save.
|
|
40
|
+
*/
|
|
41
|
+
export declare function useCustomHotkeys(): UseCustomHotkeysResult;
|
|
42
|
+
//#endregion
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useAppHotkeysStoreSource } from "./defaultOptions.js";
|
|
2
|
+
import { createHotkeyRegistrations } from "./createHotkeyRegistrations.js";
|
|
3
|
+
import { createMemo } from "solid-js";
|
|
4
|
+
|
|
5
|
+
//#region src/appHotkeys.ts
|
|
6
|
+
function useRequiredStore(name) {
|
|
7
|
+
const source = useAppHotkeysStoreSource();
|
|
8
|
+
if (!source.store) throw new Error(`${name} requires a HotkeysProvider with an appHotkeys prop`);
|
|
9
|
+
return {
|
|
10
|
+
store: source.store,
|
|
11
|
+
state: source.state
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns an accessor for the effective app hotkeys from `HotkeysProvider`,
|
|
16
|
+
* typed by `Register`: each declaration with the user's custom hotkey applied.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* const hotkeys = useAppHotkeys()
|
|
21
|
+
* createHotkey(() => hotkeys()['doc.save'], () => save())
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
function useAppHotkeys() {
|
|
25
|
+
const { store, state } = useRequiredStore("useAppHotkeys");
|
|
26
|
+
return createMemo(() => {
|
|
27
|
+
state();
|
|
28
|
+
return store.appHotkeys;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Lists every app hotkey with its live status, in declaration order, for
|
|
33
|
+
* cheat sheets and settings screens. App hotkeys whose components have not
|
|
34
|
+
* mounted are `'inactive'`; those without a hotkey are `'unbound'`.
|
|
35
|
+
*/
|
|
36
|
+
function createAppHotkeyList() {
|
|
37
|
+
const { store, state } = useRequiredStore("createAppHotkeyList");
|
|
38
|
+
const registrations = createHotkeyRegistrations();
|
|
39
|
+
return createMemo(() => {
|
|
40
|
+
state();
|
|
41
|
+
return store.getList(registrations());
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Reads and changes the user's custom hotkeys. Changes apply to every
|
|
46
|
+
* registered app hotkey immediately and are passed to the provider's
|
|
47
|
+
* `onCustomHotkeysChange`, ready to save.
|
|
48
|
+
*/
|
|
49
|
+
function useCustomHotkeys() {
|
|
50
|
+
const { store, state } = useRequiredStore("useCustomHotkeys");
|
|
51
|
+
return {
|
|
52
|
+
customHotkeys: createMemo(() => {
|
|
53
|
+
state();
|
|
54
|
+
return store.customHotkeys;
|
|
55
|
+
}),
|
|
56
|
+
setCustomHotkey: (id, hotkey) => store.setCustomHotkey(id, hotkey),
|
|
57
|
+
resetCustomHotkey: (id) => store.resetCustomHotkey(id),
|
|
58
|
+
resetCustomHotkeys: () => store.resetCustomHotkeys(),
|
|
59
|
+
findConflicts: (id, hotkey) => store.findConflicts(id, hotkey)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
export { createAppHotkeyList, useAppHotkeys, useCustomHotkeys };
|
package/dist/createHotkey.d.ts
CHANGED
|
@@ -1,69 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AppHotkey, HotkeyCallback, RegisterableHotkey, Target } from "@tanstack/hotkeys";
|
|
2
|
+
import { AdapterAppHotkeyOptions, AdapterChordOptions, AdapterCommonOptions, AdapterHotkeyOptions, AdapterOptionsFor, AdapterSequenceOptions } from "@tanstack/hotkeys/adapter";
|
|
2
3
|
//#region src/createHotkey.d.ts
|
|
3
|
-
export interface CreateHotkeyOptions extends Omit<HotkeyOptions, 'target'> {
|
|
4
|
-
/**
|
|
5
|
-
* The DOM element to attach the event listener to.
|
|
6
|
-
* Can be a direct DOM element, an accessor (for reactive targets that become
|
|
7
|
-
* available after mount), or null. Defaults to document.
|
|
8
|
-
* When using scoped targets, pass an accessor: () => ({ target: elementSignal() })
|
|
9
|
-
* so the hotkey waits for the element to be attached before registering.
|
|
10
|
-
*/
|
|
11
|
-
target?: HTMLElement | Document | Window | null;
|
|
12
|
-
}
|
|
13
4
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* })
|
|
37
|
-
*
|
|
38
|
-
* return <button onClick={() => setCount(c => c + 1)}>Count: {count()}</button>
|
|
39
|
-
* }
|
|
40
|
-
* ```
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* ```tsx
|
|
44
|
-
* function Modal(props) {
|
|
45
|
-
* // enabled option is reactive
|
|
46
|
-
* createHotkey('Escape', () => {
|
|
47
|
-
* props.onClose()
|
|
48
|
-
* }, () => ({ enabled: props.isOpen }))
|
|
5
|
+
* The element to listen on. A `null` target skips registration, so pass an
|
|
6
|
+
* accessor such as `() => ({ target: element() })` for elements that attach
|
|
7
|
+
* after mount.
|
|
8
|
+
*/
|
|
9
|
+
export type CreateHotkeyTarget = Target | null;
|
|
10
|
+
/** Options shared by chords and sequences. */
|
|
11
|
+
export type CreateHotkeyOptions = AdapterHotkeyOptions<CreateHotkeyTarget>;
|
|
12
|
+
/** Options for a chord such as `'Mod+S'`. */
|
|
13
|
+
export type CreateHotkeyChordOptions = AdapterChordOptions<CreateHotkeyTarget>;
|
|
14
|
+
/** Options for a sequence such as `['G', 'G']`. */
|
|
15
|
+
export type CreateHotkeySequenceOptions = AdapterSequenceOptions<CreateHotkeyTarget>;
|
|
16
|
+
/** Options applied to every hotkey, such as provider defaults. Each applies to the kinds that support it. */
|
|
17
|
+
export type CreateHotkeyCommonOptions = AdapterCommonOptions<CreateHotkeyTarget>;
|
|
18
|
+
/** The options type for a hotkey: chord options for chords, sequence options for sequences. */
|
|
19
|
+
export type CreateHotkeyOptionsFor<T extends RegisterableHotkey> = AdapterOptionsFor<T, CreateHotkeyTarget>;
|
|
20
|
+
/** Call-site options for an app hotkey, typed by its declared default kind. */
|
|
21
|
+
export type CreateAppHotkeyOptions<T extends AppHotkey = AppHotkey> = AdapterAppHotkeyOptions<T, CreateHotkeyTarget>;
|
|
22
|
+
type MaybeAccessor<T> = T | (() => T);
|
|
23
|
+
/**
|
|
24
|
+
* Solid primitive for registering a hotkey: a chord such as `'Mod+S'`, a
|
|
25
|
+
* sequence such as `['G', 'G']`, or an app hotkey from `defineAppHotkeys`.
|
|
26
|
+
* The hotkey and options may be accessors.
|
|
49
27
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* ```
|
|
28
|
+
* Chords accept `requireReset`; sequences accept `timeout`. A one-step
|
|
29
|
+
* sequence registers as its chord. An app hotkey uses its effective hotkey
|
|
30
|
+
* from `HotkeysProvider` (including the user's custom hotkey) and its
|
|
31
|
+
* declared meta and options, and registers nothing while unbound.
|
|
55
32
|
*
|
|
56
33
|
* @example
|
|
57
34
|
* ```tsx
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* // Scoped to a specific element - use accessor so hotkey waits for ref
|
|
62
|
-
* createHotkey('Mod+S', save, () => ({ target: editorRef() }))
|
|
63
|
-
*
|
|
64
|
-
* return <div ref={setEditorRef}>...</div>
|
|
65
|
-
* }
|
|
35
|
+
* createHotkey('Mod+S', () => save())
|
|
36
|
+
* createHotkey(['G', 'G'], () => scrollToTop(), { timeout: 500 })
|
|
37
|
+
* createHotkey('Escape', () => close(), () => ({ enabled: isOpen() }))
|
|
66
38
|
* ```
|
|
67
39
|
*/
|
|
68
|
-
export declare function createHotkey(hotkey:
|
|
40
|
+
export declare function createHotkey<T extends AppHotkey>(hotkey: MaybeAccessor<T>, callback: HotkeyCallback, options?: MaybeAccessor<CreateAppHotkeyOptions<T>>): void;
|
|
41
|
+
export declare function createHotkey<T extends RegisterableHotkey>(hotkey: MaybeAccessor<T>, callback: HotkeyCallback, options?: MaybeAccessor<CreateHotkeyOptionsFor<T>>): void;
|
|
69
42
|
//#endregion
|
package/dist/createHotkey.js
CHANGED
|
@@ -1,119 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { detectPlatform, getHotkeyManager, normalizeRegisterableHotkey } from "@tanstack/hotkeys";
|
|
3
|
-
import { createEffect, onCleanup, untrack } from "solid-js";
|
|
1
|
+
import { createHotkeys } from "./createHotkeys.js";
|
|
4
2
|
|
|
5
3
|
//#region src/createHotkey.ts
|
|
6
|
-
/**
|
|
7
|
-
* SolidJS primitive for registering a keyboard hotkey.
|
|
8
|
-
*
|
|
9
|
-
* Uses the singleton HotkeyManager for efficient event handling.
|
|
10
|
-
* The callback receives both the keyboard event and a context object
|
|
11
|
-
* containing the hotkey string and parsed hotkey.
|
|
12
|
-
*
|
|
13
|
-
* This primitive automatically tracks reactive dependencies and updates
|
|
14
|
-
* the registration when options or the callback change.
|
|
15
|
-
*
|
|
16
|
-
* @param hotkey - The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object (supports `mod` for cross-platform)
|
|
17
|
-
* @param callback - The function to call when the hotkey is pressed
|
|
18
|
-
* @param options - Options for the hotkey behavior
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```tsx
|
|
22
|
-
* function SaveButton() {
|
|
23
|
-
* const [count, setCount] = createSignal(0)
|
|
24
|
-
*
|
|
25
|
-
* // Callback always has access to latest count value
|
|
26
|
-
* createHotkey('Mod+S', (event, { hotkey }) => {
|
|
27
|
-
* console.log(`Save triggered, count is ${count()}`)
|
|
28
|
-
* handleSave()
|
|
29
|
-
* })
|
|
30
|
-
*
|
|
31
|
-
* return <button onClick={() => setCount(c => c + 1)}>Count: {count()}</button>
|
|
32
|
-
* }
|
|
33
|
-
* ```
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```tsx
|
|
37
|
-
* function Modal(props) {
|
|
38
|
-
* // enabled option is reactive
|
|
39
|
-
* createHotkey('Escape', () => {
|
|
40
|
-
* props.onClose()
|
|
41
|
-
* }, () => ({ enabled: props.isOpen }))
|
|
42
|
-
*
|
|
43
|
-
* return <Show when={props.isOpen}>
|
|
44
|
-
* <div class="modal">...</div>
|
|
45
|
-
* </Show>
|
|
46
|
-
* }
|
|
47
|
-
* ```
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```tsx
|
|
51
|
-
* function Editor() {
|
|
52
|
-
* const [editorRef, setEditorRef] = createSignal<HTMLDivElement | null>(null)
|
|
53
|
-
*
|
|
54
|
-
* // Scoped to a specific element - use accessor so hotkey waits for ref
|
|
55
|
-
* createHotkey('Mod+S', save, () => ({ target: editorRef() }))
|
|
56
|
-
*
|
|
57
|
-
* return <div ref={setEditorRef}>...</div>
|
|
58
|
-
* }
|
|
59
|
-
* ```
|
|
60
|
-
*/
|
|
61
4
|
function createHotkey(hotkey, callback, options = {}) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
onCleanup(() => {
|
|
68
|
-
if (registration?.isActive) {
|
|
69
|
-
registration.unregister();
|
|
70
|
-
registration = null;
|
|
71
|
-
}
|
|
72
|
-
lastHotkeyString = null;
|
|
73
|
-
lastTarget = null;
|
|
74
|
-
});
|
|
75
|
-
createEffect(() => {
|
|
76
|
-
const resolvedHotkey = typeof hotkey === "function" ? hotkey() : hotkey;
|
|
77
|
-
const resolvedOptions = typeof options === "function" ? options() : options;
|
|
78
|
-
const mergedOptions = {
|
|
79
|
-
...defaultOptions().hotkey,
|
|
80
|
-
...resolvedOptions
|
|
81
|
-
};
|
|
82
|
-
const platform = mergedOptions.platform ?? detectPlatform();
|
|
83
|
-
const hotkeyString = normalizeRegisterableHotkey(resolvedHotkey, platform);
|
|
84
|
-
const resolvedTarget = "target" in mergedOptions ? mergedOptions.target ?? null : typeof document !== "undefined" ? document : null;
|
|
85
|
-
if (!resolvedTarget) {
|
|
86
|
-
if (registration?.isActive) {
|
|
87
|
-
registration.unregister();
|
|
88
|
-
registration = null;
|
|
89
|
-
}
|
|
90
|
-
lastHotkeyString = null;
|
|
91
|
-
lastTarget = null;
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const { target: _target, ...optionsWithoutTarget } = mergedOptions;
|
|
95
|
-
untrack(() => {
|
|
96
|
-
if (registration?.isActive && lastHotkeyString === hotkeyString && lastTarget === resolvedTarget) {
|
|
97
|
-
registration.callback = callback;
|
|
98
|
-
registration.setOptions(optionsWithoutTarget);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (registration?.isActive) {
|
|
102
|
-
registration.unregister();
|
|
103
|
-
registration = null;
|
|
104
|
-
}
|
|
105
|
-
registration = manager.register(hotkeyString, callback, {
|
|
106
|
-
...optionsWithoutTarget,
|
|
107
|
-
target: resolvedTarget
|
|
108
|
-
});
|
|
109
|
-
if (registration.isActive) {
|
|
110
|
-
registration.callback = callback;
|
|
111
|
-
registration.setOptions(optionsWithoutTarget);
|
|
112
|
-
}
|
|
113
|
-
lastHotkeyString = hotkeyString;
|
|
114
|
-
lastTarget = resolvedTarget;
|
|
115
|
-
});
|
|
116
|
-
});
|
|
5
|
+
createHotkeys(() => [{
|
|
6
|
+
hotkey: typeof hotkey === "function" ? hotkey() : hotkey,
|
|
7
|
+
callback,
|
|
8
|
+
options: typeof options === "function" ? options() : options
|
|
9
|
+
}]);
|
|
117
10
|
}
|
|
118
11
|
|
|
119
12
|
//#endregion
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { HeldModifierOptions, RegisterableHotkey } from "@tanstack/hotkeys";
|
|
1
|
+
import { AppHotkey, HeldModifierOptions, RegisterableHotkey } from "@tanstack/hotkeys";
|
|
2
2
|
//#region src/createHotkeyHint.d.ts
|
|
3
3
|
/**
|
|
4
|
-
* Returns
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* This helper does not register a shortcut or check whether its target is focused.
|
|
4
|
+
* Returns an accessor indicating whether held modifiers reveal this shortcut.
|
|
5
|
+
* A sequence reveals its hint from its first step, and an app hotkey uses its
|
|
6
|
+
* effective hotkey (false while unbound). This helper does not register a
|
|
7
|
+
* shortcut or check whether its target is focused.
|
|
9
8
|
*/
|
|
10
|
-
export declare function createHotkeyHint(hotkey: RegisterableHotkey | (() => RegisterableHotkey), options?: HeldModifierOptions | (() => HeldModifierOptions)): () => boolean;
|
|
9
|
+
export declare function createHotkeyHint(hotkey: RegisterableHotkey | AppHotkey | (() => RegisterableHotkey | AppHotkey), options?: HeldModifierOptions | (() => HeldModifierOptions)): () => boolean;
|
|
11
10
|
//#endregion
|
package/dist/createHotkeyHint.js
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useAppHotkeysStoreSource } from "./defaultOptions.js";
|
|
2
|
+
import { getKeyStateTracker, isAppHotkey, matchesHeldModifiers, resolveAppHotkey } from "@tanstack/hotkeys";
|
|
2
3
|
import { createMemo } from "solid-js";
|
|
3
4
|
import { useSelector } from "@tanstack/solid-store";
|
|
4
5
|
|
|
5
6
|
//#region src/createHotkeyHint.ts
|
|
6
7
|
/**
|
|
7
|
-
* Returns
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* This helper does not register a shortcut or check whether its target is focused.
|
|
8
|
+
* Returns an accessor indicating whether held modifiers reveal this shortcut.
|
|
9
|
+
* A sequence reveals its hint from its first step, and an app hotkey uses its
|
|
10
|
+
* effective hotkey (false while unbound). This helper does not register a
|
|
11
|
+
* shortcut or check whether its target is focused.
|
|
12
12
|
*/
|
|
13
13
|
function createHotkeyHint(hotkey, options = {}) {
|
|
14
|
+
const appHotkeys = useAppHotkeysStoreSource();
|
|
14
15
|
const held = useSelector(getKeyStateTracker().store, (state) => state.heldKeys);
|
|
15
|
-
return createMemo(() =>
|
|
16
|
+
return createMemo(() => {
|
|
17
|
+
appHotkeys.state();
|
|
18
|
+
const value = typeof hotkey === "function" ? hotkey() : hotkey;
|
|
19
|
+
const resolved = isAppHotkey(value) ? resolveAppHotkey(value, appHotkeys.store).hotkey : value;
|
|
20
|
+
return resolved !== null && matchesHeldModifiers(resolved, held(), typeof options === "function" ? options() : options);
|
|
21
|
+
});
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
//#endregion
|
|
@@ -1,57 +1,38 @@
|
|
|
1
|
-
import { Hotkey, HotkeyRecorderOptions } from "@tanstack/hotkeys";
|
|
1
|
+
import { Hotkey, HotkeyChord, HotkeyChordRecorderOptions, HotkeyRecorderOptions, HotkeySequence, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
2
2
|
//#region src/createHotkeyRecorder.d.ts
|
|
3
|
-
export interface SolidHotkeyRecorder {
|
|
3
|
+
export interface SolidHotkeyRecorder<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
|
+
* Solid primitive for recording keyboard shortcuts. Options may be an accessor.
|
|
17
21
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* This primitive uses `useSelector` from `@tanstack/solid-store` to subscribe
|
|
24
|
-
* to the recorder's store state (same pattern as useHotkeyRecorder in React).
|
|
25
|
-
*
|
|
26
|
-
* @param options - Configuration options for the recorder (or accessor function)
|
|
27
|
-
* @returns An object with recording state signals and control functions
|
|
22
|
+
* With the default `maxSteps: 1`, the first valid chord is recorded. With a
|
|
23
|
+
* larger `maxSteps`, each chord adds a step; the recording commits on Enter
|
|
24
|
+
* (unless `commitKeys: 'none'`), after `idleTimeoutMs`, or at `maxSteps`, and
|
|
25
|
+
* `onRecord` receives a chord (one step) or a sequence.
|
|
28
26
|
*
|
|
29
27
|
* @example
|
|
30
28
|
* ```tsx
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* const recorder = createHotkeyRecorder({
|
|
35
|
-
* onRecord: (hotkey) => {
|
|
36
|
-
* setShortcut(hotkey)
|
|
37
|
-
* },
|
|
38
|
-
* onCancel: () => {
|
|
39
|
-
* console.log('Recording cancelled')
|
|
40
|
-
* },
|
|
41
|
-
* })
|
|
42
|
-
*
|
|
43
|
-
* return (
|
|
44
|
-
* <div>
|
|
45
|
-
* <button onClick={recorder.startRecording}>
|
|
46
|
-
* {recorder.isRecording() ? 'Recording...' : 'Edit Shortcut'}
|
|
47
|
-
* </button>
|
|
48
|
-
* <Show when={recorder.recordedHotkey()}>
|
|
49
|
-
* <div>Recording: {recorder.recordedHotkey()}</div>
|
|
50
|
-
* </Show>
|
|
51
|
-
* </div>
|
|
52
|
-
* )
|
|
53
|
-
* }
|
|
29
|
+
* const recorder = createHotkeyRecorder({ onRecord: setShortcut })
|
|
30
|
+
* const sequenceRecorder = createHotkeyRecorder({ maxSteps: 3, onRecord: setShortcut })
|
|
54
31
|
* ```
|
|
55
32
|
*/
|
|
56
|
-
export declare function createHotkeyRecorder(options:
|
|
33
|
+
export declare function createHotkeyRecorder(options: HotkeyChordRecorderOptions): SolidHotkeyRecorder<HotkeyChord>;
|
|
34
|
+
export declare function createHotkeyRecorder(options: HotkeySequenceRecorderOptions): SolidHotkeyRecorder;
|
|
35
|
+
export declare function createHotkeyRecorder(options: () => HotkeySequenceRecorderOptions): SolidHotkeyRecorder;
|
|
36
|
+
export declare function createHotkeyRecorder(options: () => HotkeyChordRecorderOptions): SolidHotkeyRecorder<HotkeyChord>;
|
|
37
|
+
export declare function createHotkeyRecorder(options: () => HotkeyRecorderOptions): SolidHotkeyRecorder;
|
|
57
38
|
//#endregion
|