Skip to content

Commit 1ff5f18

Browse files
feat: improve shortcut handling (#49)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 3c8a098 commit 1ff5f18

File tree

7 files changed

+136
-17
lines changed

7 files changed

+136
-17
lines changed

.changeset/eight-pianos-float.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/devtools': patch
3+
---
4+
5+
improve devtools shortcut handling

packages/devtools/src/context/devtools-store.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import type { TanStackDevtoolsPlugin } from './devtools-context'
33

44
type ModifierKey = 'Alt' | 'Control' | 'Meta' | 'Shift'
55
type KeyboardKey = ModifierKey | (string & {})
6+
export const keyboardModifiers: Array<ModifierKey> = [
7+
'Alt',
8+
'Control',
9+
'Meta',
10+
'Shift',
11+
]
612

713
type TriggerPosition =
814
| 'top-left'

packages/devtools/src/devtools.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { MainPanel } from './components/main-panel'
1212
import { ContentPanel } from './components/content-panel'
1313
import { Tabs } from './components/tabs'
1414
import { TabContent } from './components/tab-content'
15+
import { keyboardModifiers } from './context/devtools-store'
16+
import { getAllPermutations } from './utils/sanitize'
1517

1618
export default function DevTools() {
1719
const { settings } = useDevtoolsSettings()
@@ -131,12 +133,24 @@ export default function DevTools() {
131133
}
132134
})
133135
createEffect(() => {
134-
createShortcut(settings().openHotkey, () => {
135-
toggleOpen()
136-
})
136+
// we create all combinations of modifiers
137+
const modifiers = settings().openHotkey.filter((key) =>
138+
keyboardModifiers.includes(key as any),
139+
)
140+
const nonModifiers = settings().openHotkey.filter(
141+
(key) => !keyboardModifiers.includes(key as any),
142+
)
143+
144+
const allModifierCombinations = getAllPermutations(modifiers)
145+
146+
for (const combination of allModifierCombinations) {
147+
const permutation = [...combination, ...nonModifiers]
148+
createShortcut(permutation, () => {
149+
toggleOpen()
150+
})
151+
}
137152
})
138153

139-
createEffect(() => {})
140154
return (
141155
<div ref={setRootEl} data-testid={TANSTACK_DEVTOOLS}>
142156
<Show

packages/devtools/src/styles/use-styles.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,10 @@ const stylesFactory = () => {
313313
grid-template-columns: 1fr;
314314
}
315315
`,
316+
settingsModifiers: css`
317+
display: flex;
318+
gap: 0.5rem;
319+
`,
316320
}
317321
}
318322

packages/devtools/src/tabs/settings-tab.tsx

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
1-
import { Show } from 'solid-js'
2-
import { Checkbox, Input, Select } from '@tanstack/devtools-ui'
1+
import { Show, createMemo } from 'solid-js'
2+
import { Button, Checkbox, Input, Select } from '@tanstack/devtools-ui'
33
import { useDevtoolsSettings } from '../context/use-devtools-context'
44
import { uppercaseFirstLetter } from '../utils/sanitize'
55
import { useStyles } from '../styles/use-styles'
6+
import type { ModifierKey } from '@solid-primitives/keyboard'
67

78
export const SettingsTab = () => {
89
const { setSettings, settings } = useDevtoolsSettings()
910
const styles = useStyles()
10-
11+
const hotkey = createMemo(() => settings().openHotkey)
12+
const modifiers: Array<ModifierKey> = ['Control', 'Alt', 'Meta', 'Shift']
13+
const changeHotkey = (newHotkey: ModifierKey) => () => {
14+
if (hotkey().includes(newHotkey)) {
15+
return setSettings({
16+
openHotkey: hotkey().filter((key) => key !== newHotkey),
17+
})
18+
}
19+
const existingModifiers = hotkey().filter((key) =>
20+
modifiers.includes(key as any),
21+
)
22+
const otherModifiers = hotkey().filter(
23+
(key) => !modifiers.includes(key as any),
24+
)
25+
setSettings({
26+
openHotkey: [...existingModifiers, newHotkey, ...otherModifiers],
27+
})
28+
}
1129
return (
1230
<div class={styles().settingsContainer}>
1331
{/* General Settings */}
@@ -137,20 +155,71 @@ export const SettingsTab = () => {
137155
Customize keyboard shortcuts for quick access.
138156
</p>
139157
<div class={styles().settingsGroup}>
158+
<div class={styles().settingsModifiers}>
159+
<Show keyed when={hotkey()}>
160+
<Button
161+
variant="success"
162+
onclick={changeHotkey('Shift')}
163+
outline={!hotkey().includes('Shift')}
164+
>
165+
Shift
166+
</Button>
167+
<Button
168+
variant="success"
169+
onclick={changeHotkey('Alt')}
170+
outline={!hotkey().includes('Alt')}
171+
>
172+
Alt
173+
</Button>
174+
<Button
175+
variant="success"
176+
onclick={changeHotkey('Meta')}
177+
outline={!hotkey().includes('Meta')}
178+
>
179+
Meta
180+
</Button>
181+
<Button
182+
variant="success"
183+
onclick={changeHotkey('Control')}
184+
outline={!hotkey().includes('Control')}
185+
>
186+
Control
187+
</Button>
188+
</Show>
189+
</div>
140190
<Input
141191
label="Hotkey to open/close devtools"
142-
description="Use '+' to combine keys (e.g., 'Ctrl+Shift+D' or 'Alt+D')"
143-
placeholder="Ctrl+Shift+D"
144-
value={settings().openHotkey.join('+')}
145-
onChange={(e) =>
146-
setSettings({
147-
openHotkey: e
148-
.split('+')
149-
.map((key) => uppercaseFirstLetter(key))
150-
.filter(Boolean),
192+
description="Use '+' to combine keys (e.g., 'a+b' or 'd'). This will be used with the enabled modifiers from above"
193+
placeholder="a"
194+
value={hotkey()
195+
.filter((key) => !['Shift', 'Meta', 'Alt', 'Ctrl'].includes(key))
196+
.join('+')}
197+
onChange={(e) => {
198+
const makeModifierArray = (key: string) => {
199+
if (key.length === 1) return [uppercaseFirstLetter(key)]
200+
const modifiers: Array<string> = []
201+
for (const character of key) {
202+
const newLetter = uppercaseFirstLetter(character)
203+
if (!modifiers.includes(newLetter)) modifiers.push(newLetter)
204+
}
205+
return modifiers
206+
}
207+
const modifiers = e
208+
.split('+')
209+
.flatMap((key) => makeModifierArray(key))
210+
.filter(Boolean)
211+
console.log(e, modifiers)
212+
return setSettings({
213+
openHotkey: [
214+
...hotkey().filter((key) =>
215+
['Shift', 'Meta', 'Alt', 'Ctrl'].includes(key),
216+
),
217+
...modifiers,
218+
],
151219
})
152-
}
220+
}}
153221
/>
222+
Final shortcut is: {hotkey().join(' + ')}
154223
</div>
155224
</div>
156225

packages/devtools/src/utils/sanitize.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,22 @@ export const tryParseJson = <T>(json: string | null): T | undefined => {
99

1010
export const uppercaseFirstLetter = (value: string) =>
1111
value.charAt(0).toUpperCase() + value.slice(1)
12+
13+
export const getAllPermutations = <T extends any>(arr: Array<T>) => {
14+
const res: Array<Array<T>> = []
15+
16+
function permutate(arr: Array<T>, start: number) {
17+
if (start === arr.length - 1) {
18+
res.push([...arr] as any)
19+
return
20+
}
21+
for (let i = start; i < arr.length; i++) {
22+
;[arr[start], arr[i]] = [arr[i]!, arr[start]!]
23+
permutate(arr, start + 1)
24+
;[arr[start], arr[i]] = [arr[i]!, arr[start]]
25+
}
26+
}
27+
permutate(arr, 0)
28+
29+
return res
30+
}

pnpm-lock.yaml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)