@tanstack/react-hotkeys 0.20.1 → 0.21.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 +7 -1
- package/dist/_usePlatform.js +21 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/useFormatForDisplay.d.ts +27 -0
- package/dist/useFormatForDisplay.js +40 -0
- package/dist/useHotkeyRecorder.js +8 -5
- package/package.json +3 -2
- package/skills/react-hotkeys/SKILL.md +4 -1
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { UseHotkeyCommonOptions } from "./useHotkey.js";
|
|
2
|
-
import { AppHotkeys, AppHotkeysStore, CustomHotkeys, HotkeyChordRecorderOptions, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
2
|
+
import { AppHotkeys, AppHotkeysStore, CustomHotkeys, HotkeyChordRecorderOptions, HotkeyPlatform, HotkeySequenceRecorderOptions } from "@tanstack/hotkeys";
|
|
3
3
|
import { ReactElement, ReactNode } from "react";
|
|
4
4
|
import { HotkeysProviderScopeProps } from "@tanstack/hotkeys/adapter";
|
|
5
5
|
//#region src/HotkeysProvider.d.ts
|
|
6
6
|
export interface HotkeysProviderOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The user's platform, for labels from `useFormatForDisplay`, hotkeys, and
|
|
9
|
+
* recorders. Defaults to detection. When server rendering, set it from the
|
|
10
|
+
* request with `detectPlatform(userAgent)`.
|
|
11
|
+
*/
|
|
12
|
+
platform?: HotkeyPlatform;
|
|
7
13
|
/**
|
|
8
14
|
* Defaults for every hotkey registered by `useHotkey` and `useHotkeys`.
|
|
9
15
|
* `requireReset` applies to chords and `timeout` applies to sequences.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useDefaultHotkeysOptions } from "./HotkeysProvider.js";
|
|
4
|
+
import { detectPlatform } from "@tanstack/hotkeys";
|
|
5
|
+
import { useSyncExternalStore } from "react";
|
|
6
|
+
import { getServerPlatformSnapshot } from "@tanstack/hotkeys/adapter";
|
|
7
|
+
|
|
8
|
+
//#region src/_usePlatform.ts
|
|
9
|
+
const subscribe = () => () => {};
|
|
10
|
+
/**
|
|
11
|
+
* The provider's platform, or the detected one. Without a provider platform,
|
|
12
|
+
* server rendering and hydration use 'linux', then the client re-renders
|
|
13
|
+
* with the detected platform.
|
|
14
|
+
*/
|
|
15
|
+
function usePlatform() {
|
|
16
|
+
const platform = useDefaultHotkeysOptions().platform;
|
|
17
|
+
return useSyncExternalStore(subscribe, () => platform ?? detectPlatform(), () => platform ?? getServerPlatformSnapshot());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
export { usePlatform };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,9 +5,10 @@ import { ReactHotkeyRecorder, useHotkeyRecorder } from "./useHotkeyRecorder.js";
|
|
|
5
5
|
import { useHotkeyRegistrations } from "./useHotkeyRegistrations.js";
|
|
6
6
|
import { useHotkeyScopesState } from "./useHotkeyScopesState.js";
|
|
7
7
|
import { useHotkeyHint } from "./useHotkeyHint.js";
|
|
8
|
+
import { useFormatForDisplay } from "./useFormatForDisplay.js";
|
|
8
9
|
import { UseCustomHotkeysResult, useAppHotkeyList, useAppHotkeys, useCustomHotkeys } from "./useAppHotkeys.js";
|
|
9
10
|
import { useHeldKeys } from "./useHeldKeys.js";
|
|
10
11
|
import { useHeldKeyCodes } from "./useHeldKeyCodes.js";
|
|
11
12
|
import { useKeyHold } from "./useKeyHold.js";
|
|
12
13
|
export * from "@tanstack/hotkeys";
|
|
13
|
-
export { HotkeysProvider, HotkeysProviderOptions, HotkeysProviderProps, ReactHotkeyRecorder, UseAppHotkeyOptions, UseCustomHotkeysResult, UseHotkeyChordOptions, UseHotkeyCommonOptions, UseHotkeyDefinition, UseHotkeyOptions, UseHotkeyOptionsFor, UseHotkeySequenceOptions, UseHotkeyTarget, useAppHotkeyList, useAppHotkeys, useCustomHotkeys, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations, useHotkeyScopesState, useHotkeys, useHotkeysContext, useKeyHold };
|
|
14
|
+
export { HotkeysProvider, HotkeysProviderOptions, HotkeysProviderProps, ReactHotkeyRecorder, UseAppHotkeyOptions, UseCustomHotkeysResult, UseHotkeyChordOptions, UseHotkeyCommonOptions, UseHotkeyDefinition, UseHotkeyOptions, UseHotkeyOptionsFor, UseHotkeySequenceOptions, UseHotkeyTarget, useAppHotkeyList, useAppHotkeys, useCustomHotkeys, useDefaultHotkeysOptions, useFormatForDisplay, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations, useHotkeyScopesState, useHotkeys, useHotkeysContext, useKeyHold };
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { useHotkeyRecorder } from "./useHotkeyRecorder.js";
|
|
|
5
5
|
import { useHotkeyRegistrations } from "./useHotkeyRegistrations.js";
|
|
6
6
|
import { useHotkeyScopesState } from "./useHotkeyScopesState.js";
|
|
7
7
|
import { useHotkeyHint } from "./useHotkeyHint.js";
|
|
8
|
+
import { useFormatForDisplay } from "./useFormatForDisplay.js";
|
|
8
9
|
import { useAppHotkeyList, useAppHotkeys, useCustomHotkeys } from "./useAppHotkeys.js";
|
|
9
10
|
import { useHeldKeys } from "./useHeldKeys.js";
|
|
10
11
|
import { useHeldKeyCodes } from "./useHeldKeyCodes.js";
|
|
@@ -12,4 +13,4 @@ import { useKeyHold } from "./useKeyHold.js";
|
|
|
12
13
|
|
|
13
14
|
export * from "@tanstack/hotkeys"
|
|
14
15
|
|
|
15
|
-
export { HotkeysProvider, useAppHotkeyList, useAppHotkeys, useCustomHotkeys, useDefaultHotkeysOptions, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations, useHotkeyScopesState, useHotkeys, useHotkeysContext, useKeyHold };
|
|
16
|
+
export { HotkeysProvider, useAppHotkeyList, useAppHotkeys, useCustomHotkeys, useDefaultHotkeysOptions, useFormatForDisplay, useHeldKeyCodes, useHeldKeys, useHotkey, useHotkeyHint, useHotkeyRecorder, useHotkeyRegistrations, useHotkeyScopesState, useHotkeys, useHotkeysContext, useKeyHold };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { formatForDisplay } from "@tanstack/hotkeys";
|
|
2
|
+
//#region src/useFormatForDisplay.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Returns `formatForDisplay` with the platform from `HotkeysProvider`, or the
|
|
5
|
+
* detected platform without one. A `platform` passed to the returned function
|
|
6
|
+
* wins.
|
|
7
|
+
*
|
|
8
|
+
* Call the hook once at the top of the component; the returned function can
|
|
9
|
+
* be called anywhere, such as in a loop or a condition. Server-rendered apps
|
|
10
|
+
* should set the provider's `defaultOptions.platform` from the request with
|
|
11
|
+
* `detectPlatform(userAgent)`, so the server HTML matches the user's platform.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* function SaveButton() {
|
|
16
|
+
* const format = useFormatForDisplay()
|
|
17
|
+
*
|
|
18
|
+
* return (
|
|
19
|
+
* <button>
|
|
20
|
+
* Save <kbd>{format('Mod+S')}</kbd>
|
|
21
|
+
* </button>
|
|
22
|
+
* )
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function useFormatForDisplay(): typeof formatForDisplay;
|
|
27
|
+
//#endregion
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { usePlatform } from "./_usePlatform.js";
|
|
4
|
+
import { formatForDisplay } from "@tanstack/hotkeys";
|
|
5
|
+
import { useCallback } from "react";
|
|
6
|
+
|
|
7
|
+
//#region src/useFormatForDisplay.ts
|
|
8
|
+
/**
|
|
9
|
+
* Returns `formatForDisplay` with the platform from `HotkeysProvider`, or the
|
|
10
|
+
* detected platform without one. A `platform` passed to the returned function
|
|
11
|
+
* wins.
|
|
12
|
+
*
|
|
13
|
+
* Call the hook once at the top of the component; the returned function can
|
|
14
|
+
* be called anywhere, such as in a loop or a condition. Server-rendered apps
|
|
15
|
+
* should set the provider's `defaultOptions.platform` from the request with
|
|
16
|
+
* `detectPlatform(userAgent)`, so the server HTML matches the user's platform.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* function SaveButton() {
|
|
21
|
+
* const format = useFormatForDisplay()
|
|
22
|
+
*
|
|
23
|
+
* return (
|
|
24
|
+
* <button>
|
|
25
|
+
* Save <kbd>{format('Mod+S')}</kbd>
|
|
26
|
+
* </button>
|
|
27
|
+
* )
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
function useFormatForDisplay() {
|
|
32
|
+
const platform = usePlatform();
|
|
33
|
+
return useCallback((hotkey, options) => formatForDisplay(hotkey, {
|
|
34
|
+
...options,
|
|
35
|
+
platform: options?.platform ?? platform
|
|
36
|
+
}), [platform]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { useFormatForDisplay };
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useDefaultHotkeysOptions } from "./HotkeysProvider.js";
|
|
4
4
|
import { useSelector } from "./_useSelector.js";
|
|
5
5
|
import { HotkeyRecorder } from "@tanstack/hotkeys";
|
|
6
|
-
import { useEffect, useRef } from "react";
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
7
|
import { withDefaults } from "@tanstack/hotkeys/adapter";
|
|
8
8
|
|
|
9
9
|
//#region src/useHotkeyRecorder.ts
|
|
@@ -21,14 +21,17 @@ function useHotkeyRecorder(options) {
|
|
|
21
21
|
recorderRef.current?.destroy();
|
|
22
22
|
};
|
|
23
23
|
}, []);
|
|
24
|
-
|
|
25
|
-
isRecording,
|
|
26
|
-
steps,
|
|
27
|
-
recordedHotkey,
|
|
24
|
+
const [actions] = useState(() => ({
|
|
28
25
|
startRecording: () => recorderRef.current?.start(),
|
|
29
26
|
stopRecording: () => recorderRef.current?.stop(),
|
|
30
27
|
cancelRecording: () => recorderRef.current?.cancel(),
|
|
31
28
|
commitRecording: () => recorderRef.current?.commit()
|
|
29
|
+
}));
|
|
30
|
+
return {
|
|
31
|
+
isRecording,
|
|
32
|
+
steps,
|
|
33
|
+
recordedHotkey,
|
|
34
|
+
...actions
|
|
32
35
|
};
|
|
33
36
|
}
|
|
34
37
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-hotkeys",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "React adapter for TanStack Hotkeys",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,13 +35,14 @@
|
|
|
35
35
|
"skills"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@tanstack/hotkeys": "0.
|
|
38
|
+
"@tanstack/hotkeys": "0.23.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@eslint-react/eslint-plugin": "^4.2.3",
|
|
42
42
|
"@tanstack/react-store": "^0.11.2",
|
|
43
43
|
"@testing-library/react": "^16.3.3",
|
|
44
44
|
"@types/react": "^19.3.0",
|
|
45
|
+
"@types/react-dom": "^19.3.0",
|
|
45
46
|
"@vitejs/plugin-react": "^6.1.1",
|
|
46
47
|
"eslint-plugin-react-compiler": "19.1.0-rc.2",
|
|
47
48
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
@@ -55,6 +55,7 @@ export function SaveButton({ onSave }: { onSave: () => void }) {
|
|
|
55
55
|
| `<HotkeysProvider>` | Set default options, a scope (`exclusive` for dialogs), `enabled`, and app hotkeys for the components inside it. |
|
|
56
56
|
| `useHotkeysContext()` | Read the nearest provider's merged defaults and enabled state, or `null` outside a provider. |
|
|
57
57
|
| `useDefaultHotkeysOptions()` | Read the default options merged from every provider above. |
|
|
58
|
+
| `useFormatForDisplay()` | Get `formatForDisplay` with the provider's `platform` filled in. Use it for labels in server-rendered apps; call it at the top of the component. |
|
|
58
59
|
| `useAppHotkeys()` | Get the declared app hotkeys with the user's custom hotkeys applied, to pass to the register API. |
|
|
59
60
|
| `useAppHotkeyList()` | List every app hotkey with its status, including ones whose components aren't mounted, for a cheat sheet or settings screen. |
|
|
60
61
|
| `useCustomHotkeys()` | Read, change, and reset the user's custom hotkeys, and check a new one for conflicts. |
|
|
@@ -101,11 +102,13 @@ A hook uses the nearest `HotkeysProvider` above its component. Registering `Esca
|
|
|
101
102
|
|
|
102
103
|
## Pitfalls
|
|
103
104
|
|
|
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
|
|
105
|
+
- **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` for an element the component always renders.
|
|
106
|
+
- **Keep elements that mount later in state.** The hook reads a ref only after its component renders, so it misses an element that mounts later, such as one a child shows from its own state. Use `const [el, setEl] = useState<HTMLElement | null>(null)`, `target: el`, and `ref={setEl}`; the hotkey registers when the element mounts and unregisters when it unmounts.
|
|
105
107
|
- **No dependency arrays or `useCallback`.** The hooks read the hotkey, callback, and options on every render, so inline callbacks always see current state.
|
|
106
108
|
- **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
109
|
- **`matchesKeyboardEvent` takes the DOM event.** In a widget's `onKeyDown`, pass `event.nativeEvent`.
|
|
108
110
|
- **In tests,** set `<HotkeysProvider defaultOptions={{ hotkey: { platform: 'mac' } }}>`.
|
|
111
|
+
- **Server-rendered labels need the user's platform.** On the server, `formatForDisplay` can't see the user's OS, so it renders Linux labels that differ after hydration for Mac and Windows users. Set the provider's `defaultOptions.platform` to `detectPlatform(userAgent)` from the request's `User-Agent` header, and format labels with `const format = useFormatForDisplay()` at the top of the component, then `format(hotkey)`. Keep plain `formatForDisplay` for labels that render only in the browser.
|
|
109
112
|
|
|
110
113
|
When declaring app hotkeys, building a cheat sheet, or letting users rebind shortcuts, load `@tanstack/hotkeys#app-hotkeys` and read [app hotkeys in React](references/app-hotkeys.md).
|
|
111
114
|
|