import { createEffect, createMemo, createSignal, onCleanup, onMount, } from 'solid-js' import { Header, HeaderLogo, MainPanel } from '@tanstack/devtools-ui' import { useStyles } from '../styles/use-styles' import { usePacerDevtoolsState } from '../PacerContextProvider' import { UTIL_GROUPS } from './util-groups' import { UtilList } from './UtilList' import { DetailsPanel } from './DetailsPanel' import type { StateKey } from './util-groups' function readResizeObserverBlockSize(entry: ResizeObserverEntry): number { const [box] = entry.borderBoxSize if (box !== undefined) return box.blockSize return entry.contentRect.height } /** Outer plugin slot from TanStack Devtools (not the inner React mount div, which can report a tiny height). */ function resolvePluginHost(el: HTMLElement): HTMLElement | null { const byId = el.closest('[id^="plugin-container-"]') if (byId) return byId return ( el.parentElement?.parentElement?.parentElement ?? el.parentElement?.parentElement ?? el.parentElement ) } export function Shell() { const styles = useStyles() const state = usePacerDevtoolsState() const utilState = () => state const [selectedKey, setSelectedKey] = createSignal(null) const [leftPanelWidth, setLeftPanelWidth] = createSignal(300) const [isDragging, setIsDragging] = createSignal(false) const [shellRootEl, setShellRootEl] = createSignal( null, ) const [slotHeightPx, setSlotHeightPx] = createSignal( undefined, ) const selectedInstance = createMemo(() => { const key = selectedKey() if (!key) return null for (const group of UTIL_GROUPS) { const instance = (utilState() as unknown as Record>)[ group.key ].find((inst) => inst.key === key) if (instance) return { instance, type: group.displayName } } return null }) let dragStartX = 0 let dragStartWidth = 0 const handleMouseDown = (e: MouseEvent) => { e.preventDefault() e.stopPropagation() setIsDragging(true) document.body.style.cursor = 'col-resize' document.body.style.userSelect = 'none' dragStartX = e.clientX dragStartWidth = leftPanelWidth() } const handleMouseMove = (e: MouseEvent) => { if (!isDragging()) return e.preventDefault() const deltaX = e.clientX - dragStartX const newWidth = Math.max(150, Math.min(800, dragStartWidth + deltaX)) setLeftPanelWidth(newWidth) } const handleMouseUp = () => { setIsDragging(false) document.body.style.cursor = '' document.body.style.userSelect = '' } onMount(() => { document.addEventListener('mousemove', handleMouseMove) document.addEventListener('mouseup', handleMouseUp) }) onCleanup(() => { document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) }) /** * TanStack Devtools plugin slots use height: 100% without min-height: 0 on flex * ancestors, so percentage height often never constrains our tree. Observing the * outer `plugin-container-*` host (not the immediate React mount wrapper) and * setting an explicit pixel height makes inner overflow-y regions scroll without * capping to the mount div’s collapsed height. */ createEffect(() => { const el = shellRootEl() if (!el || typeof ResizeObserver === 'undefined') return const pluginSlot = resolvePluginHost(el) if (!pluginSlot) return const ro = new ResizeObserver((entries) => { const entry = entries[0] if (!entry) return const target = entry.target as HTMLElement const h = target.clientHeight > 0 ? target.clientHeight : readResizeObserverBlockSize(entry) if (h > 0) setSlotHeightPx(Math.round(h)) }) ro.observe(pluginSlot) onCleanup(() => ro.disconnect()) }) return (
TanStack Pacer
Details
) }