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
2 changes: 2 additions & 0 deletions packages/devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@
"build": "tsup"
},
"dependencies": {
"@solid-primitives/event-listener": "^2.4.3",
"@solid-primitives/keyboard": "^1.3.3",
"@solid-primitives/resize-observer": "^2.1.3",
"@tanstack/devtools-event-bus": "workspace:*",
"@tanstack/devtools-ui": "workspace:*",
"clsx": "^2.1.1",
Expand Down
158 changes: 158 additions & 0 deletions packages/devtools/src/components/source-inspector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { createEffect, createMemo, createSignal } from 'solid-js'
import { createStore } from 'solid-js/store'
import { createElementSize } from '@solid-primitives/resize-observer'
import { useKeyDownList } from '@solid-primitives/keyboard'
import { createEventListener } from '@solid-primitives/event-listener'

export const SourceInspector = () => {
const highlightStateInit = () => ({
element: null as HTMLElement | null,
bounding: { width: 0, height: 0, left: 0, top: 0 },
dataSource: '',
})

const [highlightState, setHighlightState] = createStore(highlightStateInit())
const resetHighlight = () => {
setHighlightState(highlightStateInit())
}

const [nameTagRef, setNameTagRef] = createSignal<HTMLDivElement | null>(null)
const nameTagSize = createElementSize(() => nameTagRef())

const [mousePosition, setMousePosition] = createStore({ x: 0, y: 0 })
createEventListener(document, 'mousemove', (e) => {
setMousePosition({ x: e.clientX, y: e.clientY })
})

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

createEffect(() => {
if (!isHighlightingKeysHeld()) {
resetHighlight()
return
}

const target = document.elementFromPoint(mousePosition.x, mousePosition.y)

if (!(target instanceof HTMLElement)) {
resetHighlight()
return
}

if (target === highlightState.element) {
return
}

const dataSource = target.getAttribute('data-tsd-source')
if (!dataSource) {
resetHighlight()
return
}

const rect = target.getBoundingClientRect()
const bounding = {
width: rect.width,
height: rect.height,
left: rect.left,
top: rect.top,
}

setHighlightState({
element: target,
bounding,
dataSource,
})
})

createEventListener(document, 'click', (e) => {
if (!highlightState.element) return

window.getSelection()?.removeAllRanges()
e.preventDefault()
e.stopPropagation()

fetch(
`${location.origin}/__tsd/open-source?source=${encodeURIComponent(
highlightState.dataSource,
)}`,
).catch(() => {})
})

const currentElementBoxStyles = createMemo(() => {
if (highlightState.element) {
return {
display: 'block',
width: `${highlightState.bounding.width}px`,
height: `${highlightState.bounding.height}px`,
left: `${highlightState.bounding.left}px`,
top: `${highlightState.bounding.top}px`,

'background-color': 'oklch(55.4% 0.046 257.417 /0.25)',
transition: 'all 0.05s linear',
position: 'fixed' as const,
'z-index': 9999,
}
}
return {
display: 'none',
}
})

const fileNameStyles = createMemo(() => {
if (highlightState.element && nameTagRef()) {
const windowWidth = window.innerWidth
const nameTagHeight = nameTagSize.height || 26
const nameTagWidth = nameTagSize.width || 0
let left = highlightState.bounding.left
let top = highlightState.bounding.top - nameTagHeight - 4

if (top < 0) {
top = highlightState.bounding.top + highlightState.bounding.height + 4
}

if (left + nameTagWidth > windowWidth) {
left = windowWidth - nameTagWidth - 4
}

if (left < 0) {
left = 4
}

return {
position: 'fixed' as const,
left: `${left}px`,
top: `${top}px`,
'background-color': 'oklch(55.4% 0.046 257.417 /0.80)',
color: 'white',
padding: '2px 4px',
fontSize: '12px',
'border-radius': '2px',
'z-index': 10000,
visibility: 'visible' as const,
transition: 'all 0.05s linear',
}
}
return {
display: 'none',
}
})

return (
<>
<div
ref={setNameTagRef}
style={{ ...fileNameStyles(), 'pointer-events': 'none' }}
>
{highlightState.dataSource.split(':')[0]}
</div>
<div style={{ ...currentElementBoxStyles(), 'pointer-events': 'none' }} />
</>
)
}
31 changes: 3 additions & 28 deletions packages/devtools/src/devtools.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Show, createEffect, createSignal, onCleanup } from 'solid-js'
import { Show, createEffect, createSignal } from 'solid-js'
import { createShortcut } from '@solid-primitives/keyboard'
import { Portal } from 'solid-js/web'
import { ThemeContextProvider } from '@tanstack/devtools-ui'
Expand All @@ -18,6 +18,7 @@ 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'

export default function DevTools() {
const { settings } = useDevtoolsSettings()
Expand Down Expand Up @@ -159,33 +160,6 @@ export default function DevTools() {
}
})

createEffect(() => {
// this will only work with the Vite plugin
const openSourceHandler = (e: Event) => {
const isShiftHeld = (e as KeyboardEvent).shiftKey
const isCtrlHeld =
(e as KeyboardEvent).ctrlKey || (e as KeyboardEvent).metaKey
if (!isShiftHeld || !isCtrlHeld) return

if (e.target instanceof HTMLElement) {
const dataSource = e.target.getAttribute('data-tsd-source')
window.getSelection()?.removeAllRanges()
if (dataSource) {
e.preventDefault()
e.stopPropagation()
fetch(
`${location.origin}/__tsd/open-source?source=${encodeURIComponent(
dataSource,
)}`,
).catch(() => {})
}
}
}
window.addEventListener('click', openSourceHandler)
onCleanup(() => {
window.removeEventListener('click', openSourceHandler)
})
})
const { theme } = useTheme()

return (
Expand Down Expand Up @@ -216,6 +190,7 @@ export default function DevTools() {
</ContentPanel>
</MainPanel>
</Show>
<SourceInspector />
</div>
</Portal>
</ThemeContextProvider>
Expand Down
Loading
Loading