@tscircuit/pcb-viewer 1.11.238 → 1.11.239

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
@@ -6412,7 +6412,7 @@ var ToastContainer = () => {
6412
6412
  };
6413
6413
 
6414
6414
  // src/PCBViewer.tsx
6415
- import { useEffect as useEffect16, useMemo as useMemo6, useRef as useRef11, useState as useState11 } from "react";
6415
+ import { useEffect as useEffect16, useMemo as useMemo7, useRef as useRef11, useState as useState11 } from "react";
6416
6416
 
6417
6417
  // node_modules/react-use/esm/misc/util.js
6418
6418
  var noop = function() {
@@ -6963,7 +6963,7 @@ function addInteractionMetadataToPrimitives({
6963
6963
  }
6964
6964
 
6965
6965
  // src/components/CanvasElementsRenderer.tsx
6966
- import { useCallback as useCallback4, useMemo as useMemo5, useState as useState10 } from "react";
6966
+ import { useCallback as useCallback5, useMemo as useMemo6, useState as useState10 } from "react";
6967
6967
 
6968
6968
  // src/lib/util/expand-stroke.ts
6969
6969
  function getExpandedStroke(strokeInput, defaultWidth) {
@@ -9702,43 +9702,306 @@ var WarningGraphicsOverlay = ({
9702
9702
  };
9703
9703
 
9704
9704
  // src/components/DimensionOverlay.tsx
9705
- import { useEffect as useEffect7, useRef as useRef5, useState as useState3 } from "react";
9705
+ import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo3, useRef as useRef5, useState as useState3 } from "react";
9706
9706
  import { applyToPoint as applyToPoint8, identity as identity4, inverse as inverse2 } from "transformation-matrix";
9707
+
9708
+ // src/lib/util/get-primitive-bounding-box.ts
9709
+ var mergeNumber = (a, b, fn) => fn(a, b);
9710
+ var rotatePoint = (x, y, centerX, centerY, rotationDeg) => {
9711
+ const radians = rotationDeg * Math.PI / 180;
9712
+ const cos = Math.cos(radians);
9713
+ const sin = Math.sin(radians);
9714
+ const translatedX = x - centerX;
9715
+ const translatedY = y - centerY;
9716
+ const rotatedX = translatedX * cos - translatedY * sin;
9717
+ const rotatedY = translatedX * sin + translatedY * cos;
9718
+ return {
9719
+ x: rotatedX + centerX,
9720
+ y: rotatedY + centerY
9721
+ };
9722
+ };
9723
+ var createBoxFromPoints = (points) => {
9724
+ if (points.length === 0) return null;
9725
+ let minX = points[0].x;
9726
+ let maxX = points[0].x;
9727
+ let minY = points[0].y;
9728
+ let maxY = points[0].y;
9729
+ for (const point of points) {
9730
+ minX = mergeNumber(minX, point.x, Math.min);
9731
+ maxX = mergeNumber(maxX, point.x, Math.max);
9732
+ minY = mergeNumber(minY, point.y, Math.min);
9733
+ maxY = mergeNumber(maxY, point.y, Math.max);
9734
+ }
9735
+ return { minX, maxX, minY, maxY };
9736
+ };
9737
+ var mergeBoundingBoxesInternal = (a, b) => ({
9738
+ minX: Math.min(a.minX, b.minX),
9739
+ maxX: Math.max(a.maxX, b.maxX),
9740
+ minY: Math.min(a.minY, b.minY),
9741
+ maxY: Math.max(a.maxY, b.maxY)
9742
+ });
9743
+ var mergeBoundingBoxes = (existing, next) => {
9744
+ if (!existing) return next;
9745
+ return mergeBoundingBoxesInternal(existing, next);
9746
+ };
9747
+ var getPrimitiveBoundingBox = (primitive) => {
9748
+ switch (primitive.pcb_drawing_type) {
9749
+ case "line": {
9750
+ const halfWidth = (primitive.width ?? 0) / 2;
9751
+ const points = [
9752
+ { x: primitive.x1, y: primitive.y1 },
9753
+ { x: primitive.x2, y: primitive.y2 }
9754
+ ];
9755
+ const baseBox = createBoxFromPoints(points);
9756
+ if (!baseBox) return null;
9757
+ return {
9758
+ minX: baseBox.minX - halfWidth,
9759
+ maxX: baseBox.maxX + halfWidth,
9760
+ minY: baseBox.minY - halfWidth,
9761
+ maxY: baseBox.maxY + halfWidth
9762
+ };
9763
+ }
9764
+ case "rect": {
9765
+ const halfW = primitive.w / 2;
9766
+ const halfH = primitive.h / 2;
9767
+ const corners = [
9768
+ { x: primitive.x - halfW, y: primitive.y - halfH },
9769
+ { x: primitive.x + halfW, y: primitive.y - halfH },
9770
+ { x: primitive.x + halfW, y: primitive.y + halfH },
9771
+ { x: primitive.x - halfW, y: primitive.y + halfH }
9772
+ ];
9773
+ const rotation = primitive.ccw_rotation ?? 0;
9774
+ const rotatedCorners = rotation === 0 ? corners : corners.map(
9775
+ (corner) => rotatePoint(
9776
+ corner.x,
9777
+ corner.y,
9778
+ primitive.x,
9779
+ primitive.y,
9780
+ rotation
9781
+ )
9782
+ );
9783
+ return createBoxFromPoints(rotatedCorners);
9784
+ }
9785
+ case "circle": {
9786
+ return {
9787
+ minX: primitive.x - primitive.r,
9788
+ maxX: primitive.x + primitive.r,
9789
+ minY: primitive.y - primitive.r,
9790
+ maxY: primitive.y + primitive.r
9791
+ };
9792
+ }
9793
+ case "oval": {
9794
+ return {
9795
+ minX: primitive.x - primitive.rX,
9796
+ maxX: primitive.x + primitive.rX,
9797
+ minY: primitive.y - primitive.rY,
9798
+ maxY: primitive.y + primitive.rY
9799
+ };
9800
+ }
9801
+ case "pill": {
9802
+ const halfW = primitive.w / 2;
9803
+ const halfH = primitive.h / 2;
9804
+ const corners = [
9805
+ { x: primitive.x - halfW, y: primitive.y - halfH },
9806
+ { x: primitive.x + halfW, y: primitive.y - halfH },
9807
+ { x: primitive.x + halfW, y: primitive.y + halfH },
9808
+ { x: primitive.x - halfW, y: primitive.y + halfH }
9809
+ ];
9810
+ const rotation = primitive.ccw_rotation ?? 0;
9811
+ const rotatedCorners = rotation === 0 ? corners : corners.map(
9812
+ (corner) => rotatePoint(
9813
+ corner.x,
9814
+ corner.y,
9815
+ primitive.x,
9816
+ primitive.y,
9817
+ rotation
9818
+ )
9819
+ );
9820
+ return createBoxFromPoints(rotatedCorners);
9821
+ }
9822
+ case "polygon": {
9823
+ return createBoxFromPoints(primitive.points);
9824
+ }
9825
+ case "polygon_with_arcs": {
9826
+ const points = primitive.brep_shape.outer_ring.vertices.map((vertex) => ({
9827
+ x: vertex.x,
9828
+ y: vertex.y
9829
+ }));
9830
+ return createBoxFromPoints(points);
9831
+ }
9832
+ case "text": {
9833
+ const size = primitive.size ?? 0;
9834
+ const width = primitive.text ? primitive.text.length * size * 0.6 : 0;
9835
+ const height = size;
9836
+ if (width === 0 && height === 0) {
9837
+ return {
9838
+ minX: primitive.x,
9839
+ maxX: primitive.x,
9840
+ minY: primitive.y,
9841
+ maxY: primitive.y
9842
+ };
9843
+ }
9844
+ const halfW = width / 2;
9845
+ const halfH = height / 2;
9846
+ return {
9847
+ minX: primitive.x - halfW,
9848
+ maxX: primitive.x + halfW,
9849
+ minY: primitive.y - halfH,
9850
+ maxY: primitive.y + halfH
9851
+ };
9852
+ }
9853
+ default:
9854
+ return null;
9855
+ }
9856
+ };
9857
+
9858
+ // src/components/DimensionOverlay.tsx
9707
9859
  import { Fragment, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
9860
+ var SNAP_THRESHOLD_PX = 16;
9861
+ var SNAP_MARKER_SIZE = 5;
9862
+ var shouldExcludePrimitiveFromSnapping = (primitive) => {
9863
+ if (primitive.pcb_drawing_type === "text") return true;
9864
+ const element = primitive._element;
9865
+ if (!element || typeof element !== "object") {
9866
+ return false;
9867
+ }
9868
+ const elementType = typeof element.type === "string" ? element.type : void 0;
9869
+ if (!elementType) return false;
9870
+ if (elementType.startsWith("pcb_silkscreen_")) return true;
9871
+ if (elementType.startsWith("pcb_note_")) return true;
9872
+ if (elementType === "pcb_text") return true;
9873
+ if (elementType.startsWith("pcb_fabrication_note_") && elementType !== "pcb_fabrication_note_rect") {
9874
+ return true;
9875
+ }
9876
+ return false;
9877
+ };
9708
9878
  var DimensionOverlay = ({
9709
9879
  children,
9710
9880
  transform,
9711
- focusOnHover = false
9881
+ focusOnHover = false,
9882
+ primitives = []
9712
9883
  }) => {
9713
9884
  if (!transform) transform = identity4();
9714
9885
  const [dimensionToolVisible, setDimensionToolVisible] = useState3(false);
9715
9886
  const [dimensionToolStretching, setDimensionToolStretching] = useState3(false);
9716
9887
  const [measureToolArmed, setMeasureToolArmed] = useState3(false);
9717
- const disarmMeasure = () => {
9888
+ const [activeSnapIds, setActiveSnapIds] = useState3({
9889
+ start: null,
9890
+ end: null
9891
+ });
9892
+ const disarmMeasure = useCallback3(() => {
9718
9893
  if (measureToolArmed) {
9719
9894
  setMeasureToolArmed(false);
9720
9895
  window.dispatchEvent(new Event("disarm-dimension-tool"));
9721
9896
  }
9722
- };
9897
+ }, [measureToolArmed]);
9723
9898
  const [dStart, setDStart] = useState3({ x: 0, y: 0 });
9724
9899
  const [dEnd, setDEnd] = useState3({ x: 0, y: 0 });
9725
9900
  const mousePosRef = useRef5({ x: 0, y: 0 });
9726
9901
  const containerRef = useRef5(null);
9727
9902
  const container = containerRef.current;
9728
9903
  const containerBounds = container?.getBoundingClientRect();
9904
+ const elementBoundingBoxes = useMemo3(() => {
9905
+ const boundingBoxes = /* @__PURE__ */ new Map();
9906
+ for (const primitive of primitives) {
9907
+ if (!primitive._element) continue;
9908
+ if (shouldExcludePrimitiveFromSnapping(primitive)) continue;
9909
+ const bbox = getPrimitiveBoundingBox(primitive);
9910
+ if (!bbox) continue;
9911
+ const existing = boundingBoxes.get(primitive._element);
9912
+ boundingBoxes.set(
9913
+ primitive._element,
9914
+ mergeBoundingBoxes(existing ?? void 0, bbox)
9915
+ );
9916
+ }
9917
+ return boundingBoxes;
9918
+ }, [primitives]);
9919
+ const snappingPoints = useMemo3(() => {
9920
+ const points = [];
9921
+ elementBoundingBoxes.forEach((bounds, element) => {
9922
+ if (!bounds) return;
9923
+ const centerX = (bounds.minX + bounds.maxX) / 2;
9924
+ const centerY = (bounds.minY + bounds.maxY) / 2;
9925
+ const anchorPoints = {
9926
+ top_left: { x: bounds.minX, y: bounds.minY },
9927
+ top_center: { x: centerX, y: bounds.minY },
9928
+ top_right: { x: bounds.maxX, y: bounds.minY },
9929
+ center_left: { x: bounds.minX, y: centerY },
9930
+ center: { x: centerX, y: centerY },
9931
+ center_right: { x: bounds.maxX, y: centerY },
9932
+ bottom_left: { x: bounds.minX, y: bounds.maxY },
9933
+ bottom_center: { x: centerX, y: bounds.maxY },
9934
+ bottom_right: { x: bounds.maxX, y: bounds.maxY }
9935
+ };
9936
+ for (const [anchor, point] of Object.entries(anchorPoints)) {
9937
+ points.push({
9938
+ anchor,
9939
+ point,
9940
+ element
9941
+ });
9942
+ }
9943
+ });
9944
+ return points;
9945
+ }, [elementBoundingBoxes]);
9946
+ const snappingPointsWithScreen = useMemo3(() => {
9947
+ return snappingPoints.map((snap, index) => ({
9948
+ ...snap,
9949
+ id: `${index}-${snap.anchor}`,
9950
+ screenPoint: applyToPoint8(transform, snap.point)
9951
+ }));
9952
+ }, [snappingPoints, transform]);
9953
+ const findSnap = useCallback3(
9954
+ (rwPoint) => {
9955
+ if (snappingPointsWithScreen.length === 0) {
9956
+ return { point: rwPoint, id: null };
9957
+ }
9958
+ const screenPoint = applyToPoint8(transform, rwPoint);
9959
+ let bestMatch = null;
9960
+ for (const snap of snappingPointsWithScreen) {
9961
+ const dx = snap.screenPoint.x - screenPoint.x;
9962
+ const dy = snap.screenPoint.y - screenPoint.y;
9963
+ const distance5 = Math.hypot(dx, dy);
9964
+ if (distance5 > SNAP_THRESHOLD_PX) continue;
9965
+ if (!bestMatch || distance5 < bestMatch.distance) {
9966
+ bestMatch = {
9967
+ distance: distance5,
9968
+ id: snap.id,
9969
+ point: snap.point
9970
+ };
9971
+ }
9972
+ }
9973
+ if (!bestMatch) {
9974
+ return { point: rwPoint, id: null };
9975
+ }
9976
+ return { point: bestMatch.point, id: bestMatch.id };
9977
+ },
9978
+ [snappingPointsWithScreen, transform]
9979
+ );
9729
9980
  useEffect7(() => {
9730
9981
  const container2 = containerRef.current;
9731
9982
  const down = (e) => {
9732
9983
  if (e.key === "d") {
9733
- setDStart({ x: mousePosRef.current.x, y: mousePosRef.current.y });
9734
- setDEnd({ x: mousePosRef.current.x, y: mousePosRef.current.y });
9735
- setDimensionToolVisible((visible) => !visible);
9736
- setDimensionToolStretching(true);
9984
+ const snap = findSnap({
9985
+ x: mousePosRef.current.x,
9986
+ y: mousePosRef.current.y
9987
+ });
9988
+ setDStart({ x: snap.point.x, y: snap.point.y });
9989
+ setDEnd({ x: snap.point.x, y: snap.point.y });
9990
+ setActiveSnapIds({ start: snap.id, end: snap.id });
9991
+ if (dimensionToolVisible) {
9992
+ setDimensionToolVisible(false);
9993
+ setDimensionToolStretching(false);
9994
+ setActiveSnapIds({ start: null, end: null });
9995
+ } else {
9996
+ setDimensionToolVisible(true);
9997
+ setDimensionToolStretching(true);
9998
+ }
9737
9999
  disarmMeasure();
9738
10000
  }
9739
10001
  if (e.key === "Escape") {
9740
10002
  setDimensionToolVisible(false);
9741
10003
  setDimensionToolStretching(false);
10004
+ setActiveSnapIds({ start: null, end: null });
9742
10005
  disarmMeasure();
9743
10006
  }
9744
10007
  };
@@ -9772,7 +10035,7 @@ var DimensionOverlay = ({
9772
10035
  container2.removeEventListener("mouseleave", removeKeyListener);
9773
10036
  }
9774
10037
  };
9775
- }, [containerRef]);
10038
+ }, [containerRef, dimensionToolVisible, disarmMeasure, findSnap]);
9776
10039
  const screenDStart = applyToPoint8(transform, dStart);
9777
10040
  const screenDEnd = applyToPoint8(transform, dEnd);
9778
10041
  const arrowScreenBounds = {
@@ -9811,7 +10074,9 @@ var DimensionOverlay = ({
9811
10074
  mousePosRef.current.x = rwPoint.x;
9812
10075
  mousePosRef.current.y = rwPoint.y;
9813
10076
  if (dimensionToolStretching) {
9814
- setDEnd({ x: rwPoint.x, y: rwPoint.y });
10077
+ const snap = findSnap(rwPoint);
10078
+ setDEnd({ x: snap.point.x, y: snap.point.y });
10079
+ setActiveSnapIds((prev) => ({ ...prev, end: snap.id }));
9815
10080
  }
9816
10081
  },
9817
10082
  onMouseDown: (e) => {
@@ -9820,15 +10085,19 @@ var DimensionOverlay = ({
9820
10085
  const y = e.clientY - rect.top;
9821
10086
  const rwPoint = applyToPoint8(inverse2(transform), { x, y });
9822
10087
  if (measureToolArmed && !dimensionToolVisible) {
9823
- setDStart({ x: rwPoint.x, y: rwPoint.y });
9824
- setDEnd({ x: rwPoint.x, y: rwPoint.y });
10088
+ const snap = findSnap(rwPoint);
10089
+ setDStart({ x: snap.point.x, y: snap.point.y });
10090
+ setDEnd({ x: snap.point.x, y: snap.point.y });
10091
+ setActiveSnapIds({ start: snap.id, end: snap.id });
9825
10092
  setDimensionToolVisible(true);
9826
10093
  setDimensionToolStretching(true);
9827
10094
  disarmMeasure();
9828
10095
  } else if (dimensionToolStretching) {
9829
10096
  setDimensionToolStretching(false);
10097
+ setActiveSnapIds((prev) => ({ ...prev, end: null }));
9830
10098
  } else if (dimensionToolVisible) {
9831
10099
  setDimensionToolVisible(false);
10100
+ setActiveSnapIds({ start: null, end: null });
9832
10101
  }
9833
10102
  },
9834
10103
  children: [
@@ -9952,6 +10221,49 @@ var DimensionOverlay = ({
9952
10221
  ]
9953
10222
  }
9954
10223
  ),
10224
+ dimensionToolStretching && snappingPointsWithScreen.map((snap) => {
10225
+ const isActive = snap.id === activeSnapIds.start || snap.id === activeSnapIds.end;
10226
+ const half = SNAP_MARKER_SIZE / 2;
10227
+ return /* @__PURE__ */ jsxs4(
10228
+ "svg",
10229
+ {
10230
+ width: SNAP_MARKER_SIZE,
10231
+ height: SNAP_MARKER_SIZE,
10232
+ style: {
10233
+ position: "absolute",
10234
+ left: snap.screenPoint.x - half,
10235
+ top: snap.screenPoint.y - half,
10236
+ pointerEvents: "none",
10237
+ zIndex: zIndexMap.dimensionOverlay
10238
+ },
10239
+ children: [
10240
+ /* @__PURE__ */ jsx6(
10241
+ "line",
10242
+ {
10243
+ x1: 0,
10244
+ y1: 0,
10245
+ x2: SNAP_MARKER_SIZE,
10246
+ y2: SNAP_MARKER_SIZE,
10247
+ stroke: isActive ? "#66ccff" : "white",
10248
+ strokeWidth: 1
10249
+ }
10250
+ ),
10251
+ /* @__PURE__ */ jsx6(
10252
+ "line",
10253
+ {
10254
+ x1: SNAP_MARKER_SIZE,
10255
+ y1: 0,
10256
+ x2: 0,
10257
+ y2: SNAP_MARKER_SIZE,
10258
+ stroke: isActive ? "#66ccff" : "white",
10259
+ strokeWidth: 1
10260
+ }
10261
+ )
10262
+ ]
10263
+ },
10264
+ snap.id
10265
+ );
10266
+ }),
9955
10267
  /* @__PURE__ */ jsxs4(
9956
10268
  "div",
9957
10269
  {
@@ -11055,7 +11367,7 @@ var ErrorOverlay = ({
11055
11367
  };
11056
11368
 
11057
11369
  // src/components/MouseElementTracker.tsx
11058
- import { useState as useState7, useMemo as useMemo3 } from "react";
11370
+ import { useState as useState7, useMemo as useMemo4 } from "react";
11059
11371
  import { applyToPoint as applyToPoint12, inverse as inverse5 } from "transformation-matrix";
11060
11372
 
11061
11373
  // src/components/ElementOverlayBox.tsx
@@ -11472,7 +11784,7 @@ var MouseElementTracker = ({
11472
11784
  }) => {
11473
11785
  const [mousedPrimitives, setMousedPrimitives] = useState7([]);
11474
11786
  const [mousePos, setMousePos] = useState7({ x: 0, y: 0 });
11475
- const highlightedPrimitives = useMemo3(() => {
11787
+ const highlightedPrimitives = useMemo4(() => {
11476
11788
  const highlightedPrimitives2 = [];
11477
11789
  for (const primitive of mousedPrimitives) {
11478
11790
  if (primitive._element?.type === "pcb_via") continue;
@@ -11836,15 +12148,15 @@ var PcbGroupOverlay = ({
11836
12148
 
11837
12149
  // src/components/RatsNestOverlay.tsx
11838
12150
  import { applyToPoint as applyToPoint14, identity as identity9 } from "transformation-matrix";
11839
- import { useMemo as useMemo4 } from "react";
12151
+ import { useMemo as useMemo5 } from "react";
11840
12152
  import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
11841
12153
  var RatsNestOverlay = ({ transform, soup, children }) => {
11842
12154
  const isShowingRatsNest = useGlobalStore((s) => s.is_showing_rats_nest);
11843
- const { netMap, idToNetMap } = useMemo4(
12155
+ const { netMap, idToNetMap } = useMemo5(
11844
12156
  () => getFullConnectivityMapFromCircuitJson(soup || []),
11845
12157
  [soup]
11846
12158
  );
11847
- const ratsNestLines = useMemo4(() => {
12159
+ const ratsNestLines = useMemo5(() => {
11848
12160
  if (!soup || !isShowingRatsNest) return [];
11849
12161
  const getElementPosition = (id) => {
11850
12162
  const element = su(soup)[id.replace(/_\d+$/, "")].get(id);
@@ -11941,7 +12253,7 @@ var RatsNestOverlay = ({ transform, soup, children }) => {
11941
12253
  import {
11942
12254
  useEffect as useEffect15,
11943
12255
  useState as useState9,
11944
- useCallback as useCallback3,
12256
+ useCallback as useCallback4,
11945
12257
  useRef as useRef10
11946
12258
  } from "react";
11947
12259
  import { css as css3 } from "@emotion/css";
@@ -11949,7 +12261,7 @@ import { css as css3 } from "@emotion/css";
11949
12261
  // package.json
11950
12262
  var package_default = {
11951
12263
  name: "@tscircuit/pcb-viewer",
11952
- version: "1.11.237",
12264
+ version: "1.11.238",
11953
12265
  main: "dist/index.js",
11954
12266
  type: "module",
11955
12267
  repository: "tscircuit/pcb-viewer",
@@ -12294,20 +12606,20 @@ var ToolbarOverlay = ({ children, elements }) => {
12294
12606
  useHotKey("6", hotKeyCallbacks["6"]);
12295
12607
  useHotKey("7", hotKeyCallbacks["7"]);
12296
12608
  useHotKey("8", hotKeyCallbacks["8"]);
12297
- const handleMouseEnter = useCallback3(() => {
12609
+ const handleMouseEnter = useCallback4(() => {
12298
12610
  setIsMouseOverContainer(true);
12299
12611
  }, [setIsMouseOverContainer]);
12300
- const handleMouseLeave = useCallback3(() => {
12612
+ const handleMouseLeave = useCallback4(() => {
12301
12613
  setIsMouseOverContainer(false);
12302
12614
  setLayerMenuOpen(false);
12303
12615
  setViewMenuOpen(false);
12304
12616
  setErrorsOpen(false);
12305
12617
  setHoveredErrorId(null);
12306
12618
  }, [setIsMouseOverContainer, setHoveredErrorId]);
12307
- const handleLayerMenuToggle = useCallback3(() => {
12619
+ const handleLayerMenuToggle = useCallback4(() => {
12308
12620
  setLayerMenuOpen(!isLayerMenuOpen);
12309
12621
  }, [isLayerMenuOpen]);
12310
- const handleErrorsToggle = useCallback3(() => {
12622
+ const handleErrorsToggle = useCallback4(() => {
12311
12623
  const newErrorsOpen = !isErrorsOpen;
12312
12624
  setErrorsOpen(newErrorsOpen);
12313
12625
  if (newErrorsOpen) {
@@ -12317,20 +12629,20 @@ var ToolbarOverlay = ({ children, elements }) => {
12317
12629
  setHoveredErrorId(null);
12318
12630
  }
12319
12631
  }, [isErrorsOpen, setHoveredErrorId]);
12320
- const handleEditTraceToggle = useCallback3(() => {
12632
+ const handleEditTraceToggle = useCallback4(() => {
12321
12633
  setEditMode(editModes.in_draw_trace_mode ? "off" : "draw_trace");
12322
12634
  }, [editModes.in_draw_trace_mode, setEditMode]);
12323
- const handleMoveComponentToggle = useCallback3(() => {
12635
+ const handleMoveComponentToggle = useCallback4(() => {
12324
12636
  setEditMode(editModes.in_move_footprint_mode ? "off" : "move_footprint");
12325
12637
  }, [editModes.in_move_footprint_mode, setEditMode]);
12326
- const handleRatsNestToggle = useCallback3(() => {
12638
+ const handleRatsNestToggle = useCallback4(() => {
12327
12639
  setIsShowingRatsNest(!viewSettings.is_showing_rats_nest);
12328
12640
  }, [viewSettings.is_showing_rats_nest, setIsShowingRatsNest]);
12329
- const handleMeasureToolClick = useCallback3(() => {
12641
+ const handleMeasureToolClick = useCallback4(() => {
12330
12642
  setMeasureToolArmed(true);
12331
12643
  window.dispatchEvent(new Event("arm-dimension-tool"));
12332
12644
  }, []);
12333
- const handleViewMenuToggle = useCallback3(() => {
12645
+ const handleViewMenuToggle = useCallback4(() => {
12334
12646
  const newViewMenuOpen = !isViewMenuOpen;
12335
12647
  setViewMenuOpen(newViewMenuOpen);
12336
12648
  if (newViewMenuOpen) {
@@ -12765,7 +13077,7 @@ import { jsx as jsx16 } from "react/jsx-runtime";
12765
13077
  var CanvasElementsRenderer = (props) => {
12766
13078
  const { transform, elements } = props;
12767
13079
  const hoveredErrorId = useGlobalStore((state) => state.hovered_error_id);
12768
- const [primitivesWithoutInteractionMetadata, connectivityMap] = useMemo5(() => {
13080
+ const [primitivesWithoutInteractionMetadata, connectivityMap] = useMemo6(() => {
12769
13081
  const primitivesWithoutInteractionMetadata2 = props.elements.flatMap(
12770
13082
  (elm) => convertElementToPrimitives(elm, props.elements)
12771
13083
  );
@@ -12778,7 +13090,7 @@ var CanvasElementsRenderer = (props) => {
12778
13090
  drawingObjectIdsWithMouseOver: /* @__PURE__ */ new Set(),
12779
13091
  primitiveIdsInMousedOverNet: []
12780
13092
  });
12781
- const errorRelatedIds = useMemo5(() => {
13093
+ const errorRelatedIds = useMemo6(() => {
12782
13094
  if (!hoveredErrorId) return [];
12783
13095
  const errorElements = elements.filter(
12784
13096
  (el) => el.type.includes("error")
@@ -12797,7 +13109,7 @@ var CanvasElementsRenderer = (props) => {
12797
13109
  }
12798
13110
  return relatedIds;
12799
13111
  }, [hoveredErrorId, elements]);
12800
- const primitives = useMemo5(() => {
13112
+ const primitives = useMemo6(() => {
12801
13113
  const combinedPrimitiveIds = [
12802
13114
  ...hoverState.primitiveIdsInMousedOverNet,
12803
13115
  ...errorRelatedIds
@@ -12808,7 +13120,7 @@ var CanvasElementsRenderer = (props) => {
12808
13120
  primitiveIdsInMousedOverNet: combinedPrimitiveIds
12809
13121
  });
12810
13122
  }, [primitivesWithoutInteractionMetadata, hoverState, errorRelatedIds]);
12811
- const onMouseOverPrimitives = useCallback4(
13123
+ const onMouseOverPrimitives = useCallback5(
12812
13124
  (primitivesHoveredOver) => {
12813
13125
  const primitiveIdsInMousedOverNet = [];
12814
13126
  for (const primitive of primitivesHoveredOver) {
@@ -12861,6 +13173,7 @@ var CanvasElementsRenderer = (props) => {
12861
13173
  {
12862
13174
  transform,
12863
13175
  focusOnHover: props.focusOnHover,
13176
+ primitives: primitivesWithoutInteractionMetadata,
12864
13177
  children: /* @__PURE__ */ jsx16(ToolbarOverlay, { elements, children: /* @__PURE__ */ jsx16(ErrorOverlay, { transform, elements, children: /* @__PURE__ */ jsx16(RatsNestOverlay, { transform, soup: elements, children: /* @__PURE__ */ jsx16(PcbGroupOverlay, { transform, elements, children: /* @__PURE__ */ jsx16(
12865
13178
  DebugGraphicsOverlay,
12866
13179
  {
@@ -12970,7 +13283,7 @@ var PCBViewer = ({
12970
13283
  editEvents = editEventsProp ?? editEvents;
12971
13284
  const initialRenderCompleted = useRef11(false);
12972
13285
  const touchStartRef = useRef11(null);
12973
- const circuitJsonKey = useMemo6(
13286
+ const circuitJsonKey = useMemo7(
12974
13287
  () => calculateCircuitJsonKey(circuitJson),
12975
13288
  [circuitJson]
12976
13289
  );
@@ -13003,12 +13316,12 @@ var PCBViewer = ({
13003
13316
  initialRenderCompleted.current = true;
13004
13317
  }
13005
13318
  }, [circuitJson, refDimensions]);
13006
- const pcbElmsPreEdit = useMemo6(() => {
13319
+ const pcbElmsPreEdit = useMemo7(() => {
13007
13320
  return circuitJson?.filter(
13008
13321
  (e) => e.type.startsWith("pcb_") || e.type.startsWith("source_")
13009
13322
  ) ?? [];
13010
13323
  }, [circuitJsonKey]);
13011
- const elements = useMemo6(() => {
13324
+ const elements = useMemo7(() => {
13012
13325
  return applyEditEvents({
13013
13326
  circuitJson: pcbElmsPreEdit,
13014
13327
  editEvents
@@ -13025,7 +13338,7 @@ var PCBViewer = ({
13025
13338
  setEditEvents(newEditEvents);
13026
13339
  onEditEventsChanged?.(newEditEvents);
13027
13340
  };
13028
- const mergedInitialState = useMemo6(
13341
+ const mergedInitialState = useMemo7(
13029
13342
  () => ({
13030
13343
  ...initialState,
13031
13344
  ...disablePcbGroups && { is_showing_pcb_groups: false }