@tanstack/react-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 React, { ReactNode } from "react";
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: ReactNode;
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>): React.JSX.Element;
70
+ export declare function HotkeysProvider<T extends AppHotkeys = AppHotkeys>({ children, appHotkeys, customHotkeys, onCustomHotkeysChange, defaultOptions, scope, exclusive, enabled }: HotkeysProviderProps<T>): React.JSX.Element;
51
71
  export declare function useHotkeysContext(): HotkeysContextValue | null;
52
72
  export declare function useDefaultHotkeysOptions(): HotkeysProviderOptions;
53
73
  //#endregion
@@ -1,12 +1,22 @@
1
1
  import { AppHotkeysStore, publishAppHotkeysStore } from "@tanstack/hotkeys";
2
2
  import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
3
+ import { activateHotkeysProviderScope, getHotkeysProviderScopeKey, mergeHotkeysProviderDefaults } from "@tanstack/hotkeys/adapter";
3
4
 
4
5
  //#region src/HotkeysProvider.tsx
5
6
  const HotkeysContext = createContext(null);
6
7
  const DEFAULT_OPTIONS = {};
8
+ const NO_PARENT = {
9
+ appHotkeysStore: null,
10
+ defaultOptions: DEFAULT_OPTIONS,
11
+ enabled: true
12
+ };
7
13
  /**
8
14
  * Provides default options, app hotkeys, and the user's custom hotkeys.
9
15
  *
16
+ * Nest providers to create hotkey scopes. A nested provider inherits app
17
+ * hotkeys and merges default options. With `scope`, its hotkeys fire only
18
+ * while it is mounted and enabled; with `exclusive`, only its hotkeys fire.
19
+ *
10
20
  * @example
11
21
  * ```tsx
12
22
  * <HotkeysProvider
@@ -19,12 +29,34 @@ const DEFAULT_OPTIONS = {};
19
29
  * <App />
20
30
  * </HotkeysProvider>
21
31
  * ```
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * function EditDialog() {
36
+ * return (
37
+ * <HotkeysProvider scope="edit-dialog" exclusive={{ allow: ['global'] }}>
38
+ * <DialogBody />
39
+ * </HotkeysProvider>
40
+ * )
41
+ * }
42
+ * ```
22
43
  */
23
- function HotkeysProvider({ children, appHotkeys, customHotkeys, onCustomHotkeysChange, defaultOptions = DEFAULT_OPTIONS }) {
44
+ function HotkeysProvider({ children, appHotkeys, customHotkeys, onCustomHotkeysChange, defaultOptions = DEFAULT_OPTIONS, scope, exclusive = false, enabled = true }) {
45
+ const parent = useContext(HotkeysContext) ?? NO_PARENT;
24
46
  const [store] = useState(() => appHotkeys ? new AppHotkeysStore(appHotkeys, {
25
47
  customHotkeys,
26
48
  onCustomHotkeysChange
27
49
  }) : null);
50
+ const active = parent.enabled && enabled;
51
+ const scopeKey = getHotkeysProviderScopeKey({
52
+ scope,
53
+ exclusive,
54
+ active,
55
+ parent: parent.defaultOptions.hotkey?.scope
56
+ });
57
+ useEffect(() => {
58
+ return scopeKey ? activateHotkeysProviderScope(scopeKey) : void 0;
59
+ }, [scopeKey]);
28
60
  useEffect(() => {
29
61
  store?.setOptions({ onCustomHotkeysChange });
30
62
  }, [store, onCustomHotkeysChange]);
@@ -36,9 +68,16 @@ function HotkeysProvider({ children, appHotkeys, customHotkeys, onCustomHotkeysC
36
68
  return publishAppHotkeysStore(store);
37
69
  }, [store]);
38
70
  const contextValue = useMemo(() => ({
39
- appHotkeysStore: store,
40
- defaultOptions
41
- }), [store, defaultOptions]);
71
+ appHotkeysStore: store ?? parent.appHotkeysStore,
72
+ defaultOptions: mergeHotkeysProviderDefaults(parent.defaultOptions, defaultOptions, scope),
73
+ enabled: active
74
+ }), [
75
+ store,
76
+ parent,
77
+ defaultOptions,
78
+ scope,
79
+ active
80
+ ]);
42
81
  return /* @__PURE__ */ React.createElement(HotkeysContext.Provider, { value: contextValue }, children);
43
82
  }
44
83
  function useHotkeysContext() {
@@ -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 { AdapterAppHotkeyOptions, AdapterChordOptions, AdapterCommonOptions, AdapterHotkeyOptions, AdapterOptionsFor, AdapterSequenceOptions } from "@tanstack/hotkeys/adapter";
3
3
  //#region src/useHotkey.d.ts
4
4
  /** A React ref, element, document, or window to listen on. */
@@ -37,7 +37,7 @@ export type UseAppHotkeyOptions<T extends AppHotkey = AppHotkey> = AdapterAppHot
37
37
  * Options merge in this order: provider defaults, app hotkey declaration,
38
38
  * call-site options.
39
39
  *
40
- * @param hotkey - A chord string, RawHotkey object, sequence, or app hotkey
40
+ * @param hotkey - A chord string, RawHotkeyChord object, sequence, or app hotkey
41
41
  * @param callback - The function to call when the hotkey is pressed
42
42
  * @param options - Options for the hotkey behavior
43
43
  *
@@ -55,11 +55,11 @@ export type UseAppHotkeyOptions<T extends AppHotkey = AppHotkey> = AdapterAppHot
55
55
  * @example
56
56
  * ```tsx
57
57
  * function Toolbar() {
58
- * const hotkeys = useAppHotkeys()
59
- * useHotkey(hotkeys['doc.save'], () => save())
58
+ * const appHotkeys = useAppHotkeys()
59
+ * useHotkey(appHotkeys['doc.save'], () => save())
60
60
  * }
61
61
  * ```
62
62
  */
63
63
  export declare function useHotkey<T extends AppHotkey>(hotkey: T, callback: HotkeyCallback, options?: UseAppHotkeyOptions<T>): void;
64
- export declare function useHotkey<T extends RegisterableHotkey>(hotkey: T, callback: HotkeyCallback, options?: UseHotkeyOptionsFor<T>): void;
64
+ export declare function useHotkey<const T extends RegisterableHotkey>(hotkey: T, callback: HotkeyCallback<HotkeyCallbackContextFor<T>>, options?: UseHotkeyOptionsFor<T>): void;
65
65
  //#endregion
@@ -48,7 +48,7 @@ export interface ReactHotkeyRecorder<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/react-hotkeys",
3
- "version": "0.14.4",
3
+ "version": "0.15.0",
4
4
  "description": "React 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/react-store": "^0.11.2"
38
38
  },
39
39
  "devDependencies": {