@tanstack/preact-hotkeys 0.14.4 → 0.15.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/README.md CHANGED
@@ -51,7 +51,7 @@
51
51
  # TanStack Hotkeys
52
52
 
53
53
  > [!NOTE]
54
- > TanStack Hotkeys is alpha. We are actively developing the library and are open to feedback and contributions.
54
+ > TanStack Hotkeys is in beta. We are actively developing the library and are open to feedback and contributions.
55
55
 
56
56
  Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objects, a cross-platform `Mod` key, a singleton Hotkey Manager, and utilities for cheatsheet UIs—built to stay SSR-friendly.
57
57
 
@@ -1,6 +1,7 @@
1
1
  import { UseHotkeyCommonOptions } from "./useHotkey.js";
2
2
  import { AppHotkeys, AppHotkeysStore, CustomHotkeys, HotkeyChordRecorderOptions, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
3
3
  import { ComponentChildren } from "preact";
4
+ import { HotkeysProviderScopeProps } from "@tanstack/hotkeys/adapter";
4
5
  //#region src/HotkeysProvider.d.ts
5
6
  export interface HotkeysProviderOptions {
6
7
  /**
@@ -13,9 +14,12 @@ export interface HotkeysProviderOptions {
13
14
  }
14
15
  interface HotkeysContextValue {
15
16
  appHotkeysStore: AppHotkeysStore | null;
17
+ /** Merged with every ancestor provider's defaults, including `hotkey.scope`. */
16
18
  defaultOptions: HotkeysProviderOptions;
19
+ /** Whether this provider and every ancestor are enabled. */
20
+ enabled: boolean;
17
21
  }
18
- export interface HotkeysProviderProps<T extends AppHotkeys = AppHotkeys> {
22
+ export interface HotkeysProviderProps<T extends AppHotkeys = AppHotkeys> extends HotkeysProviderScopeProps {
19
23
  children: ComponentChildren;
20
24
  /**
21
25
  * Every app hotkey, from `defineAppHotkeys`. Listed by `useAppHotkeyList`
@@ -29,11 +33,16 @@ export interface HotkeysProviderProps<T extends AppHotkeys = AppHotkeys> {
29
33
  customHotkeys?: CustomHotkeys<T> | null;
30
34
  /** Called with the custom hotkeys after every change, ready to save. */
31
35
  onCustomHotkeysChange?: (customHotkeys: CustomHotkeys<T>) => void;
36
+ /** Merged with the parent provider's defaults. */
32
37
  defaultOptions?: HotkeysProviderOptions;
33
38
  }
34
39
  /**
35
40
  * Provides default options, app hotkeys, and the user's custom hotkeys.
36
41
  *
42
+ * Nest providers to create hotkey scopes. A nested provider inherits app
43
+ * hotkeys and merges default options. With `scope`, its hotkeys fire only
44
+ * while it is mounted and enabled; with `exclusive`, only its hotkeys fire.
45
+ *
37
46
  * @example
38
47
  * ```tsx
39
48
  * <HotkeysProvider
@@ -46,8 +55,19 @@ export interface HotkeysProviderProps<T extends AppHotkeys = AppHotkeys> {
46
55
  * <App />
47
56
  * </HotkeysProvider>
48
57
  * ```
58
+ *
59
+ * @example
60
+ * ```tsx
61
+ * function EditDialog() {
62
+ * return (
63
+ * <HotkeysProvider scope="edit-dialog" exclusive={{ allow: ['global'] }}>
64
+ * <DialogBody />
65
+ * </HotkeysProvider>
66
+ * )
67
+ * }
68
+ * ```
49
69
  */
50
- export declare function HotkeysProvider<T extends AppHotkeys = AppHotkeys>({ children, appHotkeys, customHotkeys, onCustomHotkeysChange, defaultOptions }: HotkeysProviderProps<T>): import("preact").JSX.Element;
70
+ export declare function HotkeysProvider<T extends AppHotkeys = AppHotkeys>({ children, appHotkeys, customHotkeys, onCustomHotkeysChange, defaultOptions, scope, exclusive, enabled }: HotkeysProviderProps<T>): import("preact").JSX.Element;
51
71
  export declare function useHotkeysContext(): HotkeysContextValue | null;
52
72
  export declare function useDefaultHotkeysOptions(): HotkeysProviderOptions;
53
73
  //#endregion
@@ -1,14 +1,24 @@
1
1
  import { AppHotkeysStore, 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
5
  import { jsx } from "preact/jsx-runtime";
5
6
 
6
7
  //#region src/HotkeysProvider.tsx
7
8
  const HotkeysContext = createContext(null);
8
9
  const DEFAULT_OPTIONS = {};
10
+ const NO_PARENT = {
11
+ appHotkeysStore: null,
12
+ defaultOptions: DEFAULT_OPTIONS,
13
+ enabled: true
14
+ };
9
15
  /**
10
16
  * Provides default options, app hotkeys, and the user's custom hotkeys.
11
17
  *
18
+ * Nest providers to create hotkey scopes. A nested provider inherits app
19
+ * hotkeys and merges default options. With `scope`, its hotkeys fire only
20
+ * while it is mounted and enabled; with `exclusive`, only its hotkeys fire.
21
+ *
12
22
  * @example
13
23
  * ```tsx
14
24
  * <HotkeysProvider
@@ -21,12 +31,34 @@ const DEFAULT_OPTIONS = {};
21
31
  * <App />
22
32
  * </HotkeysProvider>
23
33
  * ```
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * function EditDialog() {
38
+ * return (
39
+ * <HotkeysProvider scope="edit-dialog" exclusive={{ allow: ['global'] }}>
40
+ * <DialogBody />
41
+ * </HotkeysProvider>
42
+ * )
43
+ * }
44
+ * ```
24
45
  */
25
- function HotkeysProvider({ children, appHotkeys, customHotkeys, onCustomHotkeysChange, defaultOptions = DEFAULT_OPTIONS }) {
46
+ function HotkeysProvider({ children, appHotkeys, customHotkeys, onCustomHotkeysChange, defaultOptions = DEFAULT_OPTIONS, scope, exclusive = false, enabled = true }) {
47
+ const parent = useContext(HotkeysContext) ?? NO_PARENT;
26
48
  const [store] = useState(() => appHotkeys ? new AppHotkeysStore(appHotkeys, {
27
49
  customHotkeys,
28
50
  onCustomHotkeysChange
29
51
  }) : null);
52
+ const active = parent.enabled && enabled;
53
+ const scopeKey = getHotkeysProviderScopeKey({
54
+ scope,
55
+ exclusive,
56
+ active,
57
+ parent: parent.defaultOptions.hotkey?.scope
58
+ });
59
+ useEffect(() => {
60
+ return scopeKey ? activateHotkeysProviderScope(scopeKey) : void 0;
61
+ }, [scopeKey]);
30
62
  useEffect(() => {
31
63
  store?.setOptions({ onCustomHotkeysChange });
32
64
  }, [store, onCustomHotkeysChange]);
@@ -38,9 +70,16 @@ function HotkeysProvider({ children, appHotkeys, customHotkeys, onCustomHotkeysC
38
70
  return publishAppHotkeysStore(store);
39
71
  }, [store]);
40
72
  const contextValue = useMemo(() => ({
41
- appHotkeysStore: store,
42
- defaultOptions
43
- }), [store, defaultOptions]);
73
+ appHotkeysStore: store ?? parent.appHotkeysStore,
74
+ defaultOptions: mergeHotkeysProviderDefaults(parent.defaultOptions, defaultOptions, scope),
75
+ enabled: active
76
+ }), [
77
+ store,
78
+ parent,
79
+ defaultOptions,
80
+ scope,
81
+ active
82
+ ]);
44
83
  return /* @__PURE__ */ jsx(HotkeysContext.Provider, {
45
84
  value: contextValue,
46
85
  children
@@ -7,8 +7,8 @@ import { AppHotkey, AppHotkeyListItem, CustomHotkeys, Hotkey, RegisteredAppHotke
7
7
  *
8
8
  * @example
9
9
  * ```tsx
10
- * const hotkeys = useAppHotkeys()
11
- * useHotkey(hotkeys['doc.save'], () => save())
10
+ * const appHotkeys = useAppHotkeys()
11
+ * useHotkey(appHotkeys['doc.save'], () => save())
12
12
  * ```
13
13
  */
14
14
  export declare function useAppHotkeys(): RegisteredAppHotkeys;
@@ -60,7 +60,7 @@ export interface UseCustomHotkeysResult {
60
60
  * ```tsx
61
61
  * const { setCustomHotkey, findConflicts } = useCustomHotkeys()
62
62
  * const recorder = useHotkeyRecorder({
63
- * maxSteps: 3,
63
+ * maxSteps: Infinity,
64
64
  * validate: (hotkey) =>
65
65
  * findConflicts('doc.save', hotkey).length === 0 || 'Already in use',
66
66
  * onRecord: (hotkey) => setCustomHotkey('doc.save', hotkey),
@@ -14,8 +14,8 @@ function useRequiredStore(hook) {
14
14
  *
15
15
  * @example
16
16
  * ```tsx
17
- * const hotkeys = useAppHotkeys()
18
- * useHotkey(hotkeys['doc.save'], () => save())
17
+ * const appHotkeys = useAppHotkeys()
18
+ * useHotkey(appHotkeys['doc.save'], () => save())
19
19
  * ```
20
20
  */
21
21
  function useAppHotkeys() {
@@ -57,7 +57,7 @@ function useAppHotkeyList() {
57
57
  * ```tsx
58
58
  * const { setCustomHotkey, findConflicts } = useCustomHotkeys()
59
59
  * const recorder = useHotkeyRecorder({
60
- * maxSteps: 3,
60
+ * maxSteps: Infinity,
61
61
  * validate: (hotkey) =>
62
62
  * findConflicts('doc.save', hotkey).length === 0 || 'Already in use',
63
63
  * onRecord: (hotkey) => setCustomHotkey('doc.save', hotkey),
@@ -1,4 +1,4 @@
1
- import { AppHotkey, HotkeyCallback, RegisterableHotkey } from "@tanstack/hotkeys";
1
+ import { AppHotkey, HotkeyCallback, HotkeyCallbackContextFor, RegisterableHotkey } from "@tanstack/hotkeys";
2
2
  import { RefObject } from "preact";
3
3
  import { AdapterAppHotkeyOptions, AdapterChordOptions, AdapterCommonOptions, AdapterHotkeyOptions, AdapterOptionsFor, AdapterSequenceOptions } from "@tanstack/hotkeys/adapter";
4
4
  //#region src/useHotkey.d.ts
@@ -38,7 +38,7 @@ 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, RawHotkey object, sequence, or app hotkey
41
+ * @param hotkey - A chord string, RawHotkeyChord object, sequence, or app hotkey
42
42
  * @param callback - The function to call when the hotkey is pressed
43
43
  * @param options - Options for the hotkey behavior
44
44
  *
@@ -56,11 +56,11 @@ export type UseAppHotkeyOptions<T extends AppHotkey = AppHotkey> = AdapterAppHot
56
56
  * @example
57
57
  * ```tsx
58
58
  * function Toolbar() {
59
- * const hotkeys = useAppHotkeys()
60
- * useHotkey(hotkeys['doc.save'], () => save())
59
+ * const appHotkeys = useAppHotkeys()
60
+ * useHotkey(appHotkeys['doc.save'], () => save())
61
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<T extends RegisterableHotkey>(hotkey: T, callback: HotkeyCallback, options?: UseHotkeyOptionsFor<T>): void;
65
+ export declare function useHotkey<const T extends RegisterableHotkey>(hotkey: T, callback: HotkeyCallback<HotkeyCallbackContextFor<T>>, options?: UseHotkeyOptionsFor<T>): void;
66
66
  //#endregion
@@ -48,7 +48,7 @@ export interface PreactHotkeyRecorder<THotkey extends Hotkey = Hotkey> {
48
48
  * @example
49
49
  * ```tsx
50
50
  * const recorder = useHotkeyRecorder({
51
- * maxSteps: 3,
51
+ * maxSteps: Infinity,
52
52
  * onRecord: (hotkey) => setShortcut(hotkey), // 'Mod+S' or ['G', 'H']
53
53
  * })
54
54
  * ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/preact-hotkeys",
3
- "version": "0.14.4",
3
+ "version": "0.15.0",
4
4
  "description": "Preact adapter for TanStack Hotkeys",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "dist"
34
34
  ],
35
35
  "dependencies": {
36
- "@tanstack/hotkeys": "0.15.0",
36
+ "@tanstack/hotkeys": "0.16.0",
37
37
  "@tanstack/preact-store": "^0.13.5"
38
38
  },
39
39
  "devDependencies": {