From ce24fce06afbe2ef29bd50ec53f25cde0063de1d Mon Sep 17 00:00:00 2001 From: Severin Ibarluzea Date: Wed, 25 Jun 2025 17:12:50 -0700 Subject: [PATCH] feat(viewer): highlight port on hover --- lib/components/SchematicViewer.tsx | 94 ++++++++++++++++++++++++++++++ lib/utils/z-index-map.ts | 1 + 2 files changed, 95 insertions(+) diff --git a/lib/components/SchematicViewer.tsx b/lib/components/SchematicViewer.tsx index 1e9a2c8..4116fe9 100644 --- a/lib/components/SchematicViewer.tsx +++ b/lib/components/SchematicViewer.tsx @@ -56,6 +56,14 @@ export const SchematicViewer = ({ const svgDivRef = useRef(null) const touchStartRef = useRef<{ x: number; y: number } | null>(null) + const [hoveredPortInfo, setHoveredPortInfo] = useState<{ + left: number + top: number + width: number + height: number + label?: string + } | null>(null) + const handleTouchStart = (e: React.TouchEvent) => { const touch = e.touches[0] touchStartRef.current = { @@ -189,6 +197,57 @@ export const SchematicViewer = ({ editEvents: editEventsWithUnappliedEditEvents, }) + useEffect(() => { + const svgEl = svgDivRef.current + if (!svgEl) return + const handleMouseOver = (e: MouseEvent) => { + const target = e.target as HTMLElement + if (!target.classList?.contains("component-pin")) return + const rect = target.getBoundingClientRect() + const containerRect = containerRef.current?.getBoundingClientRect() + if (!containerRect) return + let label = "" + const group = target.closest( + '[data-circuit-json-type="schematic_component"]', + ) as SVGGElement | null + if (group) { + const texts = Array.from(group.querySelectorAll("text")) + const cx = rect.x + rect.width / 2 + const cy = rect.y + rect.height / 2 + let minDist = Infinity + for (const t of texts) { + const r = t.getBoundingClientRect() + const tx = r.x + r.width / 2 + const ty = r.y + r.height / 2 + const d = (tx - cx) ** 2 + (ty - cy) ** 2 + if (d < minDist) { + minDist = d + label = t.textContent || "" + } + } + } + setHoveredPortInfo({ + left: rect.x - containerRect.x, + top: rect.y - containerRect.y, + width: rect.width, + height: rect.height, + label: label.trim() || undefined, + }) + } + const handleMouseOut = (e: MouseEvent) => { + const target = e.target as HTMLElement + if (target.classList?.contains("component-pin")) { + setHoveredPortInfo(null) + } + } + svgEl.addEventListener("mouseover", handleMouseOver) + svgEl.addEventListener("mouseout", handleMouseOut) + return () => { + svgEl.removeEventListener("mouseover", handleMouseOver) + svgEl.removeEventListener("mouseout", handleMouseOut) + } + }, [svgString, isInteractionEnabled]) + const svgDiv = useMemo( () => (
setSnapToGrid(!snapToGrid)} /> )} + {hoveredPortInfo && ( + <> +
+ {hoveredPortInfo.label && ( +
+ {hoveredPortInfo.label} +
+ )} + + )} {svgDiv}
) diff --git a/lib/utils/z-index-map.ts b/lib/utils/z-index-map.ts index 891151e..d810f17 100644 --- a/lib/utils/z-index-map.ts +++ b/lib/utils/z-index-map.ts @@ -2,4 +2,5 @@ export const zIndexMap = { schematicEditIcon: 50, schematicGridIcon: 49, clickToInteractOverlay: 100, + schematicPortHover: 60, }