@tscircuit/pcb-viewer 1.11.369 → 1.11.370

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/README.md CHANGED
@@ -75,6 +75,7 @@ The PCBViewer component accepts these props:
75
75
  - `allowEditing`: Enable/disable editing capabilities (default: true)
76
76
  - `editEvents`: Array of edit events to apply
77
77
  - `onEditEventsChanged`: Callback when edit events change
78
+ - `onBoundsSelected`: Callback when the Bounds tool completes a rectangle selection. Receives `{ minX, minY, maxX, maxY }`.
78
79
  - `initialState`: Initial state for the viewer
79
80
 
80
81
  ### Features
package/dist/index.d.ts CHANGED
@@ -51,6 +51,23 @@ type StateProps = {
51
51
  [key in keyof State]: State[key] extends boolean ? boolean : never;
52
52
  };
53
53
 
54
+ type GridConfig = {
55
+ spacing: number;
56
+ view_window: {
57
+ left: number;
58
+ right: number;
59
+ top: number;
60
+ bottom: number;
61
+ };
62
+ };
63
+
64
+ interface BoundsSelection {
65
+ minX: number;
66
+ minY: number;
67
+ maxX: number;
68
+ maxY: number;
69
+ }
70
+
54
71
  type Props = {
55
72
  circuitJson?: AnyCircuitElement[];
56
73
  height?: number;
@@ -58,22 +75,13 @@ type Props = {
58
75
  editEvents?: ManualEditEvent[];
59
76
  initialState?: Partial<StateProps>;
60
77
  onEditEventsChanged?: (editEvents: ManualEditEvent[]) => void;
78
+ onBoundsSelected?: (bounds: BoundsSelection) => void;
61
79
  focusOnHover?: boolean;
62
80
  clickToInteractEnabled?: boolean;
63
81
  debugGraphics?: GraphicsObject | null;
64
82
  disablePcbGroups?: boolean;
65
83
  };
66
- declare const PCBViewer: ({ circuitJson, debugGraphics, height, initialState, allowEditing, editEvents: editEventsProp, onEditEventsChanged, focusOnHover, clickToInteractEnabled, disablePcbGroups, }: Props) => react_jsx_runtime.JSX.Element;
67
-
68
- type GridConfig = {
69
- spacing: number;
70
- view_window: {
71
- left: number;
72
- right: number;
73
- top: number;
74
- bottom: number;
75
- };
76
- };
84
+ declare const PCBViewer: ({ circuitJson, debugGraphics, height, initialState, allowEditing, editEvents: editEventsProp, onEditEventsChanged, onBoundsSelected, focusOnHover, clickToInteractEnabled, disablePcbGroups, }: Props) => react_jsx_runtime.JSX.Element;
77
85
 
78
86
  interface CanvasElementsRendererProps {
79
87
  elements: AnyCircuitElement[];
@@ -85,6 +93,7 @@ interface CanvasElementsRendererProps {
85
93
  grid?: GridConfig;
86
94
  allowEditing: boolean;
87
95
  focusOnHover?: boolean;
96
+ onBoundsSelected?: (bounds: BoundsSelection) => void;
88
97
  cancelPanDrag: () => void;
89
98
  onCreateEditEvent: (event: ManualEditEvent) => void;
90
99
  onModifyEditEvent: (event: Partial<ManualEditEvent>) => void;
package/dist/index.js CHANGED
@@ -11611,12 +11611,17 @@ var DimensionOverlay = ({
11611
11611
  children,
11612
11612
  transform,
11613
11613
  focusOnHover = false,
11614
- primitives = []
11614
+ primitives = [],
11615
+ onBoundsSelected,
11616
+ cancelPanDrag
11615
11617
  }) => {
11616
11618
  if (!transform) transform = identity4();
11617
11619
  const [dimensionToolVisible, setDimensionToolVisible] = useState4(false);
11618
11620
  const [dimensionToolStretching, setDimensionToolStretching] = useState4(false);
11619
11621
  const [measureToolArmed, setMeasureToolArmed] = useState4(false);
11622
+ const [boundsToolArmed, setBoundsToolArmed] = useState4(false);
11623
+ const [boundsToolVisible, setBoundsToolVisible] = useState4(false);
11624
+ const [boundsToolDragging, setBoundsToolDragging] = useState4(false);
11620
11625
  const [activeSnapIds, setActiveSnapIds] = useState4({
11621
11626
  start: null,
11622
11627
  end: null
@@ -11628,8 +11633,16 @@ var DimensionOverlay = ({
11628
11633
  window.dispatchEvent(new Event("disarm-dimension-tool"));
11629
11634
  }
11630
11635
  }, [measureToolArmed]);
11636
+ const disarmBounds = useCallback6(() => {
11637
+ if (boundsToolArmed) {
11638
+ setBoundsToolArmed(false);
11639
+ window.dispatchEvent(new Event("disarm-bounds-tool"));
11640
+ }
11641
+ }, [boundsToolArmed]);
11631
11642
  const [dStart, setDStart] = useState4({ x: 0, y: 0 });
11632
11643
  const [dEnd, setDEnd] = useState4({ x: 0, y: 0 });
11644
+ const [boundsStart, setBoundsStart] = useState4({ x: 0, y: 0 });
11645
+ const [boundsEnd, setBoundsEnd] = useState4({ x: 0, y: 0 });
11633
11646
  const mousePosRef = useRef6({ x: 0, y: 0 });
11634
11647
  const containerRef = useRef6(null);
11635
11648
  const container = containerRef.current;
@@ -11768,22 +11781,74 @@ var DimensionOverlay = ({
11768
11781
  setDimensionToolVisible(false);
11769
11782
  setDimensionToolStretching(false);
11770
11783
  setActiveSnapIds({ start: null, end: null });
11784
+ setBoundsToolVisible(false);
11785
+ setBoundsToolDragging(false);
11771
11786
  disarmMeasure();
11787
+ disarmBounds();
11772
11788
  }
11773
11789
  };
11774
11790
  const armMeasure = () => {
11775
11791
  setMeasureToolArmed(true);
11792
+ setBoundsToolArmed(false);
11793
+ window.dispatchEvent(new Event("disarm-bounds-tool"));
11794
+ };
11795
+ const armBounds = () => {
11796
+ setBoundsToolArmed(true);
11797
+ setMeasureToolArmed(false);
11798
+ setDimensionToolVisible(false);
11799
+ setDimensionToolStretching(false);
11800
+ window.dispatchEvent(new Event("disarm-dimension-tool"));
11776
11801
  };
11777
11802
  window.addEventListener("keydown", down);
11778
11803
  window.addEventListener("arm-dimension-tool", armMeasure);
11804
+ window.addEventListener("arm-bounds-tool", armBounds);
11779
11805
  return () => {
11780
11806
  window.removeEventListener("keydown", down);
11781
11807
  window.removeEventListener("arm-dimension-tool", armMeasure);
11808
+ window.removeEventListener("arm-bounds-tool", armBounds);
11782
11809
  disarmMeasure();
11810
+ disarmBounds();
11783
11811
  };
11784
- }, [isMouseOverContainer, dimensionToolVisible, disarmMeasure, findSnap]);
11812
+ }, [
11813
+ isMouseOverContainer,
11814
+ dimensionToolVisible,
11815
+ disarmMeasure,
11816
+ disarmBounds,
11817
+ findSnap
11818
+ ]);
11785
11819
  const screenDStart = applyToPoint8(transform, dStart);
11786
11820
  const screenDEnd = applyToPoint8(transform, dEnd);
11821
+ const screenBoundsStart = applyToPoint8(transform, boundsStart);
11822
+ const screenBoundsEnd = applyToPoint8(transform, boundsEnd);
11823
+ const selectedBounds = {
11824
+ minX: Math.min(boundsStart.x, boundsEnd.x),
11825
+ minY: Math.min(boundsStart.y, boundsEnd.y),
11826
+ maxX: Math.max(boundsStart.x, boundsEnd.x),
11827
+ maxY: Math.max(boundsStart.y, boundsEnd.y)
11828
+ };
11829
+ const boundsScreenRect = {
11830
+ left: Math.min(screenBoundsStart.x, screenBoundsEnd.x),
11831
+ top: Math.min(screenBoundsStart.y, screenBoundsEnd.y),
11832
+ width: Math.abs(screenBoundsStart.x - screenBoundsEnd.x),
11833
+ height: Math.abs(screenBoundsStart.y - screenBoundsEnd.y)
11834
+ };
11835
+ const emitBounds = useCallback6(
11836
+ (start, end) => {
11837
+ const bounds = {
11838
+ minX: Math.min(start.x, end.x),
11839
+ minY: Math.min(start.y, end.y),
11840
+ maxX: Math.max(start.x, end.x),
11841
+ maxY: Math.max(start.y, end.y)
11842
+ };
11843
+ onBoundsSelected?.(bounds);
11844
+ window.dispatchEvent(
11845
+ new CustomEvent("pcb-viewer:bounds-selected", {
11846
+ detail: bounds
11847
+ })
11848
+ );
11849
+ },
11850
+ [onBoundsSelected]
11851
+ );
11787
11852
  const arrowScreenBounds = {
11788
11853
  left: Math.min(screenDStart.x, screenDEnd.x),
11789
11854
  right: Math.max(screenDStart.x, screenDEnd.x),
@@ -11838,13 +11903,26 @@ var DimensionOverlay = ({
11838
11903
  setDEnd({ x: snap.point.x, y: snap.point.y });
11839
11904
  setActiveSnapIds((prev) => ({ ...prev, end: snap.id }));
11840
11905
  }
11906
+ if (boundsToolDragging) {
11907
+ cancelPanDrag?.();
11908
+ setBoundsEnd({ x: rwPoint.x, y: rwPoint.y });
11909
+ }
11841
11910
  },
11842
11911
  onMouseDown: (e) => {
11843
11912
  const rect = e.currentTarget.getBoundingClientRect();
11844
11913
  const x = e.clientX - rect.left;
11845
11914
  const y = e.clientY - rect.top;
11846
11915
  const rwPoint = applyToPoint8(inverse2(transform), { x, y });
11847
- if (measureToolArmed && !dimensionToolVisible) {
11916
+ if (boundsToolArmed) {
11917
+ e.preventDefault();
11918
+ e.stopPropagation();
11919
+ cancelPanDrag?.();
11920
+ setBoundsStart({ x: rwPoint.x, y: rwPoint.y });
11921
+ setBoundsEnd({ x: rwPoint.x, y: rwPoint.y });
11922
+ setBoundsToolVisible(true);
11923
+ setBoundsToolDragging(true);
11924
+ disarmBounds();
11925
+ } else if (measureToolArmed && !dimensionToolVisible) {
11848
11926
  const snap = findSnap(rwPoint);
11849
11927
  setDStart({ x: snap.point.x, y: snap.point.y });
11850
11928
  setDEnd({ x: snap.point.x, y: snap.point.y });
@@ -11860,8 +11938,82 @@ var DimensionOverlay = ({
11860
11938
  setActiveSnapIds({ start: null, end: null });
11861
11939
  }
11862
11940
  },
11941
+ onMouseUp: (e) => {
11942
+ if (!boundsToolDragging) return;
11943
+ const rect = e.currentTarget.getBoundingClientRect();
11944
+ const x = e.clientX - rect.left;
11945
+ const y = e.clientY - rect.top;
11946
+ const rwPoint = applyToPoint8(inverse2(transform), { x, y });
11947
+ const end = { x: rwPoint.x, y: rwPoint.y };
11948
+ e.preventDefault();
11949
+ e.stopPropagation();
11950
+ cancelPanDrag?.();
11951
+ setBoundsEnd(end);
11952
+ setBoundsToolDragging(false);
11953
+ emitBounds(boundsStart, end);
11954
+ },
11863
11955
  children: [
11864
11956
  children,
11957
+ boundsToolVisible && /* @__PURE__ */ jsxs4(Fragment, { children: [
11958
+ /* @__PURE__ */ jsx6(
11959
+ "svg",
11960
+ {
11961
+ style: {
11962
+ position: "absolute",
11963
+ left: 0,
11964
+ top: 0,
11965
+ pointerEvents: "none",
11966
+ mixBlendMode: "difference",
11967
+ zIndex: zIndexMap.dimensionOverlay
11968
+ },
11969
+ width: containerBounds.width,
11970
+ height: containerBounds.height,
11971
+ children: /* @__PURE__ */ jsx6(
11972
+ "rect",
11973
+ {
11974
+ x: boundsScreenRect.left,
11975
+ y: boundsScreenRect.top,
11976
+ width: boundsScreenRect.width,
11977
+ height: boundsScreenRect.height,
11978
+ stroke: "red",
11979
+ strokeWidth: 1.5,
11980
+ strokeDasharray: "4,3",
11981
+ fill: "rgba(255, 0, 0, 0.12)"
11982
+ }
11983
+ )
11984
+ }
11985
+ ),
11986
+ /* @__PURE__ */ jsxs4(
11987
+ "div",
11988
+ {
11989
+ style: {
11990
+ left: 0,
11991
+ bottom: dimensionToolVisible ? 58 : 0,
11992
+ position: "absolute",
11993
+ color: "red",
11994
+ fontFamily: "sans-serif",
11995
+ fontSize: 12,
11996
+ margin: 4,
11997
+ pointerEvents: "none",
11998
+ mixBlendMode: "difference",
11999
+ zIndex: zIndexMap.dimensionOverlay
12000
+ },
12001
+ children: [
12002
+ "minX: ",
12003
+ selectedBounds.minX.toFixed(2),
12004
+ ", minY:",
12005
+ " ",
12006
+ selectedBounds.minY.toFixed(2),
12007
+ /* @__PURE__ */ jsx6("br", {}),
12008
+ "maxX: ",
12009
+ selectedBounds.maxX.toFixed(2),
12010
+ ", maxY:",
12011
+ " ",
12012
+ selectedBounds.maxY.toFixed(2)
12013
+ ]
12014
+ }
12015
+ )
12016
+ ] }),
11865
12017
  dimensionToolVisible && /* @__PURE__ */ jsxs4(Fragment, { children: [
11866
12018
  diagonalLabel.show && /* @__PURE__ */ jsx6(
11867
12019
  "div",
@@ -14752,7 +14904,7 @@ import { css as css3 } from "@emotion/css";
14752
14904
  // package.json
14753
14905
  var package_default = {
14754
14906
  name: "@tscircuit/pcb-viewer",
14755
- version: "1.11.368",
14907
+ version: "1.11.369",
14756
14908
  main: "dist/index.js",
14757
14909
  type: "module",
14758
14910
  repository: "tscircuit/pcb-viewer",
@@ -15564,14 +15716,21 @@ var ToolbarOverlay = ({ children, elements }) => {
15564
15716
  const [isLayerMenuOpen, setLayerMenuOpen] = useState11(false);
15565
15717
  const [isErrorsOpen, setErrorsOpen] = useState11(false);
15566
15718
  const [measureToolArmed, setMeasureToolArmed] = useState11(false);
15719
+ const [boundsToolArmed, setBoundsToolArmed] = useState11(false);
15567
15720
  useEffect17(() => {
15568
15721
  const arm = () => setMeasureToolArmed(true);
15569
15722
  const disarm = () => setMeasureToolArmed(false);
15723
+ const armBounds = () => setBoundsToolArmed(true);
15724
+ const disarmBounds = () => setBoundsToolArmed(false);
15570
15725
  window.addEventListener("arm-dimension-tool", arm);
15571
15726
  window.addEventListener("disarm-dimension-tool", disarm);
15727
+ window.addEventListener("arm-bounds-tool", armBounds);
15728
+ window.addEventListener("disarm-bounds-tool", disarmBounds);
15572
15729
  return () => {
15573
15730
  window.removeEventListener("arm-dimension-tool", arm);
15574
15731
  window.removeEventListener("disarm-dimension-tool", disarm);
15732
+ window.removeEventListener("arm-bounds-tool", armBounds);
15733
+ window.removeEventListener("disarm-bounds-tool", disarmBounds);
15575
15734
  };
15576
15735
  }, []);
15577
15736
  const pcbBoard = elements?.find((el) => el.type === "pcb_board");
@@ -15669,8 +15828,14 @@ var ToolbarOverlay = ({ children, elements }) => {
15669
15828
  }, [viewSettings.is_showing_rats_nest, setIsShowingRatsNest]);
15670
15829
  const handleMeasureToolClick = useCallback10(() => {
15671
15830
  setMeasureToolArmed(true);
15831
+ setBoundsToolArmed(false);
15672
15832
  window.dispatchEvent(new Event("arm-dimension-tool"));
15673
15833
  }, []);
15834
+ const handleBoundsToolClick = useCallback10(() => {
15835
+ setBoundsToolArmed(true);
15836
+ setMeasureToolArmed(false);
15837
+ window.dispatchEvent(new Event("arm-bounds-tool"));
15838
+ }, []);
15674
15839
  const handleViewMenuToggle = useCallback10(() => {
15675
15840
  const newViewMenuOpen = !isViewMenuOpen;
15676
15841
  setViewMenuOpen(newViewMenuOpen);
@@ -15842,6 +16007,15 @@ var ToolbarOverlay = ({ children, elements }) => {
15842
16007
  children: /* @__PURE__ */ jsx23("div", { children: "\u{1F4CF}" })
15843
16008
  }
15844
16009
  ),
16010
+ /* @__PURE__ */ jsx23(
16011
+ ToolbarButton,
16012
+ {
16013
+ isSmallScreen,
16014
+ style: boundsToolArmed ? { backgroundColor: "#444" } : {},
16015
+ onClick: handleBoundsToolClick,
16016
+ children: /* @__PURE__ */ jsx23("div", { children: "Bounds" })
16017
+ }
16018
+ ),
15845
16019
  /* @__PURE__ */ jsx23(
15846
16020
  ToolbarButton,
15847
16021
  {
@@ -16186,6 +16360,8 @@ var CanvasElementsRenderer = (props) => {
16186
16360
  transform,
16187
16361
  focusOnHover: props.focusOnHover,
16188
16362
  primitives: primitivesWithoutInteractionMetadata,
16363
+ onBoundsSelected: props.onBoundsSelected,
16364
+ cancelPanDrag: props.cancelPanDrag,
16189
16365
  children: /* @__PURE__ */ jsx24(ToolbarOverlay, { elements, children: /* @__PURE__ */ jsx24(ErrorOverlay, { transform, elements, children: /* @__PURE__ */ jsx24(RatsNestOverlay, { transform, soup: elements, children: /* @__PURE__ */ jsx24(
16190
16366
  PcbGroupOverlay,
16191
16367
  {
@@ -16294,6 +16470,7 @@ var PCBViewer = ({
16294
16470
  allowEditing = true,
16295
16471
  editEvents: editEventsProp,
16296
16472
  onEditEventsChanged,
16473
+ onBoundsSelected,
16297
16474
  focusOnHover = false,
16298
16475
  clickToInteractEnabled = false,
16299
16476
  disablePcbGroups = false
@@ -16415,6 +16592,7 @@ var PCBViewer = ({
16415
16592
  width: refDimensions.width,
16416
16593
  allowEditing,
16417
16594
  focusOnHover,
16595
+ onBoundsSelected,
16418
16596
  cancelPanDrag,
16419
16597
  onCreateEditEvent,
16420
16598
  onModifyEditEvent,