@tscircuit/schematic-viewer 2.0.25 → 2.0.27

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 CHANGED
@@ -192,6 +192,176 @@ var useChangeSchematicTracesForMovedComponents = ({
192
192
  }, [svgDivRef, activeEditEvent, circuitJson, editEvents]);
193
193
  };
194
194
 
195
+ // lib/hooks/useSchematicGroupsOverlay.ts
196
+ import { useEffect as useEffect3 } from "react";
197
+ import { su as su3 } from "@tscircuit/soup-util";
198
+ var useSchematicGroupsOverlay = (svgDivRef, circuitJson, circuitJsonKey, showGroups) => {
199
+ useEffect3(() => {
200
+ if (!svgDivRef.current || !showGroups || !circuitJson || circuitJson.length === 0) {
201
+ if (svgDivRef.current) {
202
+ const existingOverlays2 = svgDivRef.current.querySelectorAll(
203
+ ".schematic-group-overlay"
204
+ );
205
+ existingOverlays2.forEach((overlay) => overlay.remove());
206
+ }
207
+ return;
208
+ }
209
+ const svg = svgDivRef.current.querySelector("svg");
210
+ if (!svg) {
211
+ return;
212
+ }
213
+ const existingOverlays = svg.querySelectorAll(".schematic-group-overlay");
214
+ existingOverlays.forEach((overlay) => overlay.remove());
215
+ try {
216
+ const sourceGroups = su3(circuitJson).source_group?.list() || [];
217
+ const schematicComponents = su3(circuitJson).schematic_component?.list() || [];
218
+ let groupsToRender = [];
219
+ const hasMeaningfulGroups = sourceGroups.length > 0 && sourceGroups.some((group) => group.name && group.name !== "default" && group.name !== "");
220
+ if (hasMeaningfulGroups) {
221
+ const groupMap = /* @__PURE__ */ new Map();
222
+ for (const comp of schematicComponents) {
223
+ const sourceComp = su3(circuitJson).source_component.get(
224
+ comp.source_component_id
225
+ );
226
+ if (sourceComp?.source_group_id) {
227
+ if (!groupMap.has(sourceComp.source_group_id)) {
228
+ groupMap.set(sourceComp.source_group_id, []);
229
+ }
230
+ groupMap.get(sourceComp.source_group_id).push(comp);
231
+ }
232
+ }
233
+ groupsToRender = Array.from(groupMap.entries()).map(
234
+ ([groupId, components], index) => {
235
+ const group = sourceGroups.find(
236
+ (g) => g.source_group_id === groupId
237
+ );
238
+ return {
239
+ id: groupId,
240
+ name: group?.name || `Group ${index + 1}`,
241
+ components,
242
+ color: getGroupColor(index)
243
+ };
244
+ }
245
+ );
246
+ } else {
247
+ const componentTypeGroups = /* @__PURE__ */ new Map();
248
+ for (const comp of schematicComponents) {
249
+ const sourceComp = su3(circuitJson).source_component.get(
250
+ comp.source_component_id
251
+ );
252
+ if (sourceComp) {
253
+ const componentType = sourceComp.ftype || "other";
254
+ if (!componentTypeGroups.has(componentType)) {
255
+ componentTypeGroups.set(componentType, []);
256
+ }
257
+ componentTypeGroups.get(componentType).push(comp);
258
+ }
259
+ }
260
+ groupsToRender = Array.from(componentTypeGroups.entries()).map(
261
+ ([type, components], index) => ({
262
+ id: `type_${type}`,
263
+ name: `${type.charAt(0).toUpperCase() + type.slice(1)}s`,
264
+ components,
265
+ color: getGroupColor(index)
266
+ })
267
+ );
268
+ }
269
+ groupsToRender.forEach((group, groupIndex) => {
270
+ if (group.components.length === 0) return;
271
+ const groupBounds = calculateGroupBounds(group.components, svg);
272
+ if (!groupBounds) return;
273
+ const groupOverlay = document.createElementNS(
274
+ "http://www.w3.org/2000/svg",
275
+ "rect"
276
+ );
277
+ groupOverlay.setAttribute("class", "schematic-group-overlay");
278
+ groupOverlay.setAttribute("x", (groupBounds.minX - 25).toString());
279
+ groupOverlay.setAttribute("y", (groupBounds.minY - 25).toString());
280
+ groupOverlay.setAttribute(
281
+ "width",
282
+ (groupBounds.maxX - groupBounds.minX + 50).toString()
283
+ );
284
+ groupOverlay.setAttribute(
285
+ "height",
286
+ (groupBounds.maxY - groupBounds.minY + 50).toString()
287
+ );
288
+ groupOverlay.setAttribute("fill", "none");
289
+ groupOverlay.setAttribute("stroke", group.color);
290
+ groupOverlay.setAttribute("stroke-width", "3");
291
+ groupOverlay.setAttribute("stroke-dasharray", "8,4");
292
+ groupOverlay.setAttribute("opacity", "0.8");
293
+ groupOverlay.setAttribute("rx", "4");
294
+ groupOverlay.setAttribute("ry", "4");
295
+ const groupLabel = document.createElementNS(
296
+ "http://www.w3.org/2000/svg",
297
+ "text"
298
+ );
299
+ groupLabel.setAttribute("class", "schematic-group-overlay");
300
+ groupLabel.setAttribute("x", (groupBounds.minX - 10).toString());
301
+ groupLabel.setAttribute("y", (groupBounds.minY - 8).toString());
302
+ groupLabel.setAttribute("fill", group.color);
303
+ groupLabel.setAttribute("font-size", "14");
304
+ groupLabel.setAttribute("font-family", "Arial, sans-serif");
305
+ groupLabel.setAttribute("font-weight", "bold");
306
+ groupLabel.setAttribute("stroke", "#fff");
307
+ groupLabel.setAttribute("stroke-width", "0.5");
308
+ groupLabel.setAttribute("paint-order", "stroke fill");
309
+ groupLabel.textContent = group.name;
310
+ svg.appendChild(groupOverlay);
311
+ svg.appendChild(groupLabel);
312
+ });
313
+ } catch (error) {
314
+ console.error("Error creating group overlays:", error);
315
+ }
316
+ }, [svgDivRef, circuitJsonKey, showGroups]);
317
+ };
318
+ function getGroupColor(index) {
319
+ const colors2 = [
320
+ "#FF6B6B",
321
+ // Red
322
+ "#4ECDC4",
323
+ // Teal
324
+ "#45B7D1",
325
+ // Blue
326
+ "#96CEB4",
327
+ // Green
328
+ "#FF8C42",
329
+ // Orange
330
+ "#DDA0DD",
331
+ // Plum
332
+ "#98D8C8",
333
+ // Mint
334
+ "#F7DC6F"
335
+ // Light Yellow
336
+ ];
337
+ return colors2[index % colors2.length];
338
+ }
339
+ function calculateGroupBounds(components, svg) {
340
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
341
+ for (const component of components) {
342
+ let componentElement = svg.querySelector(
343
+ `g[data-schematic-component-id="${component.schematic_component_id}"]`
344
+ );
345
+ if (!componentElement) {
346
+ componentElement = svg.querySelector(
347
+ `[data-schematic-component-id="${component.schematic_component_id}"]`
348
+ );
349
+ }
350
+ if (componentElement) {
351
+ const bbox = componentElement.getBBox();
352
+ minX = Math.min(minX, bbox.x);
353
+ minY = Math.min(minY, bbox.y);
354
+ maxX = Math.max(maxX, bbox.x + bbox.width);
355
+ maxY = Math.max(maxY, bbox.y + bbox.height);
356
+ }
357
+ }
358
+ if (minX === Infinity) {
359
+ return null;
360
+ }
361
+ const bounds = { minX, minY, maxX, maxY };
362
+ return bounds;
363
+ }
364
+
195
365
  // lib/utils/debug.ts
196
366
  import Debug from "debug";
197
367
  var debug = Debug("schematic-viewer");
@@ -201,7 +371,7 @@ var enableDebug = () => {
201
371
  var debug_default = debug;
202
372
 
203
373
  // lib/components/SchematicViewer.tsx
204
- import { useEffect as useEffect6, useMemo, useRef as useRef4, useState as useState4 } from "react";
374
+ import { useEffect as useEffect7, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
205
375
  import {
206
376
  fromString,
207
377
  identity,
@@ -210,11 +380,11 @@ import {
210
380
  import { useMouseMatrixTransform } from "use-mouse-matrix-transform";
211
381
 
212
382
  // lib/hooks/use-resize-handling.ts
213
- import { useEffect as useEffect3, useState } from "react";
383
+ import { useEffect as useEffect4, useState } from "react";
214
384
  var useResizeHandling = (containerRef) => {
215
385
  const [containerWidth, setContainerWidth] = useState(0);
216
386
  const [containerHeight, setContainerHeight] = useState(0);
217
- useEffect3(() => {
387
+ useEffect4(() => {
218
388
  if (!containerRef.current) return;
219
389
  const updateDimensions = () => {
220
390
  const rect = containerRef.current?.getBoundingClientRect();
@@ -234,8 +404,8 @@ var useResizeHandling = (containerRef) => {
234
404
  };
235
405
 
236
406
  // lib/hooks/useComponentDragging.ts
237
- import { su as su3 } from "@tscircuit/soup-util";
238
- import { useCallback, useEffect as useEffect4, useRef as useRef3, useState as useState2 } from "react";
407
+ import { su as su4 } from "@tscircuit/soup-util";
408
+ import { useCallback, useEffect as useEffect5, useRef as useRef3, useState as useState2 } from "react";
239
409
  import { compose as compose2 } from "transformation-matrix";
240
410
  var debug2 = debug_default.extend("useComponentDragging");
241
411
  var useComponentDragging = ({
@@ -258,7 +428,7 @@ var useComponentDragging = ({
258
428
  const componentPositionsRef = useRef3(
259
429
  /* @__PURE__ */ new Map()
260
430
  );
261
- useEffect4(() => {
431
+ useEffect5(() => {
262
432
  editEvents.forEach((event) => {
263
433
  if ("edit_event_type" in event && event.edit_event_type === "edit_schematic_component_location" && !event.in_progress) {
264
434
  componentPositionsRef.current.set(event.schematic_component_id, {
@@ -280,7 +450,7 @@ var useComponentDragging = ({
280
450
  );
281
451
  if (!schematic_component_id) return;
282
452
  if (cancelDrag) cancelDrag();
283
- const schematic_component = su3(circuitJson).schematic_component.get(
453
+ const schematic_component = su4(circuitJson).schematic_component.get(
284
454
  schematic_component_id
285
455
  );
286
456
  if (!schematic_component) return;
@@ -367,7 +537,7 @@ var useComponentDragging = ({
367
537
  dragStartPosRef.current = null;
368
538
  setActiveEditEvent(null);
369
539
  }, [onEditEvent]);
370
- useEffect4(() => {
540
+ useEffect5(() => {
371
541
  window.addEventListener("mousemove", handleMouseMove);
372
542
  window.addEventListener("mouseup", handleMouseUp);
373
543
  return () => {
@@ -387,6 +557,9 @@ var zIndexMap = {
387
557
  schematicEditIcon: 50,
388
558
  schematicGridIcon: 49,
389
559
  spiceSimulationIcon: 51,
560
+ viewMenuIcon: 48,
561
+ viewMenu: 55,
562
+ viewMenuBackdrop: 54,
390
563
  clickToInteractOverlay: 100
391
564
  };
392
565
 
@@ -403,7 +576,7 @@ var EditIcon = ({
403
576
  style: {
404
577
  position: "absolute",
405
578
  top: "16px",
406
- right: "16px",
579
+ right: "64px",
407
580
  backgroundColor: active ? "#4CAF50" : "#fff",
408
581
  color: active ? "#fff" : "#000",
409
582
  padding: "8px",
@@ -447,7 +620,7 @@ var GridIcon = ({
447
620
  style: {
448
621
  position: "absolute",
449
622
  top: "56px",
450
- right: "16px",
623
+ right: "64px",
451
624
  backgroundColor: active ? "#4CAF50" : "#fff",
452
625
  color: active ? "#fff" : "#000",
453
626
  padding: "8px",
@@ -475,9 +648,190 @@ var GridIcon = ({
475
648
  );
476
649
  };
477
650
 
651
+ // lib/components/ViewMenuIcon.tsx
652
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
653
+ var ViewMenuIcon = ({
654
+ onClick,
655
+ active
656
+ }) => {
657
+ return /* @__PURE__ */ jsx3(
658
+ "div",
659
+ {
660
+ onClick,
661
+ style: {
662
+ position: "absolute",
663
+ top: "16px",
664
+ right: "16px",
665
+ backgroundColor: active ? "#4CAF50" : "#fff",
666
+ color: active ? "#fff" : "#000",
667
+ padding: "8px",
668
+ borderRadius: "4px",
669
+ cursor: "pointer",
670
+ boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
671
+ display: "flex",
672
+ alignItems: "center",
673
+ gap: "4px",
674
+ zIndex: zIndexMap.viewMenuIcon
675
+ },
676
+ children: /* @__PURE__ */ jsxs2(
677
+ "svg",
678
+ {
679
+ width: "16",
680
+ height: "16",
681
+ viewBox: "0 0 24 24",
682
+ fill: "none",
683
+ stroke: "currentColor",
684
+ strokeWidth: "2",
685
+ children: [
686
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "1" }),
687
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "5", r: "1" }),
688
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "19", r: "1" })
689
+ ]
690
+ }
691
+ )
692
+ }
693
+ );
694
+ };
695
+
696
+ // lib/components/ViewMenu.tsx
697
+ import { useMemo } from "react";
698
+ import { su as su5 } from "@tscircuit/soup-util";
699
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
700
+ var ViewMenu = ({
701
+ circuitJson,
702
+ circuitJsonKey,
703
+ isVisible,
704
+ onClose,
705
+ showGroups,
706
+ onToggleGroups
707
+ }) => {
708
+ const hasGroups = useMemo(() => {
709
+ if (!circuitJson || circuitJson.length === 0) return false;
710
+ try {
711
+ const sourceGroups = su5(circuitJson).source_group?.list() || [];
712
+ if (sourceGroups.length > 0) return true;
713
+ const schematicComponents = su5(circuitJson).schematic_component?.list() || [];
714
+ if (schematicComponents.length > 1) {
715
+ const componentTypes = /* @__PURE__ */ new Set();
716
+ for (const comp of schematicComponents) {
717
+ const sourceComp = su5(circuitJson).source_component.get(
718
+ comp.source_component_id
719
+ );
720
+ if (sourceComp?.ftype) {
721
+ componentTypes.add(sourceComp.ftype);
722
+ }
723
+ }
724
+ return componentTypes.size > 1;
725
+ }
726
+ return false;
727
+ } catch (error) {
728
+ console.error("Error checking for groups:", error);
729
+ return false;
730
+ }
731
+ }, [circuitJsonKey]);
732
+ if (!isVisible) return null;
733
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
734
+ /* @__PURE__ */ jsx4(
735
+ "div",
736
+ {
737
+ onClick: onClose,
738
+ style: {
739
+ position: "absolute",
740
+ inset: 0,
741
+ backgroundColor: "transparent",
742
+ zIndex: zIndexMap.viewMenuBackdrop
743
+ }
744
+ }
745
+ ),
746
+ /* @__PURE__ */ jsxs3(
747
+ "div",
748
+ {
749
+ style: {
750
+ position: "absolute",
751
+ top: "56px",
752
+ right: "16px",
753
+ backgroundColor: "#ffffff",
754
+ color: "#000000",
755
+ border: "1px solid #ccc",
756
+ borderRadius: "4px",
757
+ boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
758
+ minWidth: "200px",
759
+ zIndex: zIndexMap.viewMenu
760
+ },
761
+ children: [
762
+ /* @__PURE__ */ jsxs3(
763
+ "div",
764
+ {
765
+ onClick: () => {
766
+ if (hasGroups) {
767
+ onToggleGroups(!showGroups);
768
+ }
769
+ },
770
+ style: {
771
+ padding: "8px 12px",
772
+ cursor: hasGroups ? "pointer" : "not-allowed",
773
+ opacity: hasGroups ? 1 : 0.5,
774
+ fontSize: "13px",
775
+ color: "#000000",
776
+ fontFamily: "sans-serif",
777
+ display: "flex",
778
+ alignItems: "center",
779
+ gap: "8px"
780
+ },
781
+ onMouseEnter: (e) => {
782
+ if (hasGroups) {
783
+ e.currentTarget.style.backgroundColor = "#f0f0f0";
784
+ }
785
+ },
786
+ onMouseLeave: (e) => {
787
+ if (hasGroups) {
788
+ e.currentTarget.style.backgroundColor = "transparent";
789
+ }
790
+ },
791
+ children: [
792
+ /* @__PURE__ */ jsx4(
793
+ "div",
794
+ {
795
+ style: {
796
+ width: "16px",
797
+ height: "16px",
798
+ border: "2px solid #000",
799
+ borderRadius: "2px",
800
+ backgroundColor: "transparent",
801
+ display: "flex",
802
+ alignItems: "center",
803
+ justifyContent: "center",
804
+ fontSize: "10px",
805
+ fontWeight: "bold"
806
+ },
807
+ children: showGroups && "\u2713"
808
+ }
809
+ ),
810
+ "View Schematic Groups"
811
+ ]
812
+ }
813
+ ),
814
+ !hasGroups && /* @__PURE__ */ jsx4(
815
+ "div",
816
+ {
817
+ style: {
818
+ padding: "8px 12px",
819
+ fontSize: "11px",
820
+ color: "#666",
821
+ fontStyle: "italic"
822
+ },
823
+ children: "No groups found in this schematic"
824
+ }
825
+ )
826
+ ]
827
+ }
828
+ )
829
+ ] });
830
+ };
831
+
478
832
  // lib/components/SpiceIcon.tsx
479
- import { jsx as jsx3 } from "react/jsx-runtime";
480
- var SpiceIcon = () => /* @__PURE__ */ jsx3(
833
+ import { jsx as jsx5 } from "react/jsx-runtime";
834
+ var SpiceIcon = () => /* @__PURE__ */ jsx5(
481
835
  "svg",
482
836
  {
483
837
  width: "16",
@@ -488,23 +842,23 @@ var SpiceIcon = () => /* @__PURE__ */ jsx3(
488
842
  strokeWidth: "2",
489
843
  strokeLinecap: "round",
490
844
  strokeLinejoin: "round",
491
- children: /* @__PURE__ */ jsx3("path", { d: "M3 12h2.5l2.5-9 4 18 4-9h5.5" })
845
+ children: /* @__PURE__ */ jsx5("path", { d: "M3 12h2.5l2.5-9 4 18 4-9h5.5" })
492
846
  }
493
847
  );
494
848
 
495
849
  // lib/components/SpiceSimulationIcon.tsx
496
- import { jsx as jsx4 } from "react/jsx-runtime";
850
+ import { jsx as jsx6 } from "react/jsx-runtime";
497
851
  var SpiceSimulationIcon = ({
498
852
  onClick
499
853
  }) => {
500
- return /* @__PURE__ */ jsx4(
854
+ return /* @__PURE__ */ jsx6(
501
855
  "div",
502
856
  {
503
857
  onClick,
504
858
  style: {
505
859
  position: "absolute",
506
860
  top: "16px",
507
- right: "56px",
861
+ right: "112px",
508
862
  backgroundColor: "#fff",
509
863
  color: "#000",
510
864
  padding: "8px",
@@ -516,7 +870,7 @@ var SpiceSimulationIcon = ({
516
870
  gap: "4px",
517
871
  zIndex: zIndexMap.spiceSimulationIcon
518
872
  },
519
- children: /* @__PURE__ */ jsx4(SpiceIcon, {})
873
+ children: /* @__PURE__ */ jsx6(SpiceIcon, {})
520
874
  }
521
875
  );
522
876
  };
@@ -533,7 +887,7 @@ import {
533
887
  Legend
534
888
  } from "chart.js";
535
889
  import { Line } from "react-chartjs-2";
536
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
890
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
537
891
  ChartJS.register(
538
892
  CategoryScale,
539
893
  LinearScale,
@@ -574,7 +928,7 @@ var SpicePlot = ({
574
928
  error
575
929
  }) => {
576
930
  if (isLoading) {
577
- return /* @__PURE__ */ jsx5(
931
+ return /* @__PURE__ */ jsx7(
578
932
  "div",
579
933
  {
580
934
  style: {
@@ -589,7 +943,7 @@ var SpicePlot = ({
589
943
  );
590
944
  }
591
945
  if (error) {
592
- return /* @__PURE__ */ jsxs2(
946
+ return /* @__PURE__ */ jsxs4(
593
947
  "div",
594
948
  {
595
949
  style: {
@@ -608,7 +962,7 @@ var SpicePlot = ({
608
962
  );
609
963
  }
610
964
  if (plotData.length === 0) {
611
- return /* @__PURE__ */ jsx5(
965
+ return /* @__PURE__ */ jsx7(
612
966
  "div",
613
967
  {
614
968
  style: {
@@ -695,11 +1049,11 @@ var SpicePlot = ({
695
1049
  }
696
1050
  }
697
1051
  };
698
- return /* @__PURE__ */ jsx5("div", { style: { position: "relative", height: "300px", width: "100%" }, children: /* @__PURE__ */ jsx5(Line, { options, data: chartData }) });
1052
+ return /* @__PURE__ */ jsx7("div", { style: { position: "relative", height: "300px", width: "100%" }, children: /* @__PURE__ */ jsx7(Line, { options, data: chartData }) });
699
1053
  };
700
1054
 
701
1055
  // lib/components/SpiceSimulationOverlay.tsx
702
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
1056
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
703
1057
  var SpiceSimulationOverlay = ({
704
1058
  spiceString,
705
1059
  onClose,
@@ -708,7 +1062,7 @@ var SpiceSimulationOverlay = ({
708
1062
  isLoading,
709
1063
  error
710
1064
  }) => {
711
- return /* @__PURE__ */ jsx6(
1065
+ return /* @__PURE__ */ jsx8(
712
1066
  "div",
713
1067
  {
714
1068
  style: {
@@ -724,7 +1078,7 @@ var SpiceSimulationOverlay = ({
724
1078
  zIndex: 1002,
725
1079
  fontFamily: "sans-serif"
726
1080
  },
727
- children: /* @__PURE__ */ jsxs3(
1081
+ children: /* @__PURE__ */ jsxs5(
728
1082
  "div",
729
1083
  {
730
1084
  style: {
@@ -736,7 +1090,7 @@ var SpiceSimulationOverlay = ({
736
1090
  boxShadow: "0 4px 20px rgba(0, 0, 0, 0.15)"
737
1091
  },
738
1092
  children: [
739
- /* @__PURE__ */ jsxs3(
1093
+ /* @__PURE__ */ jsxs5(
740
1094
  "div",
741
1095
  {
742
1096
  style: {
@@ -748,7 +1102,7 @@ var SpiceSimulationOverlay = ({
748
1102
  paddingBottom: "16px"
749
1103
  },
750
1104
  children: [
751
- /* @__PURE__ */ jsx6(
1105
+ /* @__PURE__ */ jsx8(
752
1106
  "h2",
753
1107
  {
754
1108
  style: {
@@ -760,7 +1114,7 @@ var SpiceSimulationOverlay = ({
760
1114
  children: "SPICE Simulation"
761
1115
  }
762
1116
  ),
763
- /* @__PURE__ */ jsx6(
1117
+ /* @__PURE__ */ jsx8(
764
1118
  "button",
765
1119
  {
766
1120
  onClick: onClose,
@@ -779,7 +1133,7 @@ var SpiceSimulationOverlay = ({
779
1133
  ]
780
1134
  }
781
1135
  ),
782
- /* @__PURE__ */ jsx6("div", { children: /* @__PURE__ */ jsx6(
1136
+ /* @__PURE__ */ jsx8("div", { children: /* @__PURE__ */ jsx8(
783
1137
  SpicePlot,
784
1138
  {
785
1139
  plotData,
@@ -788,8 +1142,8 @@ var SpiceSimulationOverlay = ({
788
1142
  error
789
1143
  }
790
1144
  ) }),
791
- /* @__PURE__ */ jsxs3("div", { style: { marginTop: "24px" }, children: [
792
- /* @__PURE__ */ jsx6(
1145
+ /* @__PURE__ */ jsxs5("div", { style: { marginTop: "24px" }, children: [
1146
+ /* @__PURE__ */ jsx8(
793
1147
  "h3",
794
1148
  {
795
1149
  style: {
@@ -802,7 +1156,7 @@ var SpiceSimulationOverlay = ({
802
1156
  children: "SPICE Netlist"
803
1157
  }
804
1158
  ),
805
- /* @__PURE__ */ jsx6(
1159
+ /* @__PURE__ */ jsx8(
806
1160
  "pre",
807
1161
  {
808
1162
  style: {
@@ -828,7 +1182,7 @@ var SpiceSimulationOverlay = ({
828
1182
  };
829
1183
 
830
1184
  // lib/hooks/useSpiceSimulation.ts
831
- import { useState as useState3, useEffect as useEffect5 } from "react";
1185
+ import { useState as useState3, useEffect as useEffect6 } from "react";
832
1186
 
833
1187
  // lib/workers/spice-simulation.worker.blob.js
834
1188
  var b64 = "dmFyIGU9bnVsbCxzPWFzeW5jKCk9Pihhd2FpdCBpbXBvcnQoImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vZWVjaXJjdWl0LWVuZ2luZUAxLjUuMi8rZXNtIikpLlNpbXVsYXRpb24sYz1hc3luYygpPT57aWYoZSYmZS5pc0luaXRpYWxpemVkKCkpcmV0dXJuO2xldCBpPWF3YWl0IHMoKTtlPW5ldyBpLGF3YWl0IGUuc3RhcnQoKX07c2VsZi5vbm1lc3NhZ2U9YXN5bmMgaT0+e3RyeXtpZihhd2FpdCBjKCksIWUpdGhyb3cgbmV3IEVycm9yKCJTaW11bGF0aW9uIG5vdCBpbml0aWFsaXplZCIpO2xldCB0PWkuZGF0YS5zcGljZVN0cmluZyxhPXQubWF0Y2goL3dyZGF0YVxzKyhcUyspXHMrKC4qKS9pKTtpZihhKXtsZXQgbz1gLnByb2JlICR7YVsyXS50cmltKCkuc3BsaXQoL1xzKy8pLmpvaW4oIiAiKX1gO3Q9dC5yZXBsYWNlKC93cmRhdGEuKi9pLG8pfWVsc2UgaWYoIXQubWF0Y2goL1wucHJvYmUvaSkpdGhyb3cgdC5tYXRjaCgvcGxvdFxzKyguKikvaSk/bmV3IEVycm9yKCJUaGUgJ3Bsb3QnIGNvbW1hbmQgaXMgbm90IHN1cHBvcnRlZCBmb3IgZGF0YSBleHRyYWN0aW9uLiBQbGVhc2UgdXNlICd3cmRhdGEgPGZpbGVuYW1lPiA8dmFyMT4gLi4uJyBvciAnLnByb2JlIDx2YXIxPiAuLi4nIGluc3RlYWQuIik6bmV3IEVycm9yKCJObyAnLnByb2JlJyBvciAnd3JkYXRhJyBjb21tYW5kIGZvdW5kIGluIFNQSUNFIGZpbGUuIFVzZSAnd3JkYXRhIDxmaWxlbmFtZT4gPHZhcjE+IC4uLicgdG8gc3BlY2lmeSBvdXRwdXQuIik7ZS5zZXROZXRMaXN0KHQpO2xldCBuPWF3YWl0IGUucnVuU2ltKCk7c2VsZi5wb3N0TWVzc2FnZSh7dHlwZToicmVzdWx0IixyZXN1bHQ6bn0pfWNhdGNoKHQpe3NlbGYucG9zdE1lc3NhZ2Uoe3R5cGU6ImVycm9yIixlcnJvcjp0Lm1lc3NhZ2V9KX19Owo=";
@@ -885,7 +1239,7 @@ var useSpiceSimulation = (spiceString) => {
885
1239
  const [nodes, setNodes] = useState3([]);
886
1240
  const [isLoading, setIsLoading] = useState3(true);
887
1241
  const [error, setError] = useState3(null);
888
- useEffect5(() => {
1242
+ useEffect6(() => {
889
1243
  if (!spiceString) {
890
1244
  setIsLoading(false);
891
1245
  setPlotData([]);
@@ -897,21 +1251,13 @@ var useSpiceSimulation = (spiceString) => {
897
1251
  setError(null);
898
1252
  setPlotData([]);
899
1253
  setNodes([]);
900
- let worker;
901
- if (import.meta.env.DEV) {
902
- worker = new Worker(
903
- new URL("../workers/spice-simulation.worker.ts", import.meta.url),
904
- { type: "module" }
905
- );
906
- } else {
907
- const workerUrl = getSpiceSimulationWorkerBlobUrl();
908
- if (!workerUrl) {
909
- setError("Could not create SPICE simulation worker.");
910
- setIsLoading(false);
911
- return;
912
- }
913
- worker = new Worker(workerUrl, { type: "module" });
1254
+ const workerUrl = getSpiceSimulationWorkerBlobUrl();
1255
+ if (!workerUrl) {
1256
+ setError("Could not create SPICE simulation worker.");
1257
+ setIsLoading(false);
1258
+ return;
914
1259
  }
1260
+ const worker = new Worker(workerUrl, { type: "module" });
915
1261
  worker.onmessage = (event) => {
916
1262
  if (event.data.type === "result") {
917
1263
  try {
@@ -999,7 +1345,7 @@ var getSpiceFromCircuitJson = (circuitJson) => {
999
1345
  };
1000
1346
 
1001
1347
  // lib/components/SchematicViewer.tsx
1002
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1348
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1003
1349
  var SchematicViewer = ({
1004
1350
  circuitJson,
1005
1351
  containerStyle,
@@ -1020,11 +1366,11 @@ var SchematicViewer = ({
1020
1366
  const getCircuitHash = (circuitJson2) => {
1021
1367
  return `${circuitJson2?.length || 0}_${circuitJson2?.editCount || 0}`;
1022
1368
  };
1023
- const circuitJsonKey = useMemo(
1369
+ const circuitJsonKey = useMemo2(
1024
1370
  () => getCircuitHash(circuitJson),
1025
1371
  [circuitJson]
1026
1372
  );
1027
- const spiceString = useMemo(() => {
1373
+ const spiceString = useMemo2(() => {
1028
1374
  if (!spiceSimulationEnabled) return null;
1029
1375
  try {
1030
1376
  return getSpiceFromCircuitJson(circuitJson);
@@ -1044,6 +1390,8 @@ var SchematicViewer = ({
1044
1390
  const [isInteractionEnabled, setIsInteractionEnabled] = useState4(
1045
1391
  !clickToInteractEnabled
1046
1392
  );
1393
+ const [showViewMenu, setShowViewMenu] = useState4(false);
1394
+ const [showSchematicGroups, setShowSchematicGroups] = useState4(false);
1047
1395
  const svgDivRef = useRef4(null);
1048
1396
  const touchStartRef = useRef4(null);
1049
1397
  const handleTouchStart = (e) => {
@@ -1067,7 +1415,7 @@ var SchematicViewer = ({
1067
1415
  };
1068
1416
  const [internalEditEvents, setInternalEditEvents] = useState4([]);
1069
1417
  const circuitJsonRef = useRef4(circuitJson);
1070
- useEffect6(() => {
1418
+ useEffect7(() => {
1071
1419
  const circuitHash = getCircuitHash(circuitJson);
1072
1420
  const circuitHashRef = getCircuitHash(circuitJsonRef.current);
1073
1421
  if (circuitHash !== circuitHashRef) {
@@ -1088,7 +1436,7 @@ var SchematicViewer = ({
1088
1436
  enabled: isInteractionEnabled && !showSpiceOverlay
1089
1437
  });
1090
1438
  const { containerWidth, containerHeight } = useResizeHandling(containerRef);
1091
- const svgString = useMemo(() => {
1439
+ const svgString = useMemo2(() => {
1092
1440
  if (!containerWidth || !containerHeight) return "";
1093
1441
  return convertCircuitJsonToSchematicSvg(circuitJson, {
1094
1442
  width: containerWidth,
@@ -1100,13 +1448,13 @@ var SchematicViewer = ({
1100
1448
  colorOverrides
1101
1449
  });
1102
1450
  }, [circuitJson, containerWidth, containerHeight]);
1103
- const containerBackgroundColor = useMemo(() => {
1451
+ const containerBackgroundColor = useMemo2(() => {
1104
1452
  const match = svgString.match(
1105
1453
  /<svg[^>]*style="[^"]*background-color:\s*([^;\"]+)/i
1106
1454
  );
1107
1455
  return match?.[1] ?? "transparent";
1108
1456
  }, [svgString]);
1109
- const realToSvgProjection = useMemo(() => {
1457
+ const realToSvgProjection = useMemo2(() => {
1110
1458
  if (!svgString) return identity();
1111
1459
  const transformString = svgString.match(
1112
1460
  /data-real-to-screen-transform="([^"]+)"/
@@ -1124,7 +1472,7 @@ var SchematicViewer = ({
1124
1472
  onEditEvent(event);
1125
1473
  }
1126
1474
  };
1127
- const editEventsWithUnappliedEditEvents = useMemo(() => {
1475
+ const editEventsWithUnappliedEditEvents = useMemo2(() => {
1128
1476
  return [...unappliedEditEvents, ...internalEditEvents];
1129
1477
  }, [unappliedEditEvents, internalEditEvents]);
1130
1478
  const { handleMouseDown, isDragging, activeEditEvent } = useComponentDragging(
@@ -1152,8 +1500,9 @@ var SchematicViewer = ({
1152
1500
  activeEditEvent,
1153
1501
  editEvents: editEventsWithUnappliedEditEvents
1154
1502
  });
1155
- const svgDiv = useMemo(
1156
- () => /* @__PURE__ */ jsx7(
1503
+ useSchematicGroupsOverlay(svgDivRef, circuitJson, circuitJsonKey, showSchematicGroups);
1504
+ const svgDiv = useMemo2(
1505
+ () => /* @__PURE__ */ jsx9(
1157
1506
  "div",
1158
1507
  {
1159
1508
  ref: svgDivRef,
@@ -1166,7 +1515,7 @@ var SchematicViewer = ({
1166
1515
  ),
1167
1516
  [svgString, isInteractionEnabled, clickToInteractEnabled]
1168
1517
  );
1169
- return /* @__PURE__ */ jsxs4(
1518
+ return /* @__PURE__ */ jsxs6(
1170
1519
  "div",
1171
1520
  {
1172
1521
  ref: containerRef,
@@ -1207,7 +1556,7 @@ var SchematicViewer = ({
1207
1556
  handleTouchEnd(e);
1208
1557
  },
1209
1558
  children: [
1210
- !isInteractionEnabled && clickToInteractEnabled && /* @__PURE__ */ jsx7(
1559
+ !isInteractionEnabled && clickToInteractEnabled && /* @__PURE__ */ jsx9(
1211
1560
  "div",
1212
1561
  {
1213
1562
  onClick: (e) => {
@@ -1226,7 +1575,7 @@ var SchematicViewer = ({
1226
1575
  pointerEvents: "all",
1227
1576
  touchAction: "pan-x pan-y pinch-zoom"
1228
1577
  },
1229
- children: /* @__PURE__ */ jsx7(
1578
+ children: /* @__PURE__ */ jsx9(
1230
1579
  "div",
1231
1580
  {
1232
1581
  style: {
@@ -1243,22 +1592,40 @@ var SchematicViewer = ({
1243
1592
  )
1244
1593
  }
1245
1594
  ),
1246
- editingEnabled && /* @__PURE__ */ jsx7(
1595
+ /* @__PURE__ */ jsx9(
1596
+ ViewMenuIcon,
1597
+ {
1598
+ active: showViewMenu,
1599
+ onClick: () => setShowViewMenu(!showViewMenu)
1600
+ }
1601
+ ),
1602
+ editingEnabled && /* @__PURE__ */ jsx9(
1247
1603
  EditIcon,
1248
1604
  {
1249
1605
  active: editModeEnabled,
1250
1606
  onClick: () => setEditModeEnabled(!editModeEnabled)
1251
1607
  }
1252
1608
  ),
1253
- editingEnabled && editModeEnabled && /* @__PURE__ */ jsx7(
1609
+ editingEnabled && editModeEnabled && /* @__PURE__ */ jsx9(
1254
1610
  GridIcon,
1255
1611
  {
1256
1612
  active: snapToGrid,
1257
1613
  onClick: () => setSnapToGrid(!snapToGrid)
1258
1614
  }
1259
1615
  ),
1260
- spiceSimulationEnabled && /* @__PURE__ */ jsx7(SpiceSimulationIcon, { onClick: () => setShowSpiceOverlay(true) }),
1261
- showSpiceOverlay && /* @__PURE__ */ jsx7(
1616
+ /* @__PURE__ */ jsx9(
1617
+ ViewMenu,
1618
+ {
1619
+ circuitJson,
1620
+ circuitJsonKey,
1621
+ isVisible: showViewMenu,
1622
+ onClose: () => setShowViewMenu(false),
1623
+ showGroups: showSchematicGroups,
1624
+ onToggleGroups: setShowSchematicGroups
1625
+ }
1626
+ ),
1627
+ spiceSimulationEnabled && /* @__PURE__ */ jsx9(SpiceSimulationIcon, { onClick: () => setShowSpiceOverlay(true) }),
1628
+ showSpiceOverlay && /* @__PURE__ */ jsx9(
1262
1629
  SpiceSimulationOverlay,
1263
1630
  {
1264
1631
  spiceString,