@tscircuit/pcb-viewer 1.11.281 → 1.11.283

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
@@ -10468,7 +10468,6 @@ var createBoxFromPoints = (points) => {
10468
10468
  }
10469
10469
  return { minX, maxX, minY, maxY };
10470
10470
  };
10471
- var getBoundsFromPoints = createBoxFromPoints;
10472
10471
  var mergeBoundingBoxesInternal = (a, b) => ({
10473
10472
  minX: Math.min(a.minX, b.minX),
10474
10473
  maxX: Math.max(a.maxX, b.maxX),
@@ -10605,7 +10604,7 @@ function calculateDiagonalLabel(params) {
10605
10604
  } = params;
10606
10605
  const deltaX = dimensionEnd.x - dimensionStart.x;
10607
10606
  const deltaY = dimensionEnd.y - dimensionStart.y;
10608
- const distance5 = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
10607
+ const distance4 = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
10609
10608
  const screenDeltaX = screenDimensionEnd.x - screenDimensionStart.x;
10610
10609
  const screenDeltaY = screenDimensionEnd.y - screenDimensionStart.y;
10611
10610
  const screenDistance = Math.sqrt(
@@ -10647,11 +10646,11 @@ function calculateDiagonalLabel(params) {
10647
10646
  const x = midX + offsetX;
10648
10647
  const y = midY + offsetY;
10649
10648
  return {
10650
- distance: distance5,
10649
+ distance: distance4,
10651
10650
  screenDistance,
10652
10651
  x,
10653
10652
  y,
10654
- show: distance5 > 0.01 && screenDistance > 30 && isDiagonal
10653
+ show: distance4 > 0.01 && screenDistance > 30 && isDiagonal
10655
10654
  };
10656
10655
  }
10657
10656
 
@@ -10945,11 +10944,11 @@ var DimensionOverlay = ({
10945
10944
  for (const snap of snappingPointsWithScreen) {
10946
10945
  const dx = snap.screenPoint.x - screenPoint.x;
10947
10946
  const dy = snap.screenPoint.y - screenPoint.y;
10948
- const distance5 = Math.hypot(dx, dy);
10949
- if (distance5 > SNAP_THRESHOLD_PX) continue;
10950
- if (!bestMatch || distance5 < bestMatch.distance) {
10947
+ const distance4 = Math.hypot(dx, dy);
10948
+ if (distance4 > SNAP_THRESHOLD_PX) continue;
10949
+ if (!bestMatch || distance4 < bestMatch.distance) {
10951
10950
  bestMatch = {
10952
- distance: distance5,
10951
+ distance: distance4,
10953
10952
  id: snap.id,
10954
10953
  point: snap.point
10955
10954
  };
@@ -11548,10 +11547,10 @@ var isInsideOfSmtpad = (elm, point, padding = 0) => {
11548
11547
  };
11549
11548
  var isInsideOfPlatedHole = (hole, point, padding = 0) => {
11550
11549
  if (hole.shape === "circle") {
11551
- const distance5 = Math.sqrt(
11550
+ const distance4 = Math.sqrt(
11552
11551
  (point.x - hole.x) ** 2 + (point.y - hole.y) ** 2
11553
11552
  );
11554
- return distance5 <= hole.outer_diameter / 2 + padding;
11553
+ return distance4 <= hole.outer_diameter / 2 + padding;
11555
11554
  } else if (hole.shape === "circular_hole_with_rect_pad") {
11556
11555
  const dx = Math.abs(point.x - hole.x);
11557
11556
  const dy = Math.abs(point.y - hole.y);
@@ -12379,7 +12378,7 @@ var ErrorOverlay = ({
12379
12378
 
12380
12379
  // src/components/MouseElementTracker.tsx
12381
12380
  import { pointToSegmentDistance } from "@tscircuit/math-utils";
12382
- import { distance as distance4 } from "circuit-json";
12381
+ import { distance as distance3 } from "circuit-json";
12383
12382
 
12384
12383
  // src/lib/util/if-sets-match-exactly.ts
12385
12384
  function ifSetsMatchExactly(set1, set2) {
@@ -12389,7 +12388,7 @@ function ifSetsMatchExactly(set1, set2) {
12389
12388
 
12390
12389
  // src/components/MouseElementTracker.tsx
12391
12390
  import { useState as useState7, useMemo as useMemo5 } from "react";
12392
- import { applyToPoint as applyToPoint13, inverse as inverse5 } from "transformation-matrix";
12391
+ import { applyToPoint as applyToPoint14, inverse as inverse5 } from "transformation-matrix";
12393
12392
 
12394
12393
  // src/components/ElementOverlayBox.tsx
12395
12394
  import { useEffect as useEffect11, useState as useState6 } from "react";
@@ -12666,28 +12665,10 @@ var ElementOverlayBox = ({
12666
12665
  )) });
12667
12666
  };
12668
12667
 
12669
- // src/components/GroupAnchorOffsetOverlay/index.tsx
12668
+ // src/components/AnchorOffsetOverlay/Board/index.tsx
12670
12669
  import { applyToPoint as applyToPoint12 } from "transformation-matrix";
12671
12670
 
12672
- // src/components/GroupAnchorOffsetOverlay/calculateGroupBoundingBox.ts
12673
- import { distance as distance3 } from "circuit-json";
12674
- var calculateGroupBoundingBox = (groupComponents) => {
12675
- const points = [];
12676
- for (const comp of groupComponents) {
12677
- if (!comp.center) {
12678
- continue;
12679
- }
12680
- const width = typeof comp.width === "number" ? comp.width : distance3.parse(comp.width);
12681
- const height = typeof comp.height === "number" ? comp.height : distance3.parse(comp.height);
12682
- const halfWidth = width / 2;
12683
- const halfHeight = height / 2;
12684
- points.push({ x: comp.center.x - halfWidth, y: comp.center.y - halfHeight });
12685
- points.push({ x: comp.center.x + halfWidth, y: comp.center.y + halfHeight });
12686
- }
12687
- return getBoundsFromPoints(points);
12688
- };
12689
-
12690
- // src/components/GroupAnchorOffsetOverlay/constants.ts
12671
+ // src/components/AnchorOffsetOverlay/common/constants.ts
12691
12672
  var VISUAL_CONFIG = {
12692
12673
  GROUP_PADDING: 1,
12693
12674
  MIN_LINE_LENGTH_FOR_LABEL: 40,
@@ -12698,7 +12679,9 @@ var VISUAL_CONFIG = {
12698
12679
  LINE_STROKE_WIDTH: 1.5,
12699
12680
  LINE_DASH_PATTERN: "4,4",
12700
12681
  COMPONENT_MARKER_RADIUS: 3,
12701
- LABEL_FONT_SIZE: 11
12682
+ LABEL_FONT_SIZE: 11,
12683
+ ANCHOR_MARKER_SIZE: 6,
12684
+ ANCHOR_MARKER_STROKE_WIDTH: 1.5
12702
12685
  };
12703
12686
  var COLORS = {
12704
12687
  OFFSET_LINE: "white",
@@ -12707,8 +12690,255 @@ var COLORS = {
12707
12690
  LABEL_TEXT: "white"
12708
12691
  };
12709
12692
 
12710
- // src/components/GroupAnchorOffsetOverlay/index.tsx
12693
+ // src/components/AnchorOffsetOverlay/common/guards.ts
12694
+ var isPcbComponent = (element) => element?.type === "pcb_component";
12695
+ var isPcbGroup = (element) => element?.type === "pcb_group";
12696
+ var isPcbBoard = (element) => element?.type === "pcb_board";
12697
+
12698
+ // src/components/AnchorOffsetOverlay/Board/index.tsx
12711
12699
  import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
12700
+ var BoardAnchorOffsetOverlay = ({
12701
+ elements,
12702
+ highlightedPrimitives,
12703
+ transform,
12704
+ containerWidth,
12705
+ containerHeight
12706
+ }) => {
12707
+ const boards = elements.filter((el) => isPcbBoard(el));
12708
+ const components = elements.filter(
12709
+ (el) => isPcbComponent(el)
12710
+ );
12711
+ const groups = elements.filter((el) => isPcbGroup(el));
12712
+ const hoveredComponentIds = highlightedPrimitives.map((primitive) => {
12713
+ if (isPcbComponent(primitive._parent_pcb_component)) {
12714
+ return primitive._parent_pcb_component.pcb_component_id;
12715
+ }
12716
+ if (isPcbComponent(primitive._element)) {
12717
+ return primitive._element.pcb_component_id;
12718
+ }
12719
+ return null;
12720
+ }).filter((id) => Boolean(id));
12721
+ const hoveredGroupIds = /* @__PURE__ */ new Set();
12722
+ hoveredComponentIds.forEach((componentId) => {
12723
+ const component = components.find((c) => c.pcb_component_id === componentId);
12724
+ if (component?.pcb_group_id) {
12725
+ hoveredGroupIds.add(component.pcb_group_id);
12726
+ }
12727
+ });
12728
+ const isShowingAnchorOffsets = useGlobalStore(
12729
+ (state) => state.is_showing_group_anchor_offsets
12730
+ );
12731
+ if (!isShowingAnchorOffsets && hoveredComponentIds.length === 0) {
12732
+ return null;
12733
+ }
12734
+ const componentTargets = components.map((component) => {
12735
+ const boardId = component.positioned_relative_to_pcb_board_id;
12736
+ if (!boardId) return null;
12737
+ const board = boards.find((b) => b.pcb_board_id === boardId);
12738
+ return board ? { component, board, type: "component" } : null;
12739
+ }).filter(
12740
+ (target) => Boolean(target)
12741
+ );
12742
+ const groupTargets = groups.map((group) => {
12743
+ const boardId = group.positioned_relative_to_pcb_board_id;
12744
+ if (!boardId || !group.center) return null;
12745
+ const board = boards.find((b) => b.pcb_board_id === boardId);
12746
+ return board ? { group, board, type: "group" } : null;
12747
+ }).filter(
12748
+ (target) => Boolean(target)
12749
+ );
12750
+ const targets = [...componentTargets, ...groupTargets];
12751
+ if (targets.length === 0) return null;
12752
+ const shouldShowAllTargets = hoveredComponentIds.length === 0;
12753
+ const labelStyle = {
12754
+ color: COLORS.LABEL_TEXT,
12755
+ mixBlendMode: "difference",
12756
+ pointerEvents: "none",
12757
+ fontSize: VISUAL_CONFIG.LABEL_FONT_SIZE,
12758
+ fontFamily: "monospace",
12759
+ fontWeight: "bold"
12760
+ };
12761
+ const targetEntries = targets.filter((target) => {
12762
+ if (target.type === "component") {
12763
+ return shouldShowAllTargets || hoveredComponentIds.includes(target.component.pcb_component_id);
12764
+ } else {
12765
+ return shouldShowAllTargets || hoveredGroupIds.has(target.group.pcb_group_id);
12766
+ }
12767
+ });
12768
+ if (targetEntries.length === 0) return null;
12769
+ const boardAnchorScreens = /* @__PURE__ */ new Map();
12770
+ return /* @__PURE__ */ jsx12(
12771
+ "div",
12772
+ {
12773
+ style: {
12774
+ position: "absolute",
12775
+ left: 0,
12776
+ top: 0,
12777
+ width: containerWidth,
12778
+ height: containerHeight,
12779
+ overflow: "hidden",
12780
+ pointerEvents: "none",
12781
+ zIndex: zIndexMap.dimensionOverlay
12782
+ },
12783
+ children: /* @__PURE__ */ jsxs9(
12784
+ "svg",
12785
+ {
12786
+ style: {
12787
+ position: "absolute",
12788
+ left: 0,
12789
+ top: 0,
12790
+ pointerEvents: "none"
12791
+ },
12792
+ width: containerWidth,
12793
+ height: containerHeight,
12794
+ children: [
12795
+ targetEntries.map((target) => {
12796
+ const anchorPosition = target.board.center;
12797
+ const anchorKey = target.board.pcb_board_id;
12798
+ let targetCenter;
12799
+ let targetId;
12800
+ let displayOffsetX;
12801
+ let displayOffsetY;
12802
+ if (target.type === "component") {
12803
+ targetCenter = target.component.center;
12804
+ targetId = target.component.pcb_component_id;
12805
+ displayOffsetX = target.component.display_offset_x;
12806
+ displayOffsetY = target.component.display_offset_y;
12807
+ } else {
12808
+ if (!target.group.center) return null;
12809
+ targetCenter = {
12810
+ x: target.group.center.x,
12811
+ y: target.group.center.y
12812
+ };
12813
+ targetId = target.group.pcb_group_id;
12814
+ displayOffsetX = target.group.display_offset_x;
12815
+ displayOffsetY = target.group.display_offset_y;
12816
+ }
12817
+ if (!boardAnchorScreens.has(anchorKey)) {
12818
+ const screenPoint = applyToPoint12(transform, anchorPosition);
12819
+ boardAnchorScreens.set(anchorKey, screenPoint);
12820
+ }
12821
+ const anchorMarkerScreen = boardAnchorScreens.get(anchorKey);
12822
+ const targetScreen = applyToPoint12(transform, targetCenter);
12823
+ const offsetX = targetCenter.x - anchorPosition.x;
12824
+ const offsetY = targetCenter.y - anchorPosition.y;
12825
+ const xLineLength = Math.abs(targetScreen.x - anchorMarkerScreen.x);
12826
+ const yLineLength = Math.abs(targetScreen.y - anchorMarkerScreen.y);
12827
+ const isTargetAboveAnchor = targetScreen.y < anchorMarkerScreen.y;
12828
+ const isTargetRightOfAnchor = targetScreen.x > anchorMarkerScreen.x;
12829
+ const xLabelOffset = isTargetAboveAnchor ? VISUAL_CONFIG.LABEL_OFFSET_ABOVE : VISUAL_CONFIG.LABEL_OFFSET_BELOW;
12830
+ const yLabelOffset = isTargetRightOfAnchor ? VISUAL_CONFIG.LABEL_OFFSET_RIGHT : VISUAL_CONFIG.LABEL_OFFSET_LEFT;
12831
+ const shouldShowXLabel = xLineLength > VISUAL_CONFIG.MIN_LINE_LENGTH_FOR_LABEL;
12832
+ const shouldShowYLabel = yLineLength > VISUAL_CONFIG.MIN_LINE_LENGTH_FOR_LABEL;
12833
+ const xLabelText = displayOffsetX ?? `Board \u0394x: ${offsetX.toFixed(2)}mm`;
12834
+ const yLabelText = displayOffsetY ?? `Board \u0394y: ${offsetY.toFixed(2)}mm`;
12835
+ return /* @__PURE__ */ jsxs9("g", { children: [
12836
+ /* @__PURE__ */ jsx12(
12837
+ "line",
12838
+ {
12839
+ x1: anchorMarkerScreen.x,
12840
+ y1: anchorMarkerScreen.y,
12841
+ x2: targetScreen.x,
12842
+ y2: anchorMarkerScreen.y,
12843
+ stroke: COLORS.OFFSET_LINE,
12844
+ strokeWidth: VISUAL_CONFIG.LINE_STROKE_WIDTH,
12845
+ strokeDasharray: VISUAL_CONFIG.LINE_DASH_PATTERN
12846
+ }
12847
+ ),
12848
+ /* @__PURE__ */ jsx12(
12849
+ "line",
12850
+ {
12851
+ x1: targetScreen.x,
12852
+ y1: anchorMarkerScreen.y,
12853
+ x2: targetScreen.x,
12854
+ y2: targetScreen.y,
12855
+ stroke: COLORS.OFFSET_LINE,
12856
+ strokeWidth: VISUAL_CONFIG.LINE_STROKE_WIDTH,
12857
+ strokeDasharray: VISUAL_CONFIG.LINE_DASH_PATTERN
12858
+ }
12859
+ ),
12860
+ /* @__PURE__ */ jsx12(
12861
+ "circle",
12862
+ {
12863
+ cx: targetScreen.x,
12864
+ cy: targetScreen.y,
12865
+ r: VISUAL_CONFIG.COMPONENT_MARKER_RADIUS,
12866
+ fill: COLORS.COMPONENT_MARKER_FILL,
12867
+ stroke: COLORS.COMPONENT_MARKER_STROKE,
12868
+ strokeWidth: 1
12869
+ }
12870
+ ),
12871
+ shouldShowXLabel && /* @__PURE__ */ jsx12(
12872
+ "foreignObject",
12873
+ {
12874
+ x: Math.min(anchorMarkerScreen.x, targetScreen.x),
12875
+ y: anchorMarkerScreen.y + xLabelOffset,
12876
+ width: Math.abs(targetScreen.x - anchorMarkerScreen.x),
12877
+ height: 20,
12878
+ style: { overflow: "visible" },
12879
+ children: /* @__PURE__ */ jsx12("div", { style: { ...labelStyle, textAlign: "center" }, children: xLabelText })
12880
+ }
12881
+ ),
12882
+ shouldShowYLabel && /* @__PURE__ */ jsx12(
12883
+ "foreignObject",
12884
+ {
12885
+ x: targetScreen.x + yLabelOffset,
12886
+ y: Math.min(anchorMarkerScreen.y, targetScreen.y),
12887
+ width: 20,
12888
+ height: Math.abs(targetScreen.y - anchorMarkerScreen.y),
12889
+ style: { overflow: "visible" },
12890
+ children: /* @__PURE__ */ jsx12(
12891
+ "div",
12892
+ {
12893
+ style: {
12894
+ ...labelStyle,
12895
+ display: "flex",
12896
+ alignItems: "center",
12897
+ height: "100%"
12898
+ },
12899
+ children: yLabelText
12900
+ }
12901
+ )
12902
+ }
12903
+ )
12904
+ ] }, `${target.board.pcb_board_id}-${targetId}-${target.type}`);
12905
+ }),
12906
+ Array.from(boardAnchorScreens.entries()).map(
12907
+ ([boardId, anchorScreen]) => /* @__PURE__ */ jsxs9("g", { children: [
12908
+ /* @__PURE__ */ jsx12(
12909
+ "line",
12910
+ {
12911
+ x1: anchorScreen.x - VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
12912
+ y1: anchorScreen.y,
12913
+ x2: anchorScreen.x + VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
12914
+ y2: anchorScreen.y,
12915
+ stroke: COLORS.OFFSET_LINE,
12916
+ strokeWidth: VISUAL_CONFIG.ANCHOR_MARKER_STROKE_WIDTH
12917
+ }
12918
+ ),
12919
+ /* @__PURE__ */ jsx12(
12920
+ "line",
12921
+ {
12922
+ x1: anchorScreen.x,
12923
+ y1: anchorScreen.y - VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
12924
+ x2: anchorScreen.x,
12925
+ y2: anchorScreen.y + VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
12926
+ stroke: COLORS.OFFSET_LINE,
12927
+ strokeWidth: VISUAL_CONFIG.ANCHOR_MARKER_STROKE_WIDTH
12928
+ }
12929
+ )
12930
+ ] }, `anchor-${boardId}`)
12931
+ )
12932
+ ]
12933
+ }
12934
+ )
12935
+ }
12936
+ );
12937
+ };
12938
+
12939
+ // src/components/AnchorOffsetOverlay/Group/index.tsx
12940
+ import { applyToPoint as applyToPoint13 } from "transformation-matrix";
12941
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
12712
12942
  var GroupAnchorOffsetOverlay = ({
12713
12943
  elements,
12714
12944
  highlightedPrimitives,
@@ -12716,52 +12946,68 @@ var GroupAnchorOffsetOverlay = ({
12716
12946
  containerWidth,
12717
12947
  containerHeight
12718
12948
  }) => {
12719
- const is_showing_group_anchor_offsets = useGlobalStore(
12949
+ const groups = elements.filter((el) => isPcbGroup(el));
12950
+ const components = elements.filter(
12951
+ (el) => isPcbComponent(el)
12952
+ );
12953
+ const hoveredComponentIds = highlightedPrimitives.map((primitive) => {
12954
+ if (isPcbComponent(primitive._parent_pcb_component)) {
12955
+ return primitive._parent_pcb_component.pcb_component_id;
12956
+ }
12957
+ if (isPcbComponent(primitive._element)) {
12958
+ return primitive._element.pcb_component_id;
12959
+ }
12960
+ return null;
12961
+ }).filter((id) => Boolean(id));
12962
+ const hoveredGroupIds = /* @__PURE__ */ new Set();
12963
+ hoveredComponentIds.forEach((componentId) => {
12964
+ const component = components.find((c) => c.pcb_component_id === componentId);
12965
+ if (component?.pcb_group_id) {
12966
+ hoveredGroupIds.add(component.pcb_group_id);
12967
+ }
12968
+ if (component?.position_mode === "relative_to_group_anchor" && component.positioned_relative_to_pcb_group_id) {
12969
+ hoveredGroupIds.add(component.positioned_relative_to_pcb_group_id);
12970
+ }
12971
+ });
12972
+ const isShowingAnchorOffsets = useGlobalStore(
12720
12973
  (s) => s.is_showing_group_anchor_offsets
12721
12974
  );
12722
- if (!is_showing_group_anchor_offsets || highlightedPrimitives.length === 0) {
12975
+ if (!isShowingAnchorOffsets && hoveredComponentIds.length === 0) {
12723
12976
  return null;
12724
12977
  }
12725
- const hoveredPrimitive = highlightedPrimitives.find(
12726
- (p) => p._parent_pcb_component?.type === "pcb_component" || p._element?.type === "pcb_component"
12978
+ const componentTargets = components.map((component) => {
12979
+ if (component.position_mode === "relative_to_group_anchor" && component.positioned_relative_to_pcb_group_id) {
12980
+ const parentGroup = groups.find(
12981
+ (group) => group.pcb_group_id === component.positioned_relative_to_pcb_group_id
12982
+ );
12983
+ return parentGroup && parentGroup.anchor_position ? { component, parentGroup, type: "component" } : null;
12984
+ }
12985
+ if (component.pcb_group_id) {
12986
+ const parentGroup = groups.find(
12987
+ (group) => group.pcb_group_id === component.pcb_group_id
12988
+ );
12989
+ return parentGroup && parentGroup.anchor_position ? { component, parentGroup, type: "component" } : null;
12990
+ }
12991
+ return null;
12992
+ }).filter(
12993
+ (target) => Boolean(target)
12727
12994
  );
12728
- if (!hoveredPrimitive) return null;
12729
- const hoveredElement = hoveredPrimitive._parent_pcb_component || hoveredPrimitive._element;
12730
- if (!hoveredElement) return null;
12731
- let parentGroup;
12732
- let targetCenter;
12733
- if (hoveredElement.type === "pcb_component" && hoveredElement.position_mode === "relative_to_group_anchor") {
12734
- parentGroup = elements.filter((el) => el.type === "pcb_group").find(
12735
- (group) => group.pcb_group_id === hoveredElement.positioned_relative_to_pcb_group_id
12736
- );
12737
- targetCenter = hoveredElement.center;
12738
- } else if ("pcb_group_id" in hoveredElement) {
12739
- parentGroup = elements.filter((el) => el.type === "pcb_group").find((group) => group.pcb_group_id === hoveredElement.pcb_group_id);
12740
- targetCenter = hoveredElement.center;
12741
- }
12742
- if (!parentGroup?.anchor_position || !targetCenter) return null;
12743
- const groupComponents = elements.filter((el) => el.type === "pcb_component").filter((comp) => comp.pcb_group_id === parentGroup.pcb_group_id);
12744
- const boundingBox = calculateGroupBoundingBox(groupComponents);
12745
- if (!boundingBox) return null;
12746
- const groupBounds = {
12747
- minX: boundingBox.minX - VISUAL_CONFIG.GROUP_PADDING,
12748
- maxX: boundingBox.maxX + VISUAL_CONFIG.GROUP_PADDING,
12749
- minY: boundingBox.minY - VISUAL_CONFIG.GROUP_PADDING,
12750
- maxY: boundingBox.maxY + VISUAL_CONFIG.GROUP_PADDING
12751
- };
12752
- const anchorMarkerPosition = parentGroup.anchor_position;
12753
- const offsetX = targetCenter.x - anchorMarkerPosition.x;
12754
- const offsetY = targetCenter.y - anchorMarkerPosition.y;
12755
- const anchorMarkerScreen = applyToPoint12(transform, anchorMarkerPosition);
12756
- const componentScreen = applyToPoint12(transform, targetCenter);
12757
- const xLineLength = Math.abs(componentScreen.x - anchorMarkerScreen.x);
12758
- const yLineLength = Math.abs(componentScreen.y - anchorMarkerScreen.y);
12759
- const isComponentAboveAnchor = componentScreen.y < anchorMarkerScreen.y;
12760
- const isComponentRightOfAnchor = componentScreen.x > anchorMarkerScreen.x;
12761
- const xLabelOffset = isComponentAboveAnchor ? VISUAL_CONFIG.LABEL_OFFSET_ABOVE : VISUAL_CONFIG.LABEL_OFFSET_BELOW;
12762
- const yLabelOffset = isComponentRightOfAnchor ? VISUAL_CONFIG.LABEL_OFFSET_RIGHT : VISUAL_CONFIG.LABEL_OFFSET_LEFT;
12763
- const shouldShowXLabel = xLineLength > VISUAL_CONFIG.MIN_LINE_LENGTH_FOR_LABEL;
12764
- const shouldShowYLabel = yLineLength > VISUAL_CONFIG.MIN_LINE_LENGTH_FOR_LABEL;
12995
+ const groupTargets = groups.map((group) => {
12996
+ if (group.position_mode === "relative_to_group_anchor" && group.positioned_relative_to_pcb_group_id) {
12997
+ const parentGroup = groups.find(
12998
+ (g) => g.pcb_group_id === group.positioned_relative_to_pcb_group_id
12999
+ );
13000
+ if (parentGroup && parentGroup.anchor_position && group.center) {
13001
+ return { group, parentGroup, type: "group" };
13002
+ }
13003
+ }
13004
+ return null;
13005
+ }).filter(
13006
+ (target) => Boolean(target)
13007
+ );
13008
+ const targets = [...componentTargets, ...groupTargets];
13009
+ if (targets.length === 0) return null;
13010
+ const shouldShowAllTargets = hoveredComponentIds.length === 0;
12765
13011
  const labelStyle = {
12766
13012
  color: COLORS.LABEL_TEXT,
12767
13013
  mixBlendMode: "difference",
@@ -12770,7 +13016,24 @@ var GroupAnchorOffsetOverlay = ({
12770
13016
  fontFamily: "monospace",
12771
13017
  fontWeight: "bold"
12772
13018
  };
12773
- return /* @__PURE__ */ jsxs9(
13019
+ const targetEntries = targets.filter((target) => {
13020
+ if (target.type === "component") {
13021
+ return shouldShowAllTargets || hoveredComponentIds.includes(target.component.pcb_component_id);
13022
+ } else {
13023
+ return shouldShowAllTargets || hoveredGroupIds.has(target.group.pcb_group_id) || hoveredGroupIds.has(target.parentGroup.pcb_group_id);
13024
+ }
13025
+ });
13026
+ if (targetEntries.length === 0) return null;
13027
+ const groupAnchorScreens = /* @__PURE__ */ new Map();
13028
+ targetEntries.forEach((target) => {
13029
+ if (!target.parentGroup.anchor_position) return;
13030
+ const anchorScreen = applyToPoint13(
13031
+ transform,
13032
+ target.parentGroup.anchor_position
13033
+ );
13034
+ groupAnchorScreens.set(target.parentGroup.pcb_group_id, anchorScreen);
13035
+ });
13036
+ return /* @__PURE__ */ jsx13(
12774
13037
  "div",
12775
13038
  {
12776
13039
  style: {
@@ -12783,102 +13046,172 @@ var GroupAnchorOffsetOverlay = ({
12783
13046
  pointerEvents: "none",
12784
13047
  zIndex: zIndexMap.dimensionOverlay
12785
13048
  },
12786
- children: [
12787
- /* @__PURE__ */ jsxs9(
12788
- "svg",
12789
- {
12790
- style: {
12791
- position: "absolute",
12792
- left: 0,
12793
- top: 0,
12794
- pointerEvents: "none"
12795
- },
12796
- width: containerWidth,
12797
- height: containerHeight,
12798
- children: [
12799
- /* @__PURE__ */ jsx12(
12800
- "line",
12801
- {
12802
- x1: anchorMarkerScreen.x,
12803
- y1: anchorMarkerScreen.y,
12804
- x2: componentScreen.x,
12805
- y2: anchorMarkerScreen.y,
12806
- stroke: COLORS.OFFSET_LINE,
12807
- strokeWidth: VISUAL_CONFIG.LINE_STROKE_WIDTH,
12808
- strokeDasharray: VISUAL_CONFIG.LINE_DASH_PATTERN
12809
- }
12810
- ),
12811
- /* @__PURE__ */ jsx12(
12812
- "line",
12813
- {
12814
- x1: componentScreen.x,
12815
- y1: anchorMarkerScreen.y,
12816
- x2: componentScreen.x,
12817
- y2: componentScreen.y,
12818
- stroke: COLORS.OFFSET_LINE,
12819
- strokeWidth: VISUAL_CONFIG.LINE_STROKE_WIDTH,
12820
- strokeDasharray: VISUAL_CONFIG.LINE_DASH_PATTERN
12821
- }
12822
- ),
12823
- /* @__PURE__ */ jsx12(
12824
- "circle",
13049
+ children: /* @__PURE__ */ jsxs10(
13050
+ "svg",
13051
+ {
13052
+ style: {
13053
+ position: "absolute",
13054
+ left: 0,
13055
+ top: 0,
13056
+ pointerEvents: "none"
13057
+ },
13058
+ width: containerWidth,
13059
+ height: containerHeight,
13060
+ children: [
13061
+ targetEntries.map((target) => {
13062
+ const anchor = target.parentGroup.anchor_position;
13063
+ if (!anchor) return null;
13064
+ const anchorMarkerPosition = { x: anchor.x, y: anchor.y };
13065
+ let targetCenter = null;
13066
+ let targetId;
13067
+ let displayOffsetX;
13068
+ let displayOffsetY;
13069
+ if (target.type === "component") {
13070
+ const center = target.component.center;
13071
+ if (!center) return null;
13072
+ targetCenter = { x: center.x, y: center.y };
13073
+ targetId = target.component.pcb_component_id;
13074
+ displayOffsetX = target.component.display_offset_x;
13075
+ displayOffsetY = target.component.display_offset_y;
13076
+ } else {
13077
+ if (!target.group.center) return null;
13078
+ targetCenter = {
13079
+ x: target.group.center.x,
13080
+ y: target.group.center.y
13081
+ };
13082
+ targetId = target.group.pcb_group_id;
13083
+ displayOffsetX = target.group.display_offset_x;
13084
+ displayOffsetY = target.group.display_offset_y;
13085
+ }
13086
+ const offsetX = targetCenter.x - anchorMarkerPosition.x;
13087
+ const offsetY = targetCenter.y - anchorMarkerPosition.y;
13088
+ const anchorMarkerScreen = applyToPoint13(
13089
+ transform,
13090
+ anchorMarkerPosition
13091
+ );
13092
+ const targetScreen = applyToPoint13(transform, targetCenter);
13093
+ const xLineLength = Math.abs(targetScreen.x - anchorMarkerScreen.x);
13094
+ const yLineLength = Math.abs(targetScreen.y - anchorMarkerScreen.y);
13095
+ const isTargetAboveAnchor = targetScreen.y < anchorMarkerScreen.y;
13096
+ const isTargetRightOfAnchor = targetScreen.x > anchorMarkerScreen.x;
13097
+ const xLabelOffset = isTargetAboveAnchor ? VISUAL_CONFIG.LABEL_OFFSET_ABOVE : VISUAL_CONFIG.LABEL_OFFSET_BELOW;
13098
+ const yLabelOffset = isTargetRightOfAnchor ? VISUAL_CONFIG.LABEL_OFFSET_RIGHT : VISUAL_CONFIG.LABEL_OFFSET_LEFT;
13099
+ const shouldShowXLabel = xLineLength > VISUAL_CONFIG.MIN_LINE_LENGTH_FOR_LABEL;
13100
+ const shouldShowYLabel = yLineLength > VISUAL_CONFIG.MIN_LINE_LENGTH_FOR_LABEL;
13101
+ const xLabelText = displayOffsetX ?? `\u0394x: ${offsetX.toFixed(2)}mm`;
13102
+ const yLabelText = displayOffsetY ?? `\u0394y: ${offsetY.toFixed(2)}mm`;
13103
+ return /* @__PURE__ */ jsxs10(
13104
+ "g",
12825
13105
  {
12826
- cx: componentScreen.x,
12827
- cy: componentScreen.y,
12828
- r: VISUAL_CONFIG.COMPONENT_MARKER_RADIUS,
12829
- fill: COLORS.COMPONENT_MARKER_FILL,
12830
- stroke: COLORS.COMPONENT_MARKER_STROKE,
12831
- strokeWidth: 1
12832
- }
12833
- )
12834
- ]
12835
- }
12836
- ),
12837
- shouldShowXLabel && /* @__PURE__ */ jsxs9(
12838
- "div",
12839
- {
12840
- style: {
12841
- ...labelStyle,
12842
- position: "absolute",
12843
- left: Math.min(anchorMarkerScreen.x, componentScreen.x),
12844
- top: anchorMarkerScreen.y + xLabelOffset,
12845
- width: Math.abs(componentScreen.x - anchorMarkerScreen.x),
12846
- textAlign: "center"
12847
- },
12848
- children: [
12849
- "\u0394x: ",
12850
- offsetX.toFixed(2),
12851
- "mm"
12852
- ]
12853
- }
12854
- ),
12855
- shouldShowYLabel && /* @__PURE__ */ jsxs9(
12856
- "div",
12857
- {
12858
- style: {
12859
- ...labelStyle,
12860
- position: "absolute",
12861
- left: componentScreen.x + yLabelOffset,
12862
- top: Math.min(anchorMarkerScreen.y, componentScreen.y),
12863
- height: Math.abs(componentScreen.y - anchorMarkerScreen.y),
12864
- display: "flex",
12865
- flexDirection: "column",
12866
- justifyContent: "center"
12867
- },
12868
- children: [
12869
- "\u0394y: ",
12870
- offsetY.toFixed(2),
12871
- "mm"
12872
- ]
12873
- }
12874
- )
12875
- ]
13106
+ children: [
13107
+ /* @__PURE__ */ jsx13(
13108
+ "line",
13109
+ {
13110
+ x1: anchorMarkerScreen.x,
13111
+ y1: anchorMarkerScreen.y,
13112
+ x2: targetScreen.x,
13113
+ y2: anchorMarkerScreen.y,
13114
+ stroke: COLORS.OFFSET_LINE,
13115
+ strokeWidth: VISUAL_CONFIG.LINE_STROKE_WIDTH,
13116
+ strokeDasharray: VISUAL_CONFIG.LINE_DASH_PATTERN
13117
+ }
13118
+ ),
13119
+ /* @__PURE__ */ jsx13(
13120
+ "line",
13121
+ {
13122
+ x1: targetScreen.x,
13123
+ y1: anchorMarkerScreen.y,
13124
+ x2: targetScreen.x,
13125
+ y2: targetScreen.y,
13126
+ stroke: COLORS.OFFSET_LINE,
13127
+ strokeWidth: VISUAL_CONFIG.LINE_STROKE_WIDTH,
13128
+ strokeDasharray: VISUAL_CONFIG.LINE_DASH_PATTERN
13129
+ }
13130
+ ),
13131
+ /* @__PURE__ */ jsx13(
13132
+ "circle",
13133
+ {
13134
+ cx: targetScreen.x,
13135
+ cy: targetScreen.y,
13136
+ r: VISUAL_CONFIG.COMPONENT_MARKER_RADIUS,
13137
+ fill: COLORS.COMPONENT_MARKER_FILL,
13138
+ stroke: COLORS.COMPONENT_MARKER_STROKE,
13139
+ strokeWidth: 1
13140
+ }
13141
+ ),
13142
+ shouldShowXLabel && /* @__PURE__ */ jsx13(
13143
+ "foreignObject",
13144
+ {
13145
+ x: Math.min(anchorMarkerScreen.x, targetScreen.x),
13146
+ y: anchorMarkerScreen.y + xLabelOffset,
13147
+ width: Math.abs(targetScreen.x - anchorMarkerScreen.x),
13148
+ height: 20,
13149
+ style: { overflow: "visible" },
13150
+ children: /* @__PURE__ */ jsx13("div", { style: { ...labelStyle, textAlign: "center" }, children: xLabelText })
13151
+ }
13152
+ ),
13153
+ shouldShowYLabel && /* @__PURE__ */ jsx13(
13154
+ "foreignObject",
13155
+ {
13156
+ x: targetScreen.x + yLabelOffset,
13157
+ y: Math.min(anchorMarkerScreen.y, targetScreen.y),
13158
+ width: 20,
13159
+ height: Math.abs(targetScreen.y - anchorMarkerScreen.y),
13160
+ style: { overflow: "visible" },
13161
+ children: /* @__PURE__ */ jsx13(
13162
+ "div",
13163
+ {
13164
+ style: {
13165
+ ...labelStyle,
13166
+ display: "flex",
13167
+ alignItems: "center",
13168
+ height: "100%"
13169
+ },
13170
+ children: yLabelText
13171
+ }
13172
+ )
13173
+ }
13174
+ )
13175
+ ]
13176
+ },
13177
+ `${target.parentGroup.pcb_group_id}-${targetId}-${target.type}`
13178
+ );
13179
+ }),
13180
+ Array.from(groupAnchorScreens.entries()).map(
13181
+ ([groupId, anchorScreen]) => /* @__PURE__ */ jsxs10("g", { children: [
13182
+ /* @__PURE__ */ jsx13(
13183
+ "line",
13184
+ {
13185
+ x1: anchorScreen.x - VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
13186
+ y1: anchorScreen.y,
13187
+ x2: anchorScreen.x + VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
13188
+ y2: anchorScreen.y,
13189
+ stroke: COLORS.OFFSET_LINE,
13190
+ strokeWidth: VISUAL_CONFIG.ANCHOR_MARKER_STROKE_WIDTH
13191
+ }
13192
+ ),
13193
+ /* @__PURE__ */ jsx13(
13194
+ "line",
13195
+ {
13196
+ x1: anchorScreen.x,
13197
+ y1: anchorScreen.y - VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
13198
+ x2: anchorScreen.x,
13199
+ y2: anchorScreen.y + VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
13200
+ stroke: COLORS.OFFSET_LINE,
13201
+ strokeWidth: VISUAL_CONFIG.ANCHOR_MARKER_STROKE_WIDTH
13202
+ }
13203
+ )
13204
+ ] }, `anchor-${groupId}`)
13205
+ )
13206
+ ]
13207
+ }
13208
+ )
12876
13209
  }
12877
13210
  );
12878
13211
  };
12879
13212
 
12880
13213
  // src/components/MouseElementTracker.tsx
12881
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
13214
+ import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
12882
13215
  var getPolygonBoundingBox = (points) => {
12883
13216
  if (points.length === 0) return null;
12884
13217
  let minX = points[0].x;
@@ -12920,22 +13253,22 @@ var getPrimitivesUnderPoint = (primitives, rwPoint, transform) => {
12920
13253
  for (const primitive of primitives) {
12921
13254
  if (!primitive._element) continue;
12922
13255
  if ("x1" in primitive && primitive._element?.type === "pcb_trace") {
12923
- const distance5 = pointToSegmentDistance(
13256
+ const distance4 = pointToSegmentDistance(
12924
13257
  { x: rwPoint.x, y: rwPoint.y },
12925
13258
  { x: primitive.x1, y: primitive.y1 },
12926
13259
  { x: primitive.x2, y: primitive.y2 }
12927
13260
  );
12928
13261
  const lineWidth = primitive.width || 0.5;
12929
13262
  const detectionThreshold = Math.max(lineWidth * 25, 2) / transform.a;
12930
- if (distance5 < detectionThreshold) {
13263
+ if (distance4 < detectionThreshold) {
12931
13264
  newMousedPrimitives.push(primitive);
12932
13265
  }
12933
13266
  continue;
12934
13267
  }
12935
13268
  if (primitive.pcb_drawing_type === "polygon") {
12936
13269
  const points = primitive.points.map((point) => ({
12937
- x: distance4.parse(point.x),
12938
- y: distance4.parse(point.y)
13270
+ x: distance3.parse(point.x),
13271
+ y: distance3.parse(point.y)
12939
13272
  }));
12940
13273
  const boundingBox = getPolygonBoundingBox(points);
12941
13274
  if (!boundingBox) continue;
@@ -12949,8 +13282,8 @@ var getPrimitivesUnderPoint = (primitives, rwPoint, transform) => {
12949
13282
  }
12950
13283
  if (primitive.pcb_drawing_type === "polygon_with_arcs") {
12951
13284
  const points = primitive.brep_shape.outer_ring.vertices.map((v) => ({
12952
- x: distance4.parse(v.x),
12953
- y: distance4.parse(v.y)
13285
+ x: distance3.parse(v.x),
13286
+ y: distance3.parse(v.y)
12954
13287
  }));
12955
13288
  const boundingBox = getPolygonBoundingBox(points);
12956
13289
  if (!boundingBox) continue;
@@ -13024,7 +13357,7 @@ var MouseElementTracker = ({
13024
13357
  h = "h" in primitive ? primitive.h : "r" in primitive ? primitive.r * 2 : "rX" in primitive && "rY" in primitive ? primitive.rY * 2 : 0;
13025
13358
  }
13026
13359
  if (!basePoint) continue;
13027
- const screenPos = applyToPoint13(transform, basePoint);
13360
+ const screenPos = applyToPoint14(transform, basePoint);
13028
13361
  const screenSize = {
13029
13362
  w: w * transform.a,
13030
13363
  h: h * transform.a
@@ -13049,7 +13382,7 @@ var MouseElementTracker = ({
13049
13382
  }, [mousedPrimitives, transform]);
13050
13383
  const handleInteraction = (x, y, transform2, primitives2) => {
13051
13384
  setMousePos({ x, y });
13052
- const rwPoint = applyToPoint13(inverse5(transform2), { x, y });
13385
+ const rwPoint = applyToPoint14(inverse5(transform2), { x, y });
13053
13386
  const newMousedPrimitives = getPrimitivesUnderPoint(
13054
13387
  primitives2,
13055
13388
  rwPoint,
@@ -13064,7 +13397,7 @@ var MouseElementTracker = ({
13064
13397
  setMousedPrimitives(newMousedPrimitives);
13065
13398
  onMouseHoverOverPrimitives(newMousedPrimitives);
13066
13399
  };
13067
- return /* @__PURE__ */ jsxs10(
13400
+ return /* @__PURE__ */ jsxs11(
13068
13401
  "div",
13069
13402
  {
13070
13403
  ref: containerRef,
@@ -13088,7 +13421,7 @@ var MouseElementTracker = ({
13088
13421
  },
13089
13422
  children: [
13090
13423
  children,
13091
- /* @__PURE__ */ jsx13(
13424
+ /* @__PURE__ */ jsx14(
13092
13425
  ElementOverlayBox,
13093
13426
  {
13094
13427
  elements,
@@ -13096,26 +13429,38 @@ var MouseElementTracker = ({
13096
13429
  highlightedPrimitives
13097
13430
  }
13098
13431
  ),
13099
- transform && /* @__PURE__ */ jsx13(
13100
- GroupAnchorOffsetOverlay,
13101
- {
13102
- elements,
13103
- highlightedPrimitives,
13104
- transform,
13105
- containerWidth: width,
13106
- containerHeight: height
13107
- }
13108
- )
13432
+ transform && /* @__PURE__ */ jsxs11(Fragment5, { children: [
13433
+ /* @__PURE__ */ jsx14(
13434
+ BoardAnchorOffsetOverlay,
13435
+ {
13436
+ elements,
13437
+ highlightedPrimitives,
13438
+ transform,
13439
+ containerWidth: width,
13440
+ containerHeight: height
13441
+ }
13442
+ ),
13443
+ /* @__PURE__ */ jsx14(
13444
+ GroupAnchorOffsetOverlay,
13445
+ {
13446
+ elements,
13447
+ highlightedPrimitives,
13448
+ transform,
13449
+ containerWidth: width,
13450
+ containerHeight: height
13451
+ }
13452
+ )
13453
+ ] })
13109
13454
  ]
13110
13455
  }
13111
13456
  );
13112
13457
  };
13113
13458
 
13114
13459
  // src/components/PcbGroupOverlay.tsx
13115
- import { applyToPoint as applyToPoint14 } from "transformation-matrix";
13460
+ import { applyToPoint as applyToPoint15 } from "transformation-matrix";
13116
13461
  import { identity as identity8 } from "transformation-matrix";
13117
13462
  import { useRef as useRef9, useEffect as useEffect12 } from "react";
13118
- import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
13463
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
13119
13464
  var GROUP_COLORS = [
13120
13465
  "rgb(255, 100, 100)",
13121
13466
  "rgb(100, 255, 100)",
@@ -13131,16 +13476,20 @@ var GROUP_COLORS = [
13131
13476
  var PcbGroupOverlay = ({
13132
13477
  children,
13133
13478
  transform = identity8(),
13134
- elements = []
13479
+ elements = [],
13480
+ hoveredComponentIds = []
13135
13481
  }) => {
13136
13482
  const [containerRef, { width, height }] = useMeasure_default();
13137
13483
  const canvasRef = useRef9(null);
13138
- const { is_showing_pcb_groups, pcb_group_view_mode } = useGlobalStore(
13139
- (s) => ({
13140
- is_showing_pcb_groups: s.is_showing_pcb_groups,
13141
- pcb_group_view_mode: s.pcb_group_view_mode
13142
- })
13143
- );
13484
+ const {
13485
+ is_showing_pcb_groups,
13486
+ pcb_group_view_mode,
13487
+ is_showing_group_anchor_offsets
13488
+ } = useGlobalStore((s) => ({
13489
+ is_showing_pcb_groups: s.is_showing_pcb_groups,
13490
+ pcb_group_view_mode: s.pcb_group_view_mode,
13491
+ is_showing_group_anchor_offsets: s.is_showing_group_anchor_offsets
13492
+ }));
13144
13493
  useEffect12(() => {
13145
13494
  const canvas = canvasRef.current;
13146
13495
  if (!canvas || !width || !height) return;
@@ -13199,6 +13548,14 @@ var PcbGroupOverlay = ({
13199
13548
  }
13200
13549
  return 1 + getGroupDepthLevel(groupWithParent.parent_source_group_id);
13201
13550
  };
13551
+ const hoveredGroupIds = /* @__PURE__ */ new Set();
13552
+ if (hoveredComponentIds.length > 0) {
13553
+ for (const comp of pcbComponents) {
13554
+ if (!hoveredComponentIds.includes(comp.pcb_component_id)) continue;
13555
+ const targetGroupId = comp.positioned_relative_to_pcb_group_id ?? comp.pcb_group_id;
13556
+ if (targetGroupId) hoveredGroupIds.add(targetGroupId);
13557
+ }
13558
+ }
13202
13559
  visiblePcbGroups.forEach((group, groupIndex) => {
13203
13560
  let groupComponents = pcbComponents.filter(
13204
13561
  (comp) => comp.pcb_group_id === group.pcb_group_id
@@ -13244,10 +13601,10 @@ var PcbGroupOverlay = ({
13244
13601
  maxX += totalPadding;
13245
13602
  minY -= totalPadding;
13246
13603
  maxY += totalPadding;
13247
- const topLeft = applyToPoint14(transform, { x: minX, y: maxY });
13248
- const topRight = applyToPoint14(transform, { x: maxX, y: maxY });
13249
- const bottomLeft = applyToPoint14(transform, { x: minX, y: minY });
13250
- const bottomRight = applyToPoint14(transform, { x: maxX, y: minY });
13604
+ const topLeft = applyToPoint15(transform, { x: minX, y: maxY });
13605
+ const topRight = applyToPoint15(transform, { x: maxX, y: maxY });
13606
+ const bottomLeft = applyToPoint15(transform, { x: minX, y: minY });
13607
+ const bottomRight = applyToPoint15(transform, { x: maxX, y: minY });
13251
13608
  const groupColor = GROUP_COLORS[groupIndex % GROUP_COLORS.length];
13252
13609
  ctx.strokeStyle = groupColor;
13253
13610
  ctx.lineWidth = 2;
@@ -13291,8 +13648,13 @@ var PcbGroupOverlay = ({
13291
13648
  ctx.textAlign = "left";
13292
13649
  ctx.textBaseline = "middle";
13293
13650
  ctx.fillText(labelText, labelX + labelPadding, labelY - labelHeight / 2);
13294
- if (group.anchor_position) {
13295
- const anchorScreenPos = applyToPoint14(transform, group.anchor_position);
13651
+ const shouldShowAnchorMarker = is_showing_group_anchor_offsets && hoveredGroupIds.has(group.pcb_group_id) && Boolean(group.anchor_position);
13652
+ if (shouldShowAnchorMarker && group.anchor_position) {
13653
+ const anchorPositionValue = Array.isArray(group.anchor_position) ? {
13654
+ x: group.anchor_position[0] ?? 0,
13655
+ y: group.anchor_position[1] ?? 0
13656
+ } : { x: group.anchor_position.x, y: group.anchor_position.y };
13657
+ const anchorScreenPos = applyToPoint15(transform, anchorPositionValue);
13296
13658
  ctx.strokeStyle = "white";
13297
13659
  ctx.lineWidth = 1.5;
13298
13660
  ctx.setLineDash([]);
@@ -13313,16 +13675,18 @@ var PcbGroupOverlay = ({
13313
13675
  width,
13314
13676
  height,
13315
13677
  is_showing_pcb_groups,
13316
- pcb_group_view_mode
13678
+ pcb_group_view_mode,
13679
+ is_showing_group_anchor_offsets,
13680
+ hoveredComponentIds
13317
13681
  ]);
13318
- return /* @__PURE__ */ jsxs11(
13682
+ return /* @__PURE__ */ jsxs12(
13319
13683
  "div",
13320
13684
  {
13321
13685
  ref: containerRef,
13322
13686
  style: { position: "relative", width: "100%", height: "100%" },
13323
13687
  children: [
13324
13688
  children,
13325
- /* @__PURE__ */ jsx14(
13689
+ /* @__PURE__ */ jsx15(
13326
13690
  "canvas",
13327
13691
  {
13328
13692
  ref: canvasRef,
@@ -13342,9 +13706,9 @@ var PcbGroupOverlay = ({
13342
13706
  };
13343
13707
 
13344
13708
  // src/components/RatsNestOverlay.tsx
13345
- import { applyToPoint as applyToPoint15, identity as identity9 } from "transformation-matrix";
13709
+ import { applyToPoint as applyToPoint16, identity as identity9 } from "transformation-matrix";
13346
13710
  import { useMemo as useMemo6 } from "react";
13347
- import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
13711
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
13348
13712
  var RatsNestOverlay = ({ transform, soup, children }) => {
13349
13713
  const isShowingRatsNest = useGlobalStore((s) => s.is_showing_rats_nest);
13350
13714
  const { netMap, idToNetMap } = useMemo6(
@@ -13367,11 +13731,11 @@ var RatsNestOverlay = ({ transform, soup, children }) => {
13367
13731
  connectedIds.forEach((id) => {
13368
13732
  const pos = getElementPosition(id);
13369
13733
  if (pos) {
13370
- const distance5 = Math.sqrt(
13734
+ const distance4 = Math.sqrt(
13371
13735
  (sourcePoint.x - pos.x) ** 2 + (sourcePoint.y - pos.y) ** 2
13372
13736
  );
13373
- if (distance5 < minDistance && distance5 > 0) {
13374
- minDistance = distance5;
13737
+ if (distance4 < minDistance && distance4 > 0) {
13738
+ minDistance = distance4;
13375
13739
  nearestPoint = pos;
13376
13740
  }
13377
13741
  }
@@ -13407,9 +13771,9 @@ var RatsNestOverlay = ({ transform, soup, children }) => {
13407
13771
  }, [soup, netMap, idToNetMap, isShowingRatsNest]);
13408
13772
  if (!soup || !isShowingRatsNest) return children;
13409
13773
  if (!transform) transform = identity9();
13410
- return /* @__PURE__ */ jsxs12("div", { style: { position: "relative" }, children: [
13774
+ return /* @__PURE__ */ jsxs13("div", { style: { position: "relative" }, children: [
13411
13775
  children,
13412
- /* @__PURE__ */ jsx15(
13776
+ /* @__PURE__ */ jsx16(
13413
13777
  "svg",
13414
13778
  {
13415
13779
  style: {
@@ -13423,9 +13787,9 @@ var RatsNestOverlay = ({ transform, soup, children }) => {
13423
13787
  zIndex: zIndexMap.ratsNestOverlay
13424
13788
  },
13425
13789
  children: ratsNestLines.map(({ key, startPoint, endPoint, isInNet }) => {
13426
- const transformedStart = applyToPoint15(transform, startPoint);
13427
- const transformedEnd = applyToPoint15(transform, endPoint);
13428
- return /* @__PURE__ */ jsx15(
13790
+ const transformedStart = applyToPoint16(transform, startPoint);
13791
+ const transformedEnd = applyToPoint16(transform, endPoint);
13792
+ return /* @__PURE__ */ jsx16(
13429
13793
  "line",
13430
13794
  {
13431
13795
  x1: transformedStart.x,
@@ -13451,7 +13815,7 @@ import { css as css3 } from "@emotion/css";
13451
13815
  // package.json
13452
13816
  var package_default = {
13453
13817
  name: "@tscircuit/pcb-viewer",
13454
- version: "1.11.280",
13818
+ version: "1.11.282",
13455
13819
  main: "dist/index.js",
13456
13820
  type: "module",
13457
13821
  repository: "tscircuit/pcb-viewer",
@@ -13504,7 +13868,7 @@ var package_default = {
13504
13868
  "@tscircuit/alphabet": "^0.0.8",
13505
13869
  "@tscircuit/math-utils": "^0.0.29",
13506
13870
  "@vitejs/plugin-react": "^5.0.2",
13507
- "circuit-json": "^0.0.321",
13871
+ "circuit-json": "^0.0.333",
13508
13872
  "circuit-to-svg": "^0.0.271",
13509
13873
  color: "^4.2.3",
13510
13874
  "react-supergrid": "^1.0.10",
@@ -13557,9 +13921,9 @@ var useIsSmallScreen = () => {
13557
13921
  };
13558
13922
 
13559
13923
  // src/components/ToolbarOverlay.tsx
13560
- import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
13924
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
13561
13925
  var LayerButton = ({ name, selected, onClick }) => {
13562
- return /* @__PURE__ */ jsxs13(
13926
+ return /* @__PURE__ */ jsxs14(
13563
13927
  "div",
13564
13928
  {
13565
13929
  className: css3`
@@ -13575,8 +13939,8 @@ var LayerButton = ({ name, selected, onClick }) => {
13575
13939
  `,
13576
13940
  onClick,
13577
13941
  children: [
13578
- /* @__PURE__ */ jsx16("span", { style: { marginRight: 2, opacity: selected ? 1 : 0 }, children: "\u2022" }),
13579
- /* @__PURE__ */ jsx16(
13942
+ /* @__PURE__ */ jsx17("span", { style: { marginRight: 2, opacity: selected ? 1 : 0 }, children: "\u2022" }),
13943
+ /* @__PURE__ */ jsx17(
13580
13944
  "span",
13581
13945
  {
13582
13946
  style: {
@@ -13596,7 +13960,7 @@ var ToolbarButton = ({
13596
13960
  isSmallScreen,
13597
13961
  onClick,
13598
13962
  ...props
13599
- }) => /* @__PURE__ */ jsx16(
13963
+ }) => /* @__PURE__ */ jsx17(
13600
13964
  "div",
13601
13965
  {
13602
13966
  ...props,
@@ -13633,7 +13997,7 @@ var CheckboxMenuItem = ({
13633
13997
  checked,
13634
13998
  onClick
13635
13999
  }) => {
13636
- return /* @__PURE__ */ jsxs13(
14000
+ return /* @__PURE__ */ jsxs14(
13637
14001
  "div",
13638
14002
  {
13639
14003
  className: css3`
@@ -13660,15 +14024,15 @@ var CheckboxMenuItem = ({
13660
14024
  onClick();
13661
14025
  },
13662
14026
  children: [
13663
- /* @__PURE__ */ jsx16("input", { type: "checkbox", checked, onChange: () => {
14027
+ /* @__PURE__ */ jsx17("input", { type: "checkbox", checked, onChange: () => {
13664
14028
  }, readOnly: true }),
13665
- /* @__PURE__ */ jsx16("span", { style: { color: "#eee" }, children: label })
14029
+ /* @__PURE__ */ jsx17("span", { style: { color: "#eee" }, children: label })
13666
14030
  ]
13667
14031
  }
13668
14032
  );
13669
14033
  };
13670
14034
  var RadioMenuItem = ({ label, checked, onClick }) => {
13671
- return /* @__PURE__ */ jsxs13(
14035
+ return /* @__PURE__ */ jsxs14(
13672
14036
  "div",
13673
14037
  {
13674
14038
  className: css3`
@@ -13695,9 +14059,9 @@ var RadioMenuItem = ({ label, checked, onClick }) => {
13695
14059
  onClick();
13696
14060
  },
13697
14061
  children: [
13698
- /* @__PURE__ */ jsx16("input", { type: "radio", checked, onChange: () => {
14062
+ /* @__PURE__ */ jsx17("input", { type: "radio", checked, onChange: () => {
13699
14063
  }, readOnly: true }),
13700
- /* @__PURE__ */ jsx16("span", { style: { color: "#eee" }, children: label })
14064
+ /* @__PURE__ */ jsx17("span", { style: { color: "#eee" }, children: label })
13701
14065
  ]
13702
14066
  }
13703
14067
  );
@@ -13849,7 +14213,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13849
14213
  setErrorsOpen(false);
13850
14214
  }
13851
14215
  }, [isViewMenuOpen]);
13852
- return /* @__PURE__ */ jsxs13(
14216
+ return /* @__PURE__ */ jsxs14(
13853
14217
  "div",
13854
14218
  {
13855
14219
  style: { position: "relative", zIndex: "999 !important" },
@@ -13857,7 +14221,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13857
14221
  onMouseLeave: handleMouseLeave,
13858
14222
  children: [
13859
14223
  children,
13860
- /* @__PURE__ */ jsxs13(
14224
+ /* @__PURE__ */ jsxs14(
13861
14225
  "div",
13862
14226
  {
13863
14227
  style: {
@@ -13878,7 +14242,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13878
14242
  ]
13879
14243
  }
13880
14244
  ),
13881
- /* @__PURE__ */ jsxs13(
14245
+ /* @__PURE__ */ jsxs14(
13882
14246
  "div",
13883
14247
  {
13884
14248
  "data-toolbar-overlay": true,
@@ -13901,7 +14265,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13901
14265
  fontFamily: "sans-serif"
13902
14266
  },
13903
14267
  children: [
13904
- /* @__PURE__ */ jsxs13(
14268
+ /* @__PURE__ */ jsxs14(
13905
14269
  ToolbarButton,
13906
14270
  {
13907
14271
  isSmallScreen,
@@ -13912,10 +14276,10 @@ var ToolbarOverlay = ({ children, elements }) => {
13912
14276
  }
13913
14277
  },
13914
14278
  children: [
13915
- /* @__PURE__ */ jsxs13("div", { children: [
14279
+ /* @__PURE__ */ jsxs14("div", { children: [
13916
14280
  "layer:",
13917
14281
  " ",
13918
- /* @__PURE__ */ jsx16(
14282
+ /* @__PURE__ */ jsx17(
13919
14283
  "span",
13920
14284
  {
13921
14285
  style: {
@@ -13927,7 +14291,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13927
14291
  }
13928
14292
  )
13929
14293
  ] }),
13930
- isLayerMenuOpen && /* @__PURE__ */ jsx16("div", { style: { marginTop: 4, minWidth: 120 }, children: processedLayers.map((layer) => /* @__PURE__ */ jsx16(
14294
+ isLayerMenuOpen && /* @__PURE__ */ jsx17("div", { style: { marginTop: 4, minWidth: 120 }, children: processedLayers.map((layer) => /* @__PURE__ */ jsx17(
13931
14295
  LayerButton,
13932
14296
  {
13933
14297
  name: layer,
@@ -13941,7 +14305,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13941
14305
  ]
13942
14306
  }
13943
14307
  ),
13944
- /* @__PURE__ */ jsx16(
14308
+ /* @__PURE__ */ jsx17(
13945
14309
  ToolbarButton,
13946
14310
  {
13947
14311
  isSmallScreen,
@@ -13950,13 +14314,13 @@ var ToolbarOverlay = ({ children, elements }) => {
13950
14314
  ...errorCount > 0 ? { color: "red" } : {}
13951
14315
  },
13952
14316
  onClick: handleErrorsToggle,
13953
- children: /* @__PURE__ */ jsxs13("div", { children: [
14317
+ children: /* @__PURE__ */ jsxs14("div", { children: [
13954
14318
  errorCount,
13955
14319
  " errors"
13956
14320
  ] })
13957
14321
  }
13958
14322
  ),
13959
- isErrorsOpen && errorCount > 0 && /* @__PURE__ */ jsx16(
14323
+ isErrorsOpen && errorCount > 0 && /* @__PURE__ */ jsx17(
13960
14324
  "div",
13961
14325
  {
13962
14326
  style: {
@@ -13976,14 +14340,14 @@ var ToolbarOverlay = ({ children, elements }) => {
13976
14340
  },
13977
14341
  children: errorElements.map((e, i) => {
13978
14342
  const errorId = e.pcb_trace_error_id || `error_${i}_${e.error_type}_${e.message?.slice(0, 20)}`;
13979
- return /* @__PURE__ */ jsxs13(
14343
+ return /* @__PURE__ */ jsxs14(
13980
14344
  "div",
13981
14345
  {
13982
14346
  style: {
13983
14347
  borderBottom: i < errorElements.length - 1 ? "1px solid #444" : "none"
13984
14348
  },
13985
14349
  children: [
13986
- /* @__PURE__ */ jsxs13(
14350
+ /* @__PURE__ */ jsxs14(
13987
14351
  "div",
13988
14352
  {
13989
14353
  style: {
@@ -14034,7 +14398,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14034
14398
  }
14035
14399
  },
14036
14400
  children: [
14037
- /* @__PURE__ */ jsx16(
14401
+ /* @__PURE__ */ jsx17(
14038
14402
  "div",
14039
14403
  {
14040
14404
  style: {
@@ -14048,7 +14412,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14048
14412
  children: e.error_type
14049
14413
  }
14050
14414
  ),
14051
- /* @__PURE__ */ jsx16(
14415
+ /* @__PURE__ */ jsx17(
14052
14416
  "div",
14053
14417
  {
14054
14418
  style: {
@@ -14063,7 +14427,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14063
14427
  children: e.message
14064
14428
  }
14065
14429
  ),
14066
- /* @__PURE__ */ jsx16(
14430
+ /* @__PURE__ */ jsx17(
14067
14431
  "div",
14068
14432
  {
14069
14433
  ref: (el) => {
@@ -14083,7 +14447,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14083
14447
  ]
14084
14448
  }
14085
14449
  ),
14086
- /* @__PURE__ */ jsx16(
14450
+ /* @__PURE__ */ jsx17(
14087
14451
  "div",
14088
14452
  {
14089
14453
  ref: (el) => {
@@ -14096,7 +14460,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14096
14460
  backgroundColor: "#1a1a1a",
14097
14461
  borderTop: "1px solid #444"
14098
14462
  },
14099
- children: /* @__PURE__ */ jsx16(
14463
+ children: /* @__PURE__ */ jsx17(
14100
14464
  "div",
14101
14465
  {
14102
14466
  style: {
@@ -14119,58 +14483,58 @@ var ToolbarOverlay = ({ children, elements }) => {
14119
14483
  })
14120
14484
  }
14121
14485
  ),
14122
- /* @__PURE__ */ jsx16(
14486
+ /* @__PURE__ */ jsx17(
14123
14487
  ToolbarButton,
14124
14488
  {
14125
14489
  isSmallScreen,
14126
14490
  style: {},
14127
14491
  onClick: handleEditTraceToggle,
14128
- children: /* @__PURE__ */ jsxs13("div", { children: [
14492
+ children: /* @__PURE__ */ jsxs14("div", { children: [
14129
14493
  editModes.in_draw_trace_mode ? "\u2716 " : "",
14130
14494
  "Edit Traces"
14131
14495
  ] })
14132
14496
  }
14133
14497
  ),
14134
- /* @__PURE__ */ jsx16(
14498
+ /* @__PURE__ */ jsx17(
14135
14499
  ToolbarButton,
14136
14500
  {
14137
14501
  isSmallScreen,
14138
14502
  style: {},
14139
14503
  onClick: handleMoveComponentToggle,
14140
- children: /* @__PURE__ */ jsxs13("div", { children: [
14504
+ children: /* @__PURE__ */ jsxs14("div", { children: [
14141
14505
  editModes.in_move_footprint_mode ? "\u2716 " : "",
14142
14506
  "Move Components"
14143
14507
  ] })
14144
14508
  }
14145
14509
  ),
14146
- /* @__PURE__ */ jsx16(
14510
+ /* @__PURE__ */ jsx17(
14147
14511
  ToolbarButton,
14148
14512
  {
14149
14513
  isSmallScreen,
14150
14514
  style: {},
14151
14515
  onClick: handleRatsNestToggle,
14152
- children: /* @__PURE__ */ jsxs13("div", { children: [
14516
+ children: /* @__PURE__ */ jsxs14("div", { children: [
14153
14517
  viewSettings.is_showing_rats_nest ? "\u2716 " : "",
14154
14518
  "Rats Nest"
14155
14519
  ] })
14156
14520
  }
14157
14521
  ),
14158
- /* @__PURE__ */ jsx16(
14522
+ /* @__PURE__ */ jsx17(
14159
14523
  ToolbarButton,
14160
14524
  {
14161
14525
  isSmallScreen,
14162
14526
  style: measureToolArmed ? { backgroundColor: "#444" } : {},
14163
14527
  onClick: handleMeasureToolClick,
14164
- children: /* @__PURE__ */ jsx16("div", { children: "\u{1F4CF}" })
14528
+ children: /* @__PURE__ */ jsx17("div", { children: "\u{1F4CF}" })
14165
14529
  }
14166
14530
  ),
14167
- /* @__PURE__ */ jsx16(
14531
+ /* @__PURE__ */ jsx17(
14168
14532
  ToolbarButton,
14169
14533
  {
14170
14534
  isSmallScreen,
14171
14535
  onClick: handleViewMenuToggle,
14172
- children: /* @__PURE__ */ jsxs13("div", { children: [
14173
- /* @__PURE__ */ jsxs13(
14536
+ children: /* @__PURE__ */ jsxs14("div", { children: [
14537
+ /* @__PURE__ */ jsxs14(
14174
14538
  "div",
14175
14539
  {
14176
14540
  style: {
@@ -14180,7 +14544,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14180
14544
  },
14181
14545
  children: [
14182
14546
  "View",
14183
- /* @__PURE__ */ jsx16(
14547
+ /* @__PURE__ */ jsx17(
14184
14548
  "span",
14185
14549
  {
14186
14550
  style: {
@@ -14195,8 +14559,8 @@ var ToolbarOverlay = ({ children, elements }) => {
14195
14559
  ]
14196
14560
  }
14197
14561
  ),
14198
- isViewMenuOpen && /* @__PURE__ */ jsxs13("div", { style: { marginTop: 4, minWidth: 120 }, children: [
14199
- /* @__PURE__ */ jsx16(
14562
+ isViewMenuOpen && /* @__PURE__ */ jsxs14("div", { style: { marginTop: 4, minWidth: 120 }, children: [
14563
+ /* @__PURE__ */ jsx17(
14200
14564
  CheckboxMenuItem,
14201
14565
  {
14202
14566
  label: "Show All Trace Lengths",
@@ -14208,7 +14572,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14208
14572
  }
14209
14573
  }
14210
14574
  ),
14211
- /* @__PURE__ */ jsx16(
14575
+ /* @__PURE__ */ jsx17(
14212
14576
  CheckboxMenuItem,
14213
14577
  {
14214
14578
  label: "Show Autorouting Animation",
@@ -14220,7 +14584,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14220
14584
  }
14221
14585
  }
14222
14586
  ),
14223
- /* @__PURE__ */ jsx16(
14587
+ /* @__PURE__ */ jsx17(
14224
14588
  CheckboxMenuItem,
14225
14589
  {
14226
14590
  label: "Show DRC Errors",
@@ -14230,7 +14594,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14230
14594
  }
14231
14595
  }
14232
14596
  ),
14233
- /* @__PURE__ */ jsx16(
14597
+ /* @__PURE__ */ jsx17(
14234
14598
  CheckboxMenuItem,
14235
14599
  {
14236
14600
  label: "Show Copper Pours",
@@ -14242,7 +14606,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14242
14606
  }
14243
14607
  }
14244
14608
  ),
14245
- /* @__PURE__ */ jsx16(
14609
+ /* @__PURE__ */ jsx17(
14246
14610
  CheckboxMenuItem,
14247
14611
  {
14248
14612
  label: "Show Solder Mask",
@@ -14252,7 +14616,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14252
14616
  }
14253
14617
  }
14254
14618
  ),
14255
- /* @__PURE__ */ jsx16(
14619
+ /* @__PURE__ */ jsx17(
14256
14620
  CheckboxMenuItem,
14257
14621
  {
14258
14622
  label: "Show Group Anchor Offsets",
@@ -14264,7 +14628,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14264
14628
  }
14265
14629
  }
14266
14630
  ),
14267
- /* @__PURE__ */ jsx16(
14631
+ /* @__PURE__ */ jsx17(
14268
14632
  CheckboxMenuItem,
14269
14633
  {
14270
14634
  label: "Show PCB Groups",
@@ -14274,8 +14638,8 @@ var ToolbarOverlay = ({ children, elements }) => {
14274
14638
  }
14275
14639
  }
14276
14640
  ),
14277
- viewSettings.is_showing_pcb_groups && /* @__PURE__ */ jsxs13("div", { style: { marginLeft: 16 }, children: [
14278
- /* @__PURE__ */ jsx16(
14641
+ viewSettings.is_showing_pcb_groups && /* @__PURE__ */ jsxs14("div", { style: { marginLeft: 16 }, children: [
14642
+ /* @__PURE__ */ jsx17(
14279
14643
  RadioMenuItem,
14280
14644
  {
14281
14645
  label: "Show All Groups",
@@ -14285,7 +14649,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14285
14649
  }
14286
14650
  }
14287
14651
  ),
14288
- /* @__PURE__ */ jsx16(
14652
+ /* @__PURE__ */ jsx17(
14289
14653
  RadioMenuItem,
14290
14654
  {
14291
14655
  label: "Show Named Groups",
@@ -14309,7 +14673,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14309
14673
  };
14310
14674
 
14311
14675
  // src/components/CanvasElementsRenderer.tsx
14312
- import { jsx as jsx17 } from "react/jsx-runtime";
14676
+ import { jsx as jsx18 } from "react/jsx-runtime";
14313
14677
  var CanvasElementsRenderer = (props) => {
14314
14678
  const { transform, elements } = props;
14315
14679
  const hoveredErrorId = useGlobalStore((state) => state.hovered_error_id);
@@ -14333,6 +14697,7 @@ var CanvasElementsRenderer = (props) => {
14333
14697
  drawingObjectIdsWithMouseOver: /* @__PURE__ */ new Set(),
14334
14698
  primitiveIdsInMousedOverNet: []
14335
14699
  });
14700
+ const [hoveredComponentIds, setHoveredComponentIds] = useState10([]);
14336
14701
  const errorRelatedIds = useMemo7(() => {
14337
14702
  if (!hoveredErrorId) return [];
14338
14703
  const errorElements = elements.filter(
@@ -14383,17 +14748,27 @@ var CanvasElementsRenderer = (props) => {
14383
14748
  drawingObjectIdsWithMouseOver,
14384
14749
  primitiveIdsInMousedOverNet
14385
14750
  });
14751
+ const componentIds = primitivesHoveredOver.map((primitive) => {
14752
+ if (primitive._parent_pcb_component?.type === "pcb_component" && primitive._parent_pcb_component.pcb_component_id) {
14753
+ return primitive._parent_pcb_component.pcb_component_id;
14754
+ }
14755
+ if (primitive._element?.type === "pcb_component" && primitive._element.pcb_component_id) {
14756
+ return primitive._element.pcb_component_id;
14757
+ }
14758
+ return null;
14759
+ }).filter((id) => Boolean(id));
14760
+ setHoveredComponentIds(Array.from(new Set(componentIds)));
14386
14761
  },
14387
14762
  [connectivityMap]
14388
14763
  );
14389
- return /* @__PURE__ */ jsx17(
14764
+ return /* @__PURE__ */ jsx18(
14390
14765
  MouseElementTracker,
14391
14766
  {
14392
14767
  elements: elementsToRender,
14393
14768
  transform,
14394
14769
  primitives: primitivesWithoutInteractionMetadata,
14395
14770
  onMouseHoverOverPrimitives: onMouseOverPrimitives,
14396
- children: /* @__PURE__ */ jsx17(
14771
+ children: /* @__PURE__ */ jsx18(
14397
14772
  EditPlacementOverlay,
14398
14773
  {
14399
14774
  disabled: !props.allowEditing,
@@ -14402,7 +14777,7 @@ var CanvasElementsRenderer = (props) => {
14402
14777
  cancelPanDrag: props.cancelPanDrag,
14403
14778
  onCreateEditEvent: props.onCreateEditEvent,
14404
14779
  onModifyEditEvent: props.onModifyEditEvent,
14405
- children: /* @__PURE__ */ jsx17(
14780
+ children: /* @__PURE__ */ jsx18(
14406
14781
  EditTraceHintOverlay,
14407
14782
  {
14408
14783
  disabled: !props.allowEditing,
@@ -14411,36 +14786,44 @@ var CanvasElementsRenderer = (props) => {
14411
14786
  cancelPanDrag: props.cancelPanDrag,
14412
14787
  onCreateEditEvent: props.onCreateEditEvent,
14413
14788
  onModifyEditEvent: props.onModifyEditEvent,
14414
- children: /* @__PURE__ */ jsx17(
14789
+ children: /* @__PURE__ */ jsx18(
14415
14790
  DimensionOverlay,
14416
14791
  {
14417
14792
  transform,
14418
14793
  focusOnHover: props.focusOnHover,
14419
14794
  primitives: primitivesWithoutInteractionMetadata,
14420
- children: /* @__PURE__ */ jsx17(ToolbarOverlay, { elements, children: /* @__PURE__ */ jsx17(ErrorOverlay, { transform, elements, children: /* @__PURE__ */ jsx17(RatsNestOverlay, { transform, soup: elements, children: /* @__PURE__ */ jsx17(PcbGroupOverlay, { transform, elements, children: /* @__PURE__ */ jsx17(
14421
- DebugGraphicsOverlay,
14795
+ children: /* @__PURE__ */ jsx18(ToolbarOverlay, { elements, children: /* @__PURE__ */ jsx18(ErrorOverlay, { transform, elements, children: /* @__PURE__ */ jsx18(RatsNestOverlay, { transform, soup: elements, children: /* @__PURE__ */ jsx18(
14796
+ PcbGroupOverlay,
14422
14797
  {
14423
14798
  transform,
14424
- debugGraphics: props.debugGraphics,
14425
- children: /* @__PURE__ */ jsx17(
14426
- WarningGraphicsOverlay,
14799
+ elements,
14800
+ hoveredComponentIds,
14801
+ children: /* @__PURE__ */ jsx18(
14802
+ DebugGraphicsOverlay,
14427
14803
  {
14428
14804
  transform,
14429
- elements,
14430
- children: /* @__PURE__ */ jsx17(
14431
- CanvasPrimitiveRenderer,
14805
+ debugGraphics: props.debugGraphics,
14806
+ children: /* @__PURE__ */ jsx18(
14807
+ WarningGraphicsOverlay,
14432
14808
  {
14433
14809
  transform,
14434
- primitives,
14435
- width: props.width,
14436
- height: props.height,
14437
- grid: props.grid
14810
+ elements,
14811
+ children: /* @__PURE__ */ jsx18(
14812
+ CanvasPrimitiveRenderer,
14813
+ {
14814
+ transform,
14815
+ primitives,
14816
+ width: props.width,
14817
+ height: props.height,
14818
+ grid: props.grid
14819
+ }
14820
+ )
14438
14821
  }
14439
14822
  )
14440
14823
  }
14441
14824
  )
14442
14825
  }
14443
- ) }) }) }) })
14826
+ ) }) }) })
14444
14827
  }
14445
14828
  )
14446
14829
  }
@@ -14494,7 +14877,7 @@ var calculateCircuitJsonKey = (circuitJson) => {
14494
14877
  };
14495
14878
 
14496
14879
  // src/PCBViewer.tsx
14497
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
14880
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
14498
14881
  var defaultTransform = compose7(translate11(400, 300), scale5(40, -40));
14499
14882
  var PCBViewer = ({
14500
14883
  circuitJson,
@@ -14588,20 +14971,20 @@ var PCBViewer = ({
14588
14971
  }),
14589
14972
  [initialState, disablePcbGroups]
14590
14973
  );
14591
- return /* @__PURE__ */ jsxs14(
14974
+ return /* @__PURE__ */ jsxs15(
14592
14975
  "div",
14593
14976
  {
14594
14977
  ref: transformRef,
14595
14978
  style: { position: "relative" },
14596
14979
  onContextMenu: (event) => event.preventDefault(),
14597
14980
  children: [
14598
- /* @__PURE__ */ jsx18("div", { ref, children: /* @__PURE__ */ jsxs14(
14981
+ /* @__PURE__ */ jsx19("div", { ref, children: /* @__PURE__ */ jsxs15(
14599
14982
  ContextProviders,
14600
14983
  {
14601
14984
  initialState: mergedInitialState,
14602
14985
  disablePcbGroups,
14603
14986
  children: [
14604
- /* @__PURE__ */ jsx18(
14987
+ /* @__PURE__ */ jsx19(
14605
14988
  CanvasElementsRenderer,
14606
14989
  {
14607
14990
  transform,
@@ -14626,11 +15009,11 @@ var PCBViewer = ({
14626
15009
  },
14627
15010
  refDimensions.width
14628
15011
  ),
14629
- /* @__PURE__ */ jsx18(ToastContainer, {})
15012
+ /* @__PURE__ */ jsx19(ToastContainer, {})
14630
15013
  ]
14631
15014
  }
14632
15015
  ) }),
14633
- clickToInteractEnabled && !isInteractionEnabled && /* @__PURE__ */ jsx18(
15016
+ clickToInteractEnabled && !isInteractionEnabled && /* @__PURE__ */ jsx19(
14634
15017
  "div",
14635
15018
  {
14636
15019
  onClick: () => {
@@ -14667,7 +15050,7 @@ var PCBViewer = ({
14667
15050
  justifyContent: "center",
14668
15051
  touchAction: "pan-x pan-y pinch-zoom"
14669
15052
  },
14670
- children: /* @__PURE__ */ jsx18(
15053
+ children: /* @__PURE__ */ jsx19(
14671
15054
  "div",
14672
15055
  {
14673
15056
  style: {