@tscircuit/pcb-viewer 1.11.281 → 1.11.282

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
@@ -12389,7 +12389,7 @@ function ifSetsMatchExactly(set1, set2) {
12389
12389
 
12390
12390
  // src/components/MouseElementTracker.tsx
12391
12391
  import { useState as useState7, useMemo as useMemo5 } from "react";
12392
- import { applyToPoint as applyToPoint13, inverse as inverse5 } from "transformation-matrix";
12392
+ import { applyToPoint as applyToPoint14, inverse as inverse5 } from "transformation-matrix";
12393
12393
 
12394
12394
  // src/components/ElementOverlayBox.tsx
12395
12395
  import { useEffect as useEffect11, useState as useState6 } from "react";
@@ -12666,28 +12666,10 @@ var ElementOverlayBox = ({
12666
12666
  )) });
12667
12667
  };
12668
12668
 
12669
- // src/components/GroupAnchorOffsetOverlay/index.tsx
12669
+ // src/components/AnchorOffsetOverlay/Board/index.tsx
12670
12670
  import { applyToPoint as applyToPoint12 } from "transformation-matrix";
12671
12671
 
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
12672
+ // src/components/AnchorOffsetOverlay/common/constants.ts
12691
12673
  var VISUAL_CONFIG = {
12692
12674
  GROUP_PADDING: 1,
12693
12675
  MIN_LINE_LENGTH_FOR_LABEL: 40,
@@ -12698,7 +12680,9 @@ var VISUAL_CONFIG = {
12698
12680
  LINE_STROKE_WIDTH: 1.5,
12699
12681
  LINE_DASH_PATTERN: "4,4",
12700
12682
  COMPONENT_MARKER_RADIUS: 3,
12701
- LABEL_FONT_SIZE: 11
12683
+ LABEL_FONT_SIZE: 11,
12684
+ ANCHOR_MARKER_SIZE: 6,
12685
+ ANCHOR_MARKER_STROKE_WIDTH: 1.5
12702
12686
  };
12703
12687
  var COLORS = {
12704
12688
  OFFSET_LINE: "white",
@@ -12707,8 +12691,236 @@ var COLORS = {
12707
12691
  LABEL_TEXT: "white"
12708
12692
  };
12709
12693
 
12710
- // src/components/GroupAnchorOffsetOverlay/index.tsx
12694
+ // src/components/AnchorOffsetOverlay/common/guards.ts
12695
+ var isPcbComponent = (element) => element?.type === "pcb_component";
12696
+ var isPcbGroup = (element) => element?.type === "pcb_group";
12697
+ var isPcbBoard = (element) => element?.type === "pcb_board";
12698
+
12699
+ // src/components/AnchorOffsetOverlay/Board/index.tsx
12711
12700
  import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
12701
+ var BoardAnchorOffsetOverlay = ({
12702
+ elements,
12703
+ highlightedPrimitives,
12704
+ transform,
12705
+ containerWidth,
12706
+ containerHeight
12707
+ }) => {
12708
+ const boards = elements.filter((el) => isPcbBoard(el));
12709
+ const components = elements.filter(
12710
+ (el) => isPcbComponent(el)
12711
+ );
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 isShowingAnchorOffsets = useGlobalStore(
12722
+ (state) => state.is_showing_group_anchor_offsets
12723
+ );
12724
+ if (!isShowingAnchorOffsets && hoveredComponentIds.length === 0) {
12725
+ return null;
12726
+ }
12727
+ const targets = components.map((component) => {
12728
+ const boardId = component.positioned_relative_to_pcb_board_id;
12729
+ if (!boardId) return null;
12730
+ const board = boards.find((b) => b.pcb_board_id === boardId);
12731
+ return board ? { component, board } : null;
12732
+ }).filter(
12733
+ (target) => Boolean(target)
12734
+ );
12735
+ if (targets.length === 0) return null;
12736
+ const shouldShowAllTargets = hoveredComponentIds.length === 0;
12737
+ const labelStyle = {
12738
+ color: COLORS.LABEL_TEXT,
12739
+ mixBlendMode: "difference",
12740
+ pointerEvents: "none",
12741
+ fontSize: VISUAL_CONFIG.LABEL_FONT_SIZE,
12742
+ fontFamily: "monospace",
12743
+ fontWeight: "bold"
12744
+ };
12745
+ const targetEntries = targets.filter(
12746
+ ({ component }) => shouldShowAllTargets || hoveredComponentIds.includes(component.pcb_component_id)
12747
+ );
12748
+ if (targetEntries.length === 0) return null;
12749
+ const boardAnchorScreens = /* @__PURE__ */ new Map();
12750
+ return /* @__PURE__ */ jsx12(
12751
+ "div",
12752
+ {
12753
+ style: {
12754
+ position: "absolute",
12755
+ left: 0,
12756
+ top: 0,
12757
+ width: containerWidth,
12758
+ height: containerHeight,
12759
+ overflow: "hidden",
12760
+ pointerEvents: "none",
12761
+ zIndex: zIndexMap.dimensionOverlay
12762
+ },
12763
+ children: /* @__PURE__ */ jsxs9(
12764
+ "svg",
12765
+ {
12766
+ style: {
12767
+ position: "absolute",
12768
+ left: 0,
12769
+ top: 0,
12770
+ pointerEvents: "none"
12771
+ },
12772
+ width: containerWidth,
12773
+ height: containerHeight,
12774
+ children: [
12775
+ targetEntries.map(({ component, board }) => {
12776
+ const anchorPosition = board.center;
12777
+ const componentCenter = component.center;
12778
+ const anchorKey = board.pcb_board_id;
12779
+ if (!boardAnchorScreens.has(anchorKey)) {
12780
+ const screenPoint = applyToPoint12(transform, anchorPosition);
12781
+ boardAnchorScreens.set(anchorKey, screenPoint);
12782
+ }
12783
+ const anchorMarkerScreen = boardAnchorScreens.get(anchorKey);
12784
+ const componentScreen = applyToPoint12(transform, componentCenter);
12785
+ const offsetX = componentCenter.x - anchorPosition.x;
12786
+ const offsetY = componentCenter.y - anchorPosition.y;
12787
+ const xLineLength = Math.abs(componentScreen.x - anchorMarkerScreen.x);
12788
+ const yLineLength = Math.abs(componentScreen.y - anchorMarkerScreen.y);
12789
+ const isComponentAboveAnchor = componentScreen.y < anchorMarkerScreen.y;
12790
+ const isComponentRightOfAnchor = componentScreen.x > anchorMarkerScreen.x;
12791
+ const xLabelOffset = isComponentAboveAnchor ? VISUAL_CONFIG.LABEL_OFFSET_ABOVE : VISUAL_CONFIG.LABEL_OFFSET_BELOW;
12792
+ const yLabelOffset = isComponentRightOfAnchor ? VISUAL_CONFIG.LABEL_OFFSET_RIGHT : VISUAL_CONFIG.LABEL_OFFSET_LEFT;
12793
+ const shouldShowXLabel = xLineLength > VISUAL_CONFIG.MIN_LINE_LENGTH_FOR_LABEL;
12794
+ const shouldShowYLabel = yLineLength > VISUAL_CONFIG.MIN_LINE_LENGTH_FOR_LABEL;
12795
+ const xLabelText = component.display_offset_x ?? `Board \u0394x: ${offsetX.toFixed(2)}mm`;
12796
+ const yLabelText = component.display_offset_y ?? `Board \u0394y: ${offsetY.toFixed(2)}mm`;
12797
+ return /* @__PURE__ */ jsxs9("g", { children: [
12798
+ /* @__PURE__ */ jsx12(
12799
+ "line",
12800
+ {
12801
+ x1: anchorMarkerScreen.x,
12802
+ y1: anchorMarkerScreen.y,
12803
+ x2: componentScreen.x,
12804
+ y2: anchorMarkerScreen.y,
12805
+ stroke: COLORS.OFFSET_LINE,
12806
+ strokeWidth: VISUAL_CONFIG.LINE_STROKE_WIDTH,
12807
+ strokeDasharray: VISUAL_CONFIG.LINE_DASH_PATTERN
12808
+ }
12809
+ ),
12810
+ /* @__PURE__ */ jsx12(
12811
+ "line",
12812
+ {
12813
+ x1: componentScreen.x,
12814
+ y1: anchorMarkerScreen.y,
12815
+ x2: componentScreen.x,
12816
+ y2: componentScreen.y,
12817
+ stroke: COLORS.OFFSET_LINE,
12818
+ strokeWidth: VISUAL_CONFIG.LINE_STROKE_WIDTH,
12819
+ strokeDasharray: VISUAL_CONFIG.LINE_DASH_PATTERN
12820
+ }
12821
+ ),
12822
+ /* @__PURE__ */ jsx12(
12823
+ "circle",
12824
+ {
12825
+ cx: componentScreen.x,
12826
+ cy: componentScreen.y,
12827
+ r: VISUAL_CONFIG.COMPONENT_MARKER_RADIUS,
12828
+ fill: COLORS.COMPONENT_MARKER_FILL,
12829
+ stroke: COLORS.COMPONENT_MARKER_STROKE,
12830
+ strokeWidth: 1
12831
+ }
12832
+ ),
12833
+ shouldShowXLabel && /* @__PURE__ */ jsx12(
12834
+ "foreignObject",
12835
+ {
12836
+ x: Math.min(anchorMarkerScreen.x, componentScreen.x),
12837
+ y: anchorMarkerScreen.y + xLabelOffset,
12838
+ width: Math.abs(componentScreen.x - anchorMarkerScreen.x),
12839
+ height: 20,
12840
+ style: { overflow: "visible" },
12841
+ children: /* @__PURE__ */ jsx12("div", { style: { ...labelStyle, textAlign: "center" }, children: xLabelText })
12842
+ }
12843
+ ),
12844
+ shouldShowYLabel && /* @__PURE__ */ jsx12(
12845
+ "foreignObject",
12846
+ {
12847
+ x: componentScreen.x + yLabelOffset,
12848
+ y: Math.min(anchorMarkerScreen.y, componentScreen.y),
12849
+ width: 20,
12850
+ height: Math.abs(componentScreen.y - anchorMarkerScreen.y),
12851
+ style: { overflow: "visible" },
12852
+ children: /* @__PURE__ */ jsx12(
12853
+ "div",
12854
+ {
12855
+ style: {
12856
+ ...labelStyle,
12857
+ display: "flex",
12858
+ alignItems: "center",
12859
+ height: "100%"
12860
+ },
12861
+ children: yLabelText
12862
+ }
12863
+ )
12864
+ }
12865
+ )
12866
+ ] }, `${board.pcb_board_id}-${component.pcb_component_id}`);
12867
+ }),
12868
+ Array.from(boardAnchorScreens.entries()).map(
12869
+ ([boardId, anchorScreen]) => /* @__PURE__ */ jsxs9("g", { children: [
12870
+ /* @__PURE__ */ jsx12(
12871
+ "line",
12872
+ {
12873
+ x1: anchorScreen.x - VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
12874
+ y1: anchorScreen.y,
12875
+ x2: anchorScreen.x + VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
12876
+ y2: anchorScreen.y,
12877
+ stroke: COLORS.OFFSET_LINE,
12878
+ strokeWidth: VISUAL_CONFIG.ANCHOR_MARKER_STROKE_WIDTH
12879
+ }
12880
+ ),
12881
+ /* @__PURE__ */ jsx12(
12882
+ "line",
12883
+ {
12884
+ x1: anchorScreen.x,
12885
+ y1: anchorScreen.y - VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
12886
+ x2: anchorScreen.x,
12887
+ y2: anchorScreen.y + VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
12888
+ stroke: COLORS.OFFSET_LINE,
12889
+ strokeWidth: VISUAL_CONFIG.ANCHOR_MARKER_STROKE_WIDTH
12890
+ }
12891
+ )
12892
+ ] }, `anchor-${boardId}`)
12893
+ )
12894
+ ]
12895
+ }
12896
+ )
12897
+ }
12898
+ );
12899
+ };
12900
+
12901
+ // src/components/AnchorOffsetOverlay/Group/index.tsx
12902
+ import { applyToPoint as applyToPoint13 } from "transformation-matrix";
12903
+
12904
+ // src/components/AnchorOffsetOverlay/Group/calculateGroupBoundingBox.ts
12905
+ import { distance as distance3 } from "circuit-json";
12906
+ var calculateGroupBoundingBox = (groupComponents) => {
12907
+ const points = [];
12908
+ for (const comp of groupComponents) {
12909
+ if (!comp.center) {
12910
+ continue;
12911
+ }
12912
+ const width = typeof comp.width === "number" ? comp.width : distance3.parse(comp.width);
12913
+ const height = typeof comp.height === "number" ? comp.height : distance3.parse(comp.height);
12914
+ const halfWidth = width / 2;
12915
+ const halfHeight = height / 2;
12916
+ points.push({ x: comp.center.x - halfWidth, y: comp.center.y - halfHeight });
12917
+ points.push({ x: comp.center.x + halfWidth, y: comp.center.y + halfHeight });
12918
+ }
12919
+ return getBoundsFromPoints(points);
12920
+ };
12921
+
12922
+ // src/components/AnchorOffsetOverlay/Group/index.tsx
12923
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
12712
12924
  var GroupAnchorOffsetOverlay = ({
12713
12925
  elements,
12714
12926
  highlightedPrimitives,
@@ -12716,52 +12928,44 @@ var GroupAnchorOffsetOverlay = ({
12716
12928
  containerWidth,
12717
12929
  containerHeight
12718
12930
  }) => {
12719
- const is_showing_group_anchor_offsets = useGlobalStore(
12931
+ const groups = elements.filter((el) => isPcbGroup(el));
12932
+ const components = elements.filter(
12933
+ (el) => isPcbComponent(el)
12934
+ );
12935
+ const hoveredComponentIds = highlightedPrimitives.map((primitive) => {
12936
+ if (isPcbComponent(primitive._parent_pcb_component)) {
12937
+ return primitive._parent_pcb_component.pcb_component_id;
12938
+ }
12939
+ if (isPcbComponent(primitive._element)) {
12940
+ return primitive._element.pcb_component_id;
12941
+ }
12942
+ return null;
12943
+ }).filter((id) => Boolean(id));
12944
+ const isShowingAnchorOffsets = useGlobalStore(
12720
12945
  (s) => s.is_showing_group_anchor_offsets
12721
12946
  );
12722
- if (!is_showing_group_anchor_offsets || highlightedPrimitives.length === 0) {
12947
+ if (!isShowingAnchorOffsets && hoveredComponentIds.length === 0) {
12723
12948
  return null;
12724
12949
  }
12725
- const hoveredPrimitive = highlightedPrimitives.find(
12726
- (p) => p._parent_pcb_component?.type === "pcb_component" || p._element?.type === "pcb_component"
12950
+ const targets = components.map((component) => {
12951
+ if (component.position_mode === "relative_to_group_anchor" && component.positioned_relative_to_pcb_group_id) {
12952
+ const parentGroup = groups.find(
12953
+ (group) => group.pcb_group_id === component.positioned_relative_to_pcb_group_id
12954
+ );
12955
+ return parentGroup && parentGroup.anchor_position ? { component, parentGroup } : null;
12956
+ }
12957
+ if (component.pcb_group_id) {
12958
+ const parentGroup = groups.find(
12959
+ (group) => group.pcb_group_id === component.pcb_group_id
12960
+ );
12961
+ return parentGroup && parentGroup.anchor_position ? { component, parentGroup } : null;
12962
+ }
12963
+ return null;
12964
+ }).filter(
12965
+ (target) => Boolean(target)
12727
12966
  );
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;
12967
+ if (targets.length === 0) return null;
12968
+ const shouldShowAllTargets = hoveredComponentIds.length === 0;
12765
12969
  const labelStyle = {
12766
12970
  color: COLORS.LABEL_TEXT,
12767
12971
  mixBlendMode: "difference",
@@ -12770,7 +12974,17 @@ var GroupAnchorOffsetOverlay = ({
12770
12974
  fontFamily: "monospace",
12771
12975
  fontWeight: "bold"
12772
12976
  };
12773
- return /* @__PURE__ */ jsxs9(
12977
+ const targetEntries = targets.filter(
12978
+ ({ component }) => shouldShowAllTargets || hoveredComponentIds.includes(component.pcb_component_id)
12979
+ );
12980
+ if (targetEntries.length === 0) return null;
12981
+ const groupAnchorScreens = /* @__PURE__ */ new Map();
12982
+ targetEntries.forEach(({ parentGroup }) => {
12983
+ if (!parentGroup.anchor_position) return;
12984
+ const anchorScreen = applyToPoint13(transform, parentGroup.anchor_position);
12985
+ groupAnchorScreens.set(parentGroup.pcb_group_id, anchorScreen);
12986
+ });
12987
+ return /* @__PURE__ */ jsx13(
12774
12988
  "div",
12775
12989
  {
12776
12990
  style: {
@@ -12783,102 +12997,158 @@ var GroupAnchorOffsetOverlay = ({
12783
12997
  pointerEvents: "none",
12784
12998
  zIndex: zIndexMap.dimensionOverlay
12785
12999
  },
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",
13000
+ children: /* @__PURE__ */ jsxs10(
13001
+ "svg",
13002
+ {
13003
+ style: {
13004
+ position: "absolute",
13005
+ left: 0,
13006
+ top: 0,
13007
+ pointerEvents: "none"
13008
+ },
13009
+ width: containerWidth,
13010
+ height: containerHeight,
13011
+ children: [
13012
+ targetEntries.map(({ component, parentGroup }) => {
13013
+ const anchor = parentGroup.anchor_position;
13014
+ const center = component.center;
13015
+ if (!anchor || !center) return null;
13016
+ const anchorMarkerPosition = { x: anchor.x, y: anchor.y };
13017
+ const targetCenter = { x: center.x, y: center.y };
13018
+ const groupComponents = components.filter(
13019
+ (comp) => comp.pcb_group_id === parentGroup.pcb_group_id
13020
+ );
13021
+ const boundingBox = calculateGroupBoundingBox(groupComponents);
13022
+ if (!boundingBox) return null;
13023
+ const offsetX = targetCenter.x - anchorMarkerPosition.x;
13024
+ const offsetY = targetCenter.y - anchorMarkerPosition.y;
13025
+ const anchorMarkerScreen = applyToPoint13(
13026
+ transform,
13027
+ anchorMarkerPosition
13028
+ );
13029
+ const componentScreen = applyToPoint13(transform, targetCenter);
13030
+ const xLineLength = Math.abs(componentScreen.x - anchorMarkerScreen.x);
13031
+ const yLineLength = Math.abs(componentScreen.y - anchorMarkerScreen.y);
13032
+ const isComponentAboveAnchor = componentScreen.y < anchorMarkerScreen.y;
13033
+ const isComponentRightOfAnchor = componentScreen.x > anchorMarkerScreen.x;
13034
+ const xLabelOffset = isComponentAboveAnchor ? VISUAL_CONFIG.LABEL_OFFSET_ABOVE : VISUAL_CONFIG.LABEL_OFFSET_BELOW;
13035
+ const yLabelOffset = isComponentRightOfAnchor ? VISUAL_CONFIG.LABEL_OFFSET_RIGHT : VISUAL_CONFIG.LABEL_OFFSET_LEFT;
13036
+ const shouldShowXLabel = xLineLength > VISUAL_CONFIG.MIN_LINE_LENGTH_FOR_LABEL;
13037
+ const shouldShowYLabel = yLineLength > VISUAL_CONFIG.MIN_LINE_LENGTH_FOR_LABEL;
13038
+ const xLabelText = component.display_offset_x ?? `\u0394x: ${offsetX.toFixed(2)}mm`;
13039
+ const yLabelText = component.display_offset_y ?? `\u0394y: ${offsetY.toFixed(2)}mm`;
13040
+ return /* @__PURE__ */ jsxs10(
13041
+ "g",
12825
13042
  {
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
- ]
13043
+ children: [
13044
+ /* @__PURE__ */ jsx13(
13045
+ "line",
13046
+ {
13047
+ x1: anchorMarkerScreen.x,
13048
+ y1: anchorMarkerScreen.y,
13049
+ x2: componentScreen.x,
13050
+ y2: anchorMarkerScreen.y,
13051
+ stroke: COLORS.OFFSET_LINE,
13052
+ strokeWidth: VISUAL_CONFIG.LINE_STROKE_WIDTH,
13053
+ strokeDasharray: VISUAL_CONFIG.LINE_DASH_PATTERN
13054
+ }
13055
+ ),
13056
+ /* @__PURE__ */ jsx13(
13057
+ "line",
13058
+ {
13059
+ x1: componentScreen.x,
13060
+ y1: anchorMarkerScreen.y,
13061
+ x2: componentScreen.x,
13062
+ y2: componentScreen.y,
13063
+ stroke: COLORS.OFFSET_LINE,
13064
+ strokeWidth: VISUAL_CONFIG.LINE_STROKE_WIDTH,
13065
+ strokeDasharray: VISUAL_CONFIG.LINE_DASH_PATTERN
13066
+ }
13067
+ ),
13068
+ /* @__PURE__ */ jsx13(
13069
+ "circle",
13070
+ {
13071
+ cx: componentScreen.x,
13072
+ cy: componentScreen.y,
13073
+ r: VISUAL_CONFIG.COMPONENT_MARKER_RADIUS,
13074
+ fill: COLORS.COMPONENT_MARKER_FILL,
13075
+ stroke: COLORS.COMPONENT_MARKER_STROKE,
13076
+ strokeWidth: 1
13077
+ }
13078
+ ),
13079
+ shouldShowXLabel && /* @__PURE__ */ jsx13(
13080
+ "foreignObject",
13081
+ {
13082
+ x: Math.min(anchorMarkerScreen.x, componentScreen.x),
13083
+ y: anchorMarkerScreen.y + xLabelOffset,
13084
+ width: Math.abs(componentScreen.x - anchorMarkerScreen.x),
13085
+ height: 20,
13086
+ style: { overflow: "visible" },
13087
+ children: /* @__PURE__ */ jsx13("div", { style: { ...labelStyle, textAlign: "center" }, children: xLabelText })
13088
+ }
13089
+ ),
13090
+ shouldShowYLabel && /* @__PURE__ */ jsx13(
13091
+ "foreignObject",
13092
+ {
13093
+ x: componentScreen.x + yLabelOffset,
13094
+ y: Math.min(anchorMarkerScreen.y, componentScreen.y),
13095
+ width: 20,
13096
+ height: Math.abs(componentScreen.y - anchorMarkerScreen.y),
13097
+ style: { overflow: "visible" },
13098
+ children: /* @__PURE__ */ jsx13(
13099
+ "div",
13100
+ {
13101
+ style: {
13102
+ ...labelStyle,
13103
+ display: "flex",
13104
+ alignItems: "center",
13105
+ height: "100%"
13106
+ },
13107
+ children: yLabelText
13108
+ }
13109
+ )
13110
+ }
13111
+ )
13112
+ ]
13113
+ },
13114
+ `${parentGroup.pcb_group_id}-${component.pcb_component_id}`
13115
+ );
13116
+ }),
13117
+ Array.from(groupAnchorScreens.entries()).map(
13118
+ ([groupId, anchorScreen]) => /* @__PURE__ */ jsxs10("g", { children: [
13119
+ /* @__PURE__ */ jsx13(
13120
+ "line",
13121
+ {
13122
+ x1: anchorScreen.x - VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
13123
+ y1: anchorScreen.y,
13124
+ x2: anchorScreen.x + VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
13125
+ y2: anchorScreen.y,
13126
+ stroke: COLORS.OFFSET_LINE,
13127
+ strokeWidth: VISUAL_CONFIG.ANCHOR_MARKER_STROKE_WIDTH
13128
+ }
13129
+ ),
13130
+ /* @__PURE__ */ jsx13(
13131
+ "line",
13132
+ {
13133
+ x1: anchorScreen.x,
13134
+ y1: anchorScreen.y - VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
13135
+ x2: anchorScreen.x,
13136
+ y2: anchorScreen.y + VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
13137
+ stroke: COLORS.OFFSET_LINE,
13138
+ strokeWidth: VISUAL_CONFIG.ANCHOR_MARKER_STROKE_WIDTH
13139
+ }
13140
+ )
13141
+ ] }, `anchor-${groupId}`)
13142
+ )
13143
+ ]
13144
+ }
13145
+ )
12876
13146
  }
12877
13147
  );
12878
13148
  };
12879
13149
 
12880
13150
  // src/components/MouseElementTracker.tsx
12881
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
13151
+ import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
12882
13152
  var getPolygonBoundingBox = (points) => {
12883
13153
  if (points.length === 0) return null;
12884
13154
  let minX = points[0].x;
@@ -13024,7 +13294,7 @@ var MouseElementTracker = ({
13024
13294
  h = "h" in primitive ? primitive.h : "r" in primitive ? primitive.r * 2 : "rX" in primitive && "rY" in primitive ? primitive.rY * 2 : 0;
13025
13295
  }
13026
13296
  if (!basePoint) continue;
13027
- const screenPos = applyToPoint13(transform, basePoint);
13297
+ const screenPos = applyToPoint14(transform, basePoint);
13028
13298
  const screenSize = {
13029
13299
  w: w * transform.a,
13030
13300
  h: h * transform.a
@@ -13049,7 +13319,7 @@ var MouseElementTracker = ({
13049
13319
  }, [mousedPrimitives, transform]);
13050
13320
  const handleInteraction = (x, y, transform2, primitives2) => {
13051
13321
  setMousePos({ x, y });
13052
- const rwPoint = applyToPoint13(inverse5(transform2), { x, y });
13322
+ const rwPoint = applyToPoint14(inverse5(transform2), { x, y });
13053
13323
  const newMousedPrimitives = getPrimitivesUnderPoint(
13054
13324
  primitives2,
13055
13325
  rwPoint,
@@ -13064,7 +13334,7 @@ var MouseElementTracker = ({
13064
13334
  setMousedPrimitives(newMousedPrimitives);
13065
13335
  onMouseHoverOverPrimitives(newMousedPrimitives);
13066
13336
  };
13067
- return /* @__PURE__ */ jsxs10(
13337
+ return /* @__PURE__ */ jsxs11(
13068
13338
  "div",
13069
13339
  {
13070
13340
  ref: containerRef,
@@ -13088,7 +13358,7 @@ var MouseElementTracker = ({
13088
13358
  },
13089
13359
  children: [
13090
13360
  children,
13091
- /* @__PURE__ */ jsx13(
13361
+ /* @__PURE__ */ jsx14(
13092
13362
  ElementOverlayBox,
13093
13363
  {
13094
13364
  elements,
@@ -13096,26 +13366,38 @@ var MouseElementTracker = ({
13096
13366
  highlightedPrimitives
13097
13367
  }
13098
13368
  ),
13099
- transform && /* @__PURE__ */ jsx13(
13100
- GroupAnchorOffsetOverlay,
13101
- {
13102
- elements,
13103
- highlightedPrimitives,
13104
- transform,
13105
- containerWidth: width,
13106
- containerHeight: height
13107
- }
13108
- )
13369
+ transform && /* @__PURE__ */ jsxs11(Fragment5, { children: [
13370
+ /* @__PURE__ */ jsx14(
13371
+ BoardAnchorOffsetOverlay,
13372
+ {
13373
+ elements,
13374
+ highlightedPrimitives,
13375
+ transform,
13376
+ containerWidth: width,
13377
+ containerHeight: height
13378
+ }
13379
+ ),
13380
+ /* @__PURE__ */ jsx14(
13381
+ GroupAnchorOffsetOverlay,
13382
+ {
13383
+ elements,
13384
+ highlightedPrimitives,
13385
+ transform,
13386
+ containerWidth: width,
13387
+ containerHeight: height
13388
+ }
13389
+ )
13390
+ ] })
13109
13391
  ]
13110
13392
  }
13111
13393
  );
13112
13394
  };
13113
13395
 
13114
13396
  // src/components/PcbGroupOverlay.tsx
13115
- import { applyToPoint as applyToPoint14 } from "transformation-matrix";
13397
+ import { applyToPoint as applyToPoint15 } from "transformation-matrix";
13116
13398
  import { identity as identity8 } from "transformation-matrix";
13117
13399
  import { useRef as useRef9, useEffect as useEffect12 } from "react";
13118
- import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
13400
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
13119
13401
  var GROUP_COLORS = [
13120
13402
  "rgb(255, 100, 100)",
13121
13403
  "rgb(100, 255, 100)",
@@ -13131,16 +13413,20 @@ var GROUP_COLORS = [
13131
13413
  var PcbGroupOverlay = ({
13132
13414
  children,
13133
13415
  transform = identity8(),
13134
- elements = []
13416
+ elements = [],
13417
+ hoveredComponentIds = []
13135
13418
  }) => {
13136
13419
  const [containerRef, { width, height }] = useMeasure_default();
13137
13420
  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
- );
13421
+ const {
13422
+ is_showing_pcb_groups,
13423
+ pcb_group_view_mode,
13424
+ is_showing_group_anchor_offsets
13425
+ } = useGlobalStore((s) => ({
13426
+ is_showing_pcb_groups: s.is_showing_pcb_groups,
13427
+ pcb_group_view_mode: s.pcb_group_view_mode,
13428
+ is_showing_group_anchor_offsets: s.is_showing_group_anchor_offsets
13429
+ }));
13144
13430
  useEffect12(() => {
13145
13431
  const canvas = canvasRef.current;
13146
13432
  if (!canvas || !width || !height) return;
@@ -13199,6 +13485,14 @@ var PcbGroupOverlay = ({
13199
13485
  }
13200
13486
  return 1 + getGroupDepthLevel(groupWithParent.parent_source_group_id);
13201
13487
  };
13488
+ const hoveredGroupIds = /* @__PURE__ */ new Set();
13489
+ if (hoveredComponentIds.length > 0) {
13490
+ for (const comp of pcbComponents) {
13491
+ if (!hoveredComponentIds.includes(comp.pcb_component_id)) continue;
13492
+ const targetGroupId = comp.positioned_relative_to_pcb_group_id ?? comp.pcb_group_id;
13493
+ if (targetGroupId) hoveredGroupIds.add(targetGroupId);
13494
+ }
13495
+ }
13202
13496
  visiblePcbGroups.forEach((group, groupIndex) => {
13203
13497
  let groupComponents = pcbComponents.filter(
13204
13498
  (comp) => comp.pcb_group_id === group.pcb_group_id
@@ -13244,10 +13538,10 @@ var PcbGroupOverlay = ({
13244
13538
  maxX += totalPadding;
13245
13539
  minY -= totalPadding;
13246
13540
  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 });
13541
+ const topLeft = applyToPoint15(transform, { x: minX, y: maxY });
13542
+ const topRight = applyToPoint15(transform, { x: maxX, y: maxY });
13543
+ const bottomLeft = applyToPoint15(transform, { x: minX, y: minY });
13544
+ const bottomRight = applyToPoint15(transform, { x: maxX, y: minY });
13251
13545
  const groupColor = GROUP_COLORS[groupIndex % GROUP_COLORS.length];
13252
13546
  ctx.strokeStyle = groupColor;
13253
13547
  ctx.lineWidth = 2;
@@ -13291,8 +13585,13 @@ var PcbGroupOverlay = ({
13291
13585
  ctx.textAlign = "left";
13292
13586
  ctx.textBaseline = "middle";
13293
13587
  ctx.fillText(labelText, labelX + labelPadding, labelY - labelHeight / 2);
13294
- if (group.anchor_position) {
13295
- const anchorScreenPos = applyToPoint14(transform, group.anchor_position);
13588
+ const shouldShowAnchorMarker = is_showing_group_anchor_offsets && hoveredGroupIds.has(group.pcb_group_id) && Boolean(group.anchor_position);
13589
+ if (shouldShowAnchorMarker && group.anchor_position) {
13590
+ const anchorPositionValue = Array.isArray(group.anchor_position) ? {
13591
+ x: group.anchor_position[0] ?? 0,
13592
+ y: group.anchor_position[1] ?? 0
13593
+ } : { x: group.anchor_position.x, y: group.anchor_position.y };
13594
+ const anchorScreenPos = applyToPoint15(transform, anchorPositionValue);
13296
13595
  ctx.strokeStyle = "white";
13297
13596
  ctx.lineWidth = 1.5;
13298
13597
  ctx.setLineDash([]);
@@ -13313,16 +13612,18 @@ var PcbGroupOverlay = ({
13313
13612
  width,
13314
13613
  height,
13315
13614
  is_showing_pcb_groups,
13316
- pcb_group_view_mode
13615
+ pcb_group_view_mode,
13616
+ is_showing_group_anchor_offsets,
13617
+ hoveredComponentIds
13317
13618
  ]);
13318
- return /* @__PURE__ */ jsxs11(
13619
+ return /* @__PURE__ */ jsxs12(
13319
13620
  "div",
13320
13621
  {
13321
13622
  ref: containerRef,
13322
13623
  style: { position: "relative", width: "100%", height: "100%" },
13323
13624
  children: [
13324
13625
  children,
13325
- /* @__PURE__ */ jsx14(
13626
+ /* @__PURE__ */ jsx15(
13326
13627
  "canvas",
13327
13628
  {
13328
13629
  ref: canvasRef,
@@ -13342,9 +13643,9 @@ var PcbGroupOverlay = ({
13342
13643
  };
13343
13644
 
13344
13645
  // src/components/RatsNestOverlay.tsx
13345
- import { applyToPoint as applyToPoint15, identity as identity9 } from "transformation-matrix";
13646
+ import { applyToPoint as applyToPoint16, identity as identity9 } from "transformation-matrix";
13346
13647
  import { useMemo as useMemo6 } from "react";
13347
- import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
13648
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
13348
13649
  var RatsNestOverlay = ({ transform, soup, children }) => {
13349
13650
  const isShowingRatsNest = useGlobalStore((s) => s.is_showing_rats_nest);
13350
13651
  const { netMap, idToNetMap } = useMemo6(
@@ -13407,9 +13708,9 @@ var RatsNestOverlay = ({ transform, soup, children }) => {
13407
13708
  }, [soup, netMap, idToNetMap, isShowingRatsNest]);
13408
13709
  if (!soup || !isShowingRatsNest) return children;
13409
13710
  if (!transform) transform = identity9();
13410
- return /* @__PURE__ */ jsxs12("div", { style: { position: "relative" }, children: [
13711
+ return /* @__PURE__ */ jsxs13("div", { style: { position: "relative" }, children: [
13411
13712
  children,
13412
- /* @__PURE__ */ jsx15(
13713
+ /* @__PURE__ */ jsx16(
13413
13714
  "svg",
13414
13715
  {
13415
13716
  style: {
@@ -13423,9 +13724,9 @@ var RatsNestOverlay = ({ transform, soup, children }) => {
13423
13724
  zIndex: zIndexMap.ratsNestOverlay
13424
13725
  },
13425
13726
  children: ratsNestLines.map(({ key, startPoint, endPoint, isInNet }) => {
13426
- const transformedStart = applyToPoint15(transform, startPoint);
13427
- const transformedEnd = applyToPoint15(transform, endPoint);
13428
- return /* @__PURE__ */ jsx15(
13727
+ const transformedStart = applyToPoint16(transform, startPoint);
13728
+ const transformedEnd = applyToPoint16(transform, endPoint);
13729
+ return /* @__PURE__ */ jsx16(
13429
13730
  "line",
13430
13731
  {
13431
13732
  x1: transformedStart.x,
@@ -13451,7 +13752,7 @@ import { css as css3 } from "@emotion/css";
13451
13752
  // package.json
13452
13753
  var package_default = {
13453
13754
  name: "@tscircuit/pcb-viewer",
13454
- version: "1.11.280",
13755
+ version: "1.11.281",
13455
13756
  main: "dist/index.js",
13456
13757
  type: "module",
13457
13758
  repository: "tscircuit/pcb-viewer",
@@ -13504,7 +13805,7 @@ var package_default = {
13504
13805
  "@tscircuit/alphabet": "^0.0.8",
13505
13806
  "@tscircuit/math-utils": "^0.0.29",
13506
13807
  "@vitejs/plugin-react": "^5.0.2",
13507
- "circuit-json": "^0.0.321",
13808
+ "circuit-json": "^0.0.333",
13508
13809
  "circuit-to-svg": "^0.0.271",
13509
13810
  color: "^4.2.3",
13510
13811
  "react-supergrid": "^1.0.10",
@@ -13557,9 +13858,9 @@ var useIsSmallScreen = () => {
13557
13858
  };
13558
13859
 
13559
13860
  // src/components/ToolbarOverlay.tsx
13560
- import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
13861
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
13561
13862
  var LayerButton = ({ name, selected, onClick }) => {
13562
- return /* @__PURE__ */ jsxs13(
13863
+ return /* @__PURE__ */ jsxs14(
13563
13864
  "div",
13564
13865
  {
13565
13866
  className: css3`
@@ -13575,8 +13876,8 @@ var LayerButton = ({ name, selected, onClick }) => {
13575
13876
  `,
13576
13877
  onClick,
13577
13878
  children: [
13578
- /* @__PURE__ */ jsx16("span", { style: { marginRight: 2, opacity: selected ? 1 : 0 }, children: "\u2022" }),
13579
- /* @__PURE__ */ jsx16(
13879
+ /* @__PURE__ */ jsx17("span", { style: { marginRight: 2, opacity: selected ? 1 : 0 }, children: "\u2022" }),
13880
+ /* @__PURE__ */ jsx17(
13580
13881
  "span",
13581
13882
  {
13582
13883
  style: {
@@ -13596,7 +13897,7 @@ var ToolbarButton = ({
13596
13897
  isSmallScreen,
13597
13898
  onClick,
13598
13899
  ...props
13599
- }) => /* @__PURE__ */ jsx16(
13900
+ }) => /* @__PURE__ */ jsx17(
13600
13901
  "div",
13601
13902
  {
13602
13903
  ...props,
@@ -13633,7 +13934,7 @@ var CheckboxMenuItem = ({
13633
13934
  checked,
13634
13935
  onClick
13635
13936
  }) => {
13636
- return /* @__PURE__ */ jsxs13(
13937
+ return /* @__PURE__ */ jsxs14(
13637
13938
  "div",
13638
13939
  {
13639
13940
  className: css3`
@@ -13660,15 +13961,15 @@ var CheckboxMenuItem = ({
13660
13961
  onClick();
13661
13962
  },
13662
13963
  children: [
13663
- /* @__PURE__ */ jsx16("input", { type: "checkbox", checked, onChange: () => {
13964
+ /* @__PURE__ */ jsx17("input", { type: "checkbox", checked, onChange: () => {
13664
13965
  }, readOnly: true }),
13665
- /* @__PURE__ */ jsx16("span", { style: { color: "#eee" }, children: label })
13966
+ /* @__PURE__ */ jsx17("span", { style: { color: "#eee" }, children: label })
13666
13967
  ]
13667
13968
  }
13668
13969
  );
13669
13970
  };
13670
13971
  var RadioMenuItem = ({ label, checked, onClick }) => {
13671
- return /* @__PURE__ */ jsxs13(
13972
+ return /* @__PURE__ */ jsxs14(
13672
13973
  "div",
13673
13974
  {
13674
13975
  className: css3`
@@ -13695,9 +13996,9 @@ var RadioMenuItem = ({ label, checked, onClick }) => {
13695
13996
  onClick();
13696
13997
  },
13697
13998
  children: [
13698
- /* @__PURE__ */ jsx16("input", { type: "radio", checked, onChange: () => {
13999
+ /* @__PURE__ */ jsx17("input", { type: "radio", checked, onChange: () => {
13699
14000
  }, readOnly: true }),
13700
- /* @__PURE__ */ jsx16("span", { style: { color: "#eee" }, children: label })
14001
+ /* @__PURE__ */ jsx17("span", { style: { color: "#eee" }, children: label })
13701
14002
  ]
13702
14003
  }
13703
14004
  );
@@ -13849,7 +14150,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13849
14150
  setErrorsOpen(false);
13850
14151
  }
13851
14152
  }, [isViewMenuOpen]);
13852
- return /* @__PURE__ */ jsxs13(
14153
+ return /* @__PURE__ */ jsxs14(
13853
14154
  "div",
13854
14155
  {
13855
14156
  style: { position: "relative", zIndex: "999 !important" },
@@ -13857,7 +14158,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13857
14158
  onMouseLeave: handleMouseLeave,
13858
14159
  children: [
13859
14160
  children,
13860
- /* @__PURE__ */ jsxs13(
14161
+ /* @__PURE__ */ jsxs14(
13861
14162
  "div",
13862
14163
  {
13863
14164
  style: {
@@ -13878,7 +14179,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13878
14179
  ]
13879
14180
  }
13880
14181
  ),
13881
- /* @__PURE__ */ jsxs13(
14182
+ /* @__PURE__ */ jsxs14(
13882
14183
  "div",
13883
14184
  {
13884
14185
  "data-toolbar-overlay": true,
@@ -13901,7 +14202,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13901
14202
  fontFamily: "sans-serif"
13902
14203
  },
13903
14204
  children: [
13904
- /* @__PURE__ */ jsxs13(
14205
+ /* @__PURE__ */ jsxs14(
13905
14206
  ToolbarButton,
13906
14207
  {
13907
14208
  isSmallScreen,
@@ -13912,10 +14213,10 @@ var ToolbarOverlay = ({ children, elements }) => {
13912
14213
  }
13913
14214
  },
13914
14215
  children: [
13915
- /* @__PURE__ */ jsxs13("div", { children: [
14216
+ /* @__PURE__ */ jsxs14("div", { children: [
13916
14217
  "layer:",
13917
14218
  " ",
13918
- /* @__PURE__ */ jsx16(
14219
+ /* @__PURE__ */ jsx17(
13919
14220
  "span",
13920
14221
  {
13921
14222
  style: {
@@ -13927,7 +14228,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13927
14228
  }
13928
14229
  )
13929
14230
  ] }),
13930
- isLayerMenuOpen && /* @__PURE__ */ jsx16("div", { style: { marginTop: 4, minWidth: 120 }, children: processedLayers.map((layer) => /* @__PURE__ */ jsx16(
14231
+ isLayerMenuOpen && /* @__PURE__ */ jsx17("div", { style: { marginTop: 4, minWidth: 120 }, children: processedLayers.map((layer) => /* @__PURE__ */ jsx17(
13931
14232
  LayerButton,
13932
14233
  {
13933
14234
  name: layer,
@@ -13941,7 +14242,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13941
14242
  ]
13942
14243
  }
13943
14244
  ),
13944
- /* @__PURE__ */ jsx16(
14245
+ /* @__PURE__ */ jsx17(
13945
14246
  ToolbarButton,
13946
14247
  {
13947
14248
  isSmallScreen,
@@ -13950,13 +14251,13 @@ var ToolbarOverlay = ({ children, elements }) => {
13950
14251
  ...errorCount > 0 ? { color: "red" } : {}
13951
14252
  },
13952
14253
  onClick: handleErrorsToggle,
13953
- children: /* @__PURE__ */ jsxs13("div", { children: [
14254
+ children: /* @__PURE__ */ jsxs14("div", { children: [
13954
14255
  errorCount,
13955
14256
  " errors"
13956
14257
  ] })
13957
14258
  }
13958
14259
  ),
13959
- isErrorsOpen && errorCount > 0 && /* @__PURE__ */ jsx16(
14260
+ isErrorsOpen && errorCount > 0 && /* @__PURE__ */ jsx17(
13960
14261
  "div",
13961
14262
  {
13962
14263
  style: {
@@ -13976,14 +14277,14 @@ var ToolbarOverlay = ({ children, elements }) => {
13976
14277
  },
13977
14278
  children: errorElements.map((e, i) => {
13978
14279
  const errorId = e.pcb_trace_error_id || `error_${i}_${e.error_type}_${e.message?.slice(0, 20)}`;
13979
- return /* @__PURE__ */ jsxs13(
14280
+ return /* @__PURE__ */ jsxs14(
13980
14281
  "div",
13981
14282
  {
13982
14283
  style: {
13983
14284
  borderBottom: i < errorElements.length - 1 ? "1px solid #444" : "none"
13984
14285
  },
13985
14286
  children: [
13986
- /* @__PURE__ */ jsxs13(
14287
+ /* @__PURE__ */ jsxs14(
13987
14288
  "div",
13988
14289
  {
13989
14290
  style: {
@@ -14034,7 +14335,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14034
14335
  }
14035
14336
  },
14036
14337
  children: [
14037
- /* @__PURE__ */ jsx16(
14338
+ /* @__PURE__ */ jsx17(
14038
14339
  "div",
14039
14340
  {
14040
14341
  style: {
@@ -14048,7 +14349,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14048
14349
  children: e.error_type
14049
14350
  }
14050
14351
  ),
14051
- /* @__PURE__ */ jsx16(
14352
+ /* @__PURE__ */ jsx17(
14052
14353
  "div",
14053
14354
  {
14054
14355
  style: {
@@ -14063,7 +14364,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14063
14364
  children: e.message
14064
14365
  }
14065
14366
  ),
14066
- /* @__PURE__ */ jsx16(
14367
+ /* @__PURE__ */ jsx17(
14067
14368
  "div",
14068
14369
  {
14069
14370
  ref: (el) => {
@@ -14083,7 +14384,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14083
14384
  ]
14084
14385
  }
14085
14386
  ),
14086
- /* @__PURE__ */ jsx16(
14387
+ /* @__PURE__ */ jsx17(
14087
14388
  "div",
14088
14389
  {
14089
14390
  ref: (el) => {
@@ -14096,7 +14397,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14096
14397
  backgroundColor: "#1a1a1a",
14097
14398
  borderTop: "1px solid #444"
14098
14399
  },
14099
- children: /* @__PURE__ */ jsx16(
14400
+ children: /* @__PURE__ */ jsx17(
14100
14401
  "div",
14101
14402
  {
14102
14403
  style: {
@@ -14119,58 +14420,58 @@ var ToolbarOverlay = ({ children, elements }) => {
14119
14420
  })
14120
14421
  }
14121
14422
  ),
14122
- /* @__PURE__ */ jsx16(
14423
+ /* @__PURE__ */ jsx17(
14123
14424
  ToolbarButton,
14124
14425
  {
14125
14426
  isSmallScreen,
14126
14427
  style: {},
14127
14428
  onClick: handleEditTraceToggle,
14128
- children: /* @__PURE__ */ jsxs13("div", { children: [
14429
+ children: /* @__PURE__ */ jsxs14("div", { children: [
14129
14430
  editModes.in_draw_trace_mode ? "\u2716 " : "",
14130
14431
  "Edit Traces"
14131
14432
  ] })
14132
14433
  }
14133
14434
  ),
14134
- /* @__PURE__ */ jsx16(
14435
+ /* @__PURE__ */ jsx17(
14135
14436
  ToolbarButton,
14136
14437
  {
14137
14438
  isSmallScreen,
14138
14439
  style: {},
14139
14440
  onClick: handleMoveComponentToggle,
14140
- children: /* @__PURE__ */ jsxs13("div", { children: [
14441
+ children: /* @__PURE__ */ jsxs14("div", { children: [
14141
14442
  editModes.in_move_footprint_mode ? "\u2716 " : "",
14142
14443
  "Move Components"
14143
14444
  ] })
14144
14445
  }
14145
14446
  ),
14146
- /* @__PURE__ */ jsx16(
14447
+ /* @__PURE__ */ jsx17(
14147
14448
  ToolbarButton,
14148
14449
  {
14149
14450
  isSmallScreen,
14150
14451
  style: {},
14151
14452
  onClick: handleRatsNestToggle,
14152
- children: /* @__PURE__ */ jsxs13("div", { children: [
14453
+ children: /* @__PURE__ */ jsxs14("div", { children: [
14153
14454
  viewSettings.is_showing_rats_nest ? "\u2716 " : "",
14154
14455
  "Rats Nest"
14155
14456
  ] })
14156
14457
  }
14157
14458
  ),
14158
- /* @__PURE__ */ jsx16(
14459
+ /* @__PURE__ */ jsx17(
14159
14460
  ToolbarButton,
14160
14461
  {
14161
14462
  isSmallScreen,
14162
14463
  style: measureToolArmed ? { backgroundColor: "#444" } : {},
14163
14464
  onClick: handleMeasureToolClick,
14164
- children: /* @__PURE__ */ jsx16("div", { children: "\u{1F4CF}" })
14465
+ children: /* @__PURE__ */ jsx17("div", { children: "\u{1F4CF}" })
14165
14466
  }
14166
14467
  ),
14167
- /* @__PURE__ */ jsx16(
14468
+ /* @__PURE__ */ jsx17(
14168
14469
  ToolbarButton,
14169
14470
  {
14170
14471
  isSmallScreen,
14171
14472
  onClick: handleViewMenuToggle,
14172
- children: /* @__PURE__ */ jsxs13("div", { children: [
14173
- /* @__PURE__ */ jsxs13(
14473
+ children: /* @__PURE__ */ jsxs14("div", { children: [
14474
+ /* @__PURE__ */ jsxs14(
14174
14475
  "div",
14175
14476
  {
14176
14477
  style: {
@@ -14180,7 +14481,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14180
14481
  },
14181
14482
  children: [
14182
14483
  "View",
14183
- /* @__PURE__ */ jsx16(
14484
+ /* @__PURE__ */ jsx17(
14184
14485
  "span",
14185
14486
  {
14186
14487
  style: {
@@ -14195,8 +14496,8 @@ var ToolbarOverlay = ({ children, elements }) => {
14195
14496
  ]
14196
14497
  }
14197
14498
  ),
14198
- isViewMenuOpen && /* @__PURE__ */ jsxs13("div", { style: { marginTop: 4, minWidth: 120 }, children: [
14199
- /* @__PURE__ */ jsx16(
14499
+ isViewMenuOpen && /* @__PURE__ */ jsxs14("div", { style: { marginTop: 4, minWidth: 120 }, children: [
14500
+ /* @__PURE__ */ jsx17(
14200
14501
  CheckboxMenuItem,
14201
14502
  {
14202
14503
  label: "Show All Trace Lengths",
@@ -14208,7 +14509,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14208
14509
  }
14209
14510
  }
14210
14511
  ),
14211
- /* @__PURE__ */ jsx16(
14512
+ /* @__PURE__ */ jsx17(
14212
14513
  CheckboxMenuItem,
14213
14514
  {
14214
14515
  label: "Show Autorouting Animation",
@@ -14220,7 +14521,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14220
14521
  }
14221
14522
  }
14222
14523
  ),
14223
- /* @__PURE__ */ jsx16(
14524
+ /* @__PURE__ */ jsx17(
14224
14525
  CheckboxMenuItem,
14225
14526
  {
14226
14527
  label: "Show DRC Errors",
@@ -14230,7 +14531,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14230
14531
  }
14231
14532
  }
14232
14533
  ),
14233
- /* @__PURE__ */ jsx16(
14534
+ /* @__PURE__ */ jsx17(
14234
14535
  CheckboxMenuItem,
14235
14536
  {
14236
14537
  label: "Show Copper Pours",
@@ -14242,7 +14543,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14242
14543
  }
14243
14544
  }
14244
14545
  ),
14245
- /* @__PURE__ */ jsx16(
14546
+ /* @__PURE__ */ jsx17(
14246
14547
  CheckboxMenuItem,
14247
14548
  {
14248
14549
  label: "Show Solder Mask",
@@ -14252,7 +14553,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14252
14553
  }
14253
14554
  }
14254
14555
  ),
14255
- /* @__PURE__ */ jsx16(
14556
+ /* @__PURE__ */ jsx17(
14256
14557
  CheckboxMenuItem,
14257
14558
  {
14258
14559
  label: "Show Group Anchor Offsets",
@@ -14264,7 +14565,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14264
14565
  }
14265
14566
  }
14266
14567
  ),
14267
- /* @__PURE__ */ jsx16(
14568
+ /* @__PURE__ */ jsx17(
14268
14569
  CheckboxMenuItem,
14269
14570
  {
14270
14571
  label: "Show PCB Groups",
@@ -14274,8 +14575,8 @@ var ToolbarOverlay = ({ children, elements }) => {
14274
14575
  }
14275
14576
  }
14276
14577
  ),
14277
- viewSettings.is_showing_pcb_groups && /* @__PURE__ */ jsxs13("div", { style: { marginLeft: 16 }, children: [
14278
- /* @__PURE__ */ jsx16(
14578
+ viewSettings.is_showing_pcb_groups && /* @__PURE__ */ jsxs14("div", { style: { marginLeft: 16 }, children: [
14579
+ /* @__PURE__ */ jsx17(
14279
14580
  RadioMenuItem,
14280
14581
  {
14281
14582
  label: "Show All Groups",
@@ -14285,7 +14586,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14285
14586
  }
14286
14587
  }
14287
14588
  ),
14288
- /* @__PURE__ */ jsx16(
14589
+ /* @__PURE__ */ jsx17(
14289
14590
  RadioMenuItem,
14290
14591
  {
14291
14592
  label: "Show Named Groups",
@@ -14309,7 +14610,7 @@ var ToolbarOverlay = ({ children, elements }) => {
14309
14610
  };
14310
14611
 
14311
14612
  // src/components/CanvasElementsRenderer.tsx
14312
- import { jsx as jsx17 } from "react/jsx-runtime";
14613
+ import { jsx as jsx18 } from "react/jsx-runtime";
14313
14614
  var CanvasElementsRenderer = (props) => {
14314
14615
  const { transform, elements } = props;
14315
14616
  const hoveredErrorId = useGlobalStore((state) => state.hovered_error_id);
@@ -14333,6 +14634,7 @@ var CanvasElementsRenderer = (props) => {
14333
14634
  drawingObjectIdsWithMouseOver: /* @__PURE__ */ new Set(),
14334
14635
  primitiveIdsInMousedOverNet: []
14335
14636
  });
14637
+ const [hoveredComponentIds, setHoveredComponentIds] = useState10([]);
14336
14638
  const errorRelatedIds = useMemo7(() => {
14337
14639
  if (!hoveredErrorId) return [];
14338
14640
  const errorElements = elements.filter(
@@ -14383,17 +14685,27 @@ var CanvasElementsRenderer = (props) => {
14383
14685
  drawingObjectIdsWithMouseOver,
14384
14686
  primitiveIdsInMousedOverNet
14385
14687
  });
14688
+ const componentIds = primitivesHoveredOver.map((primitive) => {
14689
+ if (primitive._parent_pcb_component?.type === "pcb_component" && primitive._parent_pcb_component.pcb_component_id) {
14690
+ return primitive._parent_pcb_component.pcb_component_id;
14691
+ }
14692
+ if (primitive._element?.type === "pcb_component" && primitive._element.pcb_component_id) {
14693
+ return primitive._element.pcb_component_id;
14694
+ }
14695
+ return null;
14696
+ }).filter((id) => Boolean(id));
14697
+ setHoveredComponentIds(Array.from(new Set(componentIds)));
14386
14698
  },
14387
14699
  [connectivityMap]
14388
14700
  );
14389
- return /* @__PURE__ */ jsx17(
14701
+ return /* @__PURE__ */ jsx18(
14390
14702
  MouseElementTracker,
14391
14703
  {
14392
14704
  elements: elementsToRender,
14393
14705
  transform,
14394
14706
  primitives: primitivesWithoutInteractionMetadata,
14395
14707
  onMouseHoverOverPrimitives: onMouseOverPrimitives,
14396
- children: /* @__PURE__ */ jsx17(
14708
+ children: /* @__PURE__ */ jsx18(
14397
14709
  EditPlacementOverlay,
14398
14710
  {
14399
14711
  disabled: !props.allowEditing,
@@ -14402,7 +14714,7 @@ var CanvasElementsRenderer = (props) => {
14402
14714
  cancelPanDrag: props.cancelPanDrag,
14403
14715
  onCreateEditEvent: props.onCreateEditEvent,
14404
14716
  onModifyEditEvent: props.onModifyEditEvent,
14405
- children: /* @__PURE__ */ jsx17(
14717
+ children: /* @__PURE__ */ jsx18(
14406
14718
  EditTraceHintOverlay,
14407
14719
  {
14408
14720
  disabled: !props.allowEditing,
@@ -14411,36 +14723,44 @@ var CanvasElementsRenderer = (props) => {
14411
14723
  cancelPanDrag: props.cancelPanDrag,
14412
14724
  onCreateEditEvent: props.onCreateEditEvent,
14413
14725
  onModifyEditEvent: props.onModifyEditEvent,
14414
- children: /* @__PURE__ */ jsx17(
14726
+ children: /* @__PURE__ */ jsx18(
14415
14727
  DimensionOverlay,
14416
14728
  {
14417
14729
  transform,
14418
14730
  focusOnHover: props.focusOnHover,
14419
14731
  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,
14732
+ children: /* @__PURE__ */ jsx18(ToolbarOverlay, { elements, children: /* @__PURE__ */ jsx18(ErrorOverlay, { transform, elements, children: /* @__PURE__ */ jsx18(RatsNestOverlay, { transform, soup: elements, children: /* @__PURE__ */ jsx18(
14733
+ PcbGroupOverlay,
14422
14734
  {
14423
14735
  transform,
14424
- debugGraphics: props.debugGraphics,
14425
- children: /* @__PURE__ */ jsx17(
14426
- WarningGraphicsOverlay,
14736
+ elements,
14737
+ hoveredComponentIds,
14738
+ children: /* @__PURE__ */ jsx18(
14739
+ DebugGraphicsOverlay,
14427
14740
  {
14428
14741
  transform,
14429
- elements,
14430
- children: /* @__PURE__ */ jsx17(
14431
- CanvasPrimitiveRenderer,
14742
+ debugGraphics: props.debugGraphics,
14743
+ children: /* @__PURE__ */ jsx18(
14744
+ WarningGraphicsOverlay,
14432
14745
  {
14433
14746
  transform,
14434
- primitives,
14435
- width: props.width,
14436
- height: props.height,
14437
- grid: props.grid
14747
+ elements,
14748
+ children: /* @__PURE__ */ jsx18(
14749
+ CanvasPrimitiveRenderer,
14750
+ {
14751
+ transform,
14752
+ primitives,
14753
+ width: props.width,
14754
+ height: props.height,
14755
+ grid: props.grid
14756
+ }
14757
+ )
14438
14758
  }
14439
14759
  )
14440
14760
  }
14441
14761
  )
14442
14762
  }
14443
- ) }) }) }) })
14763
+ ) }) }) })
14444
14764
  }
14445
14765
  )
14446
14766
  }
@@ -14494,7 +14814,7 @@ var calculateCircuitJsonKey = (circuitJson) => {
14494
14814
  };
14495
14815
 
14496
14816
  // src/PCBViewer.tsx
14497
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
14817
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
14498
14818
  var defaultTransform = compose7(translate11(400, 300), scale5(40, -40));
14499
14819
  var PCBViewer = ({
14500
14820
  circuitJson,
@@ -14588,20 +14908,20 @@ var PCBViewer = ({
14588
14908
  }),
14589
14909
  [initialState, disablePcbGroups]
14590
14910
  );
14591
- return /* @__PURE__ */ jsxs14(
14911
+ return /* @__PURE__ */ jsxs15(
14592
14912
  "div",
14593
14913
  {
14594
14914
  ref: transformRef,
14595
14915
  style: { position: "relative" },
14596
14916
  onContextMenu: (event) => event.preventDefault(),
14597
14917
  children: [
14598
- /* @__PURE__ */ jsx18("div", { ref, children: /* @__PURE__ */ jsxs14(
14918
+ /* @__PURE__ */ jsx19("div", { ref, children: /* @__PURE__ */ jsxs15(
14599
14919
  ContextProviders,
14600
14920
  {
14601
14921
  initialState: mergedInitialState,
14602
14922
  disablePcbGroups,
14603
14923
  children: [
14604
- /* @__PURE__ */ jsx18(
14924
+ /* @__PURE__ */ jsx19(
14605
14925
  CanvasElementsRenderer,
14606
14926
  {
14607
14927
  transform,
@@ -14626,11 +14946,11 @@ var PCBViewer = ({
14626
14946
  },
14627
14947
  refDimensions.width
14628
14948
  ),
14629
- /* @__PURE__ */ jsx18(ToastContainer, {})
14949
+ /* @__PURE__ */ jsx19(ToastContainer, {})
14630
14950
  ]
14631
14951
  }
14632
14952
  ) }),
14633
- clickToInteractEnabled && !isInteractionEnabled && /* @__PURE__ */ jsx18(
14953
+ clickToInteractEnabled && !isInteractionEnabled && /* @__PURE__ */ jsx19(
14634
14954
  "div",
14635
14955
  {
14636
14956
  onClick: () => {
@@ -14667,7 +14987,7 @@ var PCBViewer = ({
14667
14987
  justifyContent: "center",
14668
14988
  touchAction: "pan-x pan-y pinch-zoom"
14669
14989
  },
14670
- children: /* @__PURE__ */ jsx18(
14990
+ children: /* @__PURE__ */ jsx19(
14671
14991
  "div",
14672
14992
  {
14673
14993
  style: {