@tscircuit/pcb-viewer 1.11.365 → 1.11.367

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
@@ -6679,6 +6679,7 @@ var createStore = (initialState = {}, disablePcbGroups = false) => createZustand
6679
6679
  DEFAULT_PCB_GROUP_VIEW_MODE
6680
6680
  ),
6681
6681
  hovered_error_id: null,
6682
+ focused_error_id: null,
6682
6683
  ...initialState,
6683
6684
  selectLayer: (layer) => set({ selected_layer: layer }),
6684
6685
  setEditMode: (mode) => set({
@@ -6735,7 +6736,8 @@ var createStore = (initialState = {}, disablePcbGroups = false) => createZustand
6735
6736
  setStoredString(STORAGE_KEYS.PCB_GROUP_VIEW_MODE, mode);
6736
6737
  set({ pcb_group_view_mode: mode });
6737
6738
  },
6738
- setHoveredErrorId: (errorId) => set({ hovered_error_id: errorId })
6739
+ setHoveredErrorId: (errorId) => set({ hovered_error_id: errorId }),
6740
+ setFocusedErrorId: (errorId) => set({ focused_error_id: errorId })
6739
6741
  })
6740
6742
  );
6741
6743
  var useGlobalStore = (s) => {
@@ -6788,7 +6790,7 @@ var ToastContainer = () => {
6788
6790
  };
6789
6791
 
6790
6792
  // src/PCBViewer.tsx
6791
- import { useCallback as useCallback11, useEffect as useEffect17, useMemo as useMemo9, useRef as useRef14, useState as useState13 } from "react";
6793
+ import { useCallback as useCallback12, useEffect as useEffect19, useMemo as useMemo9, useRef as useRef16, useState as useState13 } from "react";
6792
6794
 
6793
6795
  // node_modules/react-use/esm/useMountedState.js
6794
6796
  import { useCallback as useCallback2, useEffect as useEffect2, useRef } from "react";
@@ -6927,7 +6929,7 @@ var useMeasure_default = isBrowser && typeof window.ResizeObserver !== "undefine
6927
6929
  };
6928
6930
 
6929
6931
  // src/PCBViewer.tsx
6930
- import { compose as compose7, scale as scale5, translate as translate11 } from "transformation-matrix";
6932
+ import { compose as compose8, scale as scale6, translate as translate12 } from "transformation-matrix";
6931
6933
 
6932
6934
  // node_modules/use-mouse-matrix-transform/dist/chunk-TGYMZPTI.js
6933
6935
  import { compose, translate as translate5, scale } from "transformation-matrix";
@@ -7406,15 +7408,19 @@ function addInteractionMetadataToPrimitives({
7406
7408
  const newPrimitives = [];
7407
7409
  for (const primitive of primitivesWithoutInteractionMetadata) {
7408
7410
  const newPrimitive = { ...primitive };
7411
+ const primitiveElement = primitive._element;
7412
+ const parentComponent = primitive._parent_pcb_component;
7409
7413
  if (primitive?.layer === "drill") {
7410
7414
  newPrimitive.is_in_highlighted_net = false;
7411
7415
  newPrimitive.is_mouse_over = false;
7412
7416
  } else if (drawingObjectIdsWithMouseOver.has(primitive._pcb_drawing_object_id)) {
7413
7417
  newPrimitive.is_mouse_over = true;
7414
- } else if (primitive._element && ("pcb_trace_id" in primitive._element && primitiveIdsInMousedOverNet.includes(
7415
- primitive._element.pcb_trace_id
7416
- ) || "pcb_port_id" in primitive._element && primitiveIdsInMousedOverNet.includes(
7417
- primitive._element.pcb_port_id
7418
+ } else if (primitiveElement && ("pcb_trace_id" in primitiveElement && primitiveIdsInMousedOverNet.includes(primitiveElement.pcb_trace_id) || "pcb_port_id" in primitiveElement && primitiveIdsInMousedOverNet.includes(
7419
+ primitiveElement.pcb_port_id
7420
+ ) || "pcb_via_id" in primitiveElement && primitiveIdsInMousedOverNet.includes(primitiveElement.pcb_via_id) || "pcb_component_id" in primitiveElement && primitiveIdsInMousedOverNet.includes(
7421
+ primitiveElement.pcb_component_id
7422
+ ) || parentComponent && "pcb_component_id" in parentComponent && primitiveIdsInMousedOverNet.includes(
7423
+ parentComponent.pcb_component_id
7418
7424
  ))) {
7419
7425
  newPrimitive.is_in_highlighted_net = true;
7420
7426
  } else {
@@ -7426,8 +7432,399 @@ function addInteractionMetadataToPrimitives({
7426
7432
  return newPrimitives;
7427
7433
  }
7428
7434
 
7435
+ // src/lib/util/get-error-id.ts
7436
+ var getErrorId = (error, index) => {
7437
+ const explicitIdKeys = [
7438
+ "pcb_trace_error_id",
7439
+ "pcb_via_clearance_error_id",
7440
+ "pcb_component_outside_board_error_id",
7441
+ "source_failed_to_create_component_error_id",
7442
+ "pcb_error_id"
7443
+ ];
7444
+ for (const key of explicitIdKeys) {
7445
+ if (typeof error?.[key] === "string" && error[key].length > 0) {
7446
+ return error[key];
7447
+ }
7448
+ }
7449
+ for (const [key, value] of Object.entries(error ?? {})) {
7450
+ if (key.endsWith("_error_id") && typeof value === "string" && value.length > 0) {
7451
+ return value;
7452
+ }
7453
+ }
7454
+ return `error_${index}_${error?.error_type}_${error?.message?.slice(0, 20)}`;
7455
+ };
7456
+ var findErrorElementById = (elements, errorId) => {
7457
+ if (!errorId) return null;
7458
+ return elements.find((element, index) => {
7459
+ if (!element.type?.includes("error")) return false;
7460
+ return getErrorId(element, index) === errorId;
7461
+ }) ?? null;
7462
+ };
7463
+
7464
+ // src/lib/util/error-preview.ts
7465
+ import {
7466
+ getBoundsFromPoints
7467
+ } from "@tscircuit/math-utils";
7468
+ import { compose as compose3, scale as scale3, translate as translate7 } from "transformation-matrix";
7469
+ var buildErrorPreviewElementIndexes = (elements) => {
7470
+ const tracesById = /* @__PURE__ */ new Map();
7471
+ const portsById = /* @__PURE__ */ new Map();
7472
+ const viasById = /* @__PURE__ */ new Map();
7473
+ const componentsById = /* @__PURE__ */ new Map();
7474
+ const boardsById = /* @__PURE__ */ new Map();
7475
+ for (const element of elements) {
7476
+ if (element.type === "pcb_trace" && element.pcb_trace_id) {
7477
+ tracesById.set(element.pcb_trace_id, element);
7478
+ } else if (element.type === "pcb_port" && element.pcb_port_id) {
7479
+ portsById.set(element.pcb_port_id, element);
7480
+ } else if (element.type === "pcb_via" && element.pcb_via_id) {
7481
+ viasById.set(element.pcb_via_id, element);
7482
+ } else if (element.type === "pcb_component" && element.pcb_component_id) {
7483
+ componentsById.set(element.pcb_component_id, element);
7484
+ } else if (element.type === "pcb_board" && element.pcb_board_id) {
7485
+ boardsById.set(element.pcb_board_id, element);
7486
+ }
7487
+ }
7488
+ return {
7489
+ tracesById,
7490
+ portsById,
7491
+ viasById,
7492
+ componentsById,
7493
+ boardsById
7494
+ };
7495
+ };
7496
+ var mergeBounds2 = (existing, next) => {
7497
+ if (!next) return existing;
7498
+ if (!existing) return next;
7499
+ return {
7500
+ minX: Math.min(existing.minX, next.minX),
7501
+ maxX: Math.max(existing.maxX, next.maxX),
7502
+ minY: Math.min(existing.minY, next.minY),
7503
+ maxY: Math.max(existing.maxY, next.maxY)
7504
+ };
7505
+ };
7506
+ var createBoundsFromCenter = (center, radius = 0.45) => ({
7507
+ minX: center.x - radius,
7508
+ maxX: center.x + radius,
7509
+ minY: center.y - radius,
7510
+ maxY: center.y + radius
7511
+ });
7512
+ var createBoundsFromRoute = (route) => {
7513
+ if (route.length === 0) return null;
7514
+ let bounds = null;
7515
+ for (const point of route) {
7516
+ const radius = Math.max((point.width ?? 0.2) / 2, 0.3);
7517
+ bounds = mergeBounds2(bounds, createBoundsFromCenter(point, radius));
7518
+ }
7519
+ return bounds;
7520
+ };
7521
+ var createBoundsFromComponent = (component) => {
7522
+ if (!component?.center) return null;
7523
+ const width = Math.max(component.width ?? 0, 1.2);
7524
+ const height = Math.max(component.height ?? 0, 1.2);
7525
+ return {
7526
+ minX: component.center.x - width / 2,
7527
+ maxX: component.center.x + width / 2,
7528
+ minY: component.center.y - height / 2,
7529
+ maxY: component.center.y + height / 2
7530
+ };
7531
+ };
7532
+ var createBoardBounds = (board) => {
7533
+ if (!board?.center || typeof board.width !== "number" || typeof board.height !== "number") {
7534
+ return null;
7535
+ }
7536
+ return {
7537
+ minX: board.center.x - board.width / 2,
7538
+ maxX: board.center.x + board.width / 2,
7539
+ minY: board.center.y - board.height / 2,
7540
+ maxY: board.center.y + board.height / 2
7541
+ };
7542
+ };
7543
+ var expandBounds = (bounds, padding) => ({
7544
+ minX: bounds.minX - padding,
7545
+ maxX: bounds.maxX + padding,
7546
+ minY: bounds.minY - padding,
7547
+ maxY: bounds.maxY + padding
7548
+ });
7549
+ var createBoundsFromPoints = (points, radius = 0.35) => {
7550
+ const bounds = getBoundsFromPoints(points);
7551
+ if (!bounds) return null;
7552
+ return expandBounds(bounds, radius);
7553
+ };
7554
+ var getErrorFocusCenter = ({
7555
+ error,
7556
+ tracesById,
7557
+ portsById,
7558
+ viasById,
7559
+ componentsById
7560
+ }) => {
7561
+ if (error.center) return error.center;
7562
+ if (error.pcb_center) return error.pcb_center;
7563
+ if (error.component_center) return error.component_center;
7564
+ if (error.pcb_via_ids?.length) {
7565
+ const vias = error.pcb_via_ids.map((pcbViaId) => viasById.get(pcbViaId)).filter(Boolean);
7566
+ if (vias.length > 0) {
7567
+ return {
7568
+ x: vias.reduce((sum, via) => sum + via.x, 0) / vias.length,
7569
+ y: vias.reduce((sum, via) => sum + via.y, 0) / vias.length
7570
+ };
7571
+ }
7572
+ }
7573
+ if (error.pcb_port_ids?.length) {
7574
+ const ports = error.pcb_port_ids.map((pcbPortId) => portsById.get(pcbPortId)).filter(Boolean);
7575
+ if (ports.length > 0) {
7576
+ return {
7577
+ x: ports.reduce((sum, port) => sum + port.x, 0) / ports.length,
7578
+ y: ports.reduce((sum, port) => sum + port.y, 0) / ports.length
7579
+ };
7580
+ }
7581
+ }
7582
+ if (error.pcb_trace_id) {
7583
+ const trace = tracesById.get(error.pcb_trace_id);
7584
+ if (trace?.route?.length) {
7585
+ const middleIndex = Math.floor(trace.route.length / 2);
7586
+ return {
7587
+ x: trace.route[middleIndex].x,
7588
+ y: trace.route[middleIndex].y
7589
+ };
7590
+ }
7591
+ }
7592
+ if (error.pcb_component_id) {
7593
+ return componentsById.get(error.pcb_component_id)?.center ?? null;
7594
+ }
7595
+ return null;
7596
+ };
7597
+ var getErrorFocusPoint = ({
7598
+ error,
7599
+ indexes
7600
+ }) => getErrorFocusCenter({
7601
+ error,
7602
+ ...indexes
7603
+ });
7604
+ var getComponentBoundaryViolationBounds = ({
7605
+ error,
7606
+ boardsById
7607
+ }) => {
7608
+ if (!error.component_bounds || !error.pcb_board_id) return null;
7609
+ const boardBounds = createBoardBounds(boardsById.get(error.pcb_board_id));
7610
+ if (!boardBounds) return null;
7611
+ const componentBounds = error.component_bounds;
7612
+ const violationPoints = [];
7613
+ if (componentBounds.min_x < boardBounds.minX) {
7614
+ violationPoints.push({
7615
+ x: boardBounds.minX,
7616
+ y: Math.max(componentBounds.min_y, boardBounds.minY)
7617
+ });
7618
+ violationPoints.push({
7619
+ x: boardBounds.minX,
7620
+ y: Math.min(componentBounds.max_y, boardBounds.maxY)
7621
+ });
7622
+ }
7623
+ if (componentBounds.max_x > boardBounds.maxX) {
7624
+ violationPoints.push({
7625
+ x: boardBounds.maxX,
7626
+ y: Math.max(componentBounds.min_y, boardBounds.minY)
7627
+ });
7628
+ violationPoints.push({
7629
+ x: boardBounds.maxX,
7630
+ y: Math.min(componentBounds.max_y, boardBounds.maxY)
7631
+ });
7632
+ }
7633
+ if (componentBounds.min_y < boardBounds.minY) {
7634
+ violationPoints.push({
7635
+ x: Math.max(componentBounds.min_x, boardBounds.minX),
7636
+ y: boardBounds.minY
7637
+ });
7638
+ violationPoints.push({
7639
+ x: Math.min(componentBounds.max_x, boardBounds.maxX),
7640
+ y: boardBounds.minY
7641
+ });
7642
+ }
7643
+ if (componentBounds.max_y > boardBounds.maxY) {
7644
+ violationPoints.push({
7645
+ x: Math.max(componentBounds.min_x, boardBounds.minX),
7646
+ y: boardBounds.maxY
7647
+ });
7648
+ violationPoints.push({
7649
+ x: Math.min(componentBounds.max_x, boardBounds.maxX),
7650
+ y: boardBounds.maxY
7651
+ });
7652
+ }
7653
+ return createBoundsFromPoints(violationPoints, 0.3);
7654
+ };
7655
+ var nonZoomableErrorTypes = /* @__PURE__ */ new Set([
7656
+ "source_failed_to_create_component_error"
7657
+ ]);
7658
+ var getErrorPreviewBounds = ({
7659
+ error,
7660
+ indexes
7661
+ }) => {
7662
+ if (nonZoomableErrorTypes.has(error.error_type) || nonZoomableErrorTypes.has(error.type)) {
7663
+ return null;
7664
+ }
7665
+ let bounds = null;
7666
+ const focusCenter = getErrorFocusCenter({
7667
+ error,
7668
+ ...indexes
7669
+ });
7670
+ if (focusCenter) {
7671
+ bounds = mergeBounds2(bounds, createBoundsFromCenter(focusCenter, 0.35));
7672
+ }
7673
+ if (error.error_type === "pcb_component_outside_board_error") {
7674
+ bounds = mergeBounds2(
7675
+ bounds,
7676
+ getComponentBoundaryViolationBounds({
7677
+ error,
7678
+ boardsById: indexes.boardsById
7679
+ })
7680
+ );
7681
+ } else if (error.component_bounds && !focusCenter) {
7682
+ bounds = mergeBounds2(bounds, {
7683
+ minX: error.component_bounds.min_x,
7684
+ maxX: error.component_bounds.max_x,
7685
+ minY: error.component_bounds.min_y,
7686
+ maxY: error.component_bounds.max_y
7687
+ });
7688
+ }
7689
+ if (error.center && !focusCenter) {
7690
+ bounds = mergeBounds2(bounds, createBoundsFromCenter(error.center));
7691
+ }
7692
+ if (error.pcb_center && !focusCenter) {
7693
+ bounds = mergeBounds2(bounds, createBoundsFromCenter(error.pcb_center));
7694
+ }
7695
+ if (error.component_center && !focusCenter) {
7696
+ bounds = mergeBounds2(bounds, createBoundsFromCenter(error.component_center));
7697
+ }
7698
+ if (error.pcb_port_ids && !focusCenter) {
7699
+ for (const pcbPortId of error.pcb_port_ids) {
7700
+ const port = indexes.portsById.get(pcbPortId);
7701
+ if (port) {
7702
+ bounds = mergeBounds2(
7703
+ bounds,
7704
+ createBoundsFromCenter({ x: port.x, y: port.y }, 0.4)
7705
+ );
7706
+ }
7707
+ }
7708
+ }
7709
+ if (error.pcb_trace_id && !focusCenter) {
7710
+ const trace = indexes.tracesById.get(error.pcb_trace_id);
7711
+ if (trace?.route) {
7712
+ bounds = mergeBounds2(bounds, createBoundsFromRoute(trace.route));
7713
+ }
7714
+ }
7715
+ if (error.pcb_via_ids) {
7716
+ for (const pcbViaId of error.pcb_via_ids) {
7717
+ const via = indexes.viasById.get(pcbViaId);
7718
+ if (via) {
7719
+ bounds = mergeBounds2(
7720
+ bounds,
7721
+ createBoundsFromCenter(
7722
+ { x: via.x, y: via.y },
7723
+ Math.max((via.outer_diameter ?? 0.5) / 2, 0.28)
7724
+ )
7725
+ );
7726
+ }
7727
+ }
7728
+ }
7729
+ if (error.pcb_component_id && !focusCenter) {
7730
+ bounds = mergeBounds2(
7731
+ bounds,
7732
+ createBoundsFromComponent(
7733
+ indexes.componentsById.get(error.pcb_component_id)
7734
+ )
7735
+ );
7736
+ }
7737
+ if (error.pcb_component_ids && !focusCenter) {
7738
+ for (const pcbComponentId of error.pcb_component_ids) {
7739
+ bounds = mergeBounds2(
7740
+ bounds,
7741
+ createBoundsFromComponent(indexes.componentsById.get(pcbComponentId))
7742
+ );
7743
+ }
7744
+ }
7745
+ return bounds;
7746
+ };
7747
+ var createTransformForBounds = ({
7748
+ bounds,
7749
+ width,
7750
+ height
7751
+ }) => {
7752
+ const center = {
7753
+ x: (bounds.minX + bounds.maxX) / 2,
7754
+ y: (bounds.minY + bounds.maxY) / 2
7755
+ };
7756
+ const targetWidth = Math.max(bounds.maxX - bounds.minX, 0.8) + 1.4;
7757
+ const targetHeight = Math.max(bounds.maxY - bounds.minY, 0.8) + 1.4;
7758
+ const scaleFactor = Math.min(width / targetWidth, height / targetHeight, 240) * 0.92;
7759
+ return compose3(
7760
+ translate7(width / 2, height / 2),
7761
+ scale3(scaleFactor, -scaleFactor, 0, 0),
7762
+ translate7(-center.x, -center.y)
7763
+ );
7764
+ };
7765
+ var getRelatedIdsForError = (error) => {
7766
+ const relatedIds = /* @__PURE__ */ new Set();
7767
+ if (error.pcb_trace_id) {
7768
+ relatedIds.add(error.pcb_trace_id);
7769
+ }
7770
+ for (const pcbPortId of error.pcb_port_ids ?? []) {
7771
+ relatedIds.add(pcbPortId);
7772
+ }
7773
+ for (const pcbViaId of error.pcb_via_ids ?? []) {
7774
+ relatedIds.add(pcbViaId);
7775
+ }
7776
+ if (error.pcb_component_id) {
7777
+ relatedIds.add(error.pcb_component_id);
7778
+ }
7779
+ for (const pcbComponentId of error.pcb_component_ids ?? []) {
7780
+ relatedIds.add(pcbComponentId);
7781
+ }
7782
+ return [...relatedIds];
7783
+ };
7784
+
7785
+ // src/lib/util/transform-animation.ts
7786
+ var easeOutCubic = (progress) => 1 - (1 - progress) ** 3;
7787
+ var interpolateTransform = (start, end, progress) => ({
7788
+ a: start.a + (end.a - start.a) * progress,
7789
+ b: start.b + (end.b - start.b) * progress,
7790
+ c: start.c + (end.c - start.c) * progress,
7791
+ d: start.d + (end.d - start.d) * progress,
7792
+ e: start.e + (end.e - start.e) * progress,
7793
+ f: start.f + (end.f - start.f) * progress
7794
+ });
7795
+ var cancelTransformAnimation = (animationFrameId) => {
7796
+ if (animationFrameId !== null) {
7797
+ window.cancelAnimationFrame(animationFrameId);
7798
+ }
7799
+ };
7800
+ var animateTransform = ({
7801
+ startTransform,
7802
+ endTransform,
7803
+ durationMs,
7804
+ onUpdate,
7805
+ setAnimationFrameId,
7806
+ onComplete
7807
+ }) => {
7808
+ const startTime = performance.now();
7809
+ const tick = (timestamp) => {
7810
+ const elapsed = timestamp - startTime;
7811
+ const rawProgress = Math.min(1, elapsed / durationMs);
7812
+ const easedProgress = easeOutCubic(rawProgress);
7813
+ onUpdate(interpolateTransform(startTransform, endTransform, easedProgress));
7814
+ if (rawProgress < 1) {
7815
+ setAnimationFrameId(window.requestAnimationFrame(tick));
7816
+ return;
7817
+ }
7818
+ setAnimationFrameId(null);
7819
+ onComplete?.();
7820
+ };
7821
+ const animationFrameId = window.requestAnimationFrame(tick);
7822
+ setAnimationFrameId(animationFrameId);
7823
+ return animationFrameId;
7824
+ };
7825
+
7429
7826
  // src/components/CanvasElementsRenderer.tsx
7430
- import { useCallback as useCallback10, useMemo as useMemo8, useState as useState12 } from "react";
7827
+ import { useCallback as useCallback11, useEffect as useEffect18, useMemo as useMemo8, useRef as useRef15, useState as useState12 } from "react";
7431
7828
 
7432
7829
  // src/lib/util/expand-stroke.ts
7433
7830
  function getExpandedStroke(strokeInput, defaultWidth) {
@@ -7481,16 +7878,16 @@ function getExpandedStroke(strokeInput, defaultWidth) {
7481
7878
  addPoint(current, normalPrev, -1, currentWidth);
7482
7879
  addPoint(current, normalNext, -1, currentWidth);
7483
7880
  } else {
7484
- const scale6 = 1 / miterLength;
7881
+ const scale7 = 1 / miterLength;
7485
7882
  addPoint(
7486
7883
  current,
7487
- { x: miterX * scale6, y: miterY * scale6 },
7884
+ { x: miterX * scale7, y: miterY * scale7 },
7488
7885
  1,
7489
7886
  currentWidth
7490
7887
  );
7491
7888
  addPoint(
7492
7889
  current,
7493
- { x: miterX * scale6, y: miterY * scale6 },
7890
+ { x: miterX * scale7, y: miterY * scale7 },
7494
7891
  -1,
7495
7892
  currentWidth
7496
7893
  );
@@ -8180,10 +8577,10 @@ var convertElementToPrimitives = (element, allElements) => {
8180
8577
  import colorParser from "color";
8181
8578
  import {
8182
8579
  applyToPoint as applyToPoint3,
8183
- compose as compose3,
8580
+ compose as compose4,
8184
8581
  identity as identity2,
8185
8582
  inverse,
8186
- translate as translate7
8583
+ translate as translate8
8187
8584
  } from "transformation-matrix";
8188
8585
 
8189
8586
  // src/lib/colors.ts
@@ -8524,7 +8921,7 @@ var Drawer = class {
8524
8921
  );
8525
8922
  this.transform = identity2();
8526
8923
  this.transform.d *= -1;
8527
- this.transform = compose3(this.transform, translate7(0, -500));
8924
+ this.transform = compose4(this.transform, translate8(0, -500));
8528
8925
  this.lastPoint = { x: 0, y: 0 };
8529
8926
  this.equip({});
8530
8927
  }
@@ -8619,10 +9016,10 @@ var Drawer = class {
8619
9016
  }
8620
9017
  if (is_stroke_dashed) {
8621
9018
  let dashPattern = [];
8622
- const scale6 = Math.abs(this.transform.a);
8623
- if (scale6 > 0) {
9019
+ const scale7 = Math.abs(this.transform.a);
9020
+ if (scale7 > 0) {
8624
9021
  const SEGMENT_LENGTH = 0.1;
8625
- const dash = SEGMENT_LENGTH * scale6;
9022
+ const dash = SEGMENT_LENGTH * scale7;
8626
9023
  const gap = dash * 1.3;
8627
9024
  if (dash > 0.5) {
8628
9025
  dashPattern = [dash, gap];
@@ -9369,16 +9766,16 @@ function drawPlatedHolePads({
9369
9766
  }
9370
9767
 
9371
9768
  // src/lib/util/rotate-text.ts
9372
- import { compose as compose4, translate as translate8, rotate, applyToPoint as applyToPoint4 } from "transformation-matrix";
9769
+ import { compose as compose5, translate as translate9, rotate, applyToPoint as applyToPoint4 } from "transformation-matrix";
9373
9770
  function rotateText(rotateTextParams) {
9374
9771
  const { lines, anchorPoint, ccwRotation } = rotateTextParams;
9375
9772
  if (!ccwRotation) return lines;
9376
9773
  const rad = ccwRotation * Math.PI / 180;
9377
9774
  const rotationMatrix = rotate(rad);
9378
- const transform = compose4(
9379
- translate8(anchorPoint.x, anchorPoint.y),
9775
+ const transform = compose5(
9776
+ translate9(anchorPoint.x, anchorPoint.y),
9380
9777
  rotationMatrix,
9381
- translate8(-anchorPoint.x, -anchorPoint.y)
9778
+ translate9(-anchorPoint.x, -anchorPoint.y)
9382
9779
  );
9383
9780
  applyToPoint4(transform, anchorPoint);
9384
9781
  return lines.map((line) => ({
@@ -10371,17 +10768,17 @@ var FONT_SIZE_HEIGHT_RATIO = 1;
10371
10768
  var import_svgson = __toESM(require_svgson_umd(), 1);
10372
10769
  var import_pretty = __toESM(require_pretty(), 1);
10373
10770
  import {
10374
- compose as compose5,
10375
- translate as translate9,
10376
- scale as scale3,
10771
+ compose as compose6,
10772
+ translate as translate10,
10773
+ scale as scale4,
10377
10774
  applyToPoint as applyToPoint5
10378
10775
  } from "transformation-matrix";
10379
10776
 
10380
10777
  // node_modules/graphics-debug/dist/chunk-ARYXS3GC.js
10381
10778
  import {
10382
- compose as compose6,
10383
- scale as scale4,
10384
- translate as translate10,
10779
+ compose as compose7,
10780
+ scale as scale5,
10781
+ translate as translate11,
10385
10782
  applyToPoint as applyToPoint6
10386
10783
  } from "transformation-matrix";
10387
10784
  var defaultColors = [
@@ -10444,10 +10841,10 @@ function computeTransformFromViewbox(viewbox, canvasWidth, canvasHeight, options
10444
10841
  (canvasWidth - 2 * padding) / width,
10445
10842
  (canvasHeight - 2 * padding) / height
10446
10843
  );
10447
- return compose6(
10448
- translate10(canvasWidth / 2, canvasHeight / 2),
10449
- scale4(scale_factor, yFlip ? -scale_factor : scale_factor),
10450
- translate10(-(bounds.minX + width / 2), -(bounds.minY + height / 2))
10844
+ return compose7(
10845
+ translate11(canvasWidth / 2, canvasHeight / 2),
10846
+ scale5(scale_factor, yFlip ? -scale_factor : scale_factor),
10847
+ translate11(-(bounds.minX + width / 2), -(bounds.minY + height / 2))
10451
10848
  );
10452
10849
  }
10453
10850
  function getBounds(graphics) {
@@ -12274,15 +12671,11 @@ var getPopupPosition = (errorCenter, containerRef) => {
12274
12671
  };
12275
12672
  };
12276
12673
 
12277
- // src/components/ErrorOverlay.tsx
12278
- import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
12279
- var ErrorSVG = ({
12280
- screenPort1,
12281
- screenPort2,
12282
- errorCenter,
12283
- canLineBeDrawn,
12284
- isHighlighted = false
12285
- }) => /* @__PURE__ */ jsx10(
12674
+ // src/components/FocusMarkerSVG.tsx
12675
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
12676
+ var FocusMarkerSVG = ({
12677
+ center
12678
+ }) => /* @__PURE__ */ jsxs8(
12286
12679
  "svg",
12287
12680
  {
12288
12681
  style: {
@@ -12291,54 +12684,78 @@ var ErrorSVG = ({
12291
12684
  top: 0,
12292
12685
  pointerEvents: "none",
12293
12686
  mixBlendMode: "difference",
12294
- zIndex: zIndexMap.errorOverlay
12687
+ zIndex: zIndexMap.errorOverlay + 30
12295
12688
  },
12296
12689
  width: "100%",
12297
12690
  height: "100%",
12298
- children: canLineBeDrawn && /* @__PURE__ */ jsxs8(Fragment4, { children: [
12691
+ children: [
12692
+ /* @__PURE__ */ jsx10(
12693
+ "circle",
12694
+ {
12695
+ cx: center.x,
12696
+ cy: center.y,
12697
+ r: 11,
12698
+ fill: "none",
12699
+ stroke: "#ff6b6b",
12700
+ strokeWidth: 2.5,
12701
+ opacity: 0.95
12702
+ }
12703
+ ),
12704
+ /* @__PURE__ */ jsx10("circle", { cx: center.x, cy: center.y, r: 4.5, fill: "#ff6b6b", opacity: 0.95 }),
12705
+ /* @__PURE__ */ jsx10(
12706
+ "line",
12707
+ {
12708
+ x1: center.x - 18,
12709
+ y1: center.y,
12710
+ x2: center.x - 8,
12711
+ y2: center.y,
12712
+ stroke: "#ff6b6b",
12713
+ strokeWidth: 2
12714
+ }
12715
+ ),
12299
12716
  /* @__PURE__ */ jsx10(
12300
12717
  "line",
12301
12718
  {
12302
- x1: screenPort1.x,
12303
- y1: screenPort1.y,
12304
- x2: errorCenter.x,
12305
- y2: errorCenter.y,
12306
- strokeWidth: isHighlighted ? 3 : 1.5,
12307
- strokeDasharray: "2,2",
12308
- stroke: isHighlighted ? "#ff4444" : "red"
12719
+ x1: center.x + 8,
12720
+ y1: center.y,
12721
+ x2: center.x + 18,
12722
+ y2: center.y,
12723
+ stroke: "#ff6b6b",
12724
+ strokeWidth: 2
12309
12725
  }
12310
12726
  ),
12311
12727
  /* @__PURE__ */ jsx10(
12312
12728
  "line",
12313
12729
  {
12314
- x1: errorCenter.x,
12315
- y1: errorCenter.y,
12316
- x2: screenPort2.x,
12317
- y2: screenPort2.y,
12318
- strokeWidth: isHighlighted ? 3 : 1.5,
12319
- strokeDasharray: "2,2",
12320
- stroke: isHighlighted ? "#ff4444" : "red"
12730
+ x1: center.x,
12731
+ y1: center.y - 18,
12732
+ x2: center.x,
12733
+ y2: center.y - 8,
12734
+ stroke: "#ff6b6b",
12735
+ strokeWidth: 2
12321
12736
  }
12322
12737
  ),
12323
- isHighlighted ? /* @__PURE__ */ jsx10(
12324
- "rect",
12738
+ /* @__PURE__ */ jsx10(
12739
+ "line",
12325
12740
  {
12326
- x: errorCenter.x - 7,
12327
- y: errorCenter.y - 7,
12328
- width: 14,
12329
- height: 14,
12330
- transform: `rotate(45 ${errorCenter.x} ${errorCenter.y})`,
12331
- fill: "#ff4444"
12741
+ x1: center.x,
12742
+ y1: center.y + 8,
12743
+ x2: center.x,
12744
+ y2: center.y + 18,
12745
+ stroke: "#ff6b6b",
12746
+ strokeWidth: 2
12332
12747
  }
12333
- ) : /* @__PURE__ */ jsx10("circle", { cx: errorCenter.x, cy: errorCenter.y, r: 5, fill: "red" })
12334
- ] })
12748
+ )
12749
+ ]
12335
12750
  }
12336
12751
  );
12337
- var RouteSVG = ({
12338
- points,
12752
+
12753
+ // src/components/ErrorOverlay.tsx
12754
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
12755
+ var ErrorMarkerSVG = ({
12339
12756
  errorCenter,
12340
12757
  isHighlighted = false
12341
- }) => /* @__PURE__ */ jsxs8(
12758
+ }) => /* @__PURE__ */ jsx11(
12342
12759
  "svg",
12343
12760
  {
12344
12761
  style: {
@@ -12351,29 +12768,19 @@ var RouteSVG = ({
12351
12768
  },
12352
12769
  width: "100%",
12353
12770
  height: "100%",
12354
- children: [
12355
- points.length > 1 && /* @__PURE__ */ jsx10(
12356
- "polyline",
12357
- {
12358
- points: points.map((pt) => `${pt.x},${pt.y}`).join(" "),
12359
- fill: "none",
12360
- stroke: isHighlighted ? "#ff4444" : "red",
12361
- strokeWidth: isHighlighted ? 3 : 1.5,
12362
- strokeDasharray: "2,2"
12363
- }
12364
- ),
12365
- isHighlighted ? /* @__PURE__ */ jsx10(
12366
- "rect",
12367
- {
12368
- x: errorCenter.x - 7,
12369
- y: errorCenter.y - 7,
12370
- width: 14,
12371
- height: 14,
12372
- transform: `rotate(45 ${errorCenter.x} ${errorCenter.y})`,
12373
- fill: "#ff4444"
12374
- }
12375
- ) : /* @__PURE__ */ jsx10("circle", { cx: errorCenter.x, cy: errorCenter.y, r: 5, fill: "red" })
12376
- ]
12771
+ children: /* @__PURE__ */ jsx11(
12772
+ "rect",
12773
+ {
12774
+ x: errorCenter.x - 7,
12775
+ y: errorCenter.y - 7,
12776
+ width: 14,
12777
+ height: 14,
12778
+ transform: `rotate(45 ${errorCenter.x} ${errorCenter.y})`,
12779
+ fill: isHighlighted ? "#ff4444" : "transparent",
12780
+ stroke: isHighlighted ? "#ff4444" : "red",
12781
+ strokeWidth: isHighlighted ? 2.5 : 1.5
12782
+ }
12783
+ )
12377
12784
  }
12378
12785
  );
12379
12786
  var errorMessageStyles = css2`
@@ -12396,15 +12803,19 @@ var ErrorOverlay = ({
12396
12803
  elements
12397
12804
  }) => {
12398
12805
  const containerRef = useRef9(null);
12399
- const { isShowingDRCErrors, hoveredErrorId } = useGlobalStore((state) => ({
12400
- isShowingDRCErrors: state.is_showing_drc_errors,
12401
- hoveredErrorId: state.hovered_error_id
12402
- }));
12806
+ const { isShowingDRCErrors, hoveredErrorId, focusedErrorId } = useGlobalStore(
12807
+ (state) => ({
12808
+ isShowingDRCErrors: state.is_showing_drc_errors,
12809
+ hoveredErrorId: state.hovered_error_id,
12810
+ focusedErrorId: state.focused_error_id
12811
+ })
12812
+ );
12813
+ const activeErrorId = focusedErrorId ?? hoveredErrorId;
12403
12814
  if (!elements) {
12404
- return /* @__PURE__ */ jsx10("div", { style: { position: "relative" }, ref: containerRef, children });
12815
+ return /* @__PURE__ */ jsx11("div", { style: { position: "relative" }, ref: containerRef, children });
12405
12816
  }
12406
12817
  const traceErrors = elements.filter(
12407
- (el) => el.type === "pcb_trace_error"
12818
+ (el) => el.type === "pcb_trace_error" && !el.message?.includes("Multiple components found with name")
12408
12819
  );
12409
12820
  const viaClearanceErrors = elements.filter(
12410
12821
  (el) => el.type === "pcb_via_clearance_error"
@@ -12412,234 +12823,132 @@ var ErrorOverlay = ({
12412
12823
  const componentErrors = elements.filter(
12413
12824
  (el) => el.type === "pcb_trace_error" && el.message?.includes("Multiple components found with name")
12414
12825
  );
12415
- const portsMap = /* @__PURE__ */ new Map();
12416
- elements.forEach((el) => {
12417
- if (el.type === "pcb_port") {
12418
- portsMap.set(el.pcb_port_id, el);
12826
+ const elementIndexes = buildErrorPreviewElementIndexes(elements);
12827
+ const focusedErrorElement = findErrorElementById(elements, activeErrorId);
12828
+ const getScreenErrorCenter = (error) => {
12829
+ const focusCenter = getErrorFocusPoint({
12830
+ error,
12831
+ indexes: elementIndexes
12832
+ });
12833
+ if (!focusCenter || typeof focusCenter.x !== "number" || typeof focusCenter.y !== "number") {
12834
+ return null;
12419
12835
  }
12420
- });
12421
- return /* @__PURE__ */ jsxs8("div", { style: { position: "relative" }, ref: containerRef, children: [
12836
+ const screenCenter = applyToPoint11(transform, focusCenter);
12837
+ if (Number.isNaN(screenCenter.x) || Number.isNaN(screenCenter.y)) {
12838
+ return null;
12839
+ }
12840
+ return screenCenter;
12841
+ };
12842
+ let focusScreenCenter = null;
12843
+ if (focusedErrorElement) {
12844
+ focusScreenCenter = getScreenErrorCenter(
12845
+ focusedErrorElement
12846
+ );
12847
+ }
12848
+ return /* @__PURE__ */ jsxs9("div", { style: { position: "relative" }, ref: containerRef, children: [
12422
12849
  children,
12423
12850
  traceErrors.map((el, index) => {
12424
- const { pcb_port_ids, pcb_trace_id } = el;
12425
- const port1 = pcb_port_ids?.[0] ? portsMap.get(pcb_port_ids[0]) : void 0;
12426
- const port2 = pcb_port_ids?.[1] ? portsMap.get(pcb_port_ids[1]) : void 0;
12427
- const trace = elements ? su(elements).pcb_trace.get(pcb_trace_id) : void 0;
12428
- const errorId = el.pcb_trace_error_id;
12429
- const isHighlighted = hoveredErrorId === errorId;
12430
- if (port1 && port2) {
12431
- const screenPort1 = applyToPoint11(transform, {
12432
- x: port1.x,
12433
- y: port1.y
12434
- });
12435
- const screenPort2 = applyToPoint11(transform, {
12436
- x: port2.x,
12437
- y: port2.y
12438
- });
12439
- if (!isShowingDRCErrors) {
12440
- return null;
12441
- }
12442
- const canLineBeDrawn = !(Number.isNaN(screenPort1.x) || Number.isNaN(screenPort1.y) || Number.isNaN(screenPort2.x) || Number.isNaN(screenPort2.y));
12443
- const errorCenter = {
12444
- x: (screenPort1.x + screenPort2.x) / 2,
12445
- y: (screenPort1.y + screenPort2.y) / 2
12446
- };
12447
- if (Number.isNaN(errorCenter.x) || Number.isNaN(errorCenter.y)) {
12448
- return null;
12449
- }
12450
- const popupPosition = getPopupPosition(errorCenter, containerRef);
12451
- return /* @__PURE__ */ jsxs8(Fragment3, { children: [
12452
- /* @__PURE__ */ jsx10(
12453
- ErrorSVG,
12454
- {
12455
- screenPort1,
12456
- screenPort2,
12457
- errorCenter,
12458
- canLineBeDrawn,
12459
- isHighlighted
12460
- }
12461
- ),
12462
- /* @__PURE__ */ jsx10(
12463
- "div",
12464
- {
12465
- style: {
12466
- position: "absolute",
12467
- left: errorCenter.x - 15,
12468
- top: errorCenter.y - 15,
12469
- width: 30,
12470
- height: 30,
12471
- zIndex: zIndexMap.errorOverlay + 5,
12472
- cursor: "pointer",
12473
- borderRadius: "50%"
12474
- },
12475
- onMouseEnter: (e) => {
12476
- const popup = e.currentTarget.nextElementSibling;
12477
- if (popup) {
12478
- const msg = popup.querySelector(
12479
- ".error-message"
12480
- );
12481
- if (msg) msg.style.opacity = "1";
12482
- }
12483
- },
12484
- onMouseLeave: (e) => {
12485
- if (!isHighlighted) {
12486
- const popup = e.currentTarget.nextElementSibling;
12487
- if (popup) {
12488
- const msg = popup.querySelector(
12489
- ".error-message"
12490
- );
12491
- if (msg) msg.style.opacity = "0";
12492
- }
12493
- }
12851
+ const errorId = getErrorId(el, index);
12852
+ const isHighlighted = activeErrorId === errorId;
12853
+ if (!isHighlighted && !isShowingDRCErrors) return null;
12854
+ const errorCenter = getScreenErrorCenter(el);
12855
+ if (!errorCenter) return null;
12856
+ const popupPosition = getPopupPosition(errorCenter, containerRef);
12857
+ return /* @__PURE__ */ jsxs9(Fragment3, { children: [
12858
+ /* @__PURE__ */ jsx11(
12859
+ ErrorMarkerSVG,
12860
+ {
12861
+ errorCenter,
12862
+ isHighlighted
12863
+ }
12864
+ ),
12865
+ /* @__PURE__ */ jsx11(
12866
+ "div",
12867
+ {
12868
+ style: {
12869
+ position: "absolute",
12870
+ left: errorCenter.x - 15,
12871
+ top: errorCenter.y - 15,
12872
+ width: 30,
12873
+ height: 30,
12874
+ zIndex: zIndexMap.errorOverlay + 5,
12875
+ cursor: "pointer",
12876
+ borderRadius: "50%"
12877
+ },
12878
+ onMouseEnter: (e) => {
12879
+ const popup = e.currentTarget.nextElementSibling;
12880
+ if (popup) {
12881
+ const msg = popup.querySelector(
12882
+ ".error-message"
12883
+ );
12884
+ if (msg) msg.style.opacity = "1";
12494
12885
  }
12495
- }
12496
- ),
12497
- /* @__PURE__ */ jsx10(
12498
- "div",
12499
- {
12500
- style: {
12501
- position: "absolute",
12502
- zIndex: isHighlighted ? 200 : 100,
12503
- left: popupPosition.left,
12504
- top: popupPosition.top,
12505
- color: isHighlighted ? "#ff4444" : "red",
12506
- textAlign: "center",
12507
- fontFamily: "sans-serif",
12508
- fontSize: 12,
12509
- display: isShowingDRCErrors || isHighlighted ? "flex" : "none",
12510
- flexDirection: "column",
12511
- alignItems: "center",
12512
- pointerEvents: "none",
12513
- transform: popupPosition.transform
12514
- },
12515
- children: /* @__PURE__ */ jsx10(
12516
- "div",
12517
- {
12518
- className: `error-message ${errorMessageStyles}`,
12519
- style: {
12520
- opacity: isHighlighted ? 1 : 0,
12521
- border: `1px solid ${isHighlighted ? "#ff4444" : "red"}`
12522
- },
12523
- children: el.message
12524
- }
12525
- )
12526
- }
12527
- )
12528
- ] }, errorId);
12529
- }
12530
- if (trace?.route && (isShowingDRCErrors || isHighlighted)) {
12531
- const screenPoints = trace.route.map(
12532
- (pt) => applyToPoint11(transform, { x: pt.x, y: pt.y })
12533
- );
12534
- if (screenPoints.some((pt) => Number.isNaN(pt.x) || Number.isNaN(pt.y)))
12535
- return null;
12536
- const mid = Math.floor(screenPoints.length / 2);
12537
- const errorCenter = screenPoints[mid];
12538
- const popupPosition = getPopupPosition(errorCenter, containerRef);
12539
- return /* @__PURE__ */ jsxs8(Fragment3, { children: [
12540
- /* @__PURE__ */ jsx10(
12541
- RouteSVG,
12542
- {
12543
- points: screenPoints,
12544
- errorCenter,
12545
- isHighlighted
12546
- }
12547
- ),
12548
- /* @__PURE__ */ jsx10(
12549
- "div",
12550
- {
12551
- style: {
12552
- position: "absolute",
12553
- left: errorCenter.x - 15,
12554
- top: errorCenter.y - 15,
12555
- width: 30,
12556
- height: 30,
12557
- zIndex: zIndexMap.errorOverlay + 5,
12558
- cursor: "pointer",
12559
- borderRadius: "50%"
12560
- },
12561
- onMouseEnter: (e) => {
12886
+ },
12887
+ onMouseLeave: (e) => {
12888
+ if (!isHighlighted) {
12562
12889
  const popup = e.currentTarget.nextElementSibling;
12563
12890
  if (popup) {
12564
12891
  const msg = popup.querySelector(
12565
12892
  ".error-message"
12566
12893
  );
12567
- if (msg) msg.style.opacity = "1";
12568
- }
12569
- },
12570
- onMouseLeave: (e) => {
12571
- if (!isHighlighted) {
12572
- const popup = e.currentTarget.nextElementSibling;
12573
- if (popup) {
12574
- const msg = popup.querySelector(
12575
- ".error-message"
12576
- );
12577
- if (msg) msg.style.opacity = "0";
12578
- }
12894
+ if (msg) msg.style.opacity = "0";
12579
12895
  }
12580
12896
  }
12581
12897
  }
12582
- ),
12583
- /* @__PURE__ */ jsx10(
12584
- "div",
12585
- {
12586
- style: {
12587
- position: "absolute",
12588
- zIndex: isHighlighted ? zIndexMap.errorOverlay + 10 : zIndexMap.errorOverlay + 1,
12589
- left: popupPosition.left,
12590
- top: popupPosition.top,
12591
- color: isHighlighted ? "#ff4444" : "red",
12592
- textAlign: "center",
12593
- fontFamily: "sans-serif",
12594
- fontSize: 12,
12595
- display: "flex",
12596
- flexDirection: "column",
12597
- alignItems: "center",
12598
- pointerEvents: "none",
12599
- transform: popupPosition.transform
12600
- },
12601
- children: /* @__PURE__ */ jsx10(
12602
- "div",
12603
- {
12604
- className: `error-message ${errorMessageStyles}`,
12605
- style: {
12606
- opacity: isHighlighted ? 1 : 0,
12607
- border: `1px solid ${isHighlighted ? "#ff4444" : "red"}`
12608
- },
12609
- children: el.message
12610
- }
12611
- )
12612
- }
12613
- )
12614
- ] }, errorId);
12615
- }
12616
- return null;
12898
+ }
12899
+ ),
12900
+ /* @__PURE__ */ jsx11(
12901
+ "div",
12902
+ {
12903
+ style: {
12904
+ position: "absolute",
12905
+ zIndex: isHighlighted ? 200 : 100,
12906
+ left: popupPosition.left,
12907
+ top: popupPosition.top,
12908
+ color: isHighlighted ? "#ff4444" : "red",
12909
+ textAlign: "center",
12910
+ fontFamily: "sans-serif",
12911
+ fontSize: 12,
12912
+ display: isShowingDRCErrors || isHighlighted ? "flex" : "none",
12913
+ flexDirection: "column",
12914
+ alignItems: "center",
12915
+ pointerEvents: "none",
12916
+ transform: popupPosition.transform
12917
+ },
12918
+ children: /* @__PURE__ */ jsx11(
12919
+ "div",
12920
+ {
12921
+ className: `error-message ${errorMessageStyles}`,
12922
+ style: {
12923
+ opacity: isHighlighted ? 1 : 0,
12924
+ border: `1px solid ${isHighlighted ? "#ff4444" : "red"}`
12925
+ },
12926
+ children: el.message
12927
+ }
12928
+ )
12929
+ }
12930
+ )
12931
+ ] }, errorId);
12617
12932
  }),
12618
12933
  viaClearanceErrors.map((el, index) => {
12619
- if (!el.pcb_center) return null;
12620
- const errorId = el.pcb_via_ids;
12621
- const isHighlighted = hoveredErrorId === errorId[0];
12934
+ const errorId = getErrorId(el, index);
12935
+ const isHighlighted = activeErrorId === errorId;
12622
12936
  if (!isHighlighted && !isShowingDRCErrors) return null;
12623
- const errorCenter = applyToPoint11(transform, {
12624
- x: el.pcb_center.x,
12625
- y: el.pcb_center.y
12626
- });
12627
- if (Number.isNaN(errorCenter.x) || Number.isNaN(errorCenter.y))
12628
- return null;
12937
+ const errorCenter = getScreenErrorCenter(el);
12938
+ if (!errorCenter) return null;
12629
12939
  const popupPosition = getPopupPosition(
12630
12940
  { x: errorCenter.x, y: errorCenter.y },
12631
12941
  containerRef
12632
12942
  );
12633
- return /* @__PURE__ */ jsxs8(Fragment3, { children: [
12634
- /* @__PURE__ */ jsx10(
12635
- RouteSVG,
12943
+ return /* @__PURE__ */ jsxs9(Fragment3, { children: [
12944
+ /* @__PURE__ */ jsx11(
12945
+ ErrorMarkerSVG,
12636
12946
  {
12637
- points: [],
12638
12947
  errorCenter,
12639
12948
  isHighlighted
12640
12949
  }
12641
12950
  ),
12642
- /* @__PURE__ */ jsx10(
12951
+ /* @__PURE__ */ jsx11(
12643
12952
  "div",
12644
12953
  {
12645
12954
  style: {
@@ -12674,7 +12983,7 @@ var ErrorOverlay = ({
12674
12983
  }
12675
12984
  }
12676
12985
  ),
12677
- /* @__PURE__ */ jsx10(
12986
+ /* @__PURE__ */ jsx11(
12678
12987
  "div",
12679
12988
  {
12680
12989
  style: {
@@ -12692,7 +13001,7 @@ var ErrorOverlay = ({
12692
13001
  pointerEvents: "none",
12693
13002
  transform: popupPosition.transform
12694
13003
  },
12695
- children: /* @__PURE__ */ jsx10(
13004
+ children: /* @__PURE__ */ jsx11(
12696
13005
  "div",
12697
13006
  {
12698
13007
  className: `error-message ${errorMessageStyles}`,
@@ -12705,7 +13014,7 @@ var ErrorOverlay = ({
12705
13014
  )
12706
13015
  }
12707
13016
  )
12708
- ] }, errorId[0]);
13017
+ ] }, errorId);
12709
13018
  }),
12710
13019
  componentErrors.map((el, index) => {
12711
13020
  const componentName = el.component_name || el.message?.match(/name "([^"]+)"/)?.[1];
@@ -12715,8 +13024,8 @@ var ErrorOverlay = ({
12715
13024
  (src) => src.type === "source_component" && src.source_component_id === comp.source_component_id && src.name === componentName
12716
13025
  )
12717
13026
  ) || [];
12718
- const errorId = el.error_id;
12719
- const isHighlighted = hoveredErrorId === errorId;
13027
+ const errorId = getErrorId(el, index);
13028
+ const isHighlighted = activeErrorId === errorId;
12720
13029
  if (!isHighlighted && !isShowingDRCErrors) return null;
12721
13030
  return components.map((comp, compIndex) => {
12722
13031
  let center = { x: 0, y: 0 };
@@ -12732,17 +13041,17 @@ var ErrorOverlay = ({
12732
13041
  const screenCenter = applyToPoint11(transform, center);
12733
13042
  if (Number.isNaN(screenCenter.x) || Number.isNaN(screenCenter.y))
12734
13043
  return null;
12735
- const scale6 = Math.abs(transform.a);
13044
+ const scale7 = Math.abs(transform.a);
12736
13045
  const baseRadius = 0.5;
12737
13046
  const minRadius = 8;
12738
13047
  const maxRadius = 30;
12739
13048
  const scaledRadius = Math.max(
12740
13049
  minRadius,
12741
- Math.min(maxRadius, baseRadius * scale6)
13050
+ Math.min(maxRadius, baseRadius * scale7)
12742
13051
  );
12743
13052
  const popupPosition = getPopupPosition(screenCenter, containerRef);
12744
- return /* @__PURE__ */ jsxs8(Fragment3, { children: [
12745
- /* @__PURE__ */ jsx10(
13053
+ return /* @__PURE__ */ jsxs9(Fragment3, { children: [
13054
+ /* @__PURE__ */ jsx11(
12746
13055
  "svg",
12747
13056
  {
12748
13057
  style: {
@@ -12755,13 +13064,13 @@ var ErrorOverlay = ({
12755
13064
  },
12756
13065
  width: "100%",
12757
13066
  height: "100%",
12758
- children: isHighlighted ? /* @__PURE__ */ jsx10(
13067
+ children: isHighlighted ? /* @__PURE__ */ jsx11(
12759
13068
  "polygon",
12760
13069
  {
12761
13070
  points: `${screenCenter.x},${screenCenter.y - scaledRadius * 1.25} ${screenCenter.x + scaledRadius},${screenCenter.y} ${screenCenter.x},${screenCenter.y + scaledRadius * 1.25} ${screenCenter.x - scaledRadius},${screenCenter.y}`,
12762
13071
  fill: "#ff4444"
12763
13072
  }
12764
- ) : /* @__PURE__ */ jsx10(
13073
+ ) : /* @__PURE__ */ jsx11(
12765
13074
  "circle",
12766
13075
  {
12767
13076
  cx: screenCenter.x,
@@ -12775,7 +13084,7 @@ var ErrorOverlay = ({
12775
13084
  )
12776
13085
  }
12777
13086
  ),
12778
- /* @__PURE__ */ jsx10(
13087
+ /* @__PURE__ */ jsx11(
12779
13088
  "div",
12780
13089
  {
12781
13090
  style: {
@@ -12810,7 +13119,7 @@ var ErrorOverlay = ({
12810
13119
  }
12811
13120
  }
12812
13121
  ),
12813
- /* @__PURE__ */ jsx10(
13122
+ /* @__PURE__ */ jsx11(
12814
13123
  "div",
12815
13124
  {
12816
13125
  style: {
@@ -12828,7 +13137,7 @@ var ErrorOverlay = ({
12828
13137
  pointerEvents: "none",
12829
13138
  transform: popupPosition.transform
12830
13139
  },
12831
- children: /* @__PURE__ */ jsx10(
13140
+ children: /* @__PURE__ */ jsx11(
12832
13141
  "div",
12833
13142
  {
12834
13143
  className: `error-message ${errorMessageStyles}`,
@@ -12841,9 +13150,10 @@ var ErrorOverlay = ({
12841
13150
  )
12842
13151
  }
12843
13152
  )
12844
- ] }, errorId);
13153
+ ] }, `${errorId}_${compIndex}`);
12845
13154
  });
12846
- })
13155
+ }),
13156
+ focusScreenCenter && /* @__PURE__ */ jsx11(FocusMarkerSVG, { center: focusScreenCenter })
12847
13157
  ] });
12848
13158
  };
12849
13159
 
@@ -12917,7 +13227,7 @@ function filterTracesIfMultiple(filterTraces) {
12917
13227
  }
12918
13228
 
12919
13229
  // src/components/ElementOverlayBox.tsx
12920
- import { jsx as jsx11 } from "react/jsx-runtime";
13230
+ import { jsx as jsx12 } from "react/jsx-runtime";
12921
13231
  var containerStyle = {
12922
13232
  position: "absolute",
12923
13233
  left: 0,
@@ -13009,7 +13319,7 @@ var HighlightedPrimitiveBoxWithText = ({
13009
13319
  const overlayInfo = getTraceOverlayInfo(traceTextContext);
13010
13320
  if (!overlayInfo) return null;
13011
13321
  const yOffset = mousePos.y - 35;
13012
- return /* @__PURE__ */ jsx11(
13322
+ return /* @__PURE__ */ jsx12(
13013
13323
  "div",
13014
13324
  {
13015
13325
  style: {
@@ -13021,7 +13331,7 @@ var HighlightedPrimitiveBoxWithText = ({
13021
13331
  pointerEvents: "none",
13022
13332
  transform: "translateX(-50%)"
13023
13333
  },
13024
- children: /* @__PURE__ */ jsx11(
13334
+ children: /* @__PURE__ */ jsx12(
13025
13335
  "div",
13026
13336
  {
13027
13337
  style: {
@@ -13047,7 +13357,7 @@ var HighlightedPrimitiveBoxWithText = ({
13047
13357
  if (label.trim().length === 0) {
13048
13358
  return null;
13049
13359
  }
13050
- return /* @__PURE__ */ jsx11(
13360
+ return /* @__PURE__ */ jsx12(
13051
13361
  "div",
13052
13362
  {
13053
13363
  style: {
@@ -13061,7 +13371,7 @@ var HighlightedPrimitiveBoxWithText = ({
13061
13371
  transform: `rotate(${-rotation}deg)`,
13062
13372
  transformOrigin: "center center"
13063
13373
  },
13064
- children: /* @__PURE__ */ jsx11(
13374
+ children: /* @__PURE__ */ jsx12(
13065
13375
  "div",
13066
13376
  {
13067
13377
  style: {
@@ -13077,7 +13387,7 @@ var HighlightedPrimitiveBoxWithText = ({
13077
13387
  opacity: finalState ? 1 : si === 0 ? 1 : 0,
13078
13388
  transition: "width 0.2s, height 0.2s, margin-left 0.2s, margin-top 0.2s, opacity 0.2s"
13079
13389
  },
13080
- children: /* @__PURE__ */ jsx11(
13390
+ children: /* @__PURE__ */ jsx12(
13081
13391
  "div",
13082
13392
  {
13083
13393
  style: {
@@ -13125,7 +13435,7 @@ var ElementOverlayBox = ({
13125
13435
  is_showing_multiple_traces_length,
13126
13436
  elements
13127
13437
  });
13128
- return /* @__PURE__ */ jsx11("div", { style: containerStyle, children: !is_moving_component && primitives.map((primitive, i) => /* @__PURE__ */ jsx11(
13438
+ return /* @__PURE__ */ jsx12("div", { style: containerStyle, children: !is_moving_component && primitives.map((primitive, i) => /* @__PURE__ */ jsx12(
13129
13439
  HighlightedPrimitiveBoxWithText,
13130
13440
  {
13131
13441
  primitive,
@@ -13169,7 +13479,7 @@ var COLORS = {
13169
13479
  };
13170
13480
 
13171
13481
  // src/components/AnchorOffsetOverlay/AnchorOffsetOverlay.tsx
13172
- import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
13482
+ import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
13173
13483
  var AnchorOffsetOverlay = ({
13174
13484
  targets,
13175
13485
  transform,
@@ -13192,7 +13502,7 @@ var AnchorOffsetOverlay = ({
13192
13502
  anchorScreens.set(target.anchor_id, screenPoint);
13193
13503
  }
13194
13504
  });
13195
- return /* @__PURE__ */ jsx12(
13505
+ return /* @__PURE__ */ jsx13(
13196
13506
  "div",
13197
13507
  {
13198
13508
  style: {
@@ -13205,7 +13515,7 @@ var AnchorOffsetOverlay = ({
13205
13515
  pointerEvents: "none",
13206
13516
  zIndex: zIndexMap.dimensionOverlay
13207
13517
  },
13208
- children: /* @__PURE__ */ jsxs9(
13518
+ children: /* @__PURE__ */ jsxs10(
13209
13519
  "svg",
13210
13520
  {
13211
13521
  style: {
@@ -13235,8 +13545,8 @@ var AnchorOffsetOverlay = ({
13235
13545
  const shouldShowYLabel = yLineLength > VISUAL_CONFIG.MIN_LINE_LENGTH_FOR_LABEL;
13236
13546
  const xLabelText = `${target.display_offset_x ?? offsetX.toFixed(2)}mm`;
13237
13547
  const yLabelText = `${target.display_offset_y ?? offsetY.toFixed(2)}mm`;
13238
- return /* @__PURE__ */ jsxs9("g", { children: [
13239
- /* @__PURE__ */ jsx12(
13548
+ return /* @__PURE__ */ jsxs10("g", { children: [
13549
+ /* @__PURE__ */ jsx13(
13240
13550
  "line",
13241
13551
  {
13242
13552
  x1: anchorMarkerScreen.x,
@@ -13248,7 +13558,7 @@ var AnchorOffsetOverlay = ({
13248
13558
  strokeDasharray: VISUAL_CONFIG.LINE_DASH_PATTERN
13249
13559
  }
13250
13560
  ),
13251
- /* @__PURE__ */ jsx12(
13561
+ /* @__PURE__ */ jsx13(
13252
13562
  "line",
13253
13563
  {
13254
13564
  x1: targetScreen.x,
@@ -13260,7 +13570,7 @@ var AnchorOffsetOverlay = ({
13260
13570
  strokeDasharray: VISUAL_CONFIG.LINE_DASH_PATTERN
13261
13571
  }
13262
13572
  ),
13263
- target.type === "component" || target.type === "board" ? /* @__PURE__ */ jsx12(
13573
+ target.type === "component" || target.type === "board" ? /* @__PURE__ */ jsx13(
13264
13574
  "circle",
13265
13575
  {
13266
13576
  cx: targetScreen.x,
@@ -13272,8 +13582,8 @@ var AnchorOffsetOverlay = ({
13272
13582
  }
13273
13583
  ) : (
13274
13584
  // assumes "group"
13275
- /* @__PURE__ */ jsxs9(Fragment5, { children: [
13276
- /* @__PURE__ */ jsx12(
13585
+ /* @__PURE__ */ jsxs10(Fragment4, { children: [
13586
+ /* @__PURE__ */ jsx13(
13277
13587
  "line",
13278
13588
  {
13279
13589
  x1: targetScreen.x - VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
@@ -13284,7 +13594,7 @@ var AnchorOffsetOverlay = ({
13284
13594
  strokeWidth: VISUAL_CONFIG.ANCHOR_MARKER_STROKE_WIDTH
13285
13595
  }
13286
13596
  ),
13287
- /* @__PURE__ */ jsx12(
13597
+ /* @__PURE__ */ jsx13(
13288
13598
  "line",
13289
13599
  {
13290
13600
  x1: targetScreen.x,
@@ -13297,7 +13607,7 @@ var AnchorOffsetOverlay = ({
13297
13607
  )
13298
13608
  ] })
13299
13609
  ),
13300
- shouldShowXLabel && /* @__PURE__ */ jsx12(
13610
+ shouldShowXLabel && /* @__PURE__ */ jsx13(
13301
13611
  "foreignObject",
13302
13612
  {
13303
13613
  x: Math.min(anchorMarkerScreen.x, targetScreen.x),
@@ -13305,10 +13615,10 @@ var AnchorOffsetOverlay = ({
13305
13615
  width: Math.abs(targetScreen.x - anchorMarkerScreen.x),
13306
13616
  height: 20,
13307
13617
  style: { overflow: "visible" },
13308
- children: /* @__PURE__ */ jsx12("div", { style: { ...labelStyle, textAlign: "center" }, children: xLabelText })
13618
+ children: /* @__PURE__ */ jsx13("div", { style: { ...labelStyle, textAlign: "center" }, children: xLabelText })
13309
13619
  }
13310
13620
  ),
13311
- shouldShowYLabel && /* @__PURE__ */ jsx12(
13621
+ shouldShowYLabel && /* @__PURE__ */ jsx13(
13312
13622
  "foreignObject",
13313
13623
  {
13314
13624
  x: targetScreen.x + yLabelOffset,
@@ -13316,7 +13626,7 @@ var AnchorOffsetOverlay = ({
13316
13626
  width: VISUAL_CONFIG.Y_LABEL_MIN_WIDTH,
13317
13627
  height: Math.abs(targetScreen.y - anchorMarkerScreen.y),
13318
13628
  style: { overflow: "visible" },
13319
- children: /* @__PURE__ */ jsx12(
13629
+ children: /* @__PURE__ */ jsx13(
13320
13630
  "div",
13321
13631
  {
13322
13632
  style: {
@@ -13335,8 +13645,8 @@ var AnchorOffsetOverlay = ({
13335
13645
  )
13336
13646
  ] }, target.id);
13337
13647
  }),
13338
- Array.from(anchorScreens.entries()).map(([anchorId, anchorScreen]) => /* @__PURE__ */ jsxs9("g", { children: [
13339
- /* @__PURE__ */ jsx12(
13648
+ Array.from(anchorScreens.entries()).map(([anchorId, anchorScreen]) => /* @__PURE__ */ jsxs10("g", { children: [
13649
+ /* @__PURE__ */ jsx13(
13340
13650
  "line",
13341
13651
  {
13342
13652
  x1: anchorScreen.x - VISUAL_CONFIG.ANCHOR_MARKER_SIZE,
@@ -13347,7 +13657,7 @@ var AnchorOffsetOverlay = ({
13347
13657
  strokeWidth: VISUAL_CONFIG.ANCHOR_MARKER_STROKE_WIDTH
13348
13658
  }
13349
13659
  ),
13350
- /* @__PURE__ */ jsx12(
13660
+ /* @__PURE__ */ jsx13(
13351
13661
  "line",
13352
13662
  {
13353
13663
  x1: anchorScreen.x,
@@ -13367,7 +13677,7 @@ var AnchorOffsetOverlay = ({
13367
13677
  };
13368
13678
 
13369
13679
  // src/components/AnchorOffsetOverlay/BoardAnchorOffsetOverlay.tsx
13370
- import { jsx as jsx13 } from "react/jsx-runtime";
13680
+ import { jsx as jsx14 } from "react/jsx-runtime";
13371
13681
  var BoardAnchorOffsetOverlay = ({
13372
13682
  elements,
13373
13683
  highlightedPrimitives,
@@ -13454,7 +13764,7 @@ var BoardAnchorOffsetOverlay = ({
13454
13764
  display_offset_y: target.group.display_offset_y
13455
13765
  };
13456
13766
  }).filter((t) => Boolean(t));
13457
- return /* @__PURE__ */ jsx13(
13767
+ return /* @__PURE__ */ jsx14(
13458
13768
  AnchorOffsetOverlay,
13459
13769
  {
13460
13770
  targets: sharedTargets,
@@ -13466,7 +13776,7 @@ var BoardAnchorOffsetOverlay = ({
13466
13776
  };
13467
13777
 
13468
13778
  // src/components/AnchorOffsetOverlay/GroupAnchorOffsetOverlay.tsx
13469
- import { jsx as jsx14 } from "react/jsx-runtime";
13779
+ import { jsx as jsx15 } from "react/jsx-runtime";
13470
13780
  var GroupAnchorOffsetOverlay = ({
13471
13781
  elements,
13472
13782
  highlightedPrimitives,
@@ -13582,7 +13892,7 @@ var GroupAnchorOffsetOverlay = ({
13582
13892
  display_offset_y: target.group.display_offset_y
13583
13893
  };
13584
13894
  }).filter((t) => Boolean(t));
13585
- return /* @__PURE__ */ jsx14(
13895
+ return /* @__PURE__ */ jsx15(
13586
13896
  AnchorOffsetOverlay,
13587
13897
  {
13588
13898
  targets: sharedTargets,
@@ -13595,7 +13905,7 @@ var GroupAnchorOffsetOverlay = ({
13595
13905
 
13596
13906
  // src/components/AnchorOffsetOverlay/ComponentBoundingBoxOverlay.tsx
13597
13907
  import { applyToPoint as applyToPoint13 } from "transformation-matrix";
13598
- import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
13908
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
13599
13909
  var calculateComponentBoundingBox = (component, elements) => {
13600
13910
  const componentId = component.pcb_component_id;
13601
13911
  const padsAndHoles = elements.filter(
@@ -13638,7 +13948,7 @@ var ComponentBoundingBoxOverlay = ({
13638
13948
  renderData.push({ component, bbox, group });
13639
13949
  }
13640
13950
  if (renderData.length === 0) return null;
13641
- return /* @__PURE__ */ jsx15(
13951
+ return /* @__PURE__ */ jsx16(
13642
13952
  "div",
13643
13953
  {
13644
13954
  style: {
@@ -13651,7 +13961,7 @@ var ComponentBoundingBoxOverlay = ({
13651
13961
  pointerEvents: "none",
13652
13962
  zIndex: zIndexMap.dimensionOverlay
13653
13963
  },
13654
- children: /* @__PURE__ */ jsx15(
13964
+ children: /* @__PURE__ */ jsx16(
13655
13965
  "svg",
13656
13966
  {
13657
13967
  style: { position: "absolute", left: 0, top: 0, pointerEvents: "none" },
@@ -13677,8 +13987,8 @@ var ComponentBoundingBoxOverlay = ({
13677
13987
  y: (bbox.minY + bbox.maxY) / 2
13678
13988
  };
13679
13989
  const componentCenterScreen = applyToPoint13(transform, componentCenter);
13680
- return /* @__PURE__ */ jsxs10("g", { children: [
13681
- /* @__PURE__ */ jsx15(
13990
+ return /* @__PURE__ */ jsxs11("g", { children: [
13991
+ /* @__PURE__ */ jsx16(
13682
13992
  "rect",
13683
13993
  {
13684
13994
  x: screenBbox.x,
@@ -13691,7 +14001,7 @@ var ComponentBoundingBoxOverlay = ({
13691
14001
  strokeDasharray: "4,4"
13692
14002
  }
13693
14003
  ),
13694
- /* @__PURE__ */ jsx15(
14004
+ /* @__PURE__ */ jsx16(
13695
14005
  "line",
13696
14006
  {
13697
14007
  x1: componentCenterScreen.x - 6,
@@ -13702,7 +14012,7 @@ var ComponentBoundingBoxOverlay = ({
13702
14012
  strokeWidth: 1.5
13703
14013
  }
13704
14014
  ),
13705
- /* @__PURE__ */ jsx15(
14015
+ /* @__PURE__ */ jsx16(
13706
14016
  "line",
13707
14017
  {
13708
14018
  x1: componentCenterScreen.x,
@@ -13713,7 +14023,7 @@ var ComponentBoundingBoxOverlay = ({
13713
14023
  strokeWidth: 1.5
13714
14024
  }
13715
14025
  ),
13716
- /* @__PURE__ */ jsx15(
14026
+ /* @__PURE__ */ jsx16(
13717
14027
  "circle",
13718
14028
  {
13719
14029
  cx: componentCenterScreen.x,
@@ -13733,7 +14043,7 @@ var ComponentBoundingBoxOverlay = ({
13733
14043
  };
13734
14044
 
13735
14045
  // src/components/AnchorOffsetOverlay/PanelAnchorOffsetOverlay.tsx
13736
- import { jsx as jsx16 } from "react/jsx-runtime";
14046
+ import { jsx as jsx17 } from "react/jsx-runtime";
13737
14047
  var PanelAnchorOffsetOverlay = ({
13738
14048
  elements,
13739
14049
  highlightedPrimitives,
@@ -13782,7 +14092,7 @@ var PanelAnchorOffsetOverlay = ({
13782
14092
  display_offset_y: target.board.display_offset_y
13783
14093
  };
13784
14094
  }).filter((t) => Boolean(t));
13785
- return /* @__PURE__ */ jsx16(
14095
+ return /* @__PURE__ */ jsx17(
13786
14096
  AnchorOffsetOverlay,
13787
14097
  {
13788
14098
  targets: sharedTargets,
@@ -13794,7 +14104,7 @@ var PanelAnchorOffsetOverlay = ({
13794
14104
  };
13795
14105
 
13796
14106
  // src/components/MouseElementTracker.tsx
13797
- import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
14107
+ import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
13798
14108
  var getPolygonBoundingBox = (points) => {
13799
14109
  if (points.length === 0) return null;
13800
14110
  let minX = points[0].x;
@@ -13981,7 +14291,7 @@ var MouseElementTracker = ({
13981
14291
  setMousedPrimitives(newMousedPrimitives);
13982
14292
  onMouseHoverOverPrimitives(newMousedPrimitives);
13983
14293
  };
13984
- return /* @__PURE__ */ jsxs11(
14294
+ return /* @__PURE__ */ jsxs12(
13985
14295
  "div",
13986
14296
  {
13987
14297
  ref: containerRef,
@@ -14005,7 +14315,7 @@ var MouseElementTracker = ({
14005
14315
  },
14006
14316
  children: [
14007
14317
  children,
14008
- /* @__PURE__ */ jsx17(
14318
+ /* @__PURE__ */ jsx18(
14009
14319
  ElementOverlayBox,
14010
14320
  {
14011
14321
  elements,
@@ -14013,8 +14323,8 @@ var MouseElementTracker = ({
14013
14323
  highlightedPrimitives
14014
14324
  }
14015
14325
  ),
14016
- transform && /* @__PURE__ */ jsxs11(Fragment6, { children: [
14017
- /* @__PURE__ */ jsx17(
14326
+ transform && /* @__PURE__ */ jsxs12(Fragment5, { children: [
14327
+ /* @__PURE__ */ jsx18(
14018
14328
  BoardAnchorOffsetOverlay,
14019
14329
  {
14020
14330
  elements,
@@ -14024,7 +14334,7 @@ var MouseElementTracker = ({
14024
14334
  containerHeight: height
14025
14335
  }
14026
14336
  ),
14027
- /* @__PURE__ */ jsx17(
14337
+ /* @__PURE__ */ jsx18(
14028
14338
  GroupAnchorOffsetOverlay,
14029
14339
  {
14030
14340
  elements,
@@ -14034,7 +14344,7 @@ var MouseElementTracker = ({
14034
14344
  containerHeight: height
14035
14345
  }
14036
14346
  ),
14037
- /* @__PURE__ */ jsx17(
14347
+ /* @__PURE__ */ jsx18(
14038
14348
  ComponentBoundingBoxOverlay,
14039
14349
  {
14040
14350
  elements,
@@ -14044,7 +14354,7 @@ var MouseElementTracker = ({
14044
14354
  containerHeight: height
14045
14355
  }
14046
14356
  ),
14047
- /* @__PURE__ */ jsx17(
14357
+ /* @__PURE__ */ jsx18(
14048
14358
  PanelAnchorOffsetOverlay,
14049
14359
  {
14050
14360
  elements,
@@ -14064,7 +14374,7 @@ var MouseElementTracker = ({
14064
14374
  import { applyToPoint as applyToPoint15 } from "transformation-matrix";
14065
14375
  import { identity as identity8 } from "transformation-matrix";
14066
14376
  import { useRef as useRef10, useEffect as useEffect13 } from "react";
14067
- import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
14377
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
14068
14378
  var GROUP_COLORS = [
14069
14379
  "rgb(255, 100, 100)",
14070
14380
  "rgb(100, 255, 100)",
@@ -14300,14 +14610,14 @@ var PcbGroupOverlay = ({
14300
14610
  is_showing_group_anchor_offsets,
14301
14611
  hoveredComponentIds
14302
14612
  ]);
14303
- return /* @__PURE__ */ jsxs12(
14613
+ return /* @__PURE__ */ jsxs13(
14304
14614
  "div",
14305
14615
  {
14306
14616
  ref: containerRef,
14307
14617
  style: { position: "relative", width: "100%", height: "100%" },
14308
14618
  children: [
14309
14619
  children,
14310
- /* @__PURE__ */ jsx18(
14620
+ /* @__PURE__ */ jsx19(
14311
14621
  "canvas",
14312
14622
  {
14313
14623
  ref: canvasRef,
@@ -14329,7 +14639,7 @@ var PcbGroupOverlay = ({
14329
14639
  // src/components/RatsNestOverlay.tsx
14330
14640
  import { applyToPoint as applyToPoint16, identity as identity9 } from "transformation-matrix";
14331
14641
  import { useMemo as useMemo6 } from "react";
14332
- import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
14642
+ import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
14333
14643
  var RatsNestOverlay = ({ transform, soup, children }) => {
14334
14644
  const isShowingRatsNest = useGlobalStore((s) => s.is_showing_rats_nest);
14335
14645
  const { netMap, idToNetMap } = useMemo6(
@@ -14392,9 +14702,9 @@ var RatsNestOverlay = ({ transform, soup, children }) => {
14392
14702
  }, [soup, netMap, idToNetMap, isShowingRatsNest]);
14393
14703
  if (!soup || !isShowingRatsNest) return children;
14394
14704
  if (!transform) transform = identity9();
14395
- return /* @__PURE__ */ jsxs13("div", { style: { position: "relative" }, children: [
14705
+ return /* @__PURE__ */ jsxs14("div", { style: { position: "relative" }, children: [
14396
14706
  children,
14397
- /* @__PURE__ */ jsx19(
14707
+ /* @__PURE__ */ jsx20(
14398
14708
  "svg",
14399
14709
  {
14400
14710
  style: {
@@ -14410,7 +14720,7 @@ var RatsNestOverlay = ({ transform, soup, children }) => {
14410
14720
  children: ratsNestLines.map(({ key, startPoint, endPoint, isInNet }) => {
14411
14721
  const transformedStart = applyToPoint16(transform, startPoint);
14412
14722
  const transformedEnd = applyToPoint16(transform, endPoint);
14413
- return /* @__PURE__ */ jsx19(
14723
+ return /* @__PURE__ */ jsx20(
14414
14724
  "line",
14415
14725
  {
14416
14726
  x1: transformedStart.x,
@@ -14431,10 +14741,10 @@ var RatsNestOverlay = ({ transform, soup, children }) => {
14431
14741
 
14432
14742
  // src/components/ToolbarOverlay.tsx
14433
14743
  import {
14434
- useEffect as useEffect16,
14744
+ useEffect as useEffect17,
14435
14745
  useState as useState11,
14436
- useCallback as useCallback9,
14437
- useRef as useRef13,
14746
+ useCallback as useCallback10,
14747
+ useRef as useRef14,
14438
14748
  useLayoutEffect as useLayoutEffect2
14439
14749
  } from "react";
14440
14750
  import { css as css3 } from "@emotion/css";
@@ -14442,7 +14752,7 @@ import { css as css3 } from "@emotion/css";
14442
14752
  // package.json
14443
14753
  var package_default = {
14444
14754
  name: "@tscircuit/pcb-viewer",
14445
- version: "1.11.364",
14755
+ version: "1.11.366",
14446
14756
  main: "dist/index.js",
14447
14757
  type: "module",
14448
14758
  repository: "tscircuit/pcb-viewer",
@@ -14597,7 +14907,7 @@ var useMobileTouch = (onClick, options = { stopPropagation: true }) => {
14597
14907
  };
14598
14908
 
14599
14909
  // src/components/ToolbarButton.tsx
14600
- import { jsx as jsx20 } from "react/jsx-runtime";
14910
+ import { jsx as jsx21 } from "react/jsx-runtime";
14601
14911
  var ToolbarButton = ({
14602
14912
  children,
14603
14913
  isSmallScreen,
@@ -14605,7 +14915,7 @@ var ToolbarButton = ({
14605
14915
  ...props
14606
14916
  }) => {
14607
14917
  const { style: touchStyle, ...touchHandlers } = useMobileTouch(onClick);
14608
- return /* @__PURE__ */ jsx20(
14918
+ return /* @__PURE__ */ jsx21(
14609
14919
  "div",
14610
14920
  {
14611
14921
  ...props,
@@ -14633,8 +14943,87 @@ var ToolbarButton = ({
14633
14943
  };
14634
14944
 
14635
14945
  // src/components/ToolbarErrorDropdown.tsx
14636
- import { useCallback as useCallback8, useMemo as useMemo7, useState as useState10 } from "react";
14637
- import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
14946
+ import { useCallback as useCallback9, useMemo as useMemo7, useState as useState10 } from "react";
14947
+
14948
+ // src/lib/util/group-errors-by-type.ts
14949
+ var groupErrorsByType = (errors) => {
14950
+ const groups = /* @__PURE__ */ new Map();
14951
+ errors.forEach((error, index) => {
14952
+ const errorType = error.error_type || "unknown_error";
14953
+ const existingGroup = groups.get(errorType) || [];
14954
+ existingGroup.push({
14955
+ error,
14956
+ index,
14957
+ errorId: getErrorId(error, index)
14958
+ });
14959
+ groups.set(errorType, existingGroup);
14960
+ });
14961
+ return Array.from(groups.entries()).map(([errorType, groupedErrors]) => ({
14962
+ errorType,
14963
+ errors: groupedErrors
14964
+ }));
14965
+ };
14966
+
14967
+ // src/components/useToolbarErrorFocus.ts
14968
+ import { useCallback as useCallback8, useEffect as useEffect16, useRef as useRef13 } from "react";
14969
+ var useToolbarErrorFocus = ({
14970
+ onClose,
14971
+ setHoveredErrorId,
14972
+ setFocusedErrorId,
14973
+ highlightDurationMs = 3e3
14974
+ }) => {
14975
+ const clearHighlightTimeoutRef = useRef13(null);
14976
+ const focusRequestRef = useRef13(null);
14977
+ const selectedErrorIdRef = useRef13(null);
14978
+ const clearPendingSelection = useCallback8(() => {
14979
+ if (clearHighlightTimeoutRef.current !== null) {
14980
+ window.clearTimeout(clearHighlightTimeoutRef.current);
14981
+ clearHighlightTimeoutRef.current = null;
14982
+ }
14983
+ if (focusRequestRef.current !== null) {
14984
+ window.cancelAnimationFrame(focusRequestRef.current);
14985
+ focusRequestRef.current = null;
14986
+ }
14987
+ }, []);
14988
+ useEffect16(() => clearPendingSelection, [clearPendingSelection]);
14989
+ const handleErrorSelect = useCallback8(
14990
+ (errorId) => {
14991
+ clearPendingSelection();
14992
+ selectedErrorIdRef.current = errorId;
14993
+ setHoveredErrorId(errorId);
14994
+ setFocusedErrorId(null);
14995
+ focusRequestRef.current = window.requestAnimationFrame(() => {
14996
+ setFocusedErrorId(errorId);
14997
+ focusRequestRef.current = null;
14998
+ });
14999
+ onClose();
15000
+ clearHighlightTimeoutRef.current = window.setTimeout(() => {
15001
+ selectedErrorIdRef.current = null;
15002
+ setHoveredErrorId(null);
15003
+ setFocusedErrorId(null);
15004
+ clearHighlightTimeoutRef.current = null;
15005
+ }, highlightDurationMs);
15006
+ },
15007
+ [
15008
+ clearPendingSelection,
15009
+ highlightDurationMs,
15010
+ onClose,
15011
+ setFocusedErrorId,
15012
+ setHoveredErrorId
15013
+ ]
15014
+ );
15015
+ const isSelectedError = useCallback8(
15016
+ (errorId) => selectedErrorIdRef.current === errorId,
15017
+ []
15018
+ );
15019
+ return {
15020
+ handleErrorSelect,
15021
+ isSelectedError
15022
+ };
15023
+ };
15024
+
15025
+ // src/components/ToolbarErrorDropdown.tsx
15026
+ import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
14638
15027
  var CopyErrorButton = ({
14639
15028
  errorId,
14640
15029
  errorMessage,
@@ -14644,7 +15033,7 @@ var CopyErrorButton = ({
14644
15033
  const { style: touchStyle, ...touchHandlers } = useMobileTouch(
14645
15034
  () => onCopy(errorMessage, errorId)
14646
15035
  );
14647
- return /* @__PURE__ */ jsx21(
15036
+ return /* @__PURE__ */ jsx22(
14648
15037
  "button",
14649
15038
  {
14650
15039
  type: "button",
@@ -14664,7 +15053,7 @@ var CopyErrorButton = ({
14664
15053
  ...touchStyle
14665
15054
  },
14666
15055
  ...touchHandlers,
14667
- children: copiedErrorId === errorId ? /* @__PURE__ */ jsx21("span", { style: { color: "#4caf50", fontSize: 12 }, children: "Copied!" }) : /* @__PURE__ */ jsx21("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx21("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" }) })
15056
+ children: copiedErrorId === errorId ? /* @__PURE__ */ jsx22("span", { style: { color: "#4caf50", fontSize: 12 }, children: "Copied!" }) : /* @__PURE__ */ jsx22("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx22("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" }) })
14668
15057
  }
14669
15058
  );
14670
15059
  };
@@ -14673,7 +15062,9 @@ var ToolbarErrorDropdown = ({
14673
15062
  isOpen,
14674
15063
  isSmallScreen,
14675
15064
  onToggle,
14676
- setHoveredErrorId
15065
+ onClose,
15066
+ setHoveredErrorId,
15067
+ setFocusedErrorId
14677
15068
  }) => {
14678
15069
  const [copiedErrorId, setCopiedErrorId] = useState10(null);
14679
15070
  const [collapsedErrorGroups, setCollapsedErrorGroups] = useState10(
@@ -14683,6 +15074,11 @@ var ToolbarErrorDropdown = ({
14683
15074
  /* @__PURE__ */ new Set()
14684
15075
  );
14685
15076
  const [, copyToClipboard] = useCopyToClipboard_default();
15077
+ const { handleErrorSelect, isSelectedError } = useToolbarErrorFocus({
15078
+ onClose,
15079
+ setHoveredErrorId,
15080
+ setFocusedErrorId
15081
+ });
14686
15082
  const errorElements = useMemo7(
14687
15083
  () => elements?.filter(
14688
15084
  (el) => el.type.includes("error")
@@ -14690,24 +15086,11 @@ var ToolbarErrorDropdown = ({
14690
15086
  [elements]
14691
15087
  );
14692
15088
  const errorCount = errorElements.length;
14693
- const groupedErrorElements = useMemo7(() => {
14694
- const groups = /* @__PURE__ */ new Map();
14695
- errorElements.forEach((error, index) => {
14696
- const errorType = error.error_type || "unknown_error";
14697
- const existingGroup = groups.get(errorType) || [];
14698
- existingGroup.push({
14699
- error,
14700
- index,
14701
- errorId: error.pcb_trace_error_id
14702
- });
14703
- groups.set(errorType, existingGroup);
14704
- });
14705
- return Array.from(groups.entries()).map(([errorType, errors]) => ({
14706
- errorType,
14707
- errors
14708
- }));
14709
- }, [errorElements]);
14710
- const toggleErrorGroup = useCallback8((errorType) => {
15089
+ const groupedErrorElements = useMemo7(
15090
+ () => groupErrorsByType(errorElements),
15091
+ [errorElements]
15092
+ );
15093
+ const toggleErrorGroup = useCallback9((errorType) => {
14711
15094
  setCollapsedErrorGroups((prev) => {
14712
15095
  const next = new Set(prev);
14713
15096
  if (next.has(errorType)) {
@@ -14718,7 +15101,7 @@ var ToolbarErrorDropdown = ({
14718
15101
  return next;
14719
15102
  });
14720
15103
  }, []);
14721
- const toggleExpandedError = useCallback8((errorId) => {
15104
+ const toggleExpandedError = useCallback9((errorId) => {
14722
15105
  setExpandedErrorIds((prev) => {
14723
15106
  const next = new Set(prev);
14724
15107
  if (next.has(errorId)) {
@@ -14729,20 +15112,20 @@ var ToolbarErrorDropdown = ({
14729
15112
  return next;
14730
15113
  });
14731
15114
  }, []);
14732
- return /* @__PURE__ */ jsxs14("div", { style: { position: "relative" }, children: [
14733
- /* @__PURE__ */ jsx21(
15115
+ return /* @__PURE__ */ jsxs15("div", { style: { position: "relative" }, children: [
15116
+ /* @__PURE__ */ jsx22(
14734
15117
  ToolbarButton,
14735
15118
  {
14736
15119
  isSmallScreen,
14737
15120
  style: errorCount > 0 ? { color: "red" } : void 0,
14738
15121
  onClick: onToggle,
14739
- children: /* @__PURE__ */ jsxs14("div", { children: [
15122
+ children: /* @__PURE__ */ jsxs15("div", { children: [
14740
15123
  errorCount,
14741
15124
  " errors"
14742
15125
  ] })
14743
15126
  }
14744
15127
  ),
14745
- isOpen && errorCount > 0 && /* @__PURE__ */ jsx21(
15128
+ isOpen && errorCount > 0 && /* @__PURE__ */ jsx22(
14746
15129
  "div",
14747
15130
  {
14748
15131
  style: {
@@ -14762,14 +15145,14 @@ var ToolbarErrorDropdown = ({
14762
15145
  },
14763
15146
  children: groupedErrorElements.map(({ errorType, errors }, groupIndex) => {
14764
15147
  const isGroupCollapsed = collapsedErrorGroups.has(errorType);
14765
- return /* @__PURE__ */ jsxs14(
15148
+ return /* @__PURE__ */ jsxs15(
14766
15149
  "div",
14767
15150
  {
14768
15151
  style: {
14769
15152
  borderBottom: groupIndex < groupedErrorElements.length - 1 ? "1px solid #444" : "none"
14770
15153
  },
14771
15154
  children: [
14772
- /* @__PURE__ */ jsxs14(
15155
+ /* @__PURE__ */ jsxs15(
14773
15156
  "div",
14774
15157
  {
14775
15158
  style: {
@@ -14806,7 +15189,7 @@ var ToolbarErrorDropdown = ({
14806
15189
  toggleErrorGroup(errorType);
14807
15190
  },
14808
15191
  children: [
14809
- /* @__PURE__ */ jsxs14(
15192
+ /* @__PURE__ */ jsxs15(
14810
15193
  "div",
14811
15194
  {
14812
15195
  style: {
@@ -14816,7 +15199,7 @@ var ToolbarErrorDropdown = ({
14816
15199
  color: "#ff6b6b"
14817
15200
  },
14818
15201
  children: [
14819
- /* @__PURE__ */ jsx21(
15202
+ /* @__PURE__ */ jsx22(
14820
15203
  "div",
14821
15204
  {
14822
15205
  style: {
@@ -14829,7 +15212,7 @@ var ToolbarErrorDropdown = ({
14829
15212
  children: "\u203A"
14830
15213
  }
14831
15214
  ),
14832
- /* @__PURE__ */ jsx21(
15215
+ /* @__PURE__ */ jsx22(
14833
15216
  "div",
14834
15217
  {
14835
15218
  style: {
@@ -14842,7 +15225,7 @@ var ToolbarErrorDropdown = ({
14842
15225
  ]
14843
15226
  }
14844
15227
  ),
14845
- /* @__PURE__ */ jsx21(
15228
+ /* @__PURE__ */ jsx22(
14846
15229
  "div",
14847
15230
  {
14848
15231
  style: {
@@ -14860,14 +15243,14 @@ var ToolbarErrorDropdown = ({
14860
15243
  !isGroupCollapsed && errors.map(({ error, index, errorId }) => {
14861
15244
  const isExpanded = expandedErrorIds.has(errorId);
14862
15245
  const errorMessage = error.message ?? "No error message";
14863
- return /* @__PURE__ */ jsxs14(
15246
+ return /* @__PURE__ */ jsxs15(
14864
15247
  "div",
14865
15248
  {
14866
15249
  style: {
14867
15250
  borderTop: "1px solid #3a3a3a"
14868
15251
  },
14869
15252
  children: [
14870
- /* @__PURE__ */ jsxs14(
15253
+ /* @__PURE__ */ jsxs15(
14871
15254
  "div",
14872
15255
  {
14873
15256
  style: {
@@ -14886,7 +15269,9 @@ var ToolbarErrorDropdown = ({
14886
15269
  },
14887
15270
  onMouseLeave: (e) => {
14888
15271
  e.currentTarget.style.backgroundColor = "#2a2a2a";
14889
- setHoveredErrorId(null);
15272
+ if (!isSelectedError(errorId)) {
15273
+ setHoveredErrorId(null);
15274
+ }
14890
15275
  },
14891
15276
  onTouchStart: (e) => {
14892
15277
  e.stopPropagation();
@@ -14897,15 +15282,14 @@ var ToolbarErrorDropdown = ({
14897
15282
  e.stopPropagation();
14898
15283
  e.preventDefault();
14899
15284
  e.currentTarget.style.backgroundColor = "#2a2a2a";
14900
- setHoveredErrorId(null);
14901
- toggleExpandedError(errorId);
15285
+ handleErrorSelect(errorId);
14902
15286
  },
14903
15287
  onClick: (e) => {
14904
15288
  e.stopPropagation();
14905
- toggleExpandedError(errorId);
15289
+ handleErrorSelect(errorId);
14906
15290
  },
14907
15291
  children: [
14908
- /* @__PURE__ */ jsx21(
15292
+ /* @__PURE__ */ jsx22(
14909
15293
  "div",
14910
15294
  {
14911
15295
  style: {
@@ -14918,20 +15302,34 @@ var ToolbarErrorDropdown = ({
14918
15302
  whiteSpace: "nowrap",
14919
15303
  userSelect: "text"
14920
15304
  },
14921
- onMouseDown: (event) => event.stopPropagation(),
14922
- onClick: (event) => event.stopPropagation(),
14923
15305
  children: errorMessage
14924
15306
  }
14925
15307
  ),
14926
- /* @__PURE__ */ jsx21(
14927
- "div",
15308
+ /* @__PURE__ */ jsx22(
15309
+ "button",
14928
15310
  {
15311
+ type: "button",
15312
+ "aria-label": isExpanded ? "Collapse error details" : "Expand error details",
14929
15313
  style: {
14930
15314
  color: "#888",
14931
15315
  fontSize: "16px",
14932
15316
  transform: isExpanded ? "rotate(0deg)" : "rotate(90deg)",
14933
15317
  transition: "transform 0.2s ease",
14934
- flexShrink: 0
15318
+ flexShrink: 0,
15319
+ background: "none",
15320
+ border: "none",
15321
+ padding: 0,
15322
+ cursor: "pointer"
15323
+ },
15324
+ onMouseDown: (event) => event.stopPropagation(),
15325
+ onClick: (event) => {
15326
+ event.stopPropagation();
15327
+ toggleExpandedError(errorId);
15328
+ },
15329
+ onTouchEnd: (event) => {
15330
+ event.stopPropagation();
15331
+ event.preventDefault();
15332
+ toggleExpandedError(errorId);
14935
15333
  },
14936
15334
  children: "\u203A"
14937
15335
  }
@@ -14939,7 +15337,7 @@ var ToolbarErrorDropdown = ({
14939
15337
  ]
14940
15338
  }
14941
15339
  ),
14942
- isExpanded && /* @__PURE__ */ jsxs14(
15340
+ isExpanded && /* @__PURE__ */ jsxs15(
14943
15341
  "div",
14944
15342
  {
14945
15343
  "data-error-id": index,
@@ -14951,7 +15349,7 @@ var ToolbarErrorDropdown = ({
14951
15349
  position: "relative"
14952
15350
  },
14953
15351
  children: [
14954
- /* @__PURE__ */ jsx21(
15352
+ /* @__PURE__ */ jsx22(
14955
15353
  "div",
14956
15354
  {
14957
15355
  style: {
@@ -14969,7 +15367,7 @@ var ToolbarErrorDropdown = ({
14969
15367
  children: errorMessage
14970
15368
  }
14971
15369
  ),
14972
- /* @__PURE__ */ jsx21(
15370
+ /* @__PURE__ */ jsx22(
14973
15371
  CopyErrorButton,
14974
15372
  {
14975
15373
  errorId,
@@ -15001,10 +15399,10 @@ var ToolbarErrorDropdown = ({
15001
15399
  };
15002
15400
 
15003
15401
  // src/components/ToolbarOverlay.tsx
15004
- import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
15402
+ import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
15005
15403
  var LayerButton = ({ name, selected, onClick }) => {
15006
15404
  const { style: touchStyle, ...touchHandlers } = useMobileTouch(onClick);
15007
- return /* @__PURE__ */ jsxs15(
15405
+ return /* @__PURE__ */ jsxs16(
15008
15406
  "div",
15009
15407
  {
15010
15408
  className: css3`
@@ -15021,8 +15419,8 @@ var LayerButton = ({ name, selected, onClick }) => {
15021
15419
  ...touchHandlers,
15022
15420
  style: touchStyle,
15023
15421
  children: [
15024
- /* @__PURE__ */ jsx22("span", { style: { marginRight: 2, opacity: selected ? 1 : 0 }, children: "\u2022" }),
15025
- /* @__PURE__ */ jsx22(
15422
+ /* @__PURE__ */ jsx23("span", { style: { marginRight: 2, opacity: selected ? 1 : 0 }, children: "\u2022" }),
15423
+ /* @__PURE__ */ jsx23(
15026
15424
  "span",
15027
15425
  {
15028
15426
  style: {
@@ -15043,7 +15441,7 @@ var CheckboxMenuItem = ({
15043
15441
  onClick
15044
15442
  }) => {
15045
15443
  const { style: touchStyle, ...touchHandlers } = useMobileTouch(onClick);
15046
- return /* @__PURE__ */ jsxs15(
15444
+ return /* @__PURE__ */ jsxs16(
15047
15445
  "div",
15048
15446
  {
15049
15447
  className: css3`
@@ -15063,16 +15461,16 @@ var CheckboxMenuItem = ({
15063
15461
  ...touchHandlers,
15064
15462
  style: touchStyle,
15065
15463
  children: [
15066
- /* @__PURE__ */ jsx22("input", { type: "checkbox", checked, onChange: () => {
15464
+ /* @__PURE__ */ jsx23("input", { type: "checkbox", checked, onChange: () => {
15067
15465
  }, readOnly: true }),
15068
- /* @__PURE__ */ jsx22("span", { style: { color: "#eee" }, children: label })
15466
+ /* @__PURE__ */ jsx23("span", { style: { color: "#eee" }, children: label })
15069
15467
  ]
15070
15468
  }
15071
15469
  );
15072
15470
  };
15073
15471
  var RadioMenuItem = ({ label, checked, onClick }) => {
15074
15472
  const { style: touchStyle, ...touchHandlers } = useMobileTouch(onClick);
15075
- return /* @__PURE__ */ jsxs15(
15473
+ return /* @__PURE__ */ jsxs16(
15076
15474
  "div",
15077
15475
  {
15078
15476
  className: css3`
@@ -15092,9 +15490,9 @@ var RadioMenuItem = ({ label, checked, onClick }) => {
15092
15490
  ...touchHandlers,
15093
15491
  style: touchStyle,
15094
15492
  children: [
15095
- /* @__PURE__ */ jsx22("input", { type: "radio", checked, onChange: () => {
15493
+ /* @__PURE__ */ jsx23("input", { type: "radio", checked, onChange: () => {
15096
15494
  }, readOnly: true }),
15097
- /* @__PURE__ */ jsx22("span", { style: { color: "#eee" }, children: label })
15495
+ /* @__PURE__ */ jsx23("span", { style: { color: "#eee" }, children: label })
15098
15496
  ]
15099
15497
  }
15100
15498
  );
@@ -15121,7 +15519,8 @@ var ToolbarOverlay = ({ children, elements }) => {
15121
15519
  setIsShowingSilkscreen,
15122
15520
  setIsShowingFabricationNotes,
15123
15521
  setPcbGroupViewMode,
15124
- setHoveredErrorId
15522
+ setHoveredErrorId,
15523
+ setFocusedErrorId
15125
15524
  } = useGlobalStore((s) => ({
15126
15525
  isMouseOverContainer: s.is_mouse_over_container,
15127
15526
  setIsMouseOverContainer: s.setIsMouseOverContainer,
@@ -15158,13 +15557,14 @@ var ToolbarOverlay = ({ children, elements }) => {
15158
15557
  setIsShowingSilkscreen: s.setIsShowingSilkscreen,
15159
15558
  setIsShowingFabricationNotes: s.setIsShowingFabricationNotes,
15160
15559
  setPcbGroupViewMode: s.setPcbGroupViewMode,
15161
- setHoveredErrorId: s.setHoveredErrorId
15560
+ setHoveredErrorId: s.setHoveredErrorId,
15561
+ setFocusedErrorId: s.setFocusedErrorId
15162
15562
  }));
15163
15563
  const [isViewMenuOpen, setViewMenuOpen] = useState11(false);
15164
15564
  const [isLayerMenuOpen, setLayerMenuOpen] = useState11(false);
15165
15565
  const [isErrorsOpen, setErrorsOpen] = useState11(false);
15166
15566
  const [measureToolArmed, setMeasureToolArmed] = useState11(false);
15167
- useEffect16(() => {
15567
+ useEffect17(() => {
15168
15568
  const arm = () => setMeasureToolArmed(true);
15169
15569
  const disarm = () => setMeasureToolArmed(false);
15170
15570
  window.addEventListener("arm-dimension-tool", arm);
@@ -15182,8 +15582,8 @@ var ToolbarOverlay = ({ children, elements }) => {
15182
15582
  "bottom"
15183
15583
  ];
15184
15584
  const processedLayers = availableLayers;
15185
- const hasRunInitialMouseCheck = useRef13(false);
15186
- const hotkeyBoundaryRef = useRef13(null);
15585
+ const hasRunInitialMouseCheck = useRef14(false);
15586
+ const hotkeyBoundaryRef = useRef14(null);
15187
15587
  const hotKeyCallbacks = {
15188
15588
  "1": availableLayers[0] ? () => selectLayer(availableLayers[0]) : () => {
15189
15589
  },
@@ -15228,25 +15628,24 @@ var ToolbarOverlay = ({ children, elements }) => {
15228
15628
  document.removeEventListener("mousemove", checkMousePosition);
15229
15629
  };
15230
15630
  }, [setIsMouseOverContainer]);
15231
- const handleMouseEnter = useCallback9(() => {
15631
+ const handleMouseEnter = useCallback10(() => {
15232
15632
  setIsMouseOverContainer(true);
15233
15633
  }, [setIsMouseOverContainer]);
15234
- const handleMouseMove = useCallback9(() => {
15634
+ const handleMouseMove = useCallback10(() => {
15235
15635
  if (!isMouseOverContainer) {
15236
15636
  setIsMouseOverContainer(true);
15237
15637
  }
15238
15638
  }, [isMouseOverContainer, setIsMouseOverContainer]);
15239
- const handleMouseLeave = useCallback9(() => {
15639
+ const handleMouseLeave = useCallback10(() => {
15240
15640
  setIsMouseOverContainer(false);
15241
15641
  setLayerMenuOpen(false);
15242
15642
  setViewMenuOpen(false);
15243
15643
  setErrorsOpen(false);
15244
- setHoveredErrorId(null);
15245
- }, [setIsMouseOverContainer, setHoveredErrorId]);
15246
- const handleLayerMenuToggle = useCallback9(() => {
15644
+ }, [setIsMouseOverContainer]);
15645
+ const handleLayerMenuToggle = useCallback10(() => {
15247
15646
  setLayerMenuOpen(!isLayerMenuOpen);
15248
15647
  }, [isLayerMenuOpen]);
15249
- const handleErrorsToggle = useCallback9(() => {
15648
+ const handleErrorsToggle = useCallback10(() => {
15250
15649
  const newErrorsOpen = !isErrorsOpen;
15251
15650
  setErrorsOpen(newErrorsOpen);
15252
15651
  if (newErrorsOpen) {
@@ -15256,33 +15655,36 @@ var ToolbarOverlay = ({ children, elements }) => {
15256
15655
  setHoveredErrorId(null);
15257
15656
  }
15258
15657
  }, [isErrorsOpen, setHoveredErrorId]);
15259
- const handleEditTraceToggle = useCallback9(() => {
15658
+ const closeErrorsMenu = useCallback10(() => {
15659
+ setErrorsOpen(false);
15660
+ }, []);
15661
+ const handleEditTraceToggle = useCallback10(() => {
15260
15662
  setEditMode(editModes.in_draw_trace_mode ? "off" : "draw_trace");
15261
15663
  }, [editModes.in_draw_trace_mode, setEditMode]);
15262
- const handleMoveComponentToggle = useCallback9(() => {
15664
+ const handleMoveComponentToggle = useCallback10(() => {
15263
15665
  setEditMode(editModes.in_move_footprint_mode ? "off" : "move_footprint");
15264
15666
  }, [editModes.in_move_footprint_mode, setEditMode]);
15265
- const handleRatsNestToggle = useCallback9(() => {
15667
+ const handleRatsNestToggle = useCallback10(() => {
15266
15668
  setIsShowingRatsNest(!viewSettings.is_showing_rats_nest);
15267
15669
  }, [viewSettings.is_showing_rats_nest, setIsShowingRatsNest]);
15268
- const handleMeasureToolClick = useCallback9(() => {
15670
+ const handleMeasureToolClick = useCallback10(() => {
15269
15671
  setMeasureToolArmed(true);
15270
15672
  window.dispatchEvent(new Event("arm-dimension-tool"));
15271
15673
  }, []);
15272
- const handleViewMenuToggle = useCallback9(() => {
15674
+ const handleViewMenuToggle = useCallback10(() => {
15273
15675
  const newViewMenuOpen = !isViewMenuOpen;
15274
15676
  setViewMenuOpen(newViewMenuOpen);
15275
15677
  if (newViewMenuOpen) {
15276
15678
  setErrorsOpen(false);
15277
15679
  }
15278
15680
  }, [isViewMenuOpen]);
15279
- const stopCanvasInteractionPropagation = useCallback9(
15681
+ const stopCanvasInteractionPropagation = useCallback10(
15280
15682
  (event) => {
15281
15683
  event.stopPropagation();
15282
15684
  },
15283
15685
  []
15284
15686
  );
15285
- return /* @__PURE__ */ jsxs15(
15687
+ return /* @__PURE__ */ jsxs16(
15286
15688
  "div",
15287
15689
  {
15288
15690
  ref: hotkeyBoundaryRef,
@@ -15292,7 +15694,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15292
15694
  onMouseMove: handleMouseMove,
15293
15695
  children: [
15294
15696
  children,
15295
- /* @__PURE__ */ jsxs15(
15697
+ /* @__PURE__ */ jsxs16(
15296
15698
  "div",
15297
15699
  {
15298
15700
  style: {
@@ -15313,7 +15715,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15313
15715
  ]
15314
15716
  }
15315
15717
  ),
15316
- /* @__PURE__ */ jsxs15(
15718
+ /* @__PURE__ */ jsxs16(
15317
15719
  "div",
15318
15720
  {
15319
15721
  "data-toolbar-overlay": true,
@@ -15343,7 +15745,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15343
15745
  fontFamily: "sans-serif"
15344
15746
  },
15345
15747
  children: [
15346
- /* @__PURE__ */ jsxs15(
15748
+ /* @__PURE__ */ jsxs16(
15347
15749
  ToolbarButton,
15348
15750
  {
15349
15751
  isSmallScreen,
@@ -15354,10 +15756,10 @@ var ToolbarOverlay = ({ children, elements }) => {
15354
15756
  }
15355
15757
  },
15356
15758
  children: [
15357
- /* @__PURE__ */ jsxs15("div", { children: [
15759
+ /* @__PURE__ */ jsxs16("div", { children: [
15358
15760
  "layer:",
15359
15761
  " ",
15360
- /* @__PURE__ */ jsx22(
15762
+ /* @__PURE__ */ jsx23(
15361
15763
  "span",
15362
15764
  {
15363
15765
  style: {
@@ -15369,7 +15771,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15369
15771
  }
15370
15772
  )
15371
15773
  ] }),
15372
- isLayerMenuOpen && /* @__PURE__ */ jsx22("div", { style: { marginTop: 4, minWidth: 120 }, children: processedLayers.map((layer) => /* @__PURE__ */ jsx22(
15774
+ isLayerMenuOpen && /* @__PURE__ */ jsx23("div", { style: { marginTop: 4, minWidth: 120 }, children: processedLayers.map((layer) => /* @__PURE__ */ jsx23(
15373
15775
  LayerButton,
15374
15776
  {
15375
15777
  name: layer,
@@ -15383,68 +15785,70 @@ var ToolbarOverlay = ({ children, elements }) => {
15383
15785
  ]
15384
15786
  }
15385
15787
  ),
15386
- /* @__PURE__ */ jsx22(
15788
+ /* @__PURE__ */ jsx23(
15387
15789
  ToolbarErrorDropdown,
15388
15790
  {
15389
15791
  elements,
15390
15792
  isOpen: isErrorsOpen,
15391
15793
  isSmallScreen,
15392
15794
  onToggle: handleErrorsToggle,
15393
- setHoveredErrorId
15795
+ onClose: closeErrorsMenu,
15796
+ setHoveredErrorId,
15797
+ setFocusedErrorId
15394
15798
  }
15395
15799
  ),
15396
- /* @__PURE__ */ jsx22(
15800
+ /* @__PURE__ */ jsx23(
15397
15801
  ToolbarButton,
15398
15802
  {
15399
15803
  isSmallScreen,
15400
15804
  style: {},
15401
15805
  onClick: handleEditTraceToggle,
15402
- children: /* @__PURE__ */ jsxs15("div", { children: [
15806
+ children: /* @__PURE__ */ jsxs16("div", { children: [
15403
15807
  editModes.in_draw_trace_mode ? "\u2716 " : "",
15404
15808
  "Edit Traces"
15405
15809
  ] })
15406
15810
  }
15407
15811
  ),
15408
- /* @__PURE__ */ jsx22(
15812
+ /* @__PURE__ */ jsx23(
15409
15813
  ToolbarButton,
15410
15814
  {
15411
15815
  isSmallScreen,
15412
15816
  style: {},
15413
15817
  onClick: handleMoveComponentToggle,
15414
- children: /* @__PURE__ */ jsxs15("div", { children: [
15818
+ children: /* @__PURE__ */ jsxs16("div", { children: [
15415
15819
  editModes.in_move_footprint_mode ? "\u2716 " : "",
15416
15820
  "Move Components"
15417
15821
  ] })
15418
15822
  }
15419
15823
  ),
15420
- /* @__PURE__ */ jsx22(
15824
+ /* @__PURE__ */ jsx23(
15421
15825
  ToolbarButton,
15422
15826
  {
15423
15827
  isSmallScreen,
15424
15828
  style: {},
15425
15829
  onClick: handleRatsNestToggle,
15426
- children: /* @__PURE__ */ jsxs15("div", { children: [
15830
+ children: /* @__PURE__ */ jsxs16("div", { children: [
15427
15831
  viewSettings.is_showing_rats_nest ? "\u2716 " : "",
15428
15832
  "Rats Nest"
15429
15833
  ] })
15430
15834
  }
15431
15835
  ),
15432
- /* @__PURE__ */ jsx22(
15836
+ /* @__PURE__ */ jsx23(
15433
15837
  ToolbarButton,
15434
15838
  {
15435
15839
  isSmallScreen,
15436
15840
  style: measureToolArmed ? { backgroundColor: "#444" } : {},
15437
15841
  onClick: handleMeasureToolClick,
15438
- children: /* @__PURE__ */ jsx22("div", { children: "\u{1F4CF}" })
15842
+ children: /* @__PURE__ */ jsx23("div", { children: "\u{1F4CF}" })
15439
15843
  }
15440
15844
  ),
15441
- /* @__PURE__ */ jsx22(
15845
+ /* @__PURE__ */ jsx23(
15442
15846
  ToolbarButton,
15443
15847
  {
15444
15848
  isSmallScreen,
15445
15849
  onClick: handleViewMenuToggle,
15446
- children: /* @__PURE__ */ jsxs15("div", { children: [
15447
- /* @__PURE__ */ jsxs15(
15850
+ children: /* @__PURE__ */ jsxs16("div", { children: [
15851
+ /* @__PURE__ */ jsxs16(
15448
15852
  "div",
15449
15853
  {
15450
15854
  style: {
@@ -15454,7 +15858,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15454
15858
  },
15455
15859
  children: [
15456
15860
  "View",
15457
- /* @__PURE__ */ jsx22(
15861
+ /* @__PURE__ */ jsx23(
15458
15862
  "span",
15459
15863
  {
15460
15864
  style: {
@@ -15469,8 +15873,8 @@ var ToolbarOverlay = ({ children, elements }) => {
15469
15873
  ]
15470
15874
  }
15471
15875
  ),
15472
- isViewMenuOpen && /* @__PURE__ */ jsxs15("div", { style: { marginTop: 4, minWidth: 120 }, children: [
15473
- /* @__PURE__ */ jsx22(
15876
+ isViewMenuOpen && /* @__PURE__ */ jsxs16("div", { style: { marginTop: 4, minWidth: 120 }, children: [
15877
+ /* @__PURE__ */ jsx23(
15474
15878
  CheckboxMenuItem,
15475
15879
  {
15476
15880
  label: "Show All Trace Lengths",
@@ -15482,7 +15886,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15482
15886
  }
15483
15887
  }
15484
15888
  ),
15485
- /* @__PURE__ */ jsx22(
15889
+ /* @__PURE__ */ jsx23(
15486
15890
  CheckboxMenuItem,
15487
15891
  {
15488
15892
  label: "Show Autorouting Animation",
@@ -15494,7 +15898,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15494
15898
  }
15495
15899
  }
15496
15900
  ),
15497
- /* @__PURE__ */ jsx22(
15901
+ /* @__PURE__ */ jsx23(
15498
15902
  CheckboxMenuItem,
15499
15903
  {
15500
15904
  label: "Show DRC Errors",
@@ -15504,7 +15908,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15504
15908
  }
15505
15909
  }
15506
15910
  ),
15507
- /* @__PURE__ */ jsx22(
15911
+ /* @__PURE__ */ jsx23(
15508
15912
  CheckboxMenuItem,
15509
15913
  {
15510
15914
  label: "Show Copper Pours",
@@ -15516,7 +15920,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15516
15920
  }
15517
15921
  }
15518
15922
  ),
15519
- /* @__PURE__ */ jsx22(
15923
+ /* @__PURE__ */ jsx23(
15520
15924
  CheckboxMenuItem,
15521
15925
  {
15522
15926
  label: "Show Courtyards",
@@ -15526,7 +15930,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15526
15930
  }
15527
15931
  }
15528
15932
  ),
15529
- /* @__PURE__ */ jsx22(
15933
+ /* @__PURE__ */ jsx23(
15530
15934
  CheckboxMenuItem,
15531
15935
  {
15532
15936
  label: "Show Solder Mask",
@@ -15536,7 +15940,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15536
15940
  }
15537
15941
  }
15538
15942
  ),
15539
- /* @__PURE__ */ jsx22(
15943
+ /* @__PURE__ */ jsx23(
15540
15944
  CheckboxMenuItem,
15541
15945
  {
15542
15946
  label: "Show Silkscreen",
@@ -15546,7 +15950,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15546
15950
  }
15547
15951
  }
15548
15952
  ),
15549
- /* @__PURE__ */ jsx22(
15953
+ /* @__PURE__ */ jsx23(
15550
15954
  CheckboxMenuItem,
15551
15955
  {
15552
15956
  label: "Show Fabrication Notes",
@@ -15558,7 +15962,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15558
15962
  }
15559
15963
  }
15560
15964
  ),
15561
- /* @__PURE__ */ jsx22(
15965
+ /* @__PURE__ */ jsx23(
15562
15966
  CheckboxMenuItem,
15563
15967
  {
15564
15968
  label: "Show Group Anchor Offsets",
@@ -15570,7 +15974,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15570
15974
  }
15571
15975
  }
15572
15976
  ),
15573
- /* @__PURE__ */ jsx22(
15977
+ /* @__PURE__ */ jsx23(
15574
15978
  CheckboxMenuItem,
15575
15979
  {
15576
15980
  label: "Show PCB Groups",
@@ -15580,8 +15984,8 @@ var ToolbarOverlay = ({ children, elements }) => {
15580
15984
  }
15581
15985
  }
15582
15986
  ),
15583
- viewSettings.is_showing_pcb_groups && /* @__PURE__ */ jsxs15("div", { style: { marginLeft: 16 }, children: [
15584
- /* @__PURE__ */ jsx22(
15987
+ viewSettings.is_showing_pcb_groups && /* @__PURE__ */ jsxs16("div", { style: { marginLeft: 16 }, children: [
15988
+ /* @__PURE__ */ jsx23(
15585
15989
  RadioMenuItem,
15586
15990
  {
15587
15991
  label: "Show All Groups",
@@ -15591,7 +15995,7 @@ var ToolbarOverlay = ({ children, elements }) => {
15591
15995
  }
15592
15996
  }
15593
15997
  ),
15594
- /* @__PURE__ */ jsx22(
15998
+ /* @__PURE__ */ jsx23(
15595
15999
  RadioMenuItem,
15596
16000
  {
15597
16001
  label: "Show Named Groups",
@@ -15615,13 +16019,15 @@ var ToolbarOverlay = ({ children, elements }) => {
15615
16019
  };
15616
16020
 
15617
16021
  // src/components/CanvasElementsRenderer.tsx
15618
- import { jsx as jsx23 } from "react/jsx-runtime";
16022
+ import { jsx as jsx24 } from "react/jsx-runtime";
15619
16023
  var CanvasElementsRenderer = (props) => {
15620
16024
  const { transform, elements } = props;
15621
- const hoveredErrorId = useGlobalStore((state) => state.hovered_error_id);
15622
- const isShowingCopperPours = useGlobalStore(
15623
- (state) => state.is_showing_copper_pours
15624
- );
16025
+ const { hoveredErrorId, focusedErrorId, isShowingCopperPours } = useGlobalStore((state) => ({
16026
+ hoveredErrorId: state.hovered_error_id,
16027
+ focusedErrorId: state.focused_error_id,
16028
+ isShowingCopperPours: state.is_showing_copper_pours
16029
+ }));
16030
+ const activeErrorId = focusedErrorId ?? hoveredErrorId;
15625
16031
  const elementsToRender = useMemo8(
15626
16032
  () => isShowingCopperPours ? elements : elements.filter((elm) => elm.type !== "pcb_copper_pour"),
15627
16033
  [elements, isShowingCopperPours]
@@ -15640,27 +16046,71 @@ var CanvasElementsRenderer = (props) => {
15640
16046
  primitiveIdsInMousedOverNet: []
15641
16047
  });
15642
16048
  const [hoveredComponentIds, setHoveredComponentIds] = useState12([]);
16049
+ const currentTransformRef = useRef15(transform ?? null);
16050
+ const zoomAnimationFrameRef = useRef15(null);
16051
+ const elementIndexes = useMemo8(
16052
+ () => buildErrorPreviewElementIndexes(elements),
16053
+ [elements]
16054
+ );
15643
16055
  const errorRelatedIds = useMemo8(() => {
15644
- if (!hoveredErrorId) return [];
16056
+ if (!activeErrorId) return [];
15645
16057
  const errorElements = elements.filter(
15646
16058
  (el) => el.type.includes("error")
15647
16059
  );
15648
- const hoveredError = errorElements.find((el) => {
15649
- return el.error_id === hoveredErrorId;
16060
+ const activeError = errorElements.find((el, index) => {
16061
+ return getErrorId(el, index) === activeErrorId;
15650
16062
  });
15651
- if (!hoveredError) return [];
15652
- const relatedIds = [];
15653
- if (hoveredError.pcb_trace_id) {
15654
- relatedIds.push(hoveredError.pcb_trace_id);
15655
- }
15656
- if (hoveredError.pcb_port_ids) {
15657
- relatedIds.push(...hoveredError.pcb_port_ids);
15658
- }
15659
- if (hoveredError.pcb_via_ids) {
15660
- relatedIds.push(...hoveredError.pcb_via_ids);
16063
+ if (!activeError) return [];
16064
+ return getRelatedIdsForError(activeError);
16065
+ }, [activeErrorId, elements]);
16066
+ useEffect18(() => {
16067
+ if (transform) {
16068
+ currentTransformRef.current = transform;
15661
16069
  }
15662
- return relatedIds;
15663
- }, [hoveredErrorId, elements]);
16070
+ }, [transform]);
16071
+ useEffect18(() => {
16072
+ return () => {
16073
+ cancelTransformAnimation(zoomAnimationFrameRef.current);
16074
+ };
16075
+ }, []);
16076
+ useEffect18(() => {
16077
+ if (!props.width || !props.height || !props.setTransform || !focusedErrorId)
16078
+ return;
16079
+ const focusedError = findErrorElementById(elements, focusedErrorId);
16080
+ if (!focusedError) return;
16081
+ const previewBounds = getErrorPreviewBounds({
16082
+ error: focusedError,
16083
+ indexes: elementIndexes
16084
+ });
16085
+ if (!previewBounds) return;
16086
+ const startTransform = currentTransformRef.current ?? transform;
16087
+ if (!startTransform) return;
16088
+ const targetTransform = createTransformForBounds({
16089
+ bounds: previewBounds,
16090
+ width: props.width,
16091
+ height: props.height
16092
+ });
16093
+ cancelTransformAnimation(zoomAnimationFrameRef.current);
16094
+ animateTransform({
16095
+ startTransform,
16096
+ endTransform: targetTransform,
16097
+ durationMs: 420,
16098
+ setAnimationFrameId: (animationFrameId) => {
16099
+ zoomAnimationFrameRef.current = animationFrameId;
16100
+ },
16101
+ onUpdate: (nextTransform) => {
16102
+ currentTransformRef.current = nextTransform;
16103
+ props.setTransform?.(nextTransform);
16104
+ }
16105
+ });
16106
+ }, [
16107
+ focusedErrorId,
16108
+ elements,
16109
+ elementIndexes,
16110
+ props.height,
16111
+ props.setTransform,
16112
+ props.width
16113
+ ]);
15664
16114
  const primitives = useMemo8(() => {
15665
16115
  const combinedPrimitiveIds = [
15666
16116
  ...hoverState.primitiveIdsInMousedOverNet,
@@ -15672,7 +16122,7 @@ var CanvasElementsRenderer = (props) => {
15672
16122
  primitiveIdsInMousedOverNet: combinedPrimitiveIds
15673
16123
  });
15674
16124
  }, [primitivesWithoutInteractionMetadata, hoverState, errorRelatedIds]);
15675
- const onMouseOverPrimitives = useCallback10(
16125
+ const onMouseOverPrimitives = useCallback11(
15676
16126
  (primitivesHoveredOver) => {
15677
16127
  const primitiveIdsInMousedOverNet = [];
15678
16128
  for (const primitive of primitivesHoveredOver) {
@@ -15705,14 +16155,14 @@ var CanvasElementsRenderer = (props) => {
15705
16155
  },
15706
16156
  [connectivityMap]
15707
16157
  );
15708
- return /* @__PURE__ */ jsx23(
16158
+ return /* @__PURE__ */ jsx24(
15709
16159
  MouseElementTracker,
15710
16160
  {
15711
16161
  elements: elementsToRender,
15712
16162
  transform,
15713
16163
  primitives: primitivesWithoutInteractionMetadata,
15714
16164
  onMouseHoverOverPrimitives: onMouseOverPrimitives,
15715
- children: /* @__PURE__ */ jsx23(
16165
+ children: /* @__PURE__ */ jsx24(
15716
16166
  EditPlacementOverlay,
15717
16167
  {
15718
16168
  disabled: !props.allowEditing,
@@ -15721,7 +16171,7 @@ var CanvasElementsRenderer = (props) => {
15721
16171
  cancelPanDrag: props.cancelPanDrag,
15722
16172
  onCreateEditEvent: props.onCreateEditEvent,
15723
16173
  onModifyEditEvent: props.onModifyEditEvent,
15724
- children: /* @__PURE__ */ jsx23(
16174
+ children: /* @__PURE__ */ jsx24(
15725
16175
  EditTraceHintOverlay,
15726
16176
  {
15727
16177
  disabled: !props.allowEditing,
@@ -15730,29 +16180,29 @@ var CanvasElementsRenderer = (props) => {
15730
16180
  cancelPanDrag: props.cancelPanDrag,
15731
16181
  onCreateEditEvent: props.onCreateEditEvent,
15732
16182
  onModifyEditEvent: props.onModifyEditEvent,
15733
- children: /* @__PURE__ */ jsx23(
16183
+ children: /* @__PURE__ */ jsx24(
15734
16184
  DimensionOverlay,
15735
16185
  {
15736
16186
  transform,
15737
16187
  focusOnHover: props.focusOnHover,
15738
16188
  primitives: primitivesWithoutInteractionMetadata,
15739
- children: /* @__PURE__ */ jsx23(ToolbarOverlay, { elements, children: /* @__PURE__ */ jsx23(ErrorOverlay, { transform, elements, children: /* @__PURE__ */ jsx23(RatsNestOverlay, { transform, soup: elements, children: /* @__PURE__ */ jsx23(
16189
+ children: /* @__PURE__ */ jsx24(ToolbarOverlay, { elements, children: /* @__PURE__ */ jsx24(ErrorOverlay, { transform, elements, children: /* @__PURE__ */ jsx24(RatsNestOverlay, { transform, soup: elements, children: /* @__PURE__ */ jsx24(
15740
16190
  PcbGroupOverlay,
15741
16191
  {
15742
16192
  transform,
15743
16193
  elements,
15744
16194
  hoveredComponentIds,
15745
- children: /* @__PURE__ */ jsx23(
16195
+ children: /* @__PURE__ */ jsx24(
15746
16196
  DebugGraphicsOverlay,
15747
16197
  {
15748
16198
  transform,
15749
16199
  debugGraphics: props.debugGraphics,
15750
- children: /* @__PURE__ */ jsx23(
16200
+ children: /* @__PURE__ */ jsx24(
15751
16201
  WarningGraphicsOverlay,
15752
16202
  {
15753
16203
  transform,
15754
16204
  elements,
15755
- children: /* @__PURE__ */ jsx23(
16205
+ children: /* @__PURE__ */ jsx24(
15756
16206
  CanvasPrimitiveRenderer,
15757
16207
  {
15758
16208
  transform,
@@ -15834,8 +16284,8 @@ var calculateBoardSizeKey = (circuitJson) => {
15834
16284
  };
15835
16285
 
15836
16286
  // src/PCBViewer.tsx
15837
- import { jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime";
15838
- var defaultTransform = compose7(translate11(400, 300), scale5(40, -40));
16287
+ import { jsx as jsx25, jsxs as jsxs17 } from "react/jsx-runtime";
16288
+ var defaultTransform = compose8(translate12(400, 300), scale6(40, -40));
15839
16289
  var PCBViewer = ({
15840
16290
  circuitJson,
15841
16291
  debugGraphics,
@@ -15853,7 +16303,7 @@ var PCBViewer = ({
15853
16303
  );
15854
16304
  const [ref, refDimensions] = useMeasure_default();
15855
16305
  const [transform, setTransformInternal] = useState13(defaultTransform);
15856
- const shouldAllowCanvasInteraction = useCallback11(
16306
+ const shouldAllowCanvasInteraction = useCallback12(
15857
16307
  (event) => {
15858
16308
  const target = event.target;
15859
16309
  if (!(target instanceof Element)) return true;
@@ -15873,8 +16323,8 @@ var PCBViewer = ({
15873
16323
  });
15874
16324
  let [editEvents, setEditEvents] = useState13([]);
15875
16325
  editEvents = editEventsProp ?? editEvents;
15876
- const initialRenderCompleted = useRef14(false);
15877
- const touchStartRef = useRef14(null);
16326
+ const initialRenderCompleted = useRef16(false);
16327
+ const touchStartRef = useRef16(null);
15878
16328
  const circuitJsonKey = useMemo9(
15879
16329
  () => calculateCircuitJsonKey(circuitJson),
15880
16330
  [circuitJson]
@@ -15892,15 +16342,15 @@ var PCBViewer = ({
15892
16342
  (elmBounds.height ?? 0) / height2,
15893
16343
  100
15894
16344
  ) * 0.75;
15895
- const targetTransform = compose7(
15896
- translate11((elmBounds.width ?? 0) / 2, (elmBounds.height ?? 0) / 2),
15897
- scale5(scaleFactor, -scaleFactor, 0, 0),
15898
- translate11(-center.x, -center.y)
16345
+ const targetTransform = compose8(
16346
+ translate12((elmBounds.width ?? 0) / 2, (elmBounds.height ?? 0) / 2),
16347
+ scale6(scaleFactor, -scaleFactor, 0, 0),
16348
+ translate12(-center.x, -center.y)
15899
16349
  );
15900
16350
  setTransform(targetTransform);
15901
16351
  return;
15902
16352
  };
15903
- useEffect17(() => {
16353
+ useEffect19(() => {
15904
16354
  if (!refDimensions?.width) return;
15905
16355
  if (!circuitJson) return;
15906
16356
  if (circuitJson.length === 0) return;
@@ -15909,7 +16359,7 @@ var PCBViewer = ({
15909
16359
  initialRenderCompleted.current = true;
15910
16360
  }
15911
16361
  }, [circuitJson, refDimensions]);
15912
- useEffect17(() => {
16362
+ useEffect19(() => {
15913
16363
  if (initialRenderCompleted.current === true) {
15914
16364
  resetTransform();
15915
16365
  }
@@ -15943,23 +16393,24 @@ var PCBViewer = ({
15943
16393
  }),
15944
16394
  [initialState, disablePcbGroups]
15945
16395
  );
15946
- return /* @__PURE__ */ jsxs16(
16396
+ return /* @__PURE__ */ jsxs17(
15947
16397
  "div",
15948
16398
  {
15949
16399
  ref: transformRef,
15950
16400
  style: { position: "relative" },
15951
16401
  onContextMenu: (event) => event.preventDefault(),
15952
16402
  children: [
15953
- /* @__PURE__ */ jsx24("div", { ref, children: /* @__PURE__ */ jsxs16(
16403
+ /* @__PURE__ */ jsx25("div", { ref, children: /* @__PURE__ */ jsxs17(
15954
16404
  ContextProviders,
15955
16405
  {
15956
16406
  initialState: mergedInitialState,
15957
16407
  disablePcbGroups,
15958
16408
  children: [
15959
- /* @__PURE__ */ jsx24(
16409
+ /* @__PURE__ */ jsx25(
15960
16410
  CanvasElementsRenderer,
15961
16411
  {
15962
16412
  transform,
16413
+ setTransform,
15963
16414
  height,
15964
16415
  width: refDimensions.width,
15965
16416
  allowEditing,
@@ -15981,11 +16432,11 @@ var PCBViewer = ({
15981
16432
  },
15982
16433
  refDimensions.width
15983
16434
  ),
15984
- /* @__PURE__ */ jsx24(ToastContainer, {})
16435
+ /* @__PURE__ */ jsx25(ToastContainer, {})
15985
16436
  ]
15986
16437
  }
15987
16438
  ) }),
15988
- clickToInteractEnabled && !isInteractionEnabled && /* @__PURE__ */ jsx24(
16439
+ clickToInteractEnabled && !isInteractionEnabled && /* @__PURE__ */ jsx25(
15989
16440
  "div",
15990
16441
  {
15991
16442
  onClick: () => {
@@ -16022,7 +16473,7 @@ var PCBViewer = ({
16022
16473
  justifyContent: "center",
16023
16474
  touchAction: "pan-x pan-y pinch-zoom"
16024
16475
  },
16025
- children: /* @__PURE__ */ jsx24(
16476
+ children: /* @__PURE__ */ jsx25(
16026
16477
  "div",
16027
16478
  {
16028
16479
  style: {