@tscircuit/schematic-viewer 2.0.70 → 2.0.71

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