@tanstack/vue-hotkeys 0.17.0 → 0.18.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.
@@ -63,6 +63,11 @@ export interface ProvideHotkeysContextOptions {
63
63
  * ```
64
64
  */
65
65
  export declare function provideHotkeysContext(options?: ProvideHotkeysContextOptions): void;
66
+ /**
67
+ * The nearest context, or `null`. Outside a component, such as in a bare
68
+ * `effectScope`, there is no context to read, so this skips `inject` and its
69
+ * warning.
70
+ */
66
71
  export declare function useHotkeysContext(): HotkeysContextValue | null;
67
72
  export declare function useDefaultHotkeysOptions(): HotkeysProviderOptions;
68
73
  //#endregion
@@ -1,6 +1,6 @@
1
- import { AppHotkeysStore, publishAppHotkeysStore } from "@tanstack/hotkeys";
2
- import { computed, inject, onMounted, onScopeDispose, provide, toValue, watch } from "vue";
3
- import { activateHotkeysProviderScope, getHotkeysProviderScopeKey, mergeHotkeysProviderDefaults } from "@tanstack/hotkeys/adapter";
1
+ import { publishAppHotkeysStore } from "@tanstack/hotkeys";
2
+ import { computed, hasInjectionContext, inject, onMounted, onScopeDispose, provide, toValue, watch } from "vue";
3
+ import { activateHotkeysProviderScope, createAppHotkeysStore, getHotkeysProviderScopeKey, mergeHotkeysProviderDefaults } from "@tanstack/hotkeys/adapter";
4
4
 
5
5
  //#region src/HotkeysProviderContext.ts
6
6
  const HotkeysContext = Symbol("HotkeysContext");
@@ -28,7 +28,7 @@ const DEFAULT_OPTIONS = {};
28
28
  function provideHotkeysContext(options = {}) {
29
29
  const parent = inject(HotkeysContext, null);
30
30
  const appHotkeys = toValue(options.appHotkeys);
31
- const store = appHotkeys ? new AppHotkeysStore(appHotkeys, {
31
+ const store = appHotkeys ? createAppHotkeysStore(appHotkeys, {
32
32
  customHotkeys: options.customHotkeys,
33
33
  onCustomHotkeysChange: (custom) => options.onCustomHotkeysChange?.(custom)
34
34
  }) : null;
@@ -62,11 +62,16 @@ function provideHotkeysContext(options = {}) {
62
62
  };
63
63
  provide(HotkeysContext, contextValue);
64
64
  }
65
+ /**
66
+ * The nearest context, or `null`. Outside a component, such as in a bare
67
+ * `effectScope`, there is no context to read, so this skips `inject` and its
68
+ * warning.
69
+ */
65
70
  function useHotkeysContext() {
66
- return inject(HotkeysContext, null);
71
+ return hasInjectionContext() ? inject(HotkeysContext, null) : null;
67
72
  }
68
73
  function useDefaultHotkeysOptions() {
69
- return inject(HotkeysContext, null)?.defaultOptions ?? DEFAULT_OPTIONS;
74
+ return useHotkeysContext()?.defaultOptions ?? DEFAULT_OPTIONS;
70
75
  }
71
76
 
72
77
  //#endregion
@@ -1,5 +1,6 @@
1
1
  import { useHotkeysContext } from "./HotkeysProviderContext.js";
2
- import { Store, useSelector } from "@tanstack/vue-store";
2
+ import { useStoreSelector } from "./useStoreSelector.js";
3
+ import { shallowRef } from "vue";
3
4
 
4
5
  //#region src/defaultOptions.ts
5
6
  /** Captures the context once and reads current defaults inside reactive computations. */
@@ -7,7 +8,6 @@ function useDefaultHotkeysOptionsSource() {
7
8
  const context = useHotkeysContext();
8
9
  return () => context?.defaultOptions ?? {};
9
10
  }
10
- const NO_APP_HOTKEYS = new Store(null);
11
11
  /**
12
12
  * The provider's app hotkeys store and a ref that tracks its state, so
13
13
  * watchers re-run when app hotkeys or custom hotkeys change.
@@ -16,7 +16,7 @@ function useAppHotkeysStoreSource() {
16
16
  const store = useHotkeysContext()?.appHotkeysStore ?? null;
17
17
  return {
18
18
  store,
19
- state: useSelector(store?.store ?? NO_APP_HOTKEYS, (value) => value)
19
+ state: store ? useStoreSelector(store.store, (value) => value) : shallowRef(null)
20
20
  };
21
21
  }
22
22
 
@@ -1,5 +1,5 @@
1
+ import { useStoreSelector } from "./useStoreSelector.js";
1
2
  import { getKeyStateTracker } from "@tanstack/hotkeys";
2
- import { useSelector } from "@tanstack/vue-store";
3
3
 
4
4
  //#region src/useHeldKeyCodes.ts
5
5
  /**
@@ -30,7 +30,7 @@ import { useSelector } from "@tanstack/vue-store";
30
30
  */
31
31
  function useHeldKeyCodes() {
32
32
  const tracker = getKeyStateTracker();
33
- return useSelector(tracker.store, (state) => state.heldCodes);
33
+ return useStoreSelector(tracker.store, (state) => state.heldCodes);
34
34
  }
35
35
 
36
36
  //#endregion
@@ -3,8 +3,7 @@ import { Ref } from "vue";
3
3
  /**
4
4
  * Vue composable that returns a reactive ref of currently held keyboard keys.
5
5
  *
6
- * This composable uses `useSelector` from `@tanstack/vue-store` to subscribe
7
- * to the global KeyStateTracker and updates whenever keys are pressed
6
+ * This composable subscribes to the global KeyStateTracker and updates whenever keys are pressed
8
7
  * or released.
9
8
  *
10
9
  * @returns Reactive ref containing array of currently held key names
@@ -1,12 +1,11 @@
1
+ import { useStoreSelector } from "./useStoreSelector.js";
1
2
  import { getKeyStateTracker } from "@tanstack/hotkeys";
2
- import { useSelector } from "@tanstack/vue-store";
3
3
 
4
4
  //#region src/useHeldKeys.ts
5
5
  /**
6
6
  * Vue composable that returns a reactive ref of currently held keyboard keys.
7
7
  *
8
- * This composable uses `useSelector` from `@tanstack/vue-store` to subscribe
9
- * to the global KeyStateTracker and updates whenever keys are pressed
8
+ * This composable subscribes to the global KeyStateTracker and updates whenever keys are pressed
10
9
  * or released.
11
10
  *
12
11
  * @returns Reactive ref containing array of currently held key names
@@ -28,7 +27,7 @@ import { useSelector } from "@tanstack/vue-store";
28
27
  */
29
28
  function useHeldKeys() {
30
29
  const tracker = getKeyStateTracker();
31
- return useSelector(tracker.store, (state) => state.heldKeys);
30
+ return useStoreSelector(tracker.store, (state) => state.heldKeys);
32
31
  }
33
32
 
34
33
  //#endregion
@@ -1,7 +1,7 @@
1
+ import { useStoreSelector } from "./useStoreSelector.js";
1
2
  import { useAppHotkeysStoreSource } from "./defaultOptions.js";
2
3
  import { getHotkeyManager, getKeyStateTracker, isAppHotkey, matchesHeldModifiers, resolveAppHotkey } from "@tanstack/hotkeys";
3
4
  import { computed, toValue } from "vue";
4
- import { useSelector } from "@tanstack/vue-store";
5
5
 
6
6
  //#region src/useHotkeyHint.ts
7
7
  /**
@@ -15,8 +15,8 @@ import { useSelector } from "@tanstack/vue-store";
15
15
  function useHotkeyHint(hotkey, options = {}) {
16
16
  const appHotkeys = useAppHotkeysStoreSource();
17
17
  const manager = getHotkeyManager();
18
- const registrations = useSelector(manager.registrations, (state) => state);
19
- const held = useSelector(getKeyStateTracker().store, (state) => state.heldKeys);
18
+ const registrations = useStoreSelector(manager.registrations, (state) => state);
19
+ const held = useStoreSelector(getKeyStateTracker().store, (state) => state.heldKeys);
20
20
  return computed(() => {
21
21
  appHotkeys.state.value;
22
22
  const value = toValue(hotkey);
@@ -1,7 +1,7 @@
1
+ import { useStoreSelector } from "./useStoreSelector.js";
1
2
  import { useDefaultHotkeysOptionsSource } from "./defaultOptions.js";
2
3
  import { HotkeyRecorder } from "@tanstack/hotkeys";
3
4
  import { onScopeDispose, toValue, watch } from "vue";
4
- import { useSelector } from "@tanstack/vue-store";
5
5
 
6
6
  //#region src/useHotkeyRecorder.ts
7
7
  function useHotkeyRecorder(options) {
@@ -11,9 +11,9 @@ function useHotkeyRecorder(options) {
11
11
  ...toValue(options)
12
12
  });
13
13
  const recorder = new HotkeyRecorder(resolve());
14
- const isRecording = useSelector(recorder.store, (state) => state.isRecording);
15
- const steps = useSelector(recorder.store, (state) => state.steps);
16
- const recordedHotkey = useSelector(recorder.store, (state) => state.recordedHotkey);
14
+ const isRecording = useStoreSelector(recorder.store, (state) => state.isRecording);
15
+ const steps = useStoreSelector(recorder.store, (state) => state.steps);
16
+ const recordedHotkey = useStoreSelector(recorder.store, (state) => state.recordedHotkey);
17
17
  const stopWatcher = watch(resolve, (resolved) => recorder.setOptions(resolved));
18
18
  onScopeDispose(() => {
19
19
  stopWatcher();
@@ -1,5 +1,5 @@
1
+ import { useStoreSelector } from "./useStoreSelector.js";
1
2
  import { getHotkeyManager, toHotkeyRegistrationView } from "@tanstack/hotkeys";
2
- import { useSelector } from "@tanstack/vue-store";
3
3
 
4
4
  //#region src/useHotkeyRegistrations.ts
5
5
  /**
@@ -23,7 +23,7 @@ import { useSelector } from "@tanstack/vue-store";
23
23
  * ```
24
24
  */
25
25
  function useHotkeyRegistrations() {
26
- return useSelector(getHotkeyManager().registrations, (state) => Array.from(state.values()).map(toHotkeyRegistrationView));
26
+ return useStoreSelector(getHotkeyManager().registrations, (state) => Array.from(state.values()).map(toHotkeyRegistrationView));
27
27
  }
28
28
 
29
29
  //#endregion
@@ -1,5 +1,5 @@
1
+ import { useStoreSelector } from "./useStoreSelector.js";
1
2
  import { getHotkeyManager } from "@tanstack/hotkeys";
2
- import { useSelector } from "@tanstack/vue-store";
3
3
 
4
4
  //#region src/useHotkeyScopesState.ts
5
5
  /**
@@ -23,7 +23,7 @@ import { useSelector } from "@tanstack/vue-store";
23
23
  * ```
24
24
  */
25
25
  function useHotkeyScopesState() {
26
- return useSelector(getHotkeyManager().scopes, (state) => state);
26
+ return useStoreSelector(getHotkeyManager().scopes, (state) => state);
27
27
  }
28
28
 
29
29
  //#endregion
@@ -4,9 +4,8 @@ import { MaybeRefOrGetter, Ref } from "vue";
4
4
  /**
5
5
  * Vue composable that returns a reactive ref indicating whether a specific key is currently being held.
6
6
  *
7
- * This composable uses `useSelector` from `@tanstack/vue-store` to subscribe
8
- * to the global KeyStateTracker and uses a selector to determine if
9
- * the specified key is held.
7
+ * Reads the held keys from the global KeyStateTracker. The key may be a ref
8
+ * or getter; the result follows it without waiting for a key event.
10
9
  *
11
10
  * @param key - The key to check (e.g., 'Shift', 'Control', 'A')
12
11
  * @returns Reactive ref that is true if the key is currently held down
@@ -1,14 +1,12 @@
1
- import { getKeyStateTracker } from "@tanstack/hotkeys";
2
- import { unref } from "vue";
3
- import { useSelector } from "@tanstack/vue-store";
1
+ import { useHeldKeys } from "./useHeldKeys.js";
2
+ import { computed, toValue } from "vue";
4
3
 
5
4
  //#region src/useKeyHold.ts
6
5
  /**
7
6
  * Vue composable that returns a reactive ref indicating whether a specific key is currently being held.
8
7
  *
9
- * This composable uses `useSelector` from `@tanstack/vue-store` to subscribe
10
- * to the global KeyStateTracker and uses a selector to determine if
11
- * the specified key is held.
8
+ * Reads the held keys from the global KeyStateTracker. The key may be a ref
9
+ * or getter; the result follows it without waiting for a key event.
12
10
  *
13
11
  * @param key - The key to check (e.g., 'Shift', 'Control', 'A')
14
12
  * @returns Reactive ref that is true if the key is currently held down
@@ -48,11 +46,10 @@ import { useSelector } from "@tanstack/vue-store";
48
46
  * ```
49
47
  */
50
48
  function useKeyHold(key) {
51
- const tracker = getKeyStateTracker();
52
- return useSelector(tracker.store, (state) => {
53
- const keyValue = unref(key);
54
- const normalizedKey = (typeof keyValue === "string" ? keyValue : String(keyValue)).toLowerCase();
55
- return state.heldKeys.some((heldKey) => heldKey.toLowerCase() === normalizedKey);
49
+ const heldKeys = useHeldKeys();
50
+ return computed(() => {
51
+ const target = toValue(key).toLowerCase();
52
+ return heldKeys.value.some((heldKey) => heldKey.toLowerCase() === target);
56
53
  });
57
54
  }
58
55
 
@@ -0,0 +1,23 @@
1
+ import { onScopeDispose, shallowReadonly, shallowRef } from "vue";
2
+
3
+ //#region src/useStoreSelector.ts
4
+ /**
5
+ * Subscribes to a store until the effect scope is disposed and returns a ref
6
+ * to the selected value, which updates when it changes (by `===`).
7
+ *
8
+ * Does not subscribe during server rendering. Vue never disposes component
9
+ * scopes on the server, so a subscription to a shared store would never be
10
+ * released. The server gets the current value instead.
11
+ */
12
+ function useStoreSelector(store, selector) {
13
+ const selected = shallowRef(selector(store.get()));
14
+ if (typeof document === "undefined") return selected;
15
+ const { unsubscribe } = store.subscribe((state) => {
16
+ selected.value = selector(state);
17
+ });
18
+ onScopeDispose(unsubscribe);
19
+ return shallowReadonly(selected);
20
+ }
21
+
22
+ //#endregion
23
+ export { useStoreSelector };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/vue-hotkeys",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Vue adapter for TanStack Hotkeys",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -38,8 +38,7 @@
38
38
  "skills"
39
39
  ],
40
40
  "dependencies": {
41
- "@tanstack/hotkeys": "0.18.0",
42
- "@tanstack/vue-store": "^0.11.2"
41
+ "@tanstack/hotkeys": "0.20.0"
43
42
  },
44
43
  "devDependencies": {
45
44
  "@vitejs/plugin-vue": "^6.0.9",
@@ -47,7 +46,7 @@
47
46
  "vue": "^3.5.43"
48
47
  },
49
48
  "peerDependencies": {
50
- "vue": ">=3.0.0"
49
+ "vue": ">=3.3.0"
51
50
  },
52
51
  "scripts": {
53
52
  "clean": "premove ./build ./dist",