diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 00000000..df2731f1 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,201 @@ +import * as react from 'react'; +import { ReactNode } from 'react'; +import { ColorOverrides } from 'circuit-to-svg'; +import { BaseManualEditEvent, ManualEditEvent } from '@tscircuit/props'; +import { CircuitJson } from 'circuit-json'; + +interface EditSchematicWireAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_wire_add"; + from_schematic_port_id: string; + to_schematic_port_id: string; + route: Array<{ + x: number; + y: number; + }>; +} +interface EditSchematicBusAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_bus_add"; + route: Array<{ + x: number; + y: number; + }>; +} +interface EditSchematicBusEntryAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_bus_entry_add"; + anchor: { + x: number; + y: number; + }; +} +interface EditSchematicNoConnectAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_no_connect_add"; + center: { + x: number; + y: number; + }; + schematic_port_id?: string; +} +interface EditSchematicNetLabelAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_net_label_add"; + position: { + x: number; + y: number; + }; + net_name: string; + schematic_port_id?: string; + anchor_side?: "left" | "right" | "top" | "bottom"; +} +interface EditSchematicGlobalLabelAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_global_label_add"; + position: { + x: number; + y: number; + }; + net_name: string; + schematic_port_id?: string; + anchor_side?: "left" | "right" | "top" | "bottom"; +} +interface EditSchematicPowerPortAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_power_port_add"; + position: { + x: number; + y: number; + }; + net_name: string; + schematic_port_id?: string; + anchor_side?: "left" | "right" | "top" | "bottom"; +} +interface EditSchematicGroundPortAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_ground_port_add"; + position: { + x: number; + y: number; + }; + net_name: string; + schematic_port_id?: string; + anchor_side?: "left" | "right" | "top" | "bottom"; +} +interface EditSchematicTextNoteAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_text_note_add"; + position: { + x: number; + y: number; + }; + text: string; + anchor?: "left" | "right" | "center"; + font_size?: number; + color?: string; +} +interface EditSchematicHierSheetAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_hier_sheet_add"; + box: { + x: number; + y: number; + width: number; + height: number; + }; + sheet_name: string; + target_sheet_id: string; + sheet_name_pos: { + x: number; + y: number; + }; + file_name_pos: { + x: number; + y: number; + }; +} +type PlacementComponentKind = "resistor" | "capacitor" | "inductor"; +interface EditSchematicComponentAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_component_add"; + position: { + x: number; + y: number; + }; + component_kind: PlacementComponentKind; + rotation?: number; +} + +interface HierSheetTarget { + id: string; + title: string; +} + +interface Props$1 { + circuitJson: CircuitJson; + containerStyle?: React.CSSProperties; + editEvents?: ManualEditEvent[]; + onEditEvent?: (event: ManualEditEvent) => void; + defaultEditMode?: boolean; + debugGrid?: boolean; + editingEnabled?: boolean; + debug?: boolean; + clickToInteractEnabled?: boolean; + colorOverrides?: ColorOverrides; + spiceSimulationEnabled?: boolean; + disableGroups?: boolean; + /** Fade unrelated nets/chips when hovering a wire or net label. Default true. */ + netHoverHighlightEnabled?: boolean; + css?: string; + className?: string; + onSchematicComponentClicked?: (options: { + schematicComponentId: string; + event: MouseEvent; + }) => void; + showSchematicPorts?: boolean; + onSchematicPortClicked?: (options: { + schematicPortId: string; + event: MouseEvent; + }) => void; + /** Called when the active schematic sheet changes (multi-sheet circuits). */ + onSchematicSheetChange?: (schematicSheetId: string) => void; + toolMode?: "select" | "draw_wire" | "draw_bus" | "draw_bus_entry" | "draw_no_connect" | "draw_net_label" | "draw_global_label" | "draw_hier_sheet" | "draw_power_port" | "draw_ground_port" | "draw_text_note" | "draw_trace" | "draw_component"; + onWireAdded?: (event: EditSchematicWireAddEvent) => void; + onBusAdded?: (event: EditSchematicBusAddEvent) => void; + onBusEntryAdded?: (event: EditSchematicBusEntryAddEvent) => void; + onNoConnectAdded?: (event: EditSchematicNoConnectAddEvent) => void; + onNetLabelAdded?: (event: EditSchematicNetLabelAddEvent) => void; + onGlobalLabelAdded?: (event: EditSchematicGlobalLabelAddEvent) => void; + onHierSheetAdded?: (event: EditSchematicHierSheetAddEvent) => void; + onPowerPortAdded?: (event: EditSchematicPowerPortAddEvent) => void; + onGroundPortAdded?: (event: EditSchematicGroundPortAddEvent) => void; + onTextNoteAdded?: (event: EditSchematicTextNoteAddEvent) => void; + onComponentAdded?: (event: EditSchematicComponentAddEvent) => void; + placementComponentKind?: PlacementComponentKind; + hierSheetTargets?: HierSheetTarget[]; + /** Host sheet id excluded from hier-sheet placement targets. */ + activeSheetId?: string; + allowComponentEdit?: boolean; + allowCanvasPan?: boolean; +} +declare const SchematicViewer: ({ circuitJson, containerStyle, editEvents: unappliedEditEvents, onEditEvent, defaultEditMode, debugGrid, editingEnabled, debug, clickToInteractEnabled, colorOverrides, spiceSimulationEnabled, disableGroups, netHoverHighlightEnabled, onSchematicComponentClicked, showSchematicPorts, onSchematicPortClicked, onSchematicSheetChange, css, className, toolMode, onWireAdded, onBusAdded, onBusEntryAdded, onNoConnectAdded, onNetLabelAdded, onGlobalLabelAdded, onHierSheetAdded, onPowerPortAdded, onGroundPortAdded, onTextNoteAdded, onComponentAdded, placementComponentKind, hierSheetTargets, activeSheetId, allowComponentEdit, allowCanvasPan, }: Props$1) => react.JSX.Element; + +interface BoundingBoxBounds { + minX: number; + maxX: number; + minY: number; + maxY: number; +} +declare const MouseTracker: ({ children }: { + children: ReactNode; +}) => react.JSX.Element; + +interface UseMouseEventsOverBoundingBoxOptions { + bounds: BoundingBoxBounds | null; + onClick?: (event: MouseEvent) => void; +} +declare const useMouseEventsOverBoundingBox: (options: UseMouseEventsOverBoundingBoxOptions) => { + hovering: boolean; +}; + +interface Props { + circuitJson: CircuitJson; + containerStyle?: React.CSSProperties; + colorOverrides?: ColorOverrides; + width?: number; + height?: number; + className?: string; +} +declare const AnalogSimulationViewer: ({ circuitJson: inputCircuitJson, containerStyle, colorOverrides, width, height, className, }: Props) => react.JSX.Element; + +export { AnalogSimulationViewer, type EditSchematicBusAddEvent, type EditSchematicBusEntryAddEvent, type EditSchematicComponentAddEvent, type EditSchematicGlobalLabelAddEvent, type EditSchematicGroundPortAddEvent, type EditSchematicHierSheetAddEvent, type EditSchematicNetLabelAddEvent, type EditSchematicNoConnectAddEvent, type EditSchematicPowerPortAddEvent, type EditSchematicTextNoteAddEvent, type EditSchematicWireAddEvent, MouseTracker, type PlacementComponentKind, SchematicViewer, useMouseEventsOverBoundingBox }; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 00000000..7b51b5da --- /dev/null +++ b/dist/index.js @@ -0,0 +1,6792 @@ +// lib/components/SchematicViewer.tsx +import { + convertCircuitJsonToSchematicSvg +} from "circuit-to-svg"; +import { su as su7 } from "@tscircuit/soup-util"; + +// lib/hooks/useChangeSchematicComponentLocationsInSvg.ts +import "@tscircuit/soup-util"; +import "transformation-matrix"; +import { useEffect, useRef } from "react"; + +// lib/utils/get-component-offset-due-to-events.ts +var getComponentOffsetDueToEvents = ({ + editEvents, + schematic_component_id +}) => { + const editEventsForComponent = editEvents.filter( + (event) => "schematic_component_id" in event && event.schematic_component_id === schematic_component_id + ).filter( + (event) => "edit_event_type" in event && event.edit_event_type === "edit_schematic_component_location" + ); + const totalOffsetX = editEventsForComponent.reduce((acc, event) => { + return acc + event.new_center.x - event.original_center.x; + }, 0); + const totalOffsetY = editEventsForComponent.reduce((acc, event) => { + return acc + event.new_center.y - event.original_center.y; + }, 0); + return { + x: totalOffsetX, + y: totalOffsetY + }; +}; + +// lib/hooks/useChangeSchematicComponentLocationsInSvg.ts +var useChangeSchematicComponentLocationsInSvg = ({ + svgDivRef, + realToSvgProjection, + svgToScreenProjection, + activeEditEvent, + editEvents +}) => { + const lastSvgContentRef = useRef(null); + useEffect(() => { + const svg = svgDivRef.current; + if (!svg) return; + const observer = new MutationObserver((mutations) => { + const currentSvgContent = svg.innerHTML; + if (currentSvgContent !== lastSvgContentRef.current) { + lastSvgContentRef.current = currentSvgContent; + applyTransforms(); + } + }); + const applyTransforms = () => { + const componentsThatHaveBeenMoved = /* @__PURE__ */ new Set(); + for (const event of editEvents) { + if ("edit_event_type" in event && event.edit_event_type === "edit_schematic_component_location") { + componentsThatHaveBeenMoved.add(event.schematic_component_id); + } + } + if (activeEditEvent) { + componentsThatHaveBeenMoved.add(activeEditEvent.schematic_component_id); + } + const allComponents = svg.querySelectorAll( + '[data-circuit-json-type="schematic_component"]' + ); + for (const component of Array.from(allComponents)) { + const schematic_component_id = component.getAttribute( + "data-schematic-component-id" + ); + const offsetMm = getComponentOffsetDueToEvents({ + editEvents: [ + ...editEvents, + ...activeEditEvent ? [activeEditEvent] : [] + ], + schematic_component_id + }); + const offsetPx = { + x: offsetMm.x * realToSvgProjection.a, + y: offsetMm.y * realToSvgProjection.d + }; + const style = component.style; + style.transform = `translate(${offsetPx.x}px, ${offsetPx.y}px)`; + if (activeEditEvent?.schematic_component_id === schematic_component_id) { + style.outline = "solid 2px rgba(255,0,0,0.5)"; + style.outlineOffset = "5px"; + } else if (style.outline) { + style.outline = ""; + } + } + }; + observer.observe(svg, { + childList: true, + // Watch for changes to the child elements + subtree: false, + // Watch for changes in the entire subtree + characterData: false + // Watch for changes to text content + }); + applyTransforms(); + return () => { + observer.disconnect(); + }; + }, [svgDivRef, editEvents, activeEditEvent]); +}; + +// lib/hooks/useChangeSchematicTracesForMovedComponents.ts +import { useEffect as useEffect2, useRef as useRef2 } from "react"; +import { su as su2 } from "@tscircuit/soup-util"; +var useChangeSchematicTracesForMovedComponents = ({ + svgDivRef, + circuitJson, + activeEditEvent, + editEvents +}) => { + const lastSvgContentRef = useRef2(null); + useEffect2(() => { + const svg = svgDivRef.current; + if (!svg) return; + const updateTraceStyles = () => { + const allTraces = svg.querySelectorAll( + '[data-circuit-json-type="schematic_trace"] path' + ); + for (const trace of Array.from(allTraces)) { + trace.setAttribute("stroke-dasharray", "0"); + trace.style.animation = ""; + } + for (const editEvent of [ + ...editEvents, + ...activeEditEvent ? [activeEditEvent] : [] + ]) { + if ("schematic_component_id" in editEvent && editEvent.edit_event_type === "edit_schematic_component_location") { + const sch_component = su2(circuitJson).schematic_component.get( + editEvent.schematic_component_id + ); + if (!sch_component) return; + const src_ports = su2(circuitJson).source_port.list({ + source_component_id: sch_component.source_component_id + }); + const src_port_ids = new Set(src_ports.map((sp) => sp.source_port_id)); + const src_traces = su2(circuitJson).source_trace.list().filter( + (st) => st.connected_source_port_ids?.some( + (spi) => src_port_ids.has(spi) + ) + ); + const src_trace_ids = new Set( + src_traces.map((st) => st.source_trace_id) + ); + const schematic_traces = su2(circuitJson).schematic_trace.list().filter((st) => src_trace_ids.has(st.source_trace_id)); + schematic_traces.forEach((trace) => { + const traceElements = svg.querySelectorAll( + `[data-schematic-trace-id="${trace.schematic_trace_id}"] path` + ); + for (const traceElement of Array.from(traceElements)) { + if (traceElement.getAttribute("class")?.includes("invisible")) + continue; + traceElement.setAttribute("stroke-dasharray", "20,20"); + traceElement.style.animation = "dash-animation 350ms linear infinite, pulse-animation 900ms linear infinite"; + if (!svg.querySelector("style#dash-animation")) { + const style = document.createElement("style"); + style.id = "dash-animation"; + style.textContent = ` + @keyframes dash-animation { + to { + stroke-dashoffset: -40; + } + } + @keyframes pulse-animation { + 0% { opacity: 0.6; } + 50% { opacity: 0.2; } + 100% { opacity: 0.6; } + } + `; + svg.appendChild(style); + } + } + }); + } + } + }; + updateTraceStyles(); + const observer = new MutationObserver(updateTraceStyles); + observer.observe(svg, { + childList: true, + // Watch for changes to the child elements + subtree: false, + // Watch for changes in the entire subtree + characterData: false + // Watch for changes to text content + }); + return () => { + observer.disconnect(); + }; + }, [svgDivRef, activeEditEvent, circuitJson, editEvents]); +}; + +// lib/hooks/useSchematicGroupsOverlay.ts +import { useEffect as useEffect3 } from "react"; +import { su as su3 } from "@tscircuit/soup-util"; +var GROUP_COLORS = [ + "#8B0000", + // Dark Red + "#2F4F4F", + // Dark Slate Gray + "#191970", + // Midnight Blue + "#006400", + // Dark Green + "#FF4500", + // Dark Orange + "#800080", + // Purple + "#2E8B57", + // Sea Green + "#B8860B", + // Dark Goldenrod + "#C71585", + // Medium Violet Red + "#008B8B" + // Dark Cyan +]; +var useSchematicGroupsOverlay = (options) => { + const { svgDivRef, circuitJson, circuitJsonKey, showGroups } = options; + useEffect3(() => { + if (svgDivRef.current) { + const existingOverlays = svgDivRef.current.querySelectorAll( + ".schematic-group-overlay" + ); + existingOverlays.forEach((overlay) => overlay.remove()); + } + if (!svgDivRef.current || !showGroups || !circuitJson || circuitJson.length === 0) { + return; + } + const timeoutId = setTimeout(() => { + if (!svgDivRef.current) return; + const svg = svgDivRef.current.querySelector("svg"); + if (!svg) { + return; + } + const existingOverlays = svg.querySelectorAll(".schematic-group-overlay"); + existingOverlays.forEach((overlay) => overlay.remove()); + try { + const sourceGroups = su3(circuitJson).source_group?.list().filter((x) => !!!x.is_subcircuit) || []; + const schematicComponents = su3(circuitJson).schematic_component?.list() || []; + const sourceGroupHierarchy = /* @__PURE__ */ new Map(); + sourceGroups.forEach((group) => { + const groupWithParent = group; + if (groupWithParent.parent_source_group_id) { + const children = sourceGroupHierarchy.get( + groupWithParent.parent_source_group_id + ) || []; + children.push(group.source_group_id); + sourceGroupHierarchy.set( + groupWithParent.parent_source_group_id, + children + ); + } + }); + const getAllDescendantSourceGroups = (sourceGroupId) => { + const descendants = []; + const children = sourceGroupHierarchy.get(sourceGroupId) || []; + for (const child of children) { + descendants.push(child); + descendants.push(...getAllDescendantSourceGroups(child)); + } + return descendants; + }; + const getGroupDepthLevel = (sourceGroupId) => { + const groupWithParent = sourceGroups.find( + (g) => g.source_group_id === sourceGroupId + ); + if (!groupWithParent?.parent_source_group_id) { + return 0; + } + return 1 + getGroupDepthLevel(groupWithParent.parent_source_group_id); + }; + const hasMeaningfulGroups = sourceGroups.length > 0 && sourceGroups.some((group) => group.name && group.name.trim() !== ""); + let groupsToRender = []; + if (hasMeaningfulGroups) { + const groupMap = /* @__PURE__ */ new Map(); + for (const comp of schematicComponents) { + const sourceComp = su3(circuitJson).source_component.get( + comp.source_component_id + ); + if (sourceComp?.source_group_id) { + if (!groupMap.has(sourceComp.source_group_id)) { + groupMap.set(sourceComp.source_group_id, []); + } + groupMap.get(sourceComp.source_group_id).push(comp); + } + } + sourceGroups.forEach((group, index) => { + let groupComponents = groupMap.get(group.source_group_id) || []; + const descendantGroups = getAllDescendantSourceGroups( + group.source_group_id + ); + for (const descendantGroupId of descendantGroups) { + const descendantComponents = groupMap.get(descendantGroupId) || []; + groupComponents = [...groupComponents, ...descendantComponents]; + } + if (groupComponents.length > 0) { + const depthLevel = getGroupDepthLevel(group.source_group_id); + const hasChildren = getAllDescendantSourceGroups(group.source_group_id).length > 0; + if (group.name?.startsWith("unnamed_board")) return; + groupsToRender.push({ + id: group.source_group_id, + name: group.name || `Group ${index + 1}`, + components: groupComponents, + color: GROUP_COLORS[index % GROUP_COLORS.length], + depthLevel, + hasChildren, + sourceGroupId: group.source_group_id + }); + } + }); + } + const viewBox = svg.viewBox.baseVal; + const svgRect = svg.getBoundingClientRect(); + const scale = Math.min( + svgRect.width / viewBox.width, + svgRect.height / viewBox.height + ) || 1; + groupsToRender.sort((a, b) => a.depthLevel - b.depthLevel); + groupsToRender.forEach((group) => { + if (group.components.length === 0) return; + const groupBounds = calculateGroupBounds(group.components, svg); + if (!groupBounds) return; + const basePadding = Math.max( + 8, + Math.min(25, 15 / Math.max(scale, 0.3)) + ); + const hierarchyPadding = group.hasChildren ? basePadding * 0.6 : 0; + const totalPadding = basePadding + hierarchyPadding; + const baseStrokeWidth = Math.max(1, 2 / Math.max(scale, 0.5)); + const strokeWidth = group.depthLevel === 0 ? baseStrokeWidth : baseStrokeWidth * 0.7; + const baseDashSize = Math.max(4, 8 / Math.max(scale, 0.5)); + const dashMultiplier = group.hasChildren ? 1.3 : 1; + const dashSize = baseDashSize * dashMultiplier; + const gapSize = dashSize * 0.5; + const groupOverlay = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + groupOverlay.setAttribute("class", "schematic-group-overlay"); + groupOverlay.setAttribute( + "x", + (groupBounds.minX - totalPadding).toString() + ); + groupOverlay.setAttribute( + "y", + (groupBounds.minY - totalPadding).toString() + ); + groupOverlay.setAttribute( + "width", + (groupBounds.maxX - groupBounds.minX + totalPadding * 2).toString() + ); + groupOverlay.setAttribute( + "height", + (groupBounds.maxY - groupBounds.minY + totalPadding * 2).toString() + ); + groupOverlay.setAttribute("fill", "none"); + groupOverlay.setAttribute("stroke", group.color); + groupOverlay.setAttribute("stroke-width", strokeWidth.toString()); + groupOverlay.setAttribute( + "stroke-dasharray", + `${dashSize},${gapSize}` + ); + groupOverlay.setAttribute("opacity", "0.8"); + groupOverlay.setAttribute("rx", "0"); + groupOverlay.setAttribute("ry", "0"); + const baseFontSize = Math.max( + 6, + Math.min(20, 14 / Math.max(scale, 0.2)) + ); + const fontSizeReduction = group.depthLevel === 0 || group.depthLevel === 1 ? 0 : group.depthLevel * 0.2; + const fontSize = baseFontSize * (1 - fontSizeReduction); + const labelPadding = Math.max(1, fontSize * 0.2); + const labelText = group.name; + const tempText = document.createElementNS( + "http://www.w3.org/2000/svg", + "text" + ); + tempText.setAttribute("font-size", fontSize.toString()); + tempText.setAttribute("font-family", "Arial, sans-serif"); + tempText.textContent = labelText; + svg.appendChild(tempText); + const textBBox = tempText.getBBox(); + svg.removeChild(tempText); + const labelWidth = textBBox.width + labelPadding * 2; + const labelHeight = fontSize + labelPadding * 2; + const labelX = groupBounds.minX - totalPadding; + const labelY = groupBounds.minY - totalPadding - labelHeight; + const labelBg = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + labelBg.setAttribute("class", "schematic-group-overlay"); + labelBg.setAttribute("x", labelX.toString()); + labelBg.setAttribute("y", (labelY - labelHeight).toString()); + labelBg.setAttribute("width", labelWidth.toString()); + labelBg.setAttribute("height", labelHeight.toString()); + labelBg.setAttribute("fill", "transparent"); + labelBg.setAttribute("rx", "0"); + labelBg.setAttribute("ry", "0"); + const groupLabel = document.createElementNS( + "http://www.w3.org/2000/svg", + "text" + ); + groupLabel.setAttribute("class", "schematic-group-overlay"); + groupLabel.setAttribute("x", (labelX + labelPadding).toString()); + groupLabel.setAttribute( + "y", + (labelY + labelHeight - labelPadding).toString() + ); + groupLabel.setAttribute("fill", group.color); + groupLabel.setAttribute("font-size", fontSize.toString()); + groupLabel.setAttribute("font-family", "Arial, sans-serif"); + groupLabel.setAttribute( + "font-weight", + group.depthLevel === 0 ? "600" : "500" + ); + groupLabel.setAttribute("stroke", group.color); + groupLabel.setAttribute( + "stroke-width", + Math.max(0.2, fontSize * 0.02).toString() + ); + groupLabel.textContent = labelText; + svg.appendChild(groupOverlay); + svg.appendChild(labelBg); + svg.appendChild(groupLabel); + }); + } catch (error) { + console.error("Error creating group overlays:", error); + } + }, 10); + return () => clearTimeout(timeoutId); + }, [svgDivRef, circuitJsonKey, showGroups]); +}; +function calculateGroupBounds(components, svg) { + let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; + for (const component of components) { + let componentElement = svg.querySelector( + `g[data-schematic-component-id="${component.schematic_component_id}"]` + ); + if (!componentElement) { + componentElement = svg.querySelector( + `[data-schematic-component-id="${component.schematic_component_id}"]` + ); + } + if (componentElement) { + const bbox = componentElement.getBBox(); + minX = Math.min(minX, bbox.x); + minY = Math.min(minY, bbox.y); + maxX = Math.max(maxX, bbox.x + bbox.width); + maxY = Math.max(maxY, bbox.y + bbox.height); + } + } + if (minX === Infinity) { + return null; + } + const bounds = { minX, minY, maxX, maxY }; + return bounds; +} + +// lib/hooks/useSchematicNetHover.ts +import { su as su4 } from "@tscircuit/soup-util"; +import { useEffect as useEffect4 } from "react"; +var FADED_CLASS = "sch-net-faded"; +var TRACE_SELECTOR = "g.trace[data-subcircuit-connectivity-map-key], g.trace-overlays[data-subcircuit-connectivity-map-key]"; +var NET_LABEL_SELECTOR = "[data-schematic-net-label-id]"; +var useSchematicNetHover = ({ + svgDivRef, + circuitJson, + circuitJsonKey, + enabled +}) => { + useEffect4(() => { + const svgDiv = svgDivRef.current; + if (!enabled || !svgDiv) return; + const { componentIdToKeys, netLabelIdToKey } = buildNetRegistry(circuitJson); + let netElements = []; + const triggerNetKeys = /* @__PURE__ */ new Map(); + let hoveredNetKey = null; + const collectNetElements = () => { + for (const { el } of netElements) el.classList.remove(FADED_CLASS); + netElements = []; + triggerNetKeys.clear(); + hoveredNetKey = null; + const svg = svgDiv.querySelector("svg"); + if (!svg) return; + for (const el of Array.from(svg.querySelectorAll(TRACE_SELECTOR))) { + const key = el.getAttribute("data-subcircuit-connectivity-map-key"); + const keys = /* @__PURE__ */ new Set(); + if (key) { + keys.add(key); + triggerNetKeys.set(el, key); + } + netElements.push({ el, keys }); + } + for (const el of Array.from( + svg.querySelectorAll("g[data-schematic-component-id]") + )) { + const id = el.getAttribute("data-schematic-component-id"); + netElements.push({ el, keys: componentIdToKeys.get(id) ?? /* @__PURE__ */ new Set() }); + } + for (const el of Array.from(svg.querySelectorAll(NET_LABEL_SELECTOR))) { + const key = netLabelIdToKey.get( + el.getAttribute("data-schematic-net-label-id") + ); + const keys = /* @__PURE__ */ new Set(); + if (key) { + keys.add(key); + triggerNetKeys.set(el, key); + } + netElements.push({ el, keys }); + } + }; + const highlightNet = (key) => { + if (key === hoveredNetKey) return; + hoveredNetKey = key; + for (const { el, keys } of netElements) { + el.classList.toggle(FADED_CLASS, key !== null && !keys.has(key)); + } + }; + const handleMouseOver = (e) => { + const target = e.target; + if (!(target instanceof Element)) { + highlightNet(null); + return; + } + const trigger = target.closest(`${TRACE_SELECTOR}, ${NET_LABEL_SELECTOR}`); + if (!trigger) { + highlightNet(null); + return; + } + highlightNet(triggerNetKeys.get(trigger) ?? null); + }; + const handleMouseLeave = () => highlightNet(null); + collectNetElements(); + svgDiv.addEventListener("mouseover", handleMouseOver); + svgDiv.addEventListener("mouseleave", handleMouseLeave); + const observer = new MutationObserver(collectNetElements); + observer.observe(svgDiv, { childList: true }); + return () => { + observer.disconnect(); + svgDiv.removeEventListener("mouseover", handleMouseOver); + svgDiv.removeEventListener("mouseleave", handleMouseLeave); + for (const { el } of netElements) el.classList.remove(FADED_CLASS); + }; + }, [svgDivRef, circuitJsonKey, enabled]); +}; +function buildNetRegistry(circuitJson) { + const cju = su4(circuitJson); + const srcCompToSchComp = /* @__PURE__ */ new Map(); + for (const c of cju.schematic_component.list()) { + if (c.source_component_id) { + srcCompToSchComp.set(c.source_component_id, c.schematic_component_id); + } + } + const componentIdToKeys = /* @__PURE__ */ new Map(); + for (const sourceTrace of cju.source_trace.list()) { + const key = sourceTrace.subcircuit_connectivity_map_key; + if (!key) continue; + for (const portId of sourceTrace.connected_source_port_ids ?? []) { + const schCompId = srcCompToSchComp.get( + cju.source_port.get(portId)?.source_component_id ?? "" + ); + if (!schCompId) continue; + if (!componentIdToKeys.has(schCompId)) { + componentIdToKeys.set(schCompId, /* @__PURE__ */ new Set()); + } + componentIdToKeys.get(schCompId).add(key); + } + } + const netLabelIdToKey = /* @__PURE__ */ new Map(); + for (const label of cju.schematic_net_label.list()) { + if (!label.source_net_id) continue; + const key = cju.source_net.get(label.source_net_id)?.subcircuit_connectivity_map_key ?? label.source_net_id; + netLabelIdToKey.set(label.schematic_net_label_id, key); + } + return { componentIdToKeys, netLabelIdToKey }; +} + +// lib/utils/debug.ts +import Debug from "debug"; +var debug = Debug("schematic-viewer"); +var enableDebug = () => { + Debug.enable("schematic-viewer*"); +}; +var debug_default = debug; + +// lib/components/SchematicViewer.tsx +import { useCallback as useCallback18, useEffect as useEffect31, useMemo as useMemo6, useRef as useRef24, useState as useState21 } from "react"; +import { + fromString, + identity, + toString as transformToString +} from "transformation-matrix"; +import { useMouseMatrixTransform } from "use-mouse-matrix-transform"; + +// lib/hooks/use-resize-handling.ts +import { useEffect as useEffect5, useState } from "react"; +var useResizeHandling = (containerRef) => { + const [containerWidth, setContainerWidth] = useState(0); + const [containerHeight, setContainerHeight] = useState(0); + useEffect5(() => { + if (!containerRef.current) return; + const updateDimensions = () => { + const rect = containerRef.current?.getBoundingClientRect(); + setContainerWidth(rect?.width || 0); + setContainerHeight(rect?.height || 0); + }; + updateDimensions(); + const resizeObserver = new ResizeObserver(updateDimensions); + resizeObserver.observe(containerRef.current); + window.addEventListener("resize", updateDimensions); + return () => { + resizeObserver.disconnect(); + window.removeEventListener("resize", updateDimensions); + }; + }, []); + return { containerWidth, containerHeight }; +}; + +// lib/hooks/useComponentDragging.ts +import { su as su5 } from "@tscircuit/soup-util"; +import { useCallback, useEffect as useEffect6, useRef as useRef3, useState as useState2 } from "react"; +import { compose as compose2 } from "transformation-matrix"; +var debug2 = debug_default.extend("useComponentDragging"); +var useComponentDragging = ({ + onEditEvent, + editEvents = [], + circuitJson, + cancelDrag, + svgToScreenProjection, + realToSvgProjection, + enabled = false, + snapToGrid = false +}) => { + const [activeEditEvent, setActiveEditEvent] = useState2(null); + const realToScreenProjection = compose2( + realToSvgProjection, + svgToScreenProjection + ); + const dragStartPosRef = useRef3(null); + const activeEditEventRef = useRef3(null); + const componentPositionsRef = useRef3( + /* @__PURE__ */ new Map() + ); + useEffect6(() => { + editEvents.forEach((event) => { + if ("edit_event_type" in event && event.edit_event_type === "edit_schematic_component_location" && !event.in_progress) { + componentPositionsRef.current.set(event.schematic_component_id, { + ...event.new_center + }); + } + }); + }, [editEvents]); + const startDrag = useCallback( + (clientX, clientY, target) => { + if (!enabled) return false; + const componentGroup = target.closest( + '[data-circuit-json-type="schematic_component"]' + ); + if (!componentGroup) return false; + const schematic_component_id = componentGroup.getAttribute( + "data-schematic-component-id" + ); + if (!schematic_component_id) return false; + if (cancelDrag) cancelDrag(); + const schematic_component = su5(circuitJson).schematic_component.get( + schematic_component_id + ); + if (!schematic_component) return false; + dragStartPosRef.current = { x: clientX, y: clientY }; + let current_position; + const trackedPosition = componentPositionsRef.current.get( + schematic_component_id + ); + if (trackedPosition) { + current_position = { ...trackedPosition }; + } else { + const editEventOffset = getComponentOffsetDueToEvents({ + editEvents, + schematic_component_id + }); + current_position = { + x: schematic_component.center.x + editEventOffset.x, + y: schematic_component.center.y + editEventOffset.y + }; + componentPositionsRef.current.set(schematic_component_id, { + ...current_position + }); + } + const newEditEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_component_location", + schematic_component_id, + original_center: current_position, + new_center: { ...current_position }, + in_progress: true, + created_at: Date.now(), + _element: componentGroup + }; + activeEditEventRef.current = newEditEvent; + setActiveEditEvent(newEditEvent); + return true; + }, + [cancelDrag, enabled, circuitJson, editEvents] + ); + const handleMouseDown = useCallback( + (e) => { + startDrag(e.clientX, e.clientY, e.target); + }, + [startDrag] + ); + const handleTouchStart = useCallback( + (e) => { + if (e.touches.length !== 1) return; + const touch = e.touches[0]; + if (startDrag(touch.clientX, touch.clientY, e.target)) { + e.preventDefault(); + } + }, + [startDrag] + ); + const updateDragPosition = useCallback( + (clientX, clientY) => { + if (!activeEditEventRef.current || !dragStartPosRef.current) return; + const screenDelta = { + x: clientX - dragStartPosRef.current.x, + y: clientY - dragStartPosRef.current.y + }; + const mmDelta = { + x: screenDelta.x / realToScreenProjection.a, + y: screenDelta.y / realToScreenProjection.d + }; + let newCenter = { + x: activeEditEventRef.current.original_center.x + mmDelta.x, + y: activeEditEventRef.current.original_center.y + mmDelta.y + }; + if (snapToGrid) { + const snap = (v) => Math.round(v * 10) / 10; + newCenter = { x: snap(newCenter.x), y: snap(newCenter.y) }; + } + const newEditEvent = { + ...activeEditEventRef.current, + new_center: newCenter + }; + activeEditEventRef.current = newEditEvent; + setActiveEditEvent(newEditEvent); + }, + [realToScreenProjection, snapToGrid] + ); + const handleMouseMove = useCallback( + (e) => updateDragPosition(e.clientX, e.clientY), + [updateDragPosition] + ); + const handleTouchMove = useCallback( + (e) => { + if (e.touches.length !== 1 || !activeEditEventRef.current) return; + e.preventDefault(); + const touch = e.touches[0]; + updateDragPosition(touch.clientX, touch.clientY); + }, + [updateDragPosition] + ); + const endDrag = useCallback(() => { + if (!activeEditEventRef.current) return; + const finalEvent = { + ...activeEditEventRef.current, + in_progress: false + }; + componentPositionsRef.current.set(finalEvent.schematic_component_id, { + ...finalEvent.new_center + }); + debug2("endDrag calling onEditEvent with new edit event", { + newEditEvent: finalEvent + }); + if (onEditEvent) onEditEvent(finalEvent); + activeEditEventRef.current = null; + dragStartPosRef.current = null; + setActiveEditEvent(null); + }, [onEditEvent]); + const handleMouseUp = useCallback(() => endDrag(), [endDrag]); + const handleTouchEnd = useCallback(() => endDrag(), [endDrag]); + useEffect6(() => { + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mouseup", handleMouseUp); + window.addEventListener("touchmove", handleTouchMove, { passive: false }); + window.addEventListener("touchend", handleTouchEnd); + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseup", handleMouseUp); + window.removeEventListener("touchmove", handleTouchMove); + window.removeEventListener("touchend", handleTouchEnd); + }; + }, [handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]); + return { + handleMouseDown, + handleTouchStart, + isDragging: !!activeEditEventRef.current, + activeEditEvent + }; +}; + +// lib/utils/z-index-map.ts +var zIndexMap = { + schematicEditIcon: 50, + schematicGridIcon: 49, + spiceSimulationIcon: 50, + viewMenuIcon: 48, + viewMenu: 55, + viewMenuBackdrop: 54, + clickToInteractOverlay: 100, + schematicComponentHoverOutline: 47, + schematicPortHoverOutline: 48 +}; + +// lib/components/EditIcon.tsx +import { jsx, jsxs } from "react/jsx-runtime"; +var EditIcon = ({ + onClick, + active +}) => { + const handleInteraction = (e) => { + e.preventDefault(); + onClick(); + }; + return /* @__PURE__ */ jsx( + "div", + { + onClick: handleInteraction, + onTouchEnd: handleInteraction, + title: active ? "Disable edit mode" : "Enable edit mode", + style: { + position: "absolute", + top: "16px", + right: "64px", + backgroundColor: active ? "#4CAF50" : "#fff", + color: active ? "#fff" : "#000", + padding: "8px", + borderRadius: "4px", + cursor: "pointer", + boxShadow: "0 2px 4px rgba(0,0,0,0.1)", + display: "flex", + alignItems: "center", + gap: "4px", + zIndex: zIndexMap.schematicEditIcon + }, + children: /* @__PURE__ */ jsxs( + "svg", + { + width: "16", + height: "16", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2", + children: [ + /* @__PURE__ */ jsx("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }), + /* @__PURE__ */ jsx("path", { d: "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" }) + ] + } + ) + } + ); +}; + +// lib/components/GridIcon.tsx +import { jsx as jsx2 } from "react/jsx-runtime"; +var GridIcon = ({ + onClick, + active +}) => { + const handleInteraction = (e) => { + e.preventDefault(); + onClick(); + }; + return /* @__PURE__ */ jsx2( + "div", + { + onClick: handleInteraction, + onTouchEnd: handleInteraction, + title: active ? "Hide grid" : "Show grid", + style: { + position: "absolute", + top: "56px", + right: "64px", + backgroundColor: active ? "#4CAF50" : "#fff", + color: active ? "#fff" : "#000", + padding: "8px", + borderRadius: "4px", + cursor: "pointer", + boxShadow: "0 2px 4px rgba(0,0,0,0.1)", + display: "flex", + alignItems: "center", + gap: "4px", + zIndex: zIndexMap.schematicGridIcon + }, + children: /* @__PURE__ */ jsx2( + "svg", + { + width: "16", + height: "16", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2", + children: /* @__PURE__ */ jsx2("path", { d: "M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3zM14 14h7v7h-7z" }) + } + ) + } + ); +}; + +// lib/components/ViewMenu.tsx +import { useMemo } from "react"; +import { su as su6 } from "@tscircuit/soup-util"; +import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; + +// package.json +var package_default = { + name: "@tscircuit/schematic-viewer", + version: "2.0.69", + main: "dist/index.js", + type: "module", + scripts: { + start: "cosmos", + "build:webworker": "tsup --config tsup-webworker.config.ts", + "build:blob-url": "bun scripts/build-worker-blob-url.ts", + build: "bun run build:webworker && bun run build:blob-url && tsup-node ./lib/index.ts --dts --format esm --sourcemap", + "build:site": "cosmos-export", + "vercel-build": "bun run build:webworker && bun run build:blob-url && bun run build:site", + format: "biome format --write .", + "format:check": "biome format ." + }, + files: [ + "dist" + ], + devDependencies: { + "@biomejs/biome": "^1.9.4", + "@types/bun": "latest", + "@types/debug": "^4.1.12", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", + "@types/recharts": "^2.0.1", + "@vitejs/plugin-react": "^4.3.4", + react: "^19.1.0", + "react-cosmos": "^6.2.1", + "react-cosmos-plugin-vite": "^6.2.0", + "react-dom": "^19.1.0", + "react-reconciler": "^0.31.0", + semver: "^7.7.2", + tscircuit: "^0.0.2012", + tsup: "^8.3.5", + vite: "^6.0.3" + }, + peerDependencies: { + typescript: "^5.0.0", + tscircuit: "*", + "circuit-json-to-spice": "*" + }, + dependencies: { + "@radix-ui/react-dropdown-menu": "^2.1.16", + "chart.js": "^4.5.0", + debug: "^4.4.0", + "performance-now": "^2.1.0", + "react-chartjs-2": "^5.3.0", + "use-mouse-matrix-transform": "^1.2.2" + } +}; + +// lib/components/ViewMenuIcon.tsx +import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime"; +var ViewMenuIcon = ({ + active = false, + ...props +}) => { + return /* @__PURE__ */ jsx3( + "button", + { + type: "button", + title: active ? "Hide view menu" : "Show view menu", + ...props, + style: { + position: "absolute", + top: "16px", + right: "16px", + backgroundColor: active ? "#4CAF50" : "#fff", + color: active ? "#fff" : "#000", + padding: "8px", + border: "none", + borderRadius: "4px", + cursor: "pointer", + outline: "none", + boxShadow: "0 2px 4px rgba(0,0,0,0.1)", + display: "flex", + alignItems: "center", + gap: "4px", + zIndex: zIndexMap.viewMenuIcon + }, + children: /* @__PURE__ */ jsxs2( + "svg", + { + width: "16", + height: "16", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2", + children: [ + /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "1" }), + /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "5", r: "1" }), + /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "19", r: "1" }) + ] + } + ) + } + ); +}; + +// lib/components/ViewMenu.tsx +import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime"; +var FONT_FAMILY = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'; +var contentStyles = { + backgroundColor: "#ffffff", + color: "#111111", + borderRadius: 8, + boxShadow: "0 6px 24px rgba(0,0,0,0.12), 0 1px 3px rgba(0,0,0,0.08)", + border: "1px solid #e5e7eb", + padding: 4, + minWidth: 224, + fontSize: 13, + fontFamily: FONT_FAMILY, + outline: "none", + zIndex: zIndexMap.viewMenu +}; +var itemStyles = { + display: "flex", + alignItems: "center", + gap: 8, + padding: "7px 10px 7px 8px", + borderRadius: 6, + cursor: "pointer", + outline: "none", + userSelect: "none", + color: "#111111", + fontSize: 13, + fontFamily: FONT_FAMILY +}; +var iconSlotStyles = { + width: 16, + height: 16, + flexShrink: 0, + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + color: "#111111" +}; +var separatorStyles = { + height: 1, + backgroundColor: "#ececec", + margin: "4px 0" +}; +var HIGHLIGHT_CSS = ` +.sv-vm-item[data-highlighted]:not([data-disabled]), +.sv-vm-item:hover:not([data-disabled]) { background-color: #f1f3f5; } +.sv-vm-item[data-disabled] { opacity: 0.45; cursor: not-allowed; } +`; +var CheckIcon = () => /* @__PURE__ */ jsx4( + "svg", + { + width: "14", + height: "14", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2.5", + strokeLinecap: "round", + strokeLinejoin: "round", + "aria-hidden": "true", + children: /* @__PURE__ */ jsx4("path", { d: "M20 6 9 17l-5-5" }) + } +); +var ViewMenu = ({ + circuitJson, + circuitJsonKey, + open, + onOpenChange, + showGroups, + onToggleGroups, + showGrid, + onToggleGrid +}) => { + const hasGroups = useMemo(() => { + if (!circuitJson || circuitJson.length === 0) return false; + try { + const sourceGroups = su6(circuitJson).source_group?.list() || []; + if (sourceGroups.length > 0) return true; + const schematicComponents = su6(circuitJson).schematic_component?.list() || []; + if (schematicComponents.length > 1) { + const componentTypes = /* @__PURE__ */ new Set(); + for (const comp of schematicComponents) { + const sourceComp = su6(circuitJson).source_component.get( + comp.source_component_id + ); + if (sourceComp?.ftype) { + componentTypes.add(sourceComp.ftype); + } + } + return componentTypes.size > 1; + } + return false; + } catch (error) { + console.error("Error checking for groups:", error); + return false; + } + }, [circuitJsonKey]); + return /* @__PURE__ */ jsxs3(DropdownMenu.Root, { open, onOpenChange, modal: false, children: [ + /* @__PURE__ */ jsx4(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsx4(ViewMenuIcon, { active: open }) }), + /* @__PURE__ */ jsx4(DropdownMenu.Portal, { children: /* @__PURE__ */ jsxs3( + DropdownMenu.Content, + { + style: contentStyles, + side: "bottom", + align: "end", + sideOffset: 8, + collisionPadding: 10, + children: [ + /* @__PURE__ */ jsx4("style", { children: HIGHLIGHT_CSS }), + /* @__PURE__ */ jsxs3( + DropdownMenu.Item, + { + className: "sv-vm-item", + style: itemStyles, + disabled: !hasGroups, + title: hasGroups ? void 0 : "No groups found in this schematic", + onSelect: (e) => e.preventDefault(), + onPointerUp: () => { + if (hasGroups) onToggleGroups(!showGroups); + }, + children: [ + /* @__PURE__ */ jsx4("span", { style: iconSlotStyles, children: showGroups && /* @__PURE__ */ jsx4(CheckIcon, {}) }), + /* @__PURE__ */ jsx4("span", { children: "View Schematic Groups" }) + ] + } + ), + /* @__PURE__ */ jsxs3( + DropdownMenu.Item, + { + className: "sv-vm-item", + style: itemStyles, + onSelect: (e) => e.preventDefault(), + onPointerUp: () => onToggleGrid(!showGrid), + children: [ + /* @__PURE__ */ jsx4("span", { style: iconSlotStyles, children: showGrid && /* @__PURE__ */ jsx4(CheckIcon, {}) }), + /* @__PURE__ */ jsx4("span", { children: "Show Grid" }) + ] + } + ), + /* @__PURE__ */ jsx4(DropdownMenu.Separator, { style: separatorStyles }), + /* @__PURE__ */ jsxs3( + "div", + { + style: { + padding: "4px 8px", + fontSize: 12, + color: "#9ca3af", + textAlign: "center", + fontFamily: FONT_FAMILY + }, + children: [ + "v", + String(package_default?.version) + ] + } + ) + ] + } + ) }) + ] }); +}; + +// lib/components/SpiceIcon.tsx +import { jsx as jsx5 } from "react/jsx-runtime"; +var SpiceIcon = () => /* @__PURE__ */ jsx5( + "svg", + { + width: "16", + height: "16", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2", + strokeLinecap: "round", + strokeLinejoin: "round", + children: /* @__PURE__ */ jsx5("path", { d: "M3 12h2.5l2.5-9 4 18 4-9h5.5" }) + } +); + +// lib/components/SpiceSimulationIcon.tsx +import { jsx as jsx6 } from "react/jsx-runtime"; +var SpiceSimulationIcon = ({ + onClick +}) => { + return /* @__PURE__ */ jsx6( + "div", + { + onClick, + title: "Run SPICE simulation", + style: { + position: "absolute", + top: "16px", + right: "112px", + backgroundColor: "#fff", + color: "#000", + padding: "8px", + borderRadius: "4px", + cursor: "pointer", + boxShadow: "0 2px 4px rgba(0,0,0,0.1)", + display: "flex", + alignItems: "center", + gap: "4px", + zIndex: zIndexMap.spiceSimulationIcon + }, + children: /* @__PURE__ */ jsx6(SpiceIcon, {}) + } + ); +}; + +// lib/components/SpicePlot.tsx +import { useMemo as useMemo2 } from "react"; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend +} from "chart.js"; +import { Line } from "react-chartjs-2"; +import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime"; +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend +); +var colors = ["#8884d8", "#82ca9d", "#ffc658", "#ff7300", "#387908"]; +var formatTimeWithUnits = (seconds) => { + if (seconds === 0) return "0s"; + const absSeconds = Math.abs(seconds); + let unit = "s"; + let scale = 1; + if (absSeconds < 1e-12) { + unit = "fs"; + scale = 1e15; + } else if (absSeconds < 1e-9) { + unit = "ps"; + scale = 1e12; + } else if (absSeconds < 1e-6) { + unit = "ns"; + scale = 1e9; + } else if (absSeconds < 1e-3) { + unit = "us"; + scale = 1e6; + } else if (absSeconds < 1) { + unit = "ms"; + scale = 1e3; + } + return `${parseFloat((seconds * scale).toPrecision(3))}${unit}`; +}; +var SpicePlot = ({ + plotData, + nodes, + isLoading, + error, + hasRun +}) => { + const yAxisLabel = useMemo2(() => { + const hasVoltage = nodes.some((n) => n.toLowerCase().startsWith("v(")); + const hasCurrent = nodes.some((n) => n.toLowerCase().startsWith("i(")); + if (hasVoltage && hasCurrent) return "Value"; + if (hasVoltage) return "Voltage (V)"; + if (hasCurrent) return "Current (A)"; + return "Value"; + }, [nodes]); + if (isLoading) { + return /* @__PURE__ */ jsx7( + "div", + { + style: { + height: "300px", + width: "100%", + display: "flex", + alignItems: "center", + justifyContent: "center" + }, + children: "Running simulation..." + } + ); + } + if (!hasRun) { + return /* @__PURE__ */ jsx7( + "div", + { + style: { + height: "300px", + width: "100%", + display: "flex", + alignItems: "center", + justifyContent: "center" + }, + children: 'Click "Run" to start the simulation.' + } + ); + } + if (error) { + return /* @__PURE__ */ jsxs4( + "div", + { + style: { + height: "300px", + width: "100%", + display: "flex", + alignItems: "center", + justifyContent: "center", + color: "red" + }, + children: [ + "Error: ", + error + ] + } + ); + } + if (plotData.length === 0) { + return /* @__PURE__ */ jsx7( + "div", + { + style: { + height: "300px", + width: "100%", + display: "flex", + alignItems: "center", + justifyContent: "center" + }, + children: "No data to plot. Check simulation output or SPICE netlist." + } + ); + } + const chartData = { + datasets: nodes.map((node, i) => ({ + label: node, + data: plotData.map((p) => ({ + x: Number(p.name), + y: p[node] + })), + borderColor: colors[i % colors.length], + backgroundColor: colors[i % colors.length], + fill: false, + tension: 0.1 + })) + }; + const options = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: "top", + labels: { + font: { + family: "sans-serif" + } + } + }, + title: { + display: false + }, + tooltip: { + callbacks: { + title: (tooltipItems) => { + if (tooltipItems.length > 0) { + const item = tooltipItems[0]; + return formatTimeWithUnits(item.parsed.x); + } + return ""; + } + } + } + }, + scales: { + x: { + type: "linear", + title: { + display: true, + text: "Time", + font: { + family: "sans-serif" + } + }, + ticks: { + callback: (value) => formatTimeWithUnits(value), + font: { + family: "sans-serif" + } + } + }, + y: { + title: { + display: true, + text: yAxisLabel, + font: { + family: "sans-serif" + } + }, + ticks: { + font: { + family: "sans-serif" + } + } + } + } + }; + return /* @__PURE__ */ jsx7("div", { style: { position: "relative", height: "300px", width: "100%" }, children: /* @__PURE__ */ jsx7(Line, { options, data: chartData }) }); +}; + +// lib/components/SpiceSimulationOverlay.tsx +import { useEffect as useEffect7, useState as useState3 } from "react"; +import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime"; +var SpiceSimulationOverlay = ({ + spiceString, + onClose, + plotData, + nodes, + isLoading, + error, + simOptions, + onSimOptionsChange, + hasRun +}) => { + const [startTimeDraft, setStartTimeDraft] = useState3( + String(simOptions.startTime) + ); + const [durationDraft, setDurationDraft] = useState3( + String(simOptions.duration) + ); + useEffect7(() => { + setStartTimeDraft(String(simOptions.startTime)); + setDurationDraft(String(simOptions.duration)); + }, [simOptions.startTime, simOptions.duration]); + const handleRerun = () => { + onSimOptionsChange({ + ...simOptions, + startTime: Number(startTimeDraft), + duration: Number(durationDraft) + }); + }; + const filteredNodes = nodes.filter((node) => { + const isVoltage = node.toLowerCase().startsWith("v("); + const isCurrent = node.toLowerCase().startsWith("i("); + if (simOptions.showVoltage && isVoltage) return true; + if (simOptions.showCurrent && isCurrent) return true; + return false; + }); + return /* @__PURE__ */ jsx8( + "div", + { + style: { + position: "fixed", + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: "rgba(0, 0, 0, 0.5)", + display: "flex", + alignItems: "center", + justifyContent: "center", + zIndex: 1002, + fontFamily: "sans-serif" + }, + children: /* @__PURE__ */ jsxs5( + "div", + { + style: { + backgroundColor: "white", + padding: "24px", + borderRadius: "12px", + width: "90%", + maxWidth: "900px", + boxShadow: "0 4px 20px rgba(0, 0, 0, 0.15)" + }, + children: [ + /* @__PURE__ */ jsxs5( + "div", + { + style: { + display: "flex", + justifyContent: "space-between", + alignItems: "center", + marginBottom: "24px", + borderBottom: "1px solid #eee", + paddingBottom: "16px" + }, + children: [ + /* @__PURE__ */ jsx8( + "h2", + { + style: { + margin: 0, + fontSize: "22px", + fontWeight: 600, + color: "#333" + }, + children: "SPICE Simulation" + } + ), + /* @__PURE__ */ jsx8( + "button", + { + onClick: onClose, + style: { + background: "none", + border: "none", + fontSize: "28px", + cursor: "pointer", + color: "#888", + padding: 0, + lineHeight: 1 + }, + children: "\xD7" + } + ) + ] + } + ), + /* @__PURE__ */ jsx8("div", { children: /* @__PURE__ */ jsx8( + SpicePlot, + { + plotData, + nodes: filteredNodes, + isLoading, + error, + hasRun + } + ) }), + /* @__PURE__ */ jsxs5( + "div", + { + style: { + marginTop: "16px", + padding: "12px", + backgroundColor: "#f7f7f7", + borderRadius: "6px", + display: "flex", + flexWrap: "wrap", + gap: "24px", + alignItems: "center", + fontSize: "14px" + }, + children: [ + /* @__PURE__ */ jsxs5("div", { style: { display: "flex", gap: "16px" }, children: [ + /* @__PURE__ */ jsxs5( + "label", + { + style: { display: "flex", alignItems: "center", gap: "6px" }, + children: [ + /* @__PURE__ */ jsx8( + "input", + { + type: "checkbox", + checked: simOptions.showVoltage, + onChange: (e) => onSimOptionsChange({ + ...simOptions, + showVoltage: e.target.checked + }) + } + ), + "Voltage" + ] + } + ), + /* @__PURE__ */ jsxs5( + "label", + { + style: { display: "flex", alignItems: "center", gap: "6px" }, + children: [ + /* @__PURE__ */ jsx8( + "input", + { + type: "checkbox", + checked: simOptions.showCurrent, + onChange: (e) => onSimOptionsChange({ + ...simOptions, + showCurrent: e.target.checked + }) + } + ), + "Current" + ] + } + ) + ] }), + /* @__PURE__ */ jsxs5("div", { style: { display: "flex", gap: "16px", alignItems: "center" }, children: [ + /* @__PURE__ */ jsx8("label", { htmlFor: "startTime", children: "Start Time (ms):" }), + /* @__PURE__ */ jsx8( + "input", + { + id: "startTime", + type: "number", + value: startTimeDraft, + onChange: (e) => setStartTimeDraft(e.target.value), + style: { + width: "80px", + padding: "4px 8px", + borderRadius: "4px", + border: "1px solid #ccc" + } + } + ), + /* @__PURE__ */ jsx8("label", { htmlFor: "duration", children: "Duration (ms):" }), + /* @__PURE__ */ jsx8( + "input", + { + id: "duration", + type: "number", + value: durationDraft, + onChange: (e) => setDurationDraft(e.target.value), + style: { + width: "80px", + padding: "4px 8px", + borderRadius: "4px", + border: "1px solid #ccc" + } + } + ), + /* @__PURE__ */ jsx8( + "button", + { + onClick: handleRerun, + style: { + padding: "4px 12px", + borderRadius: "4px", + border: "1px solid #ccc", + backgroundColor: "#f0f0f0", + cursor: "pointer" + }, + children: hasRun ? "Rerun" : "Run" + } + ) + ] }) + ] + } + ), + /* @__PURE__ */ jsxs5("div", { style: { marginTop: "24px" }, children: [ + /* @__PURE__ */ jsx8( + "h3", + { + style: { + marginTop: 0, + marginBottom: "12px", + fontSize: "18px", + fontWeight: 600, + color: "#333" + }, + children: "SPICE Netlist" + } + ), + /* @__PURE__ */ jsx8( + "pre", + { + style: { + backgroundColor: "#fafafa", + padding: "16px", + borderRadius: "6px", + maxHeight: "150px", + overflowY: "auto", + border: "1px solid #eee", + color: "#333", + fontSize: "13px", + fontFamily: "monospace" + }, + children: spiceString + } + ) + ] }) + ] + } + ) + } + ); +}; + +// lib/hooks/useSpiceSimulation.ts +import { useState as useState4, useEffect as useEffect8 } from "react"; + +// lib/workers/spice-simulation.worker.blob.js +var b64 = "dmFyIGU9bnVsbCxzPWFzeW5jKCk9Pihhd2FpdCBpbXBvcnQoImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vZWVjaXJjdWl0LWVuZ2luZUAxLjUuMi8rZXNtIikpLlNpbXVsYXRpb24sYz1hc3luYygpPT57aWYoZSYmZS5pc0luaXRpYWxpemVkKCkpcmV0dXJuO2xldCBpPWF3YWl0IHMoKTtlPW5ldyBpLGF3YWl0IGUuc3RhcnQoKX07c2VsZi5vbm1lc3NhZ2U9YXN5bmMgaT0+e3RyeXtpZihhd2FpdCBjKCksIWUpdGhyb3cgbmV3IEVycm9yKCJTaW11bGF0aW9uIG5vdCBpbml0aWFsaXplZCIpO2xldCB0PWkuZGF0YS5zcGljZVN0cmluZyxhPXQubWF0Y2goL3dyZGF0YVxzKyhcUyspXHMrKC4qKS9pKTtpZihhKXtsZXQgbz1gLnByb2JlICR7YVsyXS50cmltKCkuc3BsaXQoL1xzKy8pLmpvaW4oIiAiKX1gO3Q9dC5yZXBsYWNlKC93cmRhdGEuKi9pLG8pfWVsc2UgaWYoIXQubWF0Y2goL1wucHJvYmUvaSkpdGhyb3cgdC5tYXRjaCgvcGxvdFxzKyguKikvaSk/bmV3IEVycm9yKCJUaGUgJ3Bsb3QnIGNvbW1hbmQgaXMgbm90IHN1cHBvcnRlZCBmb3IgZGF0YSBleHRyYWN0aW9uLiBQbGVhc2UgdXNlICd3cmRhdGEgPGZpbGVuYW1lPiA8dmFyMT4gLi4uJyBvciAnLnByb2JlIDx2YXIxPiAuLi4nIGluc3RlYWQuIik6bmV3IEVycm9yKCJObyAnLnByb2JlJyBvciAnd3JkYXRhJyBjb21tYW5kIGZvdW5kIGluIFNQSUNFIGZpbGUuIFVzZSAnd3JkYXRhIDxmaWxlbmFtZT4gPHZhcjE+IC4uLicgdG8gc3BlY2lmeSBvdXRwdXQuIik7ZS5zZXROZXRMaXN0KHQpO2xldCBuPWF3YWl0IGUucnVuU2ltKCk7c2VsZi5wb3N0TWVzc2FnZSh7dHlwZToicmVzdWx0IixyZXN1bHQ6bn0pfWNhdGNoKHQpe3NlbGYucG9zdE1lc3NhZ2Uoe3R5cGU6ImVycm9yIixlcnJvcjp0Lm1lc3NhZ2V9KX19Owo="; +var blobUrl = null; +var getSpiceSimulationWorkerBlobUrl = () => { + if (typeof window === "undefined") return null; + if (blobUrl) return blobUrl; + try { + const blob = new Blob([atob(b64)], { type: "application/javascript" }); + blobUrl = URL.createObjectURL(blob); + return blobUrl; + } catch (e) { + console.error("Failed to create blob URL for worker", e); + return null; + } +}; + +// lib/hooks/useSpiceSimulation.ts +var parseEecEngineOutput = (result) => { + const columnData = {}; + if (result.dataType === "real") { + result.data.forEach((col) => { + columnData[col.name] = col.values; + }); + } else if (result.dataType === "complex") { + result.data.forEach((col) => { + columnData[col.name] = col.values.map((v) => v.real); + }); + } else { + throw new Error("Unsupported data type in simulation result"); + } + const timeKey = Object.keys(columnData).find( + (k) => k.toLowerCase() === "time" || k.toLowerCase() === "frequency" + ); + if (!timeKey) { + throw new Error("No time or frequency data in simulation result"); + } + const timeValues = columnData[timeKey]; + const probedVariables = Object.keys(columnData).filter((k) => k !== timeKey); + const plotableNodes = probedVariables; + const plotData = timeValues.map((t, i) => { + const point = { name: t.toExponential(2) }; + probedVariables.forEach((variable) => { + point[variable] = columnData[variable][i]; + }); + return point; + }); + return { plotData, nodes: plotableNodes }; +}; +var useSpiceSimulation = (spiceString) => { + const [plotData, setPlotData] = useState4([]); + const [nodes, setNodes] = useState4([]); + const [isLoading, setIsLoading] = useState4(true); + const [error, setError] = useState4(null); + useEffect8(() => { + if (!spiceString) { + setIsLoading(false); + setPlotData([]); + setNodes([]); + setError(null); + return; + } + setIsLoading(true); + setError(null); + setPlotData([]); + setNodes([]); + const workerUrl = getSpiceSimulationWorkerBlobUrl(); + if (!workerUrl) { + setError("Could not create SPICE simulation worker."); + setIsLoading(false); + return; + } + const worker = new Worker(workerUrl, { type: "module" }); + worker.onmessage = (event) => { + if (event.data.type === "result") { + try { + const { plotData: parsedData, nodes: parsedNodes } = parseEecEngineOutput(event.data.result); + setPlotData(parsedData); + setNodes(parsedNodes); + } catch (e) { + setError(e.message || "Failed to parse simulation result"); + console.error(e); + } + } else if (event.data.type === "error") { + setError(event.data.error); + } + setIsLoading(false); + }; + worker.onerror = (err) => { + setError(err.message); + setIsLoading(false); + }; + worker.postMessage({ spiceString }); + return () => { + worker.terminate(); + }; + }, [spiceString]); + return { plotData, nodes, isLoading, error }; +}; + +// lib/utils/spice-utils.ts +import { circuitJsonToSpice } from "circuit-json-to-spice"; +var formatSimTime = (seconds) => { + if (seconds === 0) return "0"; + const absSeconds = Math.abs(seconds); + const precision = (v) => v.toPrecision(4); + if (absSeconds >= 1) return precision(seconds); + if (absSeconds >= 1e-3) return `${precision(seconds * 1e3)}m`; + if (absSeconds >= 1e-6) return `${precision(seconds * 1e6)}u`; + if (absSeconds >= 1e-9) return `${precision(seconds * 1e9)}n`; + if (absSeconds >= 1e-12) return `${precision(seconds * 1e12)}p`; + if (absSeconds >= 1e-15) return `${precision(seconds * 1e15)}f`; + return seconds.toExponential(3); +}; +var getSpiceFromCircuitJson = (circuitJson, options) => { + const spiceNetlist = circuitJsonToSpice(circuitJson); + const baseSpiceString = spiceNetlist.toSpiceString(); + const lines = baseSpiceString.split("\n").filter((l) => l.trim() !== ""); + const componentLines = lines.filter( + (l) => !l.startsWith("*") && !l.startsWith(".") && l.trim() !== "" + ); + const allNodes = /* @__PURE__ */ new Set(); + const capacitorNodes = /* @__PURE__ */ new Set(); + const componentNamesToProbeCurrent = /* @__PURE__ */ new Set(); + for (const line of componentLines) { + const parts = line.trim().split(/\s+/); + if (parts.length < 3) continue; + const componentName = parts[0]; + const componentType = componentName[0].toUpperCase(); + let nodesOnLine = []; + if (["R", "C", "L", "V", "I", "D"].includes(componentType)) { + nodesOnLine = parts.slice(1, 3); + if (componentType === "V") { + componentNamesToProbeCurrent.add(componentName); + } + } else if (componentType === "Q" && parts.length >= 4) { + nodesOnLine = parts.slice(1, 4); + } else if (componentType === "M" && parts.length >= 5) { + nodesOnLine = parts.slice(1, 5); + } else if (componentType === "X") { + nodesOnLine = parts.slice(1, -1); + } else { + continue; + } + nodesOnLine.forEach((node) => allNodes.add(node)); + if (componentType === "C") { + nodesOnLine.forEach((node) => capacitorNodes.add(node)); + } + } + allNodes.delete("0"); + capacitorNodes.delete("0"); + const icLines = Array.from(capacitorNodes).map((node) => `.ic V(${node})=0`); + const probes = []; + const probeVoltages = Array.from(allNodes).map((node) => `V(${node})`); + probes.push(...probeVoltages); + const probeCurrents = Array.from(componentNamesToProbeCurrent).map( + (name) => `I(${name})` + ); + probes.push(...probeCurrents); + const probeLine = probes.length > 0 ? `.probe ${probes.join(" ")}` : ""; + const tstart_ms = options?.startTime ?? 0; + const duration_ms = options?.duration ?? 20; + const tstart = tstart_ms * 1e-3; + const duration = duration_ms * 1e-3; + const tstop = tstart + duration; + const tstep = duration / 50; + const tranLine = `.tran ${formatSimTime(tstep)} ${formatSimTime( + tstop + )} ${formatSimTime(tstart)} UIC`; + const endStatement = ".end"; + const originalLines = baseSpiceString.split("\n"); + let endIndex = -1; + for (let i = originalLines.length - 1; i >= 0; i--) { + if (originalLines[i].trim().toLowerCase().startsWith(endStatement)) { + endIndex = i; + break; + } + } + const injectionLines = [...icLines, probeLine, tranLine].filter(Boolean); + let finalLines; + if (endIndex !== -1) { + const beforeEnd = originalLines.slice(0, endIndex); + const endLineAndAfter = originalLines.slice(endIndex); + finalLines = [...beforeEnd, ...injectionLines, ...endLineAndAfter]; + } else { + finalLines = [...originalLines, ...injectionLines, endStatement]; + } + return finalLines.join("\n"); +}; + +// lib/hooks/useLocalStorage.ts +import { useCallback as useCallback2 } from "react"; +var STORAGE_KEYS = { + IS_SHOWING_SCHEMATIC_GROUPS: "schematic_viewer_show_groups", + SELECTED_SCHEMATIC_SHEET: "schematic_viewer_selected_sheet" +}; +var getStoredBoolean = (key, defaultValue) => { + if (typeof window === "undefined") return defaultValue; + try { + const stored = localStorage.getItem(key); + return stored !== null ? JSON.parse(stored) : defaultValue; + } catch { + return defaultValue; + } +}; +var setStoredBoolean = (key, value) => { + if (typeof window === "undefined") return; + try { + localStorage.setItem(key, JSON.stringify(value)); + } catch { + } +}; +var getStoredString = (key) => { + if (typeof window === "undefined") return null; + try { + return localStorage.getItem(key); + } catch { + return null; + } +}; +var setStoredString = (key, value) => { + if (typeof window === "undefined") return; + try { + localStorage.setItem(key, value); + } catch { + } +}; +var spacePanHeld = false; +var isSpacePanHeld = () => spacePanHeld; +var setSpacePanHeld = (held) => { + spacePanHeld = held; +}; + +// lib/components/MouseTracker.tsx +import { + createContext, + useCallback as useCallback3, + useContext, + useEffect as useEffect9, + useMemo as useMemo3, + useRef as useRef4 +} from "react"; +import { Fragment, jsx as jsx9 } from "react/jsx-runtime"; +var MouseTrackerContext = createContext(null); +var DRAG_THRESHOLD_PX = 5; +var boundsAreEqual = (a, b) => { + if (!a && !b) return true; + if (!a || !b) return false; + return a.minX === b.minX && a.maxX === b.maxX && a.minY === b.minY && a.maxY === b.maxY; +}; +var MouseTracker = ({ children }) => { + const existingContext = useContext(MouseTrackerContext); + if (existingContext) { + return /* @__PURE__ */ jsx9(Fragment, { children }); + } + const storeRef = useRef4({ + pointer: null, + boundingBoxes: /* @__PURE__ */ new Map(), + hoveringIds: /* @__PURE__ */ new Set(), + subscribers: /* @__PURE__ */ new Set(), + mouseDownPosition: null + }); + const notifySubscribers = useCallback3(() => { + for (const callback of storeRef.current.subscribers) { + callback(); + } + }, []); + const updateHovering = useCallback3(() => { + const pointer = storeRef.current.pointer; + const newHovering = /* @__PURE__ */ new Set(); + if (pointer) { + for (const [id, registration] of storeRef.current.boundingBoxes) { + const bounds = registration.bounds; + if (!bounds) continue; + if (pointer.x >= bounds.minX && pointer.x <= bounds.maxX && pointer.y >= bounds.minY && pointer.y <= bounds.maxY) { + newHovering.add(id); + } + } + } + const prevHovering = storeRef.current.hoveringIds; + if (newHovering.size === prevHovering.size && [...newHovering].every((id) => prevHovering.has(id))) { + return; + } + storeRef.current.hoveringIds = newHovering; + notifySubscribers(); + }, [notifySubscribers]); + const registerBoundingBox = useCallback3( + (id, registration) => { + storeRef.current.boundingBoxes.set(id, registration); + updateHovering(); + }, + [updateHovering] + ); + const updateBoundingBox = useCallback3( + (id, registration) => { + const existing = storeRef.current.boundingBoxes.get(id); + if (existing && boundsAreEqual(existing.bounds, registration.bounds) && existing.onClick === registration.onClick) { + return; + } + storeRef.current.boundingBoxes.set(id, registration); + updateHovering(); + }, + [updateHovering] + ); + const unregisterBoundingBox = useCallback3( + (id) => { + const removed = storeRef.current.boundingBoxes.delete(id); + if (removed) { + updateHovering(); + } + }, + [updateHovering] + ); + const subscribe = useCallback3((listener) => { + storeRef.current.subscribers.add(listener); + return () => { + storeRef.current.subscribers.delete(listener); + }; + }, []); + const isHovering = useCallback3((id) => { + return storeRef.current.hoveringIds.has(id); + }, []); + useEffect9(() => { + const handlePointerPosition = (event) => { + const { clientX, clientY } = event; + const pointer = storeRef.current.pointer; + if (pointer && pointer.x === clientX && pointer.y === clientY) { + return; + } + storeRef.current.pointer = { x: clientX, y: clientY }; + updateHovering(); + }; + const handlePointerLeave = () => { + if (storeRef.current.pointer === null) return; + storeRef.current.pointer = null; + storeRef.current.mouseDownPosition = null; + updateHovering(); + }; + const handleMouseDown = (event) => { + storeRef.current.mouseDownPosition = { + x: event.clientX, + y: event.clientY + }; + }; + const handleClick = (event) => { + const { clientX, clientY } = event; + const mouseDownPos = storeRef.current.mouseDownPosition; + if (mouseDownPos) { + const distance = Math.sqrt( + Math.pow(clientX - mouseDownPos.x, 2) + Math.pow(clientY - mouseDownPos.y, 2) + ); + if (distance > DRAG_THRESHOLD_PX) { + storeRef.current.mouseDownPosition = null; + return; + } + } + storeRef.current.mouseDownPosition = null; + for (const registration of storeRef.current.boundingBoxes.values()) { + const bounds = registration.bounds; + if (!bounds) continue; + if (clientX >= bounds.minX && clientX <= bounds.maxX && clientY >= bounds.minY && clientY <= bounds.maxY) { + registration.onClick?.(event); + } + } + }; + window.addEventListener("pointermove", handlePointerPosition, { + passive: true + }); + window.addEventListener("pointerdown", handlePointerPosition, { + passive: true + }); + window.addEventListener("pointerup", handlePointerPosition, { + passive: true + }); + window.addEventListener("pointerleave", handlePointerLeave); + window.addEventListener("pointercancel", handlePointerLeave); + window.addEventListener("blur", handlePointerLeave); + window.addEventListener("mousedown", handleMouseDown, { passive: true }); + window.addEventListener("click", handleClick, { passive: true }); + return () => { + window.removeEventListener("pointermove", handlePointerPosition); + window.removeEventListener("pointerdown", handlePointerPosition); + window.removeEventListener("pointerup", handlePointerPosition); + window.removeEventListener("pointerleave", handlePointerLeave); + window.removeEventListener("pointercancel", handlePointerLeave); + window.removeEventListener("blur", handlePointerLeave); + window.removeEventListener("mousedown", handleMouseDown); + window.removeEventListener("click", handleClick); + }; + }, [updateHovering]); + const value = useMemo3( + () => ({ + registerBoundingBox, + updateBoundingBox, + unregisterBoundingBox, + subscribe, + isHovering + }), + [ + registerBoundingBox, + updateBoundingBox, + unregisterBoundingBox, + subscribe, + isHovering + ] + ); + return /* @__PURE__ */ jsx9(MouseTrackerContext.Provider, { value, children }); +}; + +// lib/components/SchematicComponentMouseTarget.tsx +import { useCallback as useCallback4, useEffect as useEffect11, useRef as useRef6, useState as useState5 } from "react"; + +// lib/hooks/useMouseEventsOverBoundingBox.ts +import { + useContext as useContext2, + useEffect as useEffect10, + useId, + useMemo as useMemo4, + useRef as useRef5, + useSyncExternalStore +} from "react"; +var useMouseEventsOverBoundingBox = (options) => { + const context = useContext2(MouseTrackerContext); + if (!context) { + throw new Error( + "useMouseEventsOverBoundingBox must be used within a MouseTracker" + ); + } + const id = useId(); + const latestOptionsRef = useRef5(options); + latestOptionsRef.current = options; + const handleClick = useMemo4( + () => (event) => { + latestOptionsRef.current.onClick?.(event); + }, + [] + ); + useEffect10(() => { + context.registerBoundingBox(id, { + bounds: latestOptionsRef.current.bounds, + onClick: latestOptionsRef.current.onClick ? handleClick : void 0 + }); + return () => { + context.unregisterBoundingBox(id); + }; + }, [context, handleClick, id]); + useEffect10(() => { + context.updateBoundingBox(id, { + bounds: latestOptionsRef.current.bounds, + onClick: latestOptionsRef.current.onClick ? handleClick : void 0 + }); + }, [ + context, + handleClick, + id, + options.bounds?.minX, + options.bounds?.maxX, + options.bounds?.minY, + options.bounds?.maxY, + options.onClick + ]); + const hovering = useSyncExternalStore( + context.subscribe, + () => context.isHovering(id), + () => false + ); + return { hovering }; +}; + +// lib/components/SchematicComponentMouseTarget.tsx +import { jsx as jsx10 } from "react/jsx-runtime"; +var areMeasurementsEqual = (a, b) => { + if (!a && !b) return true; + if (!a || !b) return false; + return Math.abs(a.bounds.minX - b.bounds.minX) < 0.5 && Math.abs(a.bounds.maxX - b.bounds.maxX) < 0.5 && Math.abs(a.bounds.minY - b.bounds.minY) < 0.5 && Math.abs(a.bounds.maxY - b.bounds.maxY) < 0.5 && Math.abs(a.rect.left - b.rect.left) < 0.5 && Math.abs(a.rect.top - b.rect.top) < 0.5 && Math.abs(a.rect.width - b.rect.width) < 0.5 && Math.abs(a.rect.height - b.rect.height) < 0.5; +}; +var SchematicComponentMouseTarget = ({ + componentId, + svgDivRef, + containerRef, + onComponentClick, + onHoverChange, + showOutline, + circuitJsonKey +}) => { + const [measurement, setMeasurement] = useState5(null); + const frameRef = useRef6(null); + const measure = useCallback4(() => { + frameRef.current = null; + const svgDiv = svgDivRef.current; + const container = containerRef.current; + if (!svgDiv || !container) { + setMeasurement((prev) => prev ? null : prev); + return; + } + const element = svgDiv.querySelector( + `[data-schematic-component-id="${componentId}"]` + ); + if (!element) { + setMeasurement((prev) => prev ? null : prev); + return; + } + const elementRect = element.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); + const nextMeasurement = { + bounds: { + minX: elementRect.left, + maxX: elementRect.right, + minY: elementRect.top, + maxY: elementRect.bottom + }, + rect: { + left: elementRect.left - containerRect.left, + top: elementRect.top - containerRect.top, + width: elementRect.width, + height: elementRect.height + } + }; + setMeasurement( + (prev) => areMeasurementsEqual(prev, nextMeasurement) ? prev : nextMeasurement + ); + }, [componentId, containerRef, svgDivRef]); + const scheduleMeasure = useCallback4(() => { + if (frameRef.current !== null) return; + frameRef.current = window.requestAnimationFrame(measure); + }, [measure]); + useEffect11(() => { + scheduleMeasure(); + }, [scheduleMeasure, circuitJsonKey]); + useEffect11(() => { + scheduleMeasure(); + const svgDiv = svgDivRef.current; + const container = containerRef.current; + if (!svgDiv || !container) return; + const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => { + scheduleMeasure(); + }) : null; + resizeObserver?.observe(container); + resizeObserver?.observe(svgDiv); + const mutationObserver = typeof MutationObserver !== "undefined" ? new MutationObserver(() => { + scheduleMeasure(); + }) : null; + mutationObserver?.observe(svgDiv, { + attributes: true, + attributeFilter: ["style", "transform"], + subtree: true, + childList: true + }); + window.addEventListener("scroll", scheduleMeasure, true); + window.addEventListener("resize", scheduleMeasure); + return () => { + resizeObserver?.disconnect(); + mutationObserver?.disconnect(); + window.removeEventListener("scroll", scheduleMeasure, true); + window.removeEventListener("resize", scheduleMeasure); + if (frameRef.current !== null) { + cancelAnimationFrame(frameRef.current); + frameRef.current = null; + } + }; + }, [scheduleMeasure, svgDivRef, containerRef]); + const handleClick = useCallback4( + (event) => { + if (onComponentClick) { + onComponentClick(componentId, event); + } + }, + [componentId, onComponentClick] + ); + const bounds = measurement?.bounds ?? null; + const { hovering } = useMouseEventsOverBoundingBox({ + bounds, + onClick: onComponentClick ? handleClick : void 0 + }); + useEffect11(() => { + if (onHoverChange) { + onHoverChange(componentId, hovering); + } + }, [hovering, componentId, onHoverChange]); + if (!measurement || !hovering || !showOutline) { + return null; + } + const rect = measurement.rect; + return /* @__PURE__ */ jsx10( + "div", + { + style: { + position: "absolute", + left: rect.left, + top: rect.top, + width: rect.width, + height: rect.height, + border: "1.5px solid rgba(51, 153, 255, 0.9)", + pointerEvents: "none", + zIndex: zIndexMap.schematicComponentHoverOutline + } + } + ); +}; + +// lib/components/SchematicPortMouseTarget.tsx +import { useCallback as useCallback5, useEffect as useEffect12, useRef as useRef7, useState as useState6 } from "react"; +import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime"; +var areMeasurementsEqual2 = (a, b) => { + if (!a && !b) return true; + if (!a || !b) return false; + return Math.abs(a.bounds.minX - b.bounds.minX) < 0.5 && Math.abs(a.bounds.maxX - b.bounds.maxX) < 0.5 && Math.abs(a.bounds.minY - b.bounds.minY) < 0.5 && Math.abs(a.bounds.maxY - b.bounds.maxY) < 0.5 && Math.abs(a.rect.left - b.rect.left) < 0.5 && Math.abs(a.rect.top - b.rect.top) < 0.5 && Math.abs(a.rect.width - b.rect.width) < 0.5 && Math.abs(a.rect.height - b.rect.height) < 0.5; +}; +var SchematicPortMouseTarget = ({ + portId, + portLabel, + svgDivRef, + containerRef, + onPortClick, + onPortMouseDown, + onHoverChange, + showOutline, + interactive = false, + hitPaddingPx = 4, + circuitJsonKey +}) => { + const [measurement, setMeasurement] = useState6(null); + const frameRef = useRef7(null); + const measure = useCallback5(() => { + frameRef.current = null; + const svgDiv = svgDivRef.current; + const container = containerRef.current; + if (!svgDiv || !container) { + setMeasurement((prev) => prev ? null : prev); + return; + } + const element = svgDiv.querySelector( + `[data-schematic-port-id="${portId}"]` + ); + if (!element) { + setMeasurement((prev) => prev ? null : prev); + return; + } + const elementRect = element.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); + const padding = hitPaddingPx; + const nextMeasurement = { + bounds: { + minX: elementRect.left - padding, + maxX: elementRect.right + padding, + minY: elementRect.top - padding, + maxY: elementRect.bottom + padding + }, + rect: { + left: elementRect.left - containerRect.left - padding, + top: elementRect.top - containerRect.top - padding, + width: elementRect.width + padding * 2, + height: elementRect.height + padding * 2 + } + }; + setMeasurement( + (prev) => areMeasurementsEqual2(prev, nextMeasurement) ? prev : nextMeasurement + ); + }, [portId, containerRef, svgDivRef, hitPaddingPx]); + const scheduleMeasure = useCallback5(() => { + if (frameRef.current !== null) return; + frameRef.current = window.requestAnimationFrame(measure); + }, [measure]); + useEffect12(() => { + scheduleMeasure(); + }, [scheduleMeasure, circuitJsonKey]); + useEffect12(() => { + scheduleMeasure(); + const svgDiv = svgDivRef.current; + const container = containerRef.current; + if (!svgDiv || !container) return; + const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => { + scheduleMeasure(); + }) : null; + resizeObserver?.observe(container); + resizeObserver?.observe(svgDiv); + const mutationObserver = typeof MutationObserver !== "undefined" ? new MutationObserver(() => { + scheduleMeasure(); + }) : null; + mutationObserver?.observe(svgDiv, { + attributes: true, + attributeFilter: ["style", "transform"], + subtree: true, + childList: true + }); + window.addEventListener("scroll", scheduleMeasure, true); + window.addEventListener("resize", scheduleMeasure); + return () => { + resizeObserver?.disconnect(); + mutationObserver?.disconnect(); + window.removeEventListener("scroll", scheduleMeasure, true); + window.removeEventListener("resize", scheduleMeasure); + if (frameRef.current !== null) { + cancelAnimationFrame(frameRef.current); + frameRef.current = null; + } + }; + }, [scheduleMeasure, svgDivRef, containerRef]); + const handleClick = useCallback5( + (event) => { + if (onPortClick) { + onPortClick(portId, event); + } + }, + [portId, onPortClick] + ); + const bounds = measurement?.bounds ?? null; + const { hovering } = useMouseEventsOverBoundingBox({ + bounds, + onClick: onPortClick ? handleClick : void 0 + }); + useEffect12(() => { + if (onHoverChange) { + onHoverChange(portId, hovering); + } + }, [hovering, portId, onHoverChange]); + if (!measurement || !showOutline) { + return null; + } + const rect = measurement.rect; + return /* @__PURE__ */ jsxs6(Fragment2, { children: [ + /* @__PURE__ */ jsx11( + "div", + { + onMouseDown: interactive && onPortMouseDown ? (e) => { + e.preventDefault(); + e.stopPropagation(); + onPortMouseDown(portId, e.nativeEvent); + } : void 0, + style: { + position: "absolute", + left: rect.left, + top: rect.top, + width: rect.width, + height: rect.height, + border: hovering ? "1.5px solid rgba(255, 153, 51, 0.9)" : "1.5px solid rgba(255, 153, 51, 0.3)", + backgroundColor: hovering ? "rgba(255, 153, 51, 0.15)" : "rgba(255, 153, 51, 0.05)", + borderRadius: "50%", + pointerEvents: interactive ? "auto" : "none", + cursor: interactive ? "crosshair" : void 0, + zIndex: zIndexMap.schematicPortHoverOutline, + transition: "border-color 0.15s, background-color 0.15s" + } + } + ), + hovering && portLabel && /* @__PURE__ */ jsx11( + "div", + { + style: { + position: "absolute", + left: rect.left + rect.width / 2, + top: rect.top - 24, + transform: "translateX(-50%)", + backgroundColor: "rgba(0, 0, 0, 0.85)", + color: "white", + padding: "4px 8px", + borderRadius: "4px", + fontSize: "12px", + fontFamily: "monospace", + whiteSpace: "nowrap", + pointerEvents: "none", + zIndex: zIndexMap.schematicPortHoverOutline + 1 + }, + children: portLabel + } + ) + ] }); +}; + +// lib/components/SchematicSheetSelector.tsx +import { useState as useState7 } from "react"; +import * as DropdownMenu2 from "@radix-ui/react-dropdown-menu"; +import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime"; +var FONT_FAMILY2 = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'; +var contentStyles2 = { + backgroundColor: "#ffffff", + color: "#111111", + borderRadius: 8, + boxShadow: "0 6px 24px rgba(0,0,0,0.12), 0 1px 3px rgba(0,0,0,0.08)", + border: "1px solid #e5e7eb", + padding: 4, + minWidth: 200, + maxWidth: 320, + maxHeight: 320, + overflowY: "auto", + fontSize: 13, + fontFamily: FONT_FAMILY2, + outline: "none", + zIndex: zIndexMap.viewMenu +}; +var itemStyles2 = { + display: "flex", + alignItems: "center", + gap: 8, + padding: "7px 10px 7px 8px", + borderRadius: 6, + cursor: "pointer", + outline: "none", + userSelect: "none", + color: "#111111", + fontSize: 13, + fontFamily: FONT_FAMILY2 +}; +var iconSlotStyles2 = { + width: 16, + height: 16, + flexShrink: 0, + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + color: "#111111" +}; +var ellipsisStyles = { + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap" +}; +var MENU_CSS = ` +.sv-sheet-item[data-highlighted], .sv-sheet-item:hover { background-color: #f1f3f5; } +.sv-sheet-chevron { transition: transform 0.2s ease; } +[data-state="open"] > .sv-sheet-chevron { transform: rotate(180deg); } +`; +var CheckIcon2 = () => /* @__PURE__ */ jsx12( + "svg", + { + width: "14", + height: "14", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2.5", + strokeLinecap: "round", + strokeLinejoin: "round", + "aria-hidden": "true", + children: /* @__PURE__ */ jsx12("path", { d: "M20 6 9 17l-5-5" }) + } +); +var ChevronDownIcon = ({ className }) => /* @__PURE__ */ jsx12( + "svg", + { + className, + width: "14", + height: "14", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2", + strokeLinecap: "round", + strokeLinejoin: "round", + style: { opacity: 0.6, flexShrink: 0 }, + "aria-hidden": "true", + children: /* @__PURE__ */ jsx12("path", { d: "m6 9 6 6 6-6" }) + } +); +var SchematicSheetSelector = ({ + sheets, + selectedSheetId, + onSelectSheet +}) => { + const [open, setOpen] = useState7(false); + if (sheets.length <= 1) return null; + const selectedSheet = sheets.find( + (s) => s.schematic_sheet_id === selectedSheetId + ); + const selectedLabel = selectedSheet?.name ?? "Select sheet"; + return /* @__PURE__ */ jsxs7(Fragment3, { children: [ + /* @__PURE__ */ jsx12("style", { children: MENU_CSS }), + /* @__PURE__ */ jsxs7(DropdownMenu2.Root, { open, onOpenChange: setOpen, modal: false, children: [ + /* @__PURE__ */ jsx12(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs7( + "button", + { + type: "button", + title: selectedLabel, + onPointerDown: (e) => e.stopPropagation(), + style: { + position: "absolute", + top: "16px", + left: "16px", + display: "flex", + alignItems: "center", + gap: "6px", + maxWidth: "220px", + padding: "8px 12px", + backgroundColor: "#ffffff", + color: "#000000", + border: "none", + outline: "none", + borderRadius: "4px", + cursor: "pointer", + boxShadow: "0 2px 4px rgba(0,0,0,0.1)", + fontSize: "13px", + fontFamily: FONT_FAMILY2, + zIndex: zIndexMap.viewMenuIcon + }, + children: [ + /* @__PURE__ */ jsx12("span", { style: { color: "#888888", flexShrink: 0 }, children: "Sheet:" }), + /* @__PURE__ */ jsx12("span", { style: { ...ellipsisStyles, minWidth: 0 }, children: selectedLabel }), + /* @__PURE__ */ jsx12(ChevronDownIcon, { className: "sv-sheet-chevron" }) + ] + } + ) }), + /* @__PURE__ */ jsx12(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsx12( + DropdownMenu2.Content, + { + style: contentStyles2, + side: "bottom", + align: "start", + sideOffset: 8, + collisionPadding: 10, + children: sheets.map((sheet) => { + const selected = sheet.schematic_sheet_id === selectedSheetId; + return /* @__PURE__ */ jsxs7( + DropdownMenu2.Item, + { + className: "sv-sheet-item", + style: itemStyles2, + title: sheet.name, + onSelect: (e) => e.preventDefault(), + onPointerUp: () => { + onSelectSheet(sheet.schematic_sheet_id); + setOpen(false); + }, + children: [ + /* @__PURE__ */ jsx12("span", { style: iconSlotStyles2, children: selected && /* @__PURE__ */ jsx12(CheckIcon2, {}) }), + /* @__PURE__ */ jsx12("span", { style: { ...ellipsisStyles, minWidth: 0 }, children: sheet.name }) + ] + }, + sheet.schematic_sheet_id + ); + }) + } + ) }) + ] }) + ] }); +}; + +// lib/hooks/useWireDrawing.ts +import { useCallback as useCallback6, useEffect as useEffect13, useRef as useRef8, useState as useState8 } from "react"; +import "transformation-matrix"; + +// lib/utils/isMouseCaptureIgnoredTarget.ts +function isMouseCaptureIgnoredTarget(target) { + const el = target; + if (!el) return false; + return Boolean( + el.closest( + "[data-schematic-ignore-mouse-capture], input, textarea, select, option" + ) + ); +} + +// lib/utils/schematicPortHitTest.ts +import { compose as compose3 } from "transformation-matrix"; +var SCHEMATIC_PORT_HIT_RADIUS_PX = 36; +function resolveSchematicPortId(circuitJson, portId) { + const bySchematic = circuitJson.find( + (el) => el.type === "schematic_port" && el.schematic_port_id === portId + ); + if (bySchematic) return portId; + const bySource = circuitJson.find( + (el) => el.type === "schematic_port" && el.source_port_id === portId + ); + return bySource?.schematic_port_id ?? null; +} +function createScreenToReal(svgToScreenProjection, realToSvgProjection, containerRef) { + return (screenX, screenY) => { + const container = containerRef.current; + if (!container) return { x: 0, y: 0 }; + const rect = container.getBoundingClientRect(); + const localX = screenX - rect.left; + const localY = screenY - rect.top; + const realToScreen = compose3(svgToScreenProjection, realToSvgProjection); + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d + }; + }; +} +function getSchematicPortCenter(circuitJson, container, schematicPortId, screenToReal) { + const port = circuitJson.find( + (el2) => el2.type === "schematic_port" && el2.schematic_port_id === schematicPortId + ); + if (port?.center) { + return { x: port.center.x, y: port.center.y }; + } + if (!container) return null; + const el = container.querySelector( + `[data-schematic-port-id="${schematicPortId}"]` + ); + if (!el) return null; + const rect = el.getBoundingClientRect(); + return screenToReal(rect.left + rect.width / 2, rect.top + rect.height / 2); +} +function getSchematicPortAtScreen(container, circuitJson, screenX, screenY, hitRadiusPx = SCHEMATIC_PORT_HIT_RADIUS_PX) { + if (!container) return null; + let closest = null; + for (const node of Array.from( + container.querySelectorAll("[data-schematic-port-id]") + )) { + const rect = node.getBoundingClientRect(); + const cx = rect.left + rect.width / 2; + const cy = rect.top + rect.height / 2; + const dist = Math.hypot(cx - screenX, cy - screenY); + if (dist < hitRadiusPx && (!closest || dist < closest.dist)) { + const id = node.getAttribute("data-schematic-port-id"); + const resolved = id ? resolveSchematicPortId(circuitJson, id) : null; + if (resolved) closest = { id: resolved, dist }; + } + } + return closest?.id ?? null; +} + +// lib/hooks/useWireDrawing.ts +var useWireDrawing = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent +}) => { + const [state, setState] = useState8({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + waypoints: [] + }); + const stateRef = useRef8(state); + stateRef.current = state; + const screenToReal = useCallback6( + createScreenToReal( + svgToScreenProjection, + realToSvgProjection, + containerRef + ), + [svgToScreenProjection, realToSvgProjection, containerRef] + ); + const getPortCenter = useCallback6( + (portId) => { + const schematicPortId = resolveSchematicPortId(circuitJson, portId); + if (!schematicPortId) return null; + return getSchematicPortCenter( + circuitJson, + containerRef.current, + schematicPortId, + screenToReal + ); + }, + [circuitJson, containerRef, screenToReal] + ); + const getPortAtScreen = useCallback6( + (screenX, screenY) => getSchematicPortAtScreen( + containerRef.current, + circuitJson, + screenX, + screenY + ), + [circuitJson, containerRef] + ); + const beginWireAtPort = useCallback6( + (portId) => { + const schematicPortId = resolveSchematicPortId(circuitJson, portId); + if (!schematicPortId) return false; + const center = getPortCenter(schematicPortId); + if (!center) return false; + setState({ + isDrawing: true, + fromPortId: schematicPortId, + previewEnd: center, + waypoints: [center] + }); + return true; + }, + [circuitJson, getPortCenter] + ); + const finishWireAtPort = useCallback6( + (portId) => { + const current = stateRef.current; + if (!current.isDrawing || !current.fromPortId) return false; + const schematicPortId = resolveSchematicPortId(circuitJson, portId); + if (!schematicPortId || schematicPortId === current.fromPortId) { + return false; + } + const toCenter = getPortCenter(schematicPortId); + if (!toCenter) return false; + const event = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_wire_add", + from_schematic_port_id: current.fromPortId, + to_schematic_port_id: schematicPortId, + route: [...current.waypoints, toCenter], + created_at: Date.now(), + in_progress: false + }; + onEditEvent?.(event); + setState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + waypoints: [] + }); + return true; + }, + [circuitJson, getPortCenter, onEditEvent] + ); + const handlePortMouseDown = useCallback6( + (portId, e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) { + return; + } + e.preventDefault(); + e.stopPropagation(); + const current = stateRef.current; + if (!current.isDrawing) { + beginWireAtPort(portId); + return; + } + if (!finishWireAtPort(portId)) { + const realPos = screenToReal(e.clientX, e.clientY); + setState((prev) => ({ + ...prev, + waypoints: [...prev.waypoints, realPos] + })); + } + }, + [enabled, beginWireAtPort, finishWireAtPort, screenToReal] + ); + const handleMouseDown = useCallback6( + (e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) { + return; + } + const current = stateRef.current; + const portId = getPortAtScreen(e.clientX, e.clientY); + if (!current.isDrawing) { + if (!portId) return; + e.preventDefault(); + e.stopPropagation(); + beginWireAtPort(portId); + return; + } + if (portId && finishWireAtPort(portId)) { + e.preventDefault(); + e.stopPropagation(); + return; + } + e.preventDefault(); + e.stopPropagation(); + const realPos = screenToReal(e.clientX, e.clientY); + setState((prev) => ({ + ...prev, + waypoints: [...prev.waypoints, realPos] + })); + }, + [enabled, getPortAtScreen, beginWireAtPort, finishWireAtPort, screenToReal] + ); + const handleMouseMove = useCallback6( + (e) => { + if (!enabled || !stateRef.current.isDrawing) return; + const realPos = screenToReal(e.clientX, e.clientY); + setState((prev) => ({ ...prev, previewEnd: realPos })); + }, + [enabled, screenToReal] + ); + const handleKeyDown = useCallback6((e) => { + if (e.key === "Escape" && stateRef.current.isDrawing) { + setState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + waypoints: [] + }); + } + }, []); + useEffect13(() => { + if (!enabled) { + setState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + waypoints: [] + }); + return; + } + window.addEventListener("mousedown", handleMouseDown, { capture: true }); + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("mousedown", handleMouseDown, { + capture: true + }); + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [enabled, handleMouseDown, handleMouseMove, handleKeyDown]); + return { wireDrawingState: state, handlePortMouseDown }; +}; + +// lib/hooks/useBusDrawing.ts +import { useCallback as useCallback7, useEffect as useEffect14, useRef as useRef9, useState as useState9 } from "react"; +import { compose as compose4 } from "transformation-matrix"; +var useBusDrawing = ({ + enabled, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent +}) => { + const [state, setState] = useState9({ + isDrawing: false, + previewEnd: null, + waypoints: [] + }); + const stateRef = useRef9(state); + stateRef.current = state; + const screenToReal = useCallback7( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) return { x: 0, y: 0 }; + const rect = container.getBoundingClientRect(); + const localX = screenX - rect.left; + const localY = screenY - rect.top; + const realToScreen = compose4(svgToScreenProjection, realToSvgProjection); + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d + }; + }, + [svgToScreenProjection, realToSvgProjection, containerRef] + ); + const dedupeRoute = (route) => { + const out = []; + for (const p of route) { + const last = out[out.length - 1]; + if (!last || Math.hypot(p.x - last.x, p.y - last.y) > 0.05) out.push(p); + } + return out; + }; + const finishBus = useCallback7( + (route) => { + const cleaned = dedupeRoute(route); + if (cleaned.length < 2) return false; + const event = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_bus_add", + route: cleaned, + created_at: Date.now(), + in_progress: false + }; + onEditEvent?.(event); + setState({ isDrawing: false, previewEnd: null, waypoints: [] }); + return true; + }, + [onEditEvent] + ); + const handleMouseDown = useCallback7( + (e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return; + const current = stateRef.current; + const realPos = screenToReal(e.clientX, e.clientY); + if (!current.isDrawing) { + e.stopPropagation(); + setState({ + isDrawing: true, + previewEnd: realPos, + waypoints: [realPos] + }); + return; + } + e.stopPropagation(); + setState((prev) => ({ + ...prev, + waypoints: [...prev.waypoints, realPos], + previewEnd: realPos + })); + }, + [enabled, screenToReal] + ); + const handleDoubleClick = useCallback7( + (e) => { + if (!enabled || !stateRef.current.isDrawing) return; + const current = stateRef.current; + const end = screenToReal(e.clientX, e.clientY); + const route = [...current.waypoints, end]; + if (finishBus(route.length >= 2 ? route : current.waypoints)) { + e.preventDefault(); + e.stopPropagation(); + } + }, + [enabled, screenToReal, finishBus] + ); + const handleMouseMove = useCallback7( + (e) => { + if (!enabled || !stateRef.current.isDrawing) return; + const realPos = screenToReal(e.clientX, e.clientY); + setState((prev) => ({ ...prev, previewEnd: realPos })); + }, + [enabled, screenToReal] + ); + const handleKeyDown = useCallback7( + (e) => { + const target = e.target; + if (target?.closest("input, textarea, select")) return; + if (e.key === "Enter" && stateRef.current.isDrawing) { + const route = stateRef.current.waypoints; + if (finishBus(route)) { + e.preventDefault(); + } + return; + } + if (e.key === "Escape" && stateRef.current.isDrawing) { + setState({ isDrawing: false, previewEnd: null, waypoints: [] }); + } + }, + [finishBus] + ); + useEffect14(() => { + if (!enabled) { + setState({ isDrawing: false, previewEnd: null, waypoints: [] }); + return; + } + window.addEventListener("mousedown", handleMouseDown, { capture: true }); + window.addEventListener("dblclick", handleDoubleClick, { capture: true }); + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("mousedown", handleMouseDown, { + capture: true + }); + window.removeEventListener("dblclick", handleDoubleClick, { + capture: true + }); + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [ + enabled, + handleMouseDown, + handleDoubleClick, + handleMouseMove, + handleKeyDown + ]); + return { busDrawingState: state }; +}; + +// lib/hooks/useBusEntryPlacement.ts +import { useCallback as useCallback8, useEffect as useEffect15, useState as useState10 } from "react"; +import { compose as compose5 } from "transformation-matrix"; +var BUS_ENTRY_STUB_LEN = 2.5; +var useBusEntryPlacement = ({ + enabled, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent +}) => { + const [previewState, setPreviewState] = useState10({ + anchor: null + }); + const screenToReal = useCallback8( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) return { x: 0, y: 0 }; + const rect = container.getBoundingClientRect(); + const localX = screenX - rect.left; + const localY = screenY - rect.top; + const realToScreen = compose5(svgToScreenProjection, realToSvgProjection); + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d + }; + }, + [svgToScreenProjection, realToSvgProjection, containerRef] + ); + const placeEntry = useCallback8( + (anchor) => { + const event = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_bus_entry_add", + anchor, + created_at: Date.now(), + in_progress: false + }; + onEditEvent?.(event); + }, + [onEditEvent] + ); + const handleMouseDown = useCallback8( + (e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return; + const anchor = screenToReal(e.clientX, e.clientY); + e.preventDefault(); + e.stopPropagation(); + placeEntry(anchor); + }, + [enabled, screenToReal, placeEntry] + ); + const handleMouseMove = useCallback8( + (e) => { + if (!enabled) return; + setPreviewState({ anchor: screenToReal(e.clientX, e.clientY) }); + }, + [enabled, screenToReal] + ); + useEffect15(() => { + if (!enabled) { + setPreviewState({ anchor: null }); + return; + } + window.addEventListener("mousedown", handleMouseDown, { capture: true }); + window.addEventListener("mousemove", handleMouseMove); + return () => { + window.removeEventListener("mousedown", handleMouseDown, { + capture: true + }); + window.removeEventListener("mousemove", handleMouseMove); + }; + }, [enabled, handleMouseDown, handleMouseMove]); + return { busEntryPreviewState: previewState }; +}; + +// lib/hooks/useNoConnectPlacement.ts +import { useCallback as useCallback9, useEffect as useEffect16, useState as useState11 } from "react"; +import { compose as compose6 } from "transformation-matrix"; +var NO_CONNECT_HALF = 0.4; +var PORT_HIT_RADIUS_PX = 36; +var useNoConnectPlacement = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent +}) => { + const [previewState, setPreviewState] = useState11({ + center: null + }); + const screenToReal = useCallback9( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) return { x: 0, y: 0 }; + const rect = container.getBoundingClientRect(); + const localX = screenX - rect.left; + const localY = screenY - rect.top; + const realToScreen = compose6(svgToScreenProjection, realToSvgProjection); + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d + }; + }, + [svgToScreenProjection, realToSvgProjection, containerRef] + ); + const resolveSchematicPortId2 = useCallback9( + (portId) => { + const bySchematic = circuitJson.find( + (el) => el.type === "schematic_port" && el.schematic_port_id === portId + ); + if (bySchematic) return portId; + const bySource = circuitJson.find( + (el) => el.type === "schematic_port" && el.source_port_id === portId + ); + return bySource?.schematic_port_id ?? null; + }, + [circuitJson] + ); + const getPortCenter = useCallback9( + (portId) => { + const schematicPortId = resolveSchematicPortId2(portId); + if (!schematicPortId) return null; + const port = circuitJson.find( + (el) => el.type === "schematic_port" && el.schematic_port_id === schematicPortId + ); + if (!port?.center) return null; + return { x: port.center.x, y: port.center.y }; + }, + [circuitJson, resolveSchematicPortId2] + ); + const getPortAtScreen = useCallback9( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) return null; + let closest = null; + const portEls = container.querySelectorAll("[data-schematic-port-id]"); + for (const node of Array.from(portEls)) { + const rect = node.getBoundingClientRect(); + const cx = rect.left + rect.width / 2; + const cy = rect.top + rect.height / 2; + const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2); + if (dist < PORT_HIT_RADIUS_PX && (!closest || dist < closest.dist)) { + const id = node.getAttribute("data-schematic-port-id"); + const resolved = id ? resolveSchematicPortId2(id) : null; + if (resolved) closest = { id: resolved, dist }; + } + } + return closest?.id ?? null; + }, + [containerRef, resolveSchematicPortId2] + ); + const placeNoConnect = useCallback9( + (center, schematicPortId) => { + const event = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_no_connect_add", + center, + schematic_port_id: schematicPortId, + created_at: Date.now(), + in_progress: false + }; + onEditEvent?.(event); + }, + [onEditEvent] + ); + const handleMouseDown = useCallback9( + (e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return; + e.preventDefault(); + e.stopPropagation(); + const portId = getPortAtScreen(e.clientX, e.clientY); + if (portId) { + const center = getPortCenter(portId); + if (center) { + placeNoConnect(center, portId); + return; + } + } + placeNoConnect(screenToReal(e.clientX, e.clientY)); + }, + [enabled, getPortAtScreen, getPortCenter, placeNoConnect, screenToReal] + ); + const handleMouseMove = useCallback9( + (e) => { + if (!enabled) return; + const portId = getPortAtScreen(e.clientX, e.clientY); + if (portId) { + const center = getPortCenter(portId); + if (center) { + setPreviewState({ center }); + return; + } + } + setPreviewState({ center: screenToReal(e.clientX, e.clientY) }); + }, + [enabled, getPortAtScreen, getPortCenter, screenToReal] + ); + useEffect16(() => { + if (!enabled) { + setPreviewState({ center: null }); + return; + } + window.addEventListener("mousedown", handleMouseDown, { capture: true }); + window.addEventListener("mousemove", handleMouseMove); + return () => { + window.removeEventListener("mousedown", handleMouseDown, { + capture: true + }); + window.removeEventListener("mousemove", handleMouseMove); + }; + }, [enabled, handleMouseDown, handleMouseMove]); + return { noConnectPreviewState: previewState }; +}; + +// lib/hooks/useNetLabelPlacement.ts +import { useCallback as useCallback10, useEffect as useEffect17, useRef as useRef10, useState as useState12 } from "react"; +import { compose as compose7 } from "transformation-matrix"; +var PORT_SNAP_RADIUS_PX = 32; +var useNetLabelPlacement = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent +}) => { + const [state, setState] = useState12({ + previewPos: null, + pendingPos: null, + pendingPortId: null + }); + const stateRef = useRef10(state); + stateRef.current = state; + const screenToReal = useCallback10( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) return { x: 0, y: 0 }; + const rect = container.getBoundingClientRect(); + const localX = screenX - rect.left; + const localY = screenY - rect.top; + const realToScreen = compose7(svgToScreenProjection, realToSvgProjection); + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d + }; + }, + [svgToScreenProjection, realToSvgProjection, containerRef] + ); + const snapToPort = useCallback10( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) + return { pos: screenToReal(screenX, screenY), portId: null }; + let closest = null; + const portEls = container.querySelectorAll("[data-schematic-port-id]"); + for (const node of Array.from(portEls)) { + const rect = node.getBoundingClientRect(); + const cx = rect.left + rect.width / 2; + const cy = rect.top + rect.height / 2; + const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2); + if (dist < PORT_SNAP_RADIUS_PX && (!closest || dist < closest.dist)) { + const id = node.getAttribute("data-schematic-port-id"); + const portEl = circuitJson.find( + (el) => el.type === "schematic_port" && el.schematic_port_id === id + ); + if (id && portEl?.center) { + closest = { id, dist, center: portEl.center }; + } + } + } + if (closest) return { pos: closest.center, portId: closest.id }; + return { pos: screenToReal(screenX, screenY), portId: null }; + }, + [containerRef, circuitJson, screenToReal] + ); + const handleMouseMove = useCallback10( + (e) => { + if (!enabled || stateRef.current.pendingPos) return; + const { pos } = snapToPort(e.clientX, e.clientY); + setState((prev) => ({ ...prev, previewPos: pos })); + }, + [enabled, snapToPort] + ); + const handleMouseDown = useCallback10( + (e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return; + if (stateRef.current.pendingPos) return; + e.preventDefault(); + e.stopPropagation(); + const { pos, portId } = snapToPort(e.clientX, e.clientY); + setState((prev) => ({ + ...prev, + pendingPos: pos, + pendingPortId: portId, + previewPos: null + })); + }, + [enabled, snapToPort] + ); + const handleKeyDown = useCallback10((e) => { + if (e.key === "Escape") { + setState({ + previewPos: null, + pendingPos: null, + pendingPortId: null + }); + } + }, []); + const confirmPlacement = useCallback10( + (netName) => { + const current = stateRef.current; + if (!current.pendingPos || !netName.trim()) return; + const event = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_net_label_add", + position: current.pendingPos, + net_name: netName.trim(), + schematic_port_id: current.pendingPortId ?? void 0, + anchor_side: "right", + created_at: Date.now(), + in_progress: false + }; + onEditEvent?.(event); + setState({ previewPos: null, pendingPos: null, pendingPortId: null }); + }, + [onEditEvent] + ); + const cancelPlacement = useCallback10(() => { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }); + }, []); + useEffect17(() => { + if (!enabled) { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }); + return; + } + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mousedown", handleMouseDown, { capture: true }); + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mousedown", handleMouseDown, { + capture: true + }); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown]); + return { netLabelState: state, confirmPlacement, cancelPlacement }; +}; + +// lib/hooks/useGlobalLabelPlacement.ts +import { useCallback as useCallback11, useEffect as useEffect18, useRef as useRef11, useState as useState13 } from "react"; +import { compose as compose8 } from "transformation-matrix"; +var PORT_SNAP_RADIUS_PX2 = 32; +var useGlobalLabelPlacement = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent +}) => { + const [state, setState] = useState13({ + previewPos: null, + pendingPos: null, + pendingPortId: null + }); + const stateRef = useRef11(state); + stateRef.current = state; + const screenToReal = useCallback11( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) return { x: 0, y: 0 }; + const rect = container.getBoundingClientRect(); + const localX = screenX - rect.left; + const localY = screenY - rect.top; + const realToScreen = compose8(svgToScreenProjection, realToSvgProjection); + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d + }; + }, + [svgToScreenProjection, realToSvgProjection, containerRef] + ); + const snapToPort = useCallback11( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) + return { pos: screenToReal(screenX, screenY), portId: null }; + let closest = null; + const portEls = container.querySelectorAll("[data-schematic-port-id]"); + for (const node of Array.from(portEls)) { + const rect = node.getBoundingClientRect(); + const cx = rect.left + rect.width / 2; + const cy = rect.top + rect.height / 2; + const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2); + if (dist < PORT_SNAP_RADIUS_PX2 && (!closest || dist < closest.dist)) { + const id = node.getAttribute("data-schematic-port-id"); + const portEl = circuitJson.find( + (el) => el.type === "schematic_port" && el.schematic_port_id === id + ); + if (id && portEl?.center) { + closest = { id, dist, center: portEl.center }; + } + } + } + if (closest) return { pos: closest.center, portId: closest.id }; + return { pos: screenToReal(screenX, screenY), portId: null }; + }, + [containerRef, circuitJson, screenToReal] + ); + const handleMouseMove = useCallback11( + (e) => { + if (!enabled || stateRef.current.pendingPos) return; + const { pos } = snapToPort(e.clientX, e.clientY); + setState((prev) => ({ ...prev, previewPos: pos })); + }, + [enabled, snapToPort] + ); + const handleMouseDown = useCallback11( + (e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return; + if (stateRef.current.pendingPos) return; + e.preventDefault(); + e.stopPropagation(); + const { pos, portId } = snapToPort(e.clientX, e.clientY); + setState((prev) => ({ + ...prev, + pendingPos: pos, + pendingPortId: portId, + previewPos: null + })); + }, + [enabled, snapToPort] + ); + const handleKeyDown = useCallback11((e) => { + if (e.key === "Escape") { + setState({ + previewPos: null, + pendingPos: null, + pendingPortId: null + }); + } + }, []); + const confirmPlacement = useCallback11( + (netName) => { + const current = stateRef.current; + if (!current.pendingPos || !netName.trim()) return; + const event = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_global_label_add", + position: current.pendingPos, + net_name: netName.trim(), + schematic_port_id: current.pendingPortId ?? void 0, + anchor_side: "right", + created_at: Date.now(), + in_progress: false + }; + onEditEvent?.(event); + setState({ previewPos: null, pendingPos: null, pendingPortId: null }); + }, + [onEditEvent] + ); + const cancelPlacement = useCallback11(() => { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }); + }, []); + useEffect18(() => { + if (!enabled) { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }); + return; + } + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mousedown", handleMouseDown, { capture: true }); + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mousedown", handleMouseDown, { + capture: true + }); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown]); + return { globalLabelState: state, confirmPlacement, cancelPlacement }; +}; + +// lib/hooks/useHierSheetPlacement.ts +import { useCallback as useCallback12, useEffect as useEffect19, useRef as useRef12, useState as useState14 } from "react"; +import { + applyToPoint as applyToPoint2, + compose as compose9, + inverse +} from "transformation-matrix"; +var MIN_BOX_PX = 48; +function normalizeScreenBox(a, b) { + const x = Math.min(a.x, b.x); + const y = Math.min(a.y, b.y); + let width = Math.abs(b.x - a.x); + let height = Math.abs(b.y - a.y); + if (width < MIN_BOX_PX) width = MIN_BOX_PX; + if (height < MIN_BOX_PX) height = MIN_BOX_PX; + return { x, y, width, height }; +} +function realBoxFromScreenBox(screenBox, localToReal) { + const { x, y, width, height } = screenBox; + const corners = [ + localToReal(x, y), + localToReal(x + width, y), + localToReal(x, y + height), + localToReal(x + width, y + height) + ]; + const xs = corners.map((c) => c.x); + const ys = corners.map((c) => c.y); + const minX = Math.min(...xs); + const maxX = Math.max(...xs); + const minY = Math.min(...ys); + const maxY = Math.max(...ys); + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY + }; +} +var useHierSheetPlacement = ({ + enabled, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent +}) => { + const [state, setState] = useState14({ + isDrawing: false, + anchorLocal: null, + previewLocal: null, + pendingBox: null, + pendingScreenBox: null + }); + const stateRef = useRef12(state); + stateRef.current = state; + const localFromMouse = useCallback12( + (clientX, clientY) => { + const container = containerRef.current; + if (!container) return { x: 0, y: 0 }; + const rect = container.getBoundingClientRect(); + return { x: clientX - rect.left, y: clientY - rect.top }; + }, + [containerRef] + ); + const localToReal = useCallback12( + (localX, localY) => { + const realToScreen = compose9(svgToScreenProjection, realToSvgProjection); + return applyToPoint2(inverse(realToScreen), { x: localX, y: localY }); + }, + [svgToScreenProjection, realToSvgProjection] + ); + const reset = useCallback12(() => { + setState({ + isDrawing: false, + anchorLocal: null, + previewLocal: null, + pendingBox: null, + pendingScreenBox: null + }); + }, []); + const handleMouseMove = useCallback12( + (e) => { + if (!enabled || stateRef.current.pendingBox) return; + const pos = localFromMouse(e.clientX, e.clientY); + if (stateRef.current.isDrawing && stateRef.current.anchorLocal) { + setState((prev) => ({ ...prev, previewLocal: pos })); + } + }, + [enabled, localFromMouse] + ); + const handleMouseDown = useCallback12( + (e) => { + if (!enabled || e.button !== 0 || stateRef.current.pendingBox || isMouseCaptureIgnoredTarget(e.target)) + return; + e.preventDefault(); + e.stopPropagation(); + const pos = localFromMouse(e.clientX, e.clientY); + const current = stateRef.current; + if (!current.isDrawing) { + setState({ + isDrawing: true, + anchorLocal: pos, + previewLocal: pos, + pendingBox: null, + pendingScreenBox: null + }); + return; + } + if (!current.anchorLocal) return; + const screenBox = normalizeScreenBox(current.anchorLocal, pos); + const box = realBoxFromScreenBox(screenBox, localToReal); + setState({ + isDrawing: false, + anchorLocal: null, + previewLocal: null, + pendingBox: box, + pendingScreenBox: screenBox + }); + }, + [enabled, localFromMouse, localToReal] + ); + const handleKeyDown = useCallback12( + (e) => { + if (e.key === "Escape") reset(); + }, + [reset] + ); + const confirmPlacement = useCallback12( + (sheetName, targetSheetId) => { + const box = stateRef.current.pendingBox; + const screenBox = stateRef.current.pendingScreenBox; + if (!box || !screenBox || !sheetName.trim() || !targetSheetId.trim()) + return; + const sheetNamePos = localToReal(screenBox.x + 6, screenBox.y + 14); + const fileNamePos = localToReal( + screenBox.x + 6, + screenBox.y + screenBox.height - 8 + ); + const event = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_hier_sheet_add", + box, + sheet_name: sheetName.trim(), + target_sheet_id: targetSheetId.trim(), + sheet_name_pos: sheetNamePos, + file_name_pos: fileNamePos, + created_at: Date.now(), + in_progress: false + }; + onEditEvent?.(event); + reset(); + }, + [onEditEvent, reset, localToReal] + ); + const cancelPlacement = useCallback12(() => reset(), [reset]); + useEffect19(() => { + if (!enabled) { + reset(); + return; + } + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mousedown", handleMouseDown, { capture: true }); + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mousedown", handleMouseDown, { + capture: true + }); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown, reset]); + return { hierSheetState: state, confirmPlacement, cancelPlacement }; +}; + +// lib/components/WirePreview.tsx +import { applyToPoint as applyToPoint3, compose as compose10 } from "transformation-matrix"; +import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime"; +var WirePreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef +}) => { + if (!state.isDrawing || !state.previewEnd || state.waypoints.length === 0) + return null; + const container = containerRef.current; + if (!container) return null; + if (!realToSvgProjection?.a || isNaN(realToSvgProjection.a) || !svgToScreenProjection?.a || isNaN(svgToScreenProjection.a)) + return null; + const realToScreen = compose10(svgToScreenProjection, realToSvgProjection); + const toScreen = (pt) => applyToPoint3(realToScreen, pt); + const points = [...state.waypoints.map(toScreen), toScreen(state.previewEnd)]; + const d = points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" "); + return /* @__PURE__ */ jsxs8( + "svg", + { + style: { + position: "absolute", + inset: 0, + width: "100%", + height: "100%", + pointerEvents: "none", + zIndex: 200 + }, + children: [ + /* @__PURE__ */ jsx13( + "path", + { + d, + stroke: "#00b4d8", + strokeWidth: 2, + strokeDasharray: "6 3", + fill: "none", + strokeLinecap: "round", + strokeLinejoin: "round" + } + ), + /* @__PURE__ */ jsx13( + "circle", + { + cx: points[0].x, + cy: points[0].y, + r: 5, + fill: "#00b4d8", + opacity: 0.8 + } + ), + /* @__PURE__ */ jsx13( + "circle", + { + cx: points[points.length - 1].x, + cy: points[points.length - 1].y, + r: 4, + fill: "none", + stroke: "#00b4d8", + strokeWidth: 1.5, + opacity: 0.8 + } + ) + ] + } + ); +}; + +// lib/components/BusPreview.tsx +import { applyToPoint as applyToPoint4, compose as compose11 } from "transformation-matrix"; +import { jsx as jsx14 } from "react/jsx-runtime"; +var BusPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef +}) => { + if (!state.isDrawing || !state.previewEnd || state.waypoints.length === 0) + return null; + const container = containerRef.current; + if (!container) return null; + if (!realToSvgProjection?.a || isNaN(realToSvgProjection.a) || !svgToScreenProjection?.a || isNaN(svgToScreenProjection.a)) + return null; + const realToScreen = compose11(svgToScreenProjection, realToSvgProjection); + const toScreen = (pt) => applyToPoint4(realToScreen, pt); + const points = [...state.waypoints.map(toScreen), toScreen(state.previewEnd)]; + const d = points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" "); + return /* @__PURE__ */ jsx14( + "svg", + { + style: { + position: "absolute", + inset: 0, + width: "100%", + height: "100%", + pointerEvents: "none", + zIndex: 200 + }, + children: /* @__PURE__ */ jsx14( + "path", + { + d, + stroke: "#7e3ec0", + strokeWidth: 4, + strokeDasharray: "8 4", + fill: "none", + strokeLinecap: "round", + strokeLinejoin: "round" + } + ) + } + ); +}; + +// lib/components/BusEntryPreview.tsx +import { applyToPoint as applyToPoint5, compose as compose12 } from "transformation-matrix"; +import { jsx as jsx15 } from "react/jsx-runtime"; +var BusEntryPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef +}) => { + if (!state.anchor || !containerRef.current) return null; + if (!realToSvgProjection?.a || !svgToScreenProjection?.a) return null; + const realToScreen = compose12(svgToScreenProjection, realToSvgProjection); + const toScreen = (pt) => applyToPoint5(realToScreen, pt); + const p1 = toScreen(state.anchor); + const p2 = toScreen({ + x: state.anchor.x + BUS_ENTRY_STUB_LEN, + y: state.anchor.y + BUS_ENTRY_STUB_LEN + }); + return /* @__PURE__ */ jsx15( + "svg", + { + style: { + position: "absolute", + inset: 0, + width: "100%", + height: "100%", + pointerEvents: "none", + zIndex: 200 + }, + children: /* @__PURE__ */ jsx15( + "line", + { + x1: p1.x, + y1: p1.y, + x2: p2.x, + y2: p2.y, + stroke: "#7e3ec0", + strokeWidth: 3, + strokeDasharray: "6 3", + strokeLinecap: "round" + } + ) + } + ); +}; + +// lib/components/NoConnectPreview.tsx +import { applyToPoint as applyToPoint6, compose as compose13 } from "transformation-matrix"; +import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime"; +var NoConnectPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef +}) => { + if (!state.center || !containerRef.current) return null; + if (!realToSvgProjection?.a || !svgToScreenProjection?.a) return null; + const realToScreen = compose13(svgToScreenProjection, realToSvgProjection); + const toScreen = (pt) => applyToPoint6(realToScreen, pt); + const { x, y } = state.center; + const d = NO_CONNECT_HALF; + const a1 = toScreen({ x: x - d, y: y - d }); + const a2 = toScreen({ x: x + d, y: y + d }); + const b1 = toScreen({ x: x + d, y: y - d }); + const b2 = toScreen({ x: x - d, y: y + d }); + return /* @__PURE__ */ jsxs9( + "svg", + { + style: { + position: "absolute", + inset: 0, + width: "100%", + height: "100%", + pointerEvents: "none", + zIndex: 200 + }, + children: [ + /* @__PURE__ */ jsx16( + "line", + { + x1: a1.x, + y1: a1.y, + x2: a2.x, + y2: a2.y, + stroke: "#c1271c", + strokeWidth: 2.5, + strokeDasharray: "4 3", + strokeLinecap: "round" + } + ), + /* @__PURE__ */ jsx16( + "line", + { + x1: b1.x, + y1: b1.y, + x2: b2.x, + y2: b2.y, + stroke: "#c1271c", + strokeWidth: 2.5, + strokeDasharray: "4 3", + strokeLinecap: "round" + } + ) + ] + } + ); +}; + +// lib/components/NetLabelPreview.tsx +import { applyToPoint as applyToPoint7, compose as compose14 } from "transformation-matrix"; +import { useRef as useRef13, useEffect as useEffect20 } from "react"; +import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime"; +var NetLabelPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm, + onCancel +}) => { + const inputRef = useRef13(null); + useEffect20(() => { + if (state.pendingPos && inputRef.current) { + inputRef.current.focus(); + inputRef.current.value = ""; + } + }, [state.pendingPos]); + const container = containerRef.current; + if (!container) return null; + if (!realToSvgProjection?.a || isNaN(realToSvgProjection.a) || !svgToScreenProjection?.a || isNaN(svgToScreenProjection.a)) + return null; + const realToScreen = compose14(svgToScreenProjection, realToSvgProjection); + const toScreen = (pt) => applyToPoint7(realToScreen, pt); + const LabelShape = ({ pos }) => { + const sp = toScreen(pos); + return /* @__PURE__ */ jsxs10("g", { transform: `translate(${sp.x}, ${sp.y})`, opacity: 0.8, children: [ + /* @__PURE__ */ jsx17( + "polygon", + { + points: "0,-10 60,-10 70,0 60,10 0,10", + fill: "none", + stroke: "#00b4d8", + strokeWidth: 1.5, + strokeDasharray: "4 2" + } + ), + /* @__PURE__ */ jsx17("text", { x: 5, y: 4, fontSize: 10, fill: "#00b4d8", fontFamily: "monospace", children: "NET" }), + /* @__PURE__ */ jsx17("circle", { cx: 0, cy: 0, r: 3, fill: "#00b4d8", opacity: 0.6 }) + ] }); + }; + return /* @__PURE__ */ jsxs10(Fragment4, { children: [ + state.previewPos && !state.pendingPos && /* @__PURE__ */ jsx17( + "svg", + { + style: { + position: "absolute", + inset: 0, + width: "100%", + height: "100%", + pointerEvents: "none", + zIndex: 50 + }, + children: /* @__PURE__ */ jsx17(LabelShape, { pos: state.previewPos }) + } + ), + state.pendingPos && (() => { + const sp = toScreen(state.pendingPos); + return /* @__PURE__ */ jsxs10( + "div", + { + style: { + position: "absolute", + left: sp.x + 4, + top: sp.y - 10, + zIndex: 60, + display: "flex", + alignItems: "center", + gap: 4 + }, + children: [ + /* @__PURE__ */ jsx17( + "input", + { + ref: inputRef, + type: "text", + placeholder: "Net name\u2026", + style: { + background: "#1a1a2e", + border: "1.5px solid #00b4d8", + color: "#fff", + padding: "3px 8px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 12, + outline: "none", + width: 120 + }, + onKeyDown: (e) => { + if (e.key === "Enter") { + onConfirm(e.target.value); + } else if (e.key === "Escape") { + onCancel(); + } + e.stopPropagation(); + } + } + ), + /* @__PURE__ */ jsx17( + "button", + { + type: "button", + style: { + background: "#00b4d8", + border: "none", + color: "#000", + padding: "3px 8px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 11, + cursor: "pointer" + }, + onClick: () => { + if (inputRef.current) onConfirm(inputRef.current.value); + }, + children: "\u2713" + } + ) + ] + } + ); + })() + ] }); +}; + +// lib/components/GlobalLabelPreview.tsx +import { applyToPoint as applyToPoint8, compose as compose15 } from "transformation-matrix"; +import { useRef as useRef14, useEffect as useEffect21 } from "react"; +import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime"; +var GLOBAL_COLOR = "#c1271c"; +var GlobalLabelPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm, + onCancel +}) => { + const inputRef = useRef14(null); + useEffect21(() => { + if (state.pendingPos && inputRef.current) { + inputRef.current.focus(); + inputRef.current.value = ""; + } + }, [state.pendingPos]); + const container = containerRef.current; + if (!container) return null; + if (!realToSvgProjection?.a || isNaN(realToSvgProjection.a) || !svgToScreenProjection?.a || isNaN(svgToScreenProjection.a)) + return null; + const realToScreen = compose15(svgToScreenProjection, realToSvgProjection); + const toScreen = (pt) => applyToPoint8(realToScreen, pt); + const LabelShape = ({ pos }) => { + const sp = toScreen(pos); + return /* @__PURE__ */ jsxs11("g", { transform: `translate(${sp.x}, ${sp.y})`, opacity: 0.85, children: [ + /* @__PURE__ */ jsx18( + "polygon", + { + points: "0,-10 60,-10 70,0 60,10 0,10", + fill: "none", + stroke: GLOBAL_COLOR, + strokeWidth: 1.5, + strokeDasharray: "4 2" + } + ), + /* @__PURE__ */ jsx18( + "text", + { + x: 5, + y: 4, + fontSize: 10, + fill: GLOBAL_COLOR, + fontFamily: "monospace", + children: "GLOBAL" + } + ), + /* @__PURE__ */ jsx18("circle", { cx: 0, cy: 0, r: 3, fill: GLOBAL_COLOR, opacity: 0.6 }) + ] }); + }; + return /* @__PURE__ */ jsxs11(Fragment5, { children: [ + state.previewPos && !state.pendingPos && /* @__PURE__ */ jsx18( + "svg", + { + style: { + position: "absolute", + inset: 0, + width: "100%", + height: "100%", + pointerEvents: "none", + zIndex: 50 + }, + children: /* @__PURE__ */ jsx18(LabelShape, { pos: state.previewPos }) + } + ), + state.pendingPos && (() => { + const sp = toScreen(state.pendingPos); + return /* @__PURE__ */ jsxs11( + "div", + { + style: { + position: "absolute", + left: sp.x + 4, + top: sp.y - 10, + zIndex: 60, + display: "flex", + alignItems: "center", + gap: 4 + }, + children: [ + /* @__PURE__ */ jsx18( + "input", + { + ref: inputRef, + type: "text", + placeholder: "Global net name\u2026", + style: { + background: "#1a1a2e", + border: `1.5px solid ${GLOBAL_COLOR}`, + color: "#fff", + padding: "3px 8px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 12, + outline: "none", + width: 140 + }, + onKeyDown: (e) => { + if (e.key === "Enter") { + onConfirm(e.target.value); + } else if (e.key === "Escape") { + onCancel(); + } + e.stopPropagation(); + } + } + ), + /* @__PURE__ */ jsx18( + "button", + { + type: "button", + style: { + background: GLOBAL_COLOR, + border: "none", + color: "#fff", + padding: "3px 8px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 11, + cursor: "pointer" + }, + onClick: () => { + if (inputRef.current) onConfirm(inputRef.current.value); + }, + children: "\u2713" + } + ) + ] + } + ); + })() + ] }); +}; + +// lib/components/HierSheetPreview.tsx +import { applyToPoint as applyToPoint9, compose as compose16 } from "transformation-matrix"; +import { useEffect as useEffect22, useMemo as useMemo5, useRef as useRef15, useState as useState15 } from "react"; +import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime"; +var SHEET_STROKE = "#0050d8"; +var SHEET_FILL = "rgba(234, 242, 255, 0.35)"; +var NAME_COLOR = "#006464"; +var FILE_COLOR = "#725600"; +var SheetSymbolShape = ({ + screenBox, + dashed = true +}) => { + const { x, y, width, height } = screenBox; + const pinLen = Math.min(10, width * 0.08); + const midY = y + height / 2; + return /* @__PURE__ */ jsxs12("g", { opacity: dashed ? 0.85 : 1, children: [ + /* @__PURE__ */ jsx19( + "rect", + { + x, + y, + width, + height, + fill: SHEET_FILL, + stroke: SHEET_STROKE, + strokeWidth: 1.8, + strokeDasharray: dashed ? "6 3" : void 0 + } + ), + /* @__PURE__ */ jsx19( + "line", + { + x1: x - pinLen, + y1: midY - height * 0.2, + x2: x, + y2: midY - height * 0.2, + stroke: SHEET_STROKE, + strokeWidth: 1.4 + } + ), + /* @__PURE__ */ jsx19( + "line", + { + x1: x - pinLen, + y1: midY + height * 0.2, + x2: x, + y2: midY + height * 0.2, + stroke: SHEET_STROKE, + strokeWidth: 1.4 + } + ), + /* @__PURE__ */ jsx19( + "line", + { + x1: x + width, + y1: midY, + x2: x + width + pinLen, + y2: midY, + stroke: SHEET_STROKE, + strokeWidth: 1.4 + } + ), + /* @__PURE__ */ jsx19( + "text", + { + x: x + 6, + y: y + 14, + fontSize: 11, + fill: NAME_COLOR, + fontFamily: "monospace", + fontWeight: 600, + children: "Sheet name" + } + ), + /* @__PURE__ */ jsx19( + "text", + { + x: x + 6, + y: y + height - 8, + fontSize: 10, + fill: FILE_COLOR, + fontFamily: "monospace", + children: "Target sheet" + } + ) + ] }); +}; +function pendingBoxKey(box) { + return `${box.x},${box.y},${box.width},${box.height}`; +} +var HierSheetPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + sheetTargets, + activeSheetId, + onConfirm, + onCancel +}) => { + const [sheetName, setSheetName] = useState15(""); + const [targetSheetId, setTargetSheetId] = useState15(""); + const initializedKeyRef = useRef15(null); + const nameInputRef = useRef15(null); + const targets = useMemo5( + () => sheetTargets.filter((t) => t.id !== activeSheetId), + [sheetTargets, activeSheetId] + ); + useEffect22(() => { + if (!state.pendingBox) { + initializedKeyRef.current = null; + setSheetName(""); + setTargetSheetId(""); + return; + } + const key = pendingBoxKey(state.pendingBox); + if (initializedKeyRef.current === key) return; + initializedKeyRef.current = key; + setSheetName(""); + setTargetSheetId(targets[0]?.id ?? ""); + requestAnimationFrame(() => nameInputRef.current?.focus()); + }, [state.pendingBox, targets]); + const container = containerRef.current; + if (!container) return null; + if (!realToSvgProjection?.a || isNaN(realToSvgProjection.a) || !svgToScreenProjection?.a || isNaN(svgToScreenProjection.a)) { + return null; + } + const realToScreen = compose16(svgToScreenProjection, realToSvgProjection); + const boxToScreen = (box) => { + const p1 = applyToPoint9(realToScreen, { x: box.x, y: box.y }); + const p2 = applyToPoint9(realToScreen, { + x: box.x + box.width, + y: box.y + box.height + }); + return { + x: Math.min(p1.x, p2.x), + y: Math.min(p1.y, p2.y), + width: Math.abs(p2.x - p1.x), + height: Math.abs(p2.y - p1.y) + }; + }; + const previewScreenBox = state.isDrawing && state.anchorLocal && state.previewLocal ? normalizeScreenBox(state.anchorLocal, state.previewLocal) : null; + const committedScreenBox = state.pendingScreenBox ?? (state.pendingBox ? boxToScreen(state.pendingBox) : null); + const dialogAnchor = committedScreenBox; + const submit = () => { + if (!sheetName.trim() || !targetSheetId.trim()) return; + onConfirm(sheetName, targetSheetId); + }; + return /* @__PURE__ */ jsxs12(Fragment6, { children: [ + previewScreenBox && /* @__PURE__ */ jsx19( + "svg", + { + style: { + position: "absolute", + inset: 0, + width: "100%", + height: "100%", + pointerEvents: "none", + zIndex: 50 + }, + children: /* @__PURE__ */ jsx19(SheetSymbolShape, { screenBox: previewScreenBox }) + } + ), + state.pendingBox && committedScreenBox && /* @__PURE__ */ jsx19( + "svg", + { + style: { + position: "absolute", + inset: 0, + width: "100%", + height: "100%", + pointerEvents: "none", + zIndex: 51 + }, + children: /* @__PURE__ */ jsx19(SheetSymbolShape, { screenBox: committedScreenBox, dashed: false }) + } + ), + state.pendingBox && dialogAnchor && /* @__PURE__ */ jsxs12( + "div", + { + style: { + position: "absolute", + left: dialogAnchor.x + 8, + top: dialogAnchor.y + dialogAnchor.height + 8, + zIndex: 60, + display: "flex", + flexDirection: "column", + gap: 6, + padding: "8px 10px", + background: "#1a1a2e", + border: `1.5px solid ${SHEET_STROKE}`, + borderRadius: 6, + minWidth: 220 + }, + onMouseDown: (e) => e.stopPropagation(), + "data-schematic-ignore-mouse-capture": true, + children: [ + /* @__PURE__ */ jsxs12( + "label", + { + style: { fontSize: 10, color: NAME_COLOR, fontFamily: "monospace" }, + children: [ + "Sheet name", + /* @__PURE__ */ jsx19( + "input", + { + ref: nameInputRef, + type: "text", + value: sheetName, + placeholder: "e.g. MCU_Sub", + onChange: (e) => setSheetName(e.target.value), + style: { + display: "block", + marginTop: 3, + width: "100%", + background: "#0f0f1a", + border: `1px solid ${NAME_COLOR}`, + color: "#fff", + padding: "4px 8px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 12, + outline: "none" + }, + onKeyDown: (e) => { + if (e.key === "Enter") { + submit(); + } else if (e.key === "Escape") { + onCancel(); + } + e.stopPropagation(); + } + } + ) + ] + } + ), + /* @__PURE__ */ jsxs12( + "label", + { + style: { fontSize: 10, color: FILE_COLOR, fontFamily: "monospace" }, + children: [ + "Target sheet (file name)", + /* @__PURE__ */ jsx19( + "select", + { + value: targetSheetId, + disabled: targets.length === 0, + onChange: (e) => setTargetSheetId(e.target.value), + style: { + display: "block", + marginTop: 3, + width: "100%", + background: "#0f0f1a", + border: `1px solid ${FILE_COLOR}`, + color: "#fff", + padding: "4px 8px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 12, + outline: "none" + }, + children: targets.length === 0 ? /* @__PURE__ */ jsx19("option", { value: "", children: "No other sheets" }) : targets.map((t) => /* @__PURE__ */ jsxs12("option", { value: t.id, children: [ + t.title, + " (", + t.id, + ")" + ] }, t.id)) + } + ) + ] + } + ), + /* @__PURE__ */ jsxs12("div", { style: { display: "flex", gap: 6, justifyContent: "flex-end" }, children: [ + /* @__PURE__ */ jsx19( + "button", + { + type: "button", + style: { + background: "transparent", + border: "1px solid #666", + color: "#ccc", + padding: "3px 10px", + borderRadius: 4, + fontSize: 11, + cursor: "pointer" + }, + onClick: onCancel, + children: "Cancel" + } + ), + /* @__PURE__ */ jsx19( + "button", + { + type: "button", + disabled: targets.length === 0 || !sheetName.trim(), + style: { + background: SHEET_STROKE, + border: "none", + color: "#fff", + padding: "3px 10px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 11, + cursor: targets.length === 0 || !sheetName.trim() ? "not-allowed" : "pointer", + opacity: targets.length === 0 || !sheetName.trim() ? 0.5 : 1 + }, + onClick: submit, + children: "Place sheet" + } + ) + ] }) + ] + } + ) + ] }); +}; + +// lib/hooks/usePowerPortPlacement.ts +import { useCallback as useCallback13, useEffect as useEffect23, useRef as useRef16, useState as useState16 } from "react"; +import { compose as compose17 } from "transformation-matrix"; +var PORT_SNAP_RADIUS_PX3 = 32; +var usePowerPortPlacement = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent +}) => { + const [state, setState] = useState16({ + previewPos: null, + pendingPos: null, + pendingPortId: null + }); + const stateRef = useRef16(state); + stateRef.current = state; + const screenToReal = useCallback13( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) return { x: 0, y: 0 }; + const rect = container.getBoundingClientRect(); + const localX = screenX - rect.left; + const localY = screenY - rect.top; + const realToScreen = compose17(svgToScreenProjection, realToSvgProjection); + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d + }; + }, + [svgToScreenProjection, realToSvgProjection, containerRef] + ); + const snapToPort = useCallback13( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) + return { pos: screenToReal(screenX, screenY), portId: null }; + let closest = null; + const portEls = container.querySelectorAll("[data-schematic-port-id]"); + for (const node of Array.from(portEls)) { + const rect = node.getBoundingClientRect(); + const cx = rect.left + rect.width / 2; + const cy = rect.top + rect.height / 2; + const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2); + if (dist < PORT_SNAP_RADIUS_PX3 && (!closest || dist < closest.dist)) { + const id = node.getAttribute("data-schematic-port-id"); + const portEl = circuitJson.find( + (el) => el.type === "schematic_port" && el.schematic_port_id === id + ); + if (id && portEl?.center) { + closest = { id, dist, center: portEl.center }; + } + } + } + if (closest) return { pos: closest.center, portId: closest.id }; + return { pos: screenToReal(screenX, screenY), portId: null }; + }, + [containerRef, circuitJson, screenToReal] + ); + const handleMouseMove = useCallback13( + (e) => { + if (!enabled || stateRef.current.pendingPos) return; + const { pos } = snapToPort(e.clientX, e.clientY); + setState((prev) => ({ ...prev, previewPos: pos })); + }, + [enabled, snapToPort] + ); + const handleMouseDown = useCallback13( + (e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return; + if (stateRef.current.pendingPos) return; + e.preventDefault(); + e.stopPropagation(); + const { pos, portId } = snapToPort(e.clientX, e.clientY); + setState((prev) => ({ + ...prev, + pendingPos: pos, + pendingPortId: portId, + previewPos: null + })); + }, + [enabled, snapToPort] + ); + const handleKeyDown = useCallback13((e) => { + if (e.key === "Escape") { + setState({ + previewPos: null, + pendingPos: null, + pendingPortId: null + }); + } + }, []); + const confirmPlacement = useCallback13( + (netName) => { + const current = stateRef.current; + if (!current.pendingPos || !netName.trim()) return; + const event = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_power_port_add", + position: current.pendingPos, + net_name: netName.trim(), + schematic_port_id: current.pendingPortId ?? void 0, + anchor_side: "bottom", + created_at: Date.now(), + in_progress: false + }; + onEditEvent?.(event); + setState({ previewPos: null, pendingPos: null, pendingPortId: null }); + }, + [onEditEvent] + ); + const cancelPlacement = useCallback13(() => { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }); + }, []); + useEffect23(() => { + if (!enabled) { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }); + return; + } + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mousedown", handleMouseDown, { capture: true }); + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mousedown", handleMouseDown, { + capture: true + }); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown]); + return { powerPortState: state, confirmPlacement, cancelPlacement }; +}; + +// lib/components/PowerPortPreview.tsx +import { applyToPoint as applyToPoint10, compose as compose18 } from "transformation-matrix"; +import { useRef as useRef17, useEffect as useEffect24 } from "react"; +import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime"; +var POWER_COLOR = "#c1271c"; +var PowerPortPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm, + onCancel +}) => { + const inputRef = useRef17(null); + useEffect24(() => { + if (state.pendingPos && inputRef.current) { + inputRef.current.focus(); + inputRef.current.value = "VCC"; + } + }, [state.pendingPos]); + const container = containerRef.current; + if (!container) return null; + if (!realToSvgProjection?.a || isNaN(realToSvgProjection.a) || !svgToScreenProjection?.a || isNaN(svgToScreenProjection.a)) { + return null; + } + const realToScreen = compose18(svgToScreenProjection, realToSvgProjection); + const toScreen = (pt) => applyToPoint10(realToScreen, pt); + const PowerShape = ({ pos }) => { + const sp = toScreen(pos); + return /* @__PURE__ */ jsxs13("g", { transform: `translate(${sp.x}, ${sp.y})`, opacity: 0.85, children: [ + /* @__PURE__ */ jsx20( + "line", + { + x1: 0, + y1: 0, + x2: 0, + y2: 18, + stroke: POWER_COLOR, + strokeWidth: 1.5 + } + ), + /* @__PURE__ */ jsx20( + "polygon", + { + points: "0,-14 -8,2 8,2", + fill: "none", + stroke: POWER_COLOR, + strokeWidth: 1.5 + } + ), + /* @__PURE__ */ jsx20( + "text", + { + x: -10, + y: -18, + fontSize: 9, + fill: POWER_COLOR, + fontFamily: "monospace", + children: "PWR" + } + ), + /* @__PURE__ */ jsx20("circle", { cx: 0, cy: 0, r: 3, fill: POWER_COLOR, opacity: 0.6 }) + ] }); + }; + return /* @__PURE__ */ jsxs13(Fragment7, { children: [ + state.previewPos && !state.pendingPos && /* @__PURE__ */ jsx20( + "svg", + { + style: { + position: "absolute", + inset: 0, + width: "100%", + height: "100%", + pointerEvents: "none", + zIndex: 50 + }, + children: /* @__PURE__ */ jsx20(PowerShape, { pos: state.previewPos }) + } + ), + state.pendingPos && (() => { + const sp = toScreen(state.pendingPos); + return /* @__PURE__ */ jsxs13( + "div", + { + style: { + position: "absolute", + left: sp.x + 4, + top: sp.y - 36, + zIndex: 60, + display: "flex", + alignItems: "center", + gap: 4 + }, + children: [ + /* @__PURE__ */ jsx20( + "input", + { + ref: inputRef, + type: "text", + placeholder: "Net name (VCC, +3V3)\u2026", + style: { + background: "#1a1a2e", + border: `1.5px solid ${POWER_COLOR}`, + color: "#fff", + padding: "3px 8px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 12, + outline: "none", + width: 140 + }, + onKeyDown: (e) => { + if (e.key === "Enter") { + onConfirm(e.target.value); + } else if (e.key === "Escape") { + onCancel(); + } + e.stopPropagation(); + } + } + ), + /* @__PURE__ */ jsx20( + "button", + { + type: "button", + style: { + background: POWER_COLOR, + border: "none", + color: "#fff", + padding: "3px 8px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 11, + cursor: "pointer" + }, + onClick: () => { + if (inputRef.current) onConfirm(inputRef.current.value); + }, + children: "\u2713" + } + ) + ] + } + ); + })() + ] }); +}; + +// lib/hooks/useGroundPortPlacement.ts +import { useCallback as useCallback14, useEffect as useEffect25, useRef as useRef18, useState as useState17 } from "react"; +import { compose as compose19 } from "transformation-matrix"; +var PORT_SNAP_RADIUS_PX4 = 32; +var useGroundPortPlacement = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent +}) => { + const [state, setState] = useState17({ + previewPos: null, + pendingPos: null, + pendingPortId: null + }); + const stateRef = useRef18(state); + stateRef.current = state; + const screenToReal = useCallback14( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) return { x: 0, y: 0 }; + const rect = container.getBoundingClientRect(); + const localX = screenX - rect.left; + const localY = screenY - rect.top; + const realToScreen = compose19(svgToScreenProjection, realToSvgProjection); + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d + }; + }, + [svgToScreenProjection, realToSvgProjection, containerRef] + ); + const snapToPort = useCallback14( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) + return { pos: screenToReal(screenX, screenY), portId: null }; + let closest = null; + const portEls = container.querySelectorAll("[data-schematic-port-id]"); + for (const node of Array.from(portEls)) { + const rect = node.getBoundingClientRect(); + const cx = rect.left + rect.width / 2; + const cy = rect.top + rect.height / 2; + const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2); + if (dist < PORT_SNAP_RADIUS_PX4 && (!closest || dist < closest.dist)) { + const id = node.getAttribute("data-schematic-port-id"); + const portEl = circuitJson.find( + (el) => el.type === "schematic_port" && el.schematic_port_id === id + ); + if (id && portEl?.center) { + closest = { id, dist, center: portEl.center }; + } + } + } + if (closest) return { pos: closest.center, portId: closest.id }; + return { pos: screenToReal(screenX, screenY), portId: null }; + }, + [containerRef, circuitJson, screenToReal] + ); + const handleMouseMove = useCallback14( + (e) => { + if (!enabled || stateRef.current.pendingPos) return; + const { pos } = snapToPort(e.clientX, e.clientY); + setState((prev) => ({ ...prev, previewPos: pos })); + }, + [enabled, snapToPort] + ); + const handleMouseDown = useCallback14( + (e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return; + if (stateRef.current.pendingPos) return; + e.preventDefault(); + e.stopPropagation(); + const { pos, portId } = snapToPort(e.clientX, e.clientY); + setState((prev) => ({ + ...prev, + pendingPos: pos, + pendingPortId: portId, + previewPos: null + })); + }, + [enabled, snapToPort] + ); + const handleKeyDown = useCallback14((e) => { + if (e.key === "Escape") { + setState({ + previewPos: null, + pendingPos: null, + pendingPortId: null + }); + } + }, []); + const confirmPlacement = useCallback14( + (netName) => { + const current = stateRef.current; + if (!current.pendingPos || !netName.trim()) return; + const event = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_ground_port_add", + position: current.pendingPos, + net_name: netName.trim(), + schematic_port_id: current.pendingPortId ?? void 0, + anchor_side: "top", + created_at: Date.now(), + in_progress: false + }; + onEditEvent?.(event); + setState({ previewPos: null, pendingPos: null, pendingPortId: null }); + }, + [onEditEvent] + ); + const cancelPlacement = useCallback14(() => { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }); + }, []); + useEffect25(() => { + if (!enabled) { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }); + return; + } + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mousedown", handleMouseDown, { capture: true }); + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mousedown", handleMouseDown, { + capture: true + }); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown]); + return { groundPortState: state, confirmPlacement, cancelPlacement }; +}; + +// lib/components/GroundPortPreview.tsx +import { applyToPoint as applyToPoint11, compose as compose20 } from "transformation-matrix"; +import { useRef as useRef19, useEffect as useEffect26 } from "react"; +import { Fragment as Fragment8, jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime"; +var GND_COLOR = "#5c5c5c"; +var GroundPortPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm, + onCancel +}) => { + const inputRef = useRef19(null); + useEffect26(() => { + if (state.pendingPos && inputRef.current) { + inputRef.current.focus(); + inputRef.current.value = "GND"; + } + }, [state.pendingPos]); + const container = containerRef.current; + if (!container) return null; + if (!realToSvgProjection?.a || isNaN(realToSvgProjection.a) || !svgToScreenProjection?.a || isNaN(svgToScreenProjection.a)) { + return null; + } + const realToScreen = compose20(svgToScreenProjection, realToSvgProjection); + const toScreen = (pt) => applyToPoint11(realToScreen, pt); + const GroundShape = ({ pos }) => { + const sp = toScreen(pos); + return /* @__PURE__ */ jsxs14("g", { transform: `translate(${sp.x}, ${sp.y})`, opacity: 0.85, children: [ + /* @__PURE__ */ jsx21( + "line", + { + x1: 0, + y1: 0, + x2: 0, + y2: 18, + stroke: GND_COLOR, + strokeWidth: 1.5 + } + ), + /* @__PURE__ */ jsx21( + "polygon", + { + points: "0,32 -8,16 8,16", + fill: "none", + stroke: GND_COLOR, + strokeWidth: 1.5 + } + ), + /* @__PURE__ */ jsx21( + "text", + { + x: -10, + y: 42, + fontSize: 9, + fill: GND_COLOR, + fontFamily: "monospace", + children: "GND" + } + ), + /* @__PURE__ */ jsx21("circle", { cx: 0, cy: 0, r: 3, fill: GND_COLOR, opacity: 0.6 }) + ] }); + }; + return /* @__PURE__ */ jsxs14(Fragment8, { children: [ + state.previewPos && !state.pendingPos && /* @__PURE__ */ jsx21( + "svg", + { + style: { + position: "absolute", + inset: 0, + width: "100%", + height: "100%", + pointerEvents: "none", + zIndex: 50 + }, + children: /* @__PURE__ */ jsx21(GroundShape, { pos: state.previewPos }) + } + ), + state.pendingPos && (() => { + const sp = toScreen(state.pendingPos); + return /* @__PURE__ */ jsxs14( + "div", + { + style: { + position: "absolute", + left: sp.x + 4, + top: sp.y - 36, + zIndex: 60, + display: "flex", + alignItems: "center", + gap: 4 + }, + children: [ + /* @__PURE__ */ jsx21( + "input", + { + ref: inputRef, + type: "text", + placeholder: "Net name (GND, AGND)\u2026", + style: { + background: "#1a1a2e", + border: `1.5px solid ${GND_COLOR}`, + color: "#fff", + padding: "3px 8px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 12, + outline: "none", + width: 140 + }, + onKeyDown: (e) => { + if (e.key === "Enter") { + onConfirm(e.target.value); + } else if (e.key === "Escape") { + onCancel(); + } + e.stopPropagation(); + } + } + ), + /* @__PURE__ */ jsx21( + "button", + { + type: "button", + style: { + background: GND_COLOR, + border: "none", + color: "#fff", + padding: "3px 8px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 11, + cursor: "pointer" + }, + onClick: () => { + if (inputRef.current) onConfirm(inputRef.current.value); + }, + children: "\u2713" + } + ) + ] + } + ); + })() + ] }); +}; + +// lib/hooks/useTextNotePlacement.ts +import { useCallback as useCallback15, useEffect as useEffect27, useRef as useRef20, useState as useState18 } from "react"; +import { compose as compose21 } from "transformation-matrix"; +var useTextNotePlacement = ({ + enabled, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent +}) => { + const [state, setState] = useState18({ + previewPos: null, + pendingPos: null + }); + const stateRef = useRef20(state); + stateRef.current = state; + const screenToReal = useCallback15( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) return { x: 0, y: 0 }; + const rect = container.getBoundingClientRect(); + const localX = screenX - rect.left; + const localY = screenY - rect.top; + const realToScreen = compose21(svgToScreenProjection, realToSvgProjection); + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d + }; + }, + [svgToScreenProjection, realToSvgProjection, containerRef] + ); + const handleMouseMove = useCallback15( + (e) => { + if (!enabled || stateRef.current.pendingPos) return; + setState((prev) => ({ + ...prev, + previewPos: screenToReal(e.clientX, e.clientY) + })); + }, + [enabled, screenToReal] + ); + const handleMouseDown = useCallback15( + (e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return; + if (stateRef.current.pendingPos) return; + e.preventDefault(); + e.stopPropagation(); + setState((prev) => ({ + ...prev, + pendingPos: screenToReal(e.clientX, e.clientY), + previewPos: null + })); + }, + [enabled, screenToReal] + ); + const handleKeyDown = useCallback15((e) => { + if (e.key === "Escape") { + setState({ previewPos: null, pendingPos: null }); + } + }, []); + const confirmPlacement = useCallback15( + (text) => { + const current = stateRef.current; + if (!current.pendingPos || !text.trim()) return; + const event = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_text_note_add", + position: current.pendingPos, + text: text.trim(), + anchor: "left", + font_size: 0.2, + color: "#1a1612", + created_at: Date.now(), + in_progress: false + }; + onEditEvent?.(event); + setState({ previewPos: null, pendingPos: null }); + }, + [onEditEvent] + ); + const cancelPlacement = useCallback15(() => { + setState({ previewPos: null, pendingPos: null }); + }, []); + useEffect27(() => { + if (!enabled) { + setState({ previewPos: null, pendingPos: null }); + return; + } + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mousedown", handleMouseDown, { capture: true }); + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mousedown", handleMouseDown, { + capture: true + }); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown]); + return { textNoteState: state, confirmPlacement, cancelPlacement }; +}; + +// lib/components/TextNotePreview.tsx +import { applyToPoint as applyToPoint12, compose as compose22 } from "transformation-matrix"; +import { useRef as useRef21, useEffect as useEffect28 } from "react"; +import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime"; +var NOTE_COLOR = "#1a1612"; +var TextNotePreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm, + onCancel +}) => { + const inputRef = useRef21(null); + useEffect28(() => { + if (state.pendingPos && inputRef.current) { + inputRef.current.focus(); + inputRef.current.value = ""; + } + }, [state.pendingPos]); + const container = containerRef.current; + if (!container) return null; + if (!realToSvgProjection?.a || isNaN(realToSvgProjection.a) || !svgToScreenProjection?.a || isNaN(svgToScreenProjection.a)) { + return null; + } + const realToScreen = compose22(svgToScreenProjection, realToSvgProjection); + const toScreen = (pt) => applyToPoint12(realToScreen, pt); + const NoteShape = ({ pos }) => { + const sp = toScreen(pos); + return /* @__PURE__ */ jsxs15("g", { transform: `translate(${sp.x}, ${sp.y})`, opacity: 0.75, children: [ + /* @__PURE__ */ jsx22( + "text", + { + x: 0, + y: 0, + fontSize: 11, + fill: NOTE_COLOR, + fontFamily: "monospace", + fontStyle: "italic", + children: "Text" + } + ), + /* @__PURE__ */ jsx22("circle", { cx: 0, cy: 0, r: 3, fill: NOTE_COLOR, opacity: 0.4 }) + ] }); + }; + return /* @__PURE__ */ jsxs15(Fragment9, { children: [ + state.previewPos && !state.pendingPos && /* @__PURE__ */ jsx22( + "svg", + { + style: { + position: "absolute", + inset: 0, + width: "100%", + height: "100%", + pointerEvents: "none", + zIndex: 50 + }, + children: /* @__PURE__ */ jsx22(NoteShape, { pos: state.previewPos }) + } + ), + state.pendingPos && (() => { + const sp = toScreen(state.pendingPos); + return /* @__PURE__ */ jsxs15( + "div", + { + style: { + position: "absolute", + left: sp.x + 4, + top: sp.y - 10, + zIndex: 60, + display: "flex", + alignItems: "center", + gap: 4 + }, + children: [ + /* @__PURE__ */ jsx22( + "input", + { + ref: inputRef, + type: "text", + placeholder: "Note text\u2026", + style: { + background: "#1a1a2e", + border: `1.5px solid ${NOTE_COLOR}`, + color: "#fff", + padding: "3px 8px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 12, + outline: "none", + width: 180 + }, + onKeyDown: (e) => { + if (e.key === "Enter") { + onConfirm(e.target.value); + } else if (e.key === "Escape") { + onCancel(); + } + e.stopPropagation(); + } + } + ), + /* @__PURE__ */ jsx22( + "button", + { + type: "button", + style: { + background: NOTE_COLOR, + border: "none", + color: "#fff", + padding: "3px 8px", + borderRadius: 4, + fontFamily: "monospace", + fontSize: 11, + cursor: "pointer" + }, + onClick: () => { + if (inputRef.current) onConfirm(inputRef.current.value); + }, + children: "\u2713" + } + ) + ] + } + ); + })() + ] }); +}; + +// lib/hooks/useTraceDrawing.ts +import { useCallback as useCallback16, useEffect as useEffect29, useRef as useRef22, useState as useState19 } from "react"; +import "transformation-matrix"; + +// lib/utils/computeTraceRoute.ts +function computeTraceRoute(from, to) { + if (Math.abs(from.x - to.x) < 1e-6 || Math.abs(from.y - to.y) < 1e-6) { + return [from, to]; + } + const viaHFirst = { x: to.x, y: from.y }; + const viaVFirst = { x: from.x, y: to.y }; + const lenH = Math.abs(from.x - viaHFirst.x) + Math.abs(from.y - viaHFirst.y) + Math.abs(viaHFirst.x - to.x) + Math.abs(viaHFirst.y - to.y); + const lenV = Math.abs(from.x - viaVFirst.x) + Math.abs(from.y - viaVFirst.y) + Math.abs(viaVFirst.x - to.x) + Math.abs(viaVFirst.y - to.y); + const corner = lenH <= lenV ? viaHFirst : viaVFirst; + return [from, corner, to]; +} + +// lib/hooks/useTraceDrawing.ts +var useTraceDrawing = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent +}) => { + const [state, setState] = useState19({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + previewRoute: [] + }); + const stateRef = useRef22(state); + stateRef.current = state; + const screenToReal = useCallback16( + createScreenToReal( + svgToScreenProjection, + realToSvgProjection, + containerRef + ), + [svgToScreenProjection, realToSvgProjection, containerRef] + ); + const getPortCenter = useCallback16( + (portId) => { + const schematicPortId = resolveSchematicPortId(circuitJson, portId); + if (!schematicPortId) return null; + return getSchematicPortCenter( + circuitJson, + containerRef.current, + schematicPortId, + screenToReal + ); + }, + [circuitJson, containerRef, screenToReal] + ); + const getPortAtScreen = useCallback16( + (screenX, screenY) => getSchematicPortAtScreen( + containerRef.current, + circuitJson, + screenX, + screenY + ), + [circuitJson, containerRef] + ); + const beginTraceAtPort = useCallback16( + (portId) => { + const schematicPortId = resolveSchematicPortId(circuitJson, portId); + if (!schematicPortId) return false; + const center = getPortCenter(schematicPortId); + if (!center) return false; + setState({ + isDrawing: true, + fromPortId: schematicPortId, + previewEnd: center, + previewRoute: [center] + }); + return true; + }, + [circuitJson, getPortCenter] + ); + const finishTraceAtPort = useCallback16( + (portId) => { + const current = stateRef.current; + if (!current.isDrawing || !current.fromPortId) return false; + const schematicPortId = resolveSchematicPortId(circuitJson, portId); + if (!schematicPortId || schematicPortId === current.fromPortId) { + return false; + } + const fromCenter = getPortCenter(current.fromPortId); + const toCenter = getPortCenter(schematicPortId); + if (!fromCenter || !toCenter) return false; + const route = computeTraceRoute(fromCenter, toCenter); + const event = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_wire_add", + from_schematic_port_id: current.fromPortId, + to_schematic_port_id: schematicPortId, + route, + created_at: Date.now(), + in_progress: false + }; + onEditEvent?.(event); + setState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + previewRoute: [] + }); + return true; + }, + [circuitJson, getPortCenter, onEditEvent] + ); + const handlePortMouseDown = useCallback16( + (portId, e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) { + return; + } + e.preventDefault(); + e.stopPropagation(); + const current = stateRef.current; + if (!current.isDrawing) { + beginTraceAtPort(portId); + return; + } + finishTraceAtPort(portId); + }, + [enabled, beginTraceAtPort, finishTraceAtPort] + ); + const handleMouseDown = useCallback16( + (e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) { + return; + } + const current = stateRef.current; + const portId = getPortAtScreen(e.clientX, e.clientY); + if (!current.isDrawing) { + if (!portId) return; + e.preventDefault(); + e.stopPropagation(); + beginTraceAtPort(portId); + return; + } + if (portId && finishTraceAtPort(portId)) { + e.preventDefault(); + e.stopPropagation(); + } + }, + [enabled, getPortAtScreen, beginTraceAtPort, finishTraceAtPort] + ); + const handleMouseMove = useCallback16( + (e) => { + if (!enabled || !stateRef.current.isDrawing || !stateRef.current.fromPortId) { + return; + } + const fromCenter = getPortCenter(stateRef.current.fromPortId); + if (!fromCenter) return; + const hover = screenToReal(e.clientX, e.clientY); + setState((prev) => ({ + ...prev, + previewEnd: hover, + previewRoute: computeTraceRoute(fromCenter, hover) + })); + }, + [enabled, getPortCenter, screenToReal] + ); + const handleKeyDown = useCallback16((e) => { + if (e.key === "Escape" && stateRef.current.isDrawing) { + setState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + previewRoute: [] + }); + } + }, []); + useEffect29(() => { + if (!enabled) { + setState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + previewRoute: [] + }); + return; + } + window.addEventListener("mousedown", handleMouseDown, { capture: true }); + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("mousedown", handleMouseDown, { + capture: true + }); + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [enabled, handleMouseDown, handleMouseMove, handleKeyDown]); + const wireDrawingState = { + isDrawing: state.isDrawing, + fromPortId: state.fromPortId, + previewEnd: state.previewEnd, + waypoints: state.previewRoute + }; + return { traceDrawingState: state, wireDrawingState, handlePortMouseDown }; +}; + +// lib/hooks/useComponentPlacement.ts +import { useCallback as useCallback17, useEffect as useEffect30, useRef as useRef23, useState as useState20 } from "react"; +import { compose as compose23 } from "transformation-matrix"; +var useComponentPlacement = ({ + enabled, + componentKind, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent +}) => { + const [state, setState] = useState20({ + previewPos: null, + rotation: 0 + }); + const stateRef = useRef23(state); + stateRef.current = state; + const kindRef = useRef23(componentKind); + kindRef.current = componentKind; + const screenToReal = useCallback17( + (screenX, screenY) => { + const container = containerRef.current; + if (!container) return { x: 0, y: 0 }; + const rect = container.getBoundingClientRect(); + const localX = screenX - rect.left; + const localY = screenY - rect.top; + const realToScreen = compose23(svgToScreenProjection, realToSvgProjection); + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d + }; + }, + [svgToScreenProjection, realToSvgProjection, containerRef] + ); + const placeAt = useCallback17( + (position) => { + const event = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_component_add", + position, + component_kind: kindRef.current, + rotation: stateRef.current.rotation, + created_at: Date.now(), + in_progress: false + }; + onEditEvent?.(event); + }, + [onEditEvent] + ); + const handleMouseMove = useCallback17( + (e) => { + if (!enabled) return; + setState((prev) => ({ + ...prev, + previewPos: screenToReal(e.clientX, e.clientY) + })); + }, + [enabled, screenToReal] + ); + const handleMouseDown = useCallback17( + (e) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return; + e.preventDefault(); + e.stopPropagation(); + placeAt(screenToReal(e.clientX, e.clientY)); + }, + [enabled, screenToReal, placeAt] + ); + const handleKeyDown = useCallback17( + (e) => { + if (!enabled) return; + if (e.key === "r" || e.key === "R") { + if (e.metaKey || e.ctrlKey || e.altKey) return; + e.preventDefault(); + setState((prev) => ({ + ...prev, + rotation: (prev.rotation + 90) % 360 + })); + } + }, + [enabled] + ); + useEffect30(() => { + if (!enabled) { + setState({ previewPos: null, rotation: 0 }); + return; + } + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mousedown", handleMouseDown, { capture: true }); + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mousedown", handleMouseDown, { + capture: true + }); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown]); + return { componentPlacementState: state }; +}; + +// lib/components/ComponentPlacementPreview.tsx +import { jsx as jsx23 } from "react/jsx-runtime"; +var KIND_LABEL = { + resistor: "R", + capacitor: "C", + inductor: "L" +}; +function ComponentPlacementPreview({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + componentKind +}) { + if (!state.previewPos) return null; + const container = containerRef.current; + if (!container) return null; + const realToScreen = { + a: realToSvgProjection.a * svgToScreenProjection.a, + d: realToSvgProjection.d * svgToScreenProjection.d, + e: realToSvgProjection.e * svgToScreenProjection.a + svgToScreenProjection.e, + f: realToSvgProjection.f * svgToScreenProjection.d + svgToScreenProjection.f + }; + const sx = state.previewPos.x * realToScreen.a + realToScreen.e; + const sy = state.previewPos.y * realToScreen.d + realToScreen.f; + const label = KIND_LABEL[componentKind] ?? "?"; + return /* @__PURE__ */ jsx23( + "div", + { + className: "pointer-events-none absolute z-20", + style: { + left: sx, + top: sy, + transform: `translate(-50%, -50%) rotate(${state.rotation}deg)` + }, + children: /* @__PURE__ */ jsx23( + "div", + { + className: "rounded border border-dashed px-2 py-1 font-mono text-[11px]", + style: { + borderColor: "#1f6feb", + color: "#1f6feb", + background: "rgba(31, 111, 235, 0.08)" + }, + children: label + } + ) + } + ); +} + +// lib/components/SchematicViewer.tsx +import { jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime"; +var SchematicViewer = ({ + circuitJson, + containerStyle, + editEvents: unappliedEditEvents = [], + onEditEvent, + defaultEditMode = false, + debugGrid = false, + editingEnabled = false, + debug: debug3 = false, + clickToInteractEnabled = false, + colorOverrides, + spiceSimulationEnabled = false, + disableGroups = false, + netHoverHighlightEnabled = true, + onSchematicComponentClicked, + showSchematicPorts = false, + onSchematicPortClicked, + onSchematicSheetChange, + css, + className, + toolMode = "select", + onWireAdded, + onBusAdded, + onBusEntryAdded, + onNoConnectAdded, + onNetLabelAdded, + onGlobalLabelAdded, + onHierSheetAdded, + onPowerPortAdded, + onGroundPortAdded, + onTextNoteAdded, + onComponentAdded, + placementComponentKind = "resistor", + hierSheetTargets = [], + activeSheetId, + allowComponentEdit = false, + allowCanvasPan = true +}) => { + if (debug3) { + enableDebug(); + } + const [showSpiceOverlay, setShowSpiceOverlay] = useState21(false); + const [spiceSimOptions, setSpiceSimOptions] = useState21({ + showVoltage: true, + showCurrent: false, + startTime: 0, + // in ms + duration: 20 + // in ms + }); + const getCircuitHash = (circuitJson2) => { + return `${circuitJson2?.length || 0}_${circuitJson2?.editCount || 0}`; + }; + const circuitJsonKey = useMemo6( + () => getCircuitHash(circuitJson), + [circuitJson] + ); + const schematicSheets = useMemo6(() => { + try { + return circuitJson.filter((elm) => elm?.type === "schematic_sheet").slice().sort((a, b) => (a.sheet_index ?? 0) - (b.sheet_index ?? 0)); + } catch (err) { + console.error("Failed to derive schematic sheets", err); + return []; + } + }, [circuitJsonKey]); + const hasMultipleSheets = schematicSheets.length > 1; + const defaultSheetId = schematicSheets[0]?.schematic_sheet_id; + const [selectedSheetId, setSelectedSheetId] = useState21( + () => { + const stored = getStoredString(STORAGE_KEYS.SELECTED_SCHEMATIC_SHEET); + if (stored && schematicSheets.some((s) => s.schematic_sheet_id === stored)) { + return stored; + } + return defaultSheetId; + } + ); + useEffect31(() => { + const stillExists = selectedSheetId !== void 0 && schematicSheets.some((s) => s.schematic_sheet_id === selectedSheetId); + if (!stillExists) { + setSelectedSheetId(defaultSheetId); + } + }, [circuitJsonKey]); + const selectedSchematicSheetId = hasMultipleSheets ? selectedSheetId ?? defaultSheetId : void 0; + const handleSelectSheet = useCallback18( + (sheetId) => { + setSelectedSheetId(sheetId); + setStoredString(STORAGE_KEYS.SELECTED_SCHEMATIC_SHEET, sheetId); + onSchematicSheetChange?.(sheetId); + }, + [onSchematicSheetChange] + ); + const spiceString = useMemo6(() => { + if (!spiceSimulationEnabled) return null; + try { + return getSpiceFromCircuitJson(circuitJson, spiceSimOptions); + } catch (e) { + console.error("Failed to generate SPICE string", e); + return null; + } + }, [ + circuitJsonKey, + spiceSimulationEnabled, + spiceSimOptions.startTime, + spiceSimOptions.duration + ]); + const [hasSpiceSimRun, setHasSpiceSimRun] = useState21(false); + useEffect31(() => { + setHasSpiceSimRun(false); + }, [circuitJsonKey]); + useEffect31(() => { + const onKeyDown = (e) => { + if (e.code !== "Space") return; + const t = e.target; + if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) + return; + setSpacePanHeld(true); + }; + const onKeyUp = (e) => { + if (e.code === "Space") setSpacePanHeld(false); + }; + window.addEventListener("keydown", onKeyDown); + window.addEventListener("keyup", onKeyUp); + return () => { + window.removeEventListener("keydown", onKeyDown); + window.removeEventListener("keyup", onKeyUp); + setSpacePanHeld(false); + }; + }, []); + const { + plotData, + nodes, + isLoading: isSpiceSimLoading, + error: spiceSimError + } = useSpiceSimulation(hasSpiceSimRun ? spiceString : null); + const [editModeEnabled, setEditModeEnabled] = useState21(defaultEditMode); + const effectiveEditMode = toolMode === "select" && editModeEnabled; + useEffect31(() => { + if (toolMode === "draw_wire" || toolMode === "draw_bus" || toolMode === "draw_bus_entry" || toolMode === "draw_no_connect" || toolMode === "draw_net_label" || toolMode === "draw_global_label" || toolMode === "draw_hier_sheet" || toolMode === "draw_power_port" || toolMode === "draw_ground_port" || toolMode === "draw_text_note") { + setEditModeEnabled(false); + } else if (toolMode === "select" && defaultEditMode) { + setEditModeEnabled(true); + } else { + setEditModeEnabled(false); + } + }, [toolMode, defaultEditMode]); + const [snapToGrid, setSnapToGrid] = useState21(true); + const [showGridInternal, setShowGridInternal] = useState21(false); + const showGrid = debugGrid || showGridInternal; + const [isInteractionEnabled, setIsInteractionEnabled] = useState21( + !clickToInteractEnabled + ); + const [showViewMenu, setShowViewMenu] = useState21(false); + const [showSchematicGroups, setShowSchematicGroups] = useState21(() => { + if (disableGroups) return false; + return getStoredBoolean("schematic_viewer_show_groups", false); + }); + const [isHoveringClickableComponent, setIsHoveringClickableComponent] = useState21(false); + const hoveringComponentsRef = useRef24(/* @__PURE__ */ new Set()); + const handleComponentHoverChange = useCallback18( + (componentId, isHovering) => { + if (isHovering) { + hoveringComponentsRef.current.add(componentId); + } else { + hoveringComponentsRef.current.delete(componentId); + } + setIsHoveringClickableComponent(hoveringComponentsRef.current.size > 0); + }, + [] + ); + const [isHoveringClickablePort, setIsHoveringClickablePort] = useState21(false); + const hoveringPortsRef = useRef24(/* @__PURE__ */ new Set()); + const handlePortHoverChange = useCallback18( + (portId, isHovering) => { + if (isHovering) { + hoveringPortsRef.current.add(portId); + } else { + hoveringPortsRef.current.delete(portId); + } + setIsHoveringClickablePort(hoveringPortsRef.current.size > 0); + }, + [] + ); + const svgDivRef = useRef24(null); + const touchStartRef = useRef24(null); + const schematicComponentIds = useMemo6(() => { + try { + const components = su7(circuitJson).schematic_component?.list() ?? []; + return components.filter( + (component) => !selectedSchematicSheetId || component.schematic_sheet_id === selectedSchematicSheetId + ).map((component) => component.schematic_component_id); + } catch (err) { + console.error("Failed to derive schematic component ids", err); + return []; + } + }, [circuitJsonKey, circuitJson, selectedSchematicSheetId]); + const schematicPortsInfo = useMemo6(() => { + if (!showSchematicPorts) return []; + try { + const ports = (su7(circuitJson).schematic_port?.list() ?? []).filter( + (port) => !selectedSchematicSheetId || port.schematic_sheet_id === selectedSchematicSheetId + ); + return ports.map((port) => { + const sourcePort = su7(circuitJson).source_port.get(port.source_port_id); + const sourceComponent = sourcePort?.source_component_id ? su7(circuitJson).source_component.get(sourcePort.source_component_id) : null; + const componentName = sourceComponent?.name ?? "?"; + const pinLabel = port.display_pin_label ?? sourcePort?.pin_number ?? sourcePort?.name ?? "?"; + return { + portId: port.schematic_port_id, + label: `${componentName}.${pinLabel}` + }; + }); + } catch (err) { + console.error("Failed to derive schematic port info", err); + return []; + } + }, [ + circuitJsonKey, + circuitJson, + showSchematicPorts, + selectedSchematicSheetId + ]); + const handleTouchStart = (e) => { + const touch = e.touches[0]; + touchStartRef.current = { + x: touch.clientX, + y: touch.clientY + }; + }; + const handleTouchEnd = (e) => { + const touch = e.changedTouches[0]; + const start = touchStartRef.current; + if (!start) return; + const deltaX = Math.abs(touch.clientX - start.x); + const deltaY = Math.abs(touch.clientY - start.y); + if (deltaX < 10 && deltaY < 10) { + e.preventDefault(); + setIsInteractionEnabled(true); + } + touchStartRef.current = null; + }; + const [internalEditEvents, setInternalEditEvents] = useState21([]); + const circuitJsonRef = useRef24(circuitJson); + useEffect31(() => { + const circuitHash = getCircuitHash(circuitJson); + const circuitHashRef = getCircuitHash(circuitJsonRef.current); + if (circuitHash !== circuitHashRef) { + setInternalEditEvents([]); + circuitJsonRef.current = circuitJson; + } + }, [circuitJson]); + const panPolicyRef = useRef24({ toolMode, allowComponentEdit, allowCanvasPan }); + panPolicyRef.current = { toolMode, allowComponentEdit, allowCanvasPan }; + const onSetSvgTransform = useCallback18( + (transform) => { + if (!svgDivRef.current) return; + svgDivRef.current.style.transform = transformToString(transform); + }, + [] + ); + const shouldDrag = useCallback18((e) => { + if (e.type === "wheel") return true; + if (e.type === "mousemove" || e.type === "mouseup" || e.type === "mouseout") { + return true; + } + if (e instanceof MouseEvent && e.button !== 0) return true; + if (isSpacePanHeld() && e instanceof MouseEvent) return true; + const { + toolMode: mode, + allowComponentEdit: allowEdit, + allowCanvasPan: pan + } = panPolicyRef.current; + if (!pan && e.type !== "wheel") { + return false; + } + if (mode === "draw_wire" || mode === "draw_trace" || mode === "draw_component" || mode === "draw_bus" || mode === "draw_bus_entry" || mode === "draw_no_connect" || mode === "draw_net_label" || mode === "draw_global_label" || mode === "draw_hier_sheet" || mode === "draw_power_port" || mode === "draw_ground_port" || mode === "draw_text_note") { + return false; + } + if (allowEdit) { + const target = e.target; + if (target.closest('[data-circuit-json-type="schematic_component"]')) { + return false; + } + } + return true; + }, []); + const { + ref: containerRef, + cancelDrag, + transform: svgToScreenProjection + } = useMouseMatrixTransform({ + onSetTransform: onSetSvgTransform, + // @ts-ignore disabled is a valid prop but not typed + enabled: isInteractionEnabled && !showSpiceOverlay, + shouldDrag + }); + const { containerWidth, containerHeight } = useResizeHandling(containerRef); + const svgString = useMemo6(() => { + if (!containerWidth || !containerHeight) return ""; + return convertCircuitJsonToSchematicSvg( + circuitJson, + { + width: containerWidth, + height: containerHeight || 720, + drawPorts: showSchematicPorts, + schematicSheetId: selectedSchematicSheetId, + grid: !showGrid ? void 0 : { + cellSize: 1, + labelCells: true + }, + colorOverrides, + css, + className + } + ); + }, [ + circuitJsonKey, + circuitJson, + containerWidth, + containerHeight, + showGrid, + colorOverrides, + css, + className, + showSchematicPorts, + selectedSchematicSheetId + ]); + const containerBackgroundColor = useMemo6(() => { + const match = svgString.match( + /]*style="[^"]*background-color:\s*([^;\"]+)/i + ); + return match?.[1] ?? "transparent"; + }, [svgString]); + const realToSvgProjection = useMemo6(() => { + if (!svgString) return identity(); + const transformString = svgString.match( + /data-real-to-screen-transform="([^"]+)"/ + )?.[1]; + try { + return fromString(transformString); + } catch (e) { + console.error(e); + return identity(); + } + }, [svgString]); + const handleEditEvent = (event) => { + setInternalEditEvents((prev) => [...prev, event]); + if (onEditEvent) { + onEditEvent(event); + } + }; + const editEventsWithUnappliedEditEvents = useMemo6(() => { + return [...unappliedEditEvents, ...internalEditEvents]; + }, [unappliedEditEvents, internalEditEvents]); + const { + handleMouseDown, + handleTouchStart: handleComponentTouchStart, + isDragging, + activeEditEvent + } = useComponentDragging({ + onEditEvent: handleEditEvent, + cancelDrag, + realToSvgProjection, + svgToScreenProjection, + circuitJson, + editEvents: editEventsWithUnappliedEditEvents, + enabled: allowComponentEdit && isInteractionEnabled && !showSpiceOverlay, + snapToGrid + }); + const isProjectionReady = svgToScreenProjection?.a != null && !isNaN(svgToScreenProjection.a) && realToSvgProjection?.a != null && !isNaN(realToSvgProjection.a); + const { wireDrawingState, handlePortMouseDown } = useWireDrawing({ + enabled: toolMode === "draw_wire" && isInteractionEnabled && isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onWireAdded + }); + const { + wireDrawingState: tracePreviewState, + handlePortMouseDown: handleTracePortMouseDown + } = useTraceDrawing({ + enabled: toolMode === "draw_trace" && isInteractionEnabled && isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onWireAdded + }); + const { componentPlacementState } = useComponentPlacement({ + enabled: toolMode === "draw_component" && isInteractionEnabled && isProjectionReady, + componentKind: placementComponentKind, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onComponentAdded + }); + const portMouseDownHandler = toolMode === "draw_trace" ? handleTracePortMouseDown : handlePortMouseDown; + const activeWirePreviewState = toolMode === "draw_trace" ? tracePreviewState : wireDrawingState; + const { busDrawingState } = useBusDrawing({ + enabled: toolMode === "draw_bus" && isInteractionEnabled && isProjectionReady, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onBusAdded + }); + const { busEntryPreviewState } = useBusEntryPlacement({ + enabled: toolMode === "draw_bus_entry" && isInteractionEnabled && isProjectionReady, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onBusEntryAdded + }); + const { noConnectPreviewState } = useNoConnectPlacement({ + enabled: toolMode === "draw_no_connect" && isInteractionEnabled && isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onNoConnectAdded + }); + const { netLabelState, confirmPlacement, cancelPlacement } = useNetLabelPlacement({ + enabled: toolMode === "draw_net_label" && isInteractionEnabled && isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onNetLabelAdded + }); + const { + globalLabelState, + confirmPlacement: confirmGlobalPlacement, + cancelPlacement: cancelGlobalPlacement + } = useGlobalLabelPlacement({ + enabled: toolMode === "draw_global_label" && isInteractionEnabled && isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onGlobalLabelAdded + }); + const { + powerPortState, + confirmPlacement: confirmPowerPlacement, + cancelPlacement: cancelPowerPlacement + } = usePowerPortPlacement({ + enabled: toolMode === "draw_power_port" && isInteractionEnabled && isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onPowerPortAdded + }); + const { + groundPortState, + confirmPlacement: confirmGroundPlacement, + cancelPlacement: cancelGroundPlacement + } = useGroundPortPlacement({ + enabled: toolMode === "draw_ground_port" && isInteractionEnabled && isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onGroundPortAdded + }); + const { + textNoteState, + confirmPlacement: confirmTextNotePlacement, + cancelPlacement: cancelTextNotePlacement + } = useTextNotePlacement({ + enabled: toolMode === "draw_text_note" && isInteractionEnabled && isProjectionReady, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onTextNoteAdded + }); + const { + hierSheetState, + confirmPlacement: confirmHierSheetPlacement, + cancelPlacement: cancelHierSheetPlacement + } = useHierSheetPlacement({ + enabled: toolMode === "draw_hier_sheet" && isInteractionEnabled && isProjectionReady, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onHierSheetAdded + }); + useChangeSchematicComponentLocationsInSvg({ + svgDivRef, + editEvents: editEventsWithUnappliedEditEvents, + realToSvgProjection, + svgToScreenProjection, + activeEditEvent + }); + useChangeSchematicTracesForMovedComponents({ + svgDivRef, + circuitJson, + activeEditEvent, + editEvents: editEventsWithUnappliedEditEvents + }); + useSchematicGroupsOverlay({ + svgDivRef, + circuitJson, + circuitJsonKey: `${circuitJsonKey}_${selectedSchematicSheetId ?? ""}`, + showGroups: showSchematicGroups && !disableGroups + }); + useSchematicNetHover({ + svgDivRef, + circuitJson, + circuitJsonKey: `${circuitJsonKey}_${selectedSchematicSheetId ?? ""}`, + enabled: netHoverHighlightEnabled + }); + const handleComponentTouchStartRef = useRef24(handleComponentTouchStart); + useEffect31(() => { + handleComponentTouchStartRef.current = handleComponentTouchStart; + }, [handleComponentTouchStart]); + const svgDiv = useMemo6( + () => /* @__PURE__ */ jsx24( + "div", + { + ref: svgDivRef, + style: { + pointerEvents: clickToInteractEnabled ? isInteractionEnabled ? "auto" : "none" : "auto", + transformOrigin: "0 0" + }, + className: onSchematicComponentClicked ? "schematic-component-clickable" : void 0, + onTouchStart: (e) => { + if (effectiveEditMode && isInteractionEnabled && !showSpiceOverlay) { + handleComponentTouchStartRef.current(e); + } + }, + dangerouslySetInnerHTML: { __html: svgString } + } + ), + [ + svgString, + isInteractionEnabled, + clickToInteractEnabled, + editModeEnabled, + showSpiceOverlay + ] + ); + return /* @__PURE__ */ jsxs16(MouseTracker, { children: [ + netHoverHighlightEnabled && /* @__PURE__ */ jsx24("style", { children: `.sch-net-faded { opacity: 0.35; } + svg :is(g.trace, g.trace-overlays, g[data-schematic-component-id], [data-schematic-net-label-id]) { transition: opacity 0.12s ease-in-out; }` }), + onSchematicComponentClicked && /* @__PURE__ */ jsx24("style", { children: `.schematic-component-clickable [data-schematic-component-id]:hover { cursor: pointer !important; }` }), + onSchematicPortClicked && /* @__PURE__ */ jsx24("style", { children: `[data-schematic-port-id]:hover { cursor: pointer !important; }` }), + /* @__PURE__ */ jsxs16( + "div", + { + ref: containerRef, + style: { + position: "relative", + backgroundColor: containerBackgroundColor, + overflow: hierSheetState.pendingBox ? "visible" : "hidden", + cursor: showSpiceOverlay ? "auto" : toolMode === "draw_wire" || toolMode === "draw_trace" || toolMode === "draw_bus" || toolMode === "draw_bus_entry" || toolMode === "draw_no_connect" || toolMode === "draw_net_label" || toolMode === "draw_global_label" || toolMode === "draw_hier_sheet" || toolMode === "draw_power_port" || toolMode === "draw_ground_port" || toolMode === "draw_text_note" || toolMode === "draw_component" ? "crosshair" : isDragging ? "grabbing" : clickToInteractEnabled && !isInteractionEnabled ? "pointer" : isHoveringClickableComponent && onSchematicComponentClicked ? "pointer" : isHoveringClickablePort && onSchematicPortClicked ? "pointer" : "grab", + minHeight: "300px", + ...containerStyle + }, + onWheelCapture: (e) => { + if (showSpiceOverlay) { + e.stopPropagation(); + } + }, + onMouseDown: (e) => { + if (clickToInteractEnabled && !isInteractionEnabled) { + e.preventDefault(); + e.stopPropagation(); + return; + } + if (allowComponentEdit) { + handleMouseDown(e); + } + }, + onMouseDownCapture: (e) => { + if (clickToInteractEnabled && !isInteractionEnabled) { + e.preventDefault(); + e.stopPropagation(); + return; + } + }, + onTouchStart: (e) => { + if (showSpiceOverlay) return; + handleTouchStart(e); + }, + onTouchEnd: (e) => { + if (showSpiceOverlay) return; + handleTouchEnd(e); + }, + children: [ + !isInteractionEnabled && clickToInteractEnabled && /* @__PURE__ */ jsx24( + "div", + { + onClick: (e) => { + e.preventDefault(); + e.stopPropagation(); + setIsInteractionEnabled(true); + }, + style: { + position: "absolute", + inset: 0, + cursor: "pointer", + zIndex: zIndexMap.clickToInteractOverlay, + display: "flex", + alignItems: "center", + justifyContent: "center", + pointerEvents: "all", + touchAction: "pan-x pan-y pinch-zoom" + }, + children: /* @__PURE__ */ jsx24( + "div", + { + style: { + backgroundColor: "rgba(0, 0, 0, 0.8)", + color: "white", + padding: "12px 24px", + borderRadius: "8px", + fontSize: "16px", + fontFamily: "sans-serif", + pointerEvents: "none" + }, + children: typeof window !== "undefined" && ("ontouchstart" in window || navigator.maxTouchPoints > 0) ? "Touch to Interact" : "Click to Interact" + } + ) + } + ), + editingEnabled && /* @__PURE__ */ jsx24( + EditIcon, + { + active: editModeEnabled, + onClick: () => setEditModeEnabled(!editModeEnabled) + } + ), + editingEnabled && editModeEnabled && /* @__PURE__ */ jsx24( + GridIcon, + { + active: snapToGrid, + onClick: () => setSnapToGrid(!snapToGrid) + } + ), + /* @__PURE__ */ jsx24( + ViewMenu, + { + circuitJson, + circuitJsonKey, + open: showViewMenu, + onOpenChange: setShowViewMenu, + showGroups: showSchematicGroups, + onToggleGroups: (value) => { + if (!disableGroups) { + setShowSchematicGroups(value); + setStoredBoolean("schematic_viewer_show_groups", value); + } + }, + showGrid, + onToggleGrid: setShowGridInternal + } + ), + /* @__PURE__ */ jsx24( + SchematicSheetSelector, + { + sheets: schematicSheets, + selectedSheetId: selectedSchematicSheetId, + onSelectSheet: handleSelectSheet + } + ), + spiceSimulationEnabled && /* @__PURE__ */ jsx24(SpiceSimulationIcon, { onClick: () => setShowSpiceOverlay(true) }), + showSpiceOverlay && /* @__PURE__ */ jsx24( + SpiceSimulationOverlay, + { + spiceString, + onClose: () => setShowSpiceOverlay(false), + plotData, + nodes, + isLoading: isSpiceSimLoading, + error: spiceSimError, + simOptions: spiceSimOptions, + onSimOptionsChange: (options) => { + setHasSpiceSimRun(true); + setSpiceSimOptions(options); + }, + hasRun: hasSpiceSimRun + } + ), + onSchematicComponentClicked && schematicComponentIds.map((componentId) => /* @__PURE__ */ jsx24( + SchematicComponentMouseTarget, + { + componentId, + svgDivRef, + containerRef, + showOutline: true, + circuitJsonKey, + onHoverChange: handleComponentHoverChange, + onComponentClick: (id, event) => { + onSchematicComponentClicked?.({ + schematicComponentId: id, + event + }); + } + }, + componentId + )), + svgDiv, + /* @__PURE__ */ jsx24( + WirePreview, + { + state: activeWirePreviewState, + realToSvgProjection, + svgToScreenProjection, + containerRef + } + ), + /* @__PURE__ */ jsx24( + ComponentPlacementPreview, + { + state: componentPlacementState, + realToSvgProjection, + svgToScreenProjection, + containerRef, + componentKind: placementComponentKind + } + ), + /* @__PURE__ */ jsx24( + BusPreview, + { + state: busDrawingState, + realToSvgProjection, + svgToScreenProjection, + containerRef + } + ), + /* @__PURE__ */ jsx24( + BusEntryPreview, + { + state: busEntryPreviewState, + realToSvgProjection, + svgToScreenProjection, + containerRef + } + ), + /* @__PURE__ */ jsx24( + NoConnectPreview, + { + state: noConnectPreviewState, + realToSvgProjection, + svgToScreenProjection, + containerRef + } + ), + /* @__PURE__ */ jsx24( + NetLabelPreview, + { + state: netLabelState, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm: confirmPlacement, + onCancel: cancelPlacement + } + ), + /* @__PURE__ */ jsx24( + GlobalLabelPreview, + { + state: globalLabelState, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm: confirmGlobalPlacement, + onCancel: cancelGlobalPlacement + } + ), + /* @__PURE__ */ jsx24( + PowerPortPreview, + { + state: powerPortState, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm: confirmPowerPlacement, + onCancel: cancelPowerPlacement + } + ), + /* @__PURE__ */ jsx24( + GroundPortPreview, + { + state: groundPortState, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm: confirmGroundPlacement, + onCancel: cancelGroundPlacement + } + ), + /* @__PURE__ */ jsx24( + TextNotePreview, + { + state: textNoteState, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm: confirmTextNotePlacement, + onCancel: cancelTextNotePlacement + } + ), + /* @__PURE__ */ jsx24( + HierSheetPreview, + { + state: hierSheetState, + realToSvgProjection, + svgToScreenProjection, + containerRef, + sheetTargets: hierSheetTargets, + activeSheetId, + onConfirm: confirmHierSheetPlacement, + onCancel: cancelHierSheetPlacement + } + ), + showSchematicPorts && schematicPortsInfo.map(({ portId, label }) => /* @__PURE__ */ jsx24( + SchematicPortMouseTarget, + { + portId, + portLabel: label, + svgDivRef, + containerRef, + showOutline: true, + interactive: toolMode === "draw_wire" || toolMode === "draw_trace", + hitPaddingPx: toolMode === "draw_trace" ? 12 : 4, + onPortMouseDown: portMouseDownHandler, + circuitJsonKey, + onHoverChange: handlePortHoverChange, + onPortClick: onSchematicPortClicked ? (id, event) => { + onSchematicPortClicked?.({ + schematicPortId: id, + event + }); + } : void 0 + }, + portId + )) + ] + } + ) + ] }); +}; + +// lib/components/AnalogSimulationViewer.tsx +import { + convertCircuitJsonToSchematicSimulationSvg +} from "circuit-to-svg"; +import { useEffect as useEffect32, useState as useState22, useMemo as useMemo7, useRef as useRef25 } from "react"; +import { useMouseMatrixTransform as useMouseMatrixTransform2 } from "use-mouse-matrix-transform"; +import { toString as transformToString2 } from "transformation-matrix"; +import { jsx as jsx25, jsxs as jsxs17 } from "react/jsx-runtime"; +var DEFAULT_RENDER_WIDTH = 1200; +var DEFAULT_RENDER_ASPECT_RATIO = 1; +var AnalogSimulationViewer = ({ + circuitJson: inputCircuitJson, + containerStyle, + colorOverrides, + width, + height, + className +}) => { + const [circuitJson, setCircuitJson] = useState22(null); + const [isLoading, setIsLoading] = useState22(true); + const [error, setError] = useState22(null); + const [svgObjectUrl, setSvgObjectUrl] = useState22(null); + const containerRef = useRef25(null); + const imgRef = useRef25(null); + const { containerWidth } = useResizeHandling( + containerRef + ); + const [isDragging, setIsDragging] = useState22(false); + const { + ref: transformRef, + cancelDrag: _cancelDrag, + transform: _svgToScreenProjection + } = useMouseMatrixTransform2({ + onSetTransform(transform) { + if (imgRef.current) { + imgRef.current.style.transform = transformToString2(transform); + } + } + }); + const renderAspectRatio = width && height ? width / height : DEFAULT_RENDER_ASPECT_RATIO; + const effectiveWidth = width || (height ? height * renderAspectRatio : containerWidth) || DEFAULT_RENDER_WIDTH; + const effectiveHeight = height || effectiveWidth / renderAspectRatio; + useEffect32(() => { + setIsLoading(true); + setError(null); + setCircuitJson(inputCircuitJson); + setIsLoading(false); + }, [inputCircuitJson]); + const simulationExperimentId = useMemo7(() => { + if (!circuitJson) return null; + const simulationElement = circuitJson.find( + (el) => el.type === "simulation_experiment" + ); + return simulationElement?.simulation_experiment_id || null; + }, [circuitJson]); + const simulationVoltageGraphIds = useMemo7(() => { + if (!circuitJson) return []; + return circuitJson.filter((el) => el.type === "simulation_transient_voltage_graph").map((el) => el.simulation_transient_voltage_graph_id); + }, [circuitJson]); + const simulationCurrentGraphIds = useMemo7(() => { + if (!circuitJson) return []; + return circuitJson.filter((el) => el.type === "simulation_transient_current_graph").map((el) => el.simulation_transient_current_graph_id); + }, [circuitJson]); + const simulationSvg = useMemo7(() => { + if (!circuitJson || !effectiveWidth || !effectiveHeight || !simulationExperimentId) + return ""; + try { + return convertCircuitJsonToSchematicSimulationSvg({ + circuitJson, + simulation_experiment_id: simulationExperimentId, + simulation_transient_current_graph_ids: simulationCurrentGraphIds, + simulation_transient_voltage_graph_ids: simulationVoltageGraphIds, + width: effectiveWidth, + height: effectiveHeight, + schematicOptions: { colorOverrides } + }); + } catch (fallbackErr) { + console.error("Failed to generate fallback schematic SVG:", fallbackErr); + return ""; + } + }, [ + circuitJson, + effectiveWidth, + effectiveHeight, + colorOverrides, + simulationExperimentId, + simulationCurrentGraphIds, + simulationVoltageGraphIds + ]); + useEffect32(() => { + if (!simulationSvg) { + setSvgObjectUrl(null); + return; + } + try { + const blob = new Blob([simulationSvg], { type: "image/svg+xml" }); + const url = URL.createObjectURL(blob); + setSvgObjectUrl(url); + return () => { + URL.revokeObjectURL(url); + }; + } catch (error2) { + console.error("Failed to create SVG object URL:", error2); + setSvgObjectUrl(null); + } + }, [simulationSvg]); + const containerBackgroundColor = useMemo7(() => { + if (!simulationSvg) return "transparent"; + const match = simulationSvg.match( + /]*style="[^"]*background-color:\s*([^;\"]+)/i + ); + return match?.[1] ?? "transparent"; + }, [simulationSvg]); + const handleMouseDown = (_e) => { + setIsDragging(true); + }; + const handleTouchStart = (_e) => { + setIsDragging(true); + }; + useEffect32(() => { + const handleMouseUp = () => { + setIsDragging(false); + }; + const handleTouchEnd = () => { + setIsDragging(false); + }; + window.addEventListener("mouseup", handleMouseUp); + window.addEventListener("touchend", handleTouchEnd); + return () => { + window.removeEventListener("mouseup", handleMouseUp); + window.removeEventListener("touchend", handleTouchEnd); + }; + }, []); + if (isLoading) { + return /* @__PURE__ */ jsx25( + "div", + { + style: { + display: "flex", + alignItems: "center", + justifyContent: "center", + backgroundColor: "#f5f5f5", + minHeight: "300px", + fontFamily: "sans-serif", + fontSize: "16px", + color: "#666", + ...containerStyle + }, + className, + children: "Loading circuit..." + } + ); + } + if (error) { + return /* @__PURE__ */ jsx25( + "div", + { + style: { + display: "flex", + alignItems: "center", + justifyContent: "center", + backgroundColor: "#fef2f2", + minHeight: "300px", + fontFamily: "sans-serif", + fontSize: "16px", + color: "#dc2626", + ...containerStyle + }, + className, + children: /* @__PURE__ */ jsxs17("div", { style: { textAlign: "center", padding: "20px" }, children: [ + /* @__PURE__ */ jsx25("div", { style: { fontWeight: "bold", marginBottom: "8px" }, children: "Circuit Conversion Error" }), + /* @__PURE__ */ jsx25("div", { style: { fontSize: "14px" }, children: error }) + ] }) + } + ); + } + if (!simulationSvg) { + return /* @__PURE__ */ jsxs17( + "div", + { + style: { + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + backgroundColor: "#f8fafc", + minHeight: "300px", + fontFamily: "sans-serif", + gap: "12px", + ...containerStyle + }, + className, + children: [ + /* @__PURE__ */ jsx25("div", { style: { fontSize: "16px", color: "#475569", fontWeight: 500 }, children: "No Simulation Found" }), + /* @__PURE__ */ jsxs17("div", { style: { fontSize: "14px", color: "#64748b" }, children: [ + "Use", + " ", + /* @__PURE__ */ jsx25( + "code", + { + style: { + backgroundColor: "#e2e8f0", + padding: "2px 6px", + borderRadius: "4px", + fontFamily: "monospace", + fontSize: "13px" + }, + children: "" + } + ), + " ", + "to create simulations" + ] }) + ] + } + ); + } + return /* @__PURE__ */ jsx25( + "div", + { + ref: (node) => { + containerRef.current = node; + transformRef.current = node; + }, + style: { + position: "relative", + backgroundColor: containerBackgroundColor, + overflow: "hidden", + minHeight: "300px", + cursor: isDragging ? "grabbing" : "grab", + ...containerStyle + }, + className, + onMouseDown: handleMouseDown, + onTouchStart: handleTouchStart, + children: svgObjectUrl ? /* @__PURE__ */ jsx25( + "img", + { + ref: imgRef, + src: svgObjectUrl, + alt: "Circuit Simulation", + style: { + transformOrigin: "0 0", + width: "100%", + height: "100%", + display: "block", + objectFit: "contain" + } + } + ) : /* @__PURE__ */ jsx25( + "div", + { + style: { + display: "flex", + alignItems: "center", + justifyContent: "center", + height: "100%", + minHeight: "300px", + color: "#666", + fontFamily: "sans-serif" + }, + children: "Failed to render SVG" + } + ) + } + ); +}; +export { + AnalogSimulationViewer, + MouseTracker, + SchematicViewer, + useMouseEventsOverBoundingBox +}; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 00000000..2e8eb256 --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../lib/components/SchematicViewer.tsx","../lib/hooks/useChangeSchematicComponentLocationsInSvg.ts","../lib/utils/get-component-offset-due-to-events.ts","../lib/hooks/useChangeSchematicTracesForMovedComponents.ts","../lib/hooks/useSchematicGroupsOverlay.ts","../lib/hooks/useSchematicNetHover.ts","../lib/utils/debug.ts","../lib/hooks/use-resize-handling.ts","../lib/hooks/useComponentDragging.ts","../lib/utils/z-index-map.ts","../lib/components/EditIcon.tsx","../lib/components/GridIcon.tsx","../lib/components/ViewMenu.tsx","../package.json","../lib/components/ViewMenuIcon.tsx","../lib/components/SpiceIcon.tsx","../lib/components/SpiceSimulationIcon.tsx","../lib/components/SpicePlot.tsx","../lib/components/SpiceSimulationOverlay.tsx","../lib/hooks/useSpiceSimulation.ts","../lib/workers/spice-simulation.worker.blob.js","../lib/utils/spice-utils.ts","../lib/hooks/useLocalStorage.ts","../lib/components/MouseTracker.tsx","../lib/components/SchematicComponentMouseTarget.tsx","../lib/hooks/useMouseEventsOverBoundingBox.ts","../lib/components/SchematicPortMouseTarget.tsx","../lib/components/SchematicSheetSelector.tsx","../lib/hooks/useWireDrawing.ts","../lib/utils/isMouseCaptureIgnoredTarget.ts","../lib/utils/schematicPortHitTest.ts","../lib/hooks/useBusDrawing.ts","../lib/hooks/useBusEntryPlacement.ts","../lib/hooks/useNoConnectPlacement.ts","../lib/hooks/useNetLabelPlacement.ts","../lib/hooks/useGlobalLabelPlacement.ts","../lib/hooks/useHierSheetPlacement.ts","../lib/components/WirePreview.tsx","../lib/components/BusPreview.tsx","../lib/components/BusEntryPreview.tsx","../lib/components/NoConnectPreview.tsx","../lib/components/NetLabelPreview.tsx","../lib/components/GlobalLabelPreview.tsx","../lib/components/HierSheetPreview.tsx","../lib/hooks/usePowerPortPlacement.ts","../lib/components/PowerPortPreview.tsx","../lib/hooks/useGroundPortPlacement.ts","../lib/components/GroundPortPreview.tsx","../lib/hooks/useTextNotePlacement.ts","../lib/components/TextNotePreview.tsx","../lib/hooks/useTraceDrawing.ts","../lib/utils/computeTraceRoute.ts","../lib/hooks/useComponentPlacement.ts","../lib/components/ComponentPlacementPreview.tsx","../lib/components/AnalogSimulationViewer.tsx"],"sourcesContent":["import {\n convertCircuitJsonToSchematicSvg,\n type ColorOverrides,\n} from \"circuit-to-svg\"\nimport { su } from \"@tscircuit/soup-util\"\nimport { useChangeSchematicComponentLocationsInSvg } from \"lib/hooks/useChangeSchematicComponentLocationsInSvg\"\nimport { useChangeSchematicTracesForMovedComponents } from \"lib/hooks/useChangeSchematicTracesForMovedComponents\"\nimport { useSchematicGroupsOverlay } from \"lib/hooks/useSchematicGroupsOverlay\"\nimport { useSchematicNetHover } from \"lib/hooks/useSchematicNetHover\"\nimport { enableDebug } from \"lib/utils/debug\"\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport {\n fromString,\n identity,\n toString as transformToString,\n} from \"transformation-matrix\"\nimport { useMouseMatrixTransform } from \"use-mouse-matrix-transform\"\nimport { useResizeHandling } from \"../hooks/use-resize-handling\"\nimport { useComponentDragging } from \"../hooks/useComponentDragging\"\nimport type { ManualEditEvent } from \"../types/edit-events\"\nimport { EditIcon } from \"./EditIcon\"\nimport { GridIcon } from \"./GridIcon\"\nimport { ViewMenu } from \"./ViewMenu\"\nimport type { CircuitJson, SchematicSheet } from \"circuit-json\"\nimport { SpiceSimulationIcon } from \"./SpiceSimulationIcon\"\nimport { SpiceSimulationOverlay } from \"./SpiceSimulationOverlay\"\nimport { zIndexMap } from \"../utils/z-index-map\"\nimport { useSpiceSimulation } from \"../hooks/useSpiceSimulation\"\nimport { getSpiceFromCircuitJson } from \"../utils/spice-utils\"\nimport {\n getStoredBoolean,\n setStoredBoolean,\n getStoredString,\n setStoredString,\n STORAGE_KEYS,\n} from \"lib/hooks/useLocalStorage\"\nimport { MouseTracker } from \"./MouseTracker\"\nimport { SchematicComponentMouseTarget } from \"./SchematicComponentMouseTarget\"\nimport { SchematicPortMouseTarget } from \"./SchematicPortMouseTarget\"\nimport { SchematicSheetSelector } from \"./SchematicSheetSelector\"\nimport { useWireDrawing } from \"../hooks/useWireDrawing\"\nimport { useBusDrawing } from \"../hooks/useBusDrawing\"\nimport { useBusEntryPlacement } from \"../hooks/useBusEntryPlacement\"\nimport { useNoConnectPlacement } from \"../hooks/useNoConnectPlacement\"\nimport { useNetLabelPlacement } from \"../hooks/useNetLabelPlacement\"\nimport { useGlobalLabelPlacement } from \"../hooks/useGlobalLabelPlacement\"\nimport { useHierSheetPlacement } from \"../hooks/useHierSheetPlacement\"\nimport { WirePreview } from \"./WirePreview\"\nimport { BusPreview } from \"./BusPreview\"\nimport { BusEntryPreview } from \"./BusEntryPreview\"\nimport { NoConnectPreview } from \"./NoConnectPreview\"\nimport { NetLabelPreview } from \"./NetLabelPreview\"\nimport { GlobalLabelPreview } from \"./GlobalLabelPreview\"\nimport { HierSheetPreview, type HierSheetTarget } from \"./HierSheetPreview\"\nimport { isSpacePanHeld, setSpacePanHeld } from \"lib/hooks/useLocalStorage\"\nimport { usePowerPortPlacement } from \"../hooks/usePowerPortPlacement\"\nimport { PowerPortPreview } from \"./PowerPortPreview\"\nimport type { EditSchematicPowerPortAddEvent } from \"../types/edit-events\"\nimport { useGroundPortPlacement } from \"../hooks/useGroundPortPlacement\"\nimport { GroundPortPreview } from \"./GroundPortPreview\"\nimport type { EditSchematicGroundPortAddEvent } from \"../types/edit-events\"\nimport { useTextNotePlacement } from \"../hooks/useTextNotePlacement\"\nimport { TextNotePreview } from \"./TextNotePreview\"\nimport type { EditSchematicTextNoteAddEvent } from \"../types/edit-events\"\nimport { useTraceDrawing } from \"../hooks/useTraceDrawing\"\nimport { useComponentPlacement } from \"../hooks/useComponentPlacement\"\nimport { ComponentPlacementPreview } from \"./ComponentPlacementPreview\"\nimport type {\n EditSchematicComponentAddEvent,\n PlacementComponentKind,\n} from \"../types/edit-events\"\nimport type {\n EditSchematicBusAddEvent,\n EditSchematicBusEntryAddEvent,\n EditSchematicGlobalLabelAddEvent,\n EditSchematicHierSheetAddEvent,\n EditSchematicNetLabelAddEvent,\n EditSchematicNoConnectAddEvent,\n EditSchematicWireAddEvent,\n} from \"../types/edit-events\"\n\ninterface Props {\n circuitJson: CircuitJson\n containerStyle?: React.CSSProperties\n editEvents?: ManualEditEvent[]\n onEditEvent?: (event: ManualEditEvent) => void\n defaultEditMode?: boolean\n debugGrid?: boolean\n editingEnabled?: boolean\n debug?: boolean\n clickToInteractEnabled?: boolean\n colorOverrides?: ColorOverrides\n spiceSimulationEnabled?: boolean\n disableGroups?: boolean\n /** Fade unrelated nets/chips when hovering a wire or net label. Default true. */\n netHoverHighlightEnabled?: boolean\n css?: string\n className?: string\n onSchematicComponentClicked?: (options: {\n schematicComponentId: string\n event: MouseEvent\n }) => void\n showSchematicPorts?: boolean\n onSchematicPortClicked?: (options: {\n schematicPortId: string\n event: MouseEvent\n }) => void\n /** Called when the active schematic sheet changes (multi-sheet circuits). */\n onSchematicSheetChange?: (schematicSheetId: string) => void\n toolMode?:\n | \"select\"\n | \"draw_wire\"\n | \"draw_bus\"\n | \"draw_bus_entry\"\n | \"draw_no_connect\"\n | \"draw_net_label\"\n | \"draw_global_label\"\n | \"draw_hier_sheet\"\n | \"draw_power_port\"\n | \"draw_ground_port\"\n | \"draw_text_note\"\n | \"draw_trace\"\n | \"draw_component\"\n onWireAdded?: (event: EditSchematicWireAddEvent) => void\n onBusAdded?: (event: EditSchematicBusAddEvent) => void\n onBusEntryAdded?: (event: EditSchematicBusEntryAddEvent) => void\n onNoConnectAdded?: (event: EditSchematicNoConnectAddEvent) => void\n onNetLabelAdded?: (event: EditSchematicNetLabelAddEvent) => void\n onGlobalLabelAdded?: (event: EditSchematicGlobalLabelAddEvent) => void\n onHierSheetAdded?: (event: EditSchematicHierSheetAddEvent) => void\n onPowerPortAdded?: (event: EditSchematicPowerPortAddEvent) => void\n onGroundPortAdded?: (event: EditSchematicGroundPortAddEvent) => void\n onTextNoteAdded?: (event: EditSchematicTextNoteAddEvent) => void\n onComponentAdded?: (event: EditSchematicComponentAddEvent) => void\n placementComponentKind?: PlacementComponentKind\n hierSheetTargets?: HierSheetTarget[]\n /** Host sheet id excluded from hier-sheet placement targets. */\n activeSheetId?: string\n allowComponentEdit?: boolean\n allowCanvasPan?: boolean\n}\n\nexport const SchematicViewer = ({\n circuitJson,\n containerStyle,\n editEvents: unappliedEditEvents = [],\n onEditEvent,\n defaultEditMode = false,\n debugGrid = false,\n editingEnabled = false,\n debug = false,\n clickToInteractEnabled = false,\n colorOverrides,\n spiceSimulationEnabled = false,\n disableGroups = false,\n netHoverHighlightEnabled = true,\n onSchematicComponentClicked,\n showSchematicPorts = false,\n onSchematicPortClicked,\n onSchematicSheetChange,\n css,\n className,\n toolMode = \"select\",\n onWireAdded,\n onBusAdded,\n onBusEntryAdded,\n onNoConnectAdded,\n onNetLabelAdded,\n onGlobalLabelAdded,\n onHierSheetAdded,\n onPowerPortAdded,\n onGroundPortAdded,\n onTextNoteAdded,\n onComponentAdded,\n placementComponentKind = \"resistor\",\n hierSheetTargets = [],\n activeSheetId,\n allowComponentEdit = false,\n allowCanvasPan = true,\n}: Props) => {\n if (debug) {\n enableDebug()\n }\n const [showSpiceOverlay, setShowSpiceOverlay] = useState(false)\n const [spiceSimOptions, setSpiceSimOptions] = useState({\n showVoltage: true,\n showCurrent: false,\n startTime: 0, // in ms\n duration: 20, // in ms\n })\n\n const getCircuitHash = (circuitJson: CircuitJson) => {\n return `${circuitJson?.length || 0}_${(circuitJson as any)?.editCount || 0}`\n }\n\n const circuitJsonKey = useMemo(\n () => getCircuitHash(circuitJson),\n [circuitJson],\n )\n\n // Schematic sheets present in the circuit, sorted by sheet_index. A circuit\n // may have zero (single implicit sheet), one, or many sheets.\n const schematicSheets = useMemo(() => {\n try {\n return (circuitJson as any[])\n .filter((elm) => elm?.type === \"schematic_sheet\")\n .slice()\n .sort((a, b) => (a.sheet_index ?? 0) - (b.sheet_index ?? 0))\n } catch (err) {\n console.error(\"Failed to derive schematic sheets\", err)\n return []\n }\n }, [circuitJsonKey])\n\n const hasMultipleSheets = schematicSheets.length > 1\n const defaultSheetId = schematicSheets[0]?.schematic_sheet_id\n\n const [selectedSheetId, setSelectedSheetId] = useState(\n () => {\n // Restore the last-viewed sheet from localStorage so it survives reloads.\n const stored = getStoredString(STORAGE_KEYS.SELECTED_SCHEMATIC_SHEET)\n if (\n stored &&\n schematicSheets.some((s) => s.schematic_sheet_id === stored)\n ) {\n return stored\n }\n return defaultSheetId\n },\n )\n\n // Keep the selection valid as the circuit changes: fall back to the default\n // sheet if the previously-selected sheet no longer exists.\n useEffect(() => {\n const stillExists =\n selectedSheetId !== undefined &&\n schematicSheets.some((s) => s.schematic_sheet_id === selectedSheetId)\n if (!stillExists) {\n setSelectedSheetId(defaultSheetId)\n }\n }, [circuitJsonKey])\n\n // The sheet that should actually be rendered. When there is a single sheet\n // (or none) we leave this undefined so circuit-to-svg uses its default and\n // behavior is unchanged for single-sheet circuits.\n const selectedSchematicSheetId = hasMultipleSheets\n ? (selectedSheetId ?? defaultSheetId)\n : undefined\n\n const handleSelectSheet = useCallback(\n (sheetId: string) => {\n setSelectedSheetId(sheetId)\n setStoredString(STORAGE_KEYS.SELECTED_SCHEMATIC_SHEET, sheetId)\n onSchematicSheetChange?.(sheetId)\n },\n [onSchematicSheetChange],\n )\n\n const spiceString = useMemo(() => {\n if (!spiceSimulationEnabled) return null\n try {\n return getSpiceFromCircuitJson(circuitJson, spiceSimOptions)\n } catch (e) {\n console.error(\"Failed to generate SPICE string\", e)\n return null\n }\n }, [\n circuitJsonKey,\n spiceSimulationEnabled,\n spiceSimOptions.startTime,\n spiceSimOptions.duration,\n ])\n\n const [hasSpiceSimRun, setHasSpiceSimRun] = useState(false)\n\n useEffect(() => {\n setHasSpiceSimRun(false)\n }, [circuitJsonKey])\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.code !== \"Space\") return\n const t = e.target\n if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement)\n return\n setSpacePanHeld(true)\n }\n const onKeyUp = (e: KeyboardEvent) => {\n if (e.code === \"Space\") setSpacePanHeld(false)\n }\n window.addEventListener(\"keydown\", onKeyDown)\n window.addEventListener(\"keyup\", onKeyUp)\n return () => {\n window.removeEventListener(\"keydown\", onKeyDown)\n window.removeEventListener(\"keyup\", onKeyUp)\n setSpacePanHeld(false)\n }\n }, [])\n\n const {\n plotData,\n nodes,\n isLoading: isSpiceSimLoading,\n error: spiceSimError,\n } = useSpiceSimulation(hasSpiceSimRun ? spiceString : null)\n\n const [editModeEnabled, setEditModeEnabled] = useState(defaultEditMode)\n const effectiveEditMode = toolMode === \"select\" && editModeEnabled\n\n useEffect(() => {\n if (\n toolMode === \"draw_wire\" ||\n toolMode === \"draw_bus\" ||\n toolMode === \"draw_bus_entry\" ||\n toolMode === \"draw_no_connect\" ||\n toolMode === \"draw_net_label\" ||\n toolMode === \"draw_global_label\" ||\n toolMode === \"draw_hier_sheet\" ||\n toolMode === \"draw_power_port\" ||\n toolMode === \"draw_ground_port\" ||\n toolMode === \"draw_text_note\"\n ) {\n setEditModeEnabled(false)\n } else if (toolMode === \"select\" && defaultEditMode) {\n setEditModeEnabled(true)\n } else {\n setEditModeEnabled(false)\n }\n }, [toolMode, defaultEditMode])\n const [snapToGrid, setSnapToGrid] = useState(true)\n const [showGridInternal, setShowGridInternal] = useState(false)\n const showGrid = debugGrid || showGridInternal\n const [isInteractionEnabled, setIsInteractionEnabled] = useState(\n !clickToInteractEnabled,\n )\n const [showViewMenu, setShowViewMenu] = useState(false)\n const [showSchematicGroups, setShowSchematicGroups] = useState(() => {\n if (disableGroups) return false\n return getStoredBoolean(\"schematic_viewer_show_groups\", false)\n })\n const [isHoveringClickableComponent, setIsHoveringClickableComponent] =\n useState(false)\n const hoveringComponentsRef = useRef>(new Set())\n\n const handleComponentHoverChange = useCallback(\n (componentId: string, isHovering: boolean) => {\n if (isHovering) {\n hoveringComponentsRef.current.add(componentId)\n } else {\n hoveringComponentsRef.current.delete(componentId)\n }\n setIsHoveringClickableComponent(hoveringComponentsRef.current.size > 0)\n },\n [],\n )\n\n const [isHoveringClickablePort, setIsHoveringClickablePort] = useState(false)\n const hoveringPortsRef = useRef>(new Set())\n\n const handlePortHoverChange = useCallback(\n (portId: string, isHovering: boolean) => {\n if (isHovering) {\n hoveringPortsRef.current.add(portId)\n } else {\n hoveringPortsRef.current.delete(portId)\n }\n setIsHoveringClickablePort(hoveringPortsRef.current.size > 0)\n },\n [],\n )\n\n const svgDivRef = useRef(null)\n const touchStartRef = useRef<{ x: number; y: number } | null>(null)\n\n const schematicComponentIds = useMemo(() => {\n try {\n const components = su(circuitJson).schematic_component?.list() ?? []\n return components\n .filter(\n (component) =>\n !selectedSchematicSheetId ||\n (component as any).schematic_sheet_id === selectedSchematicSheetId,\n )\n .map((component) => component.schematic_component_id as string)\n } catch (err) {\n console.error(\"Failed to derive schematic component ids\", err)\n return []\n }\n }, [circuitJsonKey, circuitJson, selectedSchematicSheetId])\n\n const schematicPortsInfo = useMemo(() => {\n if (!showSchematicPorts) return []\n try {\n const ports = (su(circuitJson).schematic_port?.list() ?? []).filter(\n (port) =>\n !selectedSchematicSheetId ||\n (port as any).schematic_sheet_id === selectedSchematicSheetId,\n )\n return ports.map((port) => {\n const sourcePort = su(circuitJson).source_port.get(port.source_port_id)\n const sourceComponent = sourcePort?.source_component_id\n ? su(circuitJson).source_component.get(sourcePort.source_component_id)\n : null\n const componentName = sourceComponent?.name ?? \"?\"\n const pinLabel =\n port.display_pin_label ??\n (sourcePort as any)?.pin_number ??\n (sourcePort as any)?.name ??\n \"?\"\n return {\n portId: port.schematic_port_id as string,\n label: `${componentName}.${pinLabel}`,\n }\n })\n } catch (err) {\n console.error(\"Failed to derive schematic port info\", err)\n return []\n }\n }, [\n circuitJsonKey,\n circuitJson,\n showSchematicPorts,\n selectedSchematicSheetId,\n ])\n\n const handleTouchStart = (e: React.TouchEvent) => {\n const touch = e.touches[0]\n touchStartRef.current = {\n x: touch.clientX,\n y: touch.clientY,\n }\n }\n\n const handleTouchEnd = (e: React.TouchEvent) => {\n const touch = e.changedTouches[0]\n const start = touchStartRef.current\n if (!start) return\n\n const deltaX = Math.abs(touch.clientX - start.x)\n const deltaY = Math.abs(touch.clientY - start.y)\n\n if (deltaX < 10 && deltaY < 10) {\n e.preventDefault()\n setIsInteractionEnabled(true)\n }\n\n touchStartRef.current = null\n }\n\n const [internalEditEvents, setInternalEditEvents] = useState<\n ManualEditEvent[]\n >([])\n const circuitJsonRef = useRef(circuitJson)\n\n useEffect(() => {\n const circuitHash = getCircuitHash(circuitJson)\n const circuitHashRef = getCircuitHash(circuitJsonRef.current)\n\n if (circuitHash !== circuitHashRef) {\n setInternalEditEvents([])\n circuitJsonRef.current = circuitJson\n }\n }, [circuitJson])\n\n const panPolicyRef = useRef({ toolMode, allowComponentEdit, allowCanvasPan })\n panPolicyRef.current = { toolMode, allowComponentEdit, allowCanvasPan }\n\n const onSetSvgTransform = useCallback(\n (transform: Parameters[0]) => {\n if (!svgDivRef.current) return\n svgDivRef.current.style.transform = transformToString(transform)\n },\n [],\n )\n\n const shouldDrag = useCallback((e: MouseEvent | TouchEvent | WheelEvent) => {\n if (e.type === \"wheel\") return true\n // Hit-target checks only on mousedown; re-checking on move/up stops pan mid-drag\n if (\n e.type === \"mousemove\" ||\n e.type === \"mouseup\" ||\n e.type === \"mouseout\"\n ) {\n return true\n }\n\n if (e instanceof MouseEvent && e.button !== 0) return true\n if (isSpacePanHeld() && e instanceof MouseEvent) return true\n\n const {\n toolMode: mode,\n allowComponentEdit: allowEdit,\n allowCanvasPan: pan,\n } = panPolicyRef.current\n\n if (!pan && e.type !== \"wheel\") {\n return false\n }\n\n if (\n mode === \"draw_wire\" ||\n mode === \"draw_trace\" ||\n mode === \"draw_component\" ||\n mode === \"draw_bus\" ||\n mode === \"draw_bus_entry\" ||\n mode === \"draw_no_connect\" ||\n mode === \"draw_net_label\" ||\n mode === \"draw_global_label\" ||\n mode === \"draw_hier_sheet\" ||\n mode === \"draw_power_port\" ||\n mode === \"draw_ground_port\" ||\n mode === \"draw_text_note\"\n ) {\n return false\n }\n\n if (allowEdit) {\n const target = e.target as Element\n if (target.closest('[data-circuit-json-type=\"schematic_component\"]')) {\n return false\n }\n }\n\n return true\n }, [])\n\n const {\n ref: containerRef,\n cancelDrag,\n transform: svgToScreenProjection,\n } = useMouseMatrixTransform({\n onSetTransform: onSetSvgTransform,\n // @ts-ignore disabled is a valid prop but not typed\n enabled: isInteractionEnabled && !showSpiceOverlay,\n shouldDrag,\n })\n\n const { containerWidth, containerHeight } = useResizeHandling(containerRef)\n const svgString = useMemo(() => {\n if (!containerWidth || !containerHeight) return \"\"\n\n return convertCircuitJsonToSchematicSvg(\n circuitJson as any,\n {\n width: containerWidth,\n height: containerHeight || 720,\n drawPorts: showSchematicPorts,\n schematicSheetId: selectedSchematicSheetId,\n grid: !showGrid\n ? undefined\n : {\n cellSize: 1,\n labelCells: true,\n },\n colorOverrides,\n css,\n className,\n } as any,\n )\n }, [\n circuitJsonKey,\n circuitJson,\n containerWidth,\n containerHeight,\n showGrid,\n colorOverrides,\n css,\n className,\n showSchematicPorts,\n selectedSchematicSheetId,\n ])\n\n const containerBackgroundColor = useMemo(() => {\n const match = svgString.match(\n /]*style=\"[^\"]*background-color:\\s*([^;\\\"]+)/i,\n )\n return match?.[1] ?? \"transparent\"\n }, [svgString])\n\n const realToSvgProjection = useMemo(() => {\n if (!svgString) return identity()\n const transformString = svgString.match(\n /data-real-to-screen-transform=\"([^\"]+)\"/,\n )?.[1]!\n\n try {\n return fromString(transformString)\n } catch (e) {\n console.error(e)\n return identity()\n }\n }, [svgString])\n\n const handleEditEvent = (event: ManualEditEvent) => {\n setInternalEditEvents((prev) => [...prev, event])\n if (onEditEvent) {\n onEditEvent(event)\n }\n }\n\n const editEventsWithUnappliedEditEvents = useMemo(() => {\n return [...unappliedEditEvents, ...internalEditEvents]\n }, [unappliedEditEvents, internalEditEvents])\n\n const {\n handleMouseDown,\n handleTouchStart: handleComponentTouchStart,\n isDragging,\n activeEditEvent,\n } = useComponentDragging({\n onEditEvent: handleEditEvent,\n cancelDrag,\n realToSvgProjection,\n svgToScreenProjection,\n circuitJson,\n editEvents: editEventsWithUnappliedEditEvents,\n enabled: allowComponentEdit && isInteractionEnabled && !showSpiceOverlay,\n snapToGrid,\n })\n\n const isProjectionReady =\n svgToScreenProjection?.a != null &&\n !isNaN(svgToScreenProjection.a) &&\n realToSvgProjection?.a != null &&\n !isNaN(realToSvgProjection.a)\n\n const { wireDrawingState, handlePortMouseDown } = useWireDrawing({\n enabled:\n toolMode === \"draw_wire\" && isInteractionEnabled && isProjectionReady,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent: onWireAdded,\n })\n\n const {\n wireDrawingState: tracePreviewState,\n handlePortMouseDown: handleTracePortMouseDown,\n } = useTraceDrawing({\n enabled:\n toolMode === \"draw_trace\" && isInteractionEnabled && isProjectionReady,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent: onWireAdded,\n })\n\n const { componentPlacementState } = useComponentPlacement({\n enabled:\n toolMode === \"draw_component\" &&\n isInteractionEnabled &&\n isProjectionReady,\n componentKind: placementComponentKind,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent: onComponentAdded,\n })\n\n const portMouseDownHandler =\n toolMode === \"draw_trace\" ? handleTracePortMouseDown : handlePortMouseDown\n const activeWirePreviewState =\n toolMode === \"draw_trace\" ? tracePreviewState : wireDrawingState\n\n const { busDrawingState } = useBusDrawing({\n enabled:\n toolMode === \"draw_bus\" && isInteractionEnabled && isProjectionReady,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent: onBusAdded,\n })\n\n const { busEntryPreviewState } = useBusEntryPlacement({\n enabled:\n toolMode === \"draw_bus_entry\" &&\n isInteractionEnabled &&\n isProjectionReady,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent: onBusEntryAdded,\n })\n\n const { noConnectPreviewState } = useNoConnectPlacement({\n enabled:\n toolMode === \"draw_no_connect\" &&\n isInteractionEnabled &&\n isProjectionReady,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent: onNoConnectAdded,\n })\n\n const { netLabelState, confirmPlacement, cancelPlacement } =\n useNetLabelPlacement({\n enabled:\n toolMode === \"draw_net_label\" &&\n isInteractionEnabled &&\n isProjectionReady,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent: onNetLabelAdded,\n })\n\n const {\n globalLabelState,\n confirmPlacement: confirmGlobalPlacement,\n cancelPlacement: cancelGlobalPlacement,\n } = useGlobalLabelPlacement({\n enabled:\n toolMode === \"draw_global_label\" &&\n isInteractionEnabled &&\n isProjectionReady,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent: onGlobalLabelAdded,\n })\n\n const {\n powerPortState,\n confirmPlacement: confirmPowerPlacement,\n cancelPlacement: cancelPowerPlacement,\n } = usePowerPortPlacement({\n enabled:\n toolMode === \"draw_power_port\" &&\n isInteractionEnabled &&\n isProjectionReady,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent: onPowerPortAdded,\n })\n\n const {\n groundPortState,\n confirmPlacement: confirmGroundPlacement,\n cancelPlacement: cancelGroundPlacement,\n } = useGroundPortPlacement({\n enabled:\n toolMode === \"draw_ground_port\" &&\n isInteractionEnabled &&\n isProjectionReady,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent: onGroundPortAdded,\n })\n\n const {\n textNoteState,\n confirmPlacement: confirmTextNotePlacement,\n cancelPlacement: cancelTextNotePlacement,\n } = useTextNotePlacement({\n enabled:\n toolMode === \"draw_text_note\" &&\n isInteractionEnabled &&\n isProjectionReady,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent: onTextNoteAdded,\n })\n\n const {\n hierSheetState,\n confirmPlacement: confirmHierSheetPlacement,\n cancelPlacement: cancelHierSheetPlacement,\n } = useHierSheetPlacement({\n enabled:\n toolMode === \"draw_hier_sheet\" &&\n isInteractionEnabled &&\n isProjectionReady,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent: onHierSheetAdded,\n })\n\n useChangeSchematicComponentLocationsInSvg({\n svgDivRef,\n editEvents: editEventsWithUnappliedEditEvents,\n realToSvgProjection,\n svgToScreenProjection,\n activeEditEvent,\n })\n\n useChangeSchematicTracesForMovedComponents({\n svgDivRef,\n circuitJson,\n activeEditEvent,\n editEvents: editEventsWithUnappliedEditEvents,\n })\n\n // Add group overlays when enabled. The key includes the active sheet so\n // overlays are recomputed against the freshly-rendered sheet's SVG.\n useSchematicGroupsOverlay({\n svgDivRef,\n circuitJson,\n circuitJsonKey: `${circuitJsonKey}_${selectedSchematicSheetId ?? \"\"}`,\n showGroups: showSchematicGroups && !disableGroups,\n })\n\n // Fade unrelated nets/chips when hovering a wire or net label (JS-driven; the\n // base SVG carries no interaction).\n useSchematicNetHover({\n svgDivRef,\n circuitJson,\n circuitJsonKey: `${circuitJsonKey}_${selectedSchematicSheetId ?? \"\"}`,\n enabled: netHoverHighlightEnabled,\n })\n\n // keep the latest touch handler without re-rendering the svg div\n const handleComponentTouchStartRef = useRef(handleComponentTouchStart)\n useEffect(() => {\n handleComponentTouchStartRef.current = handleComponentTouchStart\n }, [handleComponentTouchStart])\n\n const svgDiv = useMemo(\n () => (\n {\n if (effectiveEditMode && isInteractionEnabled && !showSpiceOverlay) {\n handleComponentTouchStartRef.current(e)\n }\n }}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: \n dangerouslySetInnerHTML={{ __html: svgString }}\n />\n ),\n [\n svgString,\n isInteractionEnabled,\n clickToInteractEnabled,\n editModeEnabled,\n showSpiceOverlay,\n ],\n )\n\n return (\n \n {netHoverHighlightEnabled && (\n \n )}\n {onSchematicComponentClicked && (\n \n )}\n {onSchematicPortClicked && (\n \n )}\n {\n if (showSpiceOverlay) {\n e.stopPropagation()\n }\n }}\n onMouseDown={(e) => {\n if (clickToInteractEnabled && !isInteractionEnabled) {\n e.preventDefault()\n e.stopPropagation()\n return\n }\n if (allowComponentEdit) {\n handleMouseDown(e)\n }\n }}\n onMouseDownCapture={(e) => {\n if (clickToInteractEnabled && !isInteractionEnabled) {\n e.preventDefault()\n e.stopPropagation()\n return\n }\n }}\n onTouchStart={(e) => {\n if (showSpiceOverlay) return\n handleTouchStart(e)\n }}\n onTouchEnd={(e) => {\n if (showSpiceOverlay) return\n handleTouchEnd(e)\n }}\n >\n {!isInteractionEnabled && clickToInteractEnabled && (\n {\n e.preventDefault()\n e.stopPropagation()\n setIsInteractionEnabled(true)\n }}\n style={{\n position: \"absolute\",\n inset: 0,\n cursor: \"pointer\",\n zIndex: zIndexMap.clickToInteractOverlay,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n pointerEvents: \"all\",\n touchAction: \"pan-x pan-y pinch-zoom\",\n }}\n >\n \n {typeof window !== \"undefined\" &&\n (\"ontouchstart\" in window || navigator.maxTouchPoints > 0)\n ? \"Touch to Interact\"\n : \"Click to Interact\"}\n \n \n )}\n {editingEnabled && (\n setEditModeEnabled(!editModeEnabled)}\n />\n )}\n {editingEnabled && editModeEnabled && (\n setSnapToGrid(!snapToGrid)}\n />\n )}\n {\n if (!disableGroups) {\n setShowSchematicGroups(value)\n setStoredBoolean(\"schematic_viewer_show_groups\", value)\n }\n }}\n showGrid={showGrid}\n onToggleGrid={setShowGridInternal}\n />\n \n {spiceSimulationEnabled && (\n setShowSpiceOverlay(true)} />\n )}\n {showSpiceOverlay && (\n setShowSpiceOverlay(false)}\n plotData={plotData}\n nodes={nodes}\n isLoading={isSpiceSimLoading}\n error={spiceSimError}\n simOptions={spiceSimOptions}\n onSimOptionsChange={(options) => {\n setHasSpiceSimRun(true)\n setSpiceSimOptions(options)\n }}\n hasRun={hasSpiceSimRun}\n />\n )}\n {onSchematicComponentClicked &&\n schematicComponentIds.map((componentId) => (\n {\n onSchematicComponentClicked?.({\n schematicComponentId: id,\n event,\n })\n }}\n />\n ))}\n {svgDiv}\n \n \n \n \n \n \n \n \n \n \n \n {showSchematicPorts &&\n schematicPortsInfo.map(({ portId, label }) => (\n {\n onSchematicPortClicked?.({\n schematicPortId: id,\n event,\n })\n }\n : undefined\n }\n />\n ))}\n \n \n )\n}\n","import { su } from \"@tscircuit/soup-util\"\nimport type {\n ManualEditEvent,\n EditSchematicComponentLocationEventWithElement,\n} from \"lib/types/edit-events\"\nimport { type Matrix, compose, applyToPoint } from \"transformation-matrix\"\nimport { useEffect, useRef } from \"react\"\nimport { getComponentOffsetDueToEvents } from \"lib/utils/get-component-offset-due-to-events\"\nimport type { CircuitJson } from \"circuit-json\"\n\n/**\n * This hook automatically applies the edit events to the schematic components\n * inside the svg div.\n *\n * Schematic components are \"\" elements with a \"data-circuit-json-type\"\n * attribute equal to \"schematic_component\", these elements also have a\n * data-schematic-component-id attribute equal to the schematic_component_id\n */\nexport const useChangeSchematicComponentLocationsInSvg = ({\n svgDivRef,\n realToSvgProjection,\n svgToScreenProjection,\n activeEditEvent,\n editEvents,\n}: {\n svgDivRef: React.RefObject\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n activeEditEvent: EditSchematicComponentLocationEventWithElement | null\n editEvents: ManualEditEvent[]\n}) => {\n // Keep track of the last known SVG content\n const lastSvgContentRef = useRef(null)\n\n useEffect(() => {\n const svg = svgDivRef.current\n if (!svg) return\n\n // Create a MutationObserver to watch for changes in the div's content\n const observer = new MutationObserver((mutations) => {\n // Check if the SVG content has changed\n const currentSvgContent = svg.innerHTML\n if (currentSvgContent !== lastSvgContentRef.current) {\n lastSvgContentRef.current = currentSvgContent\n\n // Apply the transforms\n applyTransforms()\n }\n })\n\n // Function to apply transforms to components\n const applyTransforms = () => {\n const componentsThatHaveBeenMoved = new Set()\n for (const event of editEvents) {\n if (\n \"edit_event_type\" in event &&\n event.edit_event_type === \"edit_schematic_component_location\"\n ) {\n componentsThatHaveBeenMoved.add(event.schematic_component_id)\n }\n }\n if (activeEditEvent) {\n componentsThatHaveBeenMoved.add(activeEditEvent.schematic_component_id)\n }\n\n // Reset all transforms\n const allComponents = svg.querySelectorAll(\n '[data-circuit-json-type=\"schematic_component\"]',\n )\n\n for (const component of Array.from(allComponents)) {\n const schematic_component_id = component.getAttribute(\n \"data-schematic-component-id\",\n )!\n\n const offsetMm = getComponentOffsetDueToEvents({\n editEvents: [\n ...editEvents,\n ...(activeEditEvent ? [activeEditEvent] : []),\n ],\n schematic_component_id,\n })\n\n const offsetPx = {\n x: offsetMm.x * realToSvgProjection.a,\n y: offsetMm.y * realToSvgProjection.d,\n }\n\n const style: any = (component as any).style\n style.transform = `translate(${offsetPx.x}px, ${offsetPx.y}px)`\n if (\n activeEditEvent?.schematic_component_id === schematic_component_id\n ) {\n style.outline = \"solid 2px rgba(255,0,0,0.5)\"\n style.outlineOffset = \"5px\"\n } else if (style.outline) {\n style.outline = \"\"\n }\n }\n }\n\n // Start observing the div for changes\n observer.observe(svg, {\n childList: true, // Watch for changes to the child elements\n subtree: false, // Watch for changes in the entire subtree\n characterData: false, // Watch for changes to text content\n })\n\n // Apply transforms immediately on mount or when editEvents change\n applyTransforms()\n\n // Cleanup function\n return () => {\n observer.disconnect()\n }\n }, [svgDivRef, editEvents, activeEditEvent]) // Dependencies remain the same\n}\n","import type {\n EditSchematicComponentLocationEvent,\n EditSchematicComponentLocationEventWithElement,\n ManualEditEvent,\n} from \"lib/types/edit-events\"\n\n/**\n * Returns the total offset of a component due to a set of edit events in\n * mm\n */\nexport const getComponentOffsetDueToEvents = ({\n editEvents,\n schematic_component_id,\n}: {\n editEvents: ManualEditEvent[]\n schematic_component_id: string\n}) => {\n const editEventsForComponent: EditSchematicComponentLocationEvent[] =\n editEvents\n .filter(\n (event) =>\n \"schematic_component_id\" in event &&\n event.schematic_component_id === schematic_component_id,\n )\n .filter(\n (event) =>\n \"edit_event_type\" in event &&\n event.edit_event_type === \"edit_schematic_component_location\",\n )\n\n const totalOffsetX = editEventsForComponent.reduce((acc, event) => {\n return acc + event.new_center.x - event.original_center.x\n }, 0)\n\n const totalOffsetY = editEventsForComponent.reduce((acc, event) => {\n return acc + event.new_center.y - event.original_center.y\n }, 0)\n\n return {\n x: totalOffsetX,\n y: totalOffsetY,\n }\n}\n","import { useEffect, useRef } from \"react\"\nimport { su } from \"@tscircuit/soup-util\"\nimport type { ManualEditEvent } from \"../types/edit-events\"\nimport type { CircuitJson } from \"circuit-json\"\n\n/**\n * This hook makes traces dashed when their connected components are being moved\n */\nexport const useChangeSchematicTracesForMovedComponents = ({\n svgDivRef,\n circuitJson,\n activeEditEvent,\n editEvents,\n}: {\n svgDivRef: React.RefObject\n circuitJson: CircuitJson\n activeEditEvent: ManualEditEvent | null\n editEvents: ManualEditEvent[]\n}) => {\n // Keep track of the last known SVG content\n const lastSvgContentRef = useRef(null)\n\n useEffect(() => {\n const svg = svgDivRef.current\n if (!svg) return\n\n const updateTraceStyles = () => {\n // Reset all traces to solid\n const allTraces = svg.querySelectorAll(\n '[data-circuit-json-type=\"schematic_trace\"] path',\n )\n\n // Reset all traces to solid\n for (const trace of Array.from(allTraces)) {\n trace.setAttribute(\"stroke-dasharray\", \"0\")\n ;(trace as any).style.animation = \"\"\n }\n\n // If there's an active edit event, make connected traces dashed\n for (const editEvent of [\n ...editEvents,\n ...(activeEditEvent ? [activeEditEvent] : []),\n ]) {\n if (\n \"schematic_component_id\" in editEvent &&\n editEvent.edit_event_type === \"edit_schematic_component_location\"\n ) {\n const sch_component = su(circuitJson).schematic_component.get(\n editEvent.schematic_component_id,\n )\n if (!sch_component) return\n\n const src_ports = su(circuitJson).source_port.list({\n source_component_id: sch_component.source_component_id,\n })\n const src_port_ids = new Set(src_ports.map((sp) => sp.source_port_id))\n const src_traces = su(circuitJson)\n .source_trace.list()\n .filter((st) =>\n st.connected_source_port_ids?.some((spi: string) =>\n src_port_ids.has(spi),\n ),\n )\n const src_trace_ids = new Set(\n src_traces.map((st) => st.source_trace_id),\n )\n const schematic_traces = su(circuitJson)\n .schematic_trace.list()\n .filter((st) => src_trace_ids.has(st.source_trace_id!))\n\n // Make the connected traces dashed\n schematic_traces.forEach((trace) => {\n const traceElements = svg.querySelectorAll(\n `[data-schematic-trace-id=\"${trace.schematic_trace_id}\"] path`,\n )\n for (const traceElement of Array.from(traceElements)) {\n if (traceElement.getAttribute(\"class\")?.includes(\"invisible\"))\n continue\n traceElement.setAttribute(\"stroke-dasharray\", \"20,20\")\n ;(traceElement as any).style.animation =\n \"dash-animation 350ms linear infinite, pulse-animation 900ms linear infinite\"\n\n if (!svg.querySelector(\"style#dash-animation\")) {\n const style = document.createElement(\"style\")\n style.id = \"dash-animation\"\n style.textContent = `\n @keyframes dash-animation {\n to {\n stroke-dashoffset: -40;\n }\n }\n @keyframes pulse-animation {\n 0% { opacity: 0.6; }\n 50% { opacity: 0.2; }\n 100% { opacity: 0.6; }\n }\n `\n svg.appendChild(style)\n }\n }\n })\n }\n }\n }\n\n // Apply styles immediately\n updateTraceStyles()\n\n // Cleanup function\n const observer = new MutationObserver(updateTraceStyles)\n observer.observe(svg, {\n childList: true, // Watch for changes to the child elements\n subtree: false, // Watch for changes in the entire subtree\n characterData: false, // Watch for changes to text content\n })\n\n return () => {\n observer.disconnect()\n }\n }, [svgDivRef, activeEditEvent, circuitJson, editEvents])\n}\n","import { useEffect } from \"react\"\nimport { su } from \"@tscircuit/soup-util\"\nimport type { CircuitJson } from \"circuit-json\"\n\ninterface UseSchematicGroupsOverlayOptions {\n svgDivRef: React.RefObject\n circuitJson: CircuitJson\n circuitJsonKey: string\n showGroups: boolean\n}\n\nconst GROUP_COLORS = [\n \"#8B0000\", // Dark Red\n \"#2F4F4F\", // Dark Slate Gray\n \"#191970\", // Midnight Blue\n \"#006400\", // Dark Green\n \"#FF4500\", // Dark Orange\n \"#800080\", // Purple\n \"#2E8B57\", // Sea Green\n \"#B8860B\", // Dark Goldenrod\n \"#C71585\", // Medium Violet Red\n \"#008B8B\", // Dark Cyan\n]\n\nexport const useSchematicGroupsOverlay = (\n options: UseSchematicGroupsOverlayOptions,\n) => {\n const { svgDivRef, circuitJson, circuitJsonKey, showGroups } = options\n\n useEffect(() => {\n // Always clean up existing overlays first\n if (svgDivRef.current) {\n const existingOverlays = svgDivRef.current.querySelectorAll(\n \".schematic-group-overlay\",\n )\n existingOverlays.forEach((overlay) => overlay.remove())\n }\n\n if (\n !svgDivRef.current ||\n !showGroups ||\n !circuitJson ||\n circuitJson.length === 0\n ) {\n return\n }\n\n // Small delay to ensure SVG is rendered when groups are enabled from localStorage\n const timeoutId = setTimeout(() => {\n if (!svgDivRef.current) return\n\n const svg = svgDivRef.current.querySelector(\"svg\")\n if (!svg) {\n return\n }\n\n const existingOverlays = svg.querySelectorAll(\".schematic-group-overlay\")\n existingOverlays.forEach((overlay) => overlay.remove())\n\n try {\n const sourceGroups =\n su(circuitJson)\n .source_group?.list()\n .filter((x) => !!!x.is_subcircuit) || []\n const schematicComponents =\n su(circuitJson).schematic_component?.list() || []\n\n const sourceGroupHierarchy = new Map()\n sourceGroups.forEach((group) => {\n const groupWithParent = group as any\n if (groupWithParent.parent_source_group_id) {\n const children =\n sourceGroupHierarchy.get(\n groupWithParent.parent_source_group_id,\n ) || []\n children.push(group.source_group_id)\n sourceGroupHierarchy.set(\n groupWithParent.parent_source_group_id,\n children,\n )\n }\n })\n\n const getAllDescendantSourceGroups = (\n sourceGroupId: string,\n ): string[] => {\n const descendants: string[] = []\n const children = sourceGroupHierarchy.get(sourceGroupId) || []\n for (const child of children) {\n descendants.push(child)\n descendants.push(...getAllDescendantSourceGroups(child))\n }\n return descendants\n }\n\n const getGroupDepthLevel = (sourceGroupId: string): number => {\n const groupWithParent = sourceGroups.find(\n (g) => g.source_group_id === sourceGroupId,\n ) as any\n if (!groupWithParent?.parent_source_group_id) {\n return 0\n }\n return 1 + getGroupDepthLevel(groupWithParent.parent_source_group_id)\n }\n\n const hasMeaningfulGroups =\n sourceGroups.length > 0 &&\n sourceGroups.some((group) => group.name && group.name.trim() !== \"\")\n\n let groupsToRender: Array<{\n id: string\n name: string\n components: any[]\n color: string\n depthLevel: number\n hasChildren: boolean\n sourceGroupId?: string\n }> = []\n\n if (hasMeaningfulGroups) {\n const groupMap = new Map()\n\n for (const comp of schematicComponents) {\n const sourceComp = su(circuitJson).source_component.get(\n comp.source_component_id!,\n )\n if (sourceComp?.source_group_id) {\n if (!groupMap.has(sourceComp.source_group_id)) {\n groupMap.set(sourceComp.source_group_id, [])\n }\n groupMap.get(sourceComp.source_group_id)!.push(comp)\n }\n }\n\n sourceGroups.forEach((group, index) => {\n let groupComponents = groupMap.get(group.source_group_id) || []\n\n const descendantGroups = getAllDescendantSourceGroups(\n group.source_group_id,\n )\n for (const descendantGroupId of descendantGroups) {\n const descendantComponents = groupMap.get(descendantGroupId) || []\n groupComponents = [...groupComponents, ...descendantComponents]\n }\n\n if (groupComponents.length > 0) {\n const depthLevel = getGroupDepthLevel(group.source_group_id)\n const hasChildren =\n getAllDescendantSourceGroups(group.source_group_id).length > 0\n\n if (group.name?.startsWith(\"unnamed_board\")) return\n groupsToRender.push({\n id: group.source_group_id,\n name: group.name || `Group ${index + 1}`,\n components: groupComponents,\n color: GROUP_COLORS[index % GROUP_COLORS.length],\n depthLevel,\n hasChildren,\n sourceGroupId: group.source_group_id,\n })\n }\n })\n }\n // else {\n // const componentTypeGroups = new Map()\n\n // for (const comp of schematicComponents) {\n // const sourceComp = su(circuitJson).source_component.get(comp.source_component_id)\n // if (sourceComp) {\n // const componentType = sourceComp.ftype || \"other\"\n // if (!componentTypeGroups.has(componentType)) {\n // componentTypeGroups.set(componentType, [])\n // }\n // componentTypeGroups.get(componentType)!.push(comp)\n // }\n // }\n // // groupsToRender = Array.from(componentTypeGroups.entries()).map(\n // // ([type, components], index) => ({\n // // id: `type_${type}`,\n // // name: `${type.charAt(0).toUpperCase() + type.slice(1)}s`,\n // // components,\n // // color: GROUP_COLORS[index % GROUP_COLORS.length],\n // // depthLevel: 0,\n // // hasChildren: false,\n // // }),\n // // )\n // }\n\n const viewBox = svg.viewBox.baseVal\n const svgRect = svg.getBoundingClientRect()\n const scale =\n Math.min(\n svgRect.width / viewBox.width,\n svgRect.height / viewBox.height,\n ) || 1\n\n groupsToRender.sort((a, b) => a.depthLevel - b.depthLevel)\n\n groupsToRender.forEach((group) => {\n if (group.components.length === 0) return\n\n const groupBounds = calculateGroupBounds(group.components, svg)\n if (!groupBounds) return\n\n const basePadding = Math.max(\n 8,\n Math.min(25, 15 / Math.max(scale, 0.3)),\n )\n const hierarchyPadding = group.hasChildren ? basePadding * 0.6 : 0\n const totalPadding = basePadding + hierarchyPadding\n\n const baseStrokeWidth = Math.max(1, 2 / Math.max(scale, 0.5))\n const strokeWidth =\n group.depthLevel === 0 ? baseStrokeWidth : baseStrokeWidth * 0.7\n\n const baseDashSize = Math.max(4, 8 / Math.max(scale, 0.5))\n const dashMultiplier = group.hasChildren ? 1.3 : 1\n const dashSize = baseDashSize * dashMultiplier\n const gapSize = dashSize * 0.5\n\n const groupOverlay = document.createElementNS(\n \"http://www.w3.org/2000/svg\",\n \"rect\",\n )\n groupOverlay.setAttribute(\"class\", \"schematic-group-overlay\")\n groupOverlay.setAttribute(\n \"x\",\n (groupBounds.minX - totalPadding).toString(),\n )\n groupOverlay.setAttribute(\n \"y\",\n (groupBounds.minY - totalPadding).toString(),\n )\n groupOverlay.setAttribute(\n \"width\",\n (groupBounds.maxX - groupBounds.minX + totalPadding * 2).toString(),\n )\n groupOverlay.setAttribute(\n \"height\",\n (groupBounds.maxY - groupBounds.minY + totalPadding * 2).toString(),\n )\n groupOverlay.setAttribute(\"fill\", \"none\")\n groupOverlay.setAttribute(\"stroke\", group.color)\n groupOverlay.setAttribute(\"stroke-width\", strokeWidth.toString())\n groupOverlay.setAttribute(\n \"stroke-dasharray\",\n `${dashSize},${gapSize}`,\n )\n groupOverlay.setAttribute(\"opacity\", \"0.8\")\n groupOverlay.setAttribute(\"rx\", \"0\")\n groupOverlay.setAttribute(\"ry\", \"0\")\n\n const baseFontSize = Math.max(\n 6,\n Math.min(20, 14 / Math.max(scale, 0.2)),\n )\n const fontSizeReduction =\n group.depthLevel === 0 || group.depthLevel === 1\n ? 0\n : group.depthLevel * 0.2\n const fontSize = baseFontSize * (1 - fontSizeReduction)\n\n const labelPadding = Math.max(1, fontSize * 0.2)\n const labelText = group.name\n\n const tempText = document.createElementNS(\n \"http://www.w3.org/2000/svg\",\n \"text\",\n )\n tempText.setAttribute(\"font-size\", fontSize.toString())\n tempText.setAttribute(\"font-family\", \"Arial, sans-serif\")\n tempText.textContent = labelText\n svg.appendChild(tempText)\n const textBBox = tempText.getBBox()\n svg.removeChild(tempText)\n\n const labelWidth = textBBox.width + labelPadding * 2\n const labelHeight = fontSize + labelPadding * 2\n const labelX = groupBounds.minX - totalPadding\n const labelY = groupBounds.minY - totalPadding - labelHeight\n\n const labelBg = document.createElementNS(\n \"http://www.w3.org/2000/svg\",\n \"rect\",\n )\n labelBg.setAttribute(\"class\", \"schematic-group-overlay\")\n labelBg.setAttribute(\"x\", labelX.toString())\n labelBg.setAttribute(\"y\", (labelY - labelHeight).toString())\n labelBg.setAttribute(\"width\", labelWidth.toString())\n labelBg.setAttribute(\"height\", labelHeight.toString())\n labelBg.setAttribute(\"fill\", \"transparent\")\n labelBg.setAttribute(\"rx\", \"0\")\n labelBg.setAttribute(\"ry\", \"0\")\n\n const groupLabel = document.createElementNS(\n \"http://www.w3.org/2000/svg\",\n \"text\",\n )\n groupLabel.setAttribute(\"class\", \"schematic-group-overlay\")\n groupLabel.setAttribute(\"x\", (labelX + labelPadding).toString())\n groupLabel.setAttribute(\n \"y\",\n (labelY + labelHeight - labelPadding).toString(),\n )\n groupLabel.setAttribute(\"fill\", group.color)\n groupLabel.setAttribute(\"font-size\", fontSize.toString())\n groupLabel.setAttribute(\"font-family\", \"Arial, sans-serif\")\n groupLabel.setAttribute(\n \"font-weight\",\n group.depthLevel === 0 ? \"600\" : \"500\",\n )\n groupLabel.setAttribute(\"stroke\", group.color)\n groupLabel.setAttribute(\n \"stroke-width\",\n Math.max(0.2, fontSize * 0.02).toString(),\n )\n groupLabel.textContent = labelText\n\n svg.appendChild(groupOverlay)\n svg.appendChild(labelBg)\n svg.appendChild(groupLabel)\n })\n } catch (error) {\n console.error(\"Error creating group overlays:\", error)\n }\n }, 10) // Small delay to ensure SVG is ready\n\n return () => clearTimeout(timeoutId)\n }, [svgDivRef, circuitJsonKey, showGroups])\n}\n\nfunction calculateGroupBounds(components: any[], svg: SVGElement) {\n let minX = Infinity,\n minY = Infinity,\n maxX = -Infinity,\n maxY = -Infinity\n\n for (const component of components) {\n let componentElement = svg.querySelector(\n `g[data-schematic-component-id=\"${component.schematic_component_id}\"]`,\n )\n\n if (!componentElement) {\n componentElement = svg.querySelector(\n `[data-schematic-component-id=\"${component.schematic_component_id}\"]`,\n )\n }\n\n if (componentElement) {\n const bbox = (componentElement as SVGGraphicsElement).getBBox()\n minX = Math.min(minX, bbox.x)\n minY = Math.min(minY, bbox.y)\n maxX = Math.max(maxX, bbox.x + bbox.width)\n maxY = Math.max(maxY, bbox.y + bbox.height)\n }\n }\n\n if (minX === Infinity) {\n return null\n }\n\n const bounds = { minX, minY, maxX, maxY }\n return bounds\n}\n","import { su } from \"@tscircuit/soup-util\"\nimport type { CircuitJson } from \"circuit-json\"\nimport { useEffect } from \"react\"\n\nconst FADED_CLASS = \"sch-net-faded\"\n\nconst TRACE_SELECTOR =\n \"g.trace[data-subcircuit-connectivity-map-key], g.trace-overlays[data-subcircuit-connectivity-map-key]\"\n\nconst NET_LABEL_SELECTOR = \"[data-schematic-net-label-id]\"\n\n/**\n * Net highlighting on hover, done entirely in JS (the base SVG carries no\n * interaction). Hovering a wire or a net label fades every element that is NOT\n * part of that net — other nets' traces/labels and chips not connected to the\n * net — so the hovered net stands out by contrast rather than by recoloring.\n *\n * Identifies elements by the attributes circuit-to-svg emits:\n * - traces: g.trace[data-subcircuit-connectivity-map-key] (+ g.trace-overlays)\n * - components: g[data-schematic-component-id]\n * - net labels: [data-schematic-net-label-id] (per element, no wrapping group)\n *\n * Faded elements get the `sch-net-faded` class (styled by SchematicViewer).\n */\nexport const useSchematicNetHover = ({\n svgDivRef,\n circuitJson,\n circuitJsonKey,\n enabled,\n}: {\n svgDivRef: React.RefObject\n circuitJson: CircuitJson\n circuitJsonKey: string\n enabled: boolean\n}) => {\n useEffect(() => {\n const svgDiv = svgDivRef.current\n if (!enabled || !svgDiv) return\n\n const { componentIdToKeys, netLabelIdToKey } = buildNetRegistry(circuitJson)\n\n // Every net element and the net key(s) it belongs to, plus each hover\n // trigger's net key. Rebuilt from the SVG whenever it re-renders; the\n // listeners below read them by closure.\n let netElements: Array<{ el: Element; keys: Set }> = []\n const triggerNetKeys = new Map()\n let hoveredNetKey: string | null = null\n\n const collectNetElements = () => {\n for (const { el } of netElements) el.classList.remove(FADED_CLASS)\n netElements = []\n triggerNetKeys.clear()\n hoveredNetKey = null\n\n const svg = svgDiv.querySelector(\"svg\")\n if (!svg) return\n\n for (const el of Array.from(svg.querySelectorAll(TRACE_SELECTOR))) {\n const key = el.getAttribute(\"data-subcircuit-connectivity-map-key\")\n const keys = new Set()\n if (key) {\n keys.add(key)\n triggerNetKeys.set(el, key)\n }\n netElements.push({ el, keys })\n }\n for (const el of Array.from(\n svg.querySelectorAll(\"g[data-schematic-component-id]\"),\n )) {\n const id = el.getAttribute(\"data-schematic-component-id\")!\n netElements.push({ el, keys: componentIdToKeys.get(id) ?? new Set() })\n }\n for (const el of Array.from(svg.querySelectorAll(NET_LABEL_SELECTOR))) {\n const key = netLabelIdToKey.get(\n el.getAttribute(\"data-schematic-net-label-id\")!,\n )\n const keys = new Set()\n if (key) {\n keys.add(key)\n triggerNetKeys.set(el, key)\n }\n netElements.push({ el, keys })\n }\n }\n\n // Fade everything not on `key` (null clears the fade).\n const highlightNet = (key: string | null) => {\n if (key === hoveredNetKey) return\n hoveredNetKey = key\n for (const { el, keys } of netElements) {\n el.classList.toggle(FADED_CLASS, key !== null && !keys.has(key))\n }\n }\n\n const handleMouseOver = (e: Event) => {\n const target = e.target\n if (!(target instanceof Element)) {\n highlightNet(null)\n return\n }\n const trigger = target.closest(`${TRACE_SELECTOR}, ${NET_LABEL_SELECTOR}`)\n if (!trigger) {\n highlightNet(null)\n return\n }\n highlightNet(triggerNetKeys.get(trigger) ?? null)\n }\n const handleMouseLeave = () => highlightNet(null)\n\n collectNetElements()\n svgDiv.addEventListener(\"mouseover\", handleMouseOver)\n svgDiv.addEventListener(\"mouseleave\", handleMouseLeave)\n\n // dangerouslySetInnerHTML replaces the node when the svg string\n // re-renders, so recollect against the fresh DOM. The listeners stay on the\n // stable svgDiv, so they don't need re-attaching.\n const observer = new MutationObserver(collectNetElements)\n observer.observe(svgDiv, { childList: true })\n\n return () => {\n observer.disconnect()\n svgDiv.removeEventListener(\"mouseover\", handleMouseOver)\n svgDiv.removeEventListener(\"mouseleave\", handleMouseLeave)\n for (const { el } of netElements) el.classList.remove(FADED_CLASS)\n }\n // Keyed on circuitJsonKey (content hash) rather than the circuitJson\n // reference, matching the other post-render SVG hooks.\n }, [svgDivRef, circuitJsonKey, enabled])\n}\n\n/**\n * Derives, from the circuit JSON, the net membership needed to relate DOM\n * elements to nets:\n * - componentIdToKeys: which connectivity nets each schematic component touches\n * - netLabelIdToKey: a schematic_net_label_id -> its connectivity key\n */\nfunction buildNetRegistry(circuitJson: CircuitJson) {\n const cju = su(circuitJson)\n\n // source_component_id -> schematic_component_id\n const srcCompToSchComp = new Map()\n for (const c of cju.schematic_component.list()) {\n if (c.source_component_id) {\n srcCompToSchComp.set(c.source_component_id, c.schematic_component_id)\n }\n }\n\n // schematic_component_id -> the connectivity nets its ports belong to (a chip\n // sits on several nets). The connectivity key lives on source_trace.\n const componentIdToKeys = new Map>()\n for (const sourceTrace of cju.source_trace.list()) {\n const key = sourceTrace.subcircuit_connectivity_map_key\n if (!key) continue\n for (const portId of sourceTrace.connected_source_port_ids ?? []) {\n const schCompId = srcCompToSchComp.get(\n cju.source_port.get(portId)?.source_component_id ?? \"\",\n )\n if (!schCompId) continue\n if (!componentIdToKeys.has(schCompId)) {\n componentIdToKeys.set(schCompId, new Set())\n }\n componentIdToKeys.get(schCompId)!.add(key)\n }\n }\n\n // schematic_net_label_id -> connectivity key, resolved via its source_net\n // (same key the net's traces use). Falls back to source_net_id, which already\n // *is* the key for auto-emitted labels on unrouted nets (no source_net).\n const netLabelIdToKey = new Map()\n for (const label of cju.schematic_net_label.list()) {\n if (!label.source_net_id) continue\n const key =\n cju.source_net.get(label.source_net_id)\n ?.subcircuit_connectivity_map_key ?? label.source_net_id\n netLabelIdToKey.set(label.schematic_net_label_id, key)\n }\n\n return { componentIdToKeys, netLabelIdToKey }\n}\n","import Debug from \"debug\"\n\nexport const debug = Debug(\"schematic-viewer\")\n\nexport const enableDebug = () => {\n Debug.enable(\"schematic-viewer*\")\n}\n\nexport default debug\n","import { useEffect, useState } from \"react\"\n\nexport const useResizeHandling = (\n containerRef: React.RefObject,\n) => {\n const [containerWidth, setContainerWidth] = useState(0)\n const [containerHeight, setContainerHeight] = useState(0)\n\n useEffect(() => {\n if (!containerRef.current) return\n\n const updateDimensions = () => {\n const rect = containerRef.current?.getBoundingClientRect()\n setContainerWidth(rect?.width || 0)\n setContainerHeight(rect?.height || 0)\n }\n\n // Set initial dimensions\n updateDimensions()\n\n // Add resize listener\n const resizeObserver = new ResizeObserver(updateDimensions)\n resizeObserver.observe(containerRef.current)\n\n // Fallback to window resize\n window.addEventListener(\"resize\", updateDimensions)\n\n return () => {\n resizeObserver.disconnect()\n window.removeEventListener(\"resize\", updateDimensions)\n }\n }, [])\n\n return { containerWidth, containerHeight }\n}\n","import { su } from \"@tscircuit/soup-util\"\nimport Debug from \"lib/utils/debug\"\nimport { getComponentOffsetDueToEvents } from \"lib/utils/get-component-offset-due-to-events\"\nimport { useCallback, useEffect, useRef, useState } from \"react\"\nimport { type Matrix, compose } from \"transformation-matrix\"\nimport type {\n EditSchematicComponentLocationEventWithElement,\n ManualEditEvent,\n} from \"../types/edit-events\"\n\nconst debug = Debug.extend(\"useComponentDragging\")\n\nexport const useComponentDragging = ({\n onEditEvent,\n editEvents = [],\n circuitJson,\n cancelDrag,\n svgToScreenProjection,\n realToSvgProjection,\n enabled = false,\n snapToGrid = false,\n}: {\n circuitJson: any[]\n editEvents: ManualEditEvent[]\n /** The projection returned from use-mouse-matrix-transform, indicating zoom on svg */\n svgToScreenProjection: Matrix\n /** The projection returned from circuit-to-svg, mm to svg */\n realToSvgProjection: Matrix\n onEditEvent?: (event: ManualEditEvent) => void\n cancelDrag?: () => void\n enabled?: boolean\n snapToGrid?: boolean\n}): {\n handleMouseDown: (e: React.MouseEvent) => void\n handleTouchStart: (e: React.TouchEvent) => void\n isDragging: boolean\n activeEditEvent: EditSchematicComponentLocationEventWithElement | null\n} => {\n const [activeEditEvent, setActiveEditEvent] =\n useState(null)\n const realToScreenProjection = compose(\n realToSvgProjection,\n svgToScreenProjection,\n )\n\n /**\n * Drag start position in screen space\n */\n const dragStartPosRef = useRef<{\n x: number\n y: number\n } | null>(null)\n\n const activeEditEventRef =\n useRef(null)\n\n // Store the latest positions of components being tracked\n const componentPositionsRef = useRef>(\n new Map(),\n )\n\n // Update position map with the latest positions from edit events\n useEffect(() => {\n // Process completed edit events to track latest positions\n editEvents.forEach((event) => {\n if (\n \"edit_event_type\" in event &&\n event.edit_event_type === \"edit_schematic_component_location\" &&\n !event.in_progress\n ) {\n componentPositionsRef.current.set(event.schematic_component_id, {\n ...event.new_center,\n })\n }\n })\n }, [editEvents])\n\n const startDrag = useCallback(\n (clientX: number, clientY: number, target: Element) => {\n if (!enabled) return false\n\n const componentGroup = target.closest(\n '[data-circuit-json-type=\"schematic_component\"]',\n )\n if (!componentGroup) return false\n\n const schematic_component_id = componentGroup.getAttribute(\n \"data-schematic-component-id\",\n )\n if (!schematic_component_id) return false\n\n if (cancelDrag) cancelDrag()\n\n const schematic_component = su(circuitJson).schematic_component.get(\n schematic_component_id,\n )\n if (!schematic_component) return false\n\n dragStartPosRef.current = { x: clientX, y: clientY }\n\n let current_position: { x: number; y: number }\n const trackedPosition = componentPositionsRef.current.get(\n schematic_component_id,\n )\n\n if (trackedPosition) {\n current_position = { ...trackedPosition }\n } else {\n const editEventOffset = getComponentOffsetDueToEvents({\n editEvents,\n schematic_component_id: schematic_component_id,\n })\n\n current_position = {\n x: schematic_component.center.x + editEventOffset.x,\n y: schematic_component.center.y + editEventOffset.y,\n }\n\n componentPositionsRef.current.set(schematic_component_id, {\n ...current_position,\n })\n }\n\n const newEditEvent: EditSchematicComponentLocationEventWithElement = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_component_location\",\n schematic_component_id: schematic_component_id,\n original_center: current_position,\n new_center: { ...current_position },\n in_progress: true,\n created_at: Date.now(),\n _element: componentGroup as any,\n }\n\n activeEditEventRef.current = newEditEvent\n setActiveEditEvent(newEditEvent)\n return true\n },\n [cancelDrag, enabled, circuitJson, editEvents],\n )\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n startDrag(e.clientX, e.clientY, e.target as Element)\n },\n [startDrag],\n )\n\n const handleTouchStart = useCallback(\n (e: React.TouchEvent) => {\n if (e.touches.length !== 1) return\n const touch = e.touches[0]\n if (startDrag(touch.clientX, touch.clientY, e.target as Element)) {\n e.preventDefault()\n }\n },\n [startDrag],\n )\n\n const updateDragPosition = useCallback(\n (clientX: number, clientY: number) => {\n if (!activeEditEventRef.current || !dragStartPosRef.current) return\n\n const screenDelta = {\n x: clientX - dragStartPosRef.current.x,\n y: clientY - dragStartPosRef.current.y,\n }\n\n const mmDelta = {\n x: screenDelta.x / realToScreenProjection.a,\n y: screenDelta.y / realToScreenProjection.d,\n }\n\n let newCenter = {\n x: activeEditEventRef.current.original_center.x + mmDelta.x,\n y: activeEditEventRef.current.original_center.y + mmDelta.y,\n }\n if (snapToGrid) {\n const snap = (v: number) => Math.round(v * 10) / 10\n newCenter = { x: snap(newCenter.x), y: snap(newCenter.y) }\n }\n\n const newEditEvent = {\n ...activeEditEventRef.current,\n new_center: newCenter,\n }\n\n activeEditEventRef.current = newEditEvent\n setActiveEditEvent(newEditEvent)\n },\n [realToScreenProjection, snapToGrid],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => updateDragPosition(e.clientX, e.clientY),\n [updateDragPosition],\n )\n\n const handleTouchMove = useCallback(\n (e: TouchEvent) => {\n if (e.touches.length !== 1 || !activeEditEventRef.current) return\n e.preventDefault()\n const touch = e.touches[0]\n updateDragPosition(touch.clientX, touch.clientY)\n },\n [updateDragPosition],\n )\n\n const endDrag = useCallback(() => {\n if (!activeEditEventRef.current) return\n const finalEvent = {\n ...activeEditEventRef.current,\n in_progress: false,\n }\n\n componentPositionsRef.current.set(finalEvent.schematic_component_id, {\n ...finalEvent.new_center,\n })\n\n debug(\"endDrag calling onEditEvent with new edit event\", {\n newEditEvent: finalEvent,\n })\n if (onEditEvent) onEditEvent(finalEvent)\n activeEditEventRef.current = null\n dragStartPosRef.current = null\n setActiveEditEvent(null)\n }, [onEditEvent])\n\n const handleMouseUp = useCallback(() => endDrag(), [endDrag])\n const handleTouchEnd = useCallback(() => endDrag(), [endDrag])\n\n useEffect(() => {\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mouseup\", handleMouseUp)\n window.addEventListener(\"touchmove\", handleTouchMove, { passive: false })\n window.addEventListener(\"touchend\", handleTouchEnd)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mouseup\", handleMouseUp)\n window.removeEventListener(\"touchmove\", handleTouchMove)\n window.removeEventListener(\"touchend\", handleTouchEnd)\n }\n }, [handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd])\n\n return {\n handleMouseDown,\n handleTouchStart,\n isDragging: !!activeEditEventRef.current,\n activeEditEvent: activeEditEvent,\n }\n}\n","export const zIndexMap = {\n schematicEditIcon: 50,\n schematicGridIcon: 49,\n spiceSimulationIcon: 50,\n viewMenuIcon: 48,\n viewMenu: 55,\n viewMenuBackdrop: 54,\n clickToInteractOverlay: 100,\n schematicComponentHoverOutline: 47,\n schematicPortHoverOutline: 48,\n}\n","import { zIndexMap } from \"../utils/z-index-map\"\n\nexport const EditIcon = ({\n onClick,\n active,\n}: { onClick: () => void; active: boolean }) => {\n const handleInteraction = (e: React.MouseEvent | React.TouchEvent) => {\n e.preventDefault()\n onClick()\n }\n\n return (\n \n \n \n \n \n \n )\n}\n","import { zIndexMap } from \"../utils/z-index-map\"\n\nexport const GridIcon = ({\n onClick,\n active,\n}: { onClick: () => void; active: boolean }) => {\n const handleInteraction = (e: React.MouseEvent | React.TouchEvent) => {\n e.preventDefault()\n onClick()\n }\n\n return (\n \n \n \n \n \n )\n}\n","import { useMemo } from \"react\"\nimport { su } from \"@tscircuit/soup-util\"\nimport type { CircuitJson } from \"circuit-json\"\nimport * as DropdownMenu from \"@radix-ui/react-dropdown-menu\"\nimport { zIndexMap } from \"../utils/z-index-map\"\nimport packageJson from \"../../package.json\"\nimport { ViewMenuIcon } from \"./ViewMenuIcon\"\n\ninterface ViewMenuProps {\n circuitJson: CircuitJson\n circuitJsonKey: string\n open: boolean\n onOpenChange: (open: boolean) => void\n showGroups: boolean\n onToggleGroups: (show: boolean) => void\n showGrid: boolean\n onToggleGrid: (show: boolean) => void\n}\n\nconst FONT_FAMILY =\n 'system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif'\n\nconst contentStyles: React.CSSProperties = {\n backgroundColor: \"#ffffff\",\n color: \"#111111\",\n borderRadius: 8,\n boxShadow: \"0 6px 24px rgba(0,0,0,0.12), 0 1px 3px rgba(0,0,0,0.08)\",\n border: \"1px solid #e5e7eb\",\n padding: 4,\n minWidth: 224,\n fontSize: 13,\n fontFamily: FONT_FAMILY,\n outline: \"none\",\n zIndex: zIndexMap.viewMenu,\n}\n\nconst itemStyles: React.CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n padding: \"7px 10px 7px 8px\",\n borderRadius: 6,\n cursor: \"pointer\",\n outline: \"none\",\n userSelect: \"none\",\n color: \"#111111\",\n fontSize: 13,\n fontFamily: FONT_FAMILY,\n}\n\nconst iconSlotStyles: React.CSSProperties = {\n width: 16,\n height: 16,\n flexShrink: 0,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n color: \"#111111\",\n}\n\nconst separatorStyles: React.CSSProperties = {\n height: 1,\n backgroundColor: \"#ececec\",\n margin: \"4px 0\",\n}\n\nconst HIGHLIGHT_CSS = `\n.sv-vm-item[data-highlighted]:not([data-disabled]),\n.sv-vm-item:hover:not([data-disabled]) { background-color: #f1f3f5; }\n.sv-vm-item[data-disabled] { opacity: 0.45; cursor: not-allowed; }\n`\n\nconst CheckIcon = () => (\n \n \n \n)\n\nexport const ViewMenu = ({\n circuitJson,\n circuitJsonKey,\n open,\n onOpenChange,\n showGroups,\n onToggleGroups,\n showGrid,\n onToggleGrid,\n}: ViewMenuProps) => {\n const hasGroups = useMemo(() => {\n if (!circuitJson || circuitJson.length === 0) return false\n\n try {\n // Check if there are explicit groups\n const sourceGroups = su(circuitJson).source_group?.list() || []\n if (sourceGroups.length > 0) return true\n\n // Check if we can create virtual groups by component type\n const schematicComponents =\n su(circuitJson).schematic_component?.list() || []\n if (schematicComponents.length > 1) {\n const componentTypes = new Set()\n for (const comp of schematicComponents) {\n const sourceComp = su(circuitJson).source_component.get(\n comp.source_component_id!,\n )\n if (sourceComp?.ftype) {\n componentTypes.add(sourceComp.ftype)\n }\n }\n return componentTypes.size > 1 // Only show if there are multiple types\n }\n\n return false\n } catch (error) {\n console.error(\"Error checking for groups:\", error)\n return false\n }\n }, [circuitJsonKey])\n\n return (\n \n \n \n \n\n \n \n \n\n {/* View toggles */}\n e.preventDefault()}\n onPointerUp={() => {\n if (hasGroups) onToggleGroups(!showGroups)\n }}\n >\n {showGroups && }\n View Schematic Groups\n \n\n e.preventDefault()}\n onPointerUp={() => onToggleGrid(!showGrid)}\n >\n {showGrid && }\n Show Grid\n \n\n \n\n {/* Version */}\n \n v{String(packageJson?.version)}\n \n \n \n \n )\n}\n","{\n \"name\": \"@tscircuit/schematic-viewer\",\n \"version\": \"2.0.69\",\n \"main\": \"dist/index.js\",\n \"type\": \"module\",\n \"scripts\": {\n \"start\": \"cosmos\",\n \"build:webworker\": \"tsup --config tsup-webworker.config.ts\",\n \"build:blob-url\": \"bun scripts/build-worker-blob-url.ts\",\n \"build\": \"bun run build:webworker && bun run build:blob-url && tsup-node ./lib/index.ts --dts --format esm --sourcemap\",\n \"build:site\": \"cosmos-export\",\n \"vercel-build\": \"bun run build:webworker && bun run build:blob-url && bun run build:site\",\n \"format\": \"biome format --write .\",\n \"format:check\": \"biome format .\"\n },\n \"files\": [\n \"dist\"\n ],\n \"devDependencies\": {\n \"@biomejs/biome\": \"^1.9.4\",\n \"@types/bun\": \"latest\",\n \"@types/debug\": \"^4.1.12\",\n \"@types/react\": \"^19.0.1\",\n \"@types/react-dom\": \"^19.0.2\",\n \"@types/recharts\": \"^2.0.1\",\n \"@vitejs/plugin-react\": \"^4.3.4\",\n \"react\": \"^19.1.0\",\n \"react-cosmos\": \"^6.2.1\",\n \"react-cosmos-plugin-vite\": \"^6.2.0\",\n \"react-dom\": \"^19.1.0\",\n \"react-reconciler\": \"^0.31.0\",\n \"semver\": \"^7.7.2\",\n \"tscircuit\": \"^0.0.2012\",\n \"tsup\": \"^8.3.5\",\n \"vite\": \"^6.0.3\"\n },\n \"peerDependencies\": {\n \"typescript\": \"^5.0.0\",\n \"tscircuit\": \"*\",\n \"circuit-json-to-spice\": \"*\"\n },\n \"dependencies\": {\n \"@radix-ui/react-dropdown-menu\": \"^2.1.16\",\n \"chart.js\": \"^4.5.0\",\n \"debug\": \"^4.4.0\",\n \"performance-now\": \"^2.1.0\",\n \"react-chartjs-2\": \"^5.3.0\",\n \"use-mouse-matrix-transform\": \"^1.2.2\"\n }\n}\n","import { zIndexMap } from \"../utils/z-index-map\"\n\ninterface ViewMenuIconProps extends React.ComponentPropsWithRef<\"button\"> {\n active?: boolean\n}\n\nexport const ViewMenuIcon = ({\n active = false,\n ...props\n}: ViewMenuIconProps) => {\n return (\n \n \n \n \n \n \n \n )\n}\n","export const SpiceIcon = () => (\n \n \n \n)\n","import { SpiceIcon } from \"./SpiceIcon\"\nimport { zIndexMap } from \"../utils/z-index-map\"\n\nexport const SpiceSimulationIcon = ({\n onClick,\n}: {\n onClick: () => void\n}) => {\n return (\n \n \n \n )\n}\n","import { useMemo } from \"react\"\nimport {\n Chart as ChartJS,\n type ChartOptions,\n CategoryScale,\n LinearScale,\n PointElement,\n LineElement,\n Title,\n Tooltip,\n Legend,\n} from \"chart.js\"\nimport { Line } from \"react-chartjs-2\"\nimport type { PlotPoint } from \"../hooks/useSpiceSimulation\"\n\nChartJS.register(\n CategoryScale,\n LinearScale,\n PointElement,\n LineElement,\n Title,\n Tooltip,\n Legend,\n)\n\nconst colors = [\"#8884d8\", \"#82ca9d\", \"#ffc658\", \"#ff7300\", \"#387908\"]\n\nconst formatTimeWithUnits = (seconds: number) => {\n if (seconds === 0) return \"0s\"\n const absSeconds = Math.abs(seconds)\n\n let unit = \"s\"\n let scale = 1\n if (absSeconds < 1e-12) {\n unit = \"fs\"\n scale = 1e15\n } else if (absSeconds < 1e-9) {\n unit = \"ps\"\n scale = 1e12\n } else if (absSeconds < 1e-6) {\n unit = \"ns\"\n scale = 1e9\n } else if (absSeconds < 1e-3) {\n unit = \"us\"\n scale = 1e6\n } else if (absSeconds < 1) {\n unit = \"ms\"\n scale = 1e3\n }\n\n return `${parseFloat((seconds * scale).toPrecision(3))}${unit}`\n}\n\nexport const SpicePlot = ({\n plotData,\n nodes,\n isLoading,\n error,\n hasRun,\n}: {\n plotData: PlotPoint[]\n nodes: string[]\n isLoading: boolean\n error: string | null\n hasRun: boolean\n}) => {\n const yAxisLabel = useMemo(() => {\n const hasVoltage = nodes.some((n) => n.toLowerCase().startsWith(\"v(\"))\n const hasCurrent = nodes.some((n) => n.toLowerCase().startsWith(\"i(\"))\n if (hasVoltage && hasCurrent) return \"Value\"\n if (hasVoltage) return \"Voltage (V)\"\n if (hasCurrent) return \"Current (A)\"\n return \"Value\"\n }, [nodes])\n\n if (isLoading) {\n return (\n \n Running simulation...\n \n )\n }\n\n if (!hasRun) {\n return (\n \n Click \"Run\" to start the simulation.\n \n )\n }\n\n if (error) {\n return (\n \n Error: {error}\n \n )\n }\n\n if (plotData.length === 0) {\n return (\n \n No data to plot. Check simulation output or SPICE netlist.\n \n )\n }\n\n const chartData = {\n datasets: nodes.map((node, i) => ({\n label: node,\n data: plotData.map((p) => ({\n x: Number(p.name),\n y: p[node] as number,\n })),\n borderColor: colors[i % colors.length],\n backgroundColor: colors[i % colors.length],\n fill: false,\n tension: 0.1,\n })),\n }\n\n const options: ChartOptions<\"line\"> = {\n responsive: true,\n maintainAspectRatio: false,\n plugins: {\n legend: {\n position: \"top\" as const,\n labels: {\n font: {\n family: \"sans-serif\",\n },\n },\n },\n title: {\n display: false,\n },\n tooltip: {\n callbacks: {\n title: (tooltipItems) => {\n if (tooltipItems.length > 0) {\n const item = tooltipItems[0]\n return formatTimeWithUnits(item.parsed.x as number)\n }\n return \"\"\n },\n },\n },\n },\n scales: {\n x: {\n type: \"linear\",\n title: {\n display: true,\n text: \"Time\",\n font: {\n family: \"sans-serif\",\n },\n },\n ticks: {\n callback: (value) => formatTimeWithUnits(value as number),\n font: {\n family: \"sans-serif\",\n },\n },\n },\n y: {\n title: {\n display: true,\n text: yAxisLabel,\n font: {\n family: \"sans-serif\",\n },\n },\n ticks: {\n font: {\n family: \"sans-serif\",\n },\n },\n },\n },\n }\n\n return (\n
\n \n
\n )\n}\n","import { SpicePlot } from \"./SpicePlot\"\nimport type { PlotPoint } from \"../hooks/useSpiceSimulation\"\nimport { useEffect, useState } from \"react\"\n\ninterface SpiceSimulationOverlayProps {\n spiceString: string | null\n onClose: () => void\n plotData: PlotPoint[]\n nodes: string[]\n isLoading: boolean\n error: string | null\n simOptions: {\n showVoltage: boolean\n showCurrent: boolean\n startTime: number\n duration: number\n }\n onSimOptionsChange: (\n options: SpiceSimulationOverlayProps[\"simOptions\"],\n ) => void\n hasRun: boolean\n}\n\nexport const SpiceSimulationOverlay = ({\n spiceString,\n onClose,\n plotData,\n nodes,\n isLoading,\n error,\n simOptions,\n onSimOptionsChange,\n hasRun,\n}: SpiceSimulationOverlayProps) => {\n const [startTimeDraft, setStartTimeDraft] = useState(\n String(simOptions.startTime),\n )\n const [durationDraft, setDurationDraft] = useState(\n String(simOptions.duration),\n )\n\n useEffect(() => {\n setStartTimeDraft(String(simOptions.startTime))\n setDurationDraft(String(simOptions.duration))\n }, [simOptions.startTime, simOptions.duration])\n\n const handleRerun = () => {\n onSimOptionsChange({\n ...simOptions,\n startTime: Number(startTimeDraft),\n duration: Number(durationDraft),\n })\n }\n\n const filteredNodes = nodes.filter((node) => {\n const isVoltage = node.toLowerCase().startsWith(\"v(\")\n const isCurrent = node.toLowerCase().startsWith(\"i(\")\n if (simOptions.showVoltage && isVoltage) return true\n if (simOptions.showCurrent && isCurrent) return true\n return false\n })\n\n return (\n \n \n \n \n SPICE Simulation\n \n \n ×\n \n \n
\n \n
\n \n
\n \n \n onSimOptionsChange({\n ...simOptions,\n showVoltage: e.target.checked,\n })\n }\n />\n Voltage\n \n \n \n onSimOptionsChange({\n ...simOptions,\n showCurrent: e.target.checked,\n })\n }\n />\n Current\n \n
\n
\n \n setStartTimeDraft(e.target.value)}\n style={{\n width: \"80px\",\n padding: \"4px 8px\",\n borderRadius: \"4px\",\n border: \"1px solid #ccc\",\n }}\n />\n \n setDurationDraft(e.target.value)}\n style={{\n width: \"80px\",\n padding: \"4px 8px\",\n borderRadius: \"4px\",\n border: \"1px solid #ccc\",\n }}\n />\n \n {hasRun ? \"Rerun\" : \"Run\"}\n \n
\n \n
\n \n SPICE Netlist\n \n \n {spiceString}\n \n
\n \n \n )\n}\n","import { useState, useEffect } from \"react\"\nimport type * as EecircuitEngine from \"../types/eecircuit-engine\"\n// @ts-ignore\nimport { getSpiceSimulationWorkerBlobUrl } from \"../workers/spice-simulation.worker.blob.js\"\n\n// Types from eecircuit-engine interface\ntype RealDataType = {\n name: string\n type: string\n values: number[]\n}\ntype ComplexNumber = {\n real: number\n img: number\n}\ntype ComplexDataType = {\n name: string\n type: string\n values: ComplexNumber[]\n}\ntype EecEngineResult =\n | {\n header: string\n numVariables: number\n variableNames: string[]\n numPoints: number\n dataType: \"real\"\n data: RealDataType[]\n }\n | {\n header: string\n numVariables: number\n variableNames: string[]\n numPoints: number\n dataType: \"complex\"\n data: ComplexDataType[]\n }\n\nexport interface PlotPoint {\n name: string // time or sweep variable\n [key: string]: number | string\n}\n\nconst parseEecEngineOutput = (\n result: EecEngineResult,\n): { plotData: PlotPoint[]; nodes: string[] } => {\n const columnData: Record = {}\n\n if (result.dataType === \"real\") {\n result.data.forEach((col) => {\n columnData[col.name] = col.values\n })\n } else if (result.dataType === \"complex\") {\n result.data.forEach((col) => {\n // For now, plot the real part of complex numbers\n columnData[col.name] = col.values.map((v) => v.real)\n })\n } else {\n throw new Error(\"Unsupported data type in simulation result\")\n }\n\n const timeKey = Object.keys(columnData).find(\n (k) => k.toLowerCase() === \"time\" || k.toLowerCase() === \"frequency\",\n )\n if (!timeKey) {\n throw new Error(\"No time or frequency data in simulation result\")\n }\n const timeValues = columnData[timeKey]\n const probedVariables = Object.keys(columnData).filter((k) => k !== timeKey)\n const plotableNodes = probedVariables\n\n const plotData: PlotPoint[] = timeValues.map((t: number, i: number) => {\n const point: PlotPoint = { name: t.toExponential(2) }\n probedVariables.forEach((variable) => {\n point[variable] = columnData[variable][i]\n })\n return point\n })\n\n return { plotData, nodes: plotableNodes }\n}\n\ntype WorkerMessage =\n | {\n type: \"result\"\n result: EecEngineResult\n }\n | { type: \"error\"; error: string }\n\nexport const useSpiceSimulation = (spiceString: string | null) => {\n const [plotData, setPlotData] = useState([])\n const [nodes, setNodes] = useState([])\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState(null)\n\n useEffect(() => {\n if (!spiceString) {\n setIsLoading(false)\n setPlotData([])\n setNodes([])\n setError(null)\n return\n }\n setIsLoading(true)\n setError(null)\n setPlotData([])\n setNodes([])\n\n const workerUrl = getSpiceSimulationWorkerBlobUrl()\n\n if (!workerUrl) {\n setError(\"Could not create SPICE simulation worker.\")\n setIsLoading(false)\n return\n }\n\n const worker = new Worker(workerUrl, { type: \"module\" })\n\n worker.onmessage = (event: MessageEvent) => {\n if (event.data.type === \"result\") {\n try {\n const { plotData: parsedData, nodes: parsedNodes } =\n parseEecEngineOutput(event.data.result)\n setPlotData(parsedData)\n setNodes(parsedNodes)\n } catch (e: any) {\n setError(e.message || \"Failed to parse simulation result\")\n console.error(e)\n }\n } else if (event.data.type === \"error\") {\n setError(event.data.error)\n }\n setIsLoading(false)\n }\n\n worker.onerror = (err) => {\n setError(err.message)\n setIsLoading(false)\n }\n\n worker.postMessage({ spiceString })\n\n return () => {\n worker.terminate()\n }\n }, [spiceString])\n\n return { plotData, nodes, isLoading, error }\n}\n","// This file is generated by scripts/build-worker-blob-url.ts\n// Do not edit this file directly.\n\nconst b64 = \"dmFyIGU9bnVsbCxzPWFzeW5jKCk9Pihhd2FpdCBpbXBvcnQoImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vZWVjaXJjdWl0LWVuZ2luZUAxLjUuMi8rZXNtIikpLlNpbXVsYXRpb24sYz1hc3luYygpPT57aWYoZSYmZS5pc0luaXRpYWxpemVkKCkpcmV0dXJuO2xldCBpPWF3YWl0IHMoKTtlPW5ldyBpLGF3YWl0IGUuc3RhcnQoKX07c2VsZi5vbm1lc3NhZ2U9YXN5bmMgaT0+e3RyeXtpZihhd2FpdCBjKCksIWUpdGhyb3cgbmV3IEVycm9yKCJTaW11bGF0aW9uIG5vdCBpbml0aWFsaXplZCIpO2xldCB0PWkuZGF0YS5zcGljZVN0cmluZyxhPXQubWF0Y2goL3dyZGF0YVxzKyhcUyspXHMrKC4qKS9pKTtpZihhKXtsZXQgbz1gLnByb2JlICR7YVsyXS50cmltKCkuc3BsaXQoL1xzKy8pLmpvaW4oIiAiKX1gO3Q9dC5yZXBsYWNlKC93cmRhdGEuKi9pLG8pfWVsc2UgaWYoIXQubWF0Y2goL1wucHJvYmUvaSkpdGhyb3cgdC5tYXRjaCgvcGxvdFxzKyguKikvaSk/bmV3IEVycm9yKCJUaGUgJ3Bsb3QnIGNvbW1hbmQgaXMgbm90IHN1cHBvcnRlZCBmb3IgZGF0YSBleHRyYWN0aW9uLiBQbGVhc2UgdXNlICd3cmRhdGEgPGZpbGVuYW1lPiA8dmFyMT4gLi4uJyBvciAnLnByb2JlIDx2YXIxPiAuLi4nIGluc3RlYWQuIik6bmV3IEVycm9yKCJObyAnLnByb2JlJyBvciAnd3JkYXRhJyBjb21tYW5kIGZvdW5kIGluIFNQSUNFIGZpbGUuIFVzZSAnd3JkYXRhIDxmaWxlbmFtZT4gPHZhcjE+IC4uLicgdG8gc3BlY2lmeSBvdXRwdXQuIik7ZS5zZXROZXRMaXN0KHQpO2xldCBuPWF3YWl0IGUucnVuU2ltKCk7c2VsZi5wb3N0TWVzc2FnZSh7dHlwZToicmVzdWx0IixyZXN1bHQ6bn0pfWNhdGNoKHQpe3NlbGYucG9zdE1lc3NhZ2Uoe3R5cGU6ImVycm9yIixlcnJvcjp0Lm1lc3NhZ2V9KX19Owo=\";\n\nlet blobUrl = null;\n\nexport const getSpiceSimulationWorkerBlobUrl = () => {\n if (typeof window === \"undefined\") return null;\n if (blobUrl) return blobUrl;\n\n try {\n const blob = new Blob([atob(b64)], { type: \"application/javascript\" });\n blobUrl = URL.createObjectURL(blob);\n return blobUrl;\n } catch (e) {\n console.error(\"Failed to create blob URL for worker\", e);\n return null;\n }\n};\n","import { circuitJsonToSpice } from \"circuit-json-to-spice\"\nimport type { CircuitJson } from \"circuit-json\"\n\nexport interface SpiceSimOptions {\n showVoltage: boolean\n showCurrent: boolean\n startTime: number // in ms\n duration: number // in ms\n}\n\nconst formatSimTime = (seconds: number): string => {\n if (seconds === 0) return \"0\"\n const absSeconds = Math.abs(seconds)\n\n const precision = (v: number) => v.toPrecision(4)\n\n if (absSeconds >= 1) return precision(seconds)\n if (absSeconds >= 1e-3) return `${precision(seconds * 1e3)}m`\n if (absSeconds >= 1e-6) return `${precision(seconds * 1e6)}u`\n if (absSeconds >= 1e-9) return `${precision(seconds * 1e9)}n`\n if (absSeconds >= 1e-12) return `${precision(seconds * 1e12)}p`\n if (absSeconds >= 1e-15) return `${precision(seconds * 1e15)}f`\n\n return seconds.toExponential(3)\n}\n\nexport const getSpiceFromCircuitJson = (\n circuitJson: CircuitJson,\n options?: Partial,\n): string => {\n const spiceNetlist = circuitJsonToSpice(circuitJson as any)\n const baseSpiceString = spiceNetlist.toSpiceString()\n\n const lines = baseSpiceString.split(\"\\n\").filter((l) => l.trim() !== \"\")\n const componentLines = lines.filter(\n (l) => !l.startsWith(\"*\") && !l.startsWith(\".\") && l.trim() !== \"\",\n )\n\n const allNodes = new Set()\n const capacitorNodes = new Set()\n const componentNamesToProbeCurrent = new Set()\n\n for (const line of componentLines) {\n const parts = line.trim().split(/\\s+/)\n if (parts.length < 3) continue\n\n const componentName = parts[0]\n const componentType = componentName[0].toUpperCase()\n let nodesOnLine: string[] = []\n\n if ([\"R\", \"C\", \"L\", \"V\", \"I\", \"D\"].includes(componentType)) {\n nodesOnLine = parts.slice(1, 3)\n // Only probe current on voltage sources\n if (componentType === \"V\") {\n componentNamesToProbeCurrent.add(componentName)\n }\n } else if (componentType === \"Q\" && parts.length >= 4) {\n // BJT\n nodesOnLine = parts.slice(1, 4)\n } else if (componentType === \"M\" && parts.length >= 5) {\n // MOSFET\n nodesOnLine = parts.slice(1, 5)\n } else if (componentType === \"X\") {\n // Subcircuit\n // Assume last part is model name, everything in between is a node\n nodesOnLine = parts.slice(1, -1)\n } else {\n continue\n }\n\n nodesOnLine.forEach((node) => allNodes.add(node))\n\n if (componentType === \"C\") {\n nodesOnLine.forEach((node) => capacitorNodes.add(node))\n }\n }\n\n // Do not probe/set IC for ground\n allNodes.delete(\"0\")\n capacitorNodes.delete(\"0\")\n\n const icLines = Array.from(capacitorNodes).map((node) => `.ic V(${node})=0`)\n\n const probes: string[] = []\n const probeVoltages = Array.from(allNodes).map((node) => `V(${node})`)\n probes.push(...probeVoltages)\n const probeCurrents = Array.from(componentNamesToProbeCurrent).map(\n (name) => `I(${name})`,\n )\n probes.push(...probeCurrents)\n\n const probeLine = probes.length > 0 ? `.probe ${probes.join(\" \")}` : \"\"\n\n const tstart_ms = options?.startTime ?? 0\n const duration_ms = options?.duration ?? 20\n const tstart = tstart_ms * 1e-3 // s\n const duration = duration_ms * 1e-3 // s\n\n const tstop = tstart + duration\n const tstep = duration / 50\n const tranLine = `.tran ${formatSimTime(tstep)} ${formatSimTime(\n tstop,\n )} ${formatSimTime(tstart)} UIC`\n\n const endStatement = \".end\"\n const originalLines = baseSpiceString.split(\"\\n\")\n let endIndex = -1\n for (let i = originalLines.length - 1; i >= 0; i--) {\n if (originalLines[i].trim().toLowerCase().startsWith(endStatement)) {\n endIndex = i\n break\n }\n }\n\n const injectionLines = [...icLines, probeLine, tranLine].filter(Boolean)\n\n let finalLines: string[]\n\n if (endIndex !== -1) {\n const beforeEnd = originalLines.slice(0, endIndex)\n const endLineAndAfter = originalLines.slice(endIndex)\n finalLines = [...beforeEnd, ...injectionLines, ...endLineAndAfter]\n } else {\n finalLines = [...originalLines, ...injectionLines, endStatement]\n }\n\n return finalLines.join(\"\\n\")\n}\n","import { useCallback } from \"react\"\n\nexport const STORAGE_KEYS = {\n IS_SHOWING_SCHEMATIC_GROUPS: \"schematic_viewer_show_groups\",\n SELECTED_SCHEMATIC_SHEET: \"schematic_viewer_selected_sheet\",\n} as const\n\nexport const getStoredBoolean = (\n key: string,\n defaultValue: boolean,\n): boolean => {\n if (typeof window === \"undefined\") return defaultValue\n try {\n const stored = localStorage.getItem(key)\n return stored !== null ? JSON.parse(stored) : defaultValue\n } catch {\n return defaultValue\n }\n}\n\nexport const setStoredBoolean = (key: string, value: boolean): void => {\n if (typeof window === \"undefined\") return\n try {\n localStorage.setItem(key, JSON.stringify(value))\n } catch {}\n}\n\nexport const getStoredString = (key: string): string | null => {\n if (typeof window === \"undefined\") return null\n try {\n return localStorage.getItem(key)\n } catch {\n return null\n }\n}\n\nexport const setStoredString = (key: string, value: string): void => {\n if (typeof window === \"undefined\") return\n try {\n localStorage.setItem(key, value)\n } catch {}\n}\n\nexport const useLocalStorage = () => {\n const getBoolean = useCallback(\n (key: string, defaultValue: boolean): boolean => {\n return getStoredBoolean(key, defaultValue)\n },\n [],\n )\n\n const setBoolean = useCallback((key: string, value: boolean): void => {\n setStoredBoolean(key, value)\n }, [])\n\n return {\n getBoolean,\n setBoolean,\n }\n}\n\nexport const useLocalStorageValue = (key: string, defaultValue: boolean) => {\n const { getBoolean, setBoolean } = useLocalStorage()\n\n const getValue = useCallback(() => {\n return getBoolean(key, defaultValue)\n }, [getBoolean, key, defaultValue])\n\n const setValue = useCallback(\n (value: boolean) => {\n setBoolean(key, value)\n },\n [setBoolean, key],\n )\n\n return {\n getValue,\n setValue,\n }\n}\n\nlet spacePanHeld = false\nexport const isSpacePanHeld = () => spacePanHeld\nexport const setSpacePanHeld = (held: boolean) => {\n spacePanHeld = held\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n type ReactNode,\n} from \"react\"\n\nexport interface BoundingBoxBounds {\n minX: number\n maxX: number\n minY: number\n maxY: number\n}\n\ninterface BoundingBoxRegistration {\n bounds: BoundingBoxBounds | null\n onClick?: ((event: MouseEvent) => void) | undefined\n}\n\nexport interface MouseTrackerContextValue {\n registerBoundingBox: (\n id: string,\n registration: BoundingBoxRegistration,\n ) => void\n updateBoundingBox: (id: string, registration: BoundingBoxRegistration) => void\n unregisterBoundingBox: (id: string) => void\n subscribe: (listener: () => void) => () => void\n isHovering: (id: string) => boolean\n}\n\nexport const MouseTrackerContext =\n createContext(null)\n\nconst DRAG_THRESHOLD_PX = 5\n\nconst boundsAreEqual = (\n a: BoundingBoxBounds | null | undefined,\n b: BoundingBoxBounds | null | undefined,\n) => {\n if (!a && !b) return true\n if (!a || !b) return false\n return (\n a.minX === b.minX &&\n a.maxX === b.maxX &&\n a.minY === b.minY &&\n a.maxY === b.maxY\n )\n}\n\nexport const MouseTracker = ({ children }: { children: ReactNode }) => {\n const existingContext = useContext(MouseTrackerContext)\n\n if (existingContext) {\n return <>{children}\n }\n\n const storeRef = useRef({\n pointer: null as { x: number; y: number } | null,\n boundingBoxes: new Map(),\n hoveringIds: new Set(),\n subscribers: new Set<() => void>(),\n mouseDownPosition: null as { x: number; y: number } | null,\n })\n\n const notifySubscribers = useCallback(() => {\n for (const callback of storeRef.current.subscribers) {\n callback()\n }\n }, [])\n\n const updateHovering = useCallback(() => {\n const pointer = storeRef.current.pointer\n const newHovering = new Set()\n\n if (pointer) {\n for (const [id, registration] of storeRef.current.boundingBoxes) {\n const bounds = registration.bounds\n if (!bounds) continue\n if (\n pointer.x >= bounds.minX &&\n pointer.x <= bounds.maxX &&\n pointer.y >= bounds.minY &&\n pointer.y <= bounds.maxY\n ) {\n newHovering.add(id)\n }\n }\n }\n\n const prevHovering = storeRef.current.hoveringIds\n if (\n newHovering.size === prevHovering.size &&\n [...newHovering].every((id) => prevHovering.has(id))\n ) {\n return\n }\n\n storeRef.current.hoveringIds = newHovering\n notifySubscribers()\n }, [notifySubscribers])\n\n const registerBoundingBox = useCallback(\n (id: string, registration: BoundingBoxRegistration) => {\n storeRef.current.boundingBoxes.set(id, registration)\n updateHovering()\n },\n [updateHovering],\n )\n\n const updateBoundingBox = useCallback(\n (id: string, registration: BoundingBoxRegistration) => {\n const existing = storeRef.current.boundingBoxes.get(id)\n if (\n existing &&\n boundsAreEqual(existing.bounds, registration.bounds) &&\n existing.onClick === registration.onClick\n ) {\n return\n }\n storeRef.current.boundingBoxes.set(id, registration)\n updateHovering()\n },\n [updateHovering],\n )\n\n const unregisterBoundingBox = useCallback(\n (id: string) => {\n const removed = storeRef.current.boundingBoxes.delete(id)\n if (removed) {\n updateHovering()\n }\n },\n [updateHovering],\n )\n\n const subscribe = useCallback((listener: () => void) => {\n storeRef.current.subscribers.add(listener)\n return () => {\n storeRef.current.subscribers.delete(listener)\n }\n }, [])\n\n const isHovering = useCallback((id: string) => {\n return storeRef.current.hoveringIds.has(id)\n }, [])\n\n useEffect(() => {\n const handlePointerPosition = (event: PointerEvent | MouseEvent) => {\n const { clientX, clientY } = event\n const pointer = storeRef.current.pointer\n if (pointer && pointer.x === clientX && pointer.y === clientY) {\n return\n }\n storeRef.current.pointer = { x: clientX, y: clientY }\n updateHovering()\n }\n\n const handlePointerLeave = () => {\n if (storeRef.current.pointer === null) return\n storeRef.current.pointer = null\n storeRef.current.mouseDownPosition = null\n updateHovering()\n }\n\n const handleMouseDown = (event: MouseEvent) => {\n storeRef.current.mouseDownPosition = {\n x: event.clientX,\n y: event.clientY,\n }\n }\n\n const handleClick = (event: MouseEvent) => {\n const { clientX, clientY } = event\n const mouseDownPos = storeRef.current.mouseDownPosition\n\n // Check if this was a drag (movement > threshold)\n if (mouseDownPos) {\n const distance = Math.sqrt(\n Math.pow(clientX - mouseDownPos.x, 2) +\n Math.pow(clientY - mouseDownPos.y, 2),\n )\n if (distance > DRAG_THRESHOLD_PX) {\n // This was a drag, not a click - don't trigger onClick\n storeRef.current.mouseDownPosition = null\n return\n }\n }\n\n storeRef.current.mouseDownPosition = null\n\n for (const registration of storeRef.current.boundingBoxes.values()) {\n const bounds = registration.bounds\n if (!bounds) continue\n if (\n clientX >= bounds.minX &&\n clientX <= bounds.maxX &&\n clientY >= bounds.minY &&\n clientY <= bounds.maxY\n ) {\n registration.onClick?.(event)\n }\n }\n }\n\n window.addEventListener(\"pointermove\", handlePointerPosition, {\n passive: true,\n })\n window.addEventListener(\"pointerdown\", handlePointerPosition, {\n passive: true,\n })\n window.addEventListener(\"pointerup\", handlePointerPosition, {\n passive: true,\n })\n window.addEventListener(\"pointerleave\", handlePointerLeave)\n window.addEventListener(\"pointercancel\", handlePointerLeave)\n window.addEventListener(\"blur\", handlePointerLeave)\n window.addEventListener(\"mousedown\", handleMouseDown, { passive: true })\n window.addEventListener(\"click\", handleClick, { passive: true })\n\n return () => {\n window.removeEventListener(\"pointermove\", handlePointerPosition)\n window.removeEventListener(\"pointerdown\", handlePointerPosition)\n window.removeEventListener(\"pointerup\", handlePointerPosition)\n window.removeEventListener(\"pointerleave\", handlePointerLeave)\n window.removeEventListener(\"pointercancel\", handlePointerLeave)\n window.removeEventListener(\"blur\", handlePointerLeave)\n window.removeEventListener(\"mousedown\", handleMouseDown)\n window.removeEventListener(\"click\", handleClick)\n }\n }, [updateHovering])\n\n const value = useMemo(\n () => ({\n registerBoundingBox,\n updateBoundingBox,\n unregisterBoundingBox,\n subscribe,\n isHovering,\n }),\n [\n registerBoundingBox,\n updateBoundingBox,\n unregisterBoundingBox,\n subscribe,\n isHovering,\n ],\n )\n\n return (\n \n {children}\n \n )\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { useMouseEventsOverBoundingBox } from \"../hooks/useMouseEventsOverBoundingBox\"\nimport type { BoundingBoxBounds } from \"./MouseTracker\"\nimport { zIndexMap } from \"../utils/z-index-map\"\n\ninterface RelativeRect {\n left: number\n top: number\n width: number\n height: number\n}\n\ninterface Measurement {\n bounds: BoundingBoxBounds\n rect: RelativeRect\n}\n\nconst areMeasurementsEqual = (a: Measurement | null, b: Measurement | null) => {\n if (!a && !b) return true\n if (!a || !b) return false\n return (\n Math.abs(a.bounds.minX - b.bounds.minX) < 0.5 &&\n Math.abs(a.bounds.maxX - b.bounds.maxX) < 0.5 &&\n Math.abs(a.bounds.minY - b.bounds.minY) < 0.5 &&\n Math.abs(a.bounds.maxY - b.bounds.maxY) < 0.5 &&\n Math.abs(a.rect.left - b.rect.left) < 0.5 &&\n Math.abs(a.rect.top - b.rect.top) < 0.5 &&\n Math.abs(a.rect.width - b.rect.width) < 0.5 &&\n Math.abs(a.rect.height - b.rect.height) < 0.5\n )\n}\n\ninterface Props {\n componentId: string\n svgDivRef: React.RefObject\n containerRef: React.RefObject\n onComponentClick?: (componentId: string, event: MouseEvent) => void\n onHoverChange?: (componentId: string, isHovering: boolean) => void\n showOutline: boolean\n circuitJsonKey: string\n}\n\nexport const SchematicComponentMouseTarget = ({\n componentId,\n svgDivRef,\n containerRef,\n onComponentClick,\n onHoverChange,\n showOutline,\n circuitJsonKey,\n}: Props) => {\n const [measurement, setMeasurement] = useState(null)\n const frameRef = useRef(null)\n\n const measure = useCallback(() => {\n frameRef.current = null\n const svgDiv = svgDivRef.current\n const container = containerRef.current\n if (!svgDiv || !container) {\n setMeasurement((prev) => (prev ? null : prev))\n return\n }\n const element = svgDiv.querySelector(\n `[data-schematic-component-id=\"${componentId}\"]`,\n )\n if (!element) {\n setMeasurement((prev) => (prev ? null : prev))\n return\n }\n\n const elementRect = element.getBoundingClientRect()\n const containerRect = container.getBoundingClientRect()\n\n const nextMeasurement: Measurement = {\n bounds: {\n minX: elementRect.left,\n maxX: elementRect.right,\n minY: elementRect.top,\n maxY: elementRect.bottom,\n },\n rect: {\n left: elementRect.left - containerRect.left,\n top: elementRect.top - containerRect.top,\n width: elementRect.width,\n height: elementRect.height,\n },\n }\n\n setMeasurement((prev) =>\n areMeasurementsEqual(prev, nextMeasurement) ? prev : nextMeasurement,\n )\n }, [componentId, containerRef, svgDivRef])\n\n const scheduleMeasure = useCallback(() => {\n if (frameRef.current !== null) return\n frameRef.current = window.requestAnimationFrame(measure)\n }, [measure])\n\n useEffect(() => {\n scheduleMeasure()\n }, [scheduleMeasure, circuitJsonKey])\n\n useEffect(() => {\n scheduleMeasure()\n const svgDiv = svgDivRef.current\n const container = containerRef.current\n if (!svgDiv || !container) return\n\n const resizeObserver =\n typeof ResizeObserver !== \"undefined\"\n ? new ResizeObserver(() => {\n scheduleMeasure()\n })\n : null\n resizeObserver?.observe(container)\n resizeObserver?.observe(svgDiv)\n\n const mutationObserver =\n typeof MutationObserver !== \"undefined\"\n ? new MutationObserver(() => {\n scheduleMeasure()\n })\n : null\n mutationObserver?.observe(svgDiv, {\n attributes: true,\n attributeFilter: [\"style\", \"transform\"],\n subtree: true,\n childList: true,\n })\n\n window.addEventListener(\"scroll\", scheduleMeasure, true)\n window.addEventListener(\"resize\", scheduleMeasure)\n\n return () => {\n resizeObserver?.disconnect()\n mutationObserver?.disconnect()\n window.removeEventListener(\"scroll\", scheduleMeasure, true)\n window.removeEventListener(\"resize\", scheduleMeasure)\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current)\n frameRef.current = null\n }\n }\n }, [scheduleMeasure, svgDivRef, containerRef])\n\n const handleClick = useCallback(\n (event: MouseEvent) => {\n if (onComponentClick) {\n onComponentClick(componentId, event)\n }\n },\n [componentId, onComponentClick],\n )\n\n const bounds = measurement?.bounds ?? null\n\n const { hovering } = useMouseEventsOverBoundingBox({\n bounds,\n onClick: onComponentClick ? handleClick : undefined,\n })\n\n // Notify parent of hover state changes\n useEffect(() => {\n if (onHoverChange) {\n onHoverChange(componentId, hovering)\n }\n }, [hovering, componentId, onHoverChange])\n\n if (!measurement || !hovering || !showOutline) {\n return null\n }\n\n const rect = measurement.rect\n\n return (\n \n )\n}\n","import {\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\"\nimport {\n MouseTrackerContext,\n type BoundingBoxBounds,\n} from \"../components/MouseTracker\"\n\ninterface UseMouseEventsOverBoundingBoxOptions {\n bounds: BoundingBoxBounds | null\n onClick?: (event: MouseEvent) => void\n}\n\nexport const useMouseEventsOverBoundingBox = (\n options: UseMouseEventsOverBoundingBoxOptions,\n) => {\n const context = useContext(MouseTrackerContext)\n\n if (!context) {\n throw new Error(\n \"useMouseEventsOverBoundingBox must be used within a MouseTracker\",\n )\n }\n\n const id = useId()\n const latestOptionsRef = useRef(options)\n latestOptionsRef.current = options\n\n const handleClick = useMemo(\n () => (event: MouseEvent) => {\n latestOptionsRef.current.onClick?.(event)\n },\n [],\n )\n\n useEffect(() => {\n context.registerBoundingBox(id, {\n bounds: latestOptionsRef.current.bounds,\n onClick: latestOptionsRef.current.onClick ? handleClick : undefined,\n })\n return () => {\n context.unregisterBoundingBox(id)\n }\n }, [context, handleClick, id])\n\n useEffect(() => {\n context.updateBoundingBox(id, {\n bounds: latestOptionsRef.current.bounds,\n onClick: latestOptionsRef.current.onClick ? handleClick : undefined,\n })\n }, [\n context,\n handleClick,\n id,\n options.bounds?.minX,\n options.bounds?.maxX,\n options.bounds?.minY,\n options.bounds?.maxY,\n options.onClick,\n ])\n\n const hovering = useSyncExternalStore(\n context.subscribe,\n () => context.isHovering(id),\n () => false,\n )\n\n return { hovering }\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { useMouseEventsOverBoundingBox } from \"../hooks/useMouseEventsOverBoundingBox\"\nimport type { BoundingBoxBounds } from \"./MouseTracker\"\nimport { zIndexMap } from \"../utils/z-index-map\"\n\ninterface RelativeRect {\n left: number\n top: number\n width: number\n height: number\n}\n\ninterface Measurement {\n bounds: BoundingBoxBounds\n rect: RelativeRect\n}\n\nconst areMeasurementsEqual = (a: Measurement | null, b: Measurement | null) => {\n if (!a && !b) return true\n if (!a || !b) return false\n return (\n Math.abs(a.bounds.minX - b.bounds.minX) < 0.5 &&\n Math.abs(a.bounds.maxX - b.bounds.maxX) < 0.5 &&\n Math.abs(a.bounds.minY - b.bounds.minY) < 0.5 &&\n Math.abs(a.bounds.maxY - b.bounds.maxY) < 0.5 &&\n Math.abs(a.rect.left - b.rect.left) < 0.5 &&\n Math.abs(a.rect.top - b.rect.top) < 0.5 &&\n Math.abs(a.rect.width - b.rect.width) < 0.5 &&\n Math.abs(a.rect.height - b.rect.height) < 0.5\n )\n}\n\ninterface Props {\n portId: string\n portLabel?: string\n svgDivRef: React.RefObject\n containerRef: React.RefObject\n onPortClick?: (portId: string, event: MouseEvent) => void\n onPortMouseDown?: (portId: string, event: MouseEvent) => void\n onHoverChange?: (portId: string, isHovering: boolean) => void\n showOutline: boolean\n interactive?: boolean\n hitPaddingPx?: number\n circuitJsonKey: string\n}\n\nexport const SchematicPortMouseTarget = ({\n portId,\n portLabel,\n svgDivRef,\n containerRef,\n onPortClick,\n onPortMouseDown,\n onHoverChange,\n showOutline,\n interactive = false,\n hitPaddingPx = 4,\n circuitJsonKey,\n}: Props) => {\n const [measurement, setMeasurement] = useState(null)\n const frameRef = useRef(null)\n\n const measure = useCallback(() => {\n frameRef.current = null\n const svgDiv = svgDivRef.current\n const container = containerRef.current\n if (!svgDiv || !container) {\n setMeasurement((prev) => (prev ? null : prev))\n return\n }\n const element = svgDiv.querySelector(\n `[data-schematic-port-id=\"${portId}\"]`,\n )\n if (!element) {\n setMeasurement((prev) => (prev ? null : prev))\n return\n }\n\n const elementRect = element.getBoundingClientRect()\n const containerRect = container.getBoundingClientRect()\n\n // Add some padding around the port for easier interaction.\n const padding = hitPaddingPx\n\n const nextMeasurement: Measurement = {\n bounds: {\n minX: elementRect.left - padding,\n maxX: elementRect.right + padding,\n minY: elementRect.top - padding,\n maxY: elementRect.bottom + padding,\n },\n rect: {\n left: elementRect.left - containerRect.left - padding,\n top: elementRect.top - containerRect.top - padding,\n width: elementRect.width + padding * 2,\n height: elementRect.height + padding * 2,\n },\n }\n\n setMeasurement((prev) =>\n areMeasurementsEqual(prev, nextMeasurement) ? prev : nextMeasurement,\n )\n }, [portId, containerRef, svgDivRef, hitPaddingPx])\n\n const scheduleMeasure = useCallback(() => {\n if (frameRef.current !== null) return\n frameRef.current = window.requestAnimationFrame(measure)\n }, [measure])\n\n useEffect(() => {\n scheduleMeasure()\n }, [scheduleMeasure, circuitJsonKey])\n\n useEffect(() => {\n scheduleMeasure()\n const svgDiv = svgDivRef.current\n const container = containerRef.current\n if (!svgDiv || !container) return\n\n const resizeObserver =\n typeof ResizeObserver !== \"undefined\"\n ? new ResizeObserver(() => {\n scheduleMeasure()\n })\n : null\n resizeObserver?.observe(container)\n resizeObserver?.observe(svgDiv)\n\n const mutationObserver =\n typeof MutationObserver !== \"undefined\"\n ? new MutationObserver(() => {\n scheduleMeasure()\n })\n : null\n mutationObserver?.observe(svgDiv, {\n attributes: true,\n attributeFilter: [\"style\", \"transform\"],\n subtree: true,\n childList: true,\n })\n\n window.addEventListener(\"scroll\", scheduleMeasure, true)\n window.addEventListener(\"resize\", scheduleMeasure)\n\n return () => {\n resizeObserver?.disconnect()\n mutationObserver?.disconnect()\n window.removeEventListener(\"scroll\", scheduleMeasure, true)\n window.removeEventListener(\"resize\", scheduleMeasure)\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current)\n frameRef.current = null\n }\n }\n }, [scheduleMeasure, svgDivRef, containerRef])\n\n const handleClick = useCallback(\n (event: MouseEvent) => {\n if (onPortClick) {\n onPortClick(portId, event)\n }\n },\n [portId, onPortClick],\n )\n\n const bounds = measurement?.bounds ?? null\n\n const { hovering } = useMouseEventsOverBoundingBox({\n bounds,\n onClick: onPortClick ? handleClick : undefined,\n })\n\n // Notify parent of hover state changes\n useEffect(() => {\n if (onHoverChange) {\n onHoverChange(portId, hovering)\n }\n }, [hovering, portId, onHoverChange])\n\n if (!measurement || !showOutline) {\n return null\n }\n\n const rect = measurement.rect\n\n return (\n <>\n {\n e.preventDefault()\n e.stopPropagation()\n onPortMouseDown(portId, e.nativeEvent)\n }\n : undefined\n }\n style={{\n position: \"absolute\",\n left: rect.left,\n top: rect.top,\n width: rect.width,\n height: rect.height,\n border: hovering\n ? \"1.5px solid rgba(255, 153, 51, 0.9)\"\n : \"1.5px solid rgba(255, 153, 51, 0.3)\",\n backgroundColor: hovering\n ? \"rgba(255, 153, 51, 0.15)\"\n : \"rgba(255, 153, 51, 0.05)\",\n borderRadius: \"50%\",\n pointerEvents: interactive ? \"auto\" : \"none\",\n cursor: interactive ? \"crosshair\" : undefined,\n zIndex: zIndexMap.schematicPortHoverOutline,\n transition: \"border-color 0.15s, background-color 0.15s\",\n }}\n />\n {hovering && portLabel && (\n \n {portLabel}\n \n )}\n \n )\n}\n","import { useState } from \"react\"\nimport * as DropdownMenu from \"@radix-ui/react-dropdown-menu\"\nimport type { SchematicSheet } from \"circuit-json\"\nimport { zIndexMap } from \"../utils/z-index-map\"\n\ninterface SchematicSheetSelectorProps {\n sheets: SchematicSheet[]\n selectedSheetId: string | undefined\n onSelectSheet: (schematicSheetId: string) => void\n}\n\nconst FONT_FAMILY =\n 'system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif'\n\nconst contentStyles: React.CSSProperties = {\n backgroundColor: \"#ffffff\",\n color: \"#111111\",\n borderRadius: 8,\n boxShadow: \"0 6px 24px rgba(0,0,0,0.12), 0 1px 3px rgba(0,0,0,0.08)\",\n border: \"1px solid #e5e7eb\",\n padding: 4,\n minWidth: 200,\n maxWidth: 320,\n maxHeight: 320,\n overflowY: \"auto\",\n fontSize: 13,\n fontFamily: FONT_FAMILY,\n outline: \"none\",\n zIndex: zIndexMap.viewMenu,\n}\n\nconst itemStyles: React.CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n padding: \"7px 10px 7px 8px\",\n borderRadius: 6,\n cursor: \"pointer\",\n outline: \"none\",\n userSelect: \"none\",\n color: \"#111111\",\n fontSize: 13,\n fontFamily: FONT_FAMILY,\n}\n\nconst iconSlotStyles: React.CSSProperties = {\n width: 16,\n height: 16,\n flexShrink: 0,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n color: \"#111111\",\n}\n\nconst ellipsisStyles: React.CSSProperties = {\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n}\n\nconst MENU_CSS = `\n.sv-sheet-item[data-highlighted], .sv-sheet-item:hover { background-color: #f1f3f5; }\n.sv-sheet-chevron { transition: transform 0.2s ease; }\n[data-state=\"open\"] > .sv-sheet-chevron { transform: rotate(180deg); }\n`\n\nconst CheckIcon = () => (\n \n \n \n)\n\nconst ChevronDownIcon = ({ className }: { className?: string }) => (\n \n \n \n)\n\n/**\n * A standalone toolbar dropdown for switching the active schematic sheet.\n * Rendered only when the circuit has more than one sheet.\n */\nexport const SchematicSheetSelector = ({\n sheets,\n selectedSheetId,\n onSelectSheet,\n}: SchematicSheetSelectorProps) => {\n const [open, setOpen] = useState(false)\n\n if (sheets.length <= 1) return null\n\n const selectedSheet = sheets.find(\n (s) => s.schematic_sheet_id === selectedSheetId,\n )\n const selectedLabel = selectedSheet?.name ?? \"Select sheet\"\n\n return (\n <>\n \n \n \n e.stopPropagation()}\n style={{\n position: \"absolute\",\n top: \"16px\",\n left: \"16px\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"6px\",\n maxWidth: \"220px\",\n padding: \"8px 12px\",\n backgroundColor: \"#ffffff\",\n color: \"#000000\",\n border: \"none\",\n outline: \"none\",\n borderRadius: \"4px\",\n cursor: \"pointer\",\n boxShadow: \"0 2px 4px rgba(0,0,0,0.1)\",\n fontSize: \"13px\",\n fontFamily: FONT_FAMILY,\n zIndex: zIndexMap.viewMenuIcon,\n }}\n >\n Sheet:\n \n {selectedLabel}\n \n \n \n \n\n \n \n {sheets.map((sheet) => {\n const selected = sheet.schematic_sheet_id === selectedSheetId\n return (\n e.preventDefault()}\n onPointerUp={() => {\n onSelectSheet(sheet.schematic_sheet_id)\n setOpen(false)\n }}\n >\n \n {selected && }\n \n \n {sheet.name}\n \n \n )\n })}\n \n \n \n \n )\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { type Matrix } from \"transformation-matrix\"\nimport type { EditSchematicWireAddEvent } from \"../types/edit-events\"\nimport { isMouseCaptureIgnoredTarget } from \"../utils/isMouseCaptureIgnoredTarget\"\nimport {\n createScreenToReal,\n getSchematicPortAtScreen,\n getSchematicPortCenter,\n resolveSchematicPortId,\n} from \"../utils/schematicPortHitTest\"\n\nexport interface WireDrawingState {\n isDrawing: boolean\n fromPortId: string | null\n previewEnd: { x: number; y: number } | null\n waypoints: Array<{ x: number; y: number }>\n}\n\nexport const useWireDrawing = ({\n enabled,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent,\n}: {\n enabled: boolean\n circuitJson: any[]\n svgToScreenProjection: Matrix\n realToSvgProjection: Matrix\n containerRef: React.RefObject\n onEditEvent?: (event: EditSchematicWireAddEvent) => void\n}) => {\n const [state, setState] = useState({\n isDrawing: false,\n fromPortId: null,\n previewEnd: null,\n waypoints: [],\n })\n\n const stateRef = useRef(state)\n stateRef.current = state\n\n const screenToReal = useCallback(\n createScreenToReal(\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n ),\n [svgToScreenProjection, realToSvgProjection, containerRef],\n )\n\n const getPortCenter = useCallback(\n (portId: string) => {\n const schematicPortId = resolveSchematicPortId(circuitJson, portId)\n if (!schematicPortId) return null\n return getSchematicPortCenter(\n circuitJson,\n containerRef.current,\n schematicPortId,\n screenToReal,\n )\n },\n [circuitJson, containerRef, screenToReal],\n )\n\n const getPortAtScreen = useCallback(\n (screenX: number, screenY: number) =>\n getSchematicPortAtScreen(\n containerRef.current,\n circuitJson,\n screenX,\n screenY,\n ),\n [circuitJson, containerRef],\n )\n\n const beginWireAtPort = useCallback(\n (portId: string) => {\n const schematicPortId = resolveSchematicPortId(circuitJson, portId)\n if (!schematicPortId) return false\n\n const center = getPortCenter(schematicPortId)\n if (!center) return false\n\n setState({\n isDrawing: true,\n fromPortId: schematicPortId,\n previewEnd: center,\n waypoints: [center],\n })\n return true\n },\n [circuitJson, getPortCenter],\n )\n\n const finishWireAtPort = useCallback(\n (portId: string) => {\n const current = stateRef.current\n if (!current.isDrawing || !current.fromPortId) return false\n\n const schematicPortId = resolveSchematicPortId(circuitJson, portId)\n if (!schematicPortId || schematicPortId === current.fromPortId) {\n return false\n }\n\n const toCenter = getPortCenter(schematicPortId)\n if (!toCenter) return false\n\n const event: EditSchematicWireAddEvent = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_wire_add\",\n from_schematic_port_id: current.fromPortId,\n to_schematic_port_id: schematicPortId,\n route: [...current.waypoints, toCenter],\n created_at: Date.now(),\n in_progress: false,\n }\n onEditEvent?.(event)\n setState({\n isDrawing: false,\n fromPortId: null,\n previewEnd: null,\n waypoints: [],\n })\n return true\n },\n [circuitJson, getPortCenter, onEditEvent],\n )\n\n const handlePortMouseDown = useCallback(\n (portId: string, e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) {\n return\n }\n\n e.preventDefault()\n e.stopPropagation()\n\n const current = stateRef.current\n if (!current.isDrawing) {\n beginWireAtPort(portId)\n return\n }\n\n if (!finishWireAtPort(portId)) {\n const realPos = screenToReal(e.clientX, e.clientY)\n setState((prev) => ({\n ...prev,\n waypoints: [...prev.waypoints, realPos],\n }))\n }\n },\n [enabled, beginWireAtPort, finishWireAtPort, screenToReal],\n )\n\n const handleMouseDown = useCallback(\n (e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) {\n return\n }\n\n const current = stateRef.current\n const portId = getPortAtScreen(e.clientX, e.clientY)\n\n if (!current.isDrawing) {\n if (!portId) return\n e.preventDefault()\n e.stopPropagation()\n beginWireAtPort(portId)\n return\n }\n\n if (portId && finishWireAtPort(portId)) {\n e.preventDefault()\n e.stopPropagation()\n return\n }\n\n e.preventDefault()\n e.stopPropagation()\n const realPos = screenToReal(e.clientX, e.clientY)\n setState((prev) => ({\n ...prev,\n waypoints: [...prev.waypoints, realPos],\n }))\n },\n [enabled, getPortAtScreen, beginWireAtPort, finishWireAtPort, screenToReal],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!enabled || !stateRef.current.isDrawing) return\n const realPos = screenToReal(e.clientX, e.clientY)\n setState((prev) => ({ ...prev, previewEnd: realPos }))\n },\n [enabled, screenToReal],\n )\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key === \"Escape\" && stateRef.current.isDrawing) {\n setState({\n isDrawing: false,\n fromPortId: null,\n previewEnd: null,\n waypoints: [],\n })\n }\n }, [])\n\n useEffect(() => {\n if (!enabled) {\n setState({\n isDrawing: false,\n fromPortId: null,\n previewEnd: null,\n waypoints: [],\n })\n return\n }\n window.addEventListener(\"mousedown\", handleMouseDown, { capture: true })\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n window.removeEventListener(\"mousedown\", handleMouseDown, {\n capture: true,\n })\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [enabled, handleMouseDown, handleMouseMove, handleKeyDown])\n\n return { wireDrawingState: state, handlePortMouseDown }\n}\n","export function isMouseCaptureIgnoredTarget(\n target: EventTarget | null,\n): boolean {\n const el = target as Element | null\n if (!el) return false\n return Boolean(\n el.closest(\n \"[data-schematic-ignore-mouse-capture], input, textarea, select, option\",\n ),\n )\n}\n","import { type Matrix, compose } from \"transformation-matrix\"\n\nexport const SCHEMATIC_PORT_HIT_RADIUS_PX = 36\n\nexport function resolveSchematicPortId(\n circuitJson: any[],\n portId: string,\n): string | null {\n const bySchematic = circuitJson.find(\n (el) => el.type === \"schematic_port\" && el.schematic_port_id === portId,\n )\n if (bySchematic) return portId\n\n const bySource = circuitJson.find(\n (el) => el.type === \"schematic_port\" && el.source_port_id === portId,\n )\n return bySource?.schematic_port_id ?? null\n}\n\nexport function createScreenToReal(\n svgToScreenProjection: Matrix,\n realToSvgProjection: Matrix,\n containerRef: { current: HTMLDivElement | null },\n) {\n return (screenX: number, screenY: number) => {\n const container = containerRef.current\n if (!container) return { x: 0, y: 0 }\n const rect = container.getBoundingClientRect()\n const localX = screenX - rect.left\n const localY = screenY - rect.top\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n return {\n x: (localX - realToScreen.e) / realToScreen.a,\n y: (localY - realToScreen.f) / realToScreen.d,\n }\n }\n}\n\nexport function getSchematicPortCenter(\n circuitJson: any[],\n container: HTMLElement | null,\n schematicPortId: string,\n screenToReal: (screenX: number, screenY: number) => { x: number; y: number },\n): { x: number; y: number } | null {\n const port = circuitJson.find(\n (el) =>\n el.type === \"schematic_port\" && el.schematic_port_id === schematicPortId,\n )\n if (port?.center) {\n return { x: port.center.x, y: port.center.y }\n }\n\n if (!container) return null\n const el = container.querySelector(\n `[data-schematic-port-id=\"${schematicPortId}\"]`,\n )\n if (!el) return null\n\n const rect = el.getBoundingClientRect()\n return screenToReal(rect.left + rect.width / 2, rect.top + rect.height / 2)\n}\n\nexport function getSchematicPortAtScreen(\n container: HTMLElement | null,\n circuitJson: any[],\n screenX: number,\n screenY: number,\n hitRadiusPx = SCHEMATIC_PORT_HIT_RADIUS_PX,\n): string | null {\n if (!container) return null\n\n let closest: { id: string; dist: number } | null = null\n for (const node of Array.from(\n container.querySelectorAll(\"[data-schematic-port-id]\"),\n )) {\n const rect = node.getBoundingClientRect()\n const cx = rect.left + rect.width / 2\n const cy = rect.top + rect.height / 2\n const dist = Math.hypot(cx - screenX, cy - screenY)\n\n if (dist < hitRadiusPx && (!closest || dist < closest.dist)) {\n const id = node.getAttribute(\"data-schematic-port-id\")\n const resolved = id ? resolveSchematicPortId(circuitJson, id) : null\n if (resolved) closest = { id: resolved, dist }\n }\n }\n\n return closest?.id ?? null\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { type Matrix, compose } from \"transformation-matrix\"\nimport type { EditSchematicBusAddEvent } from \"../types/edit-events\"\nimport { isMouseCaptureIgnoredTarget } from \"../utils/isMouseCaptureIgnoredTarget\"\n\nexport interface BusDrawingState {\n isDrawing: boolean\n previewEnd: { x: number; y: number } | null\n waypoints: Array<{ x: number; y: number }>\n}\n\nexport const useBusDrawing = ({\n enabled,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent,\n}: {\n enabled: boolean\n svgToScreenProjection: Matrix\n realToSvgProjection: Matrix\n containerRef: React.RefObject\n onEditEvent?: (event: EditSchematicBusAddEvent) => void\n}) => {\n const [state, setState] = useState({\n isDrawing: false,\n previewEnd: null,\n waypoints: [],\n })\n\n const stateRef = useRef(state)\n stateRef.current = state\n\n const screenToReal = useCallback(\n (screenX: number, screenY: number) => {\n const container = containerRef.current\n if (!container) return { x: 0, y: 0 }\n const rect = container.getBoundingClientRect()\n const localX = screenX - rect.left\n const localY = screenY - rect.top\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n return {\n x: (localX - realToScreen.e) / realToScreen.a,\n y: (localY - realToScreen.f) / realToScreen.d,\n }\n },\n [svgToScreenProjection, realToSvgProjection, containerRef],\n )\n\n const dedupeRoute = (route: Array<{ x: number; y: number }>) => {\n const out: Array<{ x: number; y: number }> = []\n for (const p of route) {\n const last = out[out.length - 1]\n if (!last || Math.hypot(p.x - last.x, p.y - last.y) > 0.05) out.push(p)\n }\n return out\n }\n\n const finishBus = useCallback(\n (route: Array<{ x: number; y: number }>) => {\n const cleaned = dedupeRoute(route)\n if (cleaned.length < 2) return false\n const event: EditSchematicBusAddEvent = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_bus_add\",\n route: cleaned,\n created_at: Date.now(),\n in_progress: false,\n }\n onEditEvent?.(event)\n setState({ isDrawing: false, previewEnd: null, waypoints: [] })\n return true\n },\n [onEditEvent],\n )\n\n const handleMouseDown = useCallback(\n (e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target))\n return\n const current = stateRef.current\n const realPos = screenToReal(e.clientX, e.clientY)\n\n if (!current.isDrawing) {\n e.stopPropagation()\n setState({\n isDrawing: true,\n previewEnd: realPos,\n waypoints: [realPos],\n })\n return\n }\n\n e.stopPropagation()\n setState((prev) => ({\n ...prev,\n waypoints: [...prev.waypoints, realPos],\n previewEnd: realPos,\n }))\n },\n [enabled, screenToReal],\n )\n\n const handleDoubleClick = useCallback(\n (e: MouseEvent) => {\n if (!enabled || !stateRef.current.isDrawing) return\n const current = stateRef.current\n const end = screenToReal(e.clientX, e.clientY)\n const route = [...current.waypoints, end]\n if (finishBus(route.length >= 2 ? route : current.waypoints)) {\n e.preventDefault()\n e.stopPropagation()\n }\n },\n [enabled, screenToReal, finishBus],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!enabled || !stateRef.current.isDrawing) return\n const realPos = screenToReal(e.clientX, e.clientY)\n setState((prev) => ({ ...prev, previewEnd: realPos }))\n },\n [enabled, screenToReal],\n )\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n const target = e.target as Element | null\n if (target?.closest(\"input, textarea, select\")) return\n\n if (e.key === \"Enter\" && stateRef.current.isDrawing) {\n const route = stateRef.current.waypoints\n if (finishBus(route)) {\n e.preventDefault()\n }\n return\n }\n\n if (e.key === \"Escape\" && stateRef.current.isDrawing) {\n setState({ isDrawing: false, previewEnd: null, waypoints: [] })\n }\n },\n [finishBus],\n )\n\n useEffect(() => {\n if (!enabled) {\n setState({ isDrawing: false, previewEnd: null, waypoints: [] })\n return\n }\n window.addEventListener(\"mousedown\", handleMouseDown, { capture: true })\n window.addEventListener(\"dblclick\", handleDoubleClick, { capture: true })\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n window.removeEventListener(\"mousedown\", handleMouseDown, {\n capture: true,\n })\n window.removeEventListener(\"dblclick\", handleDoubleClick, {\n capture: true,\n })\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [\n enabled,\n handleMouseDown,\n handleDoubleClick,\n handleMouseMove,\n handleKeyDown,\n ])\n\n return { busDrawingState: state }\n}\n","import { useCallback, useEffect, useState } from \"react\"\nimport { type Matrix, compose } from \"transformation-matrix\"\nimport type { EditSchematicBusEntryAddEvent } from \"../types/edit-events\"\nimport { isMouseCaptureIgnoredTarget } from \"../utils/isMouseCaptureIgnoredTarget\"\n\nexport const BUS_ENTRY_STUB_LEN = 2.5\n\nexport interface BusEntryPreviewState {\n anchor: { x: number; y: number } | null\n}\n\nexport const useBusEntryPlacement = ({\n enabled,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent,\n}: {\n enabled: boolean\n svgToScreenProjection: Matrix\n realToSvgProjection: Matrix\n containerRef: React.RefObject\n onEditEvent?: (event: EditSchematicBusEntryAddEvent) => void\n}) => {\n const [previewState, setPreviewState] = useState({\n anchor: null,\n })\n\n const screenToReal = useCallback(\n (screenX: number, screenY: number) => {\n const container = containerRef.current\n if (!container) return { x: 0, y: 0 }\n const rect = container.getBoundingClientRect()\n const localX = screenX - rect.left\n const localY = screenY - rect.top\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n return {\n x: (localX - realToScreen.e) / realToScreen.a,\n y: (localY - realToScreen.f) / realToScreen.d,\n }\n },\n [svgToScreenProjection, realToSvgProjection, containerRef],\n )\n\n const placeEntry = useCallback(\n (anchor: { x: number; y: number }) => {\n const event: EditSchematicBusEntryAddEvent = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_bus_entry_add\",\n anchor,\n created_at: Date.now(),\n in_progress: false,\n }\n onEditEvent?.(event)\n },\n [onEditEvent],\n )\n\n const handleMouseDown = useCallback(\n (e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target))\n return\n const anchor = screenToReal(e.clientX, e.clientY)\n e.preventDefault()\n e.stopPropagation()\n placeEntry(anchor)\n },\n [enabled, screenToReal, placeEntry],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!enabled) return\n setPreviewState({ anchor: screenToReal(e.clientX, e.clientY) })\n },\n [enabled, screenToReal],\n )\n\n useEffect(() => {\n if (!enabled) {\n setPreviewState({ anchor: null })\n return\n }\n window.addEventListener(\"mousedown\", handleMouseDown, { capture: true })\n window.addEventListener(\"mousemove\", handleMouseMove)\n return () => {\n window.removeEventListener(\"mousedown\", handleMouseDown, {\n capture: true,\n })\n window.removeEventListener(\"mousemove\", handleMouseMove)\n }\n }, [enabled, handleMouseDown, handleMouseMove])\n\n return { busEntryPreviewState: previewState }\n}\n","import { useCallback, useEffect, useState } from \"react\"\nimport { type Matrix, compose } from \"transformation-matrix\"\nimport type { EditSchematicNoConnectAddEvent } from \"../types/edit-events\"\nimport { isMouseCaptureIgnoredTarget } from \"../utils/isMouseCaptureIgnoredTarget\"\n\nexport const NO_CONNECT_HALF = 0.4\nconst PORT_HIT_RADIUS_PX = 36\n\nexport interface NoConnectPreviewState {\n center: { x: number; y: number } | null\n}\n\nexport const useNoConnectPlacement = ({\n enabled,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent,\n}: {\n enabled: boolean\n circuitJson: any[]\n svgToScreenProjection: Matrix\n realToSvgProjection: Matrix\n containerRef: React.RefObject\n onEditEvent?: (event: EditSchematicNoConnectAddEvent) => void\n}) => {\n const [previewState, setPreviewState] = useState({\n center: null,\n })\n\n const screenToReal = useCallback(\n (screenX: number, screenY: number) => {\n const container = containerRef.current\n if (!container) return { x: 0, y: 0 }\n const rect = container.getBoundingClientRect()\n const localX = screenX - rect.left\n const localY = screenY - rect.top\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n return {\n x: (localX - realToScreen.e) / realToScreen.a,\n y: (localY - realToScreen.f) / realToScreen.d,\n }\n },\n [svgToScreenProjection, realToSvgProjection, containerRef],\n )\n\n const resolveSchematicPortId = useCallback(\n (portId: string): string | null => {\n const bySchematic = (circuitJson as any[]).find(\n (el) => el.type === \"schematic_port\" && el.schematic_port_id === portId,\n )\n if (bySchematic) return portId\n const bySource = (circuitJson as any[]).find(\n (el) => el.type === \"schematic_port\" && el.source_port_id === portId,\n )\n return bySource?.schematic_port_id ?? null\n },\n [circuitJson],\n )\n\n const getPortCenter = useCallback(\n (portId: string): { x: number; y: number } | null => {\n const schematicPortId = resolveSchematicPortId(portId)\n if (!schematicPortId) return null\n const port = (circuitJson as any[]).find(\n (el) =>\n el.type === \"schematic_port\" &&\n el.schematic_port_id === schematicPortId,\n )\n if (!port?.center) return null\n return { x: port.center.x, y: port.center.y }\n },\n [circuitJson, resolveSchematicPortId],\n )\n\n const getPortAtScreen = useCallback(\n (screenX: number, screenY: number): string | null => {\n const container = containerRef.current\n if (!container) return null\n let closest: { id: string; dist: number } | null = null\n const portEls = container.querySelectorAll(\"[data-schematic-port-id]\")\n for (const node of Array.from(portEls)) {\n const rect = node.getBoundingClientRect()\n const cx = rect.left + rect.width / 2\n const cy = rect.top + rect.height / 2\n const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2)\n if (dist < PORT_HIT_RADIUS_PX && (!closest || dist < closest.dist)) {\n const id = node.getAttribute(\"data-schematic-port-id\")\n const resolved = id ? resolveSchematicPortId(id) : null\n if (resolved) closest = { id: resolved, dist }\n }\n }\n return closest?.id ?? null\n },\n [containerRef, resolveSchematicPortId],\n )\n\n const placeNoConnect = useCallback(\n (center: { x: number; y: number }, schematicPortId?: string) => {\n const event: EditSchematicNoConnectAddEvent = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_no_connect_add\",\n center,\n schematic_port_id: schematicPortId,\n created_at: Date.now(),\n in_progress: false,\n }\n onEditEvent?.(event)\n },\n [onEditEvent],\n )\n\n const handleMouseDown = useCallback(\n (e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target))\n return\n e.preventDefault()\n e.stopPropagation()\n\n const portId = getPortAtScreen(e.clientX, e.clientY)\n if (portId) {\n const center = getPortCenter(portId)\n if (center) {\n placeNoConnect(center, portId)\n return\n }\n }\n placeNoConnect(screenToReal(e.clientX, e.clientY))\n },\n [enabled, getPortAtScreen, getPortCenter, placeNoConnect, screenToReal],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!enabled) return\n const portId = getPortAtScreen(e.clientX, e.clientY)\n if (portId) {\n const center = getPortCenter(portId)\n if (center) {\n setPreviewState({ center })\n return\n }\n }\n setPreviewState({ center: screenToReal(e.clientX, e.clientY) })\n },\n [enabled, getPortAtScreen, getPortCenter, screenToReal],\n )\n\n useEffect(() => {\n if (!enabled) {\n setPreviewState({ center: null })\n return\n }\n window.addEventListener(\"mousedown\", handleMouseDown, { capture: true })\n window.addEventListener(\"mousemove\", handleMouseMove)\n return () => {\n window.removeEventListener(\"mousedown\", handleMouseDown, {\n capture: true,\n })\n window.removeEventListener(\"mousemove\", handleMouseMove)\n }\n }, [enabled, handleMouseDown, handleMouseMove])\n\n return { noConnectPreviewState: previewState }\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { type Matrix, compose } from \"transformation-matrix\"\nimport type { EditSchematicNetLabelAddEvent } from \"../types/edit-events\"\nimport { isMouseCaptureIgnoredTarget } from \"../utils/isMouseCaptureIgnoredTarget\"\n\nconst PORT_SNAP_RADIUS_PX = 32\n\nexport interface NetLabelPlacementState {\n previewPos: { x: number; y: number } | null\n pendingPos: { x: number; y: number } | null\n pendingPortId: string | null\n}\n\nexport const useNetLabelPlacement = ({\n enabled,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent,\n}: {\n enabled: boolean\n circuitJson: any[]\n svgToScreenProjection: Matrix\n realToSvgProjection: Matrix\n containerRef: React.RefObject\n onEditEvent?: (event: EditSchematicNetLabelAddEvent) => void\n}) => {\n const [state, setState] = useState({\n previewPos: null,\n pendingPos: null,\n pendingPortId: null,\n })\n\n const stateRef = useRef(state)\n stateRef.current = state\n\n const screenToReal = useCallback(\n (screenX: number, screenY: number) => {\n const container = containerRef.current\n if (!container) return { x: 0, y: 0 }\n const rect = container.getBoundingClientRect()\n const localX = screenX - rect.left\n const localY = screenY - rect.top\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n return {\n x: (localX - realToScreen.e) / realToScreen.a,\n y: (localY - realToScreen.f) / realToScreen.d,\n }\n },\n [svgToScreenProjection, realToSvgProjection, containerRef],\n )\n\n const snapToPort = useCallback(\n (\n screenX: number,\n screenY: number,\n ): { pos: { x: number; y: number }; portId: string | null } => {\n const container = containerRef.current\n if (!container)\n return { pos: screenToReal(screenX, screenY), portId: null }\n\n let closest: {\n id: string\n dist: number\n center: { x: number; y: number }\n } | null = null\n const portEls = container.querySelectorAll(\"[data-schematic-port-id]\")\n\n for (const node of Array.from(portEls)) {\n const rect = node.getBoundingClientRect()\n const cx = rect.left + rect.width / 2\n const cy = rect.top + rect.height / 2\n const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2)\n if (dist < PORT_SNAP_RADIUS_PX && (!closest || dist < closest.dist)) {\n const id = node.getAttribute(\"data-schematic-port-id\")\n const portEl = circuitJson.find(\n (el) => el.type === \"schematic_port\" && el.schematic_port_id === id,\n )\n if (id && portEl?.center) {\n closest = { id, dist, center: portEl.center }\n }\n }\n }\n\n if (closest) return { pos: closest.center, portId: closest.id }\n return { pos: screenToReal(screenX, screenY), portId: null }\n },\n [containerRef, circuitJson, screenToReal],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!enabled || stateRef.current.pendingPos) return\n const { pos } = snapToPort(e.clientX, e.clientY)\n setState((prev) => ({ ...prev, previewPos: pos }))\n },\n [enabled, snapToPort],\n )\n\n const handleMouseDown = useCallback(\n (e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target))\n return\n if (stateRef.current.pendingPos) return\n e.preventDefault()\n e.stopPropagation()\n const { pos, portId } = snapToPort(e.clientX, e.clientY)\n setState((prev) => ({\n ...prev,\n pendingPos: pos,\n pendingPortId: portId,\n previewPos: null,\n }))\n },\n [enabled, snapToPort],\n )\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n setState({\n previewPos: null,\n pendingPos: null,\n pendingPortId: null,\n })\n }\n }, [])\n\n const confirmPlacement = useCallback(\n (netName: string) => {\n const current = stateRef.current\n if (!current.pendingPos || !netName.trim()) return\n const event: EditSchematicNetLabelAddEvent = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_net_label_add\",\n position: current.pendingPos,\n net_name: netName.trim(),\n schematic_port_id: current.pendingPortId ?? undefined,\n anchor_side: \"right\",\n created_at: Date.now(),\n in_progress: false,\n }\n onEditEvent?.(event)\n setState({ previewPos: null, pendingPos: null, pendingPortId: null })\n },\n [onEditEvent],\n )\n\n const cancelPlacement = useCallback(() => {\n setState({ previewPos: null, pendingPos: null, pendingPortId: null })\n }, [])\n\n useEffect(() => {\n if (!enabled) {\n setState({ previewPos: null, pendingPos: null, pendingPortId: null })\n return\n }\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mousedown\", handleMouseDown, { capture: true })\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mousedown\", handleMouseDown, {\n capture: true,\n })\n window.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown])\n\n return { netLabelState: state, confirmPlacement, cancelPlacement }\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { type Matrix, compose } from \"transformation-matrix\"\nimport type { EditSchematicGlobalLabelAddEvent } from \"../types/edit-events\"\nimport { isMouseCaptureIgnoredTarget } from \"../utils/isMouseCaptureIgnoredTarget\"\n\nconst PORT_SNAP_RADIUS_PX = 32\n\nexport interface GlobalLabelPlacementState {\n previewPos: { x: number; y: number } | null\n pendingPos: { x: number; y: number } | null\n pendingPortId: string | null\n}\n\nexport const useGlobalLabelPlacement = ({\n enabled,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent,\n}: {\n enabled: boolean\n circuitJson: any[]\n svgToScreenProjection: Matrix\n realToSvgProjection: Matrix\n containerRef: React.RefObject\n onEditEvent?: (event: EditSchematicGlobalLabelAddEvent) => void\n}) => {\n const [state, setState] = useState({\n previewPos: null,\n pendingPos: null,\n pendingPortId: null,\n })\n\n const stateRef = useRef(state)\n stateRef.current = state\n\n const screenToReal = useCallback(\n (screenX: number, screenY: number) => {\n const container = containerRef.current\n if (!container) return { x: 0, y: 0 }\n const rect = container.getBoundingClientRect()\n const localX = screenX - rect.left\n const localY = screenY - rect.top\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n return {\n x: (localX - realToScreen.e) / realToScreen.a,\n y: (localY - realToScreen.f) / realToScreen.d,\n }\n },\n [svgToScreenProjection, realToSvgProjection, containerRef],\n )\n\n const snapToPort = useCallback(\n (\n screenX: number,\n screenY: number,\n ): { pos: { x: number; y: number }; portId: string | null } => {\n const container = containerRef.current\n if (!container)\n return { pos: screenToReal(screenX, screenY), portId: null }\n\n let closest: {\n id: string\n dist: number\n center: { x: number; y: number }\n } | null = null\n const portEls = container.querySelectorAll(\"[data-schematic-port-id]\")\n\n for (const node of Array.from(portEls)) {\n const rect = node.getBoundingClientRect()\n const cx = rect.left + rect.width / 2\n const cy = rect.top + rect.height / 2\n const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2)\n if (dist < PORT_SNAP_RADIUS_PX && (!closest || dist < closest.dist)) {\n const id = node.getAttribute(\"data-schematic-port-id\")\n const portEl = circuitJson.find(\n (el) => el.type === \"schematic_port\" && el.schematic_port_id === id,\n )\n if (id && portEl?.center) {\n closest = { id, dist, center: portEl.center }\n }\n }\n }\n\n if (closest) return { pos: closest.center, portId: closest.id }\n return { pos: screenToReal(screenX, screenY), portId: null }\n },\n [containerRef, circuitJson, screenToReal],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!enabled || stateRef.current.pendingPos) return\n const { pos } = snapToPort(e.clientX, e.clientY)\n setState((prev) => ({ ...prev, previewPos: pos }))\n },\n [enabled, snapToPort],\n )\n\n const handleMouseDown = useCallback(\n (e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target))\n return\n if (stateRef.current.pendingPos) return\n e.preventDefault()\n e.stopPropagation()\n const { pos, portId } = snapToPort(e.clientX, e.clientY)\n setState((prev) => ({\n ...prev,\n pendingPos: pos,\n pendingPortId: portId,\n previewPos: null,\n }))\n },\n [enabled, snapToPort],\n )\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n setState({\n previewPos: null,\n pendingPos: null,\n pendingPortId: null,\n })\n }\n }, [])\n\n const confirmPlacement = useCallback(\n (netName: string) => {\n const current = stateRef.current\n if (!current.pendingPos || !netName.trim()) return\n const event: EditSchematicGlobalLabelAddEvent = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_global_label_add\",\n position: current.pendingPos,\n net_name: netName.trim(),\n schematic_port_id: current.pendingPortId ?? undefined,\n anchor_side: \"right\",\n created_at: Date.now(),\n in_progress: false,\n }\n onEditEvent?.(event)\n setState({ previewPos: null, pendingPos: null, pendingPortId: null })\n },\n [onEditEvent],\n )\n\n const cancelPlacement = useCallback(() => {\n setState({ previewPos: null, pendingPos: null, pendingPortId: null })\n }, [])\n\n useEffect(() => {\n if (!enabled) {\n setState({ previewPos: null, pendingPos: null, pendingPortId: null })\n return\n }\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mousedown\", handleMouseDown, { capture: true })\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mousedown\", handleMouseDown, {\n capture: true,\n })\n window.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown])\n\n return { globalLabelState: state, confirmPlacement, cancelPlacement }\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport {\n type Matrix,\n applyToPoint,\n compose,\n inverse,\n} from \"transformation-matrix\"\nimport type { EditSchematicHierSheetAddEvent } from \"../types/edit-events\"\nimport { isMouseCaptureIgnoredTarget } from \"../utils/isMouseCaptureIgnoredTarget\"\n\nconst MIN_BOX_PX = 48\n\nexport interface ScreenBox {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport interface HierSheetPlacementState {\n isDrawing: boolean\n anchorLocal: { x: number; y: number } | null\n previewLocal: { x: number; y: number } | null\n pendingBox: { x: number; y: number; width: number; height: number } | null\n pendingScreenBox: ScreenBox | null\n}\n\nexport function normalizeScreenBox(\n a: { x: number; y: number },\n b: { x: number; y: number },\n): ScreenBox {\n const x = Math.min(a.x, b.x)\n const y = Math.min(a.y, b.y)\n let width = Math.abs(b.x - a.x)\n let height = Math.abs(b.y - a.y)\n if (width < MIN_BOX_PX) width = MIN_BOX_PX\n if (height < MIN_BOX_PX) height = MIN_BOX_PX\n return { x, y, width, height }\n}\n\nexport function realBoxFromScreenBox(\n screenBox: ScreenBox,\n localToReal: (x: number, y: number) => { x: number; y: number },\n): { x: number; y: number; width: number; height: number } {\n const { x, y, width, height } = screenBox\n const corners = [\n localToReal(x, y),\n localToReal(x + width, y),\n localToReal(x, y + height),\n localToReal(x + width, y + height),\n ]\n const xs = corners.map((c) => c.x)\n const ys = corners.map((c) => c.y)\n const minX = Math.min(...xs)\n const maxX = Math.max(...xs)\n const minY = Math.min(...ys)\n const maxY = Math.max(...ys)\n return {\n x: minX,\n y: minY,\n width: maxX - minX,\n height: maxY - minY,\n }\n}\n\nexport const useHierSheetPlacement = ({\n enabled,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent,\n}: {\n enabled: boolean\n svgToScreenProjection: Matrix\n realToSvgProjection: Matrix\n containerRef: React.RefObject\n onEditEvent?: (event: EditSchematicHierSheetAddEvent) => void\n}) => {\n const [state, setState] = useState({\n isDrawing: false,\n anchorLocal: null,\n previewLocal: null,\n pendingBox: null,\n pendingScreenBox: null,\n })\n\n const stateRef = useRef(state)\n stateRef.current = state\n\n const localFromMouse = useCallback(\n (clientX: number, clientY: number) => {\n const container = containerRef.current\n if (!container) return { x: 0, y: 0 }\n const rect = container.getBoundingClientRect()\n return { x: clientX - rect.left, y: clientY - rect.top }\n },\n [containerRef],\n )\n\n const localToReal = useCallback(\n (localX: number, localY: number) => {\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n return applyToPoint(inverse(realToScreen), { x: localX, y: localY })\n },\n [svgToScreenProjection, realToSvgProjection],\n )\n\n const reset = useCallback(() => {\n setState({\n isDrawing: false,\n anchorLocal: null,\n previewLocal: null,\n pendingBox: null,\n pendingScreenBox: null,\n })\n }, [])\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!enabled || stateRef.current.pendingBox) return\n const pos = localFromMouse(e.clientX, e.clientY)\n if (stateRef.current.isDrawing && stateRef.current.anchorLocal) {\n setState((prev) => ({ ...prev, previewLocal: pos }))\n }\n },\n [enabled, localFromMouse],\n )\n\n const handleMouseDown = useCallback(\n (e: MouseEvent) => {\n if (\n !enabled ||\n e.button !== 0 ||\n stateRef.current.pendingBox ||\n isMouseCaptureIgnoredTarget(e.target)\n )\n return\n e.preventDefault()\n e.stopPropagation()\n const pos = localFromMouse(e.clientX, e.clientY)\n const current = stateRef.current\n\n if (!current.isDrawing) {\n setState({\n isDrawing: true,\n anchorLocal: pos,\n previewLocal: pos,\n pendingBox: null,\n pendingScreenBox: null,\n })\n return\n }\n\n if (!current.anchorLocal) return\n const screenBox = normalizeScreenBox(current.anchorLocal, pos)\n const box = realBoxFromScreenBox(screenBox, localToReal)\n setState({\n isDrawing: false,\n anchorLocal: null,\n previewLocal: null,\n pendingBox: box,\n pendingScreenBox: screenBox,\n })\n },\n [enabled, localFromMouse, localToReal],\n )\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === \"Escape\") reset()\n },\n [reset],\n )\n\n const confirmPlacement = useCallback(\n (sheetName: string, targetSheetId: string) => {\n const box = stateRef.current.pendingBox\n const screenBox = stateRef.current.pendingScreenBox\n if (!box || !screenBox || !sheetName.trim() || !targetSheetId.trim())\n return\n\n const sheetNamePos = localToReal(screenBox.x + 6, screenBox.y + 14)\n const fileNamePos = localToReal(\n screenBox.x + 6,\n screenBox.y + screenBox.height - 8,\n )\n\n const event: EditSchematicHierSheetAddEvent = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_hier_sheet_add\",\n box,\n sheet_name: sheetName.trim(),\n target_sheet_id: targetSheetId.trim(),\n sheet_name_pos: sheetNamePos,\n file_name_pos: fileNamePos,\n created_at: Date.now(),\n in_progress: false,\n }\n onEditEvent?.(event)\n reset()\n },\n [onEditEvent, reset, localToReal],\n )\n\n const cancelPlacement = useCallback(() => reset(), [reset])\n\n useEffect(() => {\n if (!enabled) {\n reset()\n return\n }\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mousedown\", handleMouseDown, { capture: true })\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mousedown\", handleMouseDown, {\n capture: true,\n })\n window.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown, reset])\n\n return { hierSheetState: state, confirmPlacement, cancelPlacement }\n}\n","import { type Matrix, applyToPoint, compose } from \"transformation-matrix\"\nimport type { WireDrawingState } from \"../hooks/useWireDrawing\"\n\ninterface Props {\n state: WireDrawingState\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n containerRef: React.RefObject\n}\n\nexport const WirePreview = ({\n state,\n realToSvgProjection,\n svgToScreenProjection,\n containerRef,\n}: Props) => {\n if (!state.isDrawing || !state.previewEnd || state.waypoints.length === 0)\n return null\n\n const container = containerRef.current\n if (!container) return null\n\n if (\n !realToSvgProjection?.a ||\n isNaN(realToSvgProjection.a) ||\n !svgToScreenProjection?.a ||\n isNaN(svgToScreenProjection.a)\n )\n return null\n\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n\n const toScreen = (pt: { x: number; y: number }) =>\n applyToPoint(realToScreen, pt)\n\n const points = [...state.waypoints.map(toScreen), toScreen(state.previewEnd)]\n\n const d = points\n .map((p, i) => `${i === 0 ? \"M\" : \"L\"} ${p.x} ${p.y}`)\n .join(\" \")\n\n return (\n \n \n \n \n \n )\n}\n","import { type Matrix, applyToPoint, compose } from \"transformation-matrix\"\nimport type { BusDrawingState } from \"../hooks/useBusDrawing\"\n\ninterface Props {\n state: BusDrawingState\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n containerRef: React.RefObject\n}\n\nexport const BusPreview = ({\n state,\n realToSvgProjection,\n svgToScreenProjection,\n containerRef,\n}: Props) => {\n if (!state.isDrawing || !state.previewEnd || state.waypoints.length === 0)\n return null\n\n const container = containerRef.current\n if (!container) return null\n\n if (\n !realToSvgProjection?.a ||\n isNaN(realToSvgProjection.a) ||\n !svgToScreenProjection?.a ||\n isNaN(svgToScreenProjection.a)\n )\n return null\n\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n const toScreen = (pt: { x: number; y: number }) =>\n applyToPoint(realToScreen, pt)\n\n const points = [...state.waypoints.map(toScreen), toScreen(state.previewEnd)]\n\n const d = points\n .map((p, i) => `${i === 0 ? \"M\" : \"L\"} ${p.x} ${p.y}`)\n .join(\" \")\n\n return (\n \n \n \n )\n}\n","import { type Matrix, applyToPoint, compose } from \"transformation-matrix\"\nimport {\n BUS_ENTRY_STUB_LEN,\n type BusEntryPreviewState,\n} from \"../hooks/useBusEntryPlacement\"\n\ninterface Props {\n state: BusEntryPreviewState\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n containerRef: React.RefObject\n}\n\nexport const BusEntryPreview = ({\n state,\n realToSvgProjection,\n svgToScreenProjection,\n containerRef,\n}: Props) => {\n if (!state.anchor || !containerRef.current) return null\n if (!realToSvgProjection?.a || !svgToScreenProjection?.a) return null\n\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n const toScreen = (pt: { x: number; y: number }) =>\n applyToPoint(realToScreen, pt)\n\n const p1 = toScreen(state.anchor)\n const p2 = toScreen({\n x: state.anchor.x + BUS_ENTRY_STUB_LEN,\n y: state.anchor.y + BUS_ENTRY_STUB_LEN,\n })\n\n return (\n \n \n \n )\n}\n","import { type Matrix, applyToPoint, compose } from \"transformation-matrix\"\nimport {\n NO_CONNECT_HALF,\n type NoConnectPreviewState,\n} from \"../hooks/useNoConnectPlacement\"\n\ninterface Props {\n state: NoConnectPreviewState\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n containerRef: React.RefObject\n}\n\nexport const NoConnectPreview = ({\n state,\n realToSvgProjection,\n svgToScreenProjection,\n containerRef,\n}: Props) => {\n if (!state.center || !containerRef.current) return null\n if (!realToSvgProjection?.a || !svgToScreenProjection?.a) return null\n\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n const toScreen = (pt: { x: number; y: number }) =>\n applyToPoint(realToScreen, pt)\n const { x, y } = state.center\n const d = NO_CONNECT_HALF\n\n const a1 = toScreen({ x: x - d, y: y - d })\n const a2 = toScreen({ x: x + d, y: y + d })\n const b1 = toScreen({ x: x + d, y: y - d })\n const b2 = toScreen({ x: x - d, y: y + d })\n\n return (\n \n \n \n \n )\n}\n","import { type Matrix, applyToPoint, compose } from \"transformation-matrix\"\nimport { useRef, useEffect } from \"react\"\nimport type { NetLabelPlacementState } from \"../hooks/useNetLabelPlacement\"\n\ninterface Props {\n state: NetLabelPlacementState\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n containerRef: React.RefObject\n onConfirm: (netName: string) => void\n onCancel: () => void\n}\n\nexport const NetLabelPreview = ({\n state,\n realToSvgProjection,\n svgToScreenProjection,\n containerRef,\n onConfirm,\n onCancel,\n}: Props) => {\n const inputRef = useRef(null)\n\n useEffect(() => {\n if (state.pendingPos && inputRef.current) {\n inputRef.current.focus()\n inputRef.current.value = \"\"\n }\n }, [state.pendingPos])\n\n const container = containerRef.current\n if (!container) return null\n\n if (\n !realToSvgProjection?.a ||\n isNaN(realToSvgProjection.a) ||\n !svgToScreenProjection?.a ||\n isNaN(svgToScreenProjection.a)\n )\n return null\n\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n const toScreen = (pt: { x: number; y: number }) =>\n applyToPoint(realToScreen, pt)\n\n const LabelShape = ({ pos }: { pos: { x: number; y: number } }) => {\n const sp = toScreen(pos)\n return (\n \n \n \n NET\n \n \n \n )\n }\n\n return (\n <>\n {state.previewPos && !state.pendingPos && (\n \n \n \n )}\n\n {state.pendingPos &&\n (() => {\n const sp = toScreen(state.pendingPos)\n return (\n \n {\n if (e.key === \"Enter\") {\n onConfirm((e.target as HTMLInputElement).value)\n } else if (e.key === \"Escape\") {\n onCancel()\n }\n e.stopPropagation()\n }}\n />\n {\n if (inputRef.current) onConfirm(inputRef.current.value)\n }}\n >\n ✓\n \n \n )\n })()}\n \n )\n}\n","import { type Matrix, applyToPoint, compose } from \"transformation-matrix\"\nimport { useRef, useEffect } from \"react\"\nimport type { GlobalLabelPlacementState } from \"../hooks/useGlobalLabelPlacement\"\n\nconst GLOBAL_COLOR = \"#c1271c\"\n\ninterface Props {\n state: GlobalLabelPlacementState\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n containerRef: React.RefObject\n onConfirm: (netName: string) => void\n onCancel: () => void\n}\n\nexport const GlobalLabelPreview = ({\n state,\n realToSvgProjection,\n svgToScreenProjection,\n containerRef,\n onConfirm,\n onCancel,\n}: Props) => {\n const inputRef = useRef(null)\n\n useEffect(() => {\n if (state.pendingPos && inputRef.current) {\n inputRef.current.focus()\n inputRef.current.value = \"\"\n }\n }, [state.pendingPos])\n\n const container = containerRef.current\n if (!container) return null\n\n if (\n !realToSvgProjection?.a ||\n isNaN(realToSvgProjection.a) ||\n !svgToScreenProjection?.a ||\n isNaN(svgToScreenProjection.a)\n )\n return null\n\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n const toScreen = (pt: { x: number; y: number }) =>\n applyToPoint(realToScreen, pt)\n\n const LabelShape = ({ pos }: { pos: { x: number; y: number } }) => {\n const sp = toScreen(pos)\n return (\n \n \n \n GLOBAL\n \n \n \n )\n }\n\n return (\n <>\n {state.previewPos && !state.pendingPos && (\n \n \n \n )}\n\n {state.pendingPos &&\n (() => {\n const sp = toScreen(state.pendingPos)\n return (\n \n {\n if (e.key === \"Enter\") {\n onConfirm((e.target as HTMLInputElement).value)\n } else if (e.key === \"Escape\") {\n onCancel()\n }\n e.stopPropagation()\n }}\n />\n {\n if (inputRef.current) onConfirm(inputRef.current.value)\n }}\n >\n ✓\n \n \n )\n })()}\n \n )\n}\n","import { type Matrix, applyToPoint, compose } from \"transformation-matrix\"\nimport { useEffect, useMemo, useRef, useState } from \"react\"\nimport type {\n HierSheetPlacementState,\n ScreenBox,\n} from \"../hooks/useHierSheetPlacement\"\nimport { normalizeScreenBox } from \"../hooks/useHierSheetPlacement\"\n\nconst SHEET_STROKE = \"#0050d8\"\nconst SHEET_FILL = \"rgba(234, 242, 255, 0.35)\"\nconst NAME_COLOR = \"#006464\"\nconst FILE_COLOR = \"#725600\"\n\nexport interface HierSheetTarget {\n id: string\n title: string\n}\n\ninterface Props {\n state: HierSheetPlacementState\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n containerRef: React.RefObject\n sheetTargets: HierSheetTarget[]\n activeSheetId?: string\n onConfirm: (sheetName: string, targetSheetId: string) => void\n onCancel: () => void\n}\n\nconst SheetSymbolShape = ({\n screenBox,\n dashed = true,\n}: {\n screenBox: ScreenBox\n dashed?: boolean\n}) => {\n const { x, y, width, height } = screenBox\n const pinLen = Math.min(10, width * 0.08)\n const midY = y + height / 2\n return (\n \n \n \n \n \n \n Sheet name\n \n \n Target sheet\n \n \n )\n}\n\nfunction pendingBoxKey(box: {\n x: number\n y: number\n width: number\n height: number\n}): string {\n return `${box.x},${box.y},${box.width},${box.height}`\n}\n\nexport const HierSheetPreview = ({\n state,\n realToSvgProjection,\n svgToScreenProjection,\n containerRef,\n sheetTargets,\n activeSheetId,\n onConfirm,\n onCancel,\n}: Props) => {\n const [sheetName, setSheetName] = useState(\"\")\n const [targetSheetId, setTargetSheetId] = useState(\"\")\n const initializedKeyRef = useRef(null)\n const nameInputRef = useRef(null)\n\n const targets = useMemo(\n () => sheetTargets.filter((t) => t.id !== activeSheetId),\n [sheetTargets, activeSheetId],\n )\n\n useEffect(() => {\n if (!state.pendingBox) {\n initializedKeyRef.current = null\n setSheetName(\"\")\n setTargetSheetId(\"\")\n return\n }\n\n const key = pendingBoxKey(state.pendingBox)\n if (initializedKeyRef.current === key) return\n\n initializedKeyRef.current = key\n setSheetName(\"\")\n setTargetSheetId(targets[0]?.id ?? \"\")\n requestAnimationFrame(() => nameInputRef.current?.focus())\n }, [state.pendingBox, targets])\n\n const container = containerRef.current\n if (!container) return null\n\n if (\n !realToSvgProjection?.a ||\n isNaN(realToSvgProjection.a) ||\n !svgToScreenProjection?.a ||\n isNaN(svgToScreenProjection.a)\n ) {\n return null\n }\n\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n const boxToScreen = (box: {\n x: number\n y: number\n width: number\n height: number\n }): ScreenBox => {\n const p1 = applyToPoint(realToScreen, { x: box.x, y: box.y })\n const p2 = applyToPoint(realToScreen, {\n x: box.x + box.width,\n y: box.y + box.height,\n })\n return {\n x: Math.min(p1.x, p2.x),\n y: Math.min(p1.y, p2.y),\n width: Math.abs(p2.x - p1.x),\n height: Math.abs(p2.y - p1.y),\n }\n }\n\n const previewScreenBox =\n state.isDrawing && state.anchorLocal && state.previewLocal\n ? normalizeScreenBox(state.anchorLocal, state.previewLocal)\n : null\n\n const committedScreenBox =\n state.pendingScreenBox ??\n (state.pendingBox ? boxToScreen(state.pendingBox) : null)\n\n const dialogAnchor = committedScreenBox\n\n const submit = () => {\n if (!sheetName.trim() || !targetSheetId.trim()) return\n onConfirm(sheetName, targetSheetId)\n }\n\n return (\n <>\n {previewScreenBox && (\n \n \n \n )}\n\n {state.pendingBox && committedScreenBox && (\n \n \n \n )}\n\n {state.pendingBox && dialogAnchor && (\n e.stopPropagation()}\n data-schematic-ignore-mouse-capture\n >\n \n Sheet name\n setSheetName(e.target.value)}\n style={{\n display: \"block\",\n marginTop: 3,\n width: \"100%\",\n background: \"#0f0f1a\",\n border: `1px solid ${NAME_COLOR}`,\n color: \"#fff\",\n padding: \"4px 8px\",\n borderRadius: 4,\n fontFamily: \"monospace\",\n fontSize: 12,\n outline: \"none\",\n }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n submit()\n } else if (e.key === \"Escape\") {\n onCancel()\n }\n e.stopPropagation()\n }}\n />\n \n\n \n Target sheet (file name)\n setTargetSheetId(e.target.value)}\n style={{\n display: \"block\",\n marginTop: 3,\n width: \"100%\",\n background: \"#0f0f1a\",\n border: `1px solid ${FILE_COLOR}`,\n color: \"#fff\",\n padding: \"4px 8px\",\n borderRadius: 4,\n fontFamily: \"monospace\",\n fontSize: 12,\n outline: \"none\",\n }}\n >\n {targets.length === 0 ? (\n \n ) : (\n targets.map((t) => (\n \n ))\n )}\n \n \n\n
\n \n Cancel\n \n \n Place sheet\n \n
\n \n )}\n \n )\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { type Matrix, compose } from \"transformation-matrix\"\nimport type { EditSchematicPowerPortAddEvent } from \"../types/edit-events\"\nimport { isMouseCaptureIgnoredTarget } from \"../utils/isMouseCaptureIgnoredTarget\"\n\nconst PORT_SNAP_RADIUS_PX = 32\n\nexport interface PowerPortPlacementState {\n previewPos: { x: number; y: number } | null\n pendingPos: { x: number; y: number } | null\n pendingPortId: string | null\n}\n\nexport const usePowerPortPlacement = ({\n enabled,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent,\n}: {\n enabled: boolean\n circuitJson: any[]\n svgToScreenProjection: Matrix\n realToSvgProjection: Matrix\n containerRef: React.RefObject\n onEditEvent?: (event: EditSchematicPowerPortAddEvent) => void\n}) => {\n const [state, setState] = useState({\n previewPos: null,\n pendingPos: null,\n pendingPortId: null,\n })\n\n const stateRef = useRef(state)\n stateRef.current = state\n\n const screenToReal = useCallback(\n (screenX: number, screenY: number) => {\n const container = containerRef.current\n if (!container) return { x: 0, y: 0 }\n const rect = container.getBoundingClientRect()\n const localX = screenX - rect.left\n const localY = screenY - rect.top\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n return {\n x: (localX - realToScreen.e) / realToScreen.a,\n y: (localY - realToScreen.f) / realToScreen.d,\n }\n },\n [svgToScreenProjection, realToSvgProjection, containerRef],\n )\n\n const snapToPort = useCallback(\n (\n screenX: number,\n screenY: number,\n ): { pos: { x: number; y: number }; portId: string | null } => {\n const container = containerRef.current\n if (!container)\n return { pos: screenToReal(screenX, screenY), portId: null }\n\n let closest: {\n id: string\n dist: number\n center: { x: number; y: number }\n } | null = null\n const portEls = container.querySelectorAll(\"[data-schematic-port-id]\")\n\n for (const node of Array.from(portEls)) {\n const rect = node.getBoundingClientRect()\n const cx = rect.left + rect.width / 2\n const cy = rect.top + rect.height / 2\n const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2)\n if (dist < PORT_SNAP_RADIUS_PX && (!closest || dist < closest.dist)) {\n const id = node.getAttribute(\"data-schematic-port-id\")\n const portEl = circuitJson.find(\n (el) => el.type === \"schematic_port\" && el.schematic_port_id === id,\n )\n if (id && portEl?.center) {\n closest = { id, dist, center: portEl.center }\n }\n }\n }\n\n if (closest) return { pos: closest.center, portId: closest.id }\n return { pos: screenToReal(screenX, screenY), portId: null }\n },\n [containerRef, circuitJson, screenToReal],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!enabled || stateRef.current.pendingPos) return\n const { pos } = snapToPort(e.clientX, e.clientY)\n setState((prev) => ({ ...prev, previewPos: pos }))\n },\n [enabled, snapToPort],\n )\n\n const handleMouseDown = useCallback(\n (e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target))\n return\n if (stateRef.current.pendingPos) return\n e.preventDefault()\n e.stopPropagation()\n const { pos, portId } = snapToPort(e.clientX, e.clientY)\n setState((prev) => ({\n ...prev,\n pendingPos: pos,\n pendingPortId: portId,\n previewPos: null,\n }))\n },\n [enabled, snapToPort],\n )\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n setState({\n previewPos: null,\n pendingPos: null,\n pendingPortId: null,\n })\n }\n }, [])\n\n const confirmPlacement = useCallback(\n (netName: string) => {\n const current = stateRef.current\n if (!current.pendingPos || !netName.trim()) return\n const event: EditSchematicPowerPortAddEvent = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_power_port_add\",\n position: current.pendingPos,\n net_name: netName.trim(),\n schematic_port_id: current.pendingPortId ?? undefined,\n anchor_side: \"bottom\",\n created_at: Date.now(),\n in_progress: false,\n }\n onEditEvent?.(event)\n setState({ previewPos: null, pendingPos: null, pendingPortId: null })\n },\n [onEditEvent],\n )\n\n const cancelPlacement = useCallback(() => {\n setState({ previewPos: null, pendingPos: null, pendingPortId: null })\n }, [])\n\n useEffect(() => {\n if (!enabled) {\n setState({ previewPos: null, pendingPos: null, pendingPortId: null })\n return\n }\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mousedown\", handleMouseDown, { capture: true })\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mousedown\", handleMouseDown, {\n capture: true,\n })\n window.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown])\n\n return { powerPortState: state, confirmPlacement, cancelPlacement }\n}\n","import { type Matrix, applyToPoint, compose } from \"transformation-matrix\"\nimport { useRef, useEffect } from \"react\"\nimport type { PowerPortPlacementState } from \"../hooks/usePowerPortPlacement\"\n\nconst POWER_COLOR = \"#c1271c\"\n\ninterface Props {\n state: PowerPortPlacementState\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n containerRef: React.RefObject\n onConfirm: (netName: string) => void\n onCancel: () => void\n}\n\nexport const PowerPortPreview = ({\n state,\n realToSvgProjection,\n svgToScreenProjection,\n containerRef,\n onConfirm,\n onCancel,\n}: Props) => {\n const inputRef = useRef(null)\n\n useEffect(() => {\n if (state.pendingPos && inputRef.current) {\n inputRef.current.focus()\n inputRef.current.value = \"VCC\"\n }\n }, [state.pendingPos])\n\n const container = containerRef.current\n if (!container) return null\n\n if (\n !realToSvgProjection?.a ||\n isNaN(realToSvgProjection.a) ||\n !svgToScreenProjection?.a ||\n isNaN(svgToScreenProjection.a)\n ) {\n return null\n }\n\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n const toScreen = (pt: { x: number; y: number }) =>\n applyToPoint(realToScreen, pt)\n\n const PowerShape = ({ pos }: { pos: { x: number; y: number } }) => {\n const sp = toScreen(pos)\n return (\n \n {/* KiCad-style power arrow pointing up; wire at bottom */}\n \n \n \n PWR\n \n \n \n )\n }\n\n return (\n <>\n {state.previewPos && !state.pendingPos && (\n \n \n \n )}\n\n {state.pendingPos &&\n (() => {\n const sp = toScreen(state.pendingPos)\n return (\n \n {\n if (e.key === \"Enter\") {\n onConfirm((e.target as HTMLInputElement).value)\n } else if (e.key === \"Escape\") {\n onCancel()\n }\n e.stopPropagation()\n }}\n />\n {\n if (inputRef.current) onConfirm(inputRef.current.value)\n }}\n >\n ✓\n \n \n )\n })()}\n \n )\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { type Matrix, compose } from \"transformation-matrix\"\nimport type { EditSchematicGroundPortAddEvent } from \"../types/edit-events\"\nimport { isMouseCaptureIgnoredTarget } from \"../utils/isMouseCaptureIgnoredTarget\"\n\nconst PORT_SNAP_RADIUS_PX = 32\n\nexport interface GroundPortPlacementState {\n previewPos: { x: number; y: number } | null\n pendingPos: { x: number; y: number } | null\n pendingPortId: string | null\n}\n\nexport const useGroundPortPlacement = ({\n enabled,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent,\n}: {\n enabled: boolean\n circuitJson: any[]\n svgToScreenProjection: Matrix\n realToSvgProjection: Matrix\n containerRef: React.RefObject\n onEditEvent?: (event: EditSchematicGroundPortAddEvent) => void\n}) => {\n const [state, setState] = useState({\n previewPos: null,\n pendingPos: null,\n pendingPortId: null,\n })\n\n const stateRef = useRef(state)\n stateRef.current = state\n\n const screenToReal = useCallback(\n (screenX: number, screenY: number) => {\n const container = containerRef.current\n if (!container) return { x: 0, y: 0 }\n const rect = container.getBoundingClientRect()\n const localX = screenX - rect.left\n const localY = screenY - rect.top\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n return {\n x: (localX - realToScreen.e) / realToScreen.a,\n y: (localY - realToScreen.f) / realToScreen.d,\n }\n },\n [svgToScreenProjection, realToSvgProjection, containerRef],\n )\n\n const snapToPort = useCallback(\n (\n screenX: number,\n screenY: number,\n ): { pos: { x: number; y: number }; portId: string | null } => {\n const container = containerRef.current\n if (!container)\n return { pos: screenToReal(screenX, screenY), portId: null }\n\n let closest: {\n id: string\n dist: number\n center: { x: number; y: number }\n } | null = null\n const portEls = container.querySelectorAll(\"[data-schematic-port-id]\")\n\n for (const node of Array.from(portEls)) {\n const rect = node.getBoundingClientRect()\n const cx = rect.left + rect.width / 2\n const cy = rect.top + rect.height / 2\n const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2)\n if (dist < PORT_SNAP_RADIUS_PX && (!closest || dist < closest.dist)) {\n const id = node.getAttribute(\"data-schematic-port-id\")\n const portEl = circuitJson.find(\n (el) => el.type === \"schematic_port\" && el.schematic_port_id === id,\n )\n if (id && portEl?.center) {\n closest = { id, dist, center: portEl.center }\n }\n }\n }\n\n if (closest) return { pos: closest.center, portId: closest.id }\n return { pos: screenToReal(screenX, screenY), portId: null }\n },\n [containerRef, circuitJson, screenToReal],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!enabled || stateRef.current.pendingPos) return\n const { pos } = snapToPort(e.clientX, e.clientY)\n setState((prev) => ({ ...prev, previewPos: pos }))\n },\n [enabled, snapToPort],\n )\n\n const handleMouseDown = useCallback(\n (e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target))\n return\n if (stateRef.current.pendingPos) return\n e.preventDefault()\n e.stopPropagation()\n const { pos, portId } = snapToPort(e.clientX, e.clientY)\n setState((prev) => ({\n ...prev,\n pendingPos: pos,\n pendingPortId: portId,\n previewPos: null,\n }))\n },\n [enabled, snapToPort],\n )\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n setState({\n previewPos: null,\n pendingPos: null,\n pendingPortId: null,\n })\n }\n }, [])\n\n const confirmPlacement = useCallback(\n (netName: string) => {\n const current = stateRef.current\n if (!current.pendingPos || !netName.trim()) return\n const event: EditSchematicGroundPortAddEvent = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_ground_port_add\",\n position: current.pendingPos,\n net_name: netName.trim(),\n schematic_port_id: current.pendingPortId ?? undefined,\n anchor_side: \"top\",\n created_at: Date.now(),\n in_progress: false,\n }\n onEditEvent?.(event)\n setState({ previewPos: null, pendingPos: null, pendingPortId: null })\n },\n [onEditEvent],\n )\n\n const cancelPlacement = useCallback(() => {\n setState({ previewPos: null, pendingPos: null, pendingPortId: null })\n }, [])\n\n useEffect(() => {\n if (!enabled) {\n setState({ previewPos: null, pendingPos: null, pendingPortId: null })\n return\n }\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mousedown\", handleMouseDown, { capture: true })\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mousedown\", handleMouseDown, {\n capture: true,\n })\n window.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown])\n\n return { groundPortState: state, confirmPlacement, cancelPlacement }\n}\n","import { type Matrix, applyToPoint, compose } from \"transformation-matrix\"\nimport { useRef, useEffect } from \"react\"\nimport type { GroundPortPlacementState } from \"../hooks/useGroundPortPlacement\"\n\nconst GND_COLOR = \"#5c5c5c\"\n\ninterface Props {\n state: GroundPortPlacementState\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n containerRef: React.RefObject\n onConfirm: (netName: string) => void\n onCancel: () => void\n}\n\nexport const GroundPortPreview = ({\n state,\n realToSvgProjection,\n svgToScreenProjection,\n containerRef,\n onConfirm,\n onCancel,\n}: Props) => {\n const inputRef = useRef(null)\n\n useEffect(() => {\n if (state.pendingPos && inputRef.current) {\n inputRef.current.focus()\n inputRef.current.value = \"GND\"\n }\n }, [state.pendingPos])\n\n const container = containerRef.current\n if (!container) return null\n\n if (\n !realToSvgProjection?.a ||\n isNaN(realToSvgProjection.a) ||\n !svgToScreenProjection?.a ||\n isNaN(svgToScreenProjection.a)\n ) {\n return null\n }\n\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n const toScreen = (pt: { x: number; y: number }) =>\n applyToPoint(realToScreen, pt)\n\n const GroundShape = ({ pos }: { pos: { x: number; y: number } }) => {\n const sp = toScreen(pos)\n return (\n \n {/* KiCad-style ground arrow pointing down; wire at top */}\n \n \n \n GND\n \n \n \n )\n }\n\n return (\n <>\n {state.previewPos && !state.pendingPos && (\n \n \n \n )}\n\n {state.pendingPos &&\n (() => {\n const sp = toScreen(state.pendingPos)\n return (\n \n {\n if (e.key === \"Enter\") {\n onConfirm((e.target as HTMLInputElement).value)\n } else if (e.key === \"Escape\") {\n onCancel()\n }\n e.stopPropagation()\n }}\n />\n {\n if (inputRef.current) onConfirm(inputRef.current.value)\n }}\n >\n ✓\n \n \n )\n })()}\n \n )\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { type Matrix, compose } from \"transformation-matrix\"\nimport type { EditSchematicTextNoteAddEvent } from \"../types/edit-events\"\nimport { isMouseCaptureIgnoredTarget } from \"../utils/isMouseCaptureIgnoredTarget\"\n\nexport interface TextNotePlacementState {\n previewPos: { x: number; y: number } | null\n pendingPos: { x: number; y: number } | null\n}\n\nexport const useTextNotePlacement = ({\n enabled,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent,\n}: {\n enabled: boolean\n svgToScreenProjection: Matrix\n realToSvgProjection: Matrix\n containerRef: React.RefObject\n onEditEvent?: (event: EditSchematicTextNoteAddEvent) => void\n}) => {\n const [state, setState] = useState({\n previewPos: null,\n pendingPos: null,\n })\n\n const stateRef = useRef(state)\n stateRef.current = state\n\n const screenToReal = useCallback(\n (screenX: number, screenY: number) => {\n const container = containerRef.current\n if (!container) return { x: 0, y: 0 }\n const rect = container.getBoundingClientRect()\n const localX = screenX - rect.left\n const localY = screenY - rect.top\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n return {\n x: (localX - realToScreen.e) / realToScreen.a,\n y: (localY - realToScreen.f) / realToScreen.d,\n }\n },\n [svgToScreenProjection, realToSvgProjection, containerRef],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!enabled || stateRef.current.pendingPos) return\n setState((prev) => ({\n ...prev,\n previewPos: screenToReal(e.clientX, e.clientY),\n }))\n },\n [enabled, screenToReal],\n )\n\n const handleMouseDown = useCallback(\n (e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target))\n return\n if (stateRef.current.pendingPos) return\n e.preventDefault()\n e.stopPropagation()\n setState((prev) => ({\n ...prev,\n pendingPos: screenToReal(e.clientX, e.clientY),\n previewPos: null,\n }))\n },\n [enabled, screenToReal],\n )\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n setState({ previewPos: null, pendingPos: null })\n }\n }, [])\n\n const confirmPlacement = useCallback(\n (text: string) => {\n const current = stateRef.current\n if (!current.pendingPos || !text.trim()) return\n const event: EditSchematicTextNoteAddEvent = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_text_note_add\",\n position: current.pendingPos,\n text: text.trim(),\n anchor: \"left\",\n font_size: 0.2,\n color: \"#1a1612\",\n created_at: Date.now(),\n in_progress: false,\n }\n onEditEvent?.(event)\n setState({ previewPos: null, pendingPos: null })\n },\n [onEditEvent],\n )\n\n const cancelPlacement = useCallback(() => {\n setState({ previewPos: null, pendingPos: null })\n }, [])\n\n useEffect(() => {\n if (!enabled) {\n setState({ previewPos: null, pendingPos: null })\n return\n }\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mousedown\", handleMouseDown, { capture: true })\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mousedown\", handleMouseDown, {\n capture: true,\n })\n window.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown])\n\n return { textNoteState: state, confirmPlacement, cancelPlacement }\n}\n","import { type Matrix, applyToPoint, compose } from \"transformation-matrix\"\nimport { useRef, useEffect } from \"react\"\nimport type { TextNotePlacementState } from \"../hooks/useTextNotePlacement\"\n\nconst NOTE_COLOR = \"#1a1612\"\n\ninterface Props {\n state: TextNotePlacementState\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n containerRef: React.RefObject\n onConfirm: (text: string) => void\n onCancel: () => void\n}\n\nexport const TextNotePreview = ({\n state,\n realToSvgProjection,\n svgToScreenProjection,\n containerRef,\n onConfirm,\n onCancel,\n}: Props) => {\n const inputRef = useRef(null)\n\n useEffect(() => {\n if (state.pendingPos && inputRef.current) {\n inputRef.current.focus()\n inputRef.current.value = \"\"\n }\n }, [state.pendingPos])\n\n const container = containerRef.current\n if (!container) return null\n\n if (\n !realToSvgProjection?.a ||\n isNaN(realToSvgProjection.a) ||\n !svgToScreenProjection?.a ||\n isNaN(svgToScreenProjection.a)\n ) {\n return null\n }\n\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n const toScreen = (pt: { x: number; y: number }) =>\n applyToPoint(realToScreen, pt)\n\n const NoteShape = ({ pos }: { pos: { x: number; y: number } }) => {\n const sp = toScreen(pos)\n return (\n \n \n Text\n \n \n \n )\n }\n\n return (\n <>\n {state.previewPos && !state.pendingPos && (\n \n \n \n )}\n\n {state.pendingPos &&\n (() => {\n const sp = toScreen(state.pendingPos)\n return (\n \n {\n if (e.key === \"Enter\") {\n onConfirm((e.target as HTMLInputElement).value)\n } else if (e.key === \"Escape\") {\n onCancel()\n }\n e.stopPropagation()\n }}\n />\n {\n if (inputRef.current) onConfirm(inputRef.current.value)\n }}\n >\n ✓\n \n \n )\n })()}\n \n )\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { type Matrix } from \"transformation-matrix\"\nimport type { EditSchematicWireAddEvent } from \"../types/edit-events\"\nimport { computeTraceRoute } from \"../utils/computeTraceRoute\"\nimport { isMouseCaptureIgnoredTarget } from \"../utils/isMouseCaptureIgnoredTarget\"\nimport {\n createScreenToReal,\n getSchematicPortAtScreen,\n getSchematicPortCenter,\n resolveSchematicPortId,\n} from \"../utils/schematicPortHitTest\"\n\nexport interface TraceDrawingState {\n isDrawing: boolean\n fromPortId: string | null\n previewEnd: { x: number; y: number } | null\n previewRoute: Array<{ x: number; y: number }>\n}\n\nexport const useTraceDrawing = ({\n enabled,\n circuitJson,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent,\n}: {\n enabled: boolean\n circuitJson: any[]\n svgToScreenProjection: Matrix\n realToSvgProjection: Matrix\n containerRef: React.RefObject\n onEditEvent?: (event: EditSchematicWireAddEvent) => void\n}) => {\n const [state, setState] = useState({\n isDrawing: false,\n fromPortId: null,\n previewEnd: null,\n previewRoute: [],\n })\n\n const stateRef = useRef(state)\n stateRef.current = state\n\n const screenToReal = useCallback(\n createScreenToReal(\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n ),\n [svgToScreenProjection, realToSvgProjection, containerRef],\n )\n\n const getPortCenter = useCallback(\n (portId: string) => {\n const schematicPortId = resolveSchematicPortId(circuitJson, portId)\n if (!schematicPortId) return null\n return getSchematicPortCenter(\n circuitJson,\n containerRef.current,\n schematicPortId,\n screenToReal,\n )\n },\n [circuitJson, containerRef, screenToReal],\n )\n\n const getPortAtScreen = useCallback(\n (screenX: number, screenY: number) =>\n getSchematicPortAtScreen(\n containerRef.current,\n circuitJson,\n screenX,\n screenY,\n ),\n [circuitJson, containerRef],\n )\n\n const beginTraceAtPort = useCallback(\n (portId: string) => {\n const schematicPortId = resolveSchematicPortId(circuitJson, portId)\n if (!schematicPortId) return false\n\n const center = getPortCenter(schematicPortId)\n if (!center) return false\n\n setState({\n isDrawing: true,\n fromPortId: schematicPortId,\n previewEnd: center,\n previewRoute: [center],\n })\n return true\n },\n [circuitJson, getPortCenter],\n )\n\n const finishTraceAtPort = useCallback(\n (portId: string) => {\n const current = stateRef.current\n if (!current.isDrawing || !current.fromPortId) return false\n\n const schematicPortId = resolveSchematicPortId(circuitJson, portId)\n if (!schematicPortId || schematicPortId === current.fromPortId) {\n return false\n }\n\n const fromCenter = getPortCenter(current.fromPortId)\n const toCenter = getPortCenter(schematicPortId)\n if (!fromCenter || !toCenter) return false\n\n const route = computeTraceRoute(fromCenter, toCenter)\n const event: EditSchematicWireAddEvent = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_wire_add\",\n from_schematic_port_id: current.fromPortId,\n to_schematic_port_id: schematicPortId,\n route,\n created_at: Date.now(),\n in_progress: false,\n }\n onEditEvent?.(event)\n setState({\n isDrawing: false,\n fromPortId: null,\n previewEnd: null,\n previewRoute: [],\n })\n return true\n },\n [circuitJson, getPortCenter, onEditEvent],\n )\n\n const handlePortMouseDown = useCallback(\n (portId: string, e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) {\n return\n }\n\n e.preventDefault()\n e.stopPropagation()\n\n const current = stateRef.current\n if (!current.isDrawing) {\n beginTraceAtPort(portId)\n return\n }\n\n finishTraceAtPort(portId)\n },\n [enabled, beginTraceAtPort, finishTraceAtPort],\n )\n\n const handleMouseDown = useCallback(\n (e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) {\n return\n }\n\n const current = stateRef.current\n const portId = getPortAtScreen(e.clientX, e.clientY)\n\n if (!current.isDrawing) {\n if (!portId) return\n e.preventDefault()\n e.stopPropagation()\n beginTraceAtPort(portId)\n return\n }\n\n if (portId && finishTraceAtPort(portId)) {\n e.preventDefault()\n e.stopPropagation()\n }\n },\n [enabled, getPortAtScreen, beginTraceAtPort, finishTraceAtPort],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (\n !enabled ||\n !stateRef.current.isDrawing ||\n !stateRef.current.fromPortId\n ) {\n return\n }\n const fromCenter = getPortCenter(stateRef.current.fromPortId)\n if (!fromCenter) return\n const hover = screenToReal(e.clientX, e.clientY)\n setState((prev) => ({\n ...prev,\n previewEnd: hover,\n previewRoute: computeTraceRoute(fromCenter, hover),\n }))\n },\n [enabled, getPortCenter, screenToReal],\n )\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key === \"Escape\" && stateRef.current.isDrawing) {\n setState({\n isDrawing: false,\n fromPortId: null,\n previewEnd: null,\n previewRoute: [],\n })\n }\n }, [])\n\n useEffect(() => {\n if (!enabled) {\n setState({\n isDrawing: false,\n fromPortId: null,\n previewEnd: null,\n previewRoute: [],\n })\n return\n }\n window.addEventListener(\"mousedown\", handleMouseDown, { capture: true })\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n window.removeEventListener(\"mousedown\", handleMouseDown, {\n capture: true,\n })\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [enabled, handleMouseDown, handleMouseMove, handleKeyDown])\n\n const wireDrawingState = {\n isDrawing: state.isDrawing,\n fromPortId: state.fromPortId,\n previewEnd: state.previewEnd,\n waypoints: state.previewRoute,\n }\n\n return { traceDrawingState: state, wireDrawingState, handlePortMouseDown }\n}\n","export function computeTraceRoute(\n from: { x: number; y: number },\n to: { x: number; y: number },\n): Array<{ x: number; y: number }> {\n if (Math.abs(from.x - to.x) < 1e-6 || Math.abs(from.y - to.y) < 1e-6) {\n return [from, to]\n }\n\n const viaHFirst = { x: to.x, y: from.y }\n const viaVFirst = { x: from.x, y: to.y }\n const lenH =\n Math.abs(from.x - viaHFirst.x) +\n Math.abs(from.y - viaHFirst.y) +\n Math.abs(viaHFirst.x - to.x) +\n Math.abs(viaHFirst.y - to.y)\n const lenV =\n Math.abs(from.x - viaVFirst.x) +\n Math.abs(from.y - viaVFirst.y) +\n Math.abs(viaVFirst.x - to.x) +\n Math.abs(viaVFirst.y - to.y)\n\n const corner = lenH <= lenV ? viaHFirst : viaVFirst\n return [from, corner, to]\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { type Matrix, compose } from \"transformation-matrix\"\nimport type {\n EditSchematicComponentAddEvent,\n PlacementComponentKind,\n} from \"../types/edit-events\"\nimport { isMouseCaptureIgnoredTarget } from \"../utils/isMouseCaptureIgnoredTarget\"\n\nexport interface ComponentPlacementState {\n previewPos: { x: number; y: number } | null\n rotation: number\n}\n\nexport const useComponentPlacement = ({\n enabled,\n componentKind,\n svgToScreenProjection,\n realToSvgProjection,\n containerRef,\n onEditEvent,\n}: {\n enabled: boolean\n componentKind: PlacementComponentKind\n svgToScreenProjection: Matrix\n realToSvgProjection: Matrix\n containerRef: React.RefObject\n onEditEvent?: (event: EditSchematicComponentAddEvent) => void\n}) => {\n const [state, setState] = useState({\n previewPos: null,\n rotation: 0,\n })\n\n const stateRef = useRef(state)\n stateRef.current = state\n const kindRef = useRef(componentKind)\n kindRef.current = componentKind\n\n const screenToReal = useCallback(\n (screenX: number, screenY: number) => {\n const container = containerRef.current\n if (!container) return { x: 0, y: 0 }\n const rect = container.getBoundingClientRect()\n const localX = screenX - rect.left\n const localY = screenY - rect.top\n const realToScreen = compose(svgToScreenProjection, realToSvgProjection)\n return {\n x: (localX - realToScreen.e) / realToScreen.a,\n y: (localY - realToScreen.f) / realToScreen.d,\n }\n },\n [svgToScreenProjection, realToSvgProjection, containerRef],\n )\n\n const placeAt = useCallback(\n (position: { x: number; y: number }) => {\n const event: EditSchematicComponentAddEvent = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_component_add\",\n position,\n component_kind: kindRef.current,\n rotation: stateRef.current.rotation,\n created_at: Date.now(),\n in_progress: false,\n }\n onEditEvent?.(event)\n },\n [onEditEvent],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!enabled) return\n setState((prev) => ({\n ...prev,\n previewPos: screenToReal(e.clientX, e.clientY),\n }))\n },\n [enabled, screenToReal],\n )\n\n const handleMouseDown = useCallback(\n (e: MouseEvent) => {\n if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target))\n return\n e.preventDefault()\n e.stopPropagation()\n placeAt(screenToReal(e.clientX, e.clientY))\n },\n [enabled, screenToReal, placeAt],\n )\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (!enabled) return\n if (e.key === \"r\" || e.key === \"R\") {\n if (e.metaKey || e.ctrlKey || e.altKey) return\n e.preventDefault()\n setState((prev) => ({\n ...prev,\n rotation: (prev.rotation + 90) % 360,\n }))\n }\n },\n [enabled],\n )\n\n useEffect(() => {\n if (!enabled) {\n setState({ previewPos: null, rotation: 0 })\n return\n }\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mousedown\", handleMouseDown, { capture: true })\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mousedown\", handleMouseDown, {\n capture: true,\n })\n window.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown])\n\n return { componentPlacementState: state }\n}\n","import type { Matrix } from \"transformation-matrix\"\nimport type { ComponentPlacementState } from \"../hooks/useComponentPlacement\"\n\nconst KIND_LABEL: Record = {\n resistor: \"R\",\n capacitor: \"C\",\n inductor: \"L\",\n}\n\nexport function ComponentPlacementPreview({\n state,\n realToSvgProjection,\n svgToScreenProjection,\n containerRef,\n componentKind,\n}: {\n state: ComponentPlacementState\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n containerRef: React.RefObject\n componentKind: string\n}) {\n if (!state.previewPos) return null\n\n const container = containerRef.current\n if (!container) return null\n\n const realToScreen = {\n a: realToSvgProjection.a * svgToScreenProjection.a,\n d: realToSvgProjection.d * svgToScreenProjection.d,\n e:\n realToSvgProjection.e * svgToScreenProjection.a + svgToScreenProjection.e,\n f:\n realToSvgProjection.f * svgToScreenProjection.d + svgToScreenProjection.f,\n }\n\n const sx = state.previewPos.x * realToScreen.a + realToScreen.e\n const sy = state.previewPos.y * realToScreen.d + realToScreen.f\n const label = KIND_LABEL[componentKind] ?? \"?\"\n\n return (\n \n \n {label}\n \n \n )\n}\n","import {\n convertCircuitJsonToSchematicSimulationSvg,\n type ColorOverrides,\n} from \"circuit-to-svg\"\nimport { useEffect, useState, useMemo, useRef } from \"react\"\nimport { useResizeHandling } from \"../hooks/use-resize-handling\"\nimport { useMouseMatrixTransform } from \"use-mouse-matrix-transform\"\nimport { toString as transformToString } from \"transformation-matrix\"\nimport type { CircuitJson } from \"circuit-json\"\n\nconst DEFAULT_RENDER_WIDTH = 1200\nconst DEFAULT_RENDER_ASPECT_RATIO = 1\n\ninterface Props {\n circuitJson: CircuitJson\n containerStyle?: React.CSSProperties\n colorOverrides?: ColorOverrides\n width?: number\n height?: number\n className?: string\n}\n\nexport const AnalogSimulationViewer = ({\n circuitJson: inputCircuitJson,\n containerStyle,\n colorOverrides,\n width,\n height,\n className,\n}: Props) => {\n const [circuitJson, setCircuitJson] = useState(null)\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState(null)\n const [svgObjectUrl, setSvgObjectUrl] = useState(null)\n const containerRef = useRef(null)\n const imgRef = useRef(null)\n\n const { containerWidth } = useResizeHandling(\n containerRef as React.RefObject,\n )\n\n const [isDragging, setIsDragging] = useState(false)\n\n const {\n ref: transformRef,\n cancelDrag: _cancelDrag,\n transform: _svgToScreenProjection,\n } = useMouseMatrixTransform({\n onSetTransform(transform) {\n if (imgRef.current) {\n imgRef.current.style.transform = transformToString(transform)\n }\n },\n })\n\n const renderAspectRatio =\n width && height ? width / height : DEFAULT_RENDER_ASPECT_RATIO\n const effectiveWidth =\n width ||\n (height ? height * renderAspectRatio : containerWidth) ||\n DEFAULT_RENDER_WIDTH\n const effectiveHeight = height || effectiveWidth / renderAspectRatio\n\n // Set CircuitJSON from props\n useEffect(() => {\n setIsLoading(true)\n setError(null)\n setCircuitJson(inputCircuitJson)\n setIsLoading(false)\n }, [inputCircuitJson])\n\n // Find simulation experiment ID from circuit JSON\n const simulationExperimentId = useMemo(() => {\n if (!circuitJson) return null\n const simulationElement = circuitJson.find(\n (el) => el.type === \"simulation_experiment\",\n )\n return simulationElement?.simulation_experiment_id || null\n }, [circuitJson])\n\n // Find simulation graph IDs from circuit JSON\n const simulationVoltageGraphIds = useMemo(() => {\n if (!circuitJson) return []\n return circuitJson\n .filter((el) => el.type === \"simulation_transient_voltage_graph\")\n .map((el) => el.simulation_transient_voltage_graph_id)\n }, [circuitJson])\n\n const simulationCurrentGraphIds = useMemo(() => {\n if (!circuitJson) return []\n return circuitJson\n .filter((el) => (el as any).type === \"simulation_transient_current_graph\")\n .map((el) => (el as any).simulation_transient_current_graph_id)\n }, [circuitJson])\n\n // Generate SVG from CircuitJSON\n const simulationSvg = useMemo(() => {\n if (\n !circuitJson ||\n !effectiveWidth ||\n !effectiveHeight ||\n !simulationExperimentId\n )\n return \"\"\n\n try {\n return convertCircuitJsonToSchematicSimulationSvg({\n circuitJson,\n simulation_experiment_id: simulationExperimentId,\n simulation_transient_current_graph_ids: simulationCurrentGraphIds,\n simulation_transient_voltage_graph_ids: simulationVoltageGraphIds,\n width: effectiveWidth,\n height: effectiveHeight,\n schematicOptions: { colorOverrides },\n } as any)\n } catch (fallbackErr) {\n console.error(\"Failed to generate fallback schematic SVG:\", fallbackErr)\n return \"\"\n }\n }, [\n circuitJson,\n effectiveWidth,\n effectiveHeight,\n colorOverrides,\n simulationExperimentId,\n simulationCurrentGraphIds,\n simulationVoltageGraphIds,\n ])\n\n // Create/revoke object URL whenever the SVG changes\n useEffect(() => {\n if (!simulationSvg) {\n setSvgObjectUrl(null)\n return\n }\n\n try {\n const blob = new Blob([simulationSvg], { type: \"image/svg+xml\" })\n const url = URL.createObjectURL(blob)\n setSvgObjectUrl(url)\n return () => {\n URL.revokeObjectURL(url)\n }\n } catch (error) {\n console.error(\"Failed to create SVG object URL:\", error)\n setSvgObjectUrl(null)\n }\n }, [simulationSvg])\n\n const containerBackgroundColor = useMemo(() => {\n if (!simulationSvg) return \"transparent\"\n const match = simulationSvg.match(\n /]*style=\"[^\"]*background-color:\\s*([^;\\\"]+)/i,\n )\n return match?.[1] ?? \"transparent\"\n }, [simulationSvg])\n\n const handleMouseDown = (_e: React.MouseEvent) => {\n setIsDragging(true)\n }\n\n const handleTouchStart = (_e: React.TouchEvent) => {\n setIsDragging(true)\n }\n\n useEffect(() => {\n const handleMouseUp = () => {\n setIsDragging(false)\n }\n\n const handleTouchEnd = () => {\n setIsDragging(false)\n }\n\n window.addEventListener(\"mouseup\", handleMouseUp)\n window.addEventListener(\"touchend\", handleTouchEnd)\n\n return () => {\n window.removeEventListener(\"mouseup\", handleMouseUp)\n window.removeEventListener(\"touchend\", handleTouchEnd)\n }\n }, [])\n\n if (isLoading) {\n return (\n \n Loading circuit...\n \n )\n }\n\n if (error) {\n return (\n \n
\n
\n Circuit Conversion Error\n
\n
{error}
\n
\n \n )\n }\n\n if (!simulationSvg) {\n return (\n \n
\n No Simulation Found\n
\n
\n Use{\" \"}\n \n {\"\"}\n {\" \"}\n to create simulations\n
\n \n )\n }\n\n return (\n {\n containerRef.current = node\n transformRef.current = node\n }}\n style={{\n position: \"relative\",\n backgroundColor: containerBackgroundColor,\n overflow: \"hidden\",\n minHeight: \"300px\",\n cursor: isDragging ? \"grabbing\" : \"grab\",\n ...containerStyle,\n }}\n className={className}\n onMouseDown={handleMouseDown}\n onTouchStart={handleTouchStart}\n >\n {svgObjectUrl ? (\n \n ) : (\n \n Failed to render SVG\n \n )}\n \n )\n}\n"],"mappings":"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","names":["su","useEffect","useRef","su","useEffect","su","su","useEffect","useCallback","useEffect","useMemo","useRef","useState","useEffect","su","useEffect","useRef","useState","compose","debug","su","jsx","su","jsx","jsxs","jsx","jsxs","su","jsx","jsx","useMemo","jsx","jsxs","useEffect","useState","jsx","jsxs","useState","useEffect","useState","useEffect","useCallback","useCallback","useEffect","useMemo","useRef","jsx","useCallback","useEffect","useRef","useState","useContext","useEffect","useMemo","useRef","useContext","useRef","useMemo","useEffect","jsx","useState","useRef","useCallback","useEffect","useCallback","useEffect","useRef","useState","Fragment","jsx","jsxs","areMeasurementsEqual","useState","useRef","useCallback","useEffect","useState","DropdownMenu","Fragment","jsx","jsxs","FONT_FAMILY","contentStyles","itemStyles","iconSlotStyles","CheckIcon","useState","useCallback","useEffect","useRef","useState","compose","el","useState","useRef","useCallback","useEffect","useCallback","useEffect","useRef","useState","compose","useState","useRef","useCallback","compose","useEffect","useCallback","useEffect","useState","compose","useState","useCallback","compose","useEffect","useCallback","useEffect","useState","compose","useState","useCallback","compose","resolveSchematicPortId","useEffect","useCallback","useEffect","useRef","useState","compose","useState","useRef","useCallback","compose","useEffect","useCallback","useEffect","useRef","useState","compose","PORT_SNAP_RADIUS_PX","useState","useRef","useCallback","compose","useEffect","useCallback","useEffect","useRef","useState","applyToPoint","compose","useState","useRef","useCallback","compose","applyToPoint","useEffect","applyToPoint","compose","jsx","jsxs","applyToPoint","compose","jsx","applyToPoint","compose","jsx","compose","applyToPoint","applyToPoint","compose","jsx","jsxs","compose","applyToPoint","applyToPoint","compose","useRef","useEffect","Fragment","jsx","jsxs","applyToPoint","compose","useRef","useEffect","Fragment","jsx","jsxs","applyToPoint","compose","useEffect","useMemo","useRef","useState","Fragment","jsx","jsxs","useState","useRef","useMemo","useEffect","compose","applyToPoint","useCallback","useEffect","useRef","useState","compose","PORT_SNAP_RADIUS_PX","useState","useRef","useCallback","compose","useEffect","applyToPoint","compose","useRef","useEffect","Fragment","jsx","jsxs","useCallback","useEffect","useRef","useState","compose","PORT_SNAP_RADIUS_PX","useState","useRef","useCallback","compose","useEffect","applyToPoint","compose","useRef","useEffect","Fragment","jsx","jsxs","useCallback","useEffect","useRef","useState","compose","useState","useRef","useCallback","compose","useEffect","applyToPoint","compose","useRef","useEffect","Fragment","jsx","jsxs","useCallback","useEffect","useRef","useState","useState","useRef","useCallback","useEffect","useCallback","useEffect","useRef","useState","compose","useState","useRef","useCallback","compose","useEffect","jsx","jsx","jsxs","debug","useState","circuitJson","useMemo","useEffect","useCallback","useRef","su","useEffect","useState","useMemo","useRef","useMouseMatrixTransform","transformToString","jsx","jsxs","useState","useRef","useEffect","useMemo","error"]} \ No newline at end of file diff --git a/dist/workers/spice-simulation.worker.js b/dist/workers/spice-simulation.worker.js new file mode 100644 index 00000000..c39219bf --- /dev/null +++ b/dist/workers/spice-simulation.worker.js @@ -0,0 +1 @@ +var e=null,s=async()=>(await import("https://cdn.jsdelivr.net/npm/eecircuit-engine@1.5.2/+esm")).Simulation,c=async()=>{if(e&&e.isInitialized())return;let i=await s();e=new i,await e.start()};self.onmessage=async i=>{try{if(await c(),!e)throw new Error("Simulation not initialized");let t=i.data.spiceString,a=t.match(/wrdata\s+(\S+)\s+(.*)/i);if(a){let o=`.probe ${a[2].trim().split(/\s+/).join(" ")}`;t=t.replace(/wrdata.*/i,o)}else if(!t.match(/\.probe/i))throw t.match(/plot\s+(.*)/i)?new Error("The 'plot' command is not supported for data extraction. Please use 'wrdata ...' or '.probe ...' instead."):new Error("No '.probe' or 'wrdata' command found in SPICE file. Use 'wrdata ...' to specify output.");e.setNetList(t);let n=await e.runSim();self.postMessage({type:"result",result:n})}catch(t){self.postMessage({type:"error",error:t.message})}}; diff --git a/lib/components/AnalogSimulationViewer.tsx b/lib/components/AnalogSimulationViewer.tsx index 95ea22c8..e2765a66 100644 --- a/lib/components/AnalogSimulationViewer.tsx +++ b/lib/components/AnalogSimulationViewer.tsx @@ -89,8 +89,8 @@ export const AnalogSimulationViewer = ({ const simulationCurrentGraphIds = useMemo(() => { if (!circuitJson) return [] return circuitJson - .filter((el) => el.type === "simulation_transient_current_graph") - .map((el) => el.simulation_transient_current_graph_id) + .filter((el) => (el as any).type === "simulation_transient_current_graph") + .map((el) => (el as any).simulation_transient_current_graph_id) }, [circuitJson]) // Generate SVG from CircuitJSON @@ -112,7 +112,7 @@ export const AnalogSimulationViewer = ({ width: effectiveWidth, height: effectiveHeight, schematicOptions: { colorOverrides }, - }) + } as any) } catch (fallbackErr) { console.error("Failed to generate fallback schematic SVG:", fallbackErr) return "" diff --git a/lib/components/BusEntryPreview.tsx b/lib/components/BusEntryPreview.tsx new file mode 100644 index 00000000..14ffea14 --- /dev/null +++ b/lib/components/BusEntryPreview.tsx @@ -0,0 +1,56 @@ +import { type Matrix, applyToPoint, compose } from "transformation-matrix" +import { + BUS_ENTRY_STUB_LEN, + type BusEntryPreviewState, +} from "../hooks/useBusEntryPlacement" + +interface Props { + state: BusEntryPreviewState + realToSvgProjection: Matrix + svgToScreenProjection: Matrix + containerRef: React.RefObject +} + +export const BusEntryPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, +}: Props) => { + if (!state.anchor || !containerRef.current) return null + if (!realToSvgProjection?.a || !svgToScreenProjection?.a) return null + + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + const toScreen = (pt: { x: number; y: number }) => + applyToPoint(realToScreen, pt) + + const p1 = toScreen(state.anchor) + const p2 = toScreen({ + x: state.anchor.x + BUS_ENTRY_STUB_LEN, + y: state.anchor.y + BUS_ENTRY_STUB_LEN, + }) + + return ( + + + + ) +} diff --git a/lib/components/BusPreview.tsx b/lib/components/BusPreview.tsx new file mode 100644 index 00000000..0e4700fb --- /dev/null +++ b/lib/components/BusPreview.tsx @@ -0,0 +1,63 @@ +import { type Matrix, applyToPoint, compose } from "transformation-matrix" +import type { BusDrawingState } from "../hooks/useBusDrawing" + +interface Props { + state: BusDrawingState + realToSvgProjection: Matrix + svgToScreenProjection: Matrix + containerRef: React.RefObject +} + +export const BusPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, +}: Props) => { + if (!state.isDrawing || !state.previewEnd || state.waypoints.length === 0) + return null + + const container = containerRef.current + if (!container) return null + + if ( + !realToSvgProjection?.a || + isNaN(realToSvgProjection.a) || + !svgToScreenProjection?.a || + isNaN(svgToScreenProjection.a) + ) + return null + + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + const toScreen = (pt: { x: number; y: number }) => + applyToPoint(realToScreen, pt) + + const points = [...state.waypoints.map(toScreen), toScreen(state.previewEnd)] + + const d = points + .map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`) + .join(" ") + + return ( + + + + ) +} diff --git a/lib/components/ComponentPlacementPreview.tsx b/lib/components/ComponentPlacementPreview.tsx new file mode 100644 index 00000000..1d65c1d4 --- /dev/null +++ b/lib/components/ComponentPlacementPreview.tsx @@ -0,0 +1,62 @@ +import type { Matrix } from "transformation-matrix" +import type { ComponentPlacementState } from "../hooks/useComponentPlacement" + +const KIND_LABEL: Record = { + resistor: "R", + capacitor: "C", + inductor: "L", +} + +export function ComponentPlacementPreview({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + componentKind, +}: { + state: ComponentPlacementState + realToSvgProjection: Matrix + svgToScreenProjection: Matrix + containerRef: React.RefObject + componentKind: string +}) { + if (!state.previewPos) return null + + const container = containerRef.current + if (!container) return null + + const realToScreen = { + a: realToSvgProjection.a * svgToScreenProjection.a, + d: realToSvgProjection.d * svgToScreenProjection.d, + e: + realToSvgProjection.e * svgToScreenProjection.a + svgToScreenProjection.e, + f: + realToSvgProjection.f * svgToScreenProjection.d + svgToScreenProjection.f, + } + + const sx = state.previewPos.x * realToScreen.a + realToScreen.e + const sy = state.previewPos.y * realToScreen.d + realToScreen.f + const label = KIND_LABEL[componentKind] ?? "?" + + return ( +
+
+ {label} +
+
+ ) +} diff --git a/lib/components/GlobalLabelPreview.tsx b/lib/components/GlobalLabelPreview.tsx new file mode 100644 index 00000000..ff94d2e1 --- /dev/null +++ b/lib/components/GlobalLabelPreview.tsx @@ -0,0 +1,152 @@ +import { type Matrix, applyToPoint, compose } from "transformation-matrix" +import { useRef, useEffect } from "react" +import type { GlobalLabelPlacementState } from "../hooks/useGlobalLabelPlacement" + +const GLOBAL_COLOR = "#c1271c" + +interface Props { + state: GlobalLabelPlacementState + realToSvgProjection: Matrix + svgToScreenProjection: Matrix + containerRef: React.RefObject + onConfirm: (netName: string) => void + onCancel: () => void +} + +export const GlobalLabelPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm, + onCancel, +}: Props) => { + const inputRef = useRef(null) + + useEffect(() => { + if (state.pendingPos && inputRef.current) { + inputRef.current.focus() + inputRef.current.value = "" + } + }, [state.pendingPos]) + + const container = containerRef.current + if (!container) return null + + if ( + !realToSvgProjection?.a || + isNaN(realToSvgProjection.a) || + !svgToScreenProjection?.a || + isNaN(svgToScreenProjection.a) + ) + return null + + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + const toScreen = (pt: { x: number; y: number }) => + applyToPoint(realToScreen, pt) + + const LabelShape = ({ pos }: { pos: { x: number; y: number } }) => { + const sp = toScreen(pos) + return ( + + + + GLOBAL + + + + ) + } + + return ( + <> + {state.previewPos && !state.pendingPos && ( + + + + )} + + {state.pendingPos && + (() => { + const sp = toScreen(state.pendingPos) + return ( +
+ { + if (e.key === "Enter") { + onConfirm((e.target as HTMLInputElement).value) + } else if (e.key === "Escape") { + onCancel() + } + e.stopPropagation() + }} + /> + +
+ ) + })()} + + ) +} diff --git a/lib/components/GroundPortPreview.tsx b/lib/components/GroundPortPreview.tsx new file mode 100644 index 00000000..d6e7d7ea --- /dev/null +++ b/lib/components/GroundPortPreview.tsx @@ -0,0 +1,161 @@ +import { type Matrix, applyToPoint, compose } from "transformation-matrix" +import { useRef, useEffect } from "react" +import type { GroundPortPlacementState } from "../hooks/useGroundPortPlacement" + +const GND_COLOR = "#5c5c5c" + +interface Props { + state: GroundPortPlacementState + realToSvgProjection: Matrix + svgToScreenProjection: Matrix + containerRef: React.RefObject + onConfirm: (netName: string) => void + onCancel: () => void +} + +export const GroundPortPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm, + onCancel, +}: Props) => { + const inputRef = useRef(null) + + useEffect(() => { + if (state.pendingPos && inputRef.current) { + inputRef.current.focus() + inputRef.current.value = "GND" + } + }, [state.pendingPos]) + + const container = containerRef.current + if (!container) return null + + if ( + !realToSvgProjection?.a || + isNaN(realToSvgProjection.a) || + !svgToScreenProjection?.a || + isNaN(svgToScreenProjection.a) + ) { + return null + } + + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + const toScreen = (pt: { x: number; y: number }) => + applyToPoint(realToScreen, pt) + + const GroundShape = ({ pos }: { pos: { x: number; y: number } }) => { + const sp = toScreen(pos) + return ( + + {/* KiCad-style ground arrow pointing down; wire at top */} + + + + GND + + + + ) + } + + return ( + <> + {state.previewPos && !state.pendingPos && ( + + + + )} + + {state.pendingPos && + (() => { + const sp = toScreen(state.pendingPos) + return ( +
+ { + if (e.key === "Enter") { + onConfirm((e.target as HTMLInputElement).value) + } else if (e.key === "Escape") { + onCancel() + } + e.stopPropagation() + }} + /> + +
+ ) + })()} + + ) +} diff --git a/lib/components/HierSheetPreview.tsx b/lib/components/HierSheetPreview.tsx new file mode 100644 index 00000000..17d352d5 --- /dev/null +++ b/lib/components/HierSheetPreview.tsx @@ -0,0 +1,354 @@ +import { type Matrix, applyToPoint, compose } from "transformation-matrix" +import { useEffect, useMemo, useRef, useState } from "react" +import type { + HierSheetPlacementState, + ScreenBox, +} from "../hooks/useHierSheetPlacement" +import { normalizeScreenBox } from "../hooks/useHierSheetPlacement" + +const SHEET_STROKE = "#0050d8" +const SHEET_FILL = "rgba(234, 242, 255, 0.35)" +const NAME_COLOR = "#006464" +const FILE_COLOR = "#725600" + +export interface HierSheetTarget { + id: string + title: string +} + +interface Props { + state: HierSheetPlacementState + realToSvgProjection: Matrix + svgToScreenProjection: Matrix + containerRef: React.RefObject + sheetTargets: HierSheetTarget[] + activeSheetId?: string + onConfirm: (sheetName: string, targetSheetId: string) => void + onCancel: () => void +} + +const SheetSymbolShape = ({ + screenBox, + dashed = true, +}: { + screenBox: ScreenBox + dashed?: boolean +}) => { + const { x, y, width, height } = screenBox + const pinLen = Math.min(10, width * 0.08) + const midY = y + height / 2 + return ( + + + + + + + Sheet name + + + Target sheet + + + ) +} + +function pendingBoxKey(box: { + x: number + y: number + width: number + height: number +}): string { + return `${box.x},${box.y},${box.width},${box.height}` +} + +export const HierSheetPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + sheetTargets, + activeSheetId, + onConfirm, + onCancel, +}: Props) => { + const [sheetName, setSheetName] = useState("") + const [targetSheetId, setTargetSheetId] = useState("") + const initializedKeyRef = useRef(null) + const nameInputRef = useRef(null) + + const targets = useMemo( + () => sheetTargets.filter((t) => t.id !== activeSheetId), + [sheetTargets, activeSheetId], + ) + + useEffect(() => { + if (!state.pendingBox) { + initializedKeyRef.current = null + setSheetName("") + setTargetSheetId("") + return + } + + const key = pendingBoxKey(state.pendingBox) + if (initializedKeyRef.current === key) return + + initializedKeyRef.current = key + setSheetName("") + setTargetSheetId(targets[0]?.id ?? "") + requestAnimationFrame(() => nameInputRef.current?.focus()) + }, [state.pendingBox, targets]) + + const container = containerRef.current + if (!container) return null + + if ( + !realToSvgProjection?.a || + isNaN(realToSvgProjection.a) || + !svgToScreenProjection?.a || + isNaN(svgToScreenProjection.a) + ) { + return null + } + + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + const boxToScreen = (box: { + x: number + y: number + width: number + height: number + }): ScreenBox => { + const p1 = applyToPoint(realToScreen, { x: box.x, y: box.y }) + const p2 = applyToPoint(realToScreen, { + x: box.x + box.width, + y: box.y + box.height, + }) + return { + x: Math.min(p1.x, p2.x), + y: Math.min(p1.y, p2.y), + width: Math.abs(p2.x - p1.x), + height: Math.abs(p2.y - p1.y), + } + } + + const previewScreenBox = + state.isDrawing && state.anchorLocal && state.previewLocal + ? normalizeScreenBox(state.anchorLocal, state.previewLocal) + : null + + const committedScreenBox = + state.pendingScreenBox ?? + (state.pendingBox ? boxToScreen(state.pendingBox) : null) + + const dialogAnchor = committedScreenBox + + const submit = () => { + if (!sheetName.trim() || !targetSheetId.trim()) return + onConfirm(sheetName, targetSheetId) + } + + return ( + <> + {previewScreenBox && ( + + + + )} + + {state.pendingBox && committedScreenBox && ( + + + + )} + + {state.pendingBox && dialogAnchor && ( +
e.stopPropagation()} + data-schematic-ignore-mouse-capture + > + + + + +
+ + +
+
+ )} + + ) +} diff --git a/lib/components/NetLabelPreview.tsx b/lib/components/NetLabelPreview.tsx new file mode 100644 index 00000000..00c8e020 --- /dev/null +++ b/lib/components/NetLabelPreview.tsx @@ -0,0 +1,144 @@ +import { type Matrix, applyToPoint, compose } from "transformation-matrix" +import { useRef, useEffect } from "react" +import type { NetLabelPlacementState } from "../hooks/useNetLabelPlacement" + +interface Props { + state: NetLabelPlacementState + realToSvgProjection: Matrix + svgToScreenProjection: Matrix + containerRef: React.RefObject + onConfirm: (netName: string) => void + onCancel: () => void +} + +export const NetLabelPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm, + onCancel, +}: Props) => { + const inputRef = useRef(null) + + useEffect(() => { + if (state.pendingPos && inputRef.current) { + inputRef.current.focus() + inputRef.current.value = "" + } + }, [state.pendingPos]) + + const container = containerRef.current + if (!container) return null + + if ( + !realToSvgProjection?.a || + isNaN(realToSvgProjection.a) || + !svgToScreenProjection?.a || + isNaN(svgToScreenProjection.a) + ) + return null + + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + const toScreen = (pt: { x: number; y: number }) => + applyToPoint(realToScreen, pt) + + const LabelShape = ({ pos }: { pos: { x: number; y: number } }) => { + const sp = toScreen(pos) + return ( + + + + NET + + + + ) + } + + return ( + <> + {state.previewPos && !state.pendingPos && ( + + + + )} + + {state.pendingPos && + (() => { + const sp = toScreen(state.pendingPos) + return ( +
+ { + if (e.key === "Enter") { + onConfirm((e.target as HTMLInputElement).value) + } else if (e.key === "Escape") { + onCancel() + } + e.stopPropagation() + }} + /> + +
+ ) + })()} + + ) +} diff --git a/lib/components/NoConnectPreview.tsx b/lib/components/NoConnectPreview.tsx new file mode 100644 index 00000000..0f6071c2 --- /dev/null +++ b/lib/components/NoConnectPreview.tsx @@ -0,0 +1,67 @@ +import { type Matrix, applyToPoint, compose } from "transformation-matrix" +import { + NO_CONNECT_HALF, + type NoConnectPreviewState, +} from "../hooks/useNoConnectPlacement" + +interface Props { + state: NoConnectPreviewState + realToSvgProjection: Matrix + svgToScreenProjection: Matrix + containerRef: React.RefObject +} + +export const NoConnectPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, +}: Props) => { + if (!state.center || !containerRef.current) return null + if (!realToSvgProjection?.a || !svgToScreenProjection?.a) return null + + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + const toScreen = (pt: { x: number; y: number }) => + applyToPoint(realToScreen, pt) + const { x, y } = state.center + const d = NO_CONNECT_HALF + + const a1 = toScreen({ x: x - d, y: y - d }) + const a2 = toScreen({ x: x + d, y: y + d }) + const b1 = toScreen({ x: x + d, y: y - d }) + const b2 = toScreen({ x: x - d, y: y + d }) + + return ( + + + + + ) +} diff --git a/lib/components/PowerPortPreview.tsx b/lib/components/PowerPortPreview.tsx new file mode 100644 index 00000000..91c3d69b --- /dev/null +++ b/lib/components/PowerPortPreview.tsx @@ -0,0 +1,161 @@ +import { type Matrix, applyToPoint, compose } from "transformation-matrix" +import { useRef, useEffect } from "react" +import type { PowerPortPlacementState } from "../hooks/usePowerPortPlacement" + +const POWER_COLOR = "#c1271c" + +interface Props { + state: PowerPortPlacementState + realToSvgProjection: Matrix + svgToScreenProjection: Matrix + containerRef: React.RefObject + onConfirm: (netName: string) => void + onCancel: () => void +} + +export const PowerPortPreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm, + onCancel, +}: Props) => { + const inputRef = useRef(null) + + useEffect(() => { + if (state.pendingPos && inputRef.current) { + inputRef.current.focus() + inputRef.current.value = "VCC" + } + }, [state.pendingPos]) + + const container = containerRef.current + if (!container) return null + + if ( + !realToSvgProjection?.a || + isNaN(realToSvgProjection.a) || + !svgToScreenProjection?.a || + isNaN(svgToScreenProjection.a) + ) { + return null + } + + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + const toScreen = (pt: { x: number; y: number }) => + applyToPoint(realToScreen, pt) + + const PowerShape = ({ pos }: { pos: { x: number; y: number } }) => { + const sp = toScreen(pos) + return ( + + {/* KiCad-style power arrow pointing up; wire at bottom */} + + + + PWR + + + + ) + } + + return ( + <> + {state.previewPos && !state.pendingPos && ( + + + + )} + + {state.pendingPos && + (() => { + const sp = toScreen(state.pendingPos) + return ( +
+ { + if (e.key === "Enter") { + onConfirm((e.target as HTMLInputElement).value) + } else if (e.key === "Escape") { + onCancel() + } + e.stopPropagation() + }} + /> + +
+ ) + })()} + + ) +} diff --git a/lib/components/SchematicPortMouseTarget.tsx b/lib/components/SchematicPortMouseTarget.tsx index fd62a676..e45dd414 100644 --- a/lib/components/SchematicPortMouseTarget.tsx +++ b/lib/components/SchematicPortMouseTarget.tsx @@ -36,8 +36,11 @@ interface Props { svgDivRef: React.RefObject containerRef: React.RefObject onPortClick?: (portId: string, event: MouseEvent) => void + onPortMouseDown?: (portId: string, event: MouseEvent) => void onHoverChange?: (portId: string, isHovering: boolean) => void showOutline: boolean + interactive?: boolean + hitPaddingPx?: number circuitJsonKey: string } @@ -47,8 +50,11 @@ export const SchematicPortMouseTarget = ({ svgDivRef, containerRef, onPortClick, + onPortMouseDown, onHoverChange, showOutline, + interactive = false, + hitPaddingPx = 4, circuitJsonKey, }: Props) => { const [measurement, setMeasurement] = useState(null) @@ -73,8 +79,8 @@ export const SchematicPortMouseTarget = ({ const elementRect = element.getBoundingClientRect() const containerRect = container.getBoundingClientRect() - // Add some padding around the port for easier interaction - const padding = 4 + // Add some padding around the port for easier interaction. + const padding = hitPaddingPx const nextMeasurement: Measurement = { bounds: { @@ -94,7 +100,7 @@ export const SchematicPortMouseTarget = ({ setMeasurement((prev) => areMeasurementsEqual(prev, nextMeasurement) ? prev : nextMeasurement, ) - }, [portId, containerRef, svgDivRef]) + }, [portId, containerRef, svgDivRef, hitPaddingPx]) const scheduleMeasure = useCallback(() => { if (frameRef.current !== null) return @@ -180,6 +186,15 @@ export const SchematicPortMouseTarget = ({ return ( <>
{ + e.preventDefault() + e.stopPropagation() + onPortMouseDown(portId, e.nativeEvent) + } + : undefined + } style={{ position: "absolute", left: rect.left, @@ -193,7 +208,8 @@ export const SchematicPortMouseTarget = ({ ? "rgba(255, 153, 51, 0.15)" : "rgba(255, 153, 51, 0.05)", borderRadius: "50%", - pointerEvents: "none", + pointerEvents: interactive ? "auto" : "none", + cursor: interactive ? "crosshair" : undefined, zIndex: zIndexMap.schematicPortHoverOutline, transition: "border-color 0.15s, background-color 0.15s", }} diff --git a/lib/components/SchematicViewer.tsx b/lib/components/SchematicViewer.tsx index acfb3cfc..eee9f824 100644 --- a/lib/components/SchematicViewer.tsx +++ b/lib/components/SchematicViewer.tsx @@ -38,6 +38,46 @@ import { MouseTracker } from "./MouseTracker" import { SchematicComponentMouseTarget } from "./SchematicComponentMouseTarget" import { SchematicPortMouseTarget } from "./SchematicPortMouseTarget" import { SchematicSheetSelector } from "./SchematicSheetSelector" +import { useWireDrawing } from "../hooks/useWireDrawing" +import { useBusDrawing } from "../hooks/useBusDrawing" +import { useBusEntryPlacement } from "../hooks/useBusEntryPlacement" +import { useNoConnectPlacement } from "../hooks/useNoConnectPlacement" +import { useNetLabelPlacement } from "../hooks/useNetLabelPlacement" +import { useGlobalLabelPlacement } from "../hooks/useGlobalLabelPlacement" +import { useHierSheetPlacement } from "../hooks/useHierSheetPlacement" +import { WirePreview } from "./WirePreview" +import { BusPreview } from "./BusPreview" +import { BusEntryPreview } from "./BusEntryPreview" +import { NoConnectPreview } from "./NoConnectPreview" +import { NetLabelPreview } from "./NetLabelPreview" +import { GlobalLabelPreview } from "./GlobalLabelPreview" +import { HierSheetPreview, type HierSheetTarget } from "./HierSheetPreview" +import { isSpacePanHeld, setSpacePanHeld } from "lib/hooks/useLocalStorage" +import { usePowerPortPlacement } from "../hooks/usePowerPortPlacement" +import { PowerPortPreview } from "./PowerPortPreview" +import type { EditSchematicPowerPortAddEvent } from "../types/edit-events" +import { useGroundPortPlacement } from "../hooks/useGroundPortPlacement" +import { GroundPortPreview } from "./GroundPortPreview" +import type { EditSchematicGroundPortAddEvent } from "../types/edit-events" +import { useTextNotePlacement } from "../hooks/useTextNotePlacement" +import { TextNotePreview } from "./TextNotePreview" +import type { EditSchematicTextNoteAddEvent } from "../types/edit-events" +import { useTraceDrawing } from "../hooks/useTraceDrawing" +import { useComponentPlacement } from "../hooks/useComponentPlacement" +import { ComponentPlacementPreview } from "./ComponentPlacementPreview" +import type { + EditSchematicComponentAddEvent, + PlacementComponentKind, +} from "../types/edit-events" +import type { + EditSchematicBusAddEvent, + EditSchematicBusEntryAddEvent, + EditSchematicGlobalLabelAddEvent, + EditSchematicHierSheetAddEvent, + EditSchematicNetLabelAddEvent, + EditSchematicNoConnectAddEvent, + EditSchematicWireAddEvent, +} from "../types/edit-events" interface Props { circuitJson: CircuitJson @@ -67,6 +107,37 @@ interface Props { }) => void /** Called when the active schematic sheet changes (multi-sheet circuits). */ onSchematicSheetChange?: (schematicSheetId: string) => void + toolMode?: + | "select" + | "draw_wire" + | "draw_bus" + | "draw_bus_entry" + | "draw_no_connect" + | "draw_net_label" + | "draw_global_label" + | "draw_hier_sheet" + | "draw_power_port" + | "draw_ground_port" + | "draw_text_note" + | "draw_trace" + | "draw_component" + onWireAdded?: (event: EditSchematicWireAddEvent) => void + onBusAdded?: (event: EditSchematicBusAddEvent) => void + onBusEntryAdded?: (event: EditSchematicBusEntryAddEvent) => void + onNoConnectAdded?: (event: EditSchematicNoConnectAddEvent) => void + onNetLabelAdded?: (event: EditSchematicNetLabelAddEvent) => void + onGlobalLabelAdded?: (event: EditSchematicGlobalLabelAddEvent) => void + onHierSheetAdded?: (event: EditSchematicHierSheetAddEvent) => void + onPowerPortAdded?: (event: EditSchematicPowerPortAddEvent) => void + onGroundPortAdded?: (event: EditSchematicGroundPortAddEvent) => void + onTextNoteAdded?: (event: EditSchematicTextNoteAddEvent) => void + onComponentAdded?: (event: EditSchematicComponentAddEvent) => void + placementComponentKind?: PlacementComponentKind + hierSheetTargets?: HierSheetTarget[] + /** Host sheet id excluded from hier-sheet placement targets. */ + activeSheetId?: string + allowComponentEdit?: boolean + allowCanvasPan?: boolean } export const SchematicViewer = ({ @@ -89,6 +160,23 @@ export const SchematicViewer = ({ onSchematicSheetChange, css, className, + toolMode = "select", + onWireAdded, + onBusAdded, + onBusEntryAdded, + onNoConnectAdded, + onNetLabelAdded, + onGlobalLabelAdded, + onHierSheetAdded, + onPowerPortAdded, + onGroundPortAdded, + onTextNoteAdded, + onComponentAdded, + placementComponentKind = "resistor", + hierSheetTargets = [], + activeSheetId, + allowComponentEdit = false, + allowCanvasPan = true, }: Props) => { if (debug) { enableDebug() @@ -155,7 +243,7 @@ export const SchematicViewer = ({ // The sheet that should actually be rendered. When there is a single sheet // (or none) we leave this undefined so circuit-to-svg uses its default and // behavior is unchanged for single-sheet circuits. - const activeSheetId = hasMultipleSheets + const selectedSchematicSheetId = hasMultipleSheets ? (selectedSheetId ?? defaultSheetId) : undefined @@ -189,6 +277,26 @@ export const SchematicViewer = ({ setHasSpiceSimRun(false) }, [circuitJsonKey]) + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.code !== "Space") return + const t = e.target + if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) + return + setSpacePanHeld(true) + } + const onKeyUp = (e: KeyboardEvent) => { + if (e.code === "Space") setSpacePanHeld(false) + } + window.addEventListener("keydown", onKeyDown) + window.addEventListener("keyup", onKeyUp) + return () => { + window.removeEventListener("keydown", onKeyDown) + window.removeEventListener("keyup", onKeyUp) + setSpacePanHeld(false) + } + }, []) + const { plotData, nodes, @@ -197,6 +305,28 @@ export const SchematicViewer = ({ } = useSpiceSimulation(hasSpiceSimRun ? spiceString : null) const [editModeEnabled, setEditModeEnabled] = useState(defaultEditMode) + const effectiveEditMode = toolMode === "select" && editModeEnabled + + useEffect(() => { + if ( + toolMode === "draw_wire" || + toolMode === "draw_bus" || + toolMode === "draw_bus_entry" || + toolMode === "draw_no_connect" || + toolMode === "draw_net_label" || + toolMode === "draw_global_label" || + toolMode === "draw_hier_sheet" || + toolMode === "draw_power_port" || + toolMode === "draw_ground_port" || + toolMode === "draw_text_note" + ) { + setEditModeEnabled(false) + } else if (toolMode === "select" && defaultEditMode) { + setEditModeEnabled(true) + } else { + setEditModeEnabled(false) + } + }, [toolMode, defaultEditMode]) const [snapToGrid, setSnapToGrid] = useState(true) const [showGridInternal, setShowGridInternal] = useState(false) const showGrid = debugGrid || showGridInternal @@ -248,20 +378,23 @@ export const SchematicViewer = ({ return components .filter( (component) => - !activeSheetId || component.schematic_sheet_id === activeSheetId, + !selectedSchematicSheetId || + (component as any).schematic_sheet_id === selectedSchematicSheetId, ) .map((component) => component.schematic_component_id as string) } catch (err) { console.error("Failed to derive schematic component ids", err) return [] } - }, [circuitJsonKey, circuitJson, activeSheetId]) + }, [circuitJsonKey, circuitJson, selectedSchematicSheetId]) const schematicPortsInfo = useMemo(() => { if (!showSchematicPorts) return [] try { const ports = (su(circuitJson).schematic_port?.list() ?? []).filter( - (port) => !activeSheetId || port.schematic_sheet_id === activeSheetId, + (port) => + !selectedSchematicSheetId || + (port as any).schematic_sheet_id === selectedSchematicSheetId, ) return ports.map((port) => { const sourcePort = su(circuitJson).source_port.get(port.source_port_id) @@ -275,7 +408,7 @@ export const SchematicViewer = ({ (sourcePort as any)?.name ?? "?" return { - portId: port.source_port_id as string, + portId: port.schematic_port_id as string, label: `${componentName}.${pinLabel}`, } }) @@ -283,7 +416,12 @@ export const SchematicViewer = ({ console.error("Failed to derive schematic port info", err) return [] } - }, [circuitJsonKey, circuitJson, showSchematicPorts, activeSheetId]) + }, [ + circuitJsonKey, + circuitJson, + showSchematicPorts, + selectedSchematicSheetId, + ]) const handleTouchStart = (e: React.TouchEvent) => { const touch = e.touches[0] @@ -324,45 +462,112 @@ export const SchematicViewer = ({ } }, [circuitJson]) + const panPolicyRef = useRef({ toolMode, allowComponentEdit, allowCanvasPan }) + panPolicyRef.current = { toolMode, allowComponentEdit, allowCanvasPan } + + const onSetSvgTransform = useCallback( + (transform: Parameters[0]) => { + if (!svgDivRef.current) return + svgDivRef.current.style.transform = transformToString(transform) + }, + [], + ) + + const shouldDrag = useCallback((e: MouseEvent | TouchEvent | WheelEvent) => { + if (e.type === "wheel") return true + // Hit-target checks only on mousedown; re-checking on move/up stops pan mid-drag + if ( + e.type === "mousemove" || + e.type === "mouseup" || + e.type === "mouseout" + ) { + return true + } + + if (e instanceof MouseEvent && e.button !== 0) return true + if (isSpacePanHeld() && e instanceof MouseEvent) return true + + const { + toolMode: mode, + allowComponentEdit: allowEdit, + allowCanvasPan: pan, + } = panPolicyRef.current + + if (!pan && e.type !== "wheel") { + return false + } + + if ( + mode === "draw_wire" || + mode === "draw_trace" || + mode === "draw_component" || + mode === "draw_bus" || + mode === "draw_bus_entry" || + mode === "draw_no_connect" || + mode === "draw_net_label" || + mode === "draw_global_label" || + mode === "draw_hier_sheet" || + mode === "draw_power_port" || + mode === "draw_ground_port" || + mode === "draw_text_note" + ) { + return false + } + + if (allowEdit) { + const target = e.target as Element + if (target.closest('[data-circuit-json-type="schematic_component"]')) { + return false + } + } + + return true + }, []) + const { ref: containerRef, cancelDrag, transform: svgToScreenProjection, } = useMouseMatrixTransform({ - onSetTransform(transform) { - if (!svgDivRef.current) return - svgDivRef.current.style.transform = transformToString(transform) - }, + onSetTransform: onSetSvgTransform, // @ts-ignore disabled is a valid prop but not typed enabled: isInteractionEnabled && !showSpiceOverlay, + shouldDrag, }) const { containerWidth, containerHeight } = useResizeHandling(containerRef) const svgString = useMemo(() => { if (!containerWidth || !containerHeight) return "" - return convertCircuitJsonToSchematicSvg(circuitJson as any, { - width: containerWidth, - height: containerHeight || 720, - drawPorts: showSchematicPorts, - schematicSheetId: activeSheetId, - grid: !showGrid - ? undefined - : { - cellSize: 1, - labelCells: true, - }, - colorOverrides, - css, - className, - }) + return convertCircuitJsonToSchematicSvg( + circuitJson as any, + { + width: containerWidth, + height: containerHeight || 720, + drawPorts: showSchematicPorts, + schematicSheetId: selectedSchematicSheetId, + grid: !showGrid + ? undefined + : { + cellSize: 1, + labelCells: true, + }, + colorOverrides, + css, + className, + } as any, + ) }, [ circuitJsonKey, + circuitJson, containerWidth, containerHeight, showGrid, + colorOverrides, + css, + className, showSchematicPorts, - activeSheetId, + selectedSchematicSheetId, ]) const containerBackgroundColor = useMemo(() => { @@ -409,10 +614,179 @@ export const SchematicViewer = ({ svgToScreenProjection, circuitJson, editEvents: editEventsWithUnappliedEditEvents, - enabled: editModeEnabled && isInteractionEnabled && !showSpiceOverlay, + enabled: allowComponentEdit && isInteractionEnabled && !showSpiceOverlay, snapToGrid, }) + const isProjectionReady = + svgToScreenProjection?.a != null && + !isNaN(svgToScreenProjection.a) && + realToSvgProjection?.a != null && + !isNaN(realToSvgProjection.a) + + const { wireDrawingState, handlePortMouseDown } = useWireDrawing({ + enabled: + toolMode === "draw_wire" && isInteractionEnabled && isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onWireAdded, + }) + + const { + wireDrawingState: tracePreviewState, + handlePortMouseDown: handleTracePortMouseDown, + } = useTraceDrawing({ + enabled: + toolMode === "draw_trace" && isInteractionEnabled && isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onWireAdded, + }) + + const { componentPlacementState } = useComponentPlacement({ + enabled: + toolMode === "draw_component" && + isInteractionEnabled && + isProjectionReady, + componentKind: placementComponentKind, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onComponentAdded, + }) + + const portMouseDownHandler = + toolMode === "draw_trace" ? handleTracePortMouseDown : handlePortMouseDown + const activeWirePreviewState = + toolMode === "draw_trace" ? tracePreviewState : wireDrawingState + + const { busDrawingState } = useBusDrawing({ + enabled: + toolMode === "draw_bus" && isInteractionEnabled && isProjectionReady, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onBusAdded, + }) + + const { busEntryPreviewState } = useBusEntryPlacement({ + enabled: + toolMode === "draw_bus_entry" && + isInteractionEnabled && + isProjectionReady, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onBusEntryAdded, + }) + + const { noConnectPreviewState } = useNoConnectPlacement({ + enabled: + toolMode === "draw_no_connect" && + isInteractionEnabled && + isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onNoConnectAdded, + }) + + const { netLabelState, confirmPlacement, cancelPlacement } = + useNetLabelPlacement({ + enabled: + toolMode === "draw_net_label" && + isInteractionEnabled && + isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onNetLabelAdded, + }) + + const { + globalLabelState, + confirmPlacement: confirmGlobalPlacement, + cancelPlacement: cancelGlobalPlacement, + } = useGlobalLabelPlacement({ + enabled: + toolMode === "draw_global_label" && + isInteractionEnabled && + isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onGlobalLabelAdded, + }) + + const { + powerPortState, + confirmPlacement: confirmPowerPlacement, + cancelPlacement: cancelPowerPlacement, + } = usePowerPortPlacement({ + enabled: + toolMode === "draw_power_port" && + isInteractionEnabled && + isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onPowerPortAdded, + }) + + const { + groundPortState, + confirmPlacement: confirmGroundPlacement, + cancelPlacement: cancelGroundPlacement, + } = useGroundPortPlacement({ + enabled: + toolMode === "draw_ground_port" && + isInteractionEnabled && + isProjectionReady, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onGroundPortAdded, + }) + + const { + textNoteState, + confirmPlacement: confirmTextNotePlacement, + cancelPlacement: cancelTextNotePlacement, + } = useTextNotePlacement({ + enabled: + toolMode === "draw_text_note" && + isInteractionEnabled && + isProjectionReady, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onTextNoteAdded, + }) + + const { + hierSheetState, + confirmPlacement: confirmHierSheetPlacement, + cancelPlacement: cancelHierSheetPlacement, + } = useHierSheetPlacement({ + enabled: + toolMode === "draw_hier_sheet" && + isInteractionEnabled && + isProjectionReady, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent: onHierSheetAdded, + }) + useChangeSchematicComponentLocationsInSvg({ svgDivRef, editEvents: editEventsWithUnappliedEditEvents, @@ -433,7 +807,7 @@ export const SchematicViewer = ({ useSchematicGroupsOverlay({ svgDivRef, circuitJson, - circuitJsonKey: `${circuitJsonKey}_${activeSheetId ?? ""}`, + circuitJsonKey: `${circuitJsonKey}_${selectedSchematicSheetId ?? ""}`, showGroups: showSchematicGroups && !disableGroups, }) @@ -442,7 +816,7 @@ export const SchematicViewer = ({ useSchematicNetHover({ svgDivRef, circuitJson, - circuitJsonKey: `${circuitJsonKey}_${activeSheetId ?? ""}`, + circuitJsonKey: `${circuitJsonKey}_${selectedSchematicSheetId ?? ""}`, enabled: netHoverHighlightEnabled, }) @@ -470,7 +844,7 @@ export const SchematicViewer = ({ : undefined } onTouchStart={(e) => { - if (editModeEnabled && isInteractionEnabled && !showSpiceOverlay) { + if (effectiveEditMode && isInteractionEnabled && !showSpiceOverlay) { handleComponentTouchStartRef.current(e) } }} @@ -510,18 +884,31 @@ export const SchematicViewer = ({ style={{ position: "relative", backgroundColor: containerBackgroundColor, - overflow: "hidden", + overflow: hierSheetState.pendingBox ? "visible" : "hidden", cursor: showSpiceOverlay ? "auto" - : isDragging - ? "grabbing" - : clickToInteractEnabled && !isInteractionEnabled - ? "pointer" - : isHoveringClickableComponent && onSchematicComponentClicked + : toolMode === "draw_wire" || + toolMode === "draw_trace" || + toolMode === "draw_bus" || + toolMode === "draw_bus_entry" || + toolMode === "draw_no_connect" || + toolMode === "draw_net_label" || + toolMode === "draw_global_label" || + toolMode === "draw_hier_sheet" || + toolMode === "draw_power_port" || + toolMode === "draw_ground_port" || + toolMode === "draw_text_note" || + toolMode === "draw_component" + ? "crosshair" + : isDragging + ? "grabbing" + : clickToInteractEnabled && !isInteractionEnabled ? "pointer" - : isHoveringClickablePort && onSchematicPortClicked + : isHoveringClickableComponent && onSchematicComponentClicked ? "pointer" - : "grab", + : isHoveringClickablePort && onSchematicPortClicked + ? "pointer" + : "grab", minHeight: "300px", ...containerStyle, }} @@ -536,7 +923,9 @@ export const SchematicViewer = ({ e.stopPropagation() return } - handleMouseDown(e) + if (allowComponentEdit) { + handleMouseDown(e) + } }} onMouseDownCapture={(e) => { if (clickToInteractEnabled && !isInteractionEnabled) { @@ -620,7 +1009,7 @@ export const SchematicViewer = ({ /> {spiceSimulationEnabled && ( @@ -661,6 +1050,87 @@ export const SchematicViewer = ({ /> ))} {svgDiv} + + + + + + + + + + + {showSchematicPorts && schematicPortsInfo.map(({ portId, label }) => ( + onConfirm: (text: string) => void + onCancel: () => void +} + +export const TextNotePreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, + onConfirm, + onCancel, +}: Props) => { + const inputRef = useRef(null) + + useEffect(() => { + if (state.pendingPos && inputRef.current) { + inputRef.current.focus() + inputRef.current.value = "" + } + }, [state.pendingPos]) + + const container = containerRef.current + if (!container) return null + + if ( + !realToSvgProjection?.a || + isNaN(realToSvgProjection.a) || + !svgToScreenProjection?.a || + isNaN(svgToScreenProjection.a) + ) { + return null + } + + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + const toScreen = (pt: { x: number; y: number }) => + applyToPoint(realToScreen, pt) + + const NoteShape = ({ pos }: { pos: { x: number; y: number } }) => { + const sp = toScreen(pos) + return ( + + + Text + + + + ) + } + + return ( + <> + {state.previewPos && !state.pendingPos && ( + + + + )} + + {state.pendingPos && + (() => { + const sp = toScreen(state.pendingPos) + return ( +
+ { + if (e.key === "Enter") { + onConfirm((e.target as HTMLInputElement).value) + } else if (e.key === "Escape") { + onCancel() + } + e.stopPropagation() + }} + /> + +
+ ) + })()} + + ) +} diff --git a/lib/components/WirePreview.tsx b/lib/components/WirePreview.tsx new file mode 100644 index 00000000..380c68fb --- /dev/null +++ b/lib/components/WirePreview.tsx @@ -0,0 +1,80 @@ +import { type Matrix, applyToPoint, compose } from "transformation-matrix" +import type { WireDrawingState } from "../hooks/useWireDrawing" + +interface Props { + state: WireDrawingState + realToSvgProjection: Matrix + svgToScreenProjection: Matrix + containerRef: React.RefObject +} + +export const WirePreview = ({ + state, + realToSvgProjection, + svgToScreenProjection, + containerRef, +}: Props) => { + if (!state.isDrawing || !state.previewEnd || state.waypoints.length === 0) + return null + + const container = containerRef.current + if (!container) return null + + if ( + !realToSvgProjection?.a || + isNaN(realToSvgProjection.a) || + !svgToScreenProjection?.a || + isNaN(svgToScreenProjection.a) + ) + return null + + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + + const toScreen = (pt: { x: number; y: number }) => + applyToPoint(realToScreen, pt) + + const points = [...state.waypoints.map(toScreen), toScreen(state.previewEnd)] + + const d = points + .map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`) + .join(" ") + + return ( + + + + + + ) +} diff --git a/lib/hooks/useBusDrawing.ts b/lib/hooks/useBusDrawing.ts new file mode 100644 index 00000000..b2810c6f --- /dev/null +++ b/lib/hooks/useBusDrawing.ts @@ -0,0 +1,175 @@ +import { useCallback, useEffect, useRef, useState } from "react" +import { type Matrix, compose } from "transformation-matrix" +import type { EditSchematicBusAddEvent } from "../types/edit-events" +import { isMouseCaptureIgnoredTarget } from "../utils/isMouseCaptureIgnoredTarget" + +export interface BusDrawingState { + isDrawing: boolean + previewEnd: { x: number; y: number } | null + waypoints: Array<{ x: number; y: number }> +} + +export const useBusDrawing = ({ + enabled, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent, +}: { + enabled: boolean + svgToScreenProjection: Matrix + realToSvgProjection: Matrix + containerRef: React.RefObject + onEditEvent?: (event: EditSchematicBusAddEvent) => void +}) => { + const [state, setState] = useState({ + isDrawing: false, + previewEnd: null, + waypoints: [], + }) + + const stateRef = useRef(state) + stateRef.current = state + + const screenToReal = useCallback( + (screenX: number, screenY: number) => { + const container = containerRef.current + if (!container) return { x: 0, y: 0 } + const rect = container.getBoundingClientRect() + const localX = screenX - rect.left + const localY = screenY - rect.top + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d, + } + }, + [svgToScreenProjection, realToSvgProjection, containerRef], + ) + + const dedupeRoute = (route: Array<{ x: number; y: number }>) => { + const out: Array<{ x: number; y: number }> = [] + for (const p of route) { + const last = out[out.length - 1] + if (!last || Math.hypot(p.x - last.x, p.y - last.y) > 0.05) out.push(p) + } + return out + } + + const finishBus = useCallback( + (route: Array<{ x: number; y: number }>) => { + const cleaned = dedupeRoute(route) + if (cleaned.length < 2) return false + const event: EditSchematicBusAddEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_bus_add", + route: cleaned, + created_at: Date.now(), + in_progress: false, + } + onEditEvent?.(event) + setState({ isDrawing: false, previewEnd: null, waypoints: [] }) + return true + }, + [onEditEvent], + ) + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return + const current = stateRef.current + const realPos = screenToReal(e.clientX, e.clientY) + + if (!current.isDrawing) { + e.stopPropagation() + setState({ + isDrawing: true, + previewEnd: realPos, + waypoints: [realPos], + }) + return + } + + e.stopPropagation() + setState((prev) => ({ + ...prev, + waypoints: [...prev.waypoints, realPos], + previewEnd: realPos, + })) + }, + [enabled, screenToReal], + ) + + const handleDoubleClick = useCallback( + (e: MouseEvent) => { + if (!enabled || !stateRef.current.isDrawing) return + const current = stateRef.current + const end = screenToReal(e.clientX, e.clientY) + const route = [...current.waypoints, end] + if (finishBus(route.length >= 2 ? route : current.waypoints)) { + e.preventDefault() + e.stopPropagation() + } + }, + [enabled, screenToReal, finishBus], + ) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!enabled || !stateRef.current.isDrawing) return + const realPos = screenToReal(e.clientX, e.clientY) + setState((prev) => ({ ...prev, previewEnd: realPos })) + }, + [enabled, screenToReal], + ) + + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + const target = e.target as Element | null + if (target?.closest("input, textarea, select")) return + + if (e.key === "Enter" && stateRef.current.isDrawing) { + const route = stateRef.current.waypoints + if (finishBus(route)) { + e.preventDefault() + } + return + } + + if (e.key === "Escape" && stateRef.current.isDrawing) { + setState({ isDrawing: false, previewEnd: null, waypoints: [] }) + } + }, + [finishBus], + ) + + useEffect(() => { + if (!enabled) { + setState({ isDrawing: false, previewEnd: null, waypoints: [] }) + return + } + window.addEventListener("mousedown", handleMouseDown, { capture: true }) + window.addEventListener("dblclick", handleDoubleClick, { capture: true }) + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("keydown", handleKeyDown) + return () => { + window.removeEventListener("mousedown", handleMouseDown, { + capture: true, + }) + window.removeEventListener("dblclick", handleDoubleClick, { + capture: true, + }) + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("keydown", handleKeyDown) + } + }, [ + enabled, + handleMouseDown, + handleDoubleClick, + handleMouseMove, + handleKeyDown, + ]) + + return { busDrawingState: state } +} diff --git a/lib/hooks/useBusEntryPlacement.ts b/lib/hooks/useBusEntryPlacement.ts new file mode 100644 index 00000000..61ed2c39 --- /dev/null +++ b/lib/hooks/useBusEntryPlacement.ts @@ -0,0 +1,95 @@ +import { useCallback, useEffect, useState } from "react" +import { type Matrix, compose } from "transformation-matrix" +import type { EditSchematicBusEntryAddEvent } from "../types/edit-events" +import { isMouseCaptureIgnoredTarget } from "../utils/isMouseCaptureIgnoredTarget" + +export const BUS_ENTRY_STUB_LEN = 2.5 + +export interface BusEntryPreviewState { + anchor: { x: number; y: number } | null +} + +export const useBusEntryPlacement = ({ + enabled, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent, +}: { + enabled: boolean + svgToScreenProjection: Matrix + realToSvgProjection: Matrix + containerRef: React.RefObject + onEditEvent?: (event: EditSchematicBusEntryAddEvent) => void +}) => { + const [previewState, setPreviewState] = useState({ + anchor: null, + }) + + const screenToReal = useCallback( + (screenX: number, screenY: number) => { + const container = containerRef.current + if (!container) return { x: 0, y: 0 } + const rect = container.getBoundingClientRect() + const localX = screenX - rect.left + const localY = screenY - rect.top + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d, + } + }, + [svgToScreenProjection, realToSvgProjection, containerRef], + ) + + const placeEntry = useCallback( + (anchor: { x: number; y: number }) => { + const event: EditSchematicBusEntryAddEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_bus_entry_add", + anchor, + created_at: Date.now(), + in_progress: false, + } + onEditEvent?.(event) + }, + [onEditEvent], + ) + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return + const anchor = screenToReal(e.clientX, e.clientY) + e.preventDefault() + e.stopPropagation() + placeEntry(anchor) + }, + [enabled, screenToReal, placeEntry], + ) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!enabled) return + setPreviewState({ anchor: screenToReal(e.clientX, e.clientY) }) + }, + [enabled, screenToReal], + ) + + useEffect(() => { + if (!enabled) { + setPreviewState({ anchor: null }) + return + } + window.addEventListener("mousedown", handleMouseDown, { capture: true }) + window.addEventListener("mousemove", handleMouseMove) + return () => { + window.removeEventListener("mousedown", handleMouseDown, { + capture: true, + }) + window.removeEventListener("mousemove", handleMouseMove) + } + }, [enabled, handleMouseDown, handleMouseMove]) + + return { busEntryPreviewState: previewState } +} diff --git a/lib/hooks/useComponentPlacement.ts b/lib/hooks/useComponentPlacement.ts new file mode 100644 index 00000000..e8443b99 --- /dev/null +++ b/lib/hooks/useComponentPlacement.ts @@ -0,0 +1,126 @@ +import { useCallback, useEffect, useRef, useState } from "react" +import { type Matrix, compose } from "transformation-matrix" +import type { + EditSchematicComponentAddEvent, + PlacementComponentKind, +} from "../types/edit-events" +import { isMouseCaptureIgnoredTarget } from "../utils/isMouseCaptureIgnoredTarget" + +export interface ComponentPlacementState { + previewPos: { x: number; y: number } | null + rotation: number +} + +export const useComponentPlacement = ({ + enabled, + componentKind, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent, +}: { + enabled: boolean + componentKind: PlacementComponentKind + svgToScreenProjection: Matrix + realToSvgProjection: Matrix + containerRef: React.RefObject + onEditEvent?: (event: EditSchematicComponentAddEvent) => void +}) => { + const [state, setState] = useState({ + previewPos: null, + rotation: 0, + }) + + const stateRef = useRef(state) + stateRef.current = state + const kindRef = useRef(componentKind) + kindRef.current = componentKind + + const screenToReal = useCallback( + (screenX: number, screenY: number) => { + const container = containerRef.current + if (!container) return { x: 0, y: 0 } + const rect = container.getBoundingClientRect() + const localX = screenX - rect.left + const localY = screenY - rect.top + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d, + } + }, + [svgToScreenProjection, realToSvgProjection, containerRef], + ) + + const placeAt = useCallback( + (position: { x: number; y: number }) => { + const event: EditSchematicComponentAddEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_component_add", + position, + component_kind: kindRef.current, + rotation: stateRef.current.rotation, + created_at: Date.now(), + in_progress: false, + } + onEditEvent?.(event) + }, + [onEditEvent], + ) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!enabled) return + setState((prev) => ({ + ...prev, + previewPos: screenToReal(e.clientX, e.clientY), + })) + }, + [enabled, screenToReal], + ) + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return + e.preventDefault() + e.stopPropagation() + placeAt(screenToReal(e.clientX, e.clientY)) + }, + [enabled, screenToReal, placeAt], + ) + + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (!enabled) return + if (e.key === "r" || e.key === "R") { + if (e.metaKey || e.ctrlKey || e.altKey) return + e.preventDefault() + setState((prev) => ({ + ...prev, + rotation: (prev.rotation + 90) % 360, + })) + } + }, + [enabled], + ) + + useEffect(() => { + if (!enabled) { + setState({ previewPos: null, rotation: 0 }) + return + } + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("mousedown", handleMouseDown, { capture: true }) + window.addEventListener("keydown", handleKeyDown) + return () => { + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("mousedown", handleMouseDown, { + capture: true, + }) + window.removeEventListener("keydown", handleKeyDown) + } + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown]) + + return { componentPlacementState: state } +} diff --git a/lib/hooks/useGlobalLabelPlacement.ts b/lib/hooks/useGlobalLabelPlacement.ts new file mode 100644 index 00000000..d1b41772 --- /dev/null +++ b/lib/hooks/useGlobalLabelPlacement.ts @@ -0,0 +1,171 @@ +import { useCallback, useEffect, useRef, useState } from "react" +import { type Matrix, compose } from "transformation-matrix" +import type { EditSchematicGlobalLabelAddEvent } from "../types/edit-events" +import { isMouseCaptureIgnoredTarget } from "../utils/isMouseCaptureIgnoredTarget" + +const PORT_SNAP_RADIUS_PX = 32 + +export interface GlobalLabelPlacementState { + previewPos: { x: number; y: number } | null + pendingPos: { x: number; y: number } | null + pendingPortId: string | null +} + +export const useGlobalLabelPlacement = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent, +}: { + enabled: boolean + circuitJson: any[] + svgToScreenProjection: Matrix + realToSvgProjection: Matrix + containerRef: React.RefObject + onEditEvent?: (event: EditSchematicGlobalLabelAddEvent) => void +}) => { + const [state, setState] = useState({ + previewPos: null, + pendingPos: null, + pendingPortId: null, + }) + + const stateRef = useRef(state) + stateRef.current = state + + const screenToReal = useCallback( + (screenX: number, screenY: number) => { + const container = containerRef.current + if (!container) return { x: 0, y: 0 } + const rect = container.getBoundingClientRect() + const localX = screenX - rect.left + const localY = screenY - rect.top + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d, + } + }, + [svgToScreenProjection, realToSvgProjection, containerRef], + ) + + const snapToPort = useCallback( + ( + screenX: number, + screenY: number, + ): { pos: { x: number; y: number }; portId: string | null } => { + const container = containerRef.current + if (!container) + return { pos: screenToReal(screenX, screenY), portId: null } + + let closest: { + id: string + dist: number + center: { x: number; y: number } + } | null = null + const portEls = container.querySelectorAll("[data-schematic-port-id]") + + for (const node of Array.from(portEls)) { + const rect = node.getBoundingClientRect() + const cx = rect.left + rect.width / 2 + const cy = rect.top + rect.height / 2 + const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2) + if (dist < PORT_SNAP_RADIUS_PX && (!closest || dist < closest.dist)) { + const id = node.getAttribute("data-schematic-port-id") + const portEl = circuitJson.find( + (el) => el.type === "schematic_port" && el.schematic_port_id === id, + ) + if (id && portEl?.center) { + closest = { id, dist, center: portEl.center } + } + } + } + + if (closest) return { pos: closest.center, portId: closest.id } + return { pos: screenToReal(screenX, screenY), portId: null } + }, + [containerRef, circuitJson, screenToReal], + ) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!enabled || stateRef.current.pendingPos) return + const { pos } = snapToPort(e.clientX, e.clientY) + setState((prev) => ({ ...prev, previewPos: pos })) + }, + [enabled, snapToPort], + ) + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return + if (stateRef.current.pendingPos) return + e.preventDefault() + e.stopPropagation() + const { pos, portId } = snapToPort(e.clientX, e.clientY) + setState((prev) => ({ + ...prev, + pendingPos: pos, + pendingPortId: portId, + previewPos: null, + })) + }, + [enabled, snapToPort], + ) + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === "Escape") { + setState({ + previewPos: null, + pendingPos: null, + pendingPortId: null, + }) + } + }, []) + + const confirmPlacement = useCallback( + (netName: string) => { + const current = stateRef.current + if (!current.pendingPos || !netName.trim()) return + const event: EditSchematicGlobalLabelAddEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_global_label_add", + position: current.pendingPos, + net_name: netName.trim(), + schematic_port_id: current.pendingPortId ?? undefined, + anchor_side: "right", + created_at: Date.now(), + in_progress: false, + } + onEditEvent?.(event) + setState({ previewPos: null, pendingPos: null, pendingPortId: null }) + }, + [onEditEvent], + ) + + const cancelPlacement = useCallback(() => { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }) + }, []) + + useEffect(() => { + if (!enabled) { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }) + return + } + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("mousedown", handleMouseDown, { capture: true }) + window.addEventListener("keydown", handleKeyDown) + return () => { + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("mousedown", handleMouseDown, { + capture: true, + }) + window.removeEventListener("keydown", handleKeyDown) + } + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown]) + + return { globalLabelState: state, confirmPlacement, cancelPlacement } +} diff --git a/lib/hooks/useGroundPortPlacement.ts b/lib/hooks/useGroundPortPlacement.ts new file mode 100644 index 00000000..33368367 --- /dev/null +++ b/lib/hooks/useGroundPortPlacement.ts @@ -0,0 +1,171 @@ +import { useCallback, useEffect, useRef, useState } from "react" +import { type Matrix, compose } from "transformation-matrix" +import type { EditSchematicGroundPortAddEvent } from "../types/edit-events" +import { isMouseCaptureIgnoredTarget } from "../utils/isMouseCaptureIgnoredTarget" + +const PORT_SNAP_RADIUS_PX = 32 + +export interface GroundPortPlacementState { + previewPos: { x: number; y: number } | null + pendingPos: { x: number; y: number } | null + pendingPortId: string | null +} + +export const useGroundPortPlacement = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent, +}: { + enabled: boolean + circuitJson: any[] + svgToScreenProjection: Matrix + realToSvgProjection: Matrix + containerRef: React.RefObject + onEditEvent?: (event: EditSchematicGroundPortAddEvent) => void +}) => { + const [state, setState] = useState({ + previewPos: null, + pendingPos: null, + pendingPortId: null, + }) + + const stateRef = useRef(state) + stateRef.current = state + + const screenToReal = useCallback( + (screenX: number, screenY: number) => { + const container = containerRef.current + if (!container) return { x: 0, y: 0 } + const rect = container.getBoundingClientRect() + const localX = screenX - rect.left + const localY = screenY - rect.top + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d, + } + }, + [svgToScreenProjection, realToSvgProjection, containerRef], + ) + + const snapToPort = useCallback( + ( + screenX: number, + screenY: number, + ): { pos: { x: number; y: number }; portId: string | null } => { + const container = containerRef.current + if (!container) + return { pos: screenToReal(screenX, screenY), portId: null } + + let closest: { + id: string + dist: number + center: { x: number; y: number } + } | null = null + const portEls = container.querySelectorAll("[data-schematic-port-id]") + + for (const node of Array.from(portEls)) { + const rect = node.getBoundingClientRect() + const cx = rect.left + rect.width / 2 + const cy = rect.top + rect.height / 2 + const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2) + if (dist < PORT_SNAP_RADIUS_PX && (!closest || dist < closest.dist)) { + const id = node.getAttribute("data-schematic-port-id") + const portEl = circuitJson.find( + (el) => el.type === "schematic_port" && el.schematic_port_id === id, + ) + if (id && portEl?.center) { + closest = { id, dist, center: portEl.center } + } + } + } + + if (closest) return { pos: closest.center, portId: closest.id } + return { pos: screenToReal(screenX, screenY), portId: null } + }, + [containerRef, circuitJson, screenToReal], + ) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!enabled || stateRef.current.pendingPos) return + const { pos } = snapToPort(e.clientX, e.clientY) + setState((prev) => ({ ...prev, previewPos: pos })) + }, + [enabled, snapToPort], + ) + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return + if (stateRef.current.pendingPos) return + e.preventDefault() + e.stopPropagation() + const { pos, portId } = snapToPort(e.clientX, e.clientY) + setState((prev) => ({ + ...prev, + pendingPos: pos, + pendingPortId: portId, + previewPos: null, + })) + }, + [enabled, snapToPort], + ) + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === "Escape") { + setState({ + previewPos: null, + pendingPos: null, + pendingPortId: null, + }) + } + }, []) + + const confirmPlacement = useCallback( + (netName: string) => { + const current = stateRef.current + if (!current.pendingPos || !netName.trim()) return + const event: EditSchematicGroundPortAddEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_ground_port_add", + position: current.pendingPos, + net_name: netName.trim(), + schematic_port_id: current.pendingPortId ?? undefined, + anchor_side: "top", + created_at: Date.now(), + in_progress: false, + } + onEditEvent?.(event) + setState({ previewPos: null, pendingPos: null, pendingPortId: null }) + }, + [onEditEvent], + ) + + const cancelPlacement = useCallback(() => { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }) + }, []) + + useEffect(() => { + if (!enabled) { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }) + return + } + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("mousedown", handleMouseDown, { capture: true }) + window.addEventListener("keydown", handleKeyDown) + return () => { + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("mousedown", handleMouseDown, { + capture: true, + }) + window.removeEventListener("keydown", handleKeyDown) + } + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown]) + + return { groundPortState: state, confirmPlacement, cancelPlacement } +} diff --git a/lib/hooks/useHierSheetPlacement.ts b/lib/hooks/useHierSheetPlacement.ts new file mode 100644 index 00000000..0724f628 --- /dev/null +++ b/lib/hooks/useHierSheetPlacement.ts @@ -0,0 +1,225 @@ +import { useCallback, useEffect, useRef, useState } from "react" +import { + type Matrix, + applyToPoint, + compose, + inverse, +} from "transformation-matrix" +import type { EditSchematicHierSheetAddEvent } from "../types/edit-events" +import { isMouseCaptureIgnoredTarget } from "../utils/isMouseCaptureIgnoredTarget" + +const MIN_BOX_PX = 48 + +export interface ScreenBox { + x: number + y: number + width: number + height: number +} + +export interface HierSheetPlacementState { + isDrawing: boolean + anchorLocal: { x: number; y: number } | null + previewLocal: { x: number; y: number } | null + pendingBox: { x: number; y: number; width: number; height: number } | null + pendingScreenBox: ScreenBox | null +} + +export function normalizeScreenBox( + a: { x: number; y: number }, + b: { x: number; y: number }, +): ScreenBox { + const x = Math.min(a.x, b.x) + const y = Math.min(a.y, b.y) + let width = Math.abs(b.x - a.x) + let height = Math.abs(b.y - a.y) + if (width < MIN_BOX_PX) width = MIN_BOX_PX + if (height < MIN_BOX_PX) height = MIN_BOX_PX + return { x, y, width, height } +} + +export function realBoxFromScreenBox( + screenBox: ScreenBox, + localToReal: (x: number, y: number) => { x: number; y: number }, +): { x: number; y: number; width: number; height: number } { + const { x, y, width, height } = screenBox + const corners = [ + localToReal(x, y), + localToReal(x + width, y), + localToReal(x, y + height), + localToReal(x + width, y + height), + ] + const xs = corners.map((c) => c.x) + const ys = corners.map((c) => c.y) + const minX = Math.min(...xs) + const maxX = Math.max(...xs) + const minY = Math.min(...ys) + const maxY = Math.max(...ys) + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY, + } +} + +export const useHierSheetPlacement = ({ + enabled, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent, +}: { + enabled: boolean + svgToScreenProjection: Matrix + realToSvgProjection: Matrix + containerRef: React.RefObject + onEditEvent?: (event: EditSchematicHierSheetAddEvent) => void +}) => { + const [state, setState] = useState({ + isDrawing: false, + anchorLocal: null, + previewLocal: null, + pendingBox: null, + pendingScreenBox: null, + }) + + const stateRef = useRef(state) + stateRef.current = state + + const localFromMouse = useCallback( + (clientX: number, clientY: number) => { + const container = containerRef.current + if (!container) return { x: 0, y: 0 } + const rect = container.getBoundingClientRect() + return { x: clientX - rect.left, y: clientY - rect.top } + }, + [containerRef], + ) + + const localToReal = useCallback( + (localX: number, localY: number) => { + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + return applyToPoint(inverse(realToScreen), { x: localX, y: localY }) + }, + [svgToScreenProjection, realToSvgProjection], + ) + + const reset = useCallback(() => { + setState({ + isDrawing: false, + anchorLocal: null, + previewLocal: null, + pendingBox: null, + pendingScreenBox: null, + }) + }, []) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!enabled || stateRef.current.pendingBox) return + const pos = localFromMouse(e.clientX, e.clientY) + if (stateRef.current.isDrawing && stateRef.current.anchorLocal) { + setState((prev) => ({ ...prev, previewLocal: pos })) + } + }, + [enabled, localFromMouse], + ) + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + if ( + !enabled || + e.button !== 0 || + stateRef.current.pendingBox || + isMouseCaptureIgnoredTarget(e.target) + ) + return + e.preventDefault() + e.stopPropagation() + const pos = localFromMouse(e.clientX, e.clientY) + const current = stateRef.current + + if (!current.isDrawing) { + setState({ + isDrawing: true, + anchorLocal: pos, + previewLocal: pos, + pendingBox: null, + pendingScreenBox: null, + }) + return + } + + if (!current.anchorLocal) return + const screenBox = normalizeScreenBox(current.anchorLocal, pos) + const box = realBoxFromScreenBox(screenBox, localToReal) + setState({ + isDrawing: false, + anchorLocal: null, + previewLocal: null, + pendingBox: box, + pendingScreenBox: screenBox, + }) + }, + [enabled, localFromMouse, localToReal], + ) + + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === "Escape") reset() + }, + [reset], + ) + + const confirmPlacement = useCallback( + (sheetName: string, targetSheetId: string) => { + const box = stateRef.current.pendingBox + const screenBox = stateRef.current.pendingScreenBox + if (!box || !screenBox || !sheetName.trim() || !targetSheetId.trim()) + return + + const sheetNamePos = localToReal(screenBox.x + 6, screenBox.y + 14) + const fileNamePos = localToReal( + screenBox.x + 6, + screenBox.y + screenBox.height - 8, + ) + + const event: EditSchematicHierSheetAddEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_hier_sheet_add", + box, + sheet_name: sheetName.trim(), + target_sheet_id: targetSheetId.trim(), + sheet_name_pos: sheetNamePos, + file_name_pos: fileNamePos, + created_at: Date.now(), + in_progress: false, + } + onEditEvent?.(event) + reset() + }, + [onEditEvent, reset, localToReal], + ) + + const cancelPlacement = useCallback(() => reset(), [reset]) + + useEffect(() => { + if (!enabled) { + reset() + return + } + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("mousedown", handleMouseDown, { capture: true }) + window.addEventListener("keydown", handleKeyDown) + return () => { + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("mousedown", handleMouseDown, { + capture: true, + }) + window.removeEventListener("keydown", handleKeyDown) + } + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown, reset]) + + return { hierSheetState: state, confirmPlacement, cancelPlacement } +} diff --git a/lib/hooks/useLocalStorage.ts b/lib/hooks/useLocalStorage.ts index d4d84a1e..7bda7d7e 100644 --- a/lib/hooks/useLocalStorage.ts +++ b/lib/hooks/useLocalStorage.ts @@ -78,3 +78,9 @@ export const useLocalStorageValue = (key: string, defaultValue: boolean) => { setValue, } } + +let spacePanHeld = false +export const isSpacePanHeld = () => spacePanHeld +export const setSpacePanHeld = (held: boolean) => { + spacePanHeld = held +} diff --git a/lib/hooks/useNetLabelPlacement.ts b/lib/hooks/useNetLabelPlacement.ts new file mode 100644 index 00000000..ffc750ee --- /dev/null +++ b/lib/hooks/useNetLabelPlacement.ts @@ -0,0 +1,171 @@ +import { useCallback, useEffect, useRef, useState } from "react" +import { type Matrix, compose } from "transformation-matrix" +import type { EditSchematicNetLabelAddEvent } from "../types/edit-events" +import { isMouseCaptureIgnoredTarget } from "../utils/isMouseCaptureIgnoredTarget" + +const PORT_SNAP_RADIUS_PX = 32 + +export interface NetLabelPlacementState { + previewPos: { x: number; y: number } | null + pendingPos: { x: number; y: number } | null + pendingPortId: string | null +} + +export const useNetLabelPlacement = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent, +}: { + enabled: boolean + circuitJson: any[] + svgToScreenProjection: Matrix + realToSvgProjection: Matrix + containerRef: React.RefObject + onEditEvent?: (event: EditSchematicNetLabelAddEvent) => void +}) => { + const [state, setState] = useState({ + previewPos: null, + pendingPos: null, + pendingPortId: null, + }) + + const stateRef = useRef(state) + stateRef.current = state + + const screenToReal = useCallback( + (screenX: number, screenY: number) => { + const container = containerRef.current + if (!container) return { x: 0, y: 0 } + const rect = container.getBoundingClientRect() + const localX = screenX - rect.left + const localY = screenY - rect.top + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d, + } + }, + [svgToScreenProjection, realToSvgProjection, containerRef], + ) + + const snapToPort = useCallback( + ( + screenX: number, + screenY: number, + ): { pos: { x: number; y: number }; portId: string | null } => { + const container = containerRef.current + if (!container) + return { pos: screenToReal(screenX, screenY), portId: null } + + let closest: { + id: string + dist: number + center: { x: number; y: number } + } | null = null + const portEls = container.querySelectorAll("[data-schematic-port-id]") + + for (const node of Array.from(portEls)) { + const rect = node.getBoundingClientRect() + const cx = rect.left + rect.width / 2 + const cy = rect.top + rect.height / 2 + const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2) + if (dist < PORT_SNAP_RADIUS_PX && (!closest || dist < closest.dist)) { + const id = node.getAttribute("data-schematic-port-id") + const portEl = circuitJson.find( + (el) => el.type === "schematic_port" && el.schematic_port_id === id, + ) + if (id && portEl?.center) { + closest = { id, dist, center: portEl.center } + } + } + } + + if (closest) return { pos: closest.center, portId: closest.id } + return { pos: screenToReal(screenX, screenY), portId: null } + }, + [containerRef, circuitJson, screenToReal], + ) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!enabled || stateRef.current.pendingPos) return + const { pos } = snapToPort(e.clientX, e.clientY) + setState((prev) => ({ ...prev, previewPos: pos })) + }, + [enabled, snapToPort], + ) + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return + if (stateRef.current.pendingPos) return + e.preventDefault() + e.stopPropagation() + const { pos, portId } = snapToPort(e.clientX, e.clientY) + setState((prev) => ({ + ...prev, + pendingPos: pos, + pendingPortId: portId, + previewPos: null, + })) + }, + [enabled, snapToPort], + ) + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === "Escape") { + setState({ + previewPos: null, + pendingPos: null, + pendingPortId: null, + }) + } + }, []) + + const confirmPlacement = useCallback( + (netName: string) => { + const current = stateRef.current + if (!current.pendingPos || !netName.trim()) return + const event: EditSchematicNetLabelAddEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_net_label_add", + position: current.pendingPos, + net_name: netName.trim(), + schematic_port_id: current.pendingPortId ?? undefined, + anchor_side: "right", + created_at: Date.now(), + in_progress: false, + } + onEditEvent?.(event) + setState({ previewPos: null, pendingPos: null, pendingPortId: null }) + }, + [onEditEvent], + ) + + const cancelPlacement = useCallback(() => { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }) + }, []) + + useEffect(() => { + if (!enabled) { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }) + return + } + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("mousedown", handleMouseDown, { capture: true }) + window.addEventListener("keydown", handleKeyDown) + return () => { + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("mousedown", handleMouseDown, { + capture: true, + }) + window.removeEventListener("keydown", handleKeyDown) + } + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown]) + + return { netLabelState: state, confirmPlacement, cancelPlacement } +} diff --git a/lib/hooks/useNoConnectPlacement.ts b/lib/hooks/useNoConnectPlacement.ts new file mode 100644 index 00000000..45e164e3 --- /dev/null +++ b/lib/hooks/useNoConnectPlacement.ts @@ -0,0 +1,166 @@ +import { useCallback, useEffect, useState } from "react" +import { type Matrix, compose } from "transformation-matrix" +import type { EditSchematicNoConnectAddEvent } from "../types/edit-events" +import { isMouseCaptureIgnoredTarget } from "../utils/isMouseCaptureIgnoredTarget" + +export const NO_CONNECT_HALF = 0.4 +const PORT_HIT_RADIUS_PX = 36 + +export interface NoConnectPreviewState { + center: { x: number; y: number } | null +} + +export const useNoConnectPlacement = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent, +}: { + enabled: boolean + circuitJson: any[] + svgToScreenProjection: Matrix + realToSvgProjection: Matrix + containerRef: React.RefObject + onEditEvent?: (event: EditSchematicNoConnectAddEvent) => void +}) => { + const [previewState, setPreviewState] = useState({ + center: null, + }) + + const screenToReal = useCallback( + (screenX: number, screenY: number) => { + const container = containerRef.current + if (!container) return { x: 0, y: 0 } + const rect = container.getBoundingClientRect() + const localX = screenX - rect.left + const localY = screenY - rect.top + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d, + } + }, + [svgToScreenProjection, realToSvgProjection, containerRef], + ) + + const resolveSchematicPortId = useCallback( + (portId: string): string | null => { + const bySchematic = (circuitJson as any[]).find( + (el) => el.type === "schematic_port" && el.schematic_port_id === portId, + ) + if (bySchematic) return portId + const bySource = (circuitJson as any[]).find( + (el) => el.type === "schematic_port" && el.source_port_id === portId, + ) + return bySource?.schematic_port_id ?? null + }, + [circuitJson], + ) + + const getPortCenter = useCallback( + (portId: string): { x: number; y: number } | null => { + const schematicPortId = resolveSchematicPortId(portId) + if (!schematicPortId) return null + const port = (circuitJson as any[]).find( + (el) => + el.type === "schematic_port" && + el.schematic_port_id === schematicPortId, + ) + if (!port?.center) return null + return { x: port.center.x, y: port.center.y } + }, + [circuitJson, resolveSchematicPortId], + ) + + const getPortAtScreen = useCallback( + (screenX: number, screenY: number): string | null => { + const container = containerRef.current + if (!container) return null + let closest: { id: string; dist: number } | null = null + const portEls = container.querySelectorAll("[data-schematic-port-id]") + for (const node of Array.from(portEls)) { + const rect = node.getBoundingClientRect() + const cx = rect.left + rect.width / 2 + const cy = rect.top + rect.height / 2 + const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2) + if (dist < PORT_HIT_RADIUS_PX && (!closest || dist < closest.dist)) { + const id = node.getAttribute("data-schematic-port-id") + const resolved = id ? resolveSchematicPortId(id) : null + if (resolved) closest = { id: resolved, dist } + } + } + return closest?.id ?? null + }, + [containerRef, resolveSchematicPortId], + ) + + const placeNoConnect = useCallback( + (center: { x: number; y: number }, schematicPortId?: string) => { + const event: EditSchematicNoConnectAddEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_no_connect_add", + center, + schematic_port_id: schematicPortId, + created_at: Date.now(), + in_progress: false, + } + onEditEvent?.(event) + }, + [onEditEvent], + ) + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return + e.preventDefault() + e.stopPropagation() + + const portId = getPortAtScreen(e.clientX, e.clientY) + if (portId) { + const center = getPortCenter(portId) + if (center) { + placeNoConnect(center, portId) + return + } + } + placeNoConnect(screenToReal(e.clientX, e.clientY)) + }, + [enabled, getPortAtScreen, getPortCenter, placeNoConnect, screenToReal], + ) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!enabled) return + const portId = getPortAtScreen(e.clientX, e.clientY) + if (portId) { + const center = getPortCenter(portId) + if (center) { + setPreviewState({ center }) + return + } + } + setPreviewState({ center: screenToReal(e.clientX, e.clientY) }) + }, + [enabled, getPortAtScreen, getPortCenter, screenToReal], + ) + + useEffect(() => { + if (!enabled) { + setPreviewState({ center: null }) + return + } + window.addEventListener("mousedown", handleMouseDown, { capture: true }) + window.addEventListener("mousemove", handleMouseMove) + return () => { + window.removeEventListener("mousedown", handleMouseDown, { + capture: true, + }) + window.removeEventListener("mousemove", handleMouseMove) + } + }, [enabled, handleMouseDown, handleMouseMove]) + + return { noConnectPreviewState: previewState } +} diff --git a/lib/hooks/usePowerPortPlacement.ts b/lib/hooks/usePowerPortPlacement.ts new file mode 100644 index 00000000..7321e499 --- /dev/null +++ b/lib/hooks/usePowerPortPlacement.ts @@ -0,0 +1,171 @@ +import { useCallback, useEffect, useRef, useState } from "react" +import { type Matrix, compose } from "transformation-matrix" +import type { EditSchematicPowerPortAddEvent } from "../types/edit-events" +import { isMouseCaptureIgnoredTarget } from "../utils/isMouseCaptureIgnoredTarget" + +const PORT_SNAP_RADIUS_PX = 32 + +export interface PowerPortPlacementState { + previewPos: { x: number; y: number } | null + pendingPos: { x: number; y: number } | null + pendingPortId: string | null +} + +export const usePowerPortPlacement = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent, +}: { + enabled: boolean + circuitJson: any[] + svgToScreenProjection: Matrix + realToSvgProjection: Matrix + containerRef: React.RefObject + onEditEvent?: (event: EditSchematicPowerPortAddEvent) => void +}) => { + const [state, setState] = useState({ + previewPos: null, + pendingPos: null, + pendingPortId: null, + }) + + const stateRef = useRef(state) + stateRef.current = state + + const screenToReal = useCallback( + (screenX: number, screenY: number) => { + const container = containerRef.current + if (!container) return { x: 0, y: 0 } + const rect = container.getBoundingClientRect() + const localX = screenX - rect.left + const localY = screenY - rect.top + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d, + } + }, + [svgToScreenProjection, realToSvgProjection, containerRef], + ) + + const snapToPort = useCallback( + ( + screenX: number, + screenY: number, + ): { pos: { x: number; y: number }; portId: string | null } => { + const container = containerRef.current + if (!container) + return { pos: screenToReal(screenX, screenY), portId: null } + + let closest: { + id: string + dist: number + center: { x: number; y: number } + } | null = null + const portEls = container.querySelectorAll("[data-schematic-port-id]") + + for (const node of Array.from(portEls)) { + const rect = node.getBoundingClientRect() + const cx = rect.left + rect.width / 2 + const cy = rect.top + rect.height / 2 + const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2) + if (dist < PORT_SNAP_RADIUS_PX && (!closest || dist < closest.dist)) { + const id = node.getAttribute("data-schematic-port-id") + const portEl = circuitJson.find( + (el) => el.type === "schematic_port" && el.schematic_port_id === id, + ) + if (id && portEl?.center) { + closest = { id, dist, center: portEl.center } + } + } + } + + if (closest) return { pos: closest.center, portId: closest.id } + return { pos: screenToReal(screenX, screenY), portId: null } + }, + [containerRef, circuitJson, screenToReal], + ) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!enabled || stateRef.current.pendingPos) return + const { pos } = snapToPort(e.clientX, e.clientY) + setState((prev) => ({ ...prev, previewPos: pos })) + }, + [enabled, snapToPort], + ) + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return + if (stateRef.current.pendingPos) return + e.preventDefault() + e.stopPropagation() + const { pos, portId } = snapToPort(e.clientX, e.clientY) + setState((prev) => ({ + ...prev, + pendingPos: pos, + pendingPortId: portId, + previewPos: null, + })) + }, + [enabled, snapToPort], + ) + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === "Escape") { + setState({ + previewPos: null, + pendingPos: null, + pendingPortId: null, + }) + } + }, []) + + const confirmPlacement = useCallback( + (netName: string) => { + const current = stateRef.current + if (!current.pendingPos || !netName.trim()) return + const event: EditSchematicPowerPortAddEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_power_port_add", + position: current.pendingPos, + net_name: netName.trim(), + schematic_port_id: current.pendingPortId ?? undefined, + anchor_side: "bottom", + created_at: Date.now(), + in_progress: false, + } + onEditEvent?.(event) + setState({ previewPos: null, pendingPos: null, pendingPortId: null }) + }, + [onEditEvent], + ) + + const cancelPlacement = useCallback(() => { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }) + }, []) + + useEffect(() => { + if (!enabled) { + setState({ previewPos: null, pendingPos: null, pendingPortId: null }) + return + } + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("mousedown", handleMouseDown, { capture: true }) + window.addEventListener("keydown", handleKeyDown) + return () => { + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("mousedown", handleMouseDown, { + capture: true, + }) + window.removeEventListener("keydown", handleKeyDown) + } + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown]) + + return { powerPortState: state, confirmPlacement, cancelPlacement } +} diff --git a/lib/hooks/useTextNotePlacement.ts b/lib/hooks/useTextNotePlacement.ts new file mode 100644 index 00000000..0012ce2c --- /dev/null +++ b/lib/hooks/useTextNotePlacement.ts @@ -0,0 +1,124 @@ +import { useCallback, useEffect, useRef, useState } from "react" +import { type Matrix, compose } from "transformation-matrix" +import type { EditSchematicTextNoteAddEvent } from "../types/edit-events" +import { isMouseCaptureIgnoredTarget } from "../utils/isMouseCaptureIgnoredTarget" + +export interface TextNotePlacementState { + previewPos: { x: number; y: number } | null + pendingPos: { x: number; y: number } | null +} + +export const useTextNotePlacement = ({ + enabled, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent, +}: { + enabled: boolean + svgToScreenProjection: Matrix + realToSvgProjection: Matrix + containerRef: React.RefObject + onEditEvent?: (event: EditSchematicTextNoteAddEvent) => void +}) => { + const [state, setState] = useState({ + previewPos: null, + pendingPos: null, + }) + + const stateRef = useRef(state) + stateRef.current = state + + const screenToReal = useCallback( + (screenX: number, screenY: number) => { + const container = containerRef.current + if (!container) return { x: 0, y: 0 } + const rect = container.getBoundingClientRect() + const localX = screenX - rect.left + const localY = screenY - rect.top + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d, + } + }, + [svgToScreenProjection, realToSvgProjection, containerRef], + ) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!enabled || stateRef.current.pendingPos) return + setState((prev) => ({ + ...prev, + previewPos: screenToReal(e.clientX, e.clientY), + })) + }, + [enabled, screenToReal], + ) + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) + return + if (stateRef.current.pendingPos) return + e.preventDefault() + e.stopPropagation() + setState((prev) => ({ + ...prev, + pendingPos: screenToReal(e.clientX, e.clientY), + previewPos: null, + })) + }, + [enabled, screenToReal], + ) + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === "Escape") { + setState({ previewPos: null, pendingPos: null }) + } + }, []) + + const confirmPlacement = useCallback( + (text: string) => { + const current = stateRef.current + if (!current.pendingPos || !text.trim()) return + const event: EditSchematicTextNoteAddEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_text_note_add", + position: current.pendingPos, + text: text.trim(), + anchor: "left", + font_size: 0.2, + color: "#1a1612", + created_at: Date.now(), + in_progress: false, + } + onEditEvent?.(event) + setState({ previewPos: null, pendingPos: null }) + }, + [onEditEvent], + ) + + const cancelPlacement = useCallback(() => { + setState({ previewPos: null, pendingPos: null }) + }, []) + + useEffect(() => { + if (!enabled) { + setState({ previewPos: null, pendingPos: null }) + return + } + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("mousedown", handleMouseDown, { capture: true }) + window.addEventListener("keydown", handleKeyDown) + return () => { + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("mousedown", handleMouseDown, { + capture: true, + }) + window.removeEventListener("keydown", handleKeyDown) + } + }, [enabled, handleMouseMove, handleMouseDown, handleKeyDown]) + + return { textNoteState: state, confirmPlacement, cancelPlacement } +} diff --git a/lib/hooks/useTraceDrawing.ts b/lib/hooks/useTraceDrawing.ts new file mode 100644 index 00000000..58702298 --- /dev/null +++ b/lib/hooks/useTraceDrawing.ts @@ -0,0 +1,241 @@ +import { useCallback, useEffect, useRef, useState } from "react" +import { type Matrix } from "transformation-matrix" +import type { EditSchematicWireAddEvent } from "../types/edit-events" +import { computeTraceRoute } from "../utils/computeTraceRoute" +import { isMouseCaptureIgnoredTarget } from "../utils/isMouseCaptureIgnoredTarget" +import { + createScreenToReal, + getSchematicPortAtScreen, + getSchematicPortCenter, + resolveSchematicPortId, +} from "../utils/schematicPortHitTest" + +export interface TraceDrawingState { + isDrawing: boolean + fromPortId: string | null + previewEnd: { x: number; y: number } | null + previewRoute: Array<{ x: number; y: number }> +} + +export const useTraceDrawing = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent, +}: { + enabled: boolean + circuitJson: any[] + svgToScreenProjection: Matrix + realToSvgProjection: Matrix + containerRef: React.RefObject + onEditEvent?: (event: EditSchematicWireAddEvent) => void +}) => { + const [state, setState] = useState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + previewRoute: [], + }) + + const stateRef = useRef(state) + stateRef.current = state + + const screenToReal = useCallback( + createScreenToReal( + svgToScreenProjection, + realToSvgProjection, + containerRef, + ), + [svgToScreenProjection, realToSvgProjection, containerRef], + ) + + const getPortCenter = useCallback( + (portId: string) => { + const schematicPortId = resolveSchematicPortId(circuitJson, portId) + if (!schematicPortId) return null + return getSchematicPortCenter( + circuitJson, + containerRef.current, + schematicPortId, + screenToReal, + ) + }, + [circuitJson, containerRef, screenToReal], + ) + + const getPortAtScreen = useCallback( + (screenX: number, screenY: number) => + getSchematicPortAtScreen( + containerRef.current, + circuitJson, + screenX, + screenY, + ), + [circuitJson, containerRef], + ) + + const beginTraceAtPort = useCallback( + (portId: string) => { + const schematicPortId = resolveSchematicPortId(circuitJson, portId) + if (!schematicPortId) return false + + const center = getPortCenter(schematicPortId) + if (!center) return false + + setState({ + isDrawing: true, + fromPortId: schematicPortId, + previewEnd: center, + previewRoute: [center], + }) + return true + }, + [circuitJson, getPortCenter], + ) + + const finishTraceAtPort = useCallback( + (portId: string) => { + const current = stateRef.current + if (!current.isDrawing || !current.fromPortId) return false + + const schematicPortId = resolveSchematicPortId(circuitJson, portId) + if (!schematicPortId || schematicPortId === current.fromPortId) { + return false + } + + const fromCenter = getPortCenter(current.fromPortId) + const toCenter = getPortCenter(schematicPortId) + if (!fromCenter || !toCenter) return false + + const route = computeTraceRoute(fromCenter, toCenter) + const event: EditSchematicWireAddEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_wire_add", + from_schematic_port_id: current.fromPortId, + to_schematic_port_id: schematicPortId, + route, + created_at: Date.now(), + in_progress: false, + } + onEditEvent?.(event) + setState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + previewRoute: [], + }) + return true + }, + [circuitJson, getPortCenter, onEditEvent], + ) + + const handlePortMouseDown = useCallback( + (portId: string, e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) { + return + } + + e.preventDefault() + e.stopPropagation() + + const current = stateRef.current + if (!current.isDrawing) { + beginTraceAtPort(portId) + return + } + + finishTraceAtPort(portId) + }, + [enabled, beginTraceAtPort, finishTraceAtPort], + ) + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) { + return + } + + const current = stateRef.current + const portId = getPortAtScreen(e.clientX, e.clientY) + + if (!current.isDrawing) { + if (!portId) return + e.preventDefault() + e.stopPropagation() + beginTraceAtPort(portId) + return + } + + if (portId && finishTraceAtPort(portId)) { + e.preventDefault() + e.stopPropagation() + } + }, + [enabled, getPortAtScreen, beginTraceAtPort, finishTraceAtPort], + ) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if ( + !enabled || + !stateRef.current.isDrawing || + !stateRef.current.fromPortId + ) { + return + } + const fromCenter = getPortCenter(stateRef.current.fromPortId) + if (!fromCenter) return + const hover = screenToReal(e.clientX, e.clientY) + setState((prev) => ({ + ...prev, + previewEnd: hover, + previewRoute: computeTraceRoute(fromCenter, hover), + })) + }, + [enabled, getPortCenter, screenToReal], + ) + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === "Escape" && stateRef.current.isDrawing) { + setState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + previewRoute: [], + }) + } + }, []) + + useEffect(() => { + if (!enabled) { + setState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + previewRoute: [], + }) + return + } + window.addEventListener("mousedown", handleMouseDown, { capture: true }) + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("keydown", handleKeyDown) + return () => { + window.removeEventListener("mousedown", handleMouseDown, { + capture: true, + }) + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("keydown", handleKeyDown) + } + }, [enabled, handleMouseDown, handleMouseMove, handleKeyDown]) + + const wireDrawingState = { + isDrawing: state.isDrawing, + fromPortId: state.fromPortId, + previewEnd: state.previewEnd, + waypoints: state.previewRoute, + } + + return { traceDrawingState: state, wireDrawingState, handlePortMouseDown } +} diff --git a/lib/hooks/useWireDrawing.ts b/lib/hooks/useWireDrawing.ts new file mode 100644 index 00000000..66358e6b --- /dev/null +++ b/lib/hooks/useWireDrawing.ts @@ -0,0 +1,234 @@ +import { useCallback, useEffect, useRef, useState } from "react" +import { type Matrix } from "transformation-matrix" +import type { EditSchematicWireAddEvent } from "../types/edit-events" +import { isMouseCaptureIgnoredTarget } from "../utils/isMouseCaptureIgnoredTarget" +import { + createScreenToReal, + getSchematicPortAtScreen, + getSchematicPortCenter, + resolveSchematicPortId, +} from "../utils/schematicPortHitTest" + +export interface WireDrawingState { + isDrawing: boolean + fromPortId: string | null + previewEnd: { x: number; y: number } | null + waypoints: Array<{ x: number; y: number }> +} + +export const useWireDrawing = ({ + enabled, + circuitJson, + svgToScreenProjection, + realToSvgProjection, + containerRef, + onEditEvent, +}: { + enabled: boolean + circuitJson: any[] + svgToScreenProjection: Matrix + realToSvgProjection: Matrix + containerRef: React.RefObject + onEditEvent?: (event: EditSchematicWireAddEvent) => void +}) => { + const [state, setState] = useState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + waypoints: [], + }) + + const stateRef = useRef(state) + stateRef.current = state + + const screenToReal = useCallback( + createScreenToReal( + svgToScreenProjection, + realToSvgProjection, + containerRef, + ), + [svgToScreenProjection, realToSvgProjection, containerRef], + ) + + const getPortCenter = useCallback( + (portId: string) => { + const schematicPortId = resolveSchematicPortId(circuitJson, portId) + if (!schematicPortId) return null + return getSchematicPortCenter( + circuitJson, + containerRef.current, + schematicPortId, + screenToReal, + ) + }, + [circuitJson, containerRef, screenToReal], + ) + + const getPortAtScreen = useCallback( + (screenX: number, screenY: number) => + getSchematicPortAtScreen( + containerRef.current, + circuitJson, + screenX, + screenY, + ), + [circuitJson, containerRef], + ) + + const beginWireAtPort = useCallback( + (portId: string) => { + const schematicPortId = resolveSchematicPortId(circuitJson, portId) + if (!schematicPortId) return false + + const center = getPortCenter(schematicPortId) + if (!center) return false + + setState({ + isDrawing: true, + fromPortId: schematicPortId, + previewEnd: center, + waypoints: [center], + }) + return true + }, + [circuitJson, getPortCenter], + ) + + const finishWireAtPort = useCallback( + (portId: string) => { + const current = stateRef.current + if (!current.isDrawing || !current.fromPortId) return false + + const schematicPortId = resolveSchematicPortId(circuitJson, portId) + if (!schematicPortId || schematicPortId === current.fromPortId) { + return false + } + + const toCenter = getPortCenter(schematicPortId) + if (!toCenter) return false + + const event: EditSchematicWireAddEvent = { + edit_event_id: Math.random().toString(36).substr(2, 9), + edit_event_type: "edit_schematic_wire_add", + from_schematic_port_id: current.fromPortId, + to_schematic_port_id: schematicPortId, + route: [...current.waypoints, toCenter], + created_at: Date.now(), + in_progress: false, + } + onEditEvent?.(event) + setState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + waypoints: [], + }) + return true + }, + [circuitJson, getPortCenter, onEditEvent], + ) + + const handlePortMouseDown = useCallback( + (portId: string, e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) { + return + } + + e.preventDefault() + e.stopPropagation() + + const current = stateRef.current + if (!current.isDrawing) { + beginWireAtPort(portId) + return + } + + if (!finishWireAtPort(portId)) { + const realPos = screenToReal(e.clientX, e.clientY) + setState((prev) => ({ + ...prev, + waypoints: [...prev.waypoints, realPos], + })) + } + }, + [enabled, beginWireAtPort, finishWireAtPort, screenToReal], + ) + + const handleMouseDown = useCallback( + (e: MouseEvent) => { + if (!enabled || e.button !== 0 || isMouseCaptureIgnoredTarget(e.target)) { + return + } + + const current = stateRef.current + const portId = getPortAtScreen(e.clientX, e.clientY) + + if (!current.isDrawing) { + if (!portId) return + e.preventDefault() + e.stopPropagation() + beginWireAtPort(portId) + return + } + + if (portId && finishWireAtPort(portId)) { + e.preventDefault() + e.stopPropagation() + return + } + + e.preventDefault() + e.stopPropagation() + const realPos = screenToReal(e.clientX, e.clientY) + setState((prev) => ({ + ...prev, + waypoints: [...prev.waypoints, realPos], + })) + }, + [enabled, getPortAtScreen, beginWireAtPort, finishWireAtPort, screenToReal], + ) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!enabled || !stateRef.current.isDrawing) return + const realPos = screenToReal(e.clientX, e.clientY) + setState((prev) => ({ ...prev, previewEnd: realPos })) + }, + [enabled, screenToReal], + ) + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === "Escape" && stateRef.current.isDrawing) { + setState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + waypoints: [], + }) + } + }, []) + + useEffect(() => { + if (!enabled) { + setState({ + isDrawing: false, + fromPortId: null, + previewEnd: null, + waypoints: [], + }) + return + } + window.addEventListener("mousedown", handleMouseDown, { capture: true }) + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("keydown", handleKeyDown) + return () => { + window.removeEventListener("mousedown", handleMouseDown, { + capture: true, + }) + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("keydown", handleKeyDown) + } + }, [enabled, handleMouseDown, handleMouseMove, handleKeyDown]) + + return { wireDrawingState: state, handlePortMouseDown } +} diff --git a/lib/index.ts b/lib/index.ts index 667b15c6..aac47d25 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -2,3 +2,17 @@ export { SchematicViewer } from "./components/SchematicViewer" export { MouseTracker } from "./components/MouseTracker" export { useMouseEventsOverBoundingBox } from "./hooks/useMouseEventsOverBoundingBox" export { AnalogSimulationViewer } from "./components/AnalogSimulationViewer" +export type { + EditSchematicBusAddEvent, + EditSchematicBusEntryAddEvent, + EditSchematicGlobalLabelAddEvent, + EditSchematicHierSheetAddEvent, + EditSchematicNetLabelAddEvent, + EditSchematicNoConnectAddEvent, + EditSchematicWireAddEvent, + EditSchematicPowerPortAddEvent, + EditSchematicGroundPortAddEvent, + EditSchematicTextNoteAddEvent, + EditSchematicComponentAddEvent, + PlacementComponentKind, +} from "./types/edit-events" diff --git a/lib/types/edit-events.ts b/lib/types/edit-events.ts index 33ce0662..38675c85 100644 --- a/lib/types/edit-events.ts +++ b/lib/types/edit-events.ts @@ -9,6 +9,102 @@ export type EditSchematicComponentLocationEventWithElement = _element: SVGElement } +export interface EditSchematicWireAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_wire_add" + from_schematic_port_id: string + to_schematic_port_id: string + route: Array<{ x: number; y: number }> +} + +export interface EditSchematicBusAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_bus_add" + route: Array<{ x: number; y: number }> +} + +export interface EditSchematicBusEntryAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_bus_entry_add" + anchor: { x: number; y: number } +} + +export interface EditSchematicNoConnectAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_no_connect_add" + center: { x: number; y: number } + schematic_port_id?: string +} + +export interface EditSchematicNetLabelAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_net_label_add" + position: { x: number; y: number } + net_name: string + schematic_port_id?: string + anchor_side?: "left" | "right" | "top" | "bottom" +} + +export interface EditSchematicGlobalLabelAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_global_label_add" + position: { x: number; y: number } + net_name: string + schematic_port_id?: string + anchor_side?: "left" | "right" | "top" | "bottom" +} + +export interface EditSchematicPowerPortAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_power_port_add" + position: { x: number; y: number } + net_name: string + schematic_port_id?: string + anchor_side?: "left" | "right" | "top" | "bottom" +} + +export interface EditSchematicGroundPortAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_ground_port_add" + position: { x: number; y: number } + net_name: string + schematic_port_id?: string + anchor_side?: "left" | "right" | "top" | "bottom" +} + +export interface EditSchematicTextNoteAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_text_note_add" + position: { x: number; y: number } + text: string + anchor?: "left" | "right" | "center" + font_size?: number + color?: string +} + +export interface EditSchematicHierSheetAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_hier_sheet_add" + box: { x: number; y: number; width: number; height: number } + sheet_name: string + target_sheet_id: string + sheet_name_pos: { x: number; y: number } + file_name_pos: { x: number; y: number } +} + +export type PlacementComponentKind = "resistor" | "capacitor" | "inductor" + +export interface EditSchematicComponentAddEvent extends BaseManualEditEvent { + edit_event_type: "edit_schematic_component_add" + position: { x: number; y: number } + component_kind: PlacementComponentKind + rotation?: number +} + +export type ExtendedManualEditEvent = + | ManualEditEvent + | EditSchematicWireAddEvent + | EditSchematicBusAddEvent + | EditSchematicBusEntryAddEvent + | EditSchematicNoConnectAddEvent + | EditSchematicNetLabelAddEvent + | EditSchematicGlobalLabelAddEvent + | EditSchematicHierSheetAddEvent + | EditSchematicPowerPortAddEvent + | EditSchematicGroundPortAddEvent + | EditSchematicTextNoteAddEvent + | EditSchematicComponentAddEvent + export type { BaseManualEditEvent, EditSchematicComponentLocationEvent, diff --git a/lib/utils/computeTraceRoute.ts b/lib/utils/computeTraceRoute.ts new file mode 100644 index 00000000..37f65be1 --- /dev/null +++ b/lib/utils/computeTraceRoute.ts @@ -0,0 +1,24 @@ +export function computeTraceRoute( + from: { x: number; y: number }, + to: { x: number; y: number }, +): Array<{ x: number; y: number }> { + if (Math.abs(from.x - to.x) < 1e-6 || Math.abs(from.y - to.y) < 1e-6) { + return [from, to] + } + + const viaHFirst = { x: to.x, y: from.y } + const viaVFirst = { x: from.x, y: to.y } + const lenH = + Math.abs(from.x - viaHFirst.x) + + Math.abs(from.y - viaHFirst.y) + + Math.abs(viaHFirst.x - to.x) + + Math.abs(viaHFirst.y - to.y) + const lenV = + Math.abs(from.x - viaVFirst.x) + + Math.abs(from.y - viaVFirst.y) + + Math.abs(viaVFirst.x - to.x) + + Math.abs(viaVFirst.y - to.y) + + const corner = lenH <= lenV ? viaHFirst : viaVFirst + return [from, corner, to] +} diff --git a/lib/utils/isMouseCaptureIgnoredTarget.ts b/lib/utils/isMouseCaptureIgnoredTarget.ts new file mode 100644 index 00000000..8462988a --- /dev/null +++ b/lib/utils/isMouseCaptureIgnoredTarget.ts @@ -0,0 +1,11 @@ +export function isMouseCaptureIgnoredTarget( + target: EventTarget | null, +): boolean { + const el = target as Element | null + if (!el) return false + return Boolean( + el.closest( + "[data-schematic-ignore-mouse-capture], input, textarea, select, option", + ), + ) +} diff --git a/lib/utils/schematicPortHitTest.ts b/lib/utils/schematicPortHitTest.ts new file mode 100644 index 00000000..91435796 --- /dev/null +++ b/lib/utils/schematicPortHitTest.ts @@ -0,0 +1,89 @@ +import { type Matrix, compose } from "transformation-matrix" + +export const SCHEMATIC_PORT_HIT_RADIUS_PX = 36 + +export function resolveSchematicPortId( + circuitJson: any[], + portId: string, +): string | null { + const bySchematic = circuitJson.find( + (el) => el.type === "schematic_port" && el.schematic_port_id === portId, + ) + if (bySchematic) return portId + + const bySource = circuitJson.find( + (el) => el.type === "schematic_port" && el.source_port_id === portId, + ) + return bySource?.schematic_port_id ?? null +} + +export function createScreenToReal( + svgToScreenProjection: Matrix, + realToSvgProjection: Matrix, + containerRef: { current: HTMLDivElement | null }, +) { + return (screenX: number, screenY: number) => { + const container = containerRef.current + if (!container) return { x: 0, y: 0 } + const rect = container.getBoundingClientRect() + const localX = screenX - rect.left + const localY = screenY - rect.top + const realToScreen = compose(svgToScreenProjection, realToSvgProjection) + return { + x: (localX - realToScreen.e) / realToScreen.a, + y: (localY - realToScreen.f) / realToScreen.d, + } + } +} + +export function getSchematicPortCenter( + circuitJson: any[], + container: HTMLElement | null, + schematicPortId: string, + screenToReal: (screenX: number, screenY: number) => { x: number; y: number }, +): { x: number; y: number } | null { + const port = circuitJson.find( + (el) => + el.type === "schematic_port" && el.schematic_port_id === schematicPortId, + ) + if (port?.center) { + return { x: port.center.x, y: port.center.y } + } + + if (!container) return null + const el = container.querySelector( + `[data-schematic-port-id="${schematicPortId}"]`, + ) + if (!el) return null + + const rect = el.getBoundingClientRect() + return screenToReal(rect.left + rect.width / 2, rect.top + rect.height / 2) +} + +export function getSchematicPortAtScreen( + container: HTMLElement | null, + circuitJson: any[], + screenX: number, + screenY: number, + hitRadiusPx = SCHEMATIC_PORT_HIT_RADIUS_PX, +): string | null { + if (!container) return null + + let closest: { id: string; dist: number } | null = null + for (const node of Array.from( + container.querySelectorAll("[data-schematic-port-id]"), + )) { + const rect = node.getBoundingClientRect() + const cx = rect.left + rect.width / 2 + const cy = rect.top + rect.height / 2 + const dist = Math.hypot(cx - screenX, cy - screenY) + + if (dist < hitRadiusPx && (!closest || dist < closest.dist)) { + const id = node.getAttribute("data-schematic-port-id") + const resolved = id ? resolveSchematicPortId(circuitJson, id) : null + if (resolved) closest = { id: resolved, dist } + } + } + + return closest?.id ?? null +}