diff --git a/lib/components/SchematicPortHoverTooltip.tsx b/lib/components/SchematicPortHoverTooltip.tsx new file mode 100644 index 0000000..eda726d --- /dev/null +++ b/lib/components/SchematicPortHoverTooltip.tsx @@ -0,0 +1,36 @@ +import React from "react" +import type { HoverLabel } from "../hooks/useSchematicPortHover" +import { zIndexMap } from "../utils/z-index-map" + +export const SchematicPortHoverTooltip = ({ + containerRef, + hoverLabel, +}: { + containerRef: React.RefObject + hoverLabel: HoverLabel | null +}) => { + if (!hoverLabel) return null + const rect = containerRef.current?.getBoundingClientRect() + if (!rect) return null + const left = hoverLabel.x - rect.left + 10 + const top = hoverLabel.y - rect.top + 10 + return ( +
+ {hoverLabel.name} +
+ ) +} diff --git a/lib/components/SchematicViewer.tsx b/lib/components/SchematicViewer.tsx index 1e9a2c8..773aa90 100644 --- a/lib/components/SchematicViewer.tsx +++ b/lib/components/SchematicViewer.tsx @@ -19,6 +19,8 @@ import { EditIcon } from "./EditIcon" import { GridIcon } from "./GridIcon" import type { CircuitJson } from "circuit-json" import { zIndexMap } from "../utils/z-index-map" +import { useSchematicPortHover } from "../hooks/useSchematicPortHover" +import { SchematicPortHoverTooltip } from "./SchematicPortHoverTooltip" interface Props { circuitJson: CircuitJson @@ -129,6 +131,12 @@ export const SchematicViewer = ({ }) }, [circuitJson, containerWidth, containerHeight]) + const { hoverLabel } = useSchematicPortHover({ + svgDivRef, + circuitJson, + svgString, + }) + const containerBackgroundColor = useMemo(() => { const match = svgString.match( /]*style="[^"]*background-color:\s*([^;\"]+)/i, @@ -291,6 +299,10 @@ export const SchematicViewer = ({ /> )} {svgDiv} + ) } diff --git a/lib/hooks/useSchematicPortHover.ts b/lib/hooks/useSchematicPortHover.ts new file mode 100644 index 0000000..f540a98 --- /dev/null +++ b/lib/hooks/useSchematicPortHover.ts @@ -0,0 +1,62 @@ +import { useEffect, useState } from "react" +import { su } from "@tscircuit/soup-util" +import type { CircuitJson } from "circuit-json" + +export interface HoverLabel { + name: string + x: number + y: number +} + +export const useSchematicPortHover = ({ + svgDivRef, + circuitJson, + svgString, +}: { + svgDivRef: React.RefObject + circuitJson: CircuitJson + svgString: string +}) => { + const [hoverLabel, setHoverLabel] = useState(null) + + useEffect(() => { + const svg = svgDivRef.current + if (!svg) return + + const handleEnter = (e: Event) => { + const target = e.currentTarget as SVGGElement + const id = target.getAttribute("data-schematic-port-id") + if (!id) return + const port = su(circuitJson).source_port.get(id as any) + const name = (port as any)?.name || id + const ev = e as MouseEvent + setHoverLabel({ name, x: ev.clientX, y: ev.clientY }) + } + + const handleMove = (e: Event) => { + const ev = e as MouseEvent + setHoverLabel((prev) => + prev ? { ...prev, x: ev.clientX, y: ev.clientY } : prev, + ) + } + + const handleLeave = () => setHoverLabel(null) + + const portEls = svg.querySelectorAll(".schematic-port-hover") + portEls.forEach((el) => { + el.addEventListener("mouseenter", handleEnter) + el.addEventListener("mousemove", handleMove) + el.addEventListener("mouseleave", handleLeave) + }) + + return () => { + portEls.forEach((el) => { + el.removeEventListener("mouseenter", handleEnter) + el.removeEventListener("mousemove", handleMove) + el.removeEventListener("mouseleave", handleLeave) + }) + } + }, [svgString, circuitJson]) + + return { hoverLabel } +} diff --git a/lib/utils/z-index-map.ts b/lib/utils/z-index-map.ts index 891151e..52e01b6 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, + schematicPortHoverLabel: 200, }