@tscircuit/pcb-viewer 1.11.420 → 1.11.421

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.d.ts CHANGED
@@ -80,6 +80,27 @@ interface BoundsSelection {
80
80
  maxY: number;
81
81
  }
82
82
 
83
+ interface PcbViewerController {
84
+ /** @internal Pending command, consumed after the viewer focuses the component. */
85
+ focusRequest: {
86
+ pcbComponentId: string;
87
+ } | null;
88
+ /** @internal Acknowledge only this request, preserving newer commands. */
89
+ onFocusRequestHandled: (request: {
90
+ pcbComponentId: string;
91
+ }) => void;
92
+ }
93
+ /** Focus commands may be issued before the PCB viewer mounts, such as when switching tabs. */
94
+ declare const usePcbViewerController: () => {
95
+ controller: {
96
+ focusRequest: {
97
+ pcbComponentId: string;
98
+ };
99
+ onFocusRequestHandled: (request: NonNullable<PcbViewerController["focusRequest"]>) => void;
100
+ };
101
+ focusPcbComponent: (pcbComponentId: string) => void;
102
+ };
103
+
83
104
  type Props = {
84
105
  circuitJson?: AnyCircuitElement[];
85
106
  height?: number;
@@ -99,8 +120,10 @@ type Props = {
99
120
  clickToInteractEnabled?: boolean;
100
121
  debugGraphics?: GraphicsObject | null;
101
122
  disablePcbGroups?: boolean;
123
+ /** Connect the controller returned by usePcbViewerController. */
124
+ controller?: PcbViewerController;
102
125
  };
103
- declare const PCBViewer: ({ circuitJson, debugGraphics, height, renderer, initialState, allowEditing, editEvents: editEventsProp, onEditEventsChanged, onRenderComplete, onBoundsSelected, onViewSchematicComponent, focusOnHover, clickToInteractEnabled, disablePcbGroups, }: Props) => react_jsx_runtime.JSX.Element;
126
+ declare const PCBViewer: ({ circuitJson, debugGraphics, height, renderer, initialState, allowEditing, editEvents: editEventsProp, onEditEventsChanged, onRenderComplete, onBoundsSelected, onViewSchematicComponent, focusOnHover, clickToInteractEnabled, disablePcbGroups, controller, }: Props) => react_jsx_runtime.JSX.Element;
104
127
 
105
128
  interface CanvasElementsRendererProps {
106
129
  elements: AnyCircuitElement[];
@@ -113,6 +136,9 @@ interface CanvasElementsRendererProps {
113
136
  grid?: GridConfig;
114
137
  allowEditing: boolean;
115
138
  focusOnHover?: boolean;
139
+ focusedPcbComponent?: {
140
+ pcbComponentId: string;
141
+ } | null;
116
142
  onViewSchematicComponent?: (event: ViewSchematicComponentEvent) => void;
117
143
  onBoundsSelected?: (bounds: BoundsSelection) => void;
118
144
  onContextMenuOpenChange?: (open: boolean) => void;
@@ -123,4 +149,4 @@ interface CanvasElementsRendererProps {
123
149
  }
124
150
  declare const CanvasElementsRenderer: (props: CanvasElementsRendererProps) => react_jsx_runtime.JSX.Element;
125
151
 
126
- export { CanvasElementsRenderer, PCBViewer, type ViewSchematicComponentEvent };
152
+ export { CanvasElementsRenderer, PCBViewer, type PcbViewerController, type ViewSchematicComponentEvent, usePcbViewerController };
package/dist/index.js CHANGED
@@ -9827,7 +9827,7 @@ var ToastContainer = () => {
9827
9827
  };
9828
9828
 
9829
9829
  // src/PCBViewer.tsx
9830
- import { useCallback as useCallback12, useEffect as useEffect20, useMemo as useMemo11, useRef as useRef17, useState as useState14 } from "react";
9830
+ import { useCallback as useCallback12, useEffect as useEffect21, useMemo as useMemo11, useRef as useRef17, useState as useState14 } from "react";
9831
9831
 
9832
9832
  // node_modules/react-use/esm/useMountedState.js
9833
9833
  import { useCallback as useCallback2, useEffect as useEffect2, useRef } from "react";
@@ -11665,8 +11665,8 @@ var animateTransform = ({
11665
11665
  };
11666
11666
 
11667
11667
  // src/components/CanvasElementsRenderer.tsx
11668
- import { useCallback as useCallback11, useEffect as useEffect19, useMemo as useMemo10, useRef as useRef16, useState as useState13 } from "react";
11669
- import { applyToPoint as applyToPoint19, inverse as inverse6 } from "transformation-matrix";
11668
+ import { useCallback as useCallback11, useEffect as useEffect20, useMemo as useMemo10, useRef as useRef16, useState as useState13 } from "react";
11669
+ import { applyToPoint as applyToPoint20, inverse as inverse6 } from "transformation-matrix";
11670
11670
 
11671
11671
  // src/lib/get-trace-overlay-text.ts
11672
11672
  var getNonEmptyName = (name) => name?.trim() || null;
@@ -18585,7 +18585,7 @@ import { css as css3 } from "@emotion/css";
18585
18585
  // package.json
18586
18586
  var package_default = {
18587
18587
  name: "@tscircuit/pcb-viewer",
18588
- version: "1.11.419",
18588
+ version: "1.11.420",
18589
18589
  main: "dist/index.js",
18590
18590
  type: "module",
18591
18591
  repository: {
@@ -18602,6 +18602,7 @@ var package_default = {
18602
18602
  cosmos: "cosmos",
18603
18603
  "cosmos-export": "cosmos-export",
18604
18604
  "test:x-ray-net": "bun run build && bun tests/x-ray-net/run.ts",
18605
+ "test:component-focus": "bun run build && bun tests/component-focus/run.ts",
18605
18606
  "test:measurement-context-menu": "bun run build && bun tests/measurement-context-menu/run.ts",
18606
18607
  "test:copper-pour-opacity": "bun run build && bun tests/copper-pour-opacity/run.ts",
18607
18608
  "test:webgpu": "bun run build && bun tests/webgpu/run.ts"
@@ -20574,8 +20575,66 @@ var WarningGraphicsOverlay = ({
20574
20575
  );
20575
20576
  };
20576
20577
 
20577
- // src/components/CanvasElementsRenderer.tsx
20578
+ // src/components/FocusedPcbComponentOverlay.tsx
20579
+ import { useEffect as useEffect19 } from "react";
20580
+ import { applyToPoint as applyToPoint19 } from "transformation-matrix";
20581
+
20582
+ // src/lib/get-pcb-component-focus.ts
20583
+ var getPcbComponentFocus = (elements, pcbComponentId, width, height) => {
20584
+ if (width <= 0 || height <= 0) return;
20585
+ const component = elements.find(
20586
+ (element) => element.type === "pcb_component" && element.pcb_component_id === pcbComponentId
20587
+ );
20588
+ if (component?.type !== "pcb_component") return;
20589
+ const bounds = getPcbElementBounds(component);
20590
+ if (!bounds) return;
20591
+ return {
20592
+ component,
20593
+ bounds,
20594
+ transform: createTransformForBounds({ bounds, width, height })
20595
+ };
20596
+ };
20597
+
20598
+ // src/components/FocusedPcbComponentOverlay.tsx
20578
20599
  import { jsx as jsx25 } from "react/jsx-runtime";
20600
+ var FocusedPcbComponentOverlay = ({
20601
+ focusRequest,
20602
+ elements,
20603
+ transform
20604
+ }) => {
20605
+ const componentId = focusRequest?.pcbComponentId;
20606
+ const selectLayer = useGlobalStore((state) => state.selectLayer);
20607
+ const focus = componentId ? getPcbComponentFocus(elements, componentId, 1, 1) : void 0;
20608
+ const layer = focus?.component.layer;
20609
+ useEffect19(() => {
20610
+ if (layer) selectLayer(layer);
20611
+ }, [focusRequest, layer, selectLayer]);
20612
+ if (!focus || !transform) return null;
20613
+ const { bounds } = focus;
20614
+ const a = applyToPoint19(transform, { x: bounds.minX, y: bounds.minY });
20615
+ const b = applyToPoint19(transform, { x: bounds.maxX, y: bounds.maxY });
20616
+ return /* @__PURE__ */ jsx25(
20617
+ "div",
20618
+ {
20619
+ "data-focused-pcb-component-id": componentId,
20620
+ style: {
20621
+ position: "absolute",
20622
+ left: Math.min(a.x, b.x) - 4,
20623
+ top: Math.min(a.y, b.y) - 4,
20624
+ width: Math.abs(b.x - a.x) + 8,
20625
+ height: Math.abs(b.y - a.y) + 8,
20626
+ border: "2px solid #3399ff",
20627
+ borderRadius: 4,
20628
+ boxSizing: "border-box",
20629
+ pointerEvents: "none",
20630
+ zIndex: zIndexMap.elementOverlay
20631
+ }
20632
+ }
20633
+ );
20634
+ };
20635
+
20636
+ // src/components/CanvasElementsRenderer.tsx
20637
+ import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
20579
20638
  var CanvasElementsRenderer = (props) => {
20580
20639
  const { transform, elements } = props;
20581
20640
  const {
@@ -20605,8 +20664,8 @@ var CanvasElementsRenderer = (props) => {
20605
20664
  [elements]
20606
20665
  );
20607
20666
  const [xRayNetIds, setXRayNetIds] = useState13([]);
20608
- useEffect19(() => setXRayNetIds([]), [connectivityMap]);
20609
- useEffect19(() => {
20667
+ useEffect20(() => setXRayNetIds([]), [connectivityMap]);
20668
+ useEffect20(() => {
20610
20669
  if (xRayNetIds.length === 0) return;
20611
20670
  const onKeyDown = (event) => {
20612
20671
  if (event.key === "Escape") setXRayNetIds([]);
@@ -20633,7 +20692,7 @@ var CanvasElementsRenderer = (props) => {
20633
20692
  if (!transform) return;
20634
20693
  const hits = getPrimitivesUnderPoint(
20635
20694
  primitivesWithoutInteractionMetadata,
20636
- applyToPoint19(inverse6(transform), point2),
20695
+ applyToPoint20(inverse6(transform), point2),
20637
20696
  transform,
20638
20697
  selectedLayer
20639
20698
  ).filter(
@@ -20653,7 +20712,7 @@ var CanvasElementsRenderer = (props) => {
20653
20712
  });
20654
20713
  const hits = getPrimitivesUnderPoint(
20655
20714
  primitivesWithoutInteractionMetadata,
20656
- applyToPoint19(inverse6(transform), point2),
20715
+ applyToPoint20(inverse6(transform), point2),
20657
20716
  transform,
20658
20717
  selectedLayer,
20659
20718
  xRayNetIds.length > 0
@@ -20696,17 +20755,17 @@ var CanvasElementsRenderer = (props) => {
20696
20755
  if (!activeError) return [];
20697
20756
  return getRelatedIdsForError(activeError);
20698
20757
  }, [activeErrorId, elements]);
20699
- useEffect19(() => {
20758
+ useEffect20(() => {
20700
20759
  if (transform) {
20701
20760
  currentTransformRef.current = transform;
20702
20761
  }
20703
20762
  }, [transform]);
20704
- useEffect19(() => {
20763
+ useEffect20(() => {
20705
20764
  return () => {
20706
20765
  cancelTransformAnimation(zoomAnimationFrameRef.current);
20707
20766
  };
20708
20767
  }, []);
20709
- useEffect19(() => {
20768
+ useEffect20(() => {
20710
20769
  if (!props.width || !props.height || !props.setTransform || !focusedErrorId)
20711
20770
  return;
20712
20771
  const focusedError = findErrorElementById(elements, focusedErrorId);
@@ -20794,7 +20853,7 @@ var CanvasElementsRenderer = (props) => {
20794
20853
  },
20795
20854
  [connectivityMap]
20796
20855
  );
20797
- return /* @__PURE__ */ jsx25(
20856
+ return /* @__PURE__ */ jsxs19(
20798
20857
  MouseElementTracker,
20799
20858
  {
20800
20859
  elements: elementsToRender,
@@ -20802,87 +20861,97 @@ var CanvasElementsRenderer = (props) => {
20802
20861
  primitives: hoverPrimitives,
20803
20862
  selectedLayer,
20804
20863
  onMouseHoverOverPrimitives: onMouseOverPrimitives,
20805
- children: /* @__PURE__ */ jsx25(
20806
- EditPlacementOverlay,
20807
- {
20808
- disabled: !props.allowEditing,
20809
- transform,
20810
- soup: elements,
20811
- cancelPanDrag: props.cancelPanDrag,
20812
- onCreateEditEvent: props.onCreateEditEvent,
20813
- onModifyEditEvent: props.onModifyEditEvent,
20814
- children: /* @__PURE__ */ jsx25(
20815
- DimensionOverlay,
20816
- {
20817
- transform,
20818
- focusOnHover: props.focusOnHover,
20819
- primitives: primitivesWithoutInteractionMetadata,
20820
- onBoundsSelected: props.onBoundsSelected,
20821
- cancelPanDrag: props.cancelPanDrag,
20822
- children: /* @__PURE__ */ jsx25(
20823
- ToolbarOverlay,
20824
- {
20825
- elements,
20826
- getNetAtPoint,
20827
- xRayNetIds,
20828
- onXRayNetsChange: setXRayNetIds,
20829
- onContextMenuOpenChange: props.onContextMenuOpenChange,
20830
- getComponentAtPoint,
20831
- onViewSchematicComponent: props.onViewSchematicComponent,
20832
- children: /* @__PURE__ */ jsx25(ErrorOverlay, { transform, elements, children: /* @__PURE__ */ jsx25(RatsNestOverlay, { transform, soup: elements, children: /* @__PURE__ */ jsx25(
20833
- PcbGroupOverlay,
20834
- {
20835
- transform,
20836
- elements,
20837
- hoveredComponentIds,
20838
- children: /* @__PURE__ */ jsx25(
20839
- DebugGraphicsOverlay,
20840
- {
20841
- transform,
20842
- debugGraphics: props.debugGraphics,
20843
- children: /* @__PURE__ */ jsx25(
20844
- WarningGraphicsOverlay,
20845
- {
20846
- transform,
20847
- elements,
20848
- children: props.renderer === "canvas" ? /* @__PURE__ */ jsx25(
20849
- CanvasPrimitiveRenderer,
20850
- {
20851
- transform,
20852
- primitives,
20853
- elements: elementsToRender,
20854
- circuitJson: elements,
20855
- xRayElements,
20856
- width: props.width,
20857
- height: props.height,
20858
- grid: props.grid,
20859
- onRenderComplete: props.onRenderComplete
20860
- }
20861
- ) : /* @__PURE__ */ jsx25(
20862
- WebGpuElementsRenderer,
20863
- {
20864
- transform,
20865
- primitives,
20866
- elements,
20867
- xRayElements,
20868
- width: props.width,
20869
- height: props.height,
20870
- grid: props.grid,
20871
- onRenderComplete: props.onRenderComplete
20872
- }
20873
- )
20874
- }
20875
- )
20876
- }
20877
- )
20878
- }
20879
- ) }) })
20880
- }
20881
- )
20882
- }
20883
- )
20884
- }
20885
- )
20864
+ children: [
20865
+ /* @__PURE__ */ jsx26(
20866
+ FocusedPcbComponentOverlay,
20867
+ {
20868
+ focusRequest: props.focusedPcbComponent,
20869
+ elements,
20870
+ transform
20871
+ }
20872
+ ),
20873
+ /* @__PURE__ */ jsx26(
20874
+ EditPlacementOverlay,
20875
+ {
20876
+ disabled: !props.allowEditing,
20877
+ transform,
20878
+ soup: elements,
20879
+ cancelPanDrag: props.cancelPanDrag,
20880
+ onCreateEditEvent: props.onCreateEditEvent,
20881
+ onModifyEditEvent: props.onModifyEditEvent,
20882
+ children: /* @__PURE__ */ jsx26(
20883
+ DimensionOverlay,
20884
+ {
20885
+ transform,
20886
+ focusOnHover: props.focusOnHover,
20887
+ primitives: primitivesWithoutInteractionMetadata,
20888
+ onBoundsSelected: props.onBoundsSelected,
20889
+ cancelPanDrag: props.cancelPanDrag,
20890
+ children: /* @__PURE__ */ jsx26(
20891
+ ToolbarOverlay,
20892
+ {
20893
+ elements,
20894
+ getNetAtPoint,
20895
+ xRayNetIds,
20896
+ onXRayNetsChange: setXRayNetIds,
20897
+ onContextMenuOpenChange: props.onContextMenuOpenChange,
20898
+ getComponentAtPoint,
20899
+ onViewSchematicComponent: props.onViewSchematicComponent,
20900
+ children: /* @__PURE__ */ jsx26(ErrorOverlay, { transform, elements, children: /* @__PURE__ */ jsx26(RatsNestOverlay, { transform, soup: elements, children: /* @__PURE__ */ jsx26(
20901
+ PcbGroupOverlay,
20902
+ {
20903
+ transform,
20904
+ elements,
20905
+ hoveredComponentIds,
20906
+ children: /* @__PURE__ */ jsx26(
20907
+ DebugGraphicsOverlay,
20908
+ {
20909
+ transform,
20910
+ debugGraphics: props.debugGraphics,
20911
+ children: /* @__PURE__ */ jsx26(
20912
+ WarningGraphicsOverlay,
20913
+ {
20914
+ transform,
20915
+ elements,
20916
+ children: props.renderer === "canvas" ? /* @__PURE__ */ jsx26(
20917
+ CanvasPrimitiveRenderer,
20918
+ {
20919
+ transform,
20920
+ primitives,
20921
+ elements: elementsToRender,
20922
+ circuitJson: elements,
20923
+ xRayElements,
20924
+ width: props.width,
20925
+ height: props.height,
20926
+ grid: props.grid,
20927
+ onRenderComplete: props.onRenderComplete
20928
+ }
20929
+ ) : /* @__PURE__ */ jsx26(
20930
+ WebGpuElementsRenderer,
20931
+ {
20932
+ transform,
20933
+ primitives,
20934
+ elements,
20935
+ xRayElements,
20936
+ width: props.width,
20937
+ height: props.height,
20938
+ grid: props.grid,
20939
+ onRenderComplete: props.onRenderComplete
20940
+ }
20941
+ )
20942
+ }
20943
+ )
20944
+ }
20945
+ )
20946
+ }
20947
+ ) }) })
20948
+ }
20949
+ )
20950
+ }
20951
+ )
20952
+ }
20953
+ )
20954
+ ]
20886
20955
  }
20887
20956
  );
20888
20957
  };
@@ -21008,7 +21077,7 @@ function applyEditEvents({
21008
21077
  }
21009
21078
 
21010
21079
  // src/PCBViewer.tsx
21011
- import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
21080
+ import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
21012
21081
  var defaultTransform = compose9(translate14(400, 300), scale6(40, -40));
21013
21082
  var PCBViewer = ({
21014
21083
  circuitJson,
@@ -21024,10 +21093,12 @@ var PCBViewer = ({
21024
21093
  onViewSchematicComponent,
21025
21094
  focusOnHover = false,
21026
21095
  clickToInteractEnabled = false,
21027
- disablePcbGroups = false
21096
+ disablePcbGroups = false,
21097
+ controller
21028
21098
  }) => {
21029
21099
  const [activeRenderer, setActiveRenderer] = useState14(renderer);
21030
- useEffect20(() => setActiveRenderer(renderer), [renderer]);
21100
+ const [focusedPcbComponent, setFocusedPcbComponent] = useState14(null);
21101
+ useEffect21(() => setActiveRenderer(renderer), [renderer]);
21031
21102
  const renderingEngine = useMemo11(
21032
21103
  () => ({ renderer: activeRenderer, setRenderer: setActiveRenderer }),
21033
21104
  [activeRenderer]
@@ -21097,7 +21168,7 @@ var PCBViewer = ({
21097
21168
  setTransform(targetTransform);
21098
21169
  return;
21099
21170
  };
21100
- useEffect20(() => {
21171
+ useEffect21(() => {
21101
21172
  if (!refDimensions?.width) return;
21102
21173
  if (!circuitJson) return;
21103
21174
  if (circuitJson.length === 0) return;
@@ -21106,7 +21177,7 @@ var PCBViewer = ({
21106
21177
  initialRenderCompleted.current = true;
21107
21178
  }
21108
21179
  }, [circuitJson, refDimensions]);
21109
- useEffect20(() => {
21180
+ useEffect21(() => {
21110
21181
  if (initialRenderCompleted.current === true) {
21111
21182
  resetTransform();
21112
21183
  }
@@ -21122,6 +21193,29 @@ var PCBViewer = ({
21122
21193
  editEvents
21123
21194
  });
21124
21195
  }, [pcbElmsPreEdit, editEvents]);
21196
+ useEffect21(() => {
21197
+ const request = controller?.focusRequest;
21198
+ if (!request || !refDimensions.width || !height || !elements.length) return;
21199
+ const focus = getPcbComponentFocus(
21200
+ elements,
21201
+ request.pcbComponentId,
21202
+ refDimensions.width,
21203
+ height
21204
+ );
21205
+ if (focus) {
21206
+ cancelPanDrag();
21207
+ setTransform(focus.transform);
21208
+ setFocusedPcbComponent(request);
21209
+ }
21210
+ controller.onFocusRequestHandled(request);
21211
+ }, [
21212
+ controller,
21213
+ elements,
21214
+ refDimensions.width,
21215
+ height,
21216
+ cancelPanDrag,
21217
+ setTransform
21218
+ ]);
21125
21219
  const onCreateEditEvent = (event) => {
21126
21220
  setEditEvents([...editEvents, event]);
21127
21221
  onEditEventsChanged?.([...editEvents, event]);
@@ -21140,20 +21234,20 @@ var PCBViewer = ({
21140
21234
  }),
21141
21235
  [initialState, disablePcbGroups]
21142
21236
  );
21143
- return /* @__PURE__ */ jsx26(RenderingEngineContext.Provider, { value: renderingEngine, children: /* @__PURE__ */ jsxs19(
21237
+ return /* @__PURE__ */ jsx27(RenderingEngineContext.Provider, { value: renderingEngine, children: /* @__PURE__ */ jsxs20(
21144
21238
  "div",
21145
21239
  {
21146
21240
  ref: transformRef,
21147
21241
  style: { position: "relative" },
21148
21242
  onContextMenu: (event) => event.preventDefault(),
21149
21243
  children: [
21150
- /* @__PURE__ */ jsx26("div", { ref, children: /* @__PURE__ */ jsxs19(
21244
+ /* @__PURE__ */ jsx27("div", { ref, children: /* @__PURE__ */ jsxs20(
21151
21245
  ContextProviders,
21152
21246
  {
21153
21247
  initialState: mergedInitialState,
21154
21248
  disablePcbGroups,
21155
21249
  children: [
21156
- /* @__PURE__ */ jsx26(
21250
+ /* @__PURE__ */ jsx27(
21157
21251
  CanvasElementsRenderer,
21158
21252
  {
21159
21253
  renderer: activeRenderer,
@@ -21165,6 +21259,7 @@ var PCBViewer = ({
21165
21259
  focusOnHover,
21166
21260
  onBoundsSelected,
21167
21261
  onViewSchematicComponent,
21262
+ focusedPcbComponent,
21168
21263
  cancelPanDrag,
21169
21264
  onContextMenuOpenChange,
21170
21265
  onCreateEditEvent,
@@ -21183,11 +21278,11 @@ var PCBViewer = ({
21183
21278
  debugGraphics
21184
21279
  }
21185
21280
  ),
21186
- /* @__PURE__ */ jsx26(ToastContainer, {})
21281
+ /* @__PURE__ */ jsx27(ToastContainer, {})
21187
21282
  ]
21188
21283
  }
21189
21284
  ) }),
21190
- clickToInteractEnabled && !isInteractionEnabled && /* @__PURE__ */ jsx26(
21285
+ clickToInteractEnabled && !isInteractionEnabled && /* @__PURE__ */ jsx27(
21191
21286
  "div",
21192
21287
  {
21193
21288
  onClick: () => {
@@ -21224,7 +21319,7 @@ var PCBViewer = ({
21224
21319
  justifyContent: "center",
21225
21320
  touchAction: "pan-x pan-y pinch-zoom"
21226
21321
  },
21227
- children: /* @__PURE__ */ jsx26(
21322
+ children: /* @__PURE__ */ jsx27(
21228
21323
  "div",
21229
21324
  {
21230
21325
  style: {
@@ -21244,9 +21339,30 @@ var PCBViewer = ({
21244
21339
  }
21245
21340
  ) });
21246
21341
  };
21342
+
21343
+ // src/hooks/usePcbViewerController.ts
21344
+ import { useCallback as useCallback13, useMemo as useMemo12, useState as useState15 } from "react";
21345
+ var usePcbViewerController = () => {
21346
+ const [focusRequest, setFocusRequest] = useState15(null);
21347
+ const focusPcbComponent = useCallback13((pcbComponentId) => {
21348
+ setFocusRequest({ pcbComponentId });
21349
+ }, []);
21350
+ const onFocusRequestHandled = useCallback13(
21351
+ (request) => {
21352
+ setFocusRequest((current) => current === request ? null : current);
21353
+ },
21354
+ []
21355
+ );
21356
+ const controller = useMemo12(
21357
+ () => ({ focusRequest, onFocusRequestHandled }),
21358
+ [focusRequest, onFocusRequestHandled]
21359
+ );
21360
+ return { controller, focusPcbComponent };
21361
+ };
21247
21362
  export {
21248
21363
  CanvasElementsRenderer,
21249
- PCBViewer
21364
+ PCBViewer,
21365
+ usePcbViewerController
21250
21366
  };
21251
21367
  /*! Bundled license information:
21252
21368