react-os-shell 0.3.14 → 0.3.18

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.
@@ -513,8 +513,18 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
513
513
  const [showLayers, setShowLayers] = useState(false);
514
514
  const [showHint, setShowHint] = useState(true);
515
515
  const [measureEnabled, setMeasureEnabled] = useState(false);
516
- const [measureMode, setMeasureMode] = useState("point");
516
+ const [measureMode, setMeasureMode] = useState("horizontal");
517
+ const [measureAutocad, setMeasureAutocad] = useState(true);
517
518
  const [measureDistance, setMeasureDistance] = useState(null);
519
+ const measureModeRef = useRef(measureMode);
520
+ const measureAutocadRef = useRef(measureAutocad);
521
+ useEffect(() => {
522
+ measureModeRef.current = measureMode;
523
+ }, [measureMode]);
524
+ useEffect(() => {
525
+ measureAutocadRef.current = measureAutocad;
526
+ }, [measureAutocad]);
527
+ const measureRedrawRef = useRef(null);
518
528
  const measureRef = useRef(null);
519
529
  useEffect(() => {
520
530
  let cancelled = false;
@@ -641,6 +651,21 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
641
651
  containerRef.current.appendChild(overlay);
642
652
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
643
653
  svg.setAttribute("style", "position:absolute;inset:0;width:100%;height:100%;pointer-events:none;");
654
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
655
+ const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
656
+ marker.setAttribute("id", "dxf-measure-arrow");
657
+ marker.setAttribute("viewBox", "0 0 10 10");
658
+ marker.setAttribute("refX", "9");
659
+ marker.setAttribute("refY", "5");
660
+ marker.setAttribute("markerWidth", "6");
661
+ marker.setAttribute("markerHeight", "6");
662
+ marker.setAttribute("orient", "auto-start-reverse");
663
+ const arrowPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
664
+ arrowPath.setAttribute("d", "M 0 0 L 10 5 L 0 10 z");
665
+ arrowPath.setAttribute("fill", "#ff8800");
666
+ marker.appendChild(arrowPath);
667
+ defs.appendChild(marker);
668
+ svg.appendChild(defs);
644
669
  overlay.appendChild(svg);
645
670
  const refLineEl = document.createElementNS("http://www.w3.org/2000/svg", "line");
646
671
  refLineEl.setAttribute("stroke", "#ff8800");
@@ -649,6 +674,18 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
649
674
  refLineEl.setAttribute("opacity", "0.55");
650
675
  refLineEl.style.display = "none";
651
676
  svg.appendChild(refLineEl);
677
+ const extLineA = document.createElementNS("http://www.w3.org/2000/svg", "line");
678
+ extLineA.setAttribute("stroke", "#ff8800");
679
+ extLineA.setAttribute("stroke-width", "1");
680
+ extLineA.setAttribute("opacity", "0.85");
681
+ extLineA.style.display = "none";
682
+ svg.appendChild(extLineA);
683
+ const extLineB = document.createElementNS("http://www.w3.org/2000/svg", "line");
684
+ extLineB.setAttribute("stroke", "#ff8800");
685
+ extLineB.setAttribute("stroke-width", "1");
686
+ extLineB.setAttribute("opacity", "0.85");
687
+ extLineB.style.display = "none";
688
+ svg.appendChild(extLineB);
652
689
  const lineEl = document.createElementNS("http://www.w3.org/2000/svg", "line");
653
690
  lineEl.setAttribute("stroke", "#ff8800");
654
691
  lineEl.setAttribute("stroke-width", "1.5");
@@ -660,11 +697,12 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
660
697
  overlay.appendChild(snapEl);
661
698
  measureRef.current = {
662
699
  picks: [],
663
- lineDir: null,
664
700
  overlay,
665
701
  svg,
666
702
  line: lineEl,
667
703
  refLine: refLineEl,
704
+ extLineA,
705
+ extLineB,
668
706
  markers: [],
669
707
  label: null,
670
708
  snap: snapEl,
@@ -759,7 +797,7 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
759
797
  const s = measureRef.current;
760
798
  if (s.label) return s.label;
761
799
  const el = document.createElement("div");
762
- el.style.cssText = `position:absolute;transform:translate(-50%,-50%);padding:2px 6px;font-size:11px;font-weight:600;font-family:system-ui,-apple-system,sans-serif;background:rgba(255,136,0,0.95);color:#fff;border-radius:4px;white-space:nowrap;box-shadow:0 1px 4px rgba(0,0,0,0.25);pointer-events:none;`;
800
+ el.style.cssText = `position:absolute;left:-9999px;top:-9999px;transform:translate(-50%,-50%);padding:2px 6px;font-size:11px;font-weight:600;font-family:system-ui,-apple-system,sans-serif;background:rgba(255,136,0,0.95);color:#fff;border-radius:4px;white-space:nowrap;box-shadow:0 1px 4px rgba(0,0,0,0.25);pointer-events:none;`;
763
801
  overlay.appendChild(el);
764
802
  s.label = el;
765
803
  return el;
@@ -772,34 +810,36 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
772
810
  const computeRenderedEnds = () => {
773
811
  const s = measureRef.current;
774
812
  const a = s.picks[0];
775
- let b = s.picks[1];
776
- if (measureMode === "perp" && s.lineDir) {
777
- const d = s.lineDir;
778
- const ax = a.x, ay = a.y;
779
- const t = (b.x - ax) * d.dx + (b.y - ay) * d.dy;
780
- const fx = ax + d.dx * t;
781
- const fy = ay + d.dy * t;
782
- return { from: { x: fx, y: fy }, to: { x: b.x, y: b.y } };
813
+ const b = s.picks[1];
814
+ const mode = measureModeRef.current;
815
+ if (mode === "horizontal") {
816
+ return { from: a, to: { x: b.x, y: a.y } };
817
+ }
818
+ if (mode === "vertical") {
819
+ return { from: a, to: { x: a.x, y: b.y } };
783
820
  }
784
821
  return { from: a, to: b };
785
822
  };
786
823
  const updateOverlay = () => {
787
824
  const s = measureRef.current;
788
825
  if (!s) return;
826
+ const mode = measureModeRef.current;
827
+ const autocad = measureAutocadRef.current;
789
828
  if (s.markers[0]) positionMarker(s.markers[0], s.picks[0].x, s.picks[0].y);
790
829
  if (s.markers[1]) positionMarker(s.markers[1], s.picks[1].x, s.picks[1].y);
791
- if (measureMode === "perp" && s.picks.length >= 1 && s.lineDir && s.refLine) {
830
+ const refDir = mode === "horizontal" ? { dx: 1, dy: 0 } : mode === "vertical" ? { dx: 0, dy: 1 } : null;
831
+ if (refDir && s.picks.length >= 1 && s.refLine) {
792
832
  const a = s.picks[0];
793
833
  const w = canvas.clientWidth, h = canvas.clientHeight;
794
834
  const screenSpan = Math.hypot(w, h) * 4;
795
835
  const ap = pxFromScene(a.x, a.y);
796
- const probe = pxFromScene(a.x + s.lineDir.dx, a.y + s.lineDir.dy);
836
+ const probe = pxFromScene(a.x + refDir.dx, a.y + refDir.dy);
797
837
  const pxLen = Math.hypot(probe.x - ap.x, probe.y - ap.y) || 1;
798
838
  const sceneStep = screenSpan / pxLen;
799
- const x0 = a.x - s.lineDir.dx * sceneStep;
800
- const y0 = a.y - s.lineDir.dy * sceneStep;
801
- const x1 = a.x + s.lineDir.dx * sceneStep;
802
- const y1 = a.y + s.lineDir.dy * sceneStep;
839
+ const x0 = a.x - refDir.dx * sceneStep;
840
+ const y0 = a.y - refDir.dy * sceneStep;
841
+ const x1 = a.x + refDir.dx * sceneStep;
842
+ const y1 = a.y + refDir.dy * sceneStep;
803
843
  const p0 = pxFromScene(x0, y0);
804
844
  const p1 = pxFromScene(x1, y1);
805
845
  s.refLine.setAttribute("x1", String(p0.x));
@@ -811,6 +851,7 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
811
851
  s.refLine.style.display = "none";
812
852
  }
813
853
  if (s.picks.length === 2) {
854
+ const a = s.picks[0], b = s.picks[1];
814
855
  const ends = computeRenderedEnds();
815
856
  const fp = pxFromScene(ends.from.x, ends.from.y);
816
857
  const tp = pxFromScene(ends.to.x, ends.to.y);
@@ -819,12 +860,49 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
819
860
  s.line.setAttribute("x2", String(tp.x));
820
861
  s.line.setAttribute("y2", String(tp.y));
821
862
  s.line.style.display = "";
863
+ if (autocad) {
864
+ s.line.setAttribute("marker-start", "url(#dxf-measure-arrow)");
865
+ s.line.setAttribute("marker-end", "url(#dxf-measure-arrow)");
866
+ } else {
867
+ s.line.removeAttribute("marker-start");
868
+ s.line.removeAttribute("marker-end");
869
+ }
870
+ const extA = s.extLineA;
871
+ const extB = s.extLineB;
872
+ if (autocad && mode === "horizontal" && Math.abs(b.y - a.y) > 1e-9) {
873
+ const p = pxFromScene(b.x, a.y);
874
+ const q = pxFromScene(b.x, b.y);
875
+ extB.setAttribute("x1", String(p.x));
876
+ extB.setAttribute("y1", String(p.y));
877
+ extB.setAttribute("x2", String(q.x));
878
+ extB.setAttribute("y2", String(q.y));
879
+ extB.style.display = "";
880
+ extA.style.display = "none";
881
+ } else if (autocad && mode === "vertical" && Math.abs(b.x - a.x) > 1e-9) {
882
+ const p = pxFromScene(a.x, b.y);
883
+ const q = pxFromScene(b.x, b.y);
884
+ extB.setAttribute("x1", String(p.x));
885
+ extB.setAttribute("y1", String(p.y));
886
+ extB.setAttribute("x2", String(q.x));
887
+ extB.setAttribute("y2", String(q.y));
888
+ extB.style.display = "";
889
+ extA.style.display = "none";
890
+ } else {
891
+ extA.style.display = "none";
892
+ extB.style.display = "none";
893
+ }
822
894
  if (s.label) {
823
- s.label.style.left = `${(fp.x + tp.x) / 2}px`;
824
- s.label.style.top = `${(fp.y + tp.y) / 2}px`;
895
+ const cx = (fp.x + tp.x) / 2;
896
+ const cy = (fp.y + tp.y) / 2;
897
+ s.label.style.left = `${cx}px`;
898
+ s.label.style.top = `${cy}px`;
899
+ const w = canvas.clientWidth, h = canvas.clientHeight;
900
+ s.label.style.display = cx < 0 || cy < 0 || cx > w || cy > h ? "none" : "";
825
901
  }
826
902
  } else {
827
903
  s.line.style.display = "none";
904
+ if (s.extLineA) s.extLineA.style.display = "none";
905
+ if (s.extLineB) s.extLineB.style.display = "none";
828
906
  }
829
907
  };
830
908
  const DRAG_TOL = 4;
@@ -870,10 +948,35 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
870
948
  if (dragging || elapsed > DRAG_TIME) return;
871
949
  const raw = ev?.detail?.position;
872
950
  if (!raw) return;
873
- const useSnap = lastSnap && Math.hypot(downX - canvas.getBoundingClientRect().width, 0) >= 0;
874
- const picked = useSnap && lastSnap ? { x: lastSnap.sx, y: lastSnap.sy, snapType: lastSnap.type, lineDir: lastSnap.dir } : { x: raw.x, y: raw.y, lineDir: void 0 };
951
+ const picked = lastSnap ? { x: lastSnap.sx, y: lastSnap.sy } : { x: raw.x, y: raw.y };
875
952
  doPick(picked);
876
953
  };
954
+ const recomputeLabel = () => {
955
+ const s = measureRef.current;
956
+ if (!s || s.picks.length !== 2) return;
957
+ const a = s.picks[0], b = s.picks[1];
958
+ const mode = measureModeRef.current;
959
+ let dist;
960
+ let suffix = "";
961
+ if (mode === "horizontal") {
962
+ dist = Math.abs(b.x - a.x);
963
+ suffix = " \u2194";
964
+ } else if (mode === "vertical") {
965
+ dist = Math.abs(b.y - a.y);
966
+ suffix = " \u2195";
967
+ } else {
968
+ const dx = b.x - a.x, dy = b.y - a.y;
969
+ dist = Math.sqrt(dx * dx + dy * dy);
970
+ }
971
+ setMeasureDistance(dist);
972
+ const label = ensureLabel();
973
+ label.style.opacity = "1";
974
+ label.textContent = `${formatMeasureDistance(dist)}${suffix}`;
975
+ };
976
+ measureRedrawRef.current = () => {
977
+ recomputeLabel();
978
+ updateOverlay();
979
+ };
877
980
  const doPick = (p) => {
878
981
  const s = measureRef.current;
879
982
  if (!s) return;
@@ -881,44 +984,17 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
881
984
  for (const m of s.markers) m.parentElement?.removeChild(m);
882
985
  s.markers = [];
883
986
  s.picks = [];
884
- s.lineDir = null;
885
987
  s.line.style.display = "none";
886
988
  if (s.refLine) s.refLine.style.display = "none";
989
+ if (s.extLineA) s.extLineA.style.display = "none";
990
+ if (s.extLineB) s.extLineB.style.display = "none";
887
991
  if (s.label) s.label.style.opacity = "0";
888
992
  setMeasureDistance(null);
889
993
  }
890
- if (measureMode === "perp" && s.picks.length === 0) {
891
- if (!p.lineDir) {
892
- const label = ensureLabel();
893
- label.style.opacity = "1";
894
- label.style.left = `${pxFromScene(p.x, p.y).x}px`;
895
- label.style.top = `${pxFromScene(p.x, p.y).y - 18}px`;
896
- label.textContent = "\u22A5: snap to a line or corner first";
897
- setTimeout(() => {
898
- if (s.label && s.picks.length === 0) s.label.style.opacity = "0";
899
- }, 1500);
900
- return;
901
- }
902
- s.lineDir = p.lineDir;
903
- }
904
994
  s.picks.push({ x: p.x, y: p.y });
905
995
  s.markers.push(makeMarker());
906
996
  if (s.picks.length === 2) {
907
- const a = s.picks[0], b = s.picks[1];
908
- let dist;
909
- let suffix = "";
910
- if (measureMode === "perp" && s.lineDir) {
911
- const dx = b.x - a.x, dy = b.y - a.y;
912
- dist = Math.abs(dx * s.lineDir.dy - dy * s.lineDir.dx);
913
- suffix = " \u22A5";
914
- } else {
915
- const dx = b.x - a.x, dy = b.y - a.y;
916
- dist = Math.sqrt(dx * dx + dy * dy);
917
- }
918
- setMeasureDistance(dist);
919
- const label = ensureLabel();
920
- label.style.opacity = "1";
921
- label.textContent = `${formatMeasureDistance(dist)}${suffix}`;
997
+ recomputeLabel();
922
998
  }
923
999
  updateOverlay();
924
1000
  };
@@ -956,10 +1032,14 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
956
1032
  }
957
1033
  canvas.removeEventListener("pointermove", handlePointerMove);
958
1034
  window.removeEventListener("keydown", onKeyDown);
1035
+ measureRedrawRef.current = null;
959
1036
  teardown();
960
1037
  setMeasureDistance(null);
961
1038
  };
962
- }, [measureEnabled, measureMode, loading, error]);
1039
+ }, [measureEnabled, loading, error]);
1040
+ useEffect(() => {
1041
+ measureRedrawRef.current?.();
1042
+ }, [measureMode, measureAutocad]);
963
1043
  const toggleLayer = (name) => {
964
1044
  setLayers((prev) => prev.map((l) => {
965
1045
  if (l.name !== name) return l;
@@ -1049,30 +1129,63 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1049
1129
  ]
1050
1130
  }
1051
1131
  ),
1052
- measureEnabled && /* @__PURE__ */ jsxs("div", { className: "flex items-stretch h-7 rounded border border-gray-200 overflow-hidden text-[11px] font-semibold", children: [
1053
- /* @__PURE__ */ jsx(
1054
- "button",
1055
- {
1056
- onClick: () => setMeasureMode("point"),
1057
- className: `px-2 transition-colors ${measureMode === "point" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1058
- title: "Point \u2014 straight-line distance between two picks",
1059
- children: "Point"
1060
- }
1061
- ),
1132
+ measureEnabled && /* @__PURE__ */ jsxs(Fragment, { children: [
1062
1133
  /* @__PURE__ */ jsx(
1063
1134
  "button",
1064
1135
  {
1065
- onClick: () => setMeasureMode("perp"),
1066
- className: `px-2 transition-colors ${measureMode === "perp" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1067
- title: "Perpendicular \u2014 click on a line first, then pick a point. Reports the perpendicular distance.",
1136
+ onClick: () => {
1137
+ setMeasureAutocad((prev) => {
1138
+ const next = !prev;
1139
+ if (next && measureMode === "point") setMeasureMode("horizontal");
1140
+ return next;
1141
+ });
1142
+ },
1143
+ className: btn + (measureAutocad ? " bg-orange-100 text-orange-700" : ""),
1144
+ title: measureAutocad ? "AutoCAD dim style is ON \u2014 extension lines + arrows" : "AutoCAD dim style is OFF \u2014 plain line. Click to turn on (also switches to H if you're in Point).",
1068
1145
  children: "\u22A5"
1069
1146
  }
1070
- )
1071
- ] }),
1072
- measureEnabled && measureDistance !== null && /* @__PURE__ */ jsxs("div", { className: "px-2 py-1 text-[11px] font-mono font-semibold text-orange-600 bg-orange-50 border border-orange-200 rounded whitespace-nowrap", title: measureMode === "perp" ? "Perpendicular distance from second pick to first line" : "Straight-line distance between the two picked points", children: [
1073
- formatMeasureDistance(measureDistance),
1074
- measureMode === "perp" ? " \u22A5" : ""
1147
+ ),
1148
+ /* @__PURE__ */ jsxs("div", { className: "flex items-stretch h-7 rounded border border-gray-200 overflow-hidden text-[11px] font-semibold", children: [
1149
+ /* @__PURE__ */ jsx(
1150
+ "button",
1151
+ {
1152
+ onClick: () => setMeasureMode("point"),
1153
+ className: `px-2 transition-colors ${measureMode === "point" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1154
+ title: "Point \u2014 straight-line (Euclidean) distance between two picks",
1155
+ children: "Point"
1156
+ }
1157
+ ),
1158
+ /* @__PURE__ */ jsx(
1159
+ "button",
1160
+ {
1161
+ onClick: () => setMeasureMode("horizontal"),
1162
+ className: `px-2 transition-colors ${measureMode === "horizontal" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1163
+ title: "Horizontal \u2014 distance along the X axis between two picks (AutoCAD DIMLINEAR horizontal)",
1164
+ children: "H"
1165
+ }
1166
+ ),
1167
+ /* @__PURE__ */ jsx(
1168
+ "button",
1169
+ {
1170
+ onClick: () => setMeasureMode("vertical"),
1171
+ className: `px-2 transition-colors ${measureMode === "vertical" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1172
+ title: "Vertical \u2014 distance along the Y axis between two picks (AutoCAD DIMLINEAR vertical)",
1173
+ children: "V"
1174
+ }
1175
+ )
1176
+ ] })
1075
1177
  ] }),
1178
+ measureEnabled && measureDistance !== null && /* @__PURE__ */ jsxs(
1179
+ "div",
1180
+ {
1181
+ className: "px-2 py-1 text-[11px] font-mono font-semibold text-orange-600 bg-orange-50 border border-orange-200 rounded whitespace-nowrap",
1182
+ title: measureMode === "horizontal" ? "Horizontal distance (\u0394x) between the two picked points" : measureMode === "vertical" ? "Vertical distance (\u0394y) between the two picked points" : "Straight-line distance between the two picked points",
1183
+ children: [
1184
+ formatMeasureDistance(measureDistance),
1185
+ measureMode === "horizontal" ? " \u2194" : measureMode === "vertical" ? " \u2195" : ""
1186
+ ]
1187
+ }
1188
+ ),
1076
1189
  /* @__PURE__ */ jsx("button", { onClick: handleResetView, className: btn, title: "Fit drawing to view", children: "Fit" }),
1077
1190
  /* @__PURE__ */ jsxs("button", { onClick: onDownload ?? handleDefaultDownload, className: btn, children: [
1078
1191
  /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" }) }),
@@ -1306,6 +1419,31 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
1306
1419
  if (!OV || !v?.viewer) return;
1307
1420
  try {
1308
1421
  v.viewer.SetEdgeSettings(new OV.EdgeSettings(showEdges, hexToRgb(OV, edgeColor), edgeThreshold));
1422
+ const s = sectionRef.current;
1423
+ if (s) {
1424
+ const helperEdges = [];
1425
+ v.viewer.mainModel?.EnumerateEdges?.((edge) => {
1426
+ if (edge.userData?.__sectionHelper) helperEdges.push(edge);
1427
+ });
1428
+ for (const e of helperEdges) {
1429
+ e.parent?.remove(e);
1430
+ e.geometry?.dispose?.();
1431
+ e.material?.dispose?.();
1432
+ }
1433
+ const plane = s.plane;
1434
+ const applyMatClip = (mat) => {
1435
+ if (!mat || s.materialState.has(mat)) return;
1436
+ s.materialState.set(mat, { clippingPlanes: mat.clippingPlanes, clipShadows: mat.clipShadows });
1437
+ mat.clippingPlanes = [plane];
1438
+ mat.clipShadows = true;
1439
+ mat.needsUpdate = true;
1440
+ };
1441
+ v.viewer.mainModel?.EnumerateEdges?.((edge) => {
1442
+ const mat = edge.material;
1443
+ if (Array.isArray(mat)) for (const m of mat) applyMatClip(m);
1444
+ else applyMatClip(mat);
1445
+ });
1446
+ }
1309
1447
  v.viewer.Render?.();
1310
1448
  } catch {
1311
1449
  }
@@ -1738,6 +1876,8 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
1738
1876
  if (s.label) return s.label;
1739
1877
  const el = document.createElement("div");
1740
1878
  el.style.position = "absolute";
1879
+ el.style.left = "-9999px";
1880
+ el.style.top = "-9999px";
1741
1881
  el.style.transform = "translate(-50%, -50%)";
1742
1882
  el.style.padding = "2px 6px";
1743
1883
  el.style.fontSize = "11px";
@@ -1768,7 +1908,8 @@ function StepPanel({ url, filename, onDownload, onEmail }) {
1768
1908
  const y = (-projected.y + 1) / 2 * rect.height;
1769
1909
  s.label.style.left = `${x}px`;
1770
1910
  s.label.style.top = `${y}px`;
1771
- s.label.style.opacity = projected.z > 1 ? "0" : "1";
1911
+ const offScreen = x < 0 || y < 0 || x > rect.width || y > rect.height;
1912
+ s.label.style.opacity = projected.z > 1 || offScreen ? "0" : "1";
1772
1913
  };
1773
1914
  const tick = () => {
1774
1915
  const s = measureRef.current;
@@ -2405,5 +2546,5 @@ function ImagePanel({ url, filename, onDownload, onEmail }) {
2405
2546
  }
2406
2547
 
2407
2548
  export { Preview, setPdfPreview };
2408
- //# sourceMappingURL=chunk-S6YDMZ6X.js.map
2409
- //# sourceMappingURL=chunk-S6YDMZ6X.js.map
2549
+ //# sourceMappingURL=chunk-HADXZNYO.js.map
2550
+ //# sourceMappingURL=chunk-HADXZNYO.js.map