@tscircuit/schematic-viewer 2.0.26 → 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([]);
@@ -991,7 +1345,7 @@ var getSpiceFromCircuitJson = (circuitJson) => {
991
1345
  };
992
1346
 
993
1347
  // lib/components/SchematicViewer.tsx
994
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1348
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
995
1349
  var SchematicViewer = ({
996
1350
  circuitJson,
997
1351
  containerStyle,
@@ -1012,11 +1366,11 @@ var SchematicViewer = ({
1012
1366
  const getCircuitHash = (circuitJson2) => {
1013
1367
  return `${circuitJson2?.length || 0}_${circuitJson2?.editCount || 0}`;
1014
1368
  };
1015
- const circuitJsonKey = useMemo(
1369
+ const circuitJsonKey = useMemo2(
1016
1370
  () => getCircuitHash(circuitJson),
1017
1371
  [circuitJson]
1018
1372
  );
1019
- const spiceString = useMemo(() => {
1373
+ const spiceString = useMemo2(() => {
1020
1374
  if (!spiceSimulationEnabled) return null;
1021
1375
  try {
1022
1376
  return getSpiceFromCircuitJson(circuitJson);
@@ -1036,6 +1390,8 @@ var SchematicViewer = ({
1036
1390
  const [isInteractionEnabled, setIsInteractionEnabled] = useState4(
1037
1391
  !clickToInteractEnabled
1038
1392
  );
1393
+ const [showViewMenu, setShowViewMenu] = useState4(false);
1394
+ const [showSchematicGroups, setShowSchematicGroups] = useState4(false);
1039
1395
  const svgDivRef = useRef4(null);
1040
1396
  const touchStartRef = useRef4(null);
1041
1397
  const handleTouchStart = (e) => {
@@ -1059,7 +1415,7 @@ var SchematicViewer = ({
1059
1415
  };
1060
1416
  const [internalEditEvents, setInternalEditEvents] = useState4([]);
1061
1417
  const circuitJsonRef = useRef4(circuitJson);
1062
- useEffect6(() => {
1418
+ useEffect7(() => {
1063
1419
  const circuitHash = getCircuitHash(circuitJson);
1064
1420
  const circuitHashRef = getCircuitHash(circuitJsonRef.current);
1065
1421
  if (circuitHash !== circuitHashRef) {
@@ -1080,7 +1436,7 @@ var SchematicViewer = ({
1080
1436
  enabled: isInteractionEnabled && !showSpiceOverlay
1081
1437
  });
1082
1438
  const { containerWidth, containerHeight } = useResizeHandling(containerRef);
1083
- const svgString = useMemo(() => {
1439
+ const svgString = useMemo2(() => {
1084
1440
  if (!containerWidth || !containerHeight) return "";
1085
1441
  return convertCircuitJsonToSchematicSvg(circuitJson, {
1086
1442
  width: containerWidth,
@@ -1092,13 +1448,13 @@ var SchematicViewer = ({
1092
1448
  colorOverrides
1093
1449
  });
1094
1450
  }, [circuitJson, containerWidth, containerHeight]);
1095
- const containerBackgroundColor = useMemo(() => {
1451
+ const containerBackgroundColor = useMemo2(() => {
1096
1452
  const match = svgString.match(
1097
1453
  /<svg[^>]*style="[^"]*background-color:\s*([^;\"]+)/i
1098
1454
  );
1099
1455
  return match?.[1] ?? "transparent";
1100
1456
  }, [svgString]);
1101
- const realToSvgProjection = useMemo(() => {
1457
+ const realToSvgProjection = useMemo2(() => {
1102
1458
  if (!svgString) return identity();
1103
1459
  const transformString = svgString.match(
1104
1460
  /data-real-to-screen-transform="([^"]+)"/
@@ -1116,7 +1472,7 @@ var SchematicViewer = ({
1116
1472
  onEditEvent(event);
1117
1473
  }
1118
1474
  };
1119
- const editEventsWithUnappliedEditEvents = useMemo(() => {
1475
+ const editEventsWithUnappliedEditEvents = useMemo2(() => {
1120
1476
  return [...unappliedEditEvents, ...internalEditEvents];
1121
1477
  }, [unappliedEditEvents, internalEditEvents]);
1122
1478
  const { handleMouseDown, isDragging, activeEditEvent } = useComponentDragging(
@@ -1144,8 +1500,9 @@ var SchematicViewer = ({
1144
1500
  activeEditEvent,
1145
1501
  editEvents: editEventsWithUnappliedEditEvents
1146
1502
  });
1147
- const svgDiv = useMemo(
1148
- () => /* @__PURE__ */ jsx7(
1503
+ useSchematicGroupsOverlay(svgDivRef, circuitJson, circuitJsonKey, showSchematicGroups);
1504
+ const svgDiv = useMemo2(
1505
+ () => /* @__PURE__ */ jsx9(
1149
1506
  "div",
1150
1507
  {
1151
1508
  ref: svgDivRef,
@@ -1158,7 +1515,7 @@ var SchematicViewer = ({
1158
1515
  ),
1159
1516
  [svgString, isInteractionEnabled, clickToInteractEnabled]
1160
1517
  );
1161
- return /* @__PURE__ */ jsxs4(
1518
+ return /* @__PURE__ */ jsxs6(
1162
1519
  "div",
1163
1520
  {
1164
1521
  ref: containerRef,
@@ -1199,7 +1556,7 @@ var SchematicViewer = ({
1199
1556
  handleTouchEnd(e);
1200
1557
  },
1201
1558
  children: [
1202
- !isInteractionEnabled && clickToInteractEnabled && /* @__PURE__ */ jsx7(
1559
+ !isInteractionEnabled && clickToInteractEnabled && /* @__PURE__ */ jsx9(
1203
1560
  "div",
1204
1561
  {
1205
1562
  onClick: (e) => {
@@ -1218,7 +1575,7 @@ var SchematicViewer = ({
1218
1575
  pointerEvents: "all",
1219
1576
  touchAction: "pan-x pan-y pinch-zoom"
1220
1577
  },
1221
- children: /* @__PURE__ */ jsx7(
1578
+ children: /* @__PURE__ */ jsx9(
1222
1579
  "div",
1223
1580
  {
1224
1581
  style: {
@@ -1235,22 +1592,40 @@ var SchematicViewer = ({
1235
1592
  )
1236
1593
  }
1237
1594
  ),
1238
- editingEnabled && /* @__PURE__ */ jsx7(
1595
+ /* @__PURE__ */ jsx9(
1596
+ ViewMenuIcon,
1597
+ {
1598
+ active: showViewMenu,
1599
+ onClick: () => setShowViewMenu(!showViewMenu)
1600
+ }
1601
+ ),
1602
+ editingEnabled && /* @__PURE__ */ jsx9(
1239
1603
  EditIcon,
1240
1604
  {
1241
1605
  active: editModeEnabled,
1242
1606
  onClick: () => setEditModeEnabled(!editModeEnabled)
1243
1607
  }
1244
1608
  ),
1245
- editingEnabled && editModeEnabled && /* @__PURE__ */ jsx7(
1609
+ editingEnabled && editModeEnabled && /* @__PURE__ */ jsx9(
1246
1610
  GridIcon,
1247
1611
  {
1248
1612
  active: snapToGrid,
1249
1613
  onClick: () => setSnapToGrid(!snapToGrid)
1250
1614
  }
1251
1615
  ),
1252
- spiceSimulationEnabled && /* @__PURE__ */ jsx7(SpiceSimulationIcon, { onClick: () => setShowSpiceOverlay(true) }),
1253
- 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(
1254
1629
  SpiceSimulationOverlay,
1255
1630
  {
1256
1631
  spiceString,