Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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