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/curly-news-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/devtools': minor
---

add inspectHotkey to devtools configuration
11 changes: 10 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,21 @@ The `config` object is mainly focused around user interaction with the devtools
- `openHotkey` - The hotkey set to open the devtools

```ts
type ModifierKey = 'Alt' | 'Control' | 'Meta' | 'Shift';
type ModifierKey = 'Alt' | 'Control' | 'Meta' | 'Shift' | 'CtrlOrMeta';
type KeyboardKey = ModifierKey | (string & {});

{ openHotkey: Array<KeyboardKey> }
```

- `inspectHotkey` - The hotkey set to open the source inspector

```ts
type ModifierKey = 'Alt' | 'Control' | 'Meta' | 'Shift' | 'CtrlOrMeta';
type KeyboardKey = ModifierKey | (string & {});

{ inspectHotkey: Array<KeyboardKey> }
```

- `requireUrlFlag` - Requires a flag present in the url to enable devtools

```ts
Expand Down
11 changes: 6 additions & 5 deletions packages/devtools/src/components/source-inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { createElementSize } from '@solid-primitives/resize-observer'
import { useKeyDownList } from '@solid-primitives/keyboard'
import { createEventListener } from '@solid-primitives/event-listener'

import { useDevtoolsSettings } from '../context/use-devtools-context'
import { isHotkeyCombinationPressed } from '../utils/hotkey'

export const SourceInspector = () => {
const { settings } = useDevtoolsSettings()
const highlightStateInit = () => ({
element: null as HTMLElement | null,
bounding: { width: 0, height: 0, left: 0, top: 0 },
Expand All @@ -25,12 +29,9 @@ export const SourceInspector = () => {
})

const downList = useKeyDownList()

const isHighlightingKeysHeld = createMemo(() => {
const keys = downList()
const isShiftHeld = keys.includes('SHIFT')
const isCtrlHeld = keys.includes('CONTROL')
const isMetaHeld = keys.includes('META')
return isShiftHeld && (isCtrlHeld || isMetaHeld)
return isHotkeyCombinationPressed(downList(), settings().inspectHotkey)
})

createEffect(() => {
Expand Down
12 changes: 10 additions & 2 deletions packages/devtools/src/context/devtools-store.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type { TabName } from '../tabs'
import type { TanStackDevtoolsPlugin } from './devtools-context'

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

type TriggerPosition =
Expand Down Expand Up @@ -47,9 +49,14 @@ export type DevtoolsStore = {
panelLocation: 'top' | 'bottom'
/**
* The hotkey to open the dev tools
* @default "shift+a"
* @default ["Shift", "A"]
*/
openHotkey: Array<KeyboardKey>
/**
* The hotkey to open the source inspector
* @default ["Shift", "CtrlOrMeta"]
*/
inspectHotkey: Array<KeyboardKey>
/**
* Whether to require the URL flag to open the dev tools
* @default false
Expand Down Expand Up @@ -93,6 +100,7 @@ export const initialState: DevtoolsStore = {
position: 'bottom-right',
panelLocation: 'bottom',
openHotkey: ['Shift', 'A'],
inspectHotkey: ['Shift', 'CtrlOrMeta'],
requireUrlFlag: false,
urlFlag: 'tanstack-devtools',
theme:
Expand Down
19 changes: 5 additions & 14 deletions packages/devtools/src/devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createShortcut } from '@solid-primitives/keyboard'
import { Portal } from 'solid-js/web'
import { ThemeContextProvider } from '@tanstack/devtools-ui'
import { devtoolsEventClient } from '@tanstack/devtools-client'

import {
useDevtoolsSettings,
useHeight,
Expand All @@ -11,13 +12,12 @@ import {
} from './context/use-devtools-context'
import { useDisableTabbing } from './hooks/use-disable-tabbing'
import { TANSTACK_DEVTOOLS } from './utils/storage'
import { getHotkeyPermutations } from './utils/hotkey'
import { Trigger } from './components/trigger'
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'
import { usePiPWindow } from './context/pip-context'
import { SourceInspector } from './components/source-inspector'

Expand Down Expand Up @@ -165,18 +165,9 @@ export default function DevTools() {
}
})
createEffect(() => {
// 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]
const permutations = getHotkeyPermutations(settings().openHotkey)

for (const permutation of permutations) {
createShortcut(permutation, () => {
toggleOpen()
})
Expand Down
5 changes: 5 additions & 0 deletions packages/devtools/src/styles/use-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,11 @@ const stylesFactory = (theme: DevtoolsStore['settings']['theme']) => {
display: flex;
gap: 0.5rem;
`,
settingsStack: css`
display: flex;
flex-direction: column;
gap: 1rem;
`,

// No Plugins Fallback Styles
noPluginsFallback: css`
Expand Down
97 changes: 97 additions & 0 deletions packages/devtools/src/tabs/hotkey-config.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Show } from 'solid-js'
import { Button, Input } from '@tanstack/devtools-ui'

import { uppercaseFirstLetter } from '../utils/sanitize'
import { useStyles } from '../styles/use-styles'
import type { KeyboardKey } from '../context/devtools-store'

interface HotkeyConfigProps {
title: string
description: string
hotkey: Array<KeyboardKey>
modifiers: Array<KeyboardKey>
onHotkeyChange: (hotkey: Array<KeyboardKey>) => void
}

const MODIFIER_DISPLAY_NAMES: Record<KeyboardKey, string> = {
Shift: 'Shift',
Alt: 'Alt',
Meta: 'Meta',
Control: 'Control',
CtrlOrMeta: 'Ctrl Or Meta',
}

export const HotkeyConfig = (props: HotkeyConfigProps) => {
const styles = useStyles()

const toggleModifier = (modifier: KeyboardKey) => {
if (props.hotkey.includes(modifier)) {
props.onHotkeyChange(props.hotkey.filter((key) => key !== modifier))
} else {
const existingModifiers = props.hotkey.filter((key) =>
props.modifiers.includes(key as any),
)
const otherKeys = props.hotkey.filter(
(key) => !props.modifiers.includes(key as any),
)
props.onHotkeyChange([...existingModifiers, modifier, ...otherKeys])
}
}

const getNonModifierValue = () => {
return props.hotkey
.filter((key) => !props.modifiers.includes(key as any))
.join('+')
}

const handleKeyInput = (input: string) => {
const makeModifierArray = (key: string) => {
if (key.length === 1) return [uppercaseFirstLetter(key)]
const modifiersArray: Array<string> = []
for (const character of key) {
const newLetter = uppercaseFirstLetter(character)
if (!modifiersArray.includes(newLetter)) modifiersArray.push(newLetter)
}
return modifiersArray
}

const hotkeyModifiers = props.hotkey.filter((key) =>
props.modifiers.includes(key as any),
)
const newKeys = input
.split('+')
.flatMap((key) => makeModifierArray(key))
.filter(Boolean)
props.onHotkeyChange([...hotkeyModifiers, ...newKeys])
}

const getDisplayHotkey = () => {
return props.hotkey.join(' + ')
}

return (
<div class={styles().settingsGroup}>
<h4 style={{ margin: 0 }}>{props.description}</h4>
<div class={styles().settingsModifiers}>
<Show keyed when={props.hotkey}>
{props.modifiers.map((modifier) => (
<Button
variant="success"
onclick={() => toggleModifier(modifier)}
outline={!props.hotkey.includes(modifier)}
>
{MODIFIER_DISPLAY_NAMES[modifier] || modifier}
</Button>
))}
</Show>
</div>
<Input
description="Use '+' to combine keys (e.g., 'a+b' or 'd'). This will be used with the enabled modifiers from above"
placeholder="a"
value={getNonModifierValue()}
onChange={handleKeyInput}
/>
Final shortcut is: {getDisplayHotkey()}
</div>
)
}
109 changes: 23 additions & 86 deletions packages/devtools/src/tabs/settings-tab.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Show, createMemo } from 'solid-js'
import { Show } from 'solid-js'
import {
Button,
Checkbox,
Input,
MainPanel,
Expand All @@ -16,32 +15,18 @@ import {
Link,
SettingsCog,
} from '@tanstack/devtools-ui/icons'

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'
import { HotkeyConfig } from './hotkey-config'
import type { KeyboardKey } from '../context/devtools-store'

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],
})
}

const modifiers: Array<KeyboardKey> = ['CtrlOrMeta', 'Alt', 'Shift']

return (
<MainPanel withPadding>
<Section>
Expand Down Expand Up @@ -144,71 +129,23 @@ export const SettingsTab = () => {
<SectionDescription>
Customize keyboard shortcuts for quick access.
</SectionDescription>
<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., '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)
return setSettings({
openHotkey: [
...hotkey().filter((key) =>
['Shift', 'Meta', 'Alt', 'Ctrl'].includes(key),
),
...modifiers,
],
})
}}

<div class={styles().settingsStack}>
<HotkeyConfig
title="Open/Close Devtools"
description="Hotkey to open/close devtools"
hotkey={settings().openHotkey}
modifiers={modifiers}
onHotkeyChange={(hotkey) => setSettings({ openHotkey: hotkey })}
/>

<HotkeyConfig
title="Source Inspector"
description="Hotkey to open source inspector"
hotkey={settings().inspectHotkey}
modifiers={modifiers}
onHotkeyChange={(hotkey) => setSettings({ inspectHotkey: hotkey })}
/>
Final shortcut is: {hotkey().join(' + ')}
</div>
</Section>

Expand Down
Loading