Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eight-pianos-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/devtools': patch
---

improve devtools shortcut handling
6 changes: 6 additions & 0 deletions packages/devtools/src/context/devtools-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import type { TanStackDevtoolsPlugin } from './devtools-context'

type ModifierKey = 'Alt' | 'Control' | 'Meta' | 'Shift'
type KeyboardKey = ModifierKey | (string & {})
export const keyboardModifiers: Array<ModifierKey> = [
'Alt',
'Control',
'Meta',
'Shift',
]

type TriggerPosition =
| 'top-left'
Expand Down
22 changes: 18 additions & 4 deletions packages/devtools/src/devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { MainPanel } from './components/main-panel'
import { ContentPanel } from './components/content-panel'
import { Tabs } from './components/tabs'
import { TabContent } from './components/tab-content'
import { keyboardModifiers } from './context/devtools-store'
import { getAllPermutations } from './utils/sanitize'

export default function DevTools() {
const { settings } = useDevtoolsSettings()
Expand Down Expand Up @@ -131,12 +133,24 @@ export default function DevTools() {
}
})
createEffect(() => {
createShortcut(settings().openHotkey, () => {
toggleOpen()
})
// we create all combinations of modifiers
const modifiers = settings().openHotkey.filter((key) =>
keyboardModifiers.includes(key as any),
)
const nonModifiers = settings().openHotkey.filter(
(key) => !keyboardModifiers.includes(key as any),
)

const allModifierCombinations = getAllPermutations(modifiers)

for (const combination of allModifierCombinations) {
const permutation = [...combination, ...nonModifiers]
createShortcut(permutation, () => {
toggleOpen()
})
}
})

createEffect(() => {})
return (
<div ref={setRootEl} data-testid={TANSTACK_DEVTOOLS}>
<Show
Expand Down
4 changes: 4 additions & 0 deletions packages/devtools/src/styles/use-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,10 @@ const stylesFactory = () => {
grid-template-columns: 1fr;
}
`,
settingsModifiers: css`
display: flex;
gap: 0.5rem;
`,
}
}

Expand Down
95 changes: 82 additions & 13 deletions packages/devtools/src/tabs/settings-tab.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import { Show } from 'solid-js'
import { Checkbox, Input, Select } from '@tanstack/devtools-ui'
import { Show, createMemo } from 'solid-js'
import { Button, Checkbox, Input, Select } from '@tanstack/devtools-ui'
import { useDevtoolsSettings } from '../context/use-devtools-context'
import { uppercaseFirstLetter } from '../utils/sanitize'
import { useStyles } from '../styles/use-styles'
import type { ModifierKey } from '@solid-primitives/keyboard'

export const SettingsTab = () => {
const { setSettings, settings } = useDevtoolsSettings()
const styles = useStyles()

const hotkey = createMemo(() => settings().openHotkey)
const modifiers: Array<ModifierKey> = ['Control', 'Alt', 'Meta', 'Shift']
const changeHotkey = (newHotkey: ModifierKey) => () => {
if (hotkey().includes(newHotkey)) {
return setSettings({
openHotkey: hotkey().filter((key) => key !== newHotkey),
})
}
const existingModifiers = hotkey().filter((key) =>
modifiers.includes(key as any),
)
const otherModifiers = hotkey().filter(
(key) => !modifiers.includes(key as any),
)
setSettings({
openHotkey: [...existingModifiers, newHotkey, ...otherModifiers],
})
}
return (
<div class={styles().settingsContainer}>
{/* General Settings */}
Expand Down Expand Up @@ -137,20 +155,71 @@ export const SettingsTab = () => {
Customize keyboard shortcuts for quick access.
</p>
<div class={styles().settingsGroup}>
<div class={styles().settingsModifiers}>
<Show keyed when={hotkey()}>
<Button
variant="success"
onclick={changeHotkey('Shift')}
outline={!hotkey().includes('Shift')}
>
Shift
</Button>
<Button
variant="success"
onclick={changeHotkey('Alt')}
outline={!hotkey().includes('Alt')}
>
Alt
</Button>
<Button
variant="success"
onclick={changeHotkey('Meta')}
outline={!hotkey().includes('Meta')}
>
Meta
</Button>
<Button
variant="success"
onclick={changeHotkey('Control')}
outline={!hotkey().includes('Control')}
>
Control
</Button>
</Show>
</div>
<Input
label="Hotkey to open/close devtools"
description="Use '+' to combine keys (e.g., 'Ctrl+Shift+D' or 'Alt+D')"
placeholder="Ctrl+Shift+D"
value={settings().openHotkey.join('+')}
onChange={(e) =>
setSettings({
openHotkey: e
.split('+')
.map((key) => uppercaseFirstLetter(key))
.filter(Boolean),
description="Use '+' to combine keys (e.g., 'a+b' or 'd'). This will be used with the enabled modifiers from above"
placeholder="a"
value={hotkey()
.filter((key) => !['Shift', 'Meta', 'Alt', 'Ctrl'].includes(key))
.join('+')}
onChange={(e) => {
const makeModifierArray = (key: string) => {
if (key.length === 1) return [uppercaseFirstLetter(key)]
const modifiers: Array<string> = []
for (const character of key) {
const newLetter = uppercaseFirstLetter(character)
if (!modifiers.includes(newLetter)) modifiers.push(newLetter)
}
return modifiers
}
const modifiers = e
.split('+')
.flatMap((key) => makeModifierArray(key))
.filter(Boolean)
console.log(e, modifiers)
return setSettings({
openHotkey: [
...hotkey().filter((key) =>
['Shift', 'Meta', 'Alt', 'Ctrl'].includes(key),
),
...modifiers,
],
})
}
}}
/>
Final shortcut is: {hotkey().join(' + ')}
</div>
</div>

Expand Down
19 changes: 19 additions & 0 deletions packages/devtools/src/utils/sanitize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,22 @@ export const tryParseJson = <T>(json: string | null): T | undefined => {

export const uppercaseFirstLetter = (value: string) =>
value.charAt(0).toUpperCase() + value.slice(1)

export const getAllPermutations = <T extends any>(arr: Array<T>) => {
const res: Array<Array<T>> = []

function permutate(arr: Array<T>, start: number) {
if (start === arr.length - 1) {
res.push([...arr] as any)
return
}
for (let i = start; i < arr.length; i++) {
;[arr[start], arr[i]] = [arr[i]!, arr[start]!]
permutate(arr, start + 1)
;[arr[start], arr[i]] = [arr[i]!, arr[start]]
}
}
permutate(arr, 0)

return res
}
2 changes: 2 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.