Skip to content

Commit 6d3bdc0

Browse files
zaidhaanautofix-ci[bot]AlemTuzlak
authored
feat: add inspector hotkey configuration (inspectHotkey) (#275)
* feat: inspector hotkey configuration * feat: refactor hotkey handling * feat: refactor hotkey UI configuration + update docs * chore: add some tests * chore: changeset * ci: apply automated fixes * remove extra modifiers * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Alem Tuzlak <t.zlak97@gmail.com> Co-authored-by: Alem Tuzlak <t.zlak@hotmail.com>
1 parent 8ff7ae5 commit 6d3bdc0

File tree

11 files changed

+338
-108
lines changed

11 files changed

+338
-108
lines changed

.changeset/curly-news-work.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/devtools': minor
3+
---
4+
5+
add inspectHotkey to devtools configuration

docs/configuration.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,21 @@ The `config` object is mainly focused around user interaction with the devtools
4545
- `openHotkey` - The hotkey set to open the devtools
4646

4747
```ts
48-
type ModifierKey = 'Alt' | 'Control' | 'Meta' | 'Shift';
48+
type ModifierKey = 'Alt' | 'Control' | 'Meta' | 'Shift' | 'CtrlOrMeta';
4949
type KeyboardKey = ModifierKey | (string & {});
5050

5151
{ openHotkey: Array<KeyboardKey> }
5252
```
5353

54+
- `inspectHotkey` - The hotkey set to open the source inspector
55+
56+
```ts
57+
type ModifierKey = 'Alt' | 'Control' | 'Meta' | 'Shift' | 'CtrlOrMeta';
58+
type KeyboardKey = ModifierKey | (string & {});
59+
60+
{ inspectHotkey: Array<KeyboardKey> }
61+
```
62+
5463
- `requireUrlFlag` - Requires a flag present in the url to enable devtools
5564

5665
```ts

packages/devtools/src/components/source-inspector.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import { createElementSize } from '@solid-primitives/resize-observer'
44
import { useKeyDownList } from '@solid-primitives/keyboard'
55
import { createEventListener } from '@solid-primitives/event-listener'
66

7+
import { useDevtoolsSettings } from '../context/use-devtools-context'
8+
import { isHotkeyCombinationPressed } from '../utils/hotkey'
9+
710
export const SourceInspector = () => {
11+
const { settings } = useDevtoolsSettings()
812
const highlightStateInit = () => ({
913
element: null as HTMLElement | null,
1014
bounding: { width: 0, height: 0, left: 0, top: 0 },
@@ -25,12 +29,9 @@ export const SourceInspector = () => {
2529
})
2630

2731
const downList = useKeyDownList()
32+
2833
const isHighlightingKeysHeld = createMemo(() => {
29-
const keys = downList()
30-
const isShiftHeld = keys.includes('SHIFT')
31-
const isCtrlHeld = keys.includes('CONTROL')
32-
const isMetaHeld = keys.includes('META')
33-
return isShiftHeld && (isCtrlHeld || isMetaHeld)
34+
return isHotkeyCombinationPressed(downList(), settings().inspectHotkey)
3435
})
3536

3637
createEffect(() => {

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import type { TabName } from '../tabs'
22
import type { TanStackDevtoolsPlugin } from './devtools-context'
33

4-
type ModifierKey = 'Alt' | 'Control' | 'Meta' | 'Shift'
4+
type ModifierKey = 'Alt' | 'Control' | 'Meta' | 'Shift' | 'CtrlOrMeta'
55
type KeyboardKey = ModifierKey | (string & {})
6+
export type { ModifierKey, KeyboardKey }
67
export const keyboardModifiers: Array<ModifierKey> = [
78
'Alt',
89
'Control',
910
'Meta',
1011
'Shift',
12+
'CtrlOrMeta',
1113
]
1214

1315
type TriggerPosition =
@@ -47,9 +49,14 @@ export type DevtoolsStore = {
4749
panelLocation: 'top' | 'bottom'
4850
/**
4951
* The hotkey to open the dev tools
50-
* @default "shift+a"
52+
* @default ["Shift", "A"]
5153
*/
5254
openHotkey: Array<KeyboardKey>
55+
/**
56+
* The hotkey to open the source inspector
57+
* @default ["Shift", "CtrlOrMeta"]
58+
*/
59+
inspectHotkey: Array<KeyboardKey>
5360
/**
5461
* Whether to require the URL flag to open the dev tools
5562
* @default false
@@ -93,6 +100,7 @@ export const initialState: DevtoolsStore = {
93100
position: 'bottom-right',
94101
panelLocation: 'bottom',
95102
openHotkey: ['Shift', 'A'],
103+
inspectHotkey: ['Shift', 'CtrlOrMeta'],
96104
requireUrlFlag: false,
97105
urlFlag: 'tanstack-devtools',
98106
theme:

packages/devtools/src/devtools.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createShortcut } from '@solid-primitives/keyboard'
33
import { Portal } from 'solid-js/web'
44
import { ThemeContextProvider } from '@tanstack/devtools-ui'
55
import { devtoolsEventClient } from '@tanstack/devtools-client'
6+
67
import {
78
useDevtoolsSettings,
89
useHeight,
@@ -11,13 +12,12 @@ import {
1112
} from './context/use-devtools-context'
1213
import { useDisableTabbing } from './hooks/use-disable-tabbing'
1314
import { TANSTACK_DEVTOOLS } from './utils/storage'
15+
import { getHotkeyPermutations } from './utils/hotkey'
1416
import { Trigger } from './components/trigger'
1517
import { MainPanel } from './components/main-panel'
1618
import { ContentPanel } from './components/content-panel'
1719
import { Tabs } from './components/tabs'
1820
import { TabContent } from './components/tab-content'
19-
import { keyboardModifiers } from './context/devtools-store'
20-
import { getAllPermutations } from './utils/sanitize'
2121
import { usePiPWindow } from './context/pip-context'
2222
import { SourceInspector } from './components/source-inspector'
2323

@@ -165,18 +165,9 @@ export default function DevTools() {
165165
}
166166
})
167167
createEffect(() => {
168-
// we create all combinations of modifiers
169-
const modifiers = settings().openHotkey.filter((key) =>
170-
keyboardModifiers.includes(key as any),
171-
)
172-
const nonModifiers = settings().openHotkey.filter(
173-
(key) => !keyboardModifiers.includes(key as any),
174-
)
175-
176-
const allModifierCombinations = getAllPermutations(modifiers)
177-
178-
for (const combination of allModifierCombinations) {
179-
const permutation = [...combination, ...nonModifiers]
168+
const permutations = getHotkeyPermutations(settings().openHotkey)
169+
170+
for (const permutation of permutations) {
180171
createShortcut(permutation, () => {
181172
toggleOpen()
182173
})

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,11 @@ const stylesFactory = (theme: DevtoolsStore['settings']['theme']) => {
466466
display: flex;
467467
gap: 0.5rem;
468468
`,
469+
settingsStack: css`
470+
display: flex;
471+
flex-direction: column;
472+
gap: 1rem;
473+
`,
469474

470475
// No Plugins Fallback Styles
471476
noPluginsFallback: css`
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { Show } from 'solid-js'
2+
import { Button, Input } from '@tanstack/devtools-ui'
3+
4+
import { uppercaseFirstLetter } from '../utils/sanitize'
5+
import { useStyles } from '../styles/use-styles'
6+
import type { KeyboardKey } from '../context/devtools-store'
7+
8+
interface HotkeyConfigProps {
9+
title: string
10+
description: string
11+
hotkey: Array<KeyboardKey>
12+
modifiers: Array<KeyboardKey>
13+
onHotkeyChange: (hotkey: Array<KeyboardKey>) => void
14+
}
15+
16+
const MODIFIER_DISPLAY_NAMES: Record<KeyboardKey, string> = {
17+
Shift: 'Shift',
18+
Alt: 'Alt',
19+
Meta: 'Meta',
20+
Control: 'Control',
21+
CtrlOrMeta: 'Ctrl Or Meta',
22+
}
23+
24+
export const HotkeyConfig = (props: HotkeyConfigProps) => {
25+
const styles = useStyles()
26+
27+
const toggleModifier = (modifier: KeyboardKey) => {
28+
if (props.hotkey.includes(modifier)) {
29+
props.onHotkeyChange(props.hotkey.filter((key) => key !== modifier))
30+
} else {
31+
const existingModifiers = props.hotkey.filter((key) =>
32+
props.modifiers.includes(key as any),
33+
)
34+
const otherKeys = props.hotkey.filter(
35+
(key) => !props.modifiers.includes(key as any),
36+
)
37+
props.onHotkeyChange([...existingModifiers, modifier, ...otherKeys])
38+
}
39+
}
40+
41+
const getNonModifierValue = () => {
42+
return props.hotkey
43+
.filter((key) => !props.modifiers.includes(key as any))
44+
.join('+')
45+
}
46+
47+
const handleKeyInput = (input: string) => {
48+
const makeModifierArray = (key: string) => {
49+
if (key.length === 1) return [uppercaseFirstLetter(key)]
50+
const modifiersArray: Array<string> = []
51+
for (const character of key) {
52+
const newLetter = uppercaseFirstLetter(character)
53+
if (!modifiersArray.includes(newLetter)) modifiersArray.push(newLetter)
54+
}
55+
return modifiersArray
56+
}
57+
58+
const hotkeyModifiers = props.hotkey.filter((key) =>
59+
props.modifiers.includes(key as any),
60+
)
61+
const newKeys = input
62+
.split('+')
63+
.flatMap((key) => makeModifierArray(key))
64+
.filter(Boolean)
65+
props.onHotkeyChange([...hotkeyModifiers, ...newKeys])
66+
}
67+
68+
const getDisplayHotkey = () => {
69+
return props.hotkey.join(' + ')
70+
}
71+
72+
return (
73+
<div class={styles().settingsGroup}>
74+
<h4 style={{ margin: 0 }}>{props.description}</h4>
75+
<div class={styles().settingsModifiers}>
76+
<Show keyed when={props.hotkey}>
77+
{props.modifiers.map((modifier) => (
78+
<Button
79+
variant="success"
80+
onclick={() => toggleModifier(modifier)}
81+
outline={!props.hotkey.includes(modifier)}
82+
>
83+
{MODIFIER_DISPLAY_NAMES[modifier] || modifier}
84+
</Button>
85+
))}
86+
</Show>
87+
</div>
88+
<Input
89+
description="Use '+' to combine keys (e.g., 'a+b' or 'd'). This will be used with the enabled modifiers from above"
90+
placeholder="a"
91+
value={getNonModifierValue()}
92+
onChange={handleKeyInput}
93+
/>
94+
Final shortcut is: {getDisplayHotkey()}
95+
</div>
96+
)
97+
}

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

Lines changed: 23 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { Show, createMemo } from 'solid-js'
1+
import { Show } from 'solid-js'
22
import {
3-
Button,
43
Checkbox,
54
Input,
65
MainPanel,
@@ -16,32 +15,18 @@ import {
1615
Link,
1716
SettingsCog,
1817
} from '@tanstack/devtools-ui/icons'
18+
1919
import { useDevtoolsSettings } from '../context/use-devtools-context'
20-
import { uppercaseFirstLetter } from '../utils/sanitize'
2120
import { useStyles } from '../styles/use-styles'
22-
import type { ModifierKey } from '@solid-primitives/keyboard'
21+
import { HotkeyConfig } from './hotkey-config'
22+
import type { KeyboardKey } from '../context/devtools-store'
2323

2424
export const SettingsTab = () => {
2525
const { setSettings, settings } = useDevtoolsSettings()
2626
const styles = useStyles()
27-
const hotkey = createMemo(() => settings().openHotkey)
28-
const modifiers: Array<ModifierKey> = ['Control', 'Alt', 'Meta', 'Shift']
29-
const changeHotkey = (newHotkey: ModifierKey) => () => {
30-
if (hotkey().includes(newHotkey)) {
31-
return setSettings({
32-
openHotkey: hotkey().filter((key) => key !== newHotkey),
33-
})
34-
}
35-
const existingModifiers = hotkey().filter((key) =>
36-
modifiers.includes(key as any),
37-
)
38-
const otherModifiers = hotkey().filter(
39-
(key) => !modifiers.includes(key as any),
40-
)
41-
setSettings({
42-
openHotkey: [...existingModifiers, newHotkey, ...otherModifiers],
43-
})
44-
}
27+
28+
const modifiers: Array<KeyboardKey> = ['CtrlOrMeta', 'Alt', 'Shift']
29+
4530
return (
4631
<MainPanel withPadding>
4732
<Section>
@@ -144,71 +129,23 @@ export const SettingsTab = () => {
144129
<SectionDescription>
145130
Customize keyboard shortcuts for quick access.
146131
</SectionDescription>
147-
<div class={styles().settingsGroup}>
148-
<div class={styles().settingsModifiers}>
149-
<Show keyed when={hotkey()}>
150-
<Button
151-
variant="success"
152-
onclick={changeHotkey('Shift')}
153-
outline={!hotkey().includes('Shift')}
154-
>
155-
Shift
156-
</Button>
157-
<Button
158-
variant="success"
159-
onclick={changeHotkey('Alt')}
160-
outline={!hotkey().includes('Alt')}
161-
>
162-
Alt
163-
</Button>
164-
<Button
165-
variant="success"
166-
onclick={changeHotkey('Meta')}
167-
outline={!hotkey().includes('Meta')}
168-
>
169-
Meta
170-
</Button>
171-
<Button
172-
variant="success"
173-
onclick={changeHotkey('Control')}
174-
outline={!hotkey().includes('Control')}
175-
>
176-
Control
177-
</Button>
178-
</Show>
179-
</div>
180-
<Input
181-
label="Hotkey to open/close devtools"
182-
description="Use '+' to combine keys (e.g., 'a+b' or 'd'). This will be used with the enabled modifiers from above"
183-
placeholder="a"
184-
value={hotkey()
185-
.filter((key) => !['Shift', 'Meta', 'Alt', 'Ctrl'].includes(key))
186-
.join('+')}
187-
onChange={(e) => {
188-
const makeModifierArray = (key: string) => {
189-
if (key.length === 1) return [uppercaseFirstLetter(key)]
190-
const modifiers: Array<string> = []
191-
for (const character of key) {
192-
const newLetter = uppercaseFirstLetter(character)
193-
if (!modifiers.includes(newLetter)) modifiers.push(newLetter)
194-
}
195-
return modifiers
196-
}
197-
const modifiers = e
198-
.split('+')
199-
.flatMap((key) => makeModifierArray(key))
200-
.filter(Boolean)
201-
return setSettings({
202-
openHotkey: [
203-
...hotkey().filter((key) =>
204-
['Shift', 'Meta', 'Alt', 'Ctrl'].includes(key),
205-
),
206-
...modifiers,
207-
],
208-
})
209-
}}
132+
133+
<div class={styles().settingsStack}>
134+
<HotkeyConfig
135+
title="Open/Close Devtools"
136+
description="Hotkey to open/close devtools"
137+
hotkey={settings().openHotkey}
138+
modifiers={modifiers}
139+
onHotkeyChange={(hotkey) => setSettings({ openHotkey: hotkey })}
140+
/>
141+
142+
<HotkeyConfig
143+
title="Source Inspector"
144+
description="Hotkey to open source inspector"
145+
hotkey={settings().inspectHotkey}
146+
modifiers={modifiers}
147+
onHotkeyChange={(hotkey) => setSettings({ inspectHotkey: hotkey })}
210148
/>
211-
Final shortcut is: {hotkey().join(' + ')}
212149
</div>
213150
</Section>
214151

0 commit comments

Comments
 (0)