@tscircuit/schematic-viewer 2.0.70 → 2.0.72

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.d.ts CHANGED
@@ -59,7 +59,11 @@ interface Props {
59
59
  width?: number;
60
60
  height?: number;
61
61
  className?: string;
62
+ /** Whether to hide the schematic and render only the simulation graph. Defaults to false. */
63
+ hideSchematic?: boolean;
64
+ /** Called when the active simulation changes. */
65
+ onSimulationChange?: (simulationExperimentId: string) => void;
62
66
  }
63
- declare const AnalogSimulationViewer: ({ circuitJson: inputCircuitJson, containerStyle, colorOverrides, width, height, className, }: Props) => react.JSX.Element;
67
+ declare const AnalogSimulationViewer: ({ circuitJson: inputCircuitJson, containerStyle, colorOverrides, width, height, className, hideSchematic, onSimulationChange, }: Props) => react.JSX.Element;
64
68
 
65
69
  export { AnalogSimulationViewer, MouseTracker, SchematicViewer, useMouseEventsOverBoundingBox };
package/dist/index.js CHANGED
@@ -920,7 +920,7 @@ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
920
920
  // package.json
921
921
  var package_default = {
922
922
  name: "@tscircuit/schematic-viewer",
923
- version: "2.0.69",
923
+ version: "2.0.71",
924
924
  main: "dist/index.js",
925
925
  type: "module",
926
926
  scripts: {
@@ -2357,12 +2357,194 @@ var SchematicViewer = ({
2357
2357
 
2358
2358
  // lib/components/AnalogSimulationViewer.tsx
2359
2359
  import {
2360
- convertCircuitJsonToSchematicSimulationSvg
2360
+ convertCircuitJsonToSchematicSimulationSvg,
2361
+ convertCircuitJsonToSimulationGraphSvg
2361
2362
  } from "circuit-to-svg";
2362
- import { useEffect as useEffect12, useState as useState7, useMemo as useMemo5, useRef as useRef9 } from "react";
2363
+ import { useEffect as useEffect12, useState as useState8, useMemo as useMemo5, useRef as useRef9 } from "react";
2363
2364
  import { useMouseMatrixTransform as useMouseMatrixTransform2 } from "use-mouse-matrix-transform";
2364
2365
  import { toString as transformToString2 } from "transformation-matrix";
2365
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
2366
+
2367
+ // lib/components/AnalogSimulationSelector.tsx
2368
+ import * as DropdownMenu3 from "@radix-ui/react-dropdown-menu";
2369
+ import { useState as useState7 } from "react";
2370
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
2371
+ var FONT_FAMILY3 = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2372
+ var contentStyles3 = {
2373
+ backgroundColor: "#ffffff",
2374
+ color: "#111111",
2375
+ borderRadius: 8,
2376
+ boxShadow: "0 6px 24px rgba(0,0,0,0.12), 0 1px 3px rgba(0,0,0,0.08)",
2377
+ border: "1px solid #e5e7eb",
2378
+ padding: 4,
2379
+ minWidth: 220,
2380
+ maxWidth: 360,
2381
+ maxHeight: 320,
2382
+ overflowY: "auto",
2383
+ fontSize: 13,
2384
+ fontFamily: FONT_FAMILY3,
2385
+ outline: "none",
2386
+ zIndex: zIndexMap.viewMenu
2387
+ };
2388
+ var itemStyles3 = {
2389
+ display: "flex",
2390
+ alignItems: "center",
2391
+ gap: 8,
2392
+ padding: "7px 10px 7px 8px",
2393
+ borderRadius: 6,
2394
+ cursor: "pointer",
2395
+ outline: "none",
2396
+ userSelect: "none",
2397
+ color: "#111111",
2398
+ fontSize: 13,
2399
+ fontFamily: FONT_FAMILY3
2400
+ };
2401
+ var iconSlotStyles3 = {
2402
+ width: 16,
2403
+ height: 16,
2404
+ flexShrink: 0,
2405
+ display: "inline-flex",
2406
+ alignItems: "center",
2407
+ justifyContent: "center",
2408
+ color: "#111111"
2409
+ };
2410
+ var ellipsisStyles2 = {
2411
+ overflow: "hidden",
2412
+ textOverflow: "ellipsis",
2413
+ whiteSpace: "nowrap"
2414
+ };
2415
+ var MENU_CSS2 = `
2416
+ .sv-simulation-item[data-highlighted], .sv-simulation-item:hover { background-color: #f1f3f5; }
2417
+ .sv-simulation-chevron { transition: transform 0.2s ease; }
2418
+ [data-state="open"] > .sv-simulation-chevron { transform: rotate(180deg); }
2419
+ `;
2420
+ var CheckIcon3 = () => /* @__PURE__ */ jsx10(
2421
+ "svg",
2422
+ {
2423
+ width: "14",
2424
+ height: "14",
2425
+ viewBox: "0 0 24 24",
2426
+ fill: "none",
2427
+ stroke: "currentColor",
2428
+ strokeWidth: "2.5",
2429
+ strokeLinecap: "round",
2430
+ strokeLinejoin: "round",
2431
+ "aria-hidden": "true",
2432
+ children: /* @__PURE__ */ jsx10("path", { d: "M20 6 9 17l-5-5" })
2433
+ }
2434
+ );
2435
+ var ChevronDownIcon2 = ({ className }) => /* @__PURE__ */ jsx10(
2436
+ "svg",
2437
+ {
2438
+ className,
2439
+ width: "14",
2440
+ height: "14",
2441
+ viewBox: "0 0 24 24",
2442
+ fill: "none",
2443
+ stroke: "currentColor",
2444
+ strokeWidth: "2",
2445
+ strokeLinecap: "round",
2446
+ strokeLinejoin: "round",
2447
+ style: { opacity: 0.6, flexShrink: 0 },
2448
+ "aria-hidden": "true",
2449
+ children: /* @__PURE__ */ jsx10("path", { d: "m6 9 6 6 6-6" })
2450
+ }
2451
+ );
2452
+ var getSimulationLabels = (simulations) => {
2453
+ const nameCounts = /* @__PURE__ */ new Map();
2454
+ return simulations.map((simulation) => {
2455
+ const count = (nameCounts.get(simulation.name) ?? 0) + 1;
2456
+ nameCounts.set(simulation.name, count);
2457
+ return {
2458
+ simulation,
2459
+ label: count === 1 ? simulation.name : `${simulation.name} (${count})`
2460
+ };
2461
+ });
2462
+ };
2463
+ var AnalogSimulationSelector = ({
2464
+ simulations,
2465
+ selectedSimulationExperimentId,
2466
+ onSelectSimulation
2467
+ }) => {
2468
+ const [open, setOpen] = useState7(false);
2469
+ if (simulations.length <= 1) return null;
2470
+ const simulationLabels = getSimulationLabels(simulations);
2471
+ const selectedSimulation = simulationLabels.find(
2472
+ ({ simulation }) => simulation.simulation_experiment_id === selectedSimulationExperimentId
2473
+ );
2474
+ const selectedLabel = selectedSimulation?.label ?? "Select simulation";
2475
+ return /* @__PURE__ */ jsxs7(Fragment4, { children: [
2476
+ /* @__PURE__ */ jsx10("style", { children: MENU_CSS2 }),
2477
+ /* @__PURE__ */ jsxs7(DropdownMenu3.Root, { open, onOpenChange: setOpen, modal: false, children: [
2478
+ /* @__PURE__ */ jsx10(DropdownMenu3.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs7(
2479
+ "button",
2480
+ {
2481
+ type: "button",
2482
+ title: selectedLabel,
2483
+ onPointerDown: (event) => event.stopPropagation(),
2484
+ style: {
2485
+ position: "absolute",
2486
+ top: "16px",
2487
+ left: "16px",
2488
+ display: "flex",
2489
+ alignItems: "center",
2490
+ gap: "6px",
2491
+ maxWidth: "280px",
2492
+ padding: "8px 12px",
2493
+ backgroundColor: "#ffffff",
2494
+ color: "#000000",
2495
+ border: "none",
2496
+ outline: "none",
2497
+ borderRadius: "4px",
2498
+ cursor: "pointer",
2499
+ boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
2500
+ fontSize: "13px",
2501
+ fontFamily: FONT_FAMILY3,
2502
+ zIndex: zIndexMap.viewMenuIcon
2503
+ },
2504
+ children: [
2505
+ /* @__PURE__ */ jsx10("span", { style: { color: "#888888", flexShrink: 0 }, children: "Simulation:" }),
2506
+ /* @__PURE__ */ jsx10("span", { style: { ...ellipsisStyles2, minWidth: 0 }, children: selectedLabel }),
2507
+ /* @__PURE__ */ jsx10(ChevronDownIcon2, { className: "sv-simulation-chevron" })
2508
+ ]
2509
+ }
2510
+ ) }),
2511
+ /* @__PURE__ */ jsx10(DropdownMenu3.Portal, { children: /* @__PURE__ */ jsx10(
2512
+ DropdownMenu3.Content,
2513
+ {
2514
+ style: contentStyles3,
2515
+ side: "bottom",
2516
+ align: "start",
2517
+ sideOffset: 8,
2518
+ collisionPadding: 10,
2519
+ children: simulationLabels.map(({ simulation, label }) => {
2520
+ const selected = simulation.simulation_experiment_id === selectedSimulationExperimentId;
2521
+ return /* @__PURE__ */ jsxs7(
2522
+ DropdownMenu3.Item,
2523
+ {
2524
+ className: "sv-simulation-item",
2525
+ style: itemStyles3,
2526
+ title: label,
2527
+ onSelect: (event) => event.preventDefault(),
2528
+ onPointerUp: () => {
2529
+ onSelectSimulation(simulation.simulation_experiment_id);
2530
+ setOpen(false);
2531
+ },
2532
+ children: [
2533
+ /* @__PURE__ */ jsx10("span", { style: iconSlotStyles3, children: selected && /* @__PURE__ */ jsx10(CheckIcon3, {}) }),
2534
+ /* @__PURE__ */ jsx10("span", { style: { ...ellipsisStyles2, minWidth: 0 }, children: label })
2535
+ ]
2536
+ },
2537
+ simulation.simulation_experiment_id
2538
+ );
2539
+ })
2540
+ }
2541
+ ) })
2542
+ ] })
2543
+ ] });
2544
+ };
2545
+
2546
+ // lib/components/AnalogSimulationViewer.tsx
2547
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
2366
2548
  var DEFAULT_RENDER_WIDTH = 1200;
2367
2549
  var DEFAULT_RENDER_ASPECT_RATIO = 1;
2368
2550
  var AnalogSimulationViewer = ({
@@ -2371,18 +2553,20 @@ var AnalogSimulationViewer = ({
2371
2553
  colorOverrides,
2372
2554
  width,
2373
2555
  height,
2374
- className
2556
+ className,
2557
+ hideSchematic = false,
2558
+ onSimulationChange
2375
2559
  }) => {
2376
- const [circuitJson, setCircuitJson] = useState7(null);
2377
- const [isLoading, setIsLoading] = useState7(true);
2378
- const [error, setError] = useState7(null);
2379
- const [svgObjectUrl, setSvgObjectUrl] = useState7(null);
2560
+ const [circuitJson, setCircuitJson] = useState8(null);
2561
+ const [isLoading, setIsLoading] = useState8(true);
2562
+ const [error, setError] = useState8(null);
2563
+ const [svgObjectUrl, setSvgObjectUrl] = useState8(null);
2380
2564
  const containerRef = useRef9(null);
2381
2565
  const imgRef = useRef9(null);
2382
2566
  const { containerWidth } = useResizeHandling(
2383
2567
  containerRef
2384
2568
  );
2385
- const [isDragging, setIsDragging] = useState7(false);
2569
+ const [isDragging, setIsDragging] = useState8(false);
2386
2570
  const {
2387
2571
  ref: transformRef,
2388
2572
  cancelDrag: _cancelDrag,
@@ -2403,36 +2587,55 @@ var AnalogSimulationViewer = ({
2403
2587
  setCircuitJson(inputCircuitJson);
2404
2588
  setIsLoading(false);
2405
2589
  }, [inputCircuitJson]);
2406
- const simulationExperimentId = useMemo5(() => {
2407
- if (!circuitJson) return null;
2408
- const simulationElement = circuitJson.find(
2409
- (el) => el.type === "simulation_experiment"
2590
+ const simulationExperiments = useMemo5(() => {
2591
+ if (!circuitJson) return [];
2592
+ return circuitJson.filter(
2593
+ (element) => element.type === "simulation_experiment"
2410
2594
  );
2411
- return simulationElement?.simulation_experiment_id || null;
2412
2595
  }, [circuitJson]);
2596
+ const [selectedSimulationExperimentId, setSelectedSimulationExperimentId] = useState8(null);
2597
+ const simulationExperimentId = (selectedSimulationExperimentId && simulationExperiments.some(
2598
+ (simulation) => simulation.simulation_experiment_id === selectedSimulationExperimentId
2599
+ ) ? selectedSimulationExperimentId : simulationExperiments[0]?.simulation_experiment_id) ?? null;
2600
+ useEffect12(() => {
2601
+ if (simulationExperimentId !== selectedSimulationExperimentId) {
2602
+ setSelectedSimulationExperimentId(simulationExperimentId);
2603
+ }
2604
+ }, [simulationExperimentId, selectedSimulationExperimentId]);
2605
+ const handleSelectSimulation = (nextSimulationExperimentId) => {
2606
+ setSelectedSimulationExperimentId(nextSimulationExperimentId);
2607
+ onSimulationChange?.(nextSimulationExperimentId);
2608
+ };
2413
2609
  const simulationVoltageGraphIds = useMemo5(() => {
2414
2610
  if (!circuitJson) return [];
2415
- return circuitJson.filter((el) => el.type === "simulation_transient_voltage_graph").map((el) => el.simulation_transient_voltage_graph_id);
2416
- }, [circuitJson]);
2611
+ return circuitJson.flatMap(
2612
+ (element) => element.type === "simulation_transient_voltage_graph" && element.simulation_experiment_id === simulationExperimentId ? [element.simulation_transient_voltage_graph_id] : []
2613
+ );
2614
+ }, [circuitJson, simulationExperimentId]);
2417
2615
  const simulationCurrentGraphIds = useMemo5(() => {
2418
2616
  if (!circuitJson) return [];
2419
- return circuitJson.filter((el) => el.type === "simulation_transient_current_graph").map((el) => el.simulation_transient_current_graph_id);
2420
- }, [circuitJson]);
2617
+ return circuitJson.flatMap(
2618
+ (element) => element.type === "simulation_transient_current_graph" && element.simulation_experiment_id === simulationExperimentId ? [element.simulation_transient_current_graph_id] : []
2619
+ );
2620
+ }, [circuitJson, simulationExperimentId]);
2421
2621
  const simulationSvg = useMemo5(() => {
2422
2622
  if (!circuitJson || !effectiveWidth || !effectiveHeight || !simulationExperimentId)
2423
2623
  return "";
2424
2624
  try {
2425
- return convertCircuitJsonToSchematicSimulationSvg({
2625
+ const simulationOptions = {
2426
2626
  circuitJson,
2427
2627
  simulation_experiment_id: simulationExperimentId,
2428
2628
  simulation_transient_current_graph_ids: simulationCurrentGraphIds,
2429
2629
  simulation_transient_voltage_graph_ids: simulationVoltageGraphIds,
2430
2630
  width: effectiveWidth,
2431
- height: effectiveHeight,
2631
+ height: effectiveHeight
2632
+ };
2633
+ return hideSchematic ? convertCircuitJsonToSimulationGraphSvg(simulationOptions) : convertCircuitJsonToSchematicSimulationSvg({
2634
+ ...simulationOptions,
2432
2635
  schematicOptions: { colorOverrides }
2433
2636
  });
2434
- } catch (fallbackErr) {
2435
- console.error("Failed to generate fallback schematic SVG:", fallbackErr);
2637
+ } catch (error2) {
2638
+ console.error("Failed to generate simulation SVG:", error2);
2436
2639
  return "";
2437
2640
  }
2438
2641
  }, [
@@ -2440,6 +2643,7 @@ var AnalogSimulationViewer = ({
2440
2643
  effectiveWidth,
2441
2644
  effectiveHeight,
2442
2645
  colorOverrides,
2646
+ hideSchematic,
2443
2647
  simulationExperimentId,
2444
2648
  simulationCurrentGraphIds,
2445
2649
  simulationVoltageGraphIds
@@ -2489,7 +2693,7 @@ var AnalogSimulationViewer = ({
2489
2693
  };
2490
2694
  }, []);
2491
2695
  if (isLoading) {
2492
- return /* @__PURE__ */ jsx10(
2696
+ return /* @__PURE__ */ jsx11(
2493
2697
  "div",
2494
2698
  {
2495
2699
  style: {
@@ -2509,7 +2713,7 @@ var AnalogSimulationViewer = ({
2509
2713
  );
2510
2714
  }
2511
2715
  if (error) {
2512
- return /* @__PURE__ */ jsx10(
2716
+ return /* @__PURE__ */ jsx11(
2513
2717
  "div",
2514
2718
  {
2515
2719
  style: {
@@ -2524,15 +2728,15 @@ var AnalogSimulationViewer = ({
2524
2728
  ...containerStyle
2525
2729
  },
2526
2730
  className,
2527
- children: /* @__PURE__ */ jsxs7("div", { style: { textAlign: "center", padding: "20px" }, children: [
2528
- /* @__PURE__ */ jsx10("div", { style: { fontWeight: "bold", marginBottom: "8px" }, children: "Circuit Conversion Error" }),
2529
- /* @__PURE__ */ jsx10("div", { style: { fontSize: "14px" }, children: error })
2731
+ children: /* @__PURE__ */ jsxs8("div", { style: { textAlign: "center", padding: "20px" }, children: [
2732
+ /* @__PURE__ */ jsx11("div", { style: { fontWeight: "bold", marginBottom: "8px" }, children: "Circuit Conversion Error" }),
2733
+ /* @__PURE__ */ jsx11("div", { style: { fontSize: "14px" }, children: error })
2530
2734
  ] })
2531
2735
  }
2532
2736
  );
2533
2737
  }
2534
2738
  if (!simulationSvg) {
2535
- return /* @__PURE__ */ jsxs7(
2739
+ return /* @__PURE__ */ jsxs8(
2536
2740
  "div",
2537
2741
  {
2538
2742
  style: {
@@ -2548,11 +2752,11 @@ var AnalogSimulationViewer = ({
2548
2752
  },
2549
2753
  className,
2550
2754
  children: [
2551
- /* @__PURE__ */ jsx10("div", { style: { fontSize: "16px", color: "#475569", fontWeight: 500 }, children: "No Simulation Found" }),
2552
- /* @__PURE__ */ jsxs7("div", { style: { fontSize: "14px", color: "#64748b" }, children: [
2755
+ /* @__PURE__ */ jsx11("div", { style: { fontSize: "16px", color: "#475569", fontWeight: 500 }, children: "No Simulation Found" }),
2756
+ /* @__PURE__ */ jsxs8("div", { style: { fontSize: "14px", color: "#64748b" }, children: [
2553
2757
  "Use",
2554
2758
  " ",
2555
- /* @__PURE__ */ jsx10(
2759
+ /* @__PURE__ */ jsx11(
2556
2760
  "code",
2557
2761
  {
2558
2762
  style: {
@@ -2572,7 +2776,7 @@ var AnalogSimulationViewer = ({
2572
2776
  }
2573
2777
  );
2574
2778
  }
2575
- return /* @__PURE__ */ jsx10(
2779
+ return /* @__PURE__ */ jsxs8(
2576
2780
  "div",
2577
2781
  {
2578
2782
  ref: (node) => {
@@ -2590,35 +2794,45 @@ var AnalogSimulationViewer = ({
2590
2794
  className,
2591
2795
  onMouseDown: handleMouseDown,
2592
2796
  onTouchStart: handleTouchStart,
2593
- children: svgObjectUrl ? /* @__PURE__ */ jsx10(
2594
- "img",
2595
- {
2596
- ref: imgRef,
2597
- src: svgObjectUrl,
2598
- alt: "Circuit Simulation",
2599
- style: {
2600
- transformOrigin: "0 0",
2601
- width: "100%",
2602
- height: "100%",
2603
- display: "block",
2604
- objectFit: "contain"
2797
+ children: [
2798
+ /* @__PURE__ */ jsx11(
2799
+ AnalogSimulationSelector,
2800
+ {
2801
+ simulations: simulationExperiments,
2802
+ selectedSimulationExperimentId: simulationExperimentId,
2803
+ onSelectSimulation: handleSelectSimulation
2605
2804
  }
2606
- }
2607
- ) : /* @__PURE__ */ jsx10(
2608
- "div",
2609
- {
2610
- style: {
2611
- display: "flex",
2612
- alignItems: "center",
2613
- justifyContent: "center",
2614
- height: "100%",
2615
- minHeight: "300px",
2616
- color: "#666",
2617
- fontFamily: "sans-serif"
2618
- },
2619
- children: "Failed to render SVG"
2620
- }
2621
- )
2805
+ ),
2806
+ svgObjectUrl ? /* @__PURE__ */ jsx11(
2807
+ "img",
2808
+ {
2809
+ ref: imgRef,
2810
+ src: svgObjectUrl,
2811
+ alt: "Circuit Simulation",
2812
+ style: {
2813
+ transformOrigin: "0 0",
2814
+ width: "100%",
2815
+ height: "100%",
2816
+ display: "block",
2817
+ objectFit: "contain"
2818
+ }
2819
+ }
2820
+ ) : /* @__PURE__ */ jsx11(
2821
+ "div",
2822
+ {
2823
+ style: {
2824
+ display: "flex",
2825
+ alignItems: "center",
2826
+ justifyContent: "center",
2827
+ height: "100%",
2828
+ minHeight: "300px",
2829
+ color: "#666",
2830
+ fontFamily: "sans-serif"
2831
+ },
2832
+ children: "Failed to render SVG"
2833
+ }
2834
+ )
2835
+ ]
2622
2836
  }
2623
2837
  );
2624
2838
  };