@tanstack/solid-hotkeys 0.0.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.
Files changed (56) hide show
  1. package/README.md +305 -0
  2. package/dist/HotkeysProvider.cjs +27 -0
  3. package/dist/HotkeysProvider.cjs.map +1 -0
  4. package/dist/HotkeysProvider.d.cts +24 -0
  5. package/dist/HotkeysProvider.d.ts +24 -0
  6. package/dist/HotkeysProvider.js +25 -0
  7. package/dist/HotkeysProvider.js.map +1 -0
  8. package/dist/createHeldKeyCodes.cjs +42 -0
  9. package/dist/createHeldKeyCodes.cjs.map +1 -0
  10. package/dist/createHeldKeyCodes.d.cts +36 -0
  11. package/dist/createHeldKeyCodes.d.ts +36 -0
  12. package/dist/createHeldKeyCodes.js +42 -0
  13. package/dist/createHeldKeyCodes.js.map +1 -0
  14. package/dist/createHeldKeys.cjs +33 -0
  15. package/dist/createHeldKeys.cjs.map +1 -0
  16. package/dist/createHeldKeys.d.cts +27 -0
  17. package/dist/createHeldKeys.d.ts +27 -0
  18. package/dist/createHeldKeys.js +33 -0
  19. package/dist/createHeldKeys.js.map +1 -0
  20. package/dist/createHotkey.cjs +100 -0
  21. package/dist/createHotkey.cjs.map +1 -0
  22. package/dist/createHotkey.d.cts +72 -0
  23. package/dist/createHotkey.d.ts +72 -0
  24. package/dist/createHotkey.js +100 -0
  25. package/dist/createHotkey.js.map +1 -0
  26. package/dist/createHotkeyRecorder.cjs +78 -0
  27. package/dist/createHotkeyRecorder.cjs.map +1 -0
  28. package/dist/createHotkeyRecorder.d.cts +60 -0
  29. package/dist/createHotkeyRecorder.d.ts +60 -0
  30. package/dist/createHotkeyRecorder.js +78 -0
  31. package/dist/createHotkeyRecorder.js.map +1 -0
  32. package/dist/createHotkeySequence.cjs +58 -0
  33. package/dist/createHotkeySequence.cjs.map +1 -0
  34. package/dist/createHotkeySequence.d.cts +43 -0
  35. package/dist/createHotkeySequence.d.ts +43 -0
  36. package/dist/createHotkeySequence.js +58 -0
  37. package/dist/createHotkeySequence.js.map +1 -0
  38. package/dist/createKeyHold.cjs +56 -0
  39. package/dist/createKeyHold.cjs.map +1 -0
  40. package/dist/createKeyHold.d.cts +47 -0
  41. package/dist/createKeyHold.d.ts +47 -0
  42. package/dist/createKeyHold.js +56 -0
  43. package/dist/createKeyHold.js.map +1 -0
  44. package/dist/index.cjs +25 -0
  45. package/dist/index.d.cts +9 -0
  46. package/dist/index.d.ts +9 -0
  47. package/dist/index.js +11 -0
  48. package/package.json +67 -0
  49. package/src/HotkeysProvider.tsx +47 -0
  50. package/src/createHeldKeyCodes.ts +38 -0
  51. package/src/createHeldKeys.ts +29 -0
  52. package/src/createHotkey.ts +154 -0
  53. package/src/createHotkeyRecorder.ts +103 -0
  54. package/src/createHotkeySequence.ts +93 -0
  55. package/src/createKeyHold.ts +57 -0
  56. package/src/index.ts +13 -0
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@tanstack/solid-hotkeys",
3
+ "version": "0.0.1",
4
+ "description": "SolidJS adapter for TanStack Hotkeys",
5
+ "author": "Tanner Linsley",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/TanStack/hotkeys.git",
10
+ "directory": "packages/solid-hotkeys"
11
+ },
12
+ "homepage": "https://tanstack.com/hotkeys",
13
+ "funding": {
14
+ "type": "github",
15
+ "url": "https://github.com/sponsors/tannerlinsley"
16
+ },
17
+ "keywords": [
18
+ "solid",
19
+ "solidjs",
20
+ "tanstack",
21
+ "hotkeys",
22
+ "keyboard",
23
+ "shortcuts"
24
+ ],
25
+ "scripts": {
26
+ "clean": "premove ./build ./dist",
27
+ "lint": "eslint ./src",
28
+ "lint:fix": "eslint ./src --fix",
29
+ "test:eslint": "eslint ./src",
30
+ "test:lib": "vitest --passWithNoTests",
31
+ "test:lib:dev": "pnpm test:lib --watch",
32
+ "test:types": "tsc",
33
+ "build": "tsdown"
34
+ },
35
+ "type": "module",
36
+ "main": "./dist/index.cjs",
37
+ "module": "./dist/index.js",
38
+ "types": "./dist/index.d.cts",
39
+ "exports": {
40
+ ".": {
41
+ "import": "./dist/index.js",
42
+ "require": "./dist/index.cjs"
43
+ },
44
+ "./package.json": "./package.json"
45
+ },
46
+ "sideEffects": false,
47
+ "engines": {
48
+ "node": ">=18"
49
+ },
50
+ "files": [
51
+ "dist",
52
+ "src"
53
+ ],
54
+ "dependencies": {
55
+ "@tanstack/hotkeys": "workspace:*",
56
+ "@tanstack/solid-store": "^0.8.0"
57
+ },
58
+ "devDependencies": {
59
+ "@solidjs/testing-library": "^0.8.10",
60
+ "@testing-library/jest-dom": "^6.9.1",
61
+ "solid-js": "^1.9.11",
62
+ "vite-plugin-solid": "^2.11.10"
63
+ },
64
+ "peerDependencies": {
65
+ "solid-js": ">=1.7.0"
66
+ }
67
+ }
@@ -0,0 +1,47 @@
1
+ import { createContext, useContext } from 'solid-js'
2
+ import type { JSX, ParentComponent } from 'solid-js'
3
+ import type { HotkeyRecorderOptions } from '@tanstack/hotkeys'
4
+ import type { CreateHotkeyOptions } from './createHotkey'
5
+ import type { CreateHotkeySequenceOptions } from './createHotkeySequence'
6
+
7
+ export interface HotkeysProviderOptions {
8
+ hotkey?: Partial<CreateHotkeyOptions>
9
+ hotkeyRecorder?: Partial<HotkeyRecorderOptions>
10
+ hotkeySequence?: Partial<CreateHotkeySequenceOptions>
11
+ }
12
+
13
+ interface HotkeysContextValue {
14
+ defaultOptions: HotkeysProviderOptions
15
+ }
16
+
17
+ const HotkeysContext = createContext<HotkeysContextValue | null>(null)
18
+
19
+ export interface HotkeysProviderProps {
20
+ children: JSX.Element
21
+ defaultOptions?: HotkeysProviderOptions
22
+ }
23
+
24
+ const DEFAULT_OPTIONS: HotkeysProviderOptions = {}
25
+
26
+ export const HotkeysProvider: ParentComponent<HotkeysProviderProps> = (
27
+ props,
28
+ ) => {
29
+ const contextValue: HotkeysContextValue = {
30
+ defaultOptions: props.defaultOptions ?? DEFAULT_OPTIONS,
31
+ }
32
+
33
+ return (
34
+ <HotkeysContext.Provider value={contextValue}>
35
+ {props.children}
36
+ </HotkeysContext.Provider>
37
+ )
38
+ }
39
+
40
+ export function useHotkeysContext() {
41
+ return useContext(HotkeysContext)
42
+ }
43
+
44
+ export function useDefaultHotkeysOptions() {
45
+ const context = useContext(HotkeysContext)
46
+ return context?.defaultOptions ?? {}
47
+ }
@@ -0,0 +1,38 @@
1
+ import { useStore } from '@tanstack/solid-store'
2
+ import { getKeyStateTracker } from '@tanstack/hotkeys'
3
+
4
+ /**
5
+ * SolidJS primitive that returns a signal of a map from currently held key names to their physical `event.code` values.
6
+ *
7
+ * This is useful for debugging which physical key was pressed (e.g. distinguishing
8
+ * left vs right Shift via "ShiftLeft" / "ShiftRight").
9
+ *
10
+ * This primitive uses `useStore` from `@tanstack/solid-store` to subscribe
11
+ * to the global KeyStateTracker.
12
+ *
13
+ * @returns Signal accessor for record mapping normalized key names to their `event.code` values
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * function KeyDebugDisplay() {
18
+ * const heldKeys = createHeldKeys()
19
+ * const heldCodes = createHeldKeyCodes()
20
+ *
21
+ * return (
22
+ * <div>
23
+ * <For each={heldKeys()}>
24
+ * {(key) => (
25
+ * <kbd>
26
+ * {key} <small>{heldCodes()[key]}</small>
27
+ * </kbd>
28
+ * )}
29
+ * </For>
30
+ * </div>
31
+ * )
32
+ * }
33
+ * ```
34
+ */
35
+ export function createHeldKeyCodes(): () => Record<string, string> {
36
+ const tracker = getKeyStateTracker()
37
+ return useStore(tracker.store, (state) => state.heldCodes)
38
+ }
@@ -0,0 +1,29 @@
1
+ import { useStore } from '@tanstack/solid-store'
2
+ import { getKeyStateTracker } from '@tanstack/hotkeys'
3
+
4
+ /**
5
+ * SolidJS primitive that returns a signal of currently held keyboard keys.
6
+ *
7
+ * This primitive uses `useStore` from `@tanstack/solid-store` to subscribe
8
+ * to the global KeyStateTracker and updates whenever keys are pressed
9
+ * or released.
10
+ *
11
+ * @returns Signal accessor for array of currently held key names
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * function KeyDisplay() {
16
+ * const heldKeys = createHeldKeys()
17
+ *
18
+ * return (
19
+ * <div>
20
+ * Currently pressed: {heldKeys().join(' + ') || 'None'}
21
+ * </div>
22
+ * )
23
+ * }
24
+ * ```
25
+ */
26
+ export function createHeldKeys(): () => Array<string> {
27
+ const tracker = getKeyStateTracker()
28
+ return useStore(tracker.store, (state) => state.heldKeys)
29
+ }
@@ -0,0 +1,154 @@
1
+ import { createEffect, onCleanup } from 'solid-js'
2
+ import {
3
+ detectPlatform,
4
+ formatHotkey,
5
+ getHotkeyManager,
6
+ rawHotkeyToParsedHotkey,
7
+ } from '@tanstack/hotkeys'
8
+ import { useDefaultHotkeysOptions } from './HotkeysProvider'
9
+ import type {
10
+ Hotkey,
11
+ HotkeyCallback,
12
+ HotkeyOptions,
13
+ HotkeyRegistrationHandle,
14
+ RegisterableHotkey,
15
+ } from '@tanstack/hotkeys'
16
+
17
+ export interface CreateHotkeyOptions extends Omit<HotkeyOptions, 'target'> {
18
+ /**
19
+ * The DOM element to attach the event listener to.
20
+ * Can be a direct DOM element, an accessor (for reactive targets that become
21
+ * available after mount), or null. Defaults to document.
22
+ * When using scoped targets, pass an accessor: () => ({ target: elementSignal() })
23
+ * so the hotkey waits for the element to be attached before registering.
24
+ */
25
+ target?: HTMLElement | Document | Window | null
26
+ }
27
+
28
+ /**
29
+ * SolidJS primitive for registering a keyboard hotkey.
30
+ *
31
+ * Uses the singleton HotkeyManager for efficient event handling.
32
+ * The callback receives both the keyboard event and a context object
33
+ * containing the hotkey string and parsed hotkey.
34
+ *
35
+ * This primitive automatically tracks reactive dependencies and updates
36
+ * the registration when options or the callback change.
37
+ *
38
+ * @param hotkey - The hotkey string (e.g., 'Mod+S', 'Escape') or RawHotkey object (supports `mod` for cross-platform)
39
+ * @param callback - The function to call when the hotkey is pressed
40
+ * @param options - Options for the hotkey behavior
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * function SaveButton() {
45
+ * const [count, setCount] = createSignal(0)
46
+ *
47
+ * // Callback always has access to latest count value
48
+ * createHotkey('Mod+S', (event, { hotkey }) => {
49
+ * console.log(`Save triggered, count is ${count()}`)
50
+ * handleSave()
51
+ * })
52
+ *
53
+ * return <button onClick={() => setCount(c => c + 1)}>Count: {count()}</button>
54
+ * }
55
+ * ```
56
+ *
57
+ * @example
58
+ * ```tsx
59
+ * function Modal(props) {
60
+ * // enabled option is reactive
61
+ * createHotkey('Escape', () => {
62
+ * props.onClose()
63
+ * }, () => ({ enabled: props.isOpen }))
64
+ *
65
+ * return <Show when={props.isOpen}>
66
+ * <div class="modal">...</div>
67
+ * </Show>
68
+ * }
69
+ * ```
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * function Editor() {
74
+ * const [editorRef, setEditorRef] = createSignal<HTMLDivElement | null>(null)
75
+ *
76
+ * // Scoped to a specific element - use accessor so hotkey waits for ref
77
+ * createHotkey('Mod+S', save, () => ({ target: editorRef() }))
78
+ *
79
+ * return <div ref={setEditorRef}>...</div>
80
+ * }
81
+ * ```
82
+ */
83
+ export function createHotkey(
84
+ hotkey: RegisterableHotkey | (() => RegisterableHotkey),
85
+ callback: HotkeyCallback,
86
+ options: CreateHotkeyOptions | (() => CreateHotkeyOptions) = {},
87
+ ): void {
88
+ const defaultOptions = useDefaultHotkeysOptions()
89
+ const manager = getHotkeyManager()
90
+
91
+ let registration: HotkeyRegistrationHandle | null = null
92
+
93
+ createEffect(() => {
94
+ // Resolve reactive values
95
+ const resolvedHotkey = typeof hotkey === 'function' ? hotkey() : hotkey
96
+ const resolvedOptions = typeof options === 'function' ? options() : options
97
+
98
+ const mergedOptions = {
99
+ ...defaultOptions.hotkey,
100
+ ...resolvedOptions,
101
+ } as CreateHotkeyOptions
102
+
103
+ // Normalize to hotkey string
104
+ const platform = mergedOptions.platform ?? detectPlatform()
105
+ const hotkeyString: Hotkey =
106
+ typeof resolvedHotkey === 'string'
107
+ ? resolvedHotkey
108
+ : (formatHotkey(
109
+ rawHotkeyToParsedHotkey(resolvedHotkey, platform),
110
+ ) as Hotkey)
111
+
112
+ // Resolve target: when explicitly provided (even as null), use it and skip if null.
113
+ // When not provided, default to document. Matches React's ref handling.
114
+ const resolvedTarget =
115
+ 'target' in mergedOptions
116
+ ? (mergedOptions.target ?? null)
117
+ : typeof document !== 'undefined'
118
+ ? document
119
+ : null
120
+
121
+ if (!resolvedTarget) {
122
+ return
123
+ }
124
+
125
+ // Unregister previous registration if it exists
126
+ if (registration?.isActive) {
127
+ registration.unregister()
128
+ registration = null
129
+ }
130
+
131
+ // Extract options without target (target is handled separately)
132
+ const { target: _target, ...optionsWithoutTarget } = mergedOptions
133
+
134
+ // Register the hotkey
135
+ registration = manager.register(hotkeyString, callback, {
136
+ ...optionsWithoutTarget,
137
+ target: resolvedTarget,
138
+ })
139
+
140
+ // Update callback and options on every effect run
141
+ if (registration.isActive) {
142
+ registration.callback = callback
143
+ registration.setOptions(optionsWithoutTarget)
144
+ }
145
+
146
+ // Cleanup on disposal
147
+ onCleanup(() => {
148
+ if (registration?.isActive) {
149
+ registration.unregister()
150
+ registration = null
151
+ }
152
+ })
153
+ })
154
+ }
@@ -0,0 +1,103 @@
1
+ import { createEffect, onCleanup } from 'solid-js'
2
+ import { useStore } from '@tanstack/solid-store'
3
+ import { HotkeyRecorder } from '@tanstack/hotkeys'
4
+ import { useDefaultHotkeysOptions } from './HotkeysProvider'
5
+ import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys'
6
+
7
+ export interface SolidHotkeyRecorder {
8
+ /** Whether recording is currently active */
9
+ isRecording: () => boolean
10
+ /** The currently recorded hotkey (for live preview) */
11
+ recordedHotkey: () => Hotkey | null
12
+ /** Start recording a new hotkey */
13
+ startRecording: () => void
14
+ /** Stop recording (same as cancel) */
15
+ stopRecording: () => void
16
+ /** Cancel recording without saving */
17
+ cancelRecording: () => void
18
+ }
19
+
20
+ /**
21
+ * SolidJS primitive for recording keyboard shortcuts.
22
+ *
23
+ * This primitive provides a thin wrapper around the framework-agnostic `HotkeyRecorder`
24
+ * class, managing all the complexity of capturing keyboard events, converting them
25
+ * to hotkey strings, and handling edge cases like Escape to cancel or Backspace/Delete
26
+ * to clear.
27
+ *
28
+ * This primitive uses `useStore` from `@tanstack/solid-store` to subscribe
29
+ * to the recorder's store state (same pattern as useHotkeyRecorder in React).
30
+ *
31
+ * @param options - Configuration options for the recorder (or accessor function)
32
+ * @returns An object with recording state signals and control functions
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * function ShortcutSettings() {
37
+ * const [shortcut, setShortcut] = createSignal<Hotkey>('Mod+S')
38
+ *
39
+ * const recorder = createHotkeyRecorder({
40
+ * onRecord: (hotkey) => {
41
+ * setShortcut(hotkey)
42
+ * },
43
+ * onCancel: () => {
44
+ * console.log('Recording cancelled')
45
+ * },
46
+ * })
47
+ *
48
+ * return (
49
+ * <div>
50
+ * <button onClick={recorder.startRecording}>
51
+ * {recorder.isRecording() ? 'Recording...' : 'Edit Shortcut'}
52
+ * </button>
53
+ * <Show when={recorder.recordedHotkey()}>
54
+ * <div>Recording: {recorder.recordedHotkey()}</div>
55
+ * </Show>
56
+ * </div>
57
+ * )
58
+ * }
59
+ * ```
60
+ */
61
+ export function createHotkeyRecorder(
62
+ options: HotkeyRecorderOptions | (() => HotkeyRecorderOptions),
63
+ ): SolidHotkeyRecorder {
64
+ const defaultOptions = useDefaultHotkeysOptions()
65
+
66
+ const resolvedOptions = typeof options === 'function' ? options() : options
67
+ const mergedOptions = {
68
+ ...defaultOptions.hotkeyRecorder,
69
+ ...resolvedOptions,
70
+ } as HotkeyRecorderOptions
71
+
72
+ // Create recorder once synchronously (matches React's useRef pattern)
73
+ const recorder = new HotkeyRecorder(mergedOptions)
74
+
75
+ // Subscribe to recorder state using useStore (same pattern as useHotkeyRecorder)
76
+ const isRecording = useStore(recorder.store, (state) => state.isRecording)
77
+ const recordedHotkey = useStore(
78
+ recorder.store,
79
+ (state) => state.recordedHotkey,
80
+ )
81
+
82
+ // Sync options on every effect run (matches React's sync on render)
83
+ createEffect(() => {
84
+ const resolved = typeof options === 'function' ? options() : options
85
+ recorder.setOptions({
86
+ ...defaultOptions.hotkeyRecorder,
87
+ ...resolved,
88
+ } as HotkeyRecorderOptions)
89
+ })
90
+
91
+ // Cleanup on unmount
92
+ onCleanup(() => {
93
+ recorder.destroy()
94
+ })
95
+
96
+ return {
97
+ isRecording,
98
+ recordedHotkey,
99
+ startRecording: () => recorder.start(),
100
+ stopRecording: () => recorder.stop(),
101
+ cancelRecording: () => recorder.cancel(),
102
+ }
103
+ }
@@ -0,0 +1,93 @@
1
+ import { createEffect, onCleanup } from 'solid-js'
2
+ import { getSequenceManager } from '@tanstack/hotkeys'
3
+ import { useDefaultHotkeysOptions } from './HotkeysProvider'
4
+ import type {
5
+ HotkeyCallback,
6
+ HotkeySequence,
7
+ SequenceOptions,
8
+ } from '@tanstack/hotkeys'
9
+
10
+ export interface CreateHotkeySequenceOptions extends Omit<
11
+ SequenceOptions,
12
+ 'enabled'
13
+ > {
14
+ /** Whether the sequence is enabled. Defaults to true. */
15
+ enabled?: boolean
16
+ }
17
+
18
+ /**
19
+ * SolidJS primitive for registering a keyboard shortcut sequence (Vim-style).
20
+ *
21
+ * This primitive allows you to register multi-key sequences like 'g g' or 'd d'
22
+ * that trigger when the full sequence is pressed within a timeout.
23
+ *
24
+ * @param sequence - Array of hotkey strings that form the sequence (or accessor function)
25
+ * @param callback - Function to call when the sequence is completed
26
+ * @param options - Options for the sequence behavior (or accessor function)
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * function VimEditor() {
31
+ * // 'g g' to go to top
32
+ * createHotkeySequence(['G', 'G'], () => {
33
+ * scrollToTop()
34
+ * })
35
+ *
36
+ * // 'd d' to delete line
37
+ * createHotkeySequence(['D', 'D'], () => {
38
+ * deleteLine()
39
+ * })
40
+ *
41
+ * // 'd i w' to delete inner word
42
+ * createHotkeySequence(['D', 'I', 'W'], () => {
43
+ * deleteInnerWord()
44
+ * }, { timeout: 500 })
45
+ *
46
+ * return <div>...</div>
47
+ * }
48
+ * ```
49
+ */
50
+ export function createHotkeySequence(
51
+ sequence: HotkeySequence | (() => HotkeySequence),
52
+ callback: HotkeyCallback,
53
+ options:
54
+ | CreateHotkeySequenceOptions
55
+ | (() => CreateHotkeySequenceOptions) = {},
56
+ ): void {
57
+ const defaultOptions = useDefaultHotkeysOptions()
58
+
59
+ createEffect(() => {
60
+ // Resolve reactive values
61
+ const resolvedSequence =
62
+ typeof sequence === 'function' ? sequence() : sequence
63
+ const resolvedOptions = typeof options === 'function' ? options() : options
64
+
65
+ const mergedOptions = {
66
+ ...defaultOptions.hotkeySequence,
67
+ ...resolvedOptions,
68
+ } as CreateHotkeySequenceOptions
69
+
70
+ const { enabled = true, ...sequenceOptions } = mergedOptions
71
+
72
+ if (!enabled || resolvedSequence.length === 0) {
73
+ return
74
+ }
75
+
76
+ const manager = getSequenceManager()
77
+
78
+ // Build options object conditionally to avoid overwriting manager defaults with undefined
79
+ const registerOptions: SequenceOptions = { enabled: true }
80
+ if (sequenceOptions.timeout !== undefined)
81
+ registerOptions.timeout = sequenceOptions.timeout
82
+ if (sequenceOptions.platform !== undefined)
83
+ registerOptions.platform = sequenceOptions.platform
84
+
85
+ const unregister = manager.register(
86
+ resolvedSequence,
87
+ callback,
88
+ registerOptions,
89
+ )
90
+
91
+ onCleanup(unregister)
92
+ })
93
+ }
@@ -0,0 +1,57 @@
1
+ import { createMemo } from 'solid-js'
2
+ import { useStore } from '@tanstack/solid-store'
3
+ import { getKeyStateTracker } from '@tanstack/hotkeys'
4
+ import type { HeldKey } from '@tanstack/hotkeys'
5
+
6
+ /**
7
+ * SolidJS primitive that returns whether a specific key is currently being held.
8
+ *
9
+ * This primitive uses `useStore` from `@tanstack/solid-store` to subscribe
10
+ * to the global KeyStateTracker and uses a selector to determine if the
11
+ * specified key is held.
12
+ *
13
+ * @param key - The key to check (e.g., 'Shift', 'Control', 'A') - can be an accessor function
14
+ * @returns Signal accessor that returns true if the key is currently held down
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * function ShiftIndicator() {
19
+ * const isShiftHeld = createKeyHold('Shift')
20
+ *
21
+ * return (
22
+ * <div style={{ opacity: isShiftHeld() ? 1 : 0.5 }}>
23
+ * {isShiftHeld() ? 'Shift is pressed!' : 'Press Shift'}
24
+ * </div>
25
+ * )
26
+ * }
27
+ * ```
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * function ModifierIndicators() {
32
+ * const ctrl = createKeyHold('Control')
33
+ * const shift = createKeyHold('Shift')
34
+ * const alt = createKeyHold('Alt')
35
+ *
36
+ * return (
37
+ * <div>
38
+ * <span style={{ opacity: ctrl() ? 1 : 0.3 }}>Ctrl</span>
39
+ * <span style={{ opacity: shift() ? 1 : 0.3 }}>Shift</span>
40
+ * <span style={{ opacity: alt() ? 1 : 0.3 }}>Alt</span>
41
+ * </div>
42
+ * )
43
+ * }
44
+ * ```
45
+ */
46
+ export function createKeyHold(key: HeldKey | (() => HeldKey)): () => boolean {
47
+ const tracker = getKeyStateTracker()
48
+ const heldKeysSelector = useStore(tracker.store, (state) => state.heldKeys)
49
+
50
+ return createMemo(() => {
51
+ const resolvedKey = typeof key === 'function' ? key() : key
52
+ const normalizedKey = resolvedKey.toLowerCase()
53
+ return heldKeysSelector().some(
54
+ (heldKey) => heldKey.toLowerCase() === normalizedKey,
55
+ )
56
+ })
57
+ }
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ // Re-export everything from the core package
2
+ export * from '@tanstack/hotkeys'
3
+
4
+ // Provider
5
+ export * from './HotkeysProvider'
6
+
7
+ // SolidJS-specific primitives
8
+ export * from './createHotkey'
9
+ export * from './createHeldKeys'
10
+ export * from './createHeldKeyCodes'
11
+ export * from './createKeyHold'
12
+ export * from './createHotkeySequence'
13
+ export * from './createHotkeyRecorder'