@tscircuit/schematic-viewer 2.0.67 → 2.0.69

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
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  convertCircuitJsonToSchematicSvg
4
4
  } from "circuit-to-svg";
5
- import { su as su6 } from "@tscircuit/soup-util";
5
+ import { su as su7 } from "@tscircuit/soup-util";
6
6
 
7
7
  // lib/hooks/useChangeSchematicComponentLocationsInSvg.ts
8
8
  import "@tscircuit/soup-util";
@@ -461,6 +461,125 @@ function calculateGroupBounds(components, svg) {
461
461
  return bounds;
462
462
  }
463
463
 
464
+ // lib/hooks/useSchematicNetHover.ts
465
+ import { su as su4 } from "@tscircuit/soup-util";
466
+ import { useEffect as useEffect4 } from "react";
467
+ var FADED_CLASS = "sch-net-faded";
468
+ var TRACE_SELECTOR = "g.trace[data-subcircuit-connectivity-map-key], g.trace-overlays[data-subcircuit-connectivity-map-key]";
469
+ var NET_LABEL_SELECTOR = "[data-schematic-net-label-id]";
470
+ var useSchematicNetHover = ({
471
+ svgDivRef,
472
+ circuitJson,
473
+ circuitJsonKey,
474
+ enabled
475
+ }) => {
476
+ useEffect4(() => {
477
+ const svgDiv = svgDivRef.current;
478
+ if (!enabled || !svgDiv) return;
479
+ const { componentIdToKeys, netLabelIdToKey } = buildNetRegistry(circuitJson);
480
+ let netElements = [];
481
+ const triggerNetKeys = /* @__PURE__ */ new Map();
482
+ let hoveredNetKey = null;
483
+ const collectNetElements = () => {
484
+ for (const { el } of netElements) el.classList.remove(FADED_CLASS);
485
+ netElements = [];
486
+ triggerNetKeys.clear();
487
+ hoveredNetKey = null;
488
+ const svg = svgDiv.querySelector("svg");
489
+ if (!svg) return;
490
+ for (const el of Array.from(svg.querySelectorAll(TRACE_SELECTOR))) {
491
+ const key = el.getAttribute("data-subcircuit-connectivity-map-key");
492
+ const keys = /* @__PURE__ */ new Set();
493
+ if (key) {
494
+ keys.add(key);
495
+ triggerNetKeys.set(el, key);
496
+ }
497
+ netElements.push({ el, keys });
498
+ }
499
+ for (const el of Array.from(
500
+ svg.querySelectorAll("g[data-schematic-component-id]")
501
+ )) {
502
+ const id = el.getAttribute("data-schematic-component-id");
503
+ netElements.push({ el, keys: componentIdToKeys.get(id) ?? /* @__PURE__ */ new Set() });
504
+ }
505
+ for (const el of Array.from(svg.querySelectorAll(NET_LABEL_SELECTOR))) {
506
+ const key = netLabelIdToKey.get(
507
+ el.getAttribute("data-schematic-net-label-id")
508
+ );
509
+ const keys = /* @__PURE__ */ new Set();
510
+ if (key) {
511
+ keys.add(key);
512
+ triggerNetKeys.set(el, key);
513
+ }
514
+ netElements.push({ el, keys });
515
+ }
516
+ };
517
+ const highlightNet = (key) => {
518
+ if (key === hoveredNetKey) return;
519
+ hoveredNetKey = key;
520
+ for (const { el, keys } of netElements) {
521
+ el.classList.toggle(FADED_CLASS, key !== null && !keys.has(key));
522
+ }
523
+ };
524
+ const handleMouseOver = (e) => {
525
+ const target = e.target;
526
+ if (!(target instanceof Element)) {
527
+ highlightNet(null);
528
+ return;
529
+ }
530
+ const trigger = target.closest(`${TRACE_SELECTOR}, ${NET_LABEL_SELECTOR}`);
531
+ if (!trigger) {
532
+ highlightNet(null);
533
+ return;
534
+ }
535
+ highlightNet(triggerNetKeys.get(trigger) ?? null);
536
+ };
537
+ const handleMouseLeave = () => highlightNet(null);
538
+ collectNetElements();
539
+ svgDiv.addEventListener("mouseover", handleMouseOver);
540
+ svgDiv.addEventListener("mouseleave", handleMouseLeave);
541
+ const observer = new MutationObserver(collectNetElements);
542
+ observer.observe(svgDiv, { childList: true });
543
+ return () => {
544
+ observer.disconnect();
545
+ svgDiv.removeEventListener("mouseover", handleMouseOver);
546
+ svgDiv.removeEventListener("mouseleave", handleMouseLeave);
547
+ for (const { el } of netElements) el.classList.remove(FADED_CLASS);
548
+ };
549
+ }, [svgDivRef, circuitJsonKey, enabled]);
550
+ };
551
+ function buildNetRegistry(circuitJson) {
552
+ const cju = su4(circuitJson);
553
+ const srcCompToSchComp = /* @__PURE__ */ new Map();
554
+ for (const c of cju.schematic_component.list()) {
555
+ if (c.source_component_id) {
556
+ srcCompToSchComp.set(c.source_component_id, c.schematic_component_id);
557
+ }
558
+ }
559
+ const componentIdToKeys = /* @__PURE__ */ new Map();
560
+ for (const sourceTrace of cju.source_trace.list()) {
561
+ const key = sourceTrace.subcircuit_connectivity_map_key;
562
+ if (!key) continue;
563
+ for (const portId of sourceTrace.connected_source_port_ids ?? []) {
564
+ const schCompId = srcCompToSchComp.get(
565
+ cju.source_port.get(portId)?.source_component_id ?? ""
566
+ );
567
+ if (!schCompId) continue;
568
+ if (!componentIdToKeys.has(schCompId)) {
569
+ componentIdToKeys.set(schCompId, /* @__PURE__ */ new Set());
570
+ }
571
+ componentIdToKeys.get(schCompId).add(key);
572
+ }
573
+ }
574
+ const netLabelIdToKey = /* @__PURE__ */ new Map();
575
+ for (const label of cju.schematic_net_label.list()) {
576
+ if (!label.source_net_id) continue;
577
+ const key = cju.source_net.get(label.source_net_id)?.subcircuit_connectivity_map_key ?? label.source_net_id;
578
+ netLabelIdToKey.set(label.schematic_net_label_id, key);
579
+ }
580
+ return { componentIdToKeys, netLabelIdToKey };
581
+ }
582
+
464
583
  // lib/utils/debug.ts
465
584
  import Debug from "debug";
466
585
  var debug = Debug("schematic-viewer");
@@ -470,7 +589,7 @@ var enableDebug = () => {
470
589
  var debug_default = debug;
471
590
 
472
591
  // lib/components/SchematicViewer.tsx
473
- import { useCallback as useCallback6, useEffect as useEffect12, useMemo as useMemo5, useRef as useRef8, useState as useState7 } from "react";
592
+ import { useCallback as useCallback6, useEffect as useEffect13, useMemo as useMemo5, useRef as useRef8, useState as useState8 } from "react";
474
593
  import {
475
594
  fromString,
476
595
  identity,
@@ -479,11 +598,11 @@ import {
479
598
  import { useMouseMatrixTransform } from "use-mouse-matrix-transform";
480
599
 
481
600
  // lib/hooks/use-resize-handling.ts
482
- import { useEffect as useEffect4, useState } from "react";
601
+ import { useEffect as useEffect5, useState } from "react";
483
602
  var useResizeHandling = (containerRef) => {
484
603
  const [containerWidth, setContainerWidth] = useState(0);
485
604
  const [containerHeight, setContainerHeight] = useState(0);
486
- useEffect4(() => {
605
+ useEffect5(() => {
487
606
  if (!containerRef.current) return;
488
607
  const updateDimensions = () => {
489
608
  const rect = containerRef.current?.getBoundingClientRect();
@@ -503,8 +622,8 @@ var useResizeHandling = (containerRef) => {
503
622
  };
504
623
 
505
624
  // lib/hooks/useComponentDragging.ts
506
- import { su as su4 } from "@tscircuit/soup-util";
507
- import { useCallback, useEffect as useEffect5, useRef as useRef3, useState as useState2 } from "react";
625
+ import { su as su5 } from "@tscircuit/soup-util";
626
+ import { useCallback, useEffect as useEffect6, useRef as useRef3, useState as useState2 } from "react";
508
627
  import { compose as compose2 } from "transformation-matrix";
509
628
  var debug2 = debug_default.extend("useComponentDragging");
510
629
  var useComponentDragging = ({
@@ -527,7 +646,7 @@ var useComponentDragging = ({
527
646
  const componentPositionsRef = useRef3(
528
647
  /* @__PURE__ */ new Map()
529
648
  );
530
- useEffect5(() => {
649
+ useEffect6(() => {
531
650
  editEvents.forEach((event) => {
532
651
  if ("edit_event_type" in event && event.edit_event_type === "edit_schematic_component_location" && !event.in_progress) {
533
652
  componentPositionsRef.current.set(event.schematic_component_id, {
@@ -548,7 +667,7 @@ var useComponentDragging = ({
548
667
  );
549
668
  if (!schematic_component_id) return false;
550
669
  if (cancelDrag) cancelDrag();
551
- const schematic_component = su4(circuitJson).schematic_component.get(
670
+ const schematic_component = su5(circuitJson).schematic_component.get(
552
671
  schematic_component_id
553
672
  );
554
673
  if (!schematic_component) return false;
@@ -664,7 +783,7 @@ var useComponentDragging = ({
664
783
  }, [onEditEvent]);
665
784
  const handleMouseUp = useCallback(() => endDrag(), [endDrag]);
666
785
  const handleTouchEnd = useCallback(() => endDrag(), [endDrag]);
667
- useEffect5(() => {
786
+ useEffect6(() => {
668
787
  window.addEventListener("mousemove", handleMouseMove);
669
788
  window.addEventListener("mouseup", handleMouseUp);
670
789
  window.addEventListener("touchmove", handleTouchMove, { passive: false });
@@ -794,65 +913,15 @@ var GridIcon = ({
794
913
  );
795
914
  };
796
915
 
797
- // lib/components/ViewMenuIcon.tsx
798
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
799
- var ViewMenuIcon = ({
800
- onClick,
801
- active
802
- }) => {
803
- const handleInteraction = (e) => {
804
- e.preventDefault();
805
- onClick();
806
- };
807
- return /* @__PURE__ */ jsx3(
808
- "div",
809
- {
810
- onClick: handleInteraction,
811
- onTouchEnd: handleInteraction,
812
- title: active ? "Hide view menu" : "Show view menu",
813
- style: {
814
- position: "absolute",
815
- top: "16px",
816
- right: "16px",
817
- backgroundColor: active ? "#4CAF50" : "#fff",
818
- color: active ? "#fff" : "#000",
819
- padding: "8px",
820
- borderRadius: "4px",
821
- cursor: "pointer",
822
- boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
823
- display: "flex",
824
- alignItems: "center",
825
- gap: "4px",
826
- zIndex: zIndexMap.viewMenuIcon
827
- },
828
- children: /* @__PURE__ */ jsxs2(
829
- "svg",
830
- {
831
- width: "16",
832
- height: "16",
833
- viewBox: "0 0 24 24",
834
- fill: "none",
835
- stroke: "currentColor",
836
- strokeWidth: "2",
837
- children: [
838
- /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "1" }),
839
- /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "5", r: "1" }),
840
- /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "19", r: "1" })
841
- ]
842
- }
843
- )
844
- }
845
- );
846
- };
847
-
848
916
  // lib/components/ViewMenu.tsx
849
917
  import { useMemo } from "react";
850
- import { su as su5 } from "@tscircuit/soup-util";
918
+ import { su as su6 } from "@tscircuit/soup-util";
919
+ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
851
920
 
852
921
  // package.json
853
922
  var package_default = {
854
923
  name: "@tscircuit/schematic-viewer",
855
- version: "2.0.66",
924
+ version: "2.0.68",
856
925
  main: "dist/index.js",
857
926
  type: "module",
858
927
  scripts: {
@@ -882,7 +951,7 @@ var package_default = {
882
951
  "react-dom": "^19.1.0",
883
952
  "react-reconciler": "^0.31.0",
884
953
  semver: "^7.7.2",
885
- tscircuit: "^0.0.1938",
954
+ tscircuit: "^0.0.2012",
886
955
  tsup: "^8.3.5",
887
956
  vite: "^6.0.3"
888
957
  },
@@ -892,6 +961,7 @@ var package_default = {
892
961
  "circuit-json-to-spice": "*"
893
962
  },
894
963
  dependencies: {
964
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
895
965
  "chart.js": "^4.5.0",
896
966
  debug: "^4.4.0",
897
967
  "performance-now": "^2.1.0",
@@ -900,13 +970,123 @@ var package_default = {
900
970
  }
901
971
  };
902
972
 
973
+ // lib/components/ViewMenuIcon.tsx
974
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
975
+ var ViewMenuIcon = ({
976
+ active = false,
977
+ ...props
978
+ }) => {
979
+ return /* @__PURE__ */ jsx3(
980
+ "button",
981
+ {
982
+ type: "button",
983
+ title: active ? "Hide view menu" : "Show view menu",
984
+ ...props,
985
+ style: {
986
+ position: "absolute",
987
+ top: "16px",
988
+ right: "16px",
989
+ backgroundColor: active ? "#4CAF50" : "#fff",
990
+ color: active ? "#fff" : "#000",
991
+ padding: "8px",
992
+ border: "none",
993
+ borderRadius: "4px",
994
+ cursor: "pointer",
995
+ outline: "none",
996
+ boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
997
+ display: "flex",
998
+ alignItems: "center",
999
+ gap: "4px",
1000
+ zIndex: zIndexMap.viewMenuIcon
1001
+ },
1002
+ children: /* @__PURE__ */ jsxs2(
1003
+ "svg",
1004
+ {
1005
+ width: "16",
1006
+ height: "16",
1007
+ viewBox: "0 0 24 24",
1008
+ fill: "none",
1009
+ stroke: "currentColor",
1010
+ strokeWidth: "2",
1011
+ children: [
1012
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "1" }),
1013
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "5", r: "1" }),
1014
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "19", r: "1" })
1015
+ ]
1016
+ }
1017
+ )
1018
+ }
1019
+ );
1020
+ };
1021
+
903
1022
  // lib/components/ViewMenu.tsx
904
- import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1023
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1024
+ var FONT_FAMILY = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1025
+ var contentStyles = {
1026
+ backgroundColor: "#ffffff",
1027
+ color: "#111111",
1028
+ borderRadius: 8,
1029
+ boxShadow: "0 6px 24px rgba(0,0,0,0.12), 0 1px 3px rgba(0,0,0,0.08)",
1030
+ border: "1px solid #e5e7eb",
1031
+ padding: 4,
1032
+ minWidth: 224,
1033
+ fontSize: 13,
1034
+ fontFamily: FONT_FAMILY,
1035
+ outline: "none",
1036
+ zIndex: zIndexMap.viewMenu
1037
+ };
1038
+ var itemStyles = {
1039
+ display: "flex",
1040
+ alignItems: "center",
1041
+ gap: 8,
1042
+ padding: "7px 10px 7px 8px",
1043
+ borderRadius: 6,
1044
+ cursor: "pointer",
1045
+ outline: "none",
1046
+ userSelect: "none",
1047
+ color: "#111111",
1048
+ fontSize: 13,
1049
+ fontFamily: FONT_FAMILY
1050
+ };
1051
+ var iconSlotStyles = {
1052
+ width: 16,
1053
+ height: 16,
1054
+ flexShrink: 0,
1055
+ display: "inline-flex",
1056
+ alignItems: "center",
1057
+ justifyContent: "center",
1058
+ color: "#111111"
1059
+ };
1060
+ var separatorStyles = {
1061
+ height: 1,
1062
+ backgroundColor: "#ececec",
1063
+ margin: "4px 0"
1064
+ };
1065
+ var HIGHLIGHT_CSS = `
1066
+ .sv-vm-item[data-highlighted]:not([data-disabled]),
1067
+ .sv-vm-item:hover:not([data-disabled]) { background-color: #f1f3f5; }
1068
+ .sv-vm-item[data-disabled] { opacity: 0.45; cursor: not-allowed; }
1069
+ `;
1070
+ var CheckIcon = () => /* @__PURE__ */ jsx4(
1071
+ "svg",
1072
+ {
1073
+ width: "14",
1074
+ height: "14",
1075
+ viewBox: "0 0 24 24",
1076
+ fill: "none",
1077
+ stroke: "currentColor",
1078
+ strokeWidth: "2.5",
1079
+ strokeLinecap: "round",
1080
+ strokeLinejoin: "round",
1081
+ "aria-hidden": "true",
1082
+ children: /* @__PURE__ */ jsx4("path", { d: "M20 6 9 17l-5-5" })
1083
+ }
1084
+ );
905
1085
  var ViewMenu = ({
906
1086
  circuitJson,
907
1087
  circuitJsonKey,
908
- isVisible,
909
- onClose,
1088
+ open,
1089
+ onOpenChange,
910
1090
  showGroups,
911
1091
  onToggleGroups,
912
1092
  showGrid,
@@ -915,13 +1095,13 @@ var ViewMenu = ({
915
1095
  const hasGroups = useMemo(() => {
916
1096
  if (!circuitJson || circuitJson.length === 0) return false;
917
1097
  try {
918
- const sourceGroups = su5(circuitJson).source_group?.list() || [];
1098
+ const sourceGroups = su6(circuitJson).source_group?.list() || [];
919
1099
  if (sourceGroups.length > 0) return true;
920
- const schematicComponents = su5(circuitJson).schematic_component?.list() || [];
1100
+ const schematicComponents = su6(circuitJson).schematic_component?.list() || [];
921
1101
  if (schematicComponents.length > 1) {
922
1102
  const componentTypes = /* @__PURE__ */ new Set();
923
1103
  for (const comp of schematicComponents) {
924
- const sourceComp = su5(circuitJson).source_component.get(
1104
+ const sourceComp = su6(circuitJson).source_component.get(
925
1105
  comp.source_component_id
926
1106
  );
927
1107
  if (sourceComp?.ftype) {
@@ -936,166 +1116,58 @@ var ViewMenu = ({
936
1116
  return false;
937
1117
  }
938
1118
  }, [circuitJsonKey]);
939
- if (!isVisible) return null;
940
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
941
- /* @__PURE__ */ jsx4(
942
- "div",
1119
+ return /* @__PURE__ */ jsxs3(DropdownMenu.Root, { open, onOpenChange, modal: false, children: [
1120
+ /* @__PURE__ */ jsx4(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsx4(ViewMenuIcon, { active: open }) }),
1121
+ /* @__PURE__ */ jsx4(DropdownMenu.Portal, { children: /* @__PURE__ */ jsxs3(
1122
+ DropdownMenu.Content,
943
1123
  {
944
- onClick: onClose,
945
- onTouchEnd: (e) => {
946
- e.preventDefault();
947
- onClose();
948
- },
949
- style: {
950
- position: "absolute",
951
- inset: 0,
952
- backgroundColor: "transparent",
953
- zIndex: zIndexMap.viewMenuBackdrop
954
- }
955
- }
956
- ),
957
- /* @__PURE__ */ jsxs3(
958
- "div",
959
- {
960
- style: {
961
- position: "absolute",
962
- top: "56px",
963
- right: "16px",
964
- backgroundColor: "#ffffff",
965
- color: "#000000",
966
- border: "1px solid #ccc",
967
- borderRadius: "4px",
968
- boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
969
- minWidth: "200px",
970
- zIndex: zIndexMap.viewMenu
971
- },
1124
+ style: contentStyles,
1125
+ side: "bottom",
1126
+ align: "end",
1127
+ sideOffset: 8,
1128
+ collisionPadding: 10,
972
1129
  children: [
1130
+ /* @__PURE__ */ jsx4("style", { children: HIGHLIGHT_CSS }),
973
1131
  /* @__PURE__ */ jsxs3(
974
- "div",
1132
+ DropdownMenu.Item,
975
1133
  {
976
- onClick: () => {
977
- if (hasGroups) {
978
- onToggleGroups(!showGroups);
979
- }
980
- },
981
- onTouchEnd: (e) => {
982
- e.preventDefault();
983
- if (hasGroups) {
984
- onToggleGroups(!showGroups);
985
- }
986
- },
987
- style: {
988
- padding: "8px 12px",
989
- cursor: hasGroups ? "pointer" : "not-allowed",
990
- opacity: hasGroups ? 1 : 0.5,
991
- fontSize: "13px",
992
- color: "#000000",
993
- fontFamily: "sans-serif",
994
- display: "flex",
995
- alignItems: "center",
996
- gap: "8px"
997
- },
998
- onMouseEnter: (e) => {
999
- if (hasGroups) {
1000
- e.currentTarget.style.backgroundColor = "#f0f0f0";
1001
- }
1002
- },
1003
- onMouseLeave: (e) => {
1004
- if (hasGroups) {
1005
- e.currentTarget.style.backgroundColor = "transparent";
1006
- }
1134
+ className: "sv-vm-item",
1135
+ style: itemStyles,
1136
+ disabled: !hasGroups,
1137
+ title: hasGroups ? void 0 : "No groups found in this schematic",
1138
+ onSelect: (e) => e.preventDefault(),
1139
+ onPointerUp: () => {
1140
+ if (hasGroups) onToggleGroups(!showGroups);
1007
1141
  },
1008
1142
  children: [
1009
- /* @__PURE__ */ jsx4(
1010
- "div",
1011
- {
1012
- style: {
1013
- width: "16px",
1014
- height: "16px",
1015
- border: "2px solid #000",
1016
- borderRadius: "2px",
1017
- backgroundColor: "transparent",
1018
- display: "flex",
1019
- alignItems: "center",
1020
- justifyContent: "center",
1021
- fontSize: "10px",
1022
- fontWeight: "bold"
1023
- },
1024
- children: showGroups && "\u2713"
1025
- }
1026
- ),
1027
- "View Schematic Groups"
1143
+ /* @__PURE__ */ jsx4("span", { style: iconSlotStyles, children: showGroups && /* @__PURE__ */ jsx4(CheckIcon, {}) }),
1144
+ /* @__PURE__ */ jsx4("span", { children: "View Schematic Groups" })
1028
1145
  ]
1029
1146
  }
1030
1147
  ),
1031
- !hasGroups && /* @__PURE__ */ jsx4(
1032
- "div",
1033
- {
1034
- style: {
1035
- padding: "8px 12px",
1036
- fontSize: "11px",
1037
- color: "#666",
1038
- fontStyle: "italic"
1039
- },
1040
- children: "No groups found in this schematic"
1041
- }
1042
- ),
1043
1148
  /* @__PURE__ */ jsxs3(
1044
- "div",
1149
+ DropdownMenu.Item,
1045
1150
  {
1046
- onClick: () => onToggleGrid(!showGrid),
1047
- onTouchEnd: (e) => {
1048
- e.preventDefault();
1049
- onToggleGrid(!showGrid);
1050
- },
1051
- style: {
1052
- padding: "8px 12px",
1053
- cursor: "pointer",
1054
- fontSize: "13px",
1055
- color: "#000000",
1056
- fontFamily: "sans-serif",
1057
- display: "flex",
1058
- alignItems: "center",
1059
- gap: "8px"
1060
- },
1061
- onMouseEnter: (e) => {
1062
- e.currentTarget.style.backgroundColor = "#f0f0f0";
1063
- },
1064
- onMouseLeave: (e) => {
1065
- e.currentTarget.style.backgroundColor = "transparent";
1066
- },
1151
+ className: "sv-vm-item",
1152
+ style: itemStyles,
1153
+ onSelect: (e) => e.preventDefault(),
1154
+ onPointerUp: () => onToggleGrid(!showGrid),
1067
1155
  children: [
1068
- /* @__PURE__ */ jsx4(
1069
- "div",
1070
- {
1071
- style: {
1072
- width: "16px",
1073
- height: "16px",
1074
- border: "2px solid #000",
1075
- borderRadius: "2px",
1076
- backgroundColor: "transparent",
1077
- display: "flex",
1078
- alignItems: "center",
1079
- justifyContent: "center",
1080
- fontSize: "10px",
1081
- fontWeight: "bold"
1082
- },
1083
- children: showGrid && "\u2713"
1084
- }
1085
- ),
1086
- "Show Grid"
1156
+ /* @__PURE__ */ jsx4("span", { style: iconSlotStyles, children: showGrid && /* @__PURE__ */ jsx4(CheckIcon, {}) }),
1157
+ /* @__PURE__ */ jsx4("span", { children: "Show Grid" })
1087
1158
  ]
1088
1159
  }
1089
1160
  ),
1161
+ /* @__PURE__ */ jsx4(DropdownMenu.Separator, { style: separatorStyles }),
1090
1162
  /* @__PURE__ */ jsxs3(
1091
1163
  "div",
1092
1164
  {
1093
1165
  style: {
1094
1166
  padding: "4px 8px",
1095
- fontSize: "12px",
1096
- color: "#999",
1097
- borderTop: "1px solid #eee",
1098
- textAlign: "center"
1167
+ fontSize: 12,
1168
+ color: "#9ca3af",
1169
+ textAlign: "center",
1170
+ fontFamily: FONT_FAMILY
1099
1171
  },
1100
1172
  children: [
1101
1173
  "v",
@@ -1105,7 +1177,7 @@ var ViewMenu = ({
1105
1177
  )
1106
1178
  ]
1107
1179
  }
1108
- )
1180
+ ) })
1109
1181
  ] });
1110
1182
  };
1111
1183
 
@@ -1359,7 +1431,7 @@ var SpicePlot = ({
1359
1431
  };
1360
1432
 
1361
1433
  // lib/components/SpiceSimulationOverlay.tsx
1362
- import { useEffect as useEffect6, useState as useState3 } from "react";
1434
+ import { useEffect as useEffect7, useState as useState3 } from "react";
1363
1435
  import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1364
1436
  var SpiceSimulationOverlay = ({
1365
1437
  spiceString,
@@ -1378,7 +1450,7 @@ var SpiceSimulationOverlay = ({
1378
1450
  const [durationDraft, setDurationDraft] = useState3(
1379
1451
  String(simOptions.duration)
1380
1452
  );
1381
- useEffect6(() => {
1453
+ useEffect7(() => {
1382
1454
  setStartTimeDraft(String(simOptions.startTime));
1383
1455
  setDurationDraft(String(simOptions.duration));
1384
1456
  }, [simOptions.startTime, simOptions.duration]);
@@ -1625,7 +1697,7 @@ var SpiceSimulationOverlay = ({
1625
1697
  };
1626
1698
 
1627
1699
  // lib/hooks/useSpiceSimulation.ts
1628
- import { useState as useState4, useEffect as useEffect7 } from "react";
1700
+ import { useState as useState4, useEffect as useEffect8 } from "react";
1629
1701
 
1630
1702
  // lib/workers/spice-simulation.worker.blob.js
1631
1703
  var b64 = "dmFyIGU9bnVsbCxzPWFzeW5jKCk9Pihhd2FpdCBpbXBvcnQoImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vZWVjaXJjdWl0LWVuZ2luZUAxLjUuMi8rZXNtIikpLlNpbXVsYXRpb24sYz1hc3luYygpPT57aWYoZSYmZS5pc0luaXRpYWxpemVkKCkpcmV0dXJuO2xldCBpPWF3YWl0IHMoKTtlPW5ldyBpLGF3YWl0IGUuc3RhcnQoKX07c2VsZi5vbm1lc3NhZ2U9YXN5bmMgaT0+e3RyeXtpZihhd2FpdCBjKCksIWUpdGhyb3cgbmV3IEVycm9yKCJTaW11bGF0aW9uIG5vdCBpbml0aWFsaXplZCIpO2xldCB0PWkuZGF0YS5zcGljZVN0cmluZyxhPXQubWF0Y2goL3dyZGF0YVxzKyhcUyspXHMrKC4qKS9pKTtpZihhKXtsZXQgbz1gLnByb2JlICR7YVsyXS50cmltKCkuc3BsaXQoL1xzKy8pLmpvaW4oIiAiKX1gO3Q9dC5yZXBsYWNlKC93cmRhdGEuKi9pLG8pfWVsc2UgaWYoIXQubWF0Y2goL1wucHJvYmUvaSkpdGhyb3cgdC5tYXRjaCgvcGxvdFxzKyguKikvaSk/bmV3IEVycm9yKCJUaGUgJ3Bsb3QnIGNvbW1hbmQgaXMgbm90IHN1cHBvcnRlZCBmb3IgZGF0YSBleHRyYWN0aW9uLiBQbGVhc2UgdXNlICd3cmRhdGEgPGZpbGVuYW1lPiA8dmFyMT4gLi4uJyBvciAnLnByb2JlIDx2YXIxPiAuLi4nIGluc3RlYWQuIik6bmV3IEVycm9yKCJObyAnLnByb2JlJyBvciAnd3JkYXRhJyBjb21tYW5kIGZvdW5kIGluIFNQSUNFIGZpbGUuIFVzZSAnd3JkYXRhIDxmaWxlbmFtZT4gPHZhcjE+IC4uLicgdG8gc3BlY2lmeSBvdXRwdXQuIik7ZS5zZXROZXRMaXN0KHQpO2xldCBuPWF3YWl0IGUucnVuU2ltKCk7c2VsZi5wb3N0TWVzc2FnZSh7dHlwZToicmVzdWx0IixyZXN1bHQ6bn0pfWNhdGNoKHQpe3NlbGYucG9zdE1lc3NhZ2Uoe3R5cGU6ImVycm9yIixlcnJvcjp0Lm1lc3NhZ2V9KX19Owo=";
@@ -1680,7 +1752,7 @@ var useSpiceSimulation = (spiceString) => {
1680
1752
  const [nodes, setNodes] = useState4([]);
1681
1753
  const [isLoading, setIsLoading] = useState4(true);
1682
1754
  const [error, setError] = useState4(null);
1683
- useEffect7(() => {
1755
+ useEffect8(() => {
1684
1756
  if (!spiceString) {
1685
1757
  setIsLoading(false);
1686
1758
  setPlotData([]);
@@ -1818,6 +1890,10 @@ var getSpiceFromCircuitJson = (circuitJson, options) => {
1818
1890
 
1819
1891
  // lib/hooks/useLocalStorage.ts
1820
1892
  import { useCallback as useCallback2 } from "react";
1893
+ var STORAGE_KEYS = {
1894
+ IS_SHOWING_SCHEMATIC_GROUPS: "schematic_viewer_show_groups",
1895
+ SELECTED_SCHEMATIC_SHEET: "schematic_viewer_selected_sheet"
1896
+ };
1821
1897
  var getStoredBoolean = (key, defaultValue) => {
1822
1898
  if (typeof window === "undefined") return defaultValue;
1823
1899
  try {
@@ -1834,17 +1910,32 @@ var setStoredBoolean = (key, value) => {
1834
1910
  } catch {
1835
1911
  }
1836
1912
  };
1913
+ var getStoredString = (key) => {
1914
+ if (typeof window === "undefined") return null;
1915
+ try {
1916
+ return localStorage.getItem(key);
1917
+ } catch {
1918
+ return null;
1919
+ }
1920
+ };
1921
+ var setStoredString = (key, value) => {
1922
+ if (typeof window === "undefined") return;
1923
+ try {
1924
+ localStorage.setItem(key, value);
1925
+ } catch {
1926
+ }
1927
+ };
1837
1928
 
1838
1929
  // lib/components/MouseTracker.tsx
1839
1930
  import {
1840
1931
  createContext,
1841
1932
  useCallback as useCallback3,
1842
1933
  useContext,
1843
- useEffect as useEffect8,
1934
+ useEffect as useEffect9,
1844
1935
  useMemo as useMemo3,
1845
1936
  useRef as useRef4
1846
1937
  } from "react";
1847
- import { Fragment as Fragment2, jsx as jsx9 } from "react/jsx-runtime";
1938
+ import { Fragment, jsx as jsx9 } from "react/jsx-runtime";
1848
1939
  var MouseTrackerContext = createContext(null);
1849
1940
  var DRAG_THRESHOLD_PX = 5;
1850
1941
  var boundsAreEqual = (a, b) => {
@@ -1855,7 +1946,7 @@ var boundsAreEqual = (a, b) => {
1855
1946
  var MouseTracker = ({ children }) => {
1856
1947
  const existingContext = useContext(MouseTrackerContext);
1857
1948
  if (existingContext) {
1858
- return /* @__PURE__ */ jsx9(Fragment2, { children });
1949
+ return /* @__PURE__ */ jsx9(Fragment, { children });
1859
1950
  }
1860
1951
  const storeRef = useRef4({
1861
1952
  pointer: null,
@@ -1924,7 +2015,7 @@ var MouseTracker = ({ children }) => {
1924
2015
  const isHovering = useCallback3((id) => {
1925
2016
  return storeRef.current.hoveringIds.has(id);
1926
2017
  }, []);
1927
- useEffect8(() => {
2018
+ useEffect9(() => {
1928
2019
  const handlePointerPosition = (event) => {
1929
2020
  const { clientX, clientY } = event;
1930
2021
  const pointer = storeRef.current.pointer;
@@ -2012,12 +2103,12 @@ var MouseTracker = ({ children }) => {
2012
2103
  };
2013
2104
 
2014
2105
  // lib/components/SchematicComponentMouseTarget.tsx
2015
- import { useCallback as useCallback4, useEffect as useEffect10, useRef as useRef6, useState as useState5 } from "react";
2106
+ import { useCallback as useCallback4, useEffect as useEffect11, useRef as useRef6, useState as useState5 } from "react";
2016
2107
 
2017
2108
  // lib/hooks/useMouseEventsOverBoundingBox.ts
2018
2109
  import {
2019
2110
  useContext as useContext2,
2020
- useEffect as useEffect9,
2111
+ useEffect as useEffect10,
2021
2112
  useId,
2022
2113
  useMemo as useMemo4,
2023
2114
  useRef as useRef5,
@@ -2039,7 +2130,7 @@ var useMouseEventsOverBoundingBox = (options) => {
2039
2130
  },
2040
2131
  []
2041
2132
  );
2042
- useEffect9(() => {
2133
+ useEffect10(() => {
2043
2134
  context.registerBoundingBox(id, {
2044
2135
  bounds: latestOptionsRef.current.bounds,
2045
2136
  onClick: latestOptionsRef.current.onClick ? handleClick : void 0
@@ -2048,7 +2139,7 @@ var useMouseEventsOverBoundingBox = (options) => {
2048
2139
  context.unregisterBoundingBox(id);
2049
2140
  };
2050
2141
  }, [context, handleClick, id]);
2051
- useEffect9(() => {
2142
+ useEffect10(() => {
2052
2143
  context.updateBoundingBox(id, {
2053
2144
  bounds: latestOptionsRef.current.bounds,
2054
2145
  onClick: latestOptionsRef.current.onClick ? handleClick : void 0
@@ -2128,10 +2219,10 @@ var SchematicComponentMouseTarget = ({
2128
2219
  if (frameRef.current !== null) return;
2129
2220
  frameRef.current = window.requestAnimationFrame(measure);
2130
2221
  }, [measure]);
2131
- useEffect10(() => {
2222
+ useEffect11(() => {
2132
2223
  scheduleMeasure();
2133
2224
  }, [scheduleMeasure, circuitJsonKey]);
2134
- useEffect10(() => {
2225
+ useEffect11(() => {
2135
2226
  scheduleMeasure();
2136
2227
  const svgDiv = svgDivRef.current;
2137
2228
  const container = containerRef.current;
@@ -2176,7 +2267,7 @@ var SchematicComponentMouseTarget = ({
2176
2267
  bounds,
2177
2268
  onClick: onComponentClick ? handleClick : void 0
2178
2269
  });
2179
- useEffect10(() => {
2270
+ useEffect11(() => {
2180
2271
  if (onHoverChange) {
2181
2272
  onHoverChange(componentId, hovering);
2182
2273
  }
@@ -2203,8 +2294,8 @@ var SchematicComponentMouseTarget = ({
2203
2294
  };
2204
2295
 
2205
2296
  // lib/components/SchematicPortMouseTarget.tsx
2206
- import { useCallback as useCallback5, useEffect as useEffect11, useRef as useRef7, useState as useState6 } from "react";
2207
- import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
2297
+ import { useCallback as useCallback5, useEffect as useEffect12, useRef as useRef7, useState as useState6 } from "react";
2298
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
2208
2299
  var areMeasurementsEqual2 = (a, b) => {
2209
2300
  if (!a && !b) return true;
2210
2301
  if (!a || !b) return false;
@@ -2262,10 +2353,10 @@ var SchematicPortMouseTarget = ({
2262
2353
  if (frameRef.current !== null) return;
2263
2354
  frameRef.current = window.requestAnimationFrame(measure);
2264
2355
  }, [measure]);
2265
- useEffect11(() => {
2356
+ useEffect12(() => {
2266
2357
  scheduleMeasure();
2267
2358
  }, [scheduleMeasure, circuitJsonKey]);
2268
- useEffect11(() => {
2359
+ useEffect12(() => {
2269
2360
  scheduleMeasure();
2270
2361
  const svgDiv = svgDivRef.current;
2271
2362
  const container = containerRef.current;
@@ -2310,7 +2401,7 @@ var SchematicPortMouseTarget = ({
2310
2401
  bounds,
2311
2402
  onClick: onPortClick ? handleClick : void 0
2312
2403
  });
2313
- useEffect11(() => {
2404
+ useEffect12(() => {
2314
2405
  if (onHoverChange) {
2315
2406
  onHoverChange(portId, hovering);
2316
2407
  }
@@ -2319,7 +2410,7 @@ var SchematicPortMouseTarget = ({
2319
2410
  return null;
2320
2411
  }
2321
2412
  const rect = measurement.rect;
2322
- return /* @__PURE__ */ jsxs6(Fragment3, { children: [
2413
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
2323
2414
  /* @__PURE__ */ jsx11(
2324
2415
  "div",
2325
2416
  {
@@ -2362,8 +2453,175 @@ var SchematicPortMouseTarget = ({
2362
2453
  ] });
2363
2454
  };
2364
2455
 
2456
+ // lib/components/SchematicSheetSelector.tsx
2457
+ import { useState as useState7 } from "react";
2458
+ import * as DropdownMenu2 from "@radix-ui/react-dropdown-menu";
2459
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
2460
+ var FONT_FAMILY2 = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2461
+ var contentStyles2 = {
2462
+ backgroundColor: "#ffffff",
2463
+ color: "#111111",
2464
+ borderRadius: 8,
2465
+ boxShadow: "0 6px 24px rgba(0,0,0,0.12), 0 1px 3px rgba(0,0,0,0.08)",
2466
+ border: "1px solid #e5e7eb",
2467
+ padding: 4,
2468
+ minWidth: 200,
2469
+ maxWidth: 320,
2470
+ maxHeight: 320,
2471
+ overflowY: "auto",
2472
+ fontSize: 13,
2473
+ fontFamily: FONT_FAMILY2,
2474
+ outline: "none",
2475
+ zIndex: zIndexMap.viewMenu
2476
+ };
2477
+ var itemStyles2 = {
2478
+ display: "flex",
2479
+ alignItems: "center",
2480
+ gap: 8,
2481
+ padding: "7px 10px 7px 8px",
2482
+ borderRadius: 6,
2483
+ cursor: "pointer",
2484
+ outline: "none",
2485
+ userSelect: "none",
2486
+ color: "#111111",
2487
+ fontSize: 13,
2488
+ fontFamily: FONT_FAMILY2
2489
+ };
2490
+ var iconSlotStyles2 = {
2491
+ width: 16,
2492
+ height: 16,
2493
+ flexShrink: 0,
2494
+ display: "inline-flex",
2495
+ alignItems: "center",
2496
+ justifyContent: "center",
2497
+ color: "#111111"
2498
+ };
2499
+ var ellipsisStyles = {
2500
+ overflow: "hidden",
2501
+ textOverflow: "ellipsis",
2502
+ whiteSpace: "nowrap"
2503
+ };
2504
+ var MENU_CSS = `
2505
+ .sv-sheet-item[data-highlighted], .sv-sheet-item:hover { background-color: #f1f3f5; }
2506
+ .sv-sheet-chevron { transition: transform 0.2s ease; }
2507
+ [data-state="open"] > .sv-sheet-chevron { transform: rotate(180deg); }
2508
+ `;
2509
+ var CheckIcon2 = () => /* @__PURE__ */ jsx12(
2510
+ "svg",
2511
+ {
2512
+ width: "14",
2513
+ height: "14",
2514
+ viewBox: "0 0 24 24",
2515
+ fill: "none",
2516
+ stroke: "currentColor",
2517
+ strokeWidth: "2.5",
2518
+ strokeLinecap: "round",
2519
+ strokeLinejoin: "round",
2520
+ "aria-hidden": "true",
2521
+ children: /* @__PURE__ */ jsx12("path", { d: "M20 6 9 17l-5-5" })
2522
+ }
2523
+ );
2524
+ var ChevronDownIcon = ({ className }) => /* @__PURE__ */ jsx12(
2525
+ "svg",
2526
+ {
2527
+ className,
2528
+ width: "14",
2529
+ height: "14",
2530
+ viewBox: "0 0 24 24",
2531
+ fill: "none",
2532
+ stroke: "currentColor",
2533
+ strokeWidth: "2",
2534
+ strokeLinecap: "round",
2535
+ strokeLinejoin: "round",
2536
+ style: { opacity: 0.6, flexShrink: 0 },
2537
+ "aria-hidden": "true",
2538
+ children: /* @__PURE__ */ jsx12("path", { d: "m6 9 6 6 6-6" })
2539
+ }
2540
+ );
2541
+ var SchematicSheetSelector = ({
2542
+ sheets,
2543
+ selectedSheetId,
2544
+ onSelectSheet
2545
+ }) => {
2546
+ const [open, setOpen] = useState7(false);
2547
+ if (sheets.length <= 1) return null;
2548
+ const selectedSheet = sheets.find(
2549
+ (s) => s.schematic_sheet_id === selectedSheetId
2550
+ );
2551
+ const selectedLabel = selectedSheet?.name ?? "Select sheet";
2552
+ return /* @__PURE__ */ jsxs7(Fragment3, { children: [
2553
+ /* @__PURE__ */ jsx12("style", { children: MENU_CSS }),
2554
+ /* @__PURE__ */ jsxs7(DropdownMenu2.Root, { open, onOpenChange: setOpen, modal: false, children: [
2555
+ /* @__PURE__ */ jsx12(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs7(
2556
+ "button",
2557
+ {
2558
+ type: "button",
2559
+ title: selectedLabel,
2560
+ onPointerDown: (e) => e.stopPropagation(),
2561
+ style: {
2562
+ position: "absolute",
2563
+ top: "16px",
2564
+ left: "16px",
2565
+ display: "flex",
2566
+ alignItems: "center",
2567
+ gap: "6px",
2568
+ maxWidth: "220px",
2569
+ padding: "8px 12px",
2570
+ backgroundColor: "#ffffff",
2571
+ color: "#000000",
2572
+ border: "none",
2573
+ outline: "none",
2574
+ borderRadius: "4px",
2575
+ cursor: "pointer",
2576
+ boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
2577
+ fontSize: "13px",
2578
+ fontFamily: FONT_FAMILY2,
2579
+ zIndex: zIndexMap.viewMenuIcon
2580
+ },
2581
+ children: [
2582
+ /* @__PURE__ */ jsx12("span", { style: { color: "#888888", flexShrink: 0 }, children: "Sheet:" }),
2583
+ /* @__PURE__ */ jsx12("span", { style: { ...ellipsisStyles, minWidth: 0 }, children: selectedLabel }),
2584
+ /* @__PURE__ */ jsx12(ChevronDownIcon, { className: "sv-sheet-chevron" })
2585
+ ]
2586
+ }
2587
+ ) }),
2588
+ /* @__PURE__ */ jsx12(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsx12(
2589
+ DropdownMenu2.Content,
2590
+ {
2591
+ style: contentStyles2,
2592
+ side: "bottom",
2593
+ align: "start",
2594
+ sideOffset: 8,
2595
+ collisionPadding: 10,
2596
+ children: sheets.map((sheet) => {
2597
+ const selected = sheet.schematic_sheet_id === selectedSheetId;
2598
+ return /* @__PURE__ */ jsxs7(
2599
+ DropdownMenu2.Item,
2600
+ {
2601
+ className: "sv-sheet-item",
2602
+ style: itemStyles2,
2603
+ title: sheet.name,
2604
+ onSelect: (e) => e.preventDefault(),
2605
+ onPointerUp: () => {
2606
+ onSelectSheet(sheet.schematic_sheet_id);
2607
+ setOpen(false);
2608
+ },
2609
+ children: [
2610
+ /* @__PURE__ */ jsx12("span", { style: iconSlotStyles2, children: selected && /* @__PURE__ */ jsx12(CheckIcon2, {}) }),
2611
+ /* @__PURE__ */ jsx12("span", { style: { ...ellipsisStyles, minWidth: 0 }, children: sheet.name })
2612
+ ]
2613
+ },
2614
+ sheet.schematic_sheet_id
2615
+ );
2616
+ })
2617
+ }
2618
+ ) })
2619
+ ] })
2620
+ ] });
2621
+ };
2622
+
2365
2623
  // lib/components/SchematicViewer.tsx
2366
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
2624
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
2367
2625
  var SchematicViewer = ({
2368
2626
  circuitJson,
2369
2627
  containerStyle,
@@ -2377,17 +2635,19 @@ var SchematicViewer = ({
2377
2635
  colorOverrides,
2378
2636
  spiceSimulationEnabled = false,
2379
2637
  disableGroups = false,
2638
+ netHoverHighlightEnabled = true,
2380
2639
  onSchematicComponentClicked,
2381
2640
  showSchematicPorts = false,
2382
2641
  onSchematicPortClicked,
2642
+ onSchematicSheetChange,
2383
2643
  css,
2384
2644
  className
2385
2645
  }) => {
2386
2646
  if (debug3) {
2387
2647
  enableDebug();
2388
2648
  }
2389
- const [showSpiceOverlay, setShowSpiceOverlay] = useState7(false);
2390
- const [spiceSimOptions, setSpiceSimOptions] = useState7({
2649
+ const [showSpiceOverlay, setShowSpiceOverlay] = useState8(false);
2650
+ const [spiceSimOptions, setSpiceSimOptions] = useState8({
2391
2651
  showVoltage: true,
2392
2652
  showCurrent: false,
2393
2653
  startTime: 0,
@@ -2402,6 +2662,40 @@ var SchematicViewer = ({
2402
2662
  () => getCircuitHash(circuitJson),
2403
2663
  [circuitJson]
2404
2664
  );
2665
+ const schematicSheets = useMemo5(() => {
2666
+ try {
2667
+ return circuitJson.filter((elm) => elm?.type === "schematic_sheet").slice().sort((a, b) => (a.sheet_index ?? 0) - (b.sheet_index ?? 0));
2668
+ } catch (err) {
2669
+ console.error("Failed to derive schematic sheets", err);
2670
+ return [];
2671
+ }
2672
+ }, [circuitJsonKey]);
2673
+ const hasMultipleSheets = schematicSheets.length > 1;
2674
+ const defaultSheetId = schematicSheets[0]?.schematic_sheet_id;
2675
+ const [selectedSheetId, setSelectedSheetId] = useState8(
2676
+ () => {
2677
+ const stored = getStoredString(STORAGE_KEYS.SELECTED_SCHEMATIC_SHEET);
2678
+ if (stored && schematicSheets.some((s) => s.schematic_sheet_id === stored)) {
2679
+ return stored;
2680
+ }
2681
+ return defaultSheetId;
2682
+ }
2683
+ );
2684
+ useEffect13(() => {
2685
+ const stillExists = selectedSheetId !== void 0 && schematicSheets.some((s) => s.schematic_sheet_id === selectedSheetId);
2686
+ if (!stillExists) {
2687
+ setSelectedSheetId(defaultSheetId);
2688
+ }
2689
+ }, [circuitJsonKey]);
2690
+ const activeSheetId = hasMultipleSheets ? selectedSheetId ?? defaultSheetId : void 0;
2691
+ const handleSelectSheet = useCallback6(
2692
+ (sheetId) => {
2693
+ setSelectedSheetId(sheetId);
2694
+ setStoredString(STORAGE_KEYS.SELECTED_SCHEMATIC_SHEET, sheetId);
2695
+ onSchematicSheetChange?.(sheetId);
2696
+ },
2697
+ [onSchematicSheetChange]
2698
+ );
2405
2699
  const spiceString = useMemo5(() => {
2406
2700
  if (!spiceSimulationEnabled) return null;
2407
2701
  try {
@@ -2416,8 +2710,8 @@ var SchematicViewer = ({
2416
2710
  spiceSimOptions.startTime,
2417
2711
  spiceSimOptions.duration
2418
2712
  ]);
2419
- const [hasSpiceSimRun, setHasSpiceSimRun] = useState7(false);
2420
- useEffect12(() => {
2713
+ const [hasSpiceSimRun, setHasSpiceSimRun] = useState8(false);
2714
+ useEffect13(() => {
2421
2715
  setHasSpiceSimRun(false);
2422
2716
  }, [circuitJsonKey]);
2423
2717
  const {
@@ -2426,19 +2720,19 @@ var SchematicViewer = ({
2426
2720
  isLoading: isSpiceSimLoading,
2427
2721
  error: spiceSimError
2428
2722
  } = useSpiceSimulation(hasSpiceSimRun ? spiceString : null);
2429
- const [editModeEnabled, setEditModeEnabled] = useState7(defaultEditMode);
2430
- const [snapToGrid, setSnapToGrid] = useState7(true);
2431
- const [showGridInternal, setShowGridInternal] = useState7(false);
2723
+ const [editModeEnabled, setEditModeEnabled] = useState8(defaultEditMode);
2724
+ const [snapToGrid, setSnapToGrid] = useState8(true);
2725
+ const [showGridInternal, setShowGridInternal] = useState8(false);
2432
2726
  const showGrid = debugGrid || showGridInternal;
2433
- const [isInteractionEnabled, setIsInteractionEnabled] = useState7(
2727
+ const [isInteractionEnabled, setIsInteractionEnabled] = useState8(
2434
2728
  !clickToInteractEnabled
2435
2729
  );
2436
- const [showViewMenu, setShowViewMenu] = useState7(false);
2437
- const [showSchematicGroups, setShowSchematicGroups] = useState7(() => {
2730
+ const [showViewMenu, setShowViewMenu] = useState8(false);
2731
+ const [showSchematicGroups, setShowSchematicGroups] = useState8(() => {
2438
2732
  if (disableGroups) return false;
2439
2733
  return getStoredBoolean("schematic_viewer_show_groups", false);
2440
2734
  });
2441
- const [isHoveringClickableComponent, setIsHoveringClickableComponent] = useState7(false);
2735
+ const [isHoveringClickableComponent, setIsHoveringClickableComponent] = useState8(false);
2442
2736
  const hoveringComponentsRef = useRef8(/* @__PURE__ */ new Set());
2443
2737
  const handleComponentHoverChange = useCallback6(
2444
2738
  (componentId, isHovering) => {
@@ -2451,7 +2745,7 @@ var SchematicViewer = ({
2451
2745
  },
2452
2746
  []
2453
2747
  );
2454
- const [isHoveringClickablePort, setIsHoveringClickablePort] = useState7(false);
2748
+ const [isHoveringClickablePort, setIsHoveringClickablePort] = useState8(false);
2455
2749
  const hoveringPortsRef = useRef8(/* @__PURE__ */ new Set());
2456
2750
  const handlePortHoverChange = useCallback6(
2457
2751
  (portId, isHovering) => {
@@ -2468,19 +2762,24 @@ var SchematicViewer = ({
2468
2762
  const touchStartRef = useRef8(null);
2469
2763
  const schematicComponentIds = useMemo5(() => {
2470
2764
  try {
2471
- return su6(circuitJson).schematic_component?.list()?.map((component) => component.schematic_component_id) ?? [];
2765
+ const components = su7(circuitJson).schematic_component?.list() ?? [];
2766
+ return components.filter(
2767
+ (component) => !activeSheetId || component.schematic_sheet_id === activeSheetId
2768
+ ).map((component) => component.schematic_component_id);
2472
2769
  } catch (err) {
2473
2770
  console.error("Failed to derive schematic component ids", err);
2474
2771
  return [];
2475
2772
  }
2476
- }, [circuitJsonKey, circuitJson]);
2773
+ }, [circuitJsonKey, circuitJson, activeSheetId]);
2477
2774
  const schematicPortsInfo = useMemo5(() => {
2478
2775
  if (!showSchematicPorts) return [];
2479
2776
  try {
2480
- const ports = su6(circuitJson).schematic_port?.list() ?? [];
2777
+ const ports = (su7(circuitJson).schematic_port?.list() ?? []).filter(
2778
+ (port) => !activeSheetId || port.schematic_sheet_id === activeSheetId
2779
+ );
2481
2780
  return ports.map((port) => {
2482
- const sourcePort = su6(circuitJson).source_port.get(port.source_port_id);
2483
- const sourceComponent = sourcePort?.source_component_id ? su6(circuitJson).source_component.get(sourcePort.source_component_id) : null;
2781
+ const sourcePort = su7(circuitJson).source_port.get(port.source_port_id);
2782
+ const sourceComponent = sourcePort?.source_component_id ? su7(circuitJson).source_component.get(sourcePort.source_component_id) : null;
2484
2783
  const componentName = sourceComponent?.name ?? "?";
2485
2784
  const pinLabel = port.display_pin_label ?? sourcePort?.pin_number ?? sourcePort?.name ?? "?";
2486
2785
  return {
@@ -2492,7 +2791,7 @@ var SchematicViewer = ({
2492
2791
  console.error("Failed to derive schematic port info", err);
2493
2792
  return [];
2494
2793
  }
2495
- }, [circuitJsonKey, circuitJson, showSchematicPorts]);
2794
+ }, [circuitJsonKey, circuitJson, showSchematicPorts, activeSheetId]);
2496
2795
  const handleTouchStart = (e) => {
2497
2796
  const touch = e.touches[0];
2498
2797
  touchStartRef.current = {
@@ -2512,9 +2811,9 @@ var SchematicViewer = ({
2512
2811
  }
2513
2812
  touchStartRef.current = null;
2514
2813
  };
2515
- const [internalEditEvents, setInternalEditEvents] = useState7([]);
2814
+ const [internalEditEvents, setInternalEditEvents] = useState8([]);
2516
2815
  const circuitJsonRef = useRef8(circuitJson);
2517
- useEffect12(() => {
2816
+ useEffect13(() => {
2518
2817
  const circuitHash = getCircuitHash(circuitJson);
2519
2818
  const circuitHashRef = getCircuitHash(circuitJsonRef.current);
2520
2819
  if (circuitHash !== circuitHashRef) {
@@ -2541,6 +2840,7 @@ var SchematicViewer = ({
2541
2840
  width: containerWidth,
2542
2841
  height: containerHeight || 720,
2543
2842
  drawPorts: showSchematicPorts,
2843
+ schematicSheetId: activeSheetId,
2544
2844
  grid: !showGrid ? void 0 : {
2545
2845
  cellSize: 1,
2546
2846
  labelCells: true
@@ -2554,7 +2854,8 @@ var SchematicViewer = ({
2554
2854
  containerWidth,
2555
2855
  containerHeight,
2556
2856
  showGrid,
2557
- showSchematicPorts
2857
+ showSchematicPorts,
2858
+ activeSheetId
2558
2859
  ]);
2559
2860
  const containerBackgroundColor = useMemo5(() => {
2560
2861
  const match = svgString.match(
@@ -2614,15 +2915,21 @@ var SchematicViewer = ({
2614
2915
  useSchematicGroupsOverlay({
2615
2916
  svgDivRef,
2616
2917
  circuitJson,
2617
- circuitJsonKey,
2918
+ circuitJsonKey: `${circuitJsonKey}_${activeSheetId ?? ""}`,
2618
2919
  showGroups: showSchematicGroups && !disableGroups
2619
2920
  });
2921
+ useSchematicNetHover({
2922
+ svgDivRef,
2923
+ circuitJson,
2924
+ circuitJsonKey: `${circuitJsonKey}_${activeSheetId ?? ""}`,
2925
+ enabled: netHoverHighlightEnabled
2926
+ });
2620
2927
  const handleComponentTouchStartRef = useRef8(handleComponentTouchStart);
2621
- useEffect12(() => {
2928
+ useEffect13(() => {
2622
2929
  handleComponentTouchStartRef.current = handleComponentTouchStart;
2623
2930
  }, [handleComponentTouchStart]);
2624
2931
  const svgDiv = useMemo5(
2625
- () => /* @__PURE__ */ jsx12(
2932
+ () => /* @__PURE__ */ jsx13(
2626
2933
  "div",
2627
2934
  {
2628
2935
  ref: svgDivRef,
@@ -2647,10 +2954,12 @@ var SchematicViewer = ({
2647
2954
  showSpiceOverlay
2648
2955
  ]
2649
2956
  );
2650
- return /* @__PURE__ */ jsxs7(MouseTracker, { children: [
2651
- onSchematicComponentClicked && /* @__PURE__ */ jsx12("style", { children: `.schematic-component-clickable [data-schematic-component-id]:hover { cursor: pointer !important; }` }),
2652
- onSchematicPortClicked && /* @__PURE__ */ jsx12("style", { children: `[data-schematic-port-id]:hover { cursor: pointer !important; }` }),
2653
- /* @__PURE__ */ jsxs7(
2957
+ return /* @__PURE__ */ jsxs8(MouseTracker, { children: [
2958
+ netHoverHighlightEnabled && /* @__PURE__ */ jsx13("style", { children: `.sch-net-faded { opacity: 0.35; }
2959
+ svg :is(g.trace, g.trace-overlays, g[data-schematic-component-id], [data-schematic-net-label-id]) { transition: opacity 0.12s ease-in-out; }` }),
2960
+ onSchematicComponentClicked && /* @__PURE__ */ jsx13("style", { children: `.schematic-component-clickable [data-schematic-component-id]:hover { cursor: pointer !important; }` }),
2961
+ onSchematicPortClicked && /* @__PURE__ */ jsx13("style", { children: `[data-schematic-port-id]:hover { cursor: pointer !important; }` }),
2962
+ /* @__PURE__ */ jsxs8(
2654
2963
  "div",
2655
2964
  {
2656
2965
  ref: containerRef,
@@ -2691,7 +3000,7 @@ var SchematicViewer = ({
2691
3000
  handleTouchEnd(e);
2692
3001
  },
2693
3002
  children: [
2694
- !isInteractionEnabled && clickToInteractEnabled && /* @__PURE__ */ jsx12(
3003
+ !isInteractionEnabled && clickToInteractEnabled && /* @__PURE__ */ jsx13(
2695
3004
  "div",
2696
3005
  {
2697
3006
  onClick: (e) => {
@@ -2710,7 +3019,7 @@ var SchematicViewer = ({
2710
3019
  pointerEvents: "all",
2711
3020
  touchAction: "pan-x pan-y pinch-zoom"
2712
3021
  },
2713
- children: /* @__PURE__ */ jsx12(
3022
+ children: /* @__PURE__ */ jsx13(
2714
3023
  "div",
2715
3024
  {
2716
3025
  style: {
@@ -2727,34 +3036,27 @@ var SchematicViewer = ({
2727
3036
  )
2728
3037
  }
2729
3038
  ),
2730
- /* @__PURE__ */ jsx12(
2731
- ViewMenuIcon,
2732
- {
2733
- active: showViewMenu,
2734
- onClick: () => setShowViewMenu(!showViewMenu)
2735
- }
2736
- ),
2737
- editingEnabled && /* @__PURE__ */ jsx12(
3039
+ editingEnabled && /* @__PURE__ */ jsx13(
2738
3040
  EditIcon,
2739
3041
  {
2740
3042
  active: editModeEnabled,
2741
3043
  onClick: () => setEditModeEnabled(!editModeEnabled)
2742
3044
  }
2743
3045
  ),
2744
- editingEnabled && editModeEnabled && /* @__PURE__ */ jsx12(
3046
+ editingEnabled && editModeEnabled && /* @__PURE__ */ jsx13(
2745
3047
  GridIcon,
2746
3048
  {
2747
3049
  active: snapToGrid,
2748
3050
  onClick: () => setSnapToGrid(!snapToGrid)
2749
3051
  }
2750
3052
  ),
2751
- /* @__PURE__ */ jsx12(
3053
+ /* @__PURE__ */ jsx13(
2752
3054
  ViewMenu,
2753
3055
  {
2754
3056
  circuitJson,
2755
3057
  circuitJsonKey,
2756
- isVisible: showViewMenu,
2757
- onClose: () => setShowViewMenu(false),
3058
+ open: showViewMenu,
3059
+ onOpenChange: setShowViewMenu,
2758
3060
  showGroups: showSchematicGroups,
2759
3061
  onToggleGroups: (value) => {
2760
3062
  if (!disableGroups) {
@@ -2766,8 +3068,16 @@ var SchematicViewer = ({
2766
3068
  onToggleGrid: setShowGridInternal
2767
3069
  }
2768
3070
  ),
2769
- spiceSimulationEnabled && /* @__PURE__ */ jsx12(SpiceSimulationIcon, { onClick: () => setShowSpiceOverlay(true) }),
2770
- showSpiceOverlay && /* @__PURE__ */ jsx12(
3071
+ /* @__PURE__ */ jsx13(
3072
+ SchematicSheetSelector,
3073
+ {
3074
+ sheets: schematicSheets,
3075
+ selectedSheetId: activeSheetId,
3076
+ onSelectSheet: handleSelectSheet
3077
+ }
3078
+ ),
3079
+ spiceSimulationEnabled && /* @__PURE__ */ jsx13(SpiceSimulationIcon, { onClick: () => setShowSpiceOverlay(true) }),
3080
+ showSpiceOverlay && /* @__PURE__ */ jsx13(
2771
3081
  SpiceSimulationOverlay,
2772
3082
  {
2773
3083
  spiceString,
@@ -2784,7 +3094,7 @@ var SchematicViewer = ({
2784
3094
  hasRun: hasSpiceSimRun
2785
3095
  }
2786
3096
  ),
2787
- onSchematicComponentClicked && schematicComponentIds.map((componentId) => /* @__PURE__ */ jsx12(
3097
+ onSchematicComponentClicked && schematicComponentIds.map((componentId) => /* @__PURE__ */ jsx13(
2788
3098
  SchematicComponentMouseTarget,
2789
3099
  {
2790
3100
  componentId,
@@ -2803,7 +3113,7 @@ var SchematicViewer = ({
2803
3113
  componentId
2804
3114
  )),
2805
3115
  svgDiv,
2806
- showSchematicPorts && schematicPortsInfo.map(({ portId, label }) => /* @__PURE__ */ jsx12(
3116
+ showSchematicPorts && schematicPortsInfo.map(({ portId, label }) => /* @__PURE__ */ jsx13(
2807
3117
  SchematicPortMouseTarget,
2808
3118
  {
2809
3119
  portId,
@@ -2832,10 +3142,10 @@ var SchematicViewer = ({
2832
3142
  import {
2833
3143
  convertCircuitJsonToSchematicSimulationSvg
2834
3144
  } from "circuit-to-svg";
2835
- import { useEffect as useEffect13, useState as useState8, useMemo as useMemo6, useRef as useRef9 } from "react";
3145
+ import { useEffect as useEffect14, useState as useState9, useMemo as useMemo6, useRef as useRef9 } from "react";
2836
3146
  import { useMouseMatrixTransform as useMouseMatrixTransform2 } from "use-mouse-matrix-transform";
2837
3147
  import { toString as transformToString2 } from "transformation-matrix";
2838
- import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
3148
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
2839
3149
  var DEFAULT_RENDER_WIDTH = 1200;
2840
3150
  var DEFAULT_RENDER_ASPECT_RATIO = 1;
2841
3151
  var AnalogSimulationViewer = ({
@@ -2846,16 +3156,16 @@ var AnalogSimulationViewer = ({
2846
3156
  height,
2847
3157
  className
2848
3158
  }) => {
2849
- const [circuitJson, setCircuitJson] = useState8(null);
2850
- const [isLoading, setIsLoading] = useState8(true);
2851
- const [error, setError] = useState8(null);
2852
- const [svgObjectUrl, setSvgObjectUrl] = useState8(null);
3159
+ const [circuitJson, setCircuitJson] = useState9(null);
3160
+ const [isLoading, setIsLoading] = useState9(true);
3161
+ const [error, setError] = useState9(null);
3162
+ const [svgObjectUrl, setSvgObjectUrl] = useState9(null);
2853
3163
  const containerRef = useRef9(null);
2854
3164
  const imgRef = useRef9(null);
2855
3165
  const { containerWidth } = useResizeHandling(
2856
3166
  containerRef
2857
3167
  );
2858
- const [isDragging, setIsDragging] = useState8(false);
3168
+ const [isDragging, setIsDragging] = useState9(false);
2859
3169
  const {
2860
3170
  ref: transformRef,
2861
3171
  cancelDrag: _cancelDrag,
@@ -2870,7 +3180,7 @@ var AnalogSimulationViewer = ({
2870
3180
  const renderAspectRatio = width && height ? width / height : DEFAULT_RENDER_ASPECT_RATIO;
2871
3181
  const effectiveWidth = width || (height ? height * renderAspectRatio : containerWidth) || DEFAULT_RENDER_WIDTH;
2872
3182
  const effectiveHeight = height || effectiveWidth / renderAspectRatio;
2873
- useEffect13(() => {
3183
+ useEffect14(() => {
2874
3184
  setIsLoading(true);
2875
3185
  setError(null);
2876
3186
  setCircuitJson(inputCircuitJson);
@@ -2917,7 +3227,7 @@ var AnalogSimulationViewer = ({
2917
3227
  simulationCurrentGraphIds,
2918
3228
  simulationVoltageGraphIds
2919
3229
  ]);
2920
- useEffect13(() => {
3230
+ useEffect14(() => {
2921
3231
  if (!simulationSvg) {
2922
3232
  setSvgObjectUrl(null);
2923
3233
  return;
@@ -2947,7 +3257,7 @@ var AnalogSimulationViewer = ({
2947
3257
  const handleTouchStart = (_e) => {
2948
3258
  setIsDragging(true);
2949
3259
  };
2950
- useEffect13(() => {
3260
+ useEffect14(() => {
2951
3261
  const handleMouseUp = () => {
2952
3262
  setIsDragging(false);
2953
3263
  };
@@ -2962,7 +3272,7 @@ var AnalogSimulationViewer = ({
2962
3272
  };
2963
3273
  }, []);
2964
3274
  if (isLoading) {
2965
- return /* @__PURE__ */ jsx13(
3275
+ return /* @__PURE__ */ jsx14(
2966
3276
  "div",
2967
3277
  {
2968
3278
  style: {
@@ -2982,7 +3292,7 @@ var AnalogSimulationViewer = ({
2982
3292
  );
2983
3293
  }
2984
3294
  if (error) {
2985
- return /* @__PURE__ */ jsx13(
3295
+ return /* @__PURE__ */ jsx14(
2986
3296
  "div",
2987
3297
  {
2988
3298
  style: {
@@ -2997,15 +3307,15 @@ var AnalogSimulationViewer = ({
2997
3307
  ...containerStyle
2998
3308
  },
2999
3309
  className,
3000
- children: /* @__PURE__ */ jsxs8("div", { style: { textAlign: "center", padding: "20px" }, children: [
3001
- /* @__PURE__ */ jsx13("div", { style: { fontWeight: "bold", marginBottom: "8px" }, children: "Circuit Conversion Error" }),
3002
- /* @__PURE__ */ jsx13("div", { style: { fontSize: "14px" }, children: error })
3310
+ children: /* @__PURE__ */ jsxs9("div", { style: { textAlign: "center", padding: "20px" }, children: [
3311
+ /* @__PURE__ */ jsx14("div", { style: { fontWeight: "bold", marginBottom: "8px" }, children: "Circuit Conversion Error" }),
3312
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: "14px" }, children: error })
3003
3313
  ] })
3004
3314
  }
3005
3315
  );
3006
3316
  }
3007
3317
  if (!simulationSvg) {
3008
- return /* @__PURE__ */ jsxs8(
3318
+ return /* @__PURE__ */ jsxs9(
3009
3319
  "div",
3010
3320
  {
3011
3321
  style: {
@@ -3021,11 +3331,11 @@ var AnalogSimulationViewer = ({
3021
3331
  },
3022
3332
  className,
3023
3333
  children: [
3024
- /* @__PURE__ */ jsx13("div", { style: { fontSize: "16px", color: "#475569", fontWeight: 500 }, children: "No Simulation Found" }),
3025
- /* @__PURE__ */ jsxs8("div", { style: { fontSize: "14px", color: "#64748b" }, children: [
3334
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: "16px", color: "#475569", fontWeight: 500 }, children: "No Simulation Found" }),
3335
+ /* @__PURE__ */ jsxs9("div", { style: { fontSize: "14px", color: "#64748b" }, children: [
3026
3336
  "Use",
3027
3337
  " ",
3028
- /* @__PURE__ */ jsx13(
3338
+ /* @__PURE__ */ jsx14(
3029
3339
  "code",
3030
3340
  {
3031
3341
  style: {
@@ -3045,7 +3355,7 @@ var AnalogSimulationViewer = ({
3045
3355
  }
3046
3356
  );
3047
3357
  }
3048
- return /* @__PURE__ */ jsx13(
3358
+ return /* @__PURE__ */ jsx14(
3049
3359
  "div",
3050
3360
  {
3051
3361
  ref: (node) => {
@@ -3063,7 +3373,7 @@ var AnalogSimulationViewer = ({
3063
3373
  className,
3064
3374
  onMouseDown: handleMouseDown,
3065
3375
  onTouchStart: handleTouchStart,
3066
- children: svgObjectUrl ? /* @__PURE__ */ jsx13(
3376
+ children: svgObjectUrl ? /* @__PURE__ */ jsx14(
3067
3377
  "img",
3068
3378
  {
3069
3379
  ref: imgRef,
@@ -3077,7 +3387,7 @@ var AnalogSimulationViewer = ({
3077
3387
  objectFit: "contain"
3078
3388
  }
3079
3389
  }
3080
- ) : /* @__PURE__ */ jsx13(
3390
+ ) : /* @__PURE__ */ jsx14(
3081
3391
  "div",
3082
3392
  {
3083
3393
  style: {