@tscircuit/schematic-viewer 2.0.26 → 2.0.28

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