@tscircuit/pcb-viewer 1.11.420 → 1.11.422

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,10 +80,32 @@ 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;
86
- /** Initial engine; users can switch via the context menu. Prop changes reset it.
107
+ /** Initial engine override; otherwise restores the saved user preference.
108
+ * Users can switch via the context menu. Prop changes reset it.
87
109
  * WebGPU runs in a worker and reports failures without changing engines. */
88
110
  renderer?: "webgpu" | "canvas";
89
111
  allowEditing?: boolean;
@@ -99,8 +121,10 @@ type Props = {
99
121
  clickToInteractEnabled?: boolean;
100
122
  debugGraphics?: GraphicsObject | null;
101
123
  disablePcbGroups?: boolean;
124
+ /** Connect the controller returned by usePcbViewerController. */
125
+ controller?: PcbViewerController;
102
126
  };
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;
127
+ 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
128
 
105
129
  interface CanvasElementsRendererProps {
106
130
  elements: AnyCircuitElement[];
@@ -113,6 +137,9 @@ interface CanvasElementsRendererProps {
113
137
  grid?: GridConfig;
114
138
  allowEditing: boolean;
115
139
  focusOnHover?: boolean;
140
+ focusedPcbComponent?: {
141
+ pcbComponentId: string;
142
+ } | null;
116
143
  onViewSchematicComponent?: (event: ViewSchematicComponentEvent) => void;
117
144
  onBoundsSelected?: (bounds: BoundsSelection) => void;
118
145
  onContextMenuOpenChange?: (open: boolean) => void;
@@ -123,4 +150,4 @@ interface CanvasElementsRendererProps {
123
150
  }
124
151
  declare const CanvasElementsRenderer: (props: CanvasElementsRendererProps) => react_jsx_runtime.JSX.Element;
125
152
 
126
- export { CanvasElementsRenderer, PCBViewer, type ViewSchematicComponentEvent };
153
+ export { CanvasElementsRenderer, PCBViewer, type PcbViewerController, type ViewSchematicComponentEvent, usePcbViewerController };
package/dist/index.js CHANGED
@@ -9596,6 +9596,7 @@ import { useContext as useContext2 } from "react";
9596
9596
  // src/hooks/useLocalStorage.ts
9597
9597
  import { useCallback } from "react";
9598
9598
  var STORAGE_KEYS = {
9599
+ RENDERING_ENGINE: "pcb_viewer_rendering_engine",
9599
9600
  COPPER_POUR_OPACITY: "pcb_viewer_copper_pour_opacity",
9600
9601
  HIDDEN_LAYER_OPACITY: "pcb_viewer_hidden_layer_opacity",
9601
9602
  IS_SHOWING_PCB_GROUPS: "pcb_viewer_is_showing_pcb_groups",
@@ -9827,7 +9828,7 @@ var ToastContainer = () => {
9827
9828
  };
9828
9829
 
9829
9830
  // src/PCBViewer.tsx
9830
- import { useCallback as useCallback12, useEffect as useEffect20, useMemo as useMemo11, useRef as useRef17, useState as useState14 } from "react";
9831
+ import { useCallback as useCallback12, useEffect as useEffect21, useMemo as useMemo11, useRef as useRef17, useState as useState14 } from "react";
9831
9832
 
9832
9833
  // node_modules/react-use/esm/useMountedState.js
9833
9834
  import { useCallback as useCallback2, useEffect as useEffect2, useRef } from "react";
@@ -11665,8 +11666,8 @@ var animateTransform = ({
11665
11666
  };
11666
11667
 
11667
11668
  // 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";
11669
+ import { useCallback as useCallback11, useEffect as useEffect20, useMemo as useMemo10, useRef as useRef16, useState as useState13 } from "react";
11670
+ import { applyToPoint as applyToPoint20, inverse as inverse6 } from "transformation-matrix";
11670
11671
 
11671
11672
  // src/lib/get-trace-overlay-text.ts
11672
11673
  var getNonEmptyName = (name) => name?.trim() || null;
@@ -18585,7 +18586,7 @@ import { css as css3 } from "@emotion/css";
18585
18586
  // package.json
18586
18587
  var package_default = {
18587
18588
  name: "@tscircuit/pcb-viewer",
18588
- version: "1.11.419",
18589
+ version: "1.11.421",
18589
18590
  main: "dist/index.js",
18590
18591
  type: "module",
18591
18592
  repository: {
@@ -18602,6 +18603,7 @@ var package_default = {
18602
18603
  cosmos: "cosmos",
18603
18604
  "cosmos-export": "cosmos-export",
18604
18605
  "test:x-ray-net": "bun run build && bun tests/x-ray-net/run.ts",
18606
+ "test:component-focus": "bun run build && bun tests/component-focus/run.ts",
18605
18607
  "test:measurement-context-menu": "bun run build && bun tests/measurement-context-menu/run.ts",
18606
18608
  "test:copper-pour-opacity": "bun run build && bun tests/copper-pour-opacity/run.ts",
18607
18609
  "test:webgpu": "bun run build && bun tests/webgpu/run.ts"
@@ -20574,8 +20576,66 @@ var WarningGraphicsOverlay = ({
20574
20576
  );
20575
20577
  };
20576
20578
 
20577
- // src/components/CanvasElementsRenderer.tsx
20579
+ // src/components/FocusedPcbComponentOverlay.tsx
20580
+ import { useEffect as useEffect19 } from "react";
20581
+ import { applyToPoint as applyToPoint19 } from "transformation-matrix";
20582
+
20583
+ // src/lib/get-pcb-component-focus.ts
20584
+ var getPcbComponentFocus = (elements, pcbComponentId, width, height) => {
20585
+ if (width <= 0 || height <= 0) return;
20586
+ const component = elements.find(
20587
+ (element) => element.type === "pcb_component" && element.pcb_component_id === pcbComponentId
20588
+ );
20589
+ if (component?.type !== "pcb_component") return;
20590
+ const bounds = getPcbElementBounds(component);
20591
+ if (!bounds) return;
20592
+ return {
20593
+ component,
20594
+ bounds,
20595
+ transform: createTransformForBounds({ bounds, width, height })
20596
+ };
20597
+ };
20598
+
20599
+ // src/components/FocusedPcbComponentOverlay.tsx
20578
20600
  import { jsx as jsx25 } from "react/jsx-runtime";
20601
+ var FocusedPcbComponentOverlay = ({
20602
+ focusRequest,
20603
+ elements,
20604
+ transform
20605
+ }) => {
20606
+ const componentId = focusRequest?.pcbComponentId;
20607
+ const selectLayer = useGlobalStore((state) => state.selectLayer);
20608
+ const focus = componentId ? getPcbComponentFocus(elements, componentId, 1, 1) : void 0;
20609
+ const layer = focus?.component.layer;
20610
+ useEffect19(() => {
20611
+ if (layer) selectLayer(layer);
20612
+ }, [focusRequest, layer, selectLayer]);
20613
+ if (!focus || !transform) return null;
20614
+ const { bounds } = focus;
20615
+ const a = applyToPoint19(transform, { x: bounds.minX, y: bounds.minY });
20616
+ const b = applyToPoint19(transform, { x: bounds.maxX, y: bounds.maxY });
20617
+ return /* @__PURE__ */ jsx25(
20618
+ "div",
20619
+ {
20620
+ "data-focused-pcb-component-id": componentId,
20621
+ style: {
20622
+ position: "absolute",
20623
+ left: Math.min(a.x, b.x) - 4,
20624
+ top: Math.min(a.y, b.y) - 4,
20625
+ width: Math.abs(b.x - a.x) + 8,
20626
+ height: Math.abs(b.y - a.y) + 8,
20627
+ border: "2px solid #3399ff",
20628
+ borderRadius: 4,
20629
+ boxSizing: "border-box",
20630
+ pointerEvents: "none",
20631
+ zIndex: zIndexMap.elementOverlay
20632
+ }
20633
+ }
20634
+ );
20635
+ };
20636
+
20637
+ // src/components/CanvasElementsRenderer.tsx
20638
+ import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
20579
20639
  var CanvasElementsRenderer = (props) => {
20580
20640
  const { transform, elements } = props;
20581
20641
  const {
@@ -20605,8 +20665,8 @@ var CanvasElementsRenderer = (props) => {
20605
20665
  [elements]
20606
20666
  );
20607
20667
  const [xRayNetIds, setXRayNetIds] = useState13([]);
20608
- useEffect19(() => setXRayNetIds([]), [connectivityMap]);
20609
- useEffect19(() => {
20668
+ useEffect20(() => setXRayNetIds([]), [connectivityMap]);
20669
+ useEffect20(() => {
20610
20670
  if (xRayNetIds.length === 0) return;
20611
20671
  const onKeyDown = (event) => {
20612
20672
  if (event.key === "Escape") setXRayNetIds([]);
@@ -20633,7 +20693,7 @@ var CanvasElementsRenderer = (props) => {
20633
20693
  if (!transform) return;
20634
20694
  const hits = getPrimitivesUnderPoint(
20635
20695
  primitivesWithoutInteractionMetadata,
20636
- applyToPoint19(inverse6(transform), point2),
20696
+ applyToPoint20(inverse6(transform), point2),
20637
20697
  transform,
20638
20698
  selectedLayer
20639
20699
  ).filter(
@@ -20653,7 +20713,7 @@ var CanvasElementsRenderer = (props) => {
20653
20713
  });
20654
20714
  const hits = getPrimitivesUnderPoint(
20655
20715
  primitivesWithoutInteractionMetadata,
20656
- applyToPoint19(inverse6(transform), point2),
20716
+ applyToPoint20(inverse6(transform), point2),
20657
20717
  transform,
20658
20718
  selectedLayer,
20659
20719
  xRayNetIds.length > 0
@@ -20696,17 +20756,17 @@ var CanvasElementsRenderer = (props) => {
20696
20756
  if (!activeError) return [];
20697
20757
  return getRelatedIdsForError(activeError);
20698
20758
  }, [activeErrorId, elements]);
20699
- useEffect19(() => {
20759
+ useEffect20(() => {
20700
20760
  if (transform) {
20701
20761
  currentTransformRef.current = transform;
20702
20762
  }
20703
20763
  }, [transform]);
20704
- useEffect19(() => {
20764
+ useEffect20(() => {
20705
20765
  return () => {
20706
20766
  cancelTransformAnimation(zoomAnimationFrameRef.current);
20707
20767
  };
20708
20768
  }, []);
20709
- useEffect19(() => {
20769
+ useEffect20(() => {
20710
20770
  if (!props.width || !props.height || !props.setTransform || !focusedErrorId)
20711
20771
  return;
20712
20772
  const focusedError = findErrorElementById(elements, focusedErrorId);
@@ -20794,7 +20854,7 @@ var CanvasElementsRenderer = (props) => {
20794
20854
  },
20795
20855
  [connectivityMap]
20796
20856
  );
20797
- return /* @__PURE__ */ jsx25(
20857
+ return /* @__PURE__ */ jsxs19(
20798
20858
  MouseElementTracker,
20799
20859
  {
20800
20860
  elements: elementsToRender,
@@ -20802,87 +20862,97 @@ var CanvasElementsRenderer = (props) => {
20802
20862
  primitives: hoverPrimitives,
20803
20863
  selectedLayer,
20804
20864
  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
- )
20865
+ children: [
20866
+ /* @__PURE__ */ jsx26(
20867
+ FocusedPcbComponentOverlay,
20868
+ {
20869
+ focusRequest: props.focusedPcbComponent,
20870
+ elements,
20871
+ transform
20872
+ }
20873
+ ),
20874
+ /* @__PURE__ */ jsx26(
20875
+ EditPlacementOverlay,
20876
+ {
20877
+ disabled: !props.allowEditing,
20878
+ transform,
20879
+ soup: elements,
20880
+ cancelPanDrag: props.cancelPanDrag,
20881
+ onCreateEditEvent: props.onCreateEditEvent,
20882
+ onModifyEditEvent: props.onModifyEditEvent,
20883
+ children: /* @__PURE__ */ jsx26(
20884
+ DimensionOverlay,
20885
+ {
20886
+ transform,
20887
+ focusOnHover: props.focusOnHover,
20888
+ primitives: primitivesWithoutInteractionMetadata,
20889
+ onBoundsSelected: props.onBoundsSelected,
20890
+ cancelPanDrag: props.cancelPanDrag,
20891
+ children: /* @__PURE__ */ jsx26(
20892
+ ToolbarOverlay,
20893
+ {
20894
+ elements,
20895
+ getNetAtPoint,
20896
+ xRayNetIds,
20897
+ onXRayNetsChange: setXRayNetIds,
20898
+ onContextMenuOpenChange: props.onContextMenuOpenChange,
20899
+ getComponentAtPoint,
20900
+ onViewSchematicComponent: props.onViewSchematicComponent,
20901
+ children: /* @__PURE__ */ jsx26(ErrorOverlay, { transform, elements, children: /* @__PURE__ */ jsx26(RatsNestOverlay, { transform, soup: elements, children: /* @__PURE__ */ jsx26(
20902
+ PcbGroupOverlay,
20903
+ {
20904
+ transform,
20905
+ elements,
20906
+ hoveredComponentIds,
20907
+ children: /* @__PURE__ */ jsx26(
20908
+ DebugGraphicsOverlay,
20909
+ {
20910
+ transform,
20911
+ debugGraphics: props.debugGraphics,
20912
+ children: /* @__PURE__ */ jsx26(
20913
+ WarningGraphicsOverlay,
20914
+ {
20915
+ transform,
20916
+ elements,
20917
+ children: props.renderer === "canvas" ? /* @__PURE__ */ jsx26(
20918
+ CanvasPrimitiveRenderer,
20919
+ {
20920
+ transform,
20921
+ primitives,
20922
+ elements: elementsToRender,
20923
+ circuitJson: elements,
20924
+ xRayElements,
20925
+ width: props.width,
20926
+ height: props.height,
20927
+ grid: props.grid,
20928
+ onRenderComplete: props.onRenderComplete
20929
+ }
20930
+ ) : /* @__PURE__ */ jsx26(
20931
+ WebGpuElementsRenderer,
20932
+ {
20933
+ transform,
20934
+ primitives,
20935
+ elements,
20936
+ xRayElements,
20937
+ width: props.width,
20938
+ height: props.height,
20939
+ grid: props.grid,
20940
+ onRenderComplete: props.onRenderComplete
20941
+ }
20942
+ )
20943
+ }
20944
+ )
20945
+ }
20946
+ )
20947
+ }
20948
+ ) }) })
20949
+ }
20950
+ )
20951
+ }
20952
+ )
20953
+ }
20954
+ )
20955
+ ]
20886
20956
  }
20887
20957
  );
20888
20958
  };
@@ -21008,13 +21078,17 @@ function applyEditEvents({
21008
21078
  }
21009
21079
 
21010
21080
  // src/PCBViewer.tsx
21011
- import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
21081
+ import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
21012
21082
  var defaultTransform = compose9(translate14(400, 300), scale6(40, -40));
21083
+ var getPreferredRenderer = () => {
21084
+ const stored = getStoredString(STORAGE_KEYS.RENDERING_ENGINE, "webgpu");
21085
+ return stored === "canvas" || stored === "webgpu" ? stored : "webgpu";
21086
+ };
21013
21087
  var PCBViewer = ({
21014
21088
  circuitJson,
21015
21089
  debugGraphics,
21016
21090
  height = 600,
21017
- renderer = "webgpu",
21091
+ renderer,
21018
21092
  initialState,
21019
21093
  allowEditing = true,
21020
21094
  editEvents: editEventsProp,
@@ -21024,13 +21098,24 @@ var PCBViewer = ({
21024
21098
  onViewSchematicComponent,
21025
21099
  focusOnHover = false,
21026
21100
  clickToInteractEnabled = false,
21027
- disablePcbGroups = false
21101
+ disablePcbGroups = false,
21102
+ controller
21028
21103
  }) => {
21029
- const [activeRenderer, setActiveRenderer] = useState14(renderer);
21030
- useEffect20(() => setActiveRenderer(renderer), [renderer]);
21104
+ const [activeRenderer, setActiveRenderer] = useState14(
21105
+ () => renderer ?? getPreferredRenderer()
21106
+ );
21107
+ const [focusedPcbComponent, setFocusedPcbComponent] = useState14(null);
21108
+ useEffect21(
21109
+ () => setActiveRenderer(renderer ?? getPreferredRenderer()),
21110
+ [renderer]
21111
+ );
21112
+ const setPreferredRenderer = useCallback12((engine) => {
21113
+ setActiveRenderer(engine);
21114
+ setStoredString(STORAGE_KEYS.RENDERING_ENGINE, engine);
21115
+ }, []);
21031
21116
  const renderingEngine = useMemo11(
21032
- () => ({ renderer: activeRenderer, setRenderer: setActiveRenderer }),
21033
- [activeRenderer]
21117
+ () => ({ renderer: activeRenderer, setRenderer: setPreferredRenderer }),
21118
+ [activeRenderer, setPreferredRenderer]
21034
21119
  );
21035
21120
  const [isInteractionEnabled, setIsInteractionEnabled] = useState14(
21036
21121
  !clickToInteractEnabled
@@ -21097,7 +21182,7 @@ var PCBViewer = ({
21097
21182
  setTransform(targetTransform);
21098
21183
  return;
21099
21184
  };
21100
- useEffect20(() => {
21185
+ useEffect21(() => {
21101
21186
  if (!refDimensions?.width) return;
21102
21187
  if (!circuitJson) return;
21103
21188
  if (circuitJson.length === 0) return;
@@ -21106,7 +21191,7 @@ var PCBViewer = ({
21106
21191
  initialRenderCompleted.current = true;
21107
21192
  }
21108
21193
  }, [circuitJson, refDimensions]);
21109
- useEffect20(() => {
21194
+ useEffect21(() => {
21110
21195
  if (initialRenderCompleted.current === true) {
21111
21196
  resetTransform();
21112
21197
  }
@@ -21122,6 +21207,29 @@ var PCBViewer = ({
21122
21207
  editEvents
21123
21208
  });
21124
21209
  }, [pcbElmsPreEdit, editEvents]);
21210
+ useEffect21(() => {
21211
+ const request = controller?.focusRequest;
21212
+ if (!request || !refDimensions.width || !height || !elements.length) return;
21213
+ const focus = getPcbComponentFocus(
21214
+ elements,
21215
+ request.pcbComponentId,
21216
+ refDimensions.width,
21217
+ height
21218
+ );
21219
+ if (focus) {
21220
+ cancelPanDrag();
21221
+ setTransform(focus.transform);
21222
+ setFocusedPcbComponent(request);
21223
+ }
21224
+ controller.onFocusRequestHandled(request);
21225
+ }, [
21226
+ controller,
21227
+ elements,
21228
+ refDimensions.width,
21229
+ height,
21230
+ cancelPanDrag,
21231
+ setTransform
21232
+ ]);
21125
21233
  const onCreateEditEvent = (event) => {
21126
21234
  setEditEvents([...editEvents, event]);
21127
21235
  onEditEventsChanged?.([...editEvents, event]);
@@ -21140,20 +21248,20 @@ var PCBViewer = ({
21140
21248
  }),
21141
21249
  [initialState, disablePcbGroups]
21142
21250
  );
21143
- return /* @__PURE__ */ jsx26(RenderingEngineContext.Provider, { value: renderingEngine, children: /* @__PURE__ */ jsxs19(
21251
+ return /* @__PURE__ */ jsx27(RenderingEngineContext.Provider, { value: renderingEngine, children: /* @__PURE__ */ jsxs20(
21144
21252
  "div",
21145
21253
  {
21146
21254
  ref: transformRef,
21147
21255
  style: { position: "relative" },
21148
21256
  onContextMenu: (event) => event.preventDefault(),
21149
21257
  children: [
21150
- /* @__PURE__ */ jsx26("div", { ref, children: /* @__PURE__ */ jsxs19(
21258
+ /* @__PURE__ */ jsx27("div", { ref, children: /* @__PURE__ */ jsxs20(
21151
21259
  ContextProviders,
21152
21260
  {
21153
21261
  initialState: mergedInitialState,
21154
21262
  disablePcbGroups,
21155
21263
  children: [
21156
- /* @__PURE__ */ jsx26(
21264
+ /* @__PURE__ */ jsx27(
21157
21265
  CanvasElementsRenderer,
21158
21266
  {
21159
21267
  renderer: activeRenderer,
@@ -21165,6 +21273,7 @@ var PCBViewer = ({
21165
21273
  focusOnHover,
21166
21274
  onBoundsSelected,
21167
21275
  onViewSchematicComponent,
21276
+ focusedPcbComponent,
21168
21277
  cancelPanDrag,
21169
21278
  onContextMenuOpenChange,
21170
21279
  onCreateEditEvent,
@@ -21183,11 +21292,11 @@ var PCBViewer = ({
21183
21292
  debugGraphics
21184
21293
  }
21185
21294
  ),
21186
- /* @__PURE__ */ jsx26(ToastContainer, {})
21295
+ /* @__PURE__ */ jsx27(ToastContainer, {})
21187
21296
  ]
21188
21297
  }
21189
21298
  ) }),
21190
- clickToInteractEnabled && !isInteractionEnabled && /* @__PURE__ */ jsx26(
21299
+ clickToInteractEnabled && !isInteractionEnabled && /* @__PURE__ */ jsx27(
21191
21300
  "div",
21192
21301
  {
21193
21302
  onClick: () => {
@@ -21224,7 +21333,7 @@ var PCBViewer = ({
21224
21333
  justifyContent: "center",
21225
21334
  touchAction: "pan-x pan-y pinch-zoom"
21226
21335
  },
21227
- children: /* @__PURE__ */ jsx26(
21336
+ children: /* @__PURE__ */ jsx27(
21228
21337
  "div",
21229
21338
  {
21230
21339
  style: {
@@ -21244,9 +21353,30 @@ var PCBViewer = ({
21244
21353
  }
21245
21354
  ) });
21246
21355
  };
21356
+
21357
+ // src/hooks/usePcbViewerController.ts
21358
+ import { useCallback as useCallback13, useMemo as useMemo12, useState as useState15 } from "react";
21359
+ var usePcbViewerController = () => {
21360
+ const [focusRequest, setFocusRequest] = useState15(null);
21361
+ const focusPcbComponent = useCallback13((pcbComponentId) => {
21362
+ setFocusRequest({ pcbComponentId });
21363
+ }, []);
21364
+ const onFocusRequestHandled = useCallback13(
21365
+ (request) => {
21366
+ setFocusRequest((current) => current === request ? null : current);
21367
+ },
21368
+ []
21369
+ );
21370
+ const controller = useMemo12(
21371
+ () => ({ focusRequest, onFocusRequestHandled }),
21372
+ [focusRequest, onFocusRequestHandled]
21373
+ );
21374
+ return { controller, focusPcbComponent };
21375
+ };
21247
21376
  export {
21248
21377
  CanvasElementsRenderer,
21249
- PCBViewer
21378
+ PCBViewer,
21379
+ usePcbViewerController
21250
21380
  };
21251
21381
  /*! Bundled license information:
21252
21382