@tscircuit/pcb-viewer 1.11.237 → 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) {
@@ -8394,6 +8394,7 @@ var zIndexMap = {
8394
8394
  };
8395
8395
 
8396
8396
  // src/lib/Drawer.ts
8397
+ import colorParser from "color";
8397
8398
  var LAYER_NAME_TO_COLOR = {
8398
8399
  // Standard colors, you shouldn't use these except for testing
8399
8400
  red: "red",
@@ -8762,11 +8763,14 @@ var Drawer = class {
8762
8763
  ctx.lineCap = "round";
8763
8764
  if (mode === "add") {
8764
8765
  ctx.globalCompositeOperation = "source-over";
8765
- let colorString = color2?.[0] === "#" || color2?.startsWith("rgb") ? color2 : LAYER_NAME_TO_COLOR[color2?.toLowerCase()] ? LAYER_NAME_TO_COLOR[color2?.toLowerCase()] : null;
8766
- if (colorString === null) {
8767
- console.warn(`Color mapping for "${color2}" not found`);
8768
- colorString = "white";
8769
- }
8766
+ let colorString = LAYER_NAME_TO_COLOR[color2.toLowerCase()];
8767
+ if (!colorString)
8768
+ try {
8769
+ colorString = colorParser(color2).rgb().toString();
8770
+ } catch (error) {
8771
+ console.warn(`Invalid color format: '${color2}'`);
8772
+ colorString = "white";
8773
+ }
8770
8774
  ctx.fillStyle = colorString;
8771
8775
  ctx.strokeStyle = colorString;
8772
8776
  } else {
@@ -9698,43 +9702,306 @@ var WarningGraphicsOverlay = ({
9698
9702
  };
9699
9703
 
9700
9704
  // src/components/DimensionOverlay.tsx
9701
- 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";
9702
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
9703
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
+ };
9704
9878
  var DimensionOverlay = ({
9705
9879
  children,
9706
9880
  transform,
9707
- focusOnHover = false
9881
+ focusOnHover = false,
9882
+ primitives = []
9708
9883
  }) => {
9709
9884
  if (!transform) transform = identity4();
9710
9885
  const [dimensionToolVisible, setDimensionToolVisible] = useState3(false);
9711
9886
  const [dimensionToolStretching, setDimensionToolStretching] = useState3(false);
9712
9887
  const [measureToolArmed, setMeasureToolArmed] = useState3(false);
9713
- const disarmMeasure = () => {
9888
+ const [activeSnapIds, setActiveSnapIds] = useState3({
9889
+ start: null,
9890
+ end: null
9891
+ });
9892
+ const disarmMeasure = useCallback3(() => {
9714
9893
  if (measureToolArmed) {
9715
9894
  setMeasureToolArmed(false);
9716
9895
  window.dispatchEvent(new Event("disarm-dimension-tool"));
9717
9896
  }
9718
- };
9897
+ }, [measureToolArmed]);
9719
9898
  const [dStart, setDStart] = useState3({ x: 0, y: 0 });
9720
9899
  const [dEnd, setDEnd] = useState3({ x: 0, y: 0 });
9721
9900
  const mousePosRef = useRef5({ x: 0, y: 0 });
9722
9901
  const containerRef = useRef5(null);
9723
9902
  const container = containerRef.current;
9724
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
+ );
9725
9980
  useEffect7(() => {
9726
9981
  const container2 = containerRef.current;
9727
9982
  const down = (e) => {
9728
9983
  if (e.key === "d") {
9729
- setDStart({ x: mousePosRef.current.x, y: mousePosRef.current.y });
9730
- setDEnd({ x: mousePosRef.current.x, y: mousePosRef.current.y });
9731
- setDimensionToolVisible((visible) => !visible);
9732
- 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
+ }
9733
9999
  disarmMeasure();
9734
10000
  }
9735
10001
  if (e.key === "Escape") {
9736
10002
  setDimensionToolVisible(false);
9737
10003
  setDimensionToolStretching(false);
10004
+ setActiveSnapIds({ start: null, end: null });
9738
10005
  disarmMeasure();
9739
10006
  }
9740
10007
  };
@@ -9768,7 +10035,7 @@ var DimensionOverlay = ({
9768
10035
  container2.removeEventListener("mouseleave", removeKeyListener);
9769
10036
  }
9770
10037
  };
9771
- }, [containerRef]);
10038
+ }, [containerRef, dimensionToolVisible, disarmMeasure, findSnap]);
9772
10039
  const screenDStart = applyToPoint8(transform, dStart);
9773
10040
  const screenDEnd = applyToPoint8(transform, dEnd);
9774
10041
  const arrowScreenBounds = {
@@ -9807,7 +10074,9 @@ var DimensionOverlay = ({
9807
10074
  mousePosRef.current.x = rwPoint.x;
9808
10075
  mousePosRef.current.y = rwPoint.y;
9809
10076
  if (dimensionToolStretching) {
9810
- 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 }));
9811
10080
  }
9812
10081
  },
9813
10082
  onMouseDown: (e) => {
@@ -9816,15 +10085,19 @@ var DimensionOverlay = ({
9816
10085
  const y = e.clientY - rect.top;
9817
10086
  const rwPoint = applyToPoint8(inverse2(transform), { x, y });
9818
10087
  if (measureToolArmed && !dimensionToolVisible) {
9819
- setDStart({ x: rwPoint.x, y: rwPoint.y });
9820
- 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 });
9821
10092
  setDimensionToolVisible(true);
9822
10093
  setDimensionToolStretching(true);
9823
10094
  disarmMeasure();
9824
10095
  } else if (dimensionToolStretching) {
9825
10096
  setDimensionToolStretching(false);
10097
+ setActiveSnapIds((prev) => ({ ...prev, end: null }));
9826
10098
  } else if (dimensionToolVisible) {
9827
10099
  setDimensionToolVisible(false);
10100
+ setActiveSnapIds({ start: null, end: null });
9828
10101
  }
9829
10102
  },
9830
10103
  children: [
@@ -9948,6 +10221,49 @@ var DimensionOverlay = ({
9948
10221
  ]
9949
10222
  }
9950
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
+ }),
9951
10267
  /* @__PURE__ */ jsxs4(
9952
10268
  "div",
9953
10269
  {
@@ -11051,7 +11367,7 @@ var ErrorOverlay = ({
11051
11367
  };
11052
11368
 
11053
11369
  // src/components/MouseElementTracker.tsx
11054
- import { useState as useState7, useMemo as useMemo3 } from "react";
11370
+ import { useState as useState7, useMemo as useMemo4 } from "react";
11055
11371
  import { applyToPoint as applyToPoint12, inverse as inverse5 } from "transformation-matrix";
11056
11372
 
11057
11373
  // src/components/ElementOverlayBox.tsx
@@ -11468,7 +11784,7 @@ var MouseElementTracker = ({
11468
11784
  }) => {
11469
11785
  const [mousedPrimitives, setMousedPrimitives] = useState7([]);
11470
11786
  const [mousePos, setMousePos] = useState7({ x: 0, y: 0 });
11471
- const highlightedPrimitives = useMemo3(() => {
11787
+ const highlightedPrimitives = useMemo4(() => {
11472
11788
  const highlightedPrimitives2 = [];
11473
11789
  for (const primitive of mousedPrimitives) {
11474
11790
  if (primitive._element?.type === "pcb_via") continue;
@@ -11832,15 +12148,15 @@ var PcbGroupOverlay = ({
11832
12148
 
11833
12149
  // src/components/RatsNestOverlay.tsx
11834
12150
  import { applyToPoint as applyToPoint14, identity as identity9 } from "transformation-matrix";
11835
- import { useMemo as useMemo4 } from "react";
12151
+ import { useMemo as useMemo5 } from "react";
11836
12152
  import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
11837
12153
  var RatsNestOverlay = ({ transform, soup, children }) => {
11838
12154
  const isShowingRatsNest = useGlobalStore((s) => s.is_showing_rats_nest);
11839
- const { netMap, idToNetMap } = useMemo4(
12155
+ const { netMap, idToNetMap } = useMemo5(
11840
12156
  () => getFullConnectivityMapFromCircuitJson(soup || []),
11841
12157
  [soup]
11842
12158
  );
11843
- const ratsNestLines = useMemo4(() => {
12159
+ const ratsNestLines = useMemo5(() => {
11844
12160
  if (!soup || !isShowingRatsNest) return [];
11845
12161
  const getElementPosition = (id) => {
11846
12162
  const element = su(soup)[id.replace(/_\d+$/, "")].get(id);
@@ -11937,7 +12253,7 @@ var RatsNestOverlay = ({ transform, soup, children }) => {
11937
12253
  import {
11938
12254
  useEffect as useEffect15,
11939
12255
  useState as useState9,
11940
- useCallback as useCallback3,
12256
+ useCallback as useCallback4,
11941
12257
  useRef as useRef10
11942
12258
  } from "react";
11943
12259
  import { css as css3 } from "@emotion/css";
@@ -11945,7 +12261,7 @@ import { css as css3 } from "@emotion/css";
11945
12261
  // package.json
11946
12262
  var package_default = {
11947
12263
  name: "@tscircuit/pcb-viewer",
11948
- version: "1.11.236",
12264
+ version: "1.11.238",
11949
12265
  main: "dist/index.js",
11950
12266
  type: "module",
11951
12267
  repository: "tscircuit/pcb-viewer",
@@ -12290,20 +12606,20 @@ var ToolbarOverlay = ({ children, elements }) => {
12290
12606
  useHotKey("6", hotKeyCallbacks["6"]);
12291
12607
  useHotKey("7", hotKeyCallbacks["7"]);
12292
12608
  useHotKey("8", hotKeyCallbacks["8"]);
12293
- const handleMouseEnter = useCallback3(() => {
12609
+ const handleMouseEnter = useCallback4(() => {
12294
12610
  setIsMouseOverContainer(true);
12295
12611
  }, [setIsMouseOverContainer]);
12296
- const handleMouseLeave = useCallback3(() => {
12612
+ const handleMouseLeave = useCallback4(() => {
12297
12613
  setIsMouseOverContainer(false);
12298
12614
  setLayerMenuOpen(false);
12299
12615
  setViewMenuOpen(false);
12300
12616
  setErrorsOpen(false);
12301
12617
  setHoveredErrorId(null);
12302
12618
  }, [setIsMouseOverContainer, setHoveredErrorId]);
12303
- const handleLayerMenuToggle = useCallback3(() => {
12619
+ const handleLayerMenuToggle = useCallback4(() => {
12304
12620
  setLayerMenuOpen(!isLayerMenuOpen);
12305
12621
  }, [isLayerMenuOpen]);
12306
- const handleErrorsToggle = useCallback3(() => {
12622
+ const handleErrorsToggle = useCallback4(() => {
12307
12623
  const newErrorsOpen = !isErrorsOpen;
12308
12624
  setErrorsOpen(newErrorsOpen);
12309
12625
  if (newErrorsOpen) {
@@ -12313,20 +12629,20 @@ var ToolbarOverlay = ({ children, elements }) => {
12313
12629
  setHoveredErrorId(null);
12314
12630
  }
12315
12631
  }, [isErrorsOpen, setHoveredErrorId]);
12316
- const handleEditTraceToggle = useCallback3(() => {
12632
+ const handleEditTraceToggle = useCallback4(() => {
12317
12633
  setEditMode(editModes.in_draw_trace_mode ? "off" : "draw_trace");
12318
12634
  }, [editModes.in_draw_trace_mode, setEditMode]);
12319
- const handleMoveComponentToggle = useCallback3(() => {
12635
+ const handleMoveComponentToggle = useCallback4(() => {
12320
12636
  setEditMode(editModes.in_move_footprint_mode ? "off" : "move_footprint");
12321
12637
  }, [editModes.in_move_footprint_mode, setEditMode]);
12322
- const handleRatsNestToggle = useCallback3(() => {
12638
+ const handleRatsNestToggle = useCallback4(() => {
12323
12639
  setIsShowingRatsNest(!viewSettings.is_showing_rats_nest);
12324
12640
  }, [viewSettings.is_showing_rats_nest, setIsShowingRatsNest]);
12325
- const handleMeasureToolClick = useCallback3(() => {
12641
+ const handleMeasureToolClick = useCallback4(() => {
12326
12642
  setMeasureToolArmed(true);
12327
12643
  window.dispatchEvent(new Event("arm-dimension-tool"));
12328
12644
  }, []);
12329
- const handleViewMenuToggle = useCallback3(() => {
12645
+ const handleViewMenuToggle = useCallback4(() => {
12330
12646
  const newViewMenuOpen = !isViewMenuOpen;
12331
12647
  setViewMenuOpen(newViewMenuOpen);
12332
12648
  if (newViewMenuOpen) {
@@ -12761,7 +13077,7 @@ import { jsx as jsx16 } from "react/jsx-runtime";
12761
13077
  var CanvasElementsRenderer = (props) => {
12762
13078
  const { transform, elements } = props;
12763
13079
  const hoveredErrorId = useGlobalStore((state) => state.hovered_error_id);
12764
- const [primitivesWithoutInteractionMetadata, connectivityMap] = useMemo5(() => {
13080
+ const [primitivesWithoutInteractionMetadata, connectivityMap] = useMemo6(() => {
12765
13081
  const primitivesWithoutInteractionMetadata2 = props.elements.flatMap(
12766
13082
  (elm) => convertElementToPrimitives(elm, props.elements)
12767
13083
  );
@@ -12774,7 +13090,7 @@ var CanvasElementsRenderer = (props) => {
12774
13090
  drawingObjectIdsWithMouseOver: /* @__PURE__ */ new Set(),
12775
13091
  primitiveIdsInMousedOverNet: []
12776
13092
  });
12777
- const errorRelatedIds = useMemo5(() => {
13093
+ const errorRelatedIds = useMemo6(() => {
12778
13094
  if (!hoveredErrorId) return [];
12779
13095
  const errorElements = elements.filter(
12780
13096
  (el) => el.type.includes("error")
@@ -12793,7 +13109,7 @@ var CanvasElementsRenderer = (props) => {
12793
13109
  }
12794
13110
  return relatedIds;
12795
13111
  }, [hoveredErrorId, elements]);
12796
- const primitives = useMemo5(() => {
13112
+ const primitives = useMemo6(() => {
12797
13113
  const combinedPrimitiveIds = [
12798
13114
  ...hoverState.primitiveIdsInMousedOverNet,
12799
13115
  ...errorRelatedIds
@@ -12804,7 +13120,7 @@ var CanvasElementsRenderer = (props) => {
12804
13120
  primitiveIdsInMousedOverNet: combinedPrimitiveIds
12805
13121
  });
12806
13122
  }, [primitivesWithoutInteractionMetadata, hoverState, errorRelatedIds]);
12807
- const onMouseOverPrimitives = useCallback4(
13123
+ const onMouseOverPrimitives = useCallback5(
12808
13124
  (primitivesHoveredOver) => {
12809
13125
  const primitiveIdsInMousedOverNet = [];
12810
13126
  for (const primitive of primitivesHoveredOver) {
@@ -12857,6 +13173,7 @@ var CanvasElementsRenderer = (props) => {
12857
13173
  {
12858
13174
  transform,
12859
13175
  focusOnHover: props.focusOnHover,
13176
+ primitives: primitivesWithoutInteractionMetadata,
12860
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(
12861
13178
  DebugGraphicsOverlay,
12862
13179
  {
@@ -12966,7 +13283,7 @@ var PCBViewer = ({
12966
13283
  editEvents = editEventsProp ?? editEvents;
12967
13284
  const initialRenderCompleted = useRef11(false);
12968
13285
  const touchStartRef = useRef11(null);
12969
- const circuitJsonKey = useMemo6(
13286
+ const circuitJsonKey = useMemo7(
12970
13287
  () => calculateCircuitJsonKey(circuitJson),
12971
13288
  [circuitJson]
12972
13289
  );
@@ -12999,12 +13316,12 @@ var PCBViewer = ({
12999
13316
  initialRenderCompleted.current = true;
13000
13317
  }
13001
13318
  }, [circuitJson, refDimensions]);
13002
- const pcbElmsPreEdit = useMemo6(() => {
13319
+ const pcbElmsPreEdit = useMemo7(() => {
13003
13320
  return circuitJson?.filter(
13004
13321
  (e) => e.type.startsWith("pcb_") || e.type.startsWith("source_")
13005
13322
  ) ?? [];
13006
13323
  }, [circuitJsonKey]);
13007
- const elements = useMemo6(() => {
13324
+ const elements = useMemo7(() => {
13008
13325
  return applyEditEvents({
13009
13326
  circuitJson: pcbElmsPreEdit,
13010
13327
  editEvents
@@ -13021,7 +13338,7 @@ var PCBViewer = ({
13021
13338
  setEditEvents(newEditEvents);
13022
13339
  onEditEventsChanged?.(newEditEvents);
13023
13340
  };
13024
- const mergedInitialState = useMemo6(
13341
+ const mergedInitialState = useMemo7(
13025
13342
  () => ({
13026
13343
  ...initialState,
13027
13344
  ...disablePcbGroups && { is_showing_pcb_groups: false }