Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions lib/components/SchematicViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
import { su } from "@tscircuit/soup-util"
import { useChangeSchematicComponentLocationsInSvg } from "lib/hooks/useChangeSchematicComponentLocationsInSvg"
import { useChangeSchematicTracesForMovedComponents } from "lib/hooks/useChangeSchematicTracesForMovedComponents"
import { useTraceHoverHighlight } from "lib/hooks/useTraceHoverHighlight"
import { useSchematicGroupsOverlay } from "lib/hooks/useSchematicGroupsOverlay"
import { enableDebug } from "lib/utils/debug"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
Expand Down Expand Up @@ -338,6 +339,11 @@ export const SchematicViewer = ({
editEvents: editEventsWithUnappliedEditEvents,
})

useTraceHoverHighlight({
svgDivRef,
circuitJson,
})

// Add group overlays when enabled
useSchematicGroupsOverlay({
svgDivRef,
Expand Down
99 changes: 99 additions & 0 deletions lib/hooks/useTraceHoverHighlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useEffect } from "react"
import type { CircuitJson } from "circuit-json"

const HIGHLIGHT_COLOR = "#60a5fa"

/**
* This hook highlights traces on hover and all traces connected to the same net.
* Net grouping is derived from the SVG's data-subcircuit-connectivity-map-key attribute.
*/
export const useTraceHoverHighlight = ({
svgDivRef,
circuitJson,
}: {
svgDivRef: React.RefObject<HTMLDivElement | null>
circuitJson: CircuitJson
}) => {
useEffect(() => {
const svg = svgDivRef.current
if (!svg) return

// Hover state
const originalStrokes = new Map<Element, string>()
let currentNetKey: string | null = null

const clearHighlights = () => {
for (const [el, stroke] of originalStrokes) {
el.setAttribute("stroke", stroke)
}
originalStrokes.clear()
currentNetKey = null
}

const applyHighlights = (netKey: string) => {
// Find all trace groups on the same net
const sameNetTraces = svg.querySelectorAll(
`[data-subcircuit-connectivity-map-key="${netKey}"][data-circuit-json-type="schematic_trace"]`,
)
for (const traceGroup of Array.from(sameNetTraces)) {
const paths = traceGroup.querySelectorAll("path")
for (const path of Array.from(paths)) {
if (path.getAttribute("class")?.includes("invisible")) continue
originalStrokes.set(path, path.getAttribute("stroke") || "")
path.setAttribute("stroke", HIGHLIGHT_COLOR)
}
}
currentNetKey = netKey
}

const handlePointerMove = (e: PointerEvent) => {
const target = e.target as Element
if (!target?.closest) return

const traceGroup = target.closest(
"[data-circuit-json-type='schematic_trace']",
)
if (!traceGroup) {
if (currentNetKey !== null) clearHighlights()
return
}

const netKey = traceGroup.getAttribute(
"data-subcircuit-connectivity-map-key",
)
if (!netKey) {
// No net key — highlight just this single trace
const traceId = traceGroup.getAttribute("data-schematic-trace-id")
if (!traceId || currentNetKey === `single:${traceId}`) return
clearHighlights()
const paths = traceGroup.querySelectorAll("path")
for (const path of Array.from(paths)) {
if (path.getAttribute("class")?.includes("invisible")) continue
originalStrokes.set(path, path.getAttribute("stroke") || "")
path.setAttribute("stroke", HIGHLIGHT_COLOR)
}
currentNetKey = `single:${traceId}`
return
}

// Already highlighting this net
if (netKey === currentNetKey) return

clearHighlights()
applyHighlights(netKey)
}

const handlePointerLeave = () => {
clearHighlights()
}

svg.addEventListener("pointermove", handlePointerMove)
svg.addEventListener("pointerleave", handlePointerLeave)

return () => {
clearHighlights()
svg.removeEventListener("pointermove", handlePointerMove)
svg.removeEventListener("pointerleave", handlePointerLeave)
}
}, [svgDivRef, circuitJson])
}