@tscircuit/schematic-viewer 2.0.24 → 2.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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/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"],"sourcesContent":["import {\n convertCircuitJsonToSchematicSvg,\n type ColorOverrides,\n} from \"circuit-to-svg\"\nimport { useChangeSchematicComponentLocationsInSvg } from \"lib/hooks/useChangeSchematicComponentLocationsInSvg\"\nimport { useChangeSchematicTracesForMovedComponents } from \"lib/hooks/useChangeSchematicTracesForMovedComponents\"\nimport { enableDebug } from \"lib/utils/debug\"\nimport { 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 type { CircuitJson } from \"circuit-json\"\nimport { zIndexMap } from \"../utils/z-index-map\"\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}\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}: Props) => {\n if (debug) {\n enableDebug()\n }\n const [editModeEnabled, setEditModeEnabled] = useState(defaultEditMode)\n const [snapToGrid, setSnapToGrid] = useState(true)\n const [isInteractionEnabled, setIsInteractionEnabled] = useState<boolean>(\n !clickToInteractEnabled,\n )\n const svgDivRef = useRef<HTMLDivElement>(null)\n const touchStartRef = useRef<{ x: number; y: number } | null>(null)\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>(circuitJson)\n\n const getCircuitHash = (circuitJson: CircuitJson) => {\n return `${circuitJson?.length || 0}_${(circuitJson as any)?.editCount || 0}`\n }\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 {\n ref: containerRef,\n cancelDrag,\n transform: svgToScreenProjection,\n } = useMouseMatrixTransform({\n onSetTransform(transform) {\n if (!svgDivRef.current) return\n svgDivRef.current.style.transform = transformToString(transform)\n },\n // @ts-ignore disabled is a valid prop but not typed\n enabled: isInteractionEnabled,\n })\n\n const { containerWidth, containerHeight } = useResizeHandling(containerRef)\n const svgString = useMemo(() => {\n if (!containerWidth || !containerHeight) return \"\"\n\n return convertCircuitJsonToSchematicSvg(circuitJson as any, {\n width: containerWidth,\n height: containerHeight || 720,\n grid: !debugGrid\n ? undefined\n : {\n cellSize: 1,\n labelCells: true,\n },\n colorOverrides,\n })\n }, [circuitJson, containerWidth, containerHeight])\n\n const containerBackgroundColor = useMemo(() => {\n const match = svgString.match(\n /<svg[^>]*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 { handleMouseDown, isDragging, activeEditEvent } = useComponentDragging(\n {\n onEditEvent: handleEditEvent,\n cancelDrag,\n realToSvgProjection,\n svgToScreenProjection,\n circuitJson,\n editEvents: editEventsWithUnappliedEditEvents,\n enabled: editModeEnabled && isInteractionEnabled,\n snapToGrid,\n },\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 const svgDiv = useMemo(\n () => (\n <div\n ref={svgDivRef}\n style={{\n pointerEvents: clickToInteractEnabled\n ? isInteractionEnabled\n ? \"auto\"\n : \"none\"\n : \"auto\",\n transformOrigin: \"0 0\",\n }}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>\n dangerouslySetInnerHTML={{ __html: svgString }}\n />\n ),\n [svgString, isInteractionEnabled, clickToInteractEnabled],\n )\n\n return (\n <div\n ref={containerRef}\n style={{\n position: \"relative\",\n backgroundColor: containerBackgroundColor,\n overflow: \"hidden\",\n cursor: isDragging\n ? \"grabbing\"\n : clickToInteractEnabled && !isInteractionEnabled\n ? \"pointer\"\n : \"grab\",\n minHeight: \"300px\",\n ...containerStyle,\n }}\n onMouseDown={(e) => {\n if (clickToInteractEnabled && !isInteractionEnabled) {\n e.preventDefault()\n e.stopPropagation()\n return\n }\n handleMouseDown(e)\n }}\n onMouseDownCapture={(e) => {\n if (clickToInteractEnabled && !isInteractionEnabled) {\n e.preventDefault()\n e.stopPropagation()\n return\n }\n }}\n onTouchStart={handleTouchStart}\n onTouchEnd={handleTouchEnd}\n >\n {!isInteractionEnabled && clickToInteractEnabled && (\n <div\n onClick={(e) => {\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 <div\n style={{\n backgroundColor: \"rgba(0, 0, 0, 0.8)\",\n color: \"white\",\n padding: \"12px 24px\",\n borderRadius: \"8px\",\n fontSize: \"16px\",\n fontFamily: \"sans-serif\",\n pointerEvents: \"none\",\n }}\n >\n {typeof window !== \"undefined\" &&\n (\"ontouchstart\" in window || navigator.maxTouchPoints > 0)\n ? \"Touch to Interact\"\n : \"Click to Interact\"}\n </div>\n </div>\n )}\n {editingEnabled && (\n <EditIcon\n active={editModeEnabled}\n onClick={() => setEditModeEnabled(!editModeEnabled)}\n />\n )}\n {editingEnabled && editModeEnabled && (\n <GridIcon\n active={snapToGrid}\n onClick={() => setSnapToGrid(!snapToGrid)}\n />\n )}\n {svgDiv}\n </div>\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 \"<g>\" 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<HTMLDivElement | null>\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<string | null>(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<string>()\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<HTMLDivElement | null>\n circuitJson: CircuitJson\n activeEditEvent: ManualEditEvent | null\n editEvents: ManualEditEvent[]\n}) => {\n // Keep track of the last known SVG content\n const lastSvgContentRef = useRef<string | null>(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) =>\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 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<HTMLElement>,\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 isDragging: boolean\n activeEditEvent: EditSchematicComponentLocationEventWithElement | null\n} => {\n const [activeEditEvent, setActiveEditEvent] =\n useState<EditSchematicComponentLocationEventWithElement | null>(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<EditSchematicComponentLocationEventWithElement | null>(null)\n\n // Store the latest positions of components being tracked\n const componentPositionsRef = useRef<Map<string, { x: number; y: number }>>(\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 handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n if (!enabled) return\n\n const target = e.target as SVGElement\n const componentGroup = target.closest(\n '[data-circuit-json-type=\"schematic_component\"]',\n )\n if (!componentGroup) return\n\n const schematic_component_id = componentGroup.getAttribute(\n \"data-schematic-component-id\",\n )\n if (!schematic_component_id) return\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\n\n dragStartPosRef.current = {\n x: e.clientX,\n y: e.clientY,\n }\n\n // Get the current position of the component\n // Check if we're already tracking this component\n let current_position: { x: number; y: number }\n const trackedPosition = componentPositionsRef.current.get(\n schematic_component_id,\n )\n\n if (trackedPosition) {\n // Use the tracked position from previous edits\n current_position = { ...trackedPosition }\n } else {\n // Calculate position based on component data and edit events\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 // Store this initial position\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 },\n [cancelDrag, enabled, circuitJson, editEvents],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!activeEditEventRef.current || !dragStartPosRef.current) return\n\n const screenDelta = {\n x: e.clientX - dragStartPosRef.current.x,\n y: e.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 handleMouseUp = useCallback(() => {\n if (!activeEditEventRef.current) return\n const finalEvent = {\n ...activeEditEventRef.current,\n in_progress: false,\n }\n\n // Update our stored position for this component\n componentPositionsRef.current.set(finalEvent.schematic_component_id, {\n ...finalEvent.new_center,\n })\n\n debug(\"handleMouseUp 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 useEffect(() => {\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mouseup\", handleMouseUp)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mouseup\", handleMouseUp)\n }\n }, [handleMouseMove, handleMouseUp])\n\n return {\n handleMouseDown,\n isDragging: !!activeEditEventRef.current,\n activeEditEvent: activeEditEvent,\n }\n}\n","export const zIndexMap = {\n schematicEditIcon: 50,\n schematicGridIcon: 49,\n clickToInteractOverlay: 100,\n}\n","import { zIndexMap } from \"../utils/z-index-map\"\n\nexport const EditIcon = ({\n onClick,\n active,\n}: { onClick: () => void; active: boolean }) => {\n return (\n <div\n onClick={onClick}\n style={{\n position: \"absolute\",\n top: \"16px\",\n right: \"16px\",\n backgroundColor: active ? \"#4CAF50\" : \"#fff\",\n color: active ? \"#fff\" : \"#000\",\n padding: \"8px\",\n borderRadius: \"4px\",\n cursor: \"pointer\",\n boxShadow: \"0 2px 4px rgba(0,0,0,0.1)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"4px\",\n zIndex: zIndexMap.schematicEditIcon,\n }}\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n >\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" />\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\" />\n </svg>\n </div>\n )\n}\n","import { zIndexMap } from \"../utils/z-index-map\"\n\nexport const GridIcon = ({\n onClick,\n active,\n}: { onClick: () => void; active: boolean }) => {\n return (\n <div\n onClick={onClick}\n style={{\n position: \"absolute\",\n top: \"56px\",\n right: \"16px\",\n backgroundColor: active ? \"#4CAF50\" : \"#fff\",\n color: active ? \"#fff\" : \"#000\",\n padding: \"8px\",\n borderRadius: \"4px\",\n cursor: \"pointer\",\n boxShadow: \"0 2px 4px rgba(0,0,0,0.1)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"4px\",\n zIndex: zIndexMap.schematicGridIcon,\n }}\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n >\n <path d=\"M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3zM14 14h7v7h-7z\" />\n </svg>\n </div>\n )\n}\n"],"mappings":"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","names":["useEffect","useRef","su","useEffect","useRef","useState","useEffect","su","useEffect","useRef","useState","compose","debug","su","jsx","jsx","jsxs","debug","useState","useRef","circuitJson","useEffect"]}
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/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/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"],"sourcesContent":["import {\n convertCircuitJsonToSchematicSvg,\n type ColorOverrides,\n} from \"circuit-to-svg\"\nimport { useChangeSchematicComponentLocationsInSvg } from \"lib/hooks/useChangeSchematicComponentLocationsInSvg\"\nimport { useChangeSchematicTracesForMovedComponents } from \"lib/hooks/useChangeSchematicTracesForMovedComponents\"\nimport { enableDebug } from \"lib/utils/debug\"\nimport { 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 type { CircuitJson } 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\"\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}\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}: Props) => {\n if (debug) {\n enableDebug()\n }\n const [showSpiceOverlay, setShowSpiceOverlay] = useState(false)\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 const spiceString = useMemo(() => {\n if (!spiceSimulationEnabled) return null\n try {\n return getSpiceFromCircuitJson(circuitJson)\n } catch (e) {\n console.error(\"Failed to generate SPICE string\", e)\n return null\n }\n }, [circuitJsonKey, spiceSimulationEnabled])\n\n const {\n plotData,\n nodes,\n isLoading: isSpiceSimLoading,\n error: spiceSimError,\n } = useSpiceSimulation(spiceString)\n\n const [editModeEnabled, setEditModeEnabled] = useState(defaultEditMode)\n const [snapToGrid, setSnapToGrid] = useState(true)\n const [isInteractionEnabled, setIsInteractionEnabled] = useState<boolean>(\n !clickToInteractEnabled,\n )\n const svgDivRef = useRef<HTMLDivElement>(null)\n const touchStartRef = useRef<{ x: number; y: number } | null>(null)\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>(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 {\n ref: containerRef,\n cancelDrag,\n transform: svgToScreenProjection,\n } = useMouseMatrixTransform({\n onSetTransform(transform) {\n if (!svgDivRef.current) return\n svgDivRef.current.style.transform = transformToString(transform)\n },\n // @ts-ignore disabled is a valid prop but not typed\n enabled: isInteractionEnabled && !showSpiceOverlay,\n })\n\n const { containerWidth, containerHeight } = useResizeHandling(containerRef)\n const svgString = useMemo(() => {\n if (!containerWidth || !containerHeight) return \"\"\n\n return convertCircuitJsonToSchematicSvg(circuitJson as any, {\n width: containerWidth,\n height: containerHeight || 720,\n grid: !debugGrid\n ? undefined\n : {\n cellSize: 1,\n labelCells: true,\n },\n colorOverrides,\n })\n }, [circuitJson, containerWidth, containerHeight])\n\n const containerBackgroundColor = useMemo(() => {\n const match = svgString.match(\n /<svg[^>]*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 { handleMouseDown, isDragging, activeEditEvent } = useComponentDragging(\n {\n onEditEvent: handleEditEvent,\n cancelDrag,\n realToSvgProjection,\n svgToScreenProjection,\n circuitJson,\n editEvents: editEventsWithUnappliedEditEvents,\n enabled: editModeEnabled && isInteractionEnabled && !showSpiceOverlay,\n snapToGrid,\n },\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 const svgDiv = useMemo(\n () => (\n <div\n ref={svgDivRef}\n style={{\n pointerEvents: clickToInteractEnabled\n ? isInteractionEnabled\n ? \"auto\"\n : \"none\"\n : \"auto\",\n transformOrigin: \"0 0\",\n }}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>\n dangerouslySetInnerHTML={{ __html: svgString }}\n />\n ),\n [svgString, isInteractionEnabled, clickToInteractEnabled],\n )\n\n return (\n <div\n ref={containerRef}\n style={{\n position: \"relative\",\n backgroundColor: containerBackgroundColor,\n overflow: \"hidden\",\n cursor: showSpiceOverlay\n ? \"auto\"\n : isDragging\n ? \"grabbing\"\n : clickToInteractEnabled && !isInteractionEnabled\n ? \"pointer\"\n : \"grab\",\n minHeight: \"300px\",\n ...containerStyle,\n }}\n onWheelCapture={(e) => {\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 handleMouseDown(e)\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 <div\n onClick={(e) => {\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 <div\n style={{\n backgroundColor: \"rgba(0, 0, 0, 0.8)\",\n color: \"white\",\n padding: \"12px 24px\",\n borderRadius: \"8px\",\n fontSize: \"16px\",\n fontFamily: \"sans-serif\",\n pointerEvents: \"none\",\n }}\n >\n {typeof window !== \"undefined\" &&\n (\"ontouchstart\" in window || navigator.maxTouchPoints > 0)\n ? \"Touch to Interact\"\n : \"Click to Interact\"}\n </div>\n </div>\n )}\n {editingEnabled && (\n <EditIcon\n active={editModeEnabled}\n onClick={() => setEditModeEnabled(!editModeEnabled)}\n />\n )}\n {editingEnabled && editModeEnabled && (\n <GridIcon\n active={snapToGrid}\n onClick={() => setSnapToGrid(!snapToGrid)}\n />\n )}\n {spiceSimulationEnabled && (\n <SpiceSimulationIcon onClick={() => setShowSpiceOverlay(true)} />\n )}\n {showSpiceOverlay && (\n <SpiceSimulationOverlay\n spiceString={spiceString}\n onClose={() => setShowSpiceOverlay(false)}\n plotData={plotData}\n nodes={nodes}\n isLoading={isSpiceSimLoading}\n error={spiceSimError}\n />\n )}\n {svgDiv}\n </div>\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 \"<g>\" 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<HTMLDivElement | null>\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<string | null>(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<string>()\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<HTMLDivElement | null>\n circuitJson: CircuitJson\n activeEditEvent: ManualEditEvent | null\n editEvents: ManualEditEvent[]\n}) => {\n // Keep track of the last known SVG content\n const lastSvgContentRef = useRef<string | null>(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) =>\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 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<HTMLElement>,\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 isDragging: boolean\n activeEditEvent: EditSchematicComponentLocationEventWithElement | null\n} => {\n const [activeEditEvent, setActiveEditEvent] =\n useState<EditSchematicComponentLocationEventWithElement | null>(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<EditSchematicComponentLocationEventWithElement | null>(null)\n\n // Store the latest positions of components being tracked\n const componentPositionsRef = useRef<Map<string, { x: number; y: number }>>(\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 handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n if (!enabled) return\n\n const target = e.target as SVGElement\n const componentGroup = target.closest(\n '[data-circuit-json-type=\"schematic_component\"]',\n )\n if (!componentGroup) return\n\n const schematic_component_id = componentGroup.getAttribute(\n \"data-schematic-component-id\",\n )\n if (!schematic_component_id) return\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\n\n dragStartPosRef.current = {\n x: e.clientX,\n y: e.clientY,\n }\n\n // Get the current position of the component\n // Check if we're already tracking this component\n let current_position: { x: number; y: number }\n const trackedPosition = componentPositionsRef.current.get(\n schematic_component_id,\n )\n\n if (trackedPosition) {\n // Use the tracked position from previous edits\n current_position = { ...trackedPosition }\n } else {\n // Calculate position based on component data and edit events\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 // Store this initial position\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 },\n [cancelDrag, enabled, circuitJson, editEvents],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!activeEditEventRef.current || !dragStartPosRef.current) return\n\n const screenDelta = {\n x: e.clientX - dragStartPosRef.current.x,\n y: e.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 handleMouseUp = useCallback(() => {\n if (!activeEditEventRef.current) return\n const finalEvent = {\n ...activeEditEventRef.current,\n in_progress: false,\n }\n\n // Update our stored position for this component\n componentPositionsRef.current.set(finalEvent.schematic_component_id, {\n ...finalEvent.new_center,\n })\n\n debug(\"handleMouseUp 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 useEffect(() => {\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mouseup\", handleMouseUp)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mouseup\", handleMouseUp)\n }\n }, [handleMouseMove, handleMouseUp])\n\n return {\n handleMouseDown,\n isDragging: !!activeEditEventRef.current,\n activeEditEvent: activeEditEvent,\n }\n}\n","export const zIndexMap = {\n schematicEditIcon: 50,\n schematicGridIcon: 49,\n spiceSimulationIcon: 51,\n clickToInteractOverlay: 100,\n}\n","import { zIndexMap } from \"../utils/z-index-map\"\n\nexport const EditIcon = ({\n onClick,\n active,\n}: { onClick: () => void; active: boolean }) => {\n return (\n <div\n onClick={onClick}\n style={{\n position: \"absolute\",\n top: \"16px\",\n right: \"16px\",\n backgroundColor: active ? \"#4CAF50\" : \"#fff\",\n color: active ? \"#fff\" : \"#000\",\n padding: \"8px\",\n borderRadius: \"4px\",\n cursor: \"pointer\",\n boxShadow: \"0 2px 4px rgba(0,0,0,0.1)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"4px\",\n zIndex: zIndexMap.schematicEditIcon,\n }}\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n >\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" />\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\" />\n </svg>\n </div>\n )\n}\n","import { zIndexMap } from \"../utils/z-index-map\"\n\nexport const GridIcon = ({\n onClick,\n active,\n}: { onClick: () => void; active: boolean }) => {\n return (\n <div\n onClick={onClick}\n style={{\n position: \"absolute\",\n top: \"56px\",\n right: \"16px\",\n backgroundColor: active ? \"#4CAF50\" : \"#fff\",\n color: active ? \"#fff\" : \"#000\",\n padding: \"8px\",\n borderRadius: \"4px\",\n cursor: \"pointer\",\n boxShadow: \"0 2px 4px rgba(0,0,0,0.1)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"4px\",\n zIndex: zIndexMap.schematicGridIcon,\n }}\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n >\n <path d=\"M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3zM14 14h7v7h-7z\" />\n </svg>\n </div>\n )\n}\n","export const SpiceIcon = () => (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M3 12h2.5l2.5-9 4 18 4-9h5.5\" />\n </svg>\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 <div\n onClick={onClick}\n style={{\n position: \"absolute\",\n top: \"16px\",\n right: \"56px\",\n backgroundColor: \"#fff\",\n color: \"#000\",\n padding: \"8px\",\n borderRadius: \"4px\",\n cursor: \"pointer\",\n boxShadow: \"0 2px 4px rgba(0,0,0,0.1)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"4px\",\n zIndex: zIndexMap.spiceSimulationIcon,\n }}\n >\n <SpiceIcon />\n </div>\n )\n}\n","import {\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}: {\n plotData: PlotPoint[]\n nodes: string[]\n isLoading: boolean\n error: string | null\n}) => {\n if (isLoading) {\n return (\n <div\n style={{\n height: \"300px\",\n width: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n Running simulation...\n </div>\n )\n }\n\n if (error) {\n return (\n <div\n style={{\n height: \"300px\",\n width: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n color: \"red\",\n }}\n >\n Error: {error}\n </div>\n )\n }\n\n if (plotData.length === 0) {\n return (\n <div\n style={{\n height: \"300px\",\n width: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n No data to plot. Check simulation output or SPICE netlist.\n </div>\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)\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: \"Voltage\",\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 <div style={{ position: \"relative\", height: \"300px\", width: \"100%\" }}>\n <Line options={options} data={chartData} />\n </div>\n )\n}\n","import { SpicePlot } from \"./SpicePlot\"\nimport type { PlotPoint } from \"../hooks/useSpiceSimulation\"\n\ninterface SpiceSimulationOverlayProps {\n spiceString: string | null\n onClose: () => void\n plotData: PlotPoint[]\n nodes: string[]\n isLoading: boolean\n error: string | null\n}\n\nexport const SpiceSimulationOverlay = ({\n spiceString,\n onClose,\n plotData,\n nodes,\n isLoading,\n error,\n}: SpiceSimulationOverlayProps) => {\n return (\n <div\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: \"rgba(0, 0, 0, 0.5)\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n zIndex: 1002,\n fontFamily: \"sans-serif\",\n }}\n >\n <div\n style={{\n backgroundColor: \"white\",\n padding: \"24px\",\n borderRadius: \"12px\",\n width: \"90%\",\n maxWidth: \"900px\",\n boxShadow: \"0 4px 20px rgba(0, 0, 0, 0.15)\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"24px\",\n borderBottom: \"1px solid #eee\",\n paddingBottom: \"16px\",\n }}\n >\n <h2\n style={{\n margin: 0,\n fontSize: \"22px\",\n fontWeight: 600,\n color: \"#333\",\n }}\n >\n SPICE Simulation\n </h2>\n <button\n onClick={onClose}\n style={{\n background: \"none\",\n border: \"none\",\n fontSize: \"28px\",\n cursor: \"pointer\",\n color: \"#888\",\n padding: 0,\n lineHeight: 1,\n }}\n >\n &times;\n </button>\n </div>\n <div>\n <SpicePlot\n plotData={plotData}\n nodes={nodes}\n isLoading={isLoading}\n error={error}\n />\n </div>\n <div style={{ marginTop: \"24px\" }}>\n <h3\n style={{\n marginTop: 0,\n marginBottom: \"12px\",\n fontSize: \"18px\",\n fontWeight: 600,\n color: \"#333\",\n }}\n >\n SPICE Netlist\n </h3>\n <pre\n style={{\n backgroundColor: \"#fafafa\",\n padding: \"16px\",\n borderRadius: \"6px\",\n maxHeight: \"150px\",\n overflowY: \"auto\",\n border: \"1px solid #eee\",\n color: \"#333\",\n fontSize: \"13px\",\n fontFamily: \"monospace\",\n }}\n >\n {spiceString}\n </pre>\n </div>\n </div>\n </div>\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<string, number[]> = {}\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.map((n) =>\n n.replace(/v\\(([^)]+)\\)/i, \"$1\"),\n )\n\n const plotData: PlotPoint[] = timeValues.map((t: number, i: number) => {\n const point: PlotPoint = { name: t.toExponential(2) }\n probedVariables.forEach((variable, j) => {\n point[plotableNodes[j]] = 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<PlotPoint[]>([])\n const [nodes, setNodes] = useState<string[]>([])\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<string | null>(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<WorkerMessage>) => {\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 const getSpiceFromCircuitJson = (circuitJson: CircuitJson): 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<string>()\n const capacitorNodes = new Set<string>()\n\n for (const line of componentLines) {\n const parts = line.trim().split(/\\s+/)\n if (parts.length < 3) continue\n\n const componentType = parts[0][0].toUpperCase()\n let nodesOnLine: string[] = []\n\n if ([\"R\", \"C\", \"L\", \"V\", \"I\", \"D\"].includes(componentType)) {\n nodesOnLine = parts.slice(1, 3)\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 probeNodes = Array.from(allNodes).map((node) => `V(${node})`)\n const probeLine =\n probeNodes.length > 0 ? `.probe ${probeNodes.join(\" \")}` : \"\"\n\n const tranLine = \".tran 0.1ms 50ms 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"],"mappings":"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","names":["useEffect","useRef","su","useEffect","useRef","useState","useEffect","su","useEffect","useRef","useState","compose","debug","su","jsx","jsx","jsx","jsx","jsxs","jsx","jsxs","useState","useEffect","useState","useEffect","jsx","jsxs","debug","useState","circuitJson","useRef","useEffect"]}
@@ -0,0 +1 @@
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 <filename> <var1> ...' or '.probe <var1> ...' instead."):new Error("No '.probe' or 'wrdata' command found in SPICE file. Use 'wrdata <filename> <var1> ...' 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})}};
@@ -0,0 +1,77 @@
1
+ import { renderToCircuitJson } from "lib/dev/render-to-circuit-json"
2
+ import { SchematicViewer } from "lib/index"
3
+ import type { CircuitJson } from "circuit-json"
4
+ import { sel } from "tscircuit"
5
+
6
+ const circuit = (
7
+ <board width={16} height={16}>
8
+ <chip
9
+ name="V1"
10
+ footprint="sot23"
11
+ pinLabels={{
12
+ pin1: "VOUT",
13
+ pin2: "GND",
14
+ }}
15
+ pinAttributes={{
16
+ VOUT: { providesPower: true, providesVoltage: 5 },
17
+ GND: { providesGround: true },
18
+ }}
19
+ connections={{
20
+ pin3: "net.NC",
21
+ }}
22
+ />
23
+
24
+ <resistor
25
+ name="R1"
26
+ resistance="1k"
27
+ footprint="0402"
28
+ pcbX={4}
29
+ pcbY={4}
30
+ schX={-2}
31
+ schY={2}
32
+ />
33
+ <resistor
34
+ name="R2"
35
+ resistance="2k"
36
+ footprint="0402"
37
+ pcbX={-4}
38
+ pcbY={-4}
39
+ schX={0}
40
+ schY={4}
41
+ />
42
+ <capacitor
43
+ name="C1"
44
+ capacitance="10uF"
45
+ footprint="0402"
46
+ pcbX={0}
47
+ pcbY={-2}
48
+ schX={0}
49
+ schY={2}
50
+ />
51
+
52
+ <trace from={"net.VOUT"} to={sel.R1.pin1} />
53
+ <trace from={".V1 > .VOUT"} to={"net.VOUT"} />
54
+ <trace from={sel.R1.pin2} to={sel.R2.pin1} />
55
+ <trace from={sel.R2.pin2} to={"net.GND"} />
56
+ <trace from={"net.GND"} to={".V1 > .GND"} />
57
+
58
+ <trace from={sel.C1.pin1} to={sel.R1.pin2} />
59
+ <trace from={sel.C1.pin2} to={"net.GND"} />
60
+ </board>
61
+ )
62
+
63
+ export default () => {
64
+ const circuitJson = renderToCircuitJson(circuit) as CircuitJson
65
+
66
+ return (
67
+ <div style={{ position: "relative", height: "100%" }}>
68
+ <SchematicViewer
69
+ circuitJson={circuitJson}
70
+ containerStyle={{ height: "100%" }}
71
+ debugGrid
72
+ editingEnabled
73
+ spiceSimulationEnabled
74
+ />
75
+ </div>
76
+ )
77
+ }
@@ -18,7 +18,11 @@ import type { ManualEditEvent } from "../types/edit-events"
18
18
  import { EditIcon } from "./EditIcon"
19
19
  import { GridIcon } from "./GridIcon"
20
20
  import type { CircuitJson } from "circuit-json"
21
+ import { SpiceSimulationIcon } from "./SpiceSimulationIcon"
22
+ import { SpiceSimulationOverlay } from "./SpiceSimulationOverlay"
21
23
  import { zIndexMap } from "../utils/z-index-map"
24
+ import { useSpiceSimulation } from "../hooks/useSpiceSimulation"
25
+ import { getSpiceFromCircuitJson } from "../utils/spice-utils"
22
26
 
23
27
  interface Props {
24
28
  circuitJson: CircuitJson
@@ -31,6 +35,7 @@ interface Props {
31
35
  debug?: boolean
32
36
  clickToInteractEnabled?: boolean
33
37
  colorOverrides?: ColorOverrides
38
+ spiceSimulationEnabled?: boolean
34
39
  }
35
40
 
36
41
  export const SchematicViewer = ({
@@ -44,10 +49,39 @@ export const SchematicViewer = ({
44
49
  debug = false,
45
50
  clickToInteractEnabled = false,
46
51
  colorOverrides,
52
+ spiceSimulationEnabled = false,
47
53
  }: Props) => {
48
54
  if (debug) {
49
55
  enableDebug()
50
56
  }
57
+ const [showSpiceOverlay, setShowSpiceOverlay] = useState(false)
58
+
59
+ const getCircuitHash = (circuitJson: CircuitJson) => {
60
+ return `${circuitJson?.length || 0}_${(circuitJson as any)?.editCount || 0}`
61
+ }
62
+
63
+ const circuitJsonKey = useMemo(
64
+ () => getCircuitHash(circuitJson),
65
+ [circuitJson],
66
+ )
67
+
68
+ const spiceString = useMemo(() => {
69
+ if (!spiceSimulationEnabled) return null
70
+ try {
71
+ return getSpiceFromCircuitJson(circuitJson)
72
+ } catch (e) {
73
+ console.error("Failed to generate SPICE string", e)
74
+ return null
75
+ }
76
+ }, [circuitJsonKey, spiceSimulationEnabled])
77
+
78
+ const {
79
+ plotData,
80
+ nodes,
81
+ isLoading: isSpiceSimLoading,
82
+ error: spiceSimError,
83
+ } = useSpiceSimulation(spiceString)
84
+
51
85
  const [editModeEnabled, setEditModeEnabled] = useState(defaultEditMode)
52
86
  const [snapToGrid, setSnapToGrid] = useState(true)
53
87
  const [isInteractionEnabled, setIsInteractionEnabled] = useState<boolean>(
@@ -85,10 +119,6 @@ export const SchematicViewer = ({
85
119
  >([])
86
120
  const circuitJsonRef = useRef<CircuitJson>(circuitJson)
87
121
 
88
- const getCircuitHash = (circuitJson: CircuitJson) => {
89
- return `${circuitJson?.length || 0}_${(circuitJson as any)?.editCount || 0}`
90
- }
91
-
92
122
  useEffect(() => {
93
123
  const circuitHash = getCircuitHash(circuitJson)
94
124
  const circuitHashRef = getCircuitHash(circuitJsonRef.current)
@@ -109,7 +139,7 @@ export const SchematicViewer = ({
109
139
  svgDivRef.current.style.transform = transformToString(transform)
110
140
  },
111
141
  // @ts-ignore disabled is a valid prop but not typed
112
- enabled: isInteractionEnabled,
142
+ enabled: isInteractionEnabled && !showSpiceOverlay,
113
143
  })
114
144
 
115
145
  const { containerWidth, containerHeight } = useResizeHandling(containerRef)
@@ -169,7 +199,7 @@ export const SchematicViewer = ({
169
199
  svgToScreenProjection,
170
200
  circuitJson,
171
201
  editEvents: editEventsWithUnappliedEditEvents,
172
- enabled: editModeEnabled && isInteractionEnabled,
202
+ enabled: editModeEnabled && isInteractionEnabled && !showSpiceOverlay,
173
203
  snapToGrid,
174
204
  },
175
205
  )
@@ -215,14 +245,21 @@ export const SchematicViewer = ({
215
245
  position: "relative",
216
246
  backgroundColor: containerBackgroundColor,
217
247
  overflow: "hidden",
218
- cursor: isDragging
219
- ? "grabbing"
220
- : clickToInteractEnabled && !isInteractionEnabled
221
- ? "pointer"
222
- : "grab",
248
+ cursor: showSpiceOverlay
249
+ ? "auto"
250
+ : isDragging
251
+ ? "grabbing"
252
+ : clickToInteractEnabled && !isInteractionEnabled
253
+ ? "pointer"
254
+ : "grab",
223
255
  minHeight: "300px",
224
256
  ...containerStyle,
225
257
  }}
258
+ onWheelCapture={(e) => {
259
+ if (showSpiceOverlay) {
260
+ e.stopPropagation()
261
+ }
262
+ }}
226
263
  onMouseDown={(e) => {
227
264
  if (clickToInteractEnabled && !isInteractionEnabled) {
228
265
  e.preventDefault()
@@ -238,8 +275,14 @@ export const SchematicViewer = ({
238
275
  return
239
276
  }
240
277
  }}
241
- onTouchStart={handleTouchStart}
242
- onTouchEnd={handleTouchEnd}
278
+ onTouchStart={(e) => {
279
+ if (showSpiceOverlay) return
280
+ handleTouchStart(e)
281
+ }}
282
+ onTouchEnd={(e) => {
283
+ if (showSpiceOverlay) return
284
+ handleTouchEnd(e)
285
+ }}
243
286
  >
244
287
  {!isInteractionEnabled && clickToInteractEnabled && (
245
288
  <div
@@ -290,6 +333,19 @@ export const SchematicViewer = ({
290
333
  onClick={() => setSnapToGrid(!snapToGrid)}
291
334
  />
292
335
  )}
336
+ {spiceSimulationEnabled && (
337
+ <SpiceSimulationIcon onClick={() => setShowSpiceOverlay(true)} />
338
+ )}
339
+ {showSpiceOverlay && (
340
+ <SpiceSimulationOverlay
341
+ spiceString={spiceString}
342
+ onClose={() => setShowSpiceOverlay(false)}
343
+ plotData={plotData}
344
+ nodes={nodes}
345
+ isLoading={isSpiceSimLoading}
346
+ error={spiceSimError}
347
+ />
348
+ )}
293
349
  {svgDiv}
294
350
  </div>
295
351
  )
@@ -0,0 +1,14 @@
1
+ export const SpiceIcon = () => (
2
+ <svg
3
+ width="16"
4
+ height="16"
5
+ viewBox="0 0 24 24"
6
+ fill="none"
7
+ stroke="currentColor"
8
+ strokeWidth="2"
9
+ strokeLinecap="round"
10
+ strokeLinejoin="round"
11
+ >
12
+ <path d="M3 12h2.5l2.5-9 4 18 4-9h5.5" />
13
+ </svg>
14
+ )