@tscircuit/3d-viewer 0.0.431 → 0.0.433

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.
Files changed (2) hide show
  1. package/dist/index.js +132 -24
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14228,7 +14228,7 @@ var require_browser = __commonJS({
14228
14228
  });
14229
14229
 
14230
14230
  // src/CadViewer.tsx
14231
- import { useState as useState33, useCallback as useCallback20, useRef as useRef22, useEffect as useEffect39 } from "react";
14231
+ import { useState as useState33, useCallback as useCallback20, useRef as useRef23, useEffect as useEffect39 } from "react";
14232
14232
 
14233
14233
  // src/CadViewerJscad.tsx
14234
14234
  import { su as su4 } from "@tscircuit/circuit-json-util";
@@ -28168,13 +28168,13 @@ var AnyCadComponent = ({
28168
28168
  };
28169
28169
 
28170
28170
  // src/CadViewerContainer.tsx
28171
- import { forwardRef as forwardRef2, useMemo as useMemo15, useState as useState8 } from "react";
28171
+ import { forwardRef as forwardRef2, useMemo as useMemo15, useRef as useRef6, useState as useState8 } from "react";
28172
28172
  import * as THREE14 from "three";
28173
28173
 
28174
28174
  // package.json
28175
28175
  var package_default = {
28176
28176
  name: "@tscircuit/3d-viewer",
28177
- version: "0.0.430",
28177
+ version: "0.0.432",
28178
28178
  main: "./dist/index.js",
28179
28179
  module: "./dist/index.js",
28180
28180
  type: "module",
@@ -28460,7 +28460,7 @@ var configureRenderer = (renderer) => {
28460
28460
  // src/react-three/Canvas.tsx
28461
28461
  import { jsx as jsx11 } from "react/jsx-runtime";
28462
28462
  var Canvas = forwardRef(
28463
- ({ children, scene: sceneProps, camera: cameraProps, style }, ref) => {
28463
+ ({ children, scene: sceneProps, camera: cameraProps, style, onCreated }, ref) => {
28464
28464
  const mountRef = useRef4(null);
28465
28465
  const [contextState, setContextState] = useState7(
28466
28466
  null
@@ -28468,6 +28468,8 @@ var Canvas = forwardRef(
28468
28468
  const frameListeners = useRef4(
28469
28469
  []
28470
28470
  );
28471
+ const onCreatedRef = useRef4(void 0);
28472
+ onCreatedRef.current = onCreated;
28471
28473
  const addFrameListener = useCallback4(
28472
28474
  (listener) => {
28473
28475
  frameListeners.current.push(listener);
@@ -28526,6 +28528,7 @@ var Canvas = forwardRef(
28526
28528
  addFrameListener,
28527
28529
  removeFrameListener
28528
28530
  });
28531
+ onCreatedRef.current?.({ camera, renderer });
28529
28532
  let animationFrameId;
28530
28533
  const clock = new THREE10.Clock();
28531
28534
  const animate = () => {
@@ -28585,10 +28588,17 @@ var OrbitControls = ({
28585
28588
  return new ThreeOrbitControls(camera, renderer.domElement);
28586
28589
  }, [camera, renderer]);
28587
28590
  useEffect13(() => {
28588
- if (!onControlsChange) return;
28589
- onControlsChange(controls ?? null);
28591
+ onControlsChange?.(controls ?? null);
28592
+ return () => onControlsChange?.(null);
28593
+ }, [controls, onControlsChange]);
28594
+ useEffect13(() => {
28595
+ if (!controls) return;
28596
+ const handleChange = () => {
28597
+ onControlsChange?.(controls);
28598
+ };
28599
+ controls.addEventListener("change", handleChange);
28590
28600
  return () => {
28591
- onControlsChange(null);
28601
+ controls.removeEventListener("change", handleChange);
28592
28602
  };
28593
28603
  }, [controls, onControlsChange]);
28594
28604
  useEffect13(() => {
@@ -28754,6 +28764,45 @@ var Lights = () => {
28754
28764
  return null;
28755
28765
  };
28756
28766
 
28767
+ // src/hooks/useSessionCamera.ts
28768
+ var CAMERA_KEY = "cadViewerCameraStateSession";
28769
+ var saveCameraToSession = (camera, controls) => {
28770
+ try {
28771
+ const savedCameraSession = {
28772
+ position: camera.position.toArray(),
28773
+ quaternion: camera.quaternion.toArray(),
28774
+ up: camera.up.toArray(),
28775
+ fov: camera.fov ?? 50,
28776
+ target: controls.target.toArray()
28777
+ };
28778
+ sessionStorage.setItem(CAMERA_KEY, JSON.stringify(savedCameraSession));
28779
+ } catch (err) {
28780
+ console.warn("Failed to save camera:", err);
28781
+ }
28782
+ };
28783
+ var loadCameraFromSession = (camera, controls) => {
28784
+ try {
28785
+ const raw = sessionStorage.getItem(CAMERA_KEY);
28786
+ if (!raw) return false;
28787
+ const state = JSON.parse(raw);
28788
+ camera.position.fromArray(state.position);
28789
+ camera.quaternion.fromArray(state.quaternion);
28790
+ camera.up.fromArray(state.up);
28791
+ if ("fov" in camera) {
28792
+ const persp = camera;
28793
+ persp.fov = state.fov;
28794
+ persp.updateProjectionMatrix?.();
28795
+ }
28796
+ controls.target.fromArray(state.target);
28797
+ controls.update();
28798
+ camera.updateMatrixWorld();
28799
+ return true;
28800
+ } catch (err) {
28801
+ console.warn("Failed to restore camera:", err);
28802
+ return false;
28803
+ }
28804
+ };
28805
+
28757
28806
  // src/hooks/useCameraController.ts
28758
28807
  import { useCallback as useCallback5, useEffect as useEffect16, useMemo as useMemo14, useRef as useRef5 } from "react";
28759
28808
  import * as THREE13 from "three";
@@ -29045,6 +29094,10 @@ var CadViewerContainer = forwardRef2(
29045
29094
  const [isInteractionEnabled, setIsInteractionEnabled] = useState8(
29046
29095
  !clickToInteractEnabled
29047
29096
  );
29097
+ const saveTimeoutRef = useRef6(null);
29098
+ const controlsRef = useRef6(null);
29099
+ const cameraRef = useRef6(null);
29100
+ const restoredOnceRef = useRef6(false);
29048
29101
  const gridSectionSize = useMemo15(() => {
29049
29102
  if (!boardDimensions) return 10;
29050
29103
  const width10 = boardDimensions.width ?? 0;
@@ -29098,6 +29151,23 @@ var CadViewerContainer = forwardRef2(
29098
29151
  ref,
29099
29152
  scene: { up: new THREE14.Vector3(0, 0, 1) },
29100
29153
  camera: { up: [0, 0, 1], position: initialCameraPosition },
29154
+ onCreated: ({ camera }) => {
29155
+ cameraRef.current = camera;
29156
+ if (!restoredOnceRef.current && controlsRef.current) {
29157
+ const restored = loadCameraFromSession(
29158
+ cameraRef.current,
29159
+ controlsRef.current
29160
+ );
29161
+ if (restored) restoredOnceRef.current = true;
29162
+ }
29163
+ if (controlsRef.current && !restoredOnceRef.current) {
29164
+ setTimeout(() => {
29165
+ if (cameraRef.current && controlsRef.current) {
29166
+ saveCameraToSession(cameraRef.current, controlsRef.current);
29167
+ }
29168
+ }, 0);
29169
+ }
29170
+ },
29101
29171
  children: [
29102
29172
  /* @__PURE__ */ jsx12(CameraAnimator, { ...cameraAnimatorProps }),
29103
29173
  /* @__PURE__ */ jsx12(RotationTracker, {}),
@@ -29113,7 +29183,26 @@ var CadViewerContainer = forwardRef2(
29113
29183
  enableDamping: true,
29114
29184
  dampingFactor: 0.1,
29115
29185
  target: orbitTarget,
29116
- onControlsChange: handleControlsChange
29186
+ onControlsChange: (controls) => {
29187
+ handleControlsChange(controls);
29188
+ controlsRef.current = controls;
29189
+ if (cameraRef.current && controlsRef.current && !restoredOnceRef.current) {
29190
+ const restored = loadCameraFromSession(
29191
+ cameraRef.current,
29192
+ controlsRef.current
29193
+ );
29194
+ if (restored) {
29195
+ restoredOnceRef.current = true;
29196
+ return;
29197
+ }
29198
+ }
29199
+ clearTimeout(saveTimeoutRef.current);
29200
+ saveTimeoutRef.current = setTimeout(() => {
29201
+ if (cameraRef.current && controlsRef.current) {
29202
+ saveCameraToSession(cameraRef.current, controlsRef.current);
29203
+ }
29204
+ }, 150);
29205
+ }
29117
29206
  }
29118
29207
  ),
29119
29208
  /* @__PURE__ */ jsx12(Lights, {}),
@@ -29237,7 +29326,7 @@ var useStlsFromGeom = (geom) => {
29237
29326
  };
29238
29327
 
29239
29328
  // src/hooks/useBoardGeomBuilder.ts
29240
- import { useState as useState10, useEffect as useEffect18, useRef as useRef6 } from "react";
29329
+ import { useState as useState10, useEffect as useEffect18, useRef as useRef7 } from "react";
29241
29330
 
29242
29331
  // src/soup-to-3d/index.ts
29243
29332
  var import_primitives2 = __toESM(require_primitives(), 1);
@@ -31098,7 +31187,7 @@ var BoardGeomBuilder = class {
31098
31187
  // src/hooks/useBoardGeomBuilder.ts
31099
31188
  var useBoardGeomBuilder = (circuitJson) => {
31100
31189
  const [boardGeom, setBoardGeom] = useState10(null);
31101
- const isProcessingRef = useRef6(false);
31190
+ const isProcessingRef = useRef7(false);
31102
31191
  useEffect18(() => {
31103
31192
  let isCancelled = false;
31104
31193
  if (!circuitJson) {
@@ -31476,7 +31565,7 @@ import { su as su13 } from "@tscircuit/circuit-json-util";
31476
31565
  import { useEffect as useEffect22, useMemo as useMemo21, useState as useState15 } from "react";
31477
31566
 
31478
31567
  // src/hooks/useManifoldBoardBuilder.ts
31479
- import { useState as useState14, useEffect as useEffect21, useMemo as useMemo20, useRef as useRef7 } from "react";
31568
+ import { useState as useState14, useEffect as useEffect21, useMemo as useMemo20, useRef as useRef8 } from "react";
31480
31569
  import { su as su12 } from "@tscircuit/circuit-json-util";
31481
31570
  import * as THREE24 from "three";
31482
31571
 
@@ -32821,7 +32910,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
32821
32910
  const [pcbThickness, setPcbThickness] = useState14(null);
32822
32911
  const [error, setError] = useState14(null);
32823
32912
  const [isLoading, setIsLoading] = useState14(true);
32824
- const manifoldInstancesForCleanup = useRef7([]);
32913
+ const manifoldInstancesForCleanup = useRef8([]);
32825
32914
  const boardData = useMemo20(() => {
32826
32915
  const boards = su12(circuitJson).pcb_board.list();
32827
32916
  if (boards.length === 0) {
@@ -33470,17 +33559,17 @@ try {
33470
33559
  var CadViewerManifold_default = CadViewerManifold;
33471
33560
 
33472
33561
  // src/hooks/useContextMenu.ts
33473
- import { useState as useState16, useCallback as useCallback8, useRef as useRef8, useEffect as useEffect23 } from "react";
33562
+ import { useState as useState16, useCallback as useCallback8, useRef as useRef9, useEffect as useEffect23 } from "react";
33474
33563
  var useContextMenu = ({ containerRef }) => {
33475
33564
  const [menuVisible, setMenuVisible] = useState16(false);
33476
33565
  const [menuPos, setMenuPos] = useState16({
33477
33566
  x: 0,
33478
33567
  y: 0
33479
33568
  });
33480
- const menuRef = useRef8(null);
33481
- const interactionOriginPosRef = useRef8(null);
33482
- const longPressTimeoutRef = useRef8(null);
33483
- const ignoreNextContextMenuRef = useRef8(false);
33569
+ const menuRef = useRef9(null);
33570
+ const interactionOriginPosRef = useRef9(null);
33571
+ const longPressTimeoutRef = useRef9(null);
33572
+ const ignoreNextContextMenuRef = useRef9(false);
33484
33573
  const clearLongPressTimeout = () => {
33485
33574
  if (longPressTimeoutRef.current !== null) {
33486
33575
  clearTimeout(longPressTimeoutRef.current);
@@ -33574,9 +33663,16 @@ var useContextMenu = ({ containerRef }) => {
33574
33663
  }, []);
33575
33664
  const handleClickAway = useCallback8((e) => {
33576
33665
  const target = e.target;
33577
- if (menuRef.current && !menuRef.current.contains(target)) {
33578
- setMenuVisible(false);
33666
+ if (menuRef.current && menuRef.current.contains(target)) {
33667
+ return;
33579
33668
  }
33669
+ const isInRadixPortal = target.closest?.(
33670
+ "[data-radix-popper-content-wrapper], [data-radix-dropdown-menu-content], [data-radix-dropdown-menu-sub-content]"
33671
+ );
33672
+ if (isInRadixPortal) {
33673
+ return;
33674
+ }
33675
+ setMenuVisible(false);
33580
33676
  }, []);
33581
33677
  useEffect23(() => {
33582
33678
  if (menuVisible) {
@@ -39156,6 +39252,7 @@ var AppearanceMenu = () => {
39156
39252
  },
39157
39253
  onMouseEnter: () => setHoveredItem("appearance"),
39158
39254
  onMouseLeave: () => setHoveredItem(null),
39255
+ onTouchStart: () => setHoveredItem("appearance"),
39159
39256
  children: [
39160
39257
  /* @__PURE__ */ jsx32("span", { style: { flex: 1, display: "flex", alignItems: "center" }, children: "Appearance" }),
39161
39258
  /* @__PURE__ */ jsx32(
@@ -39194,6 +39291,7 @@ var AppearanceMenu = () => {
39194
39291
  },
39195
39292
  onMouseEnter: () => setHoveredItem("boardBody"),
39196
39293
  onMouseLeave: () => setHoveredItem(null),
39294
+ onTouchStart: () => setHoveredItem("boardBody"),
39197
39295
  children: [
39198
39296
  /* @__PURE__ */ jsx32("span", { style: iconContainerStyles, children: visibility.boardBody && /* @__PURE__ */ jsx32(CheckIcon, {}) }),
39199
39297
  /* @__PURE__ */ jsx32("span", { style: { display: "flex", alignItems: "center" }, children: "Board Body" })
@@ -39214,6 +39312,7 @@ var AppearanceMenu = () => {
39214
39312
  },
39215
39313
  onMouseEnter: () => setHoveredItem("topCopper"),
39216
39314
  onMouseLeave: () => setHoveredItem(null),
39315
+ onTouchStart: () => setHoveredItem("topCopper"),
39217
39316
  children: [
39218
39317
  /* @__PURE__ */ jsx32("span", { style: iconContainerStyles, children: visibility.topCopper && /* @__PURE__ */ jsx32(CheckIcon, {}) }),
39219
39318
  /* @__PURE__ */ jsx32("span", { style: { display: "flex", alignItems: "center" }, children: "Top Copper" })
@@ -39234,6 +39333,7 @@ var AppearanceMenu = () => {
39234
39333
  },
39235
39334
  onMouseEnter: () => setHoveredItem("bottomCopper"),
39236
39335
  onMouseLeave: () => setHoveredItem(null),
39336
+ onTouchStart: () => setHoveredItem("bottomCopper"),
39237
39337
  children: [
39238
39338
  /* @__PURE__ */ jsx32("span", { style: iconContainerStyles, children: visibility.bottomCopper && /* @__PURE__ */ jsx32(CheckIcon, {}) }),
39239
39339
  /* @__PURE__ */ jsx32("span", { style: { display: "flex", alignItems: "center" }, children: "Bottom Copper" })
@@ -39254,6 +39354,7 @@ var AppearanceMenu = () => {
39254
39354
  },
39255
39355
  onMouseEnter: () => setHoveredItem("topSilkscreen"),
39256
39356
  onMouseLeave: () => setHoveredItem(null),
39357
+ onTouchStart: () => setHoveredItem("topSilkscreen"),
39257
39358
  children: [
39258
39359
  /* @__PURE__ */ jsx32("span", { style: iconContainerStyles, children: visibility.topSilkscreen && /* @__PURE__ */ jsx32(CheckIcon, {}) }),
39259
39360
  /* @__PURE__ */ jsx32("span", { style: { display: "flex", alignItems: "center" }, children: "Top Silkscreen" })
@@ -39274,6 +39375,7 @@ var AppearanceMenu = () => {
39274
39375
  },
39275
39376
  onMouseEnter: () => setHoveredItem("bottomSilkscreen"),
39276
39377
  onMouseLeave: () => setHoveredItem(null),
39378
+ onTouchStart: () => setHoveredItem("bottomSilkscreen"),
39277
39379
  children: [
39278
39380
  /* @__PURE__ */ jsx32("span", { style: iconContainerStyles, children: visibility.bottomSilkscreen && /* @__PURE__ */ jsx32(CheckIcon, {}) }),
39279
39381
  /* @__PURE__ */ jsx32("span", { style: { display: "flex", alignItems: "center" }, children: "Bottom Silkscreen" })
@@ -39294,6 +39396,7 @@ var AppearanceMenu = () => {
39294
39396
  },
39295
39397
  onMouseEnter: () => setHoveredItem("smtModels"),
39296
39398
  onMouseLeave: () => setHoveredItem(null),
39399
+ onTouchStart: () => setHoveredItem("smtModels"),
39297
39400
  children: [
39298
39401
  /* @__PURE__ */ jsx32("span", { style: iconContainerStyles, children: visibility.smtModels && /* @__PURE__ */ jsx32(CheckIcon, {}) }),
39299
39402
  /* @__PURE__ */ jsx32("span", { style: { display: "flex", alignItems: "center" }, children: "CAD Models" })
@@ -39423,6 +39526,7 @@ var ContextMenu = ({
39423
39526
  },
39424
39527
  onMouseEnter: () => setHoveredItem("camera"),
39425
39528
  onMouseLeave: () => setHoveredItem(null),
39529
+ onTouchStart: () => setHoveredItem("camera"),
39426
39530
  children: [
39427
39531
  /* @__PURE__ */ jsx33(
39428
39532
  "span",
@@ -39469,6 +39573,7 @@ var ContextMenu = ({
39469
39573
  },
39470
39574
  onMouseEnter: () => setHoveredItem(option),
39471
39575
  onMouseLeave: () => setHoveredItem(null),
39576
+ onTouchStart: () => setHoveredItem(option),
39472
39577
  children: [
39473
39578
  /* @__PURE__ */ jsx33("span", { style: iconContainerStyles2, children: cameraPreset === option && /* @__PURE__ */ jsx33(DotIcon, {}) }),
39474
39579
  /* @__PURE__ */ jsx33("span", { style: { display: "flex", alignItems: "center" }, children: option })
@@ -39494,6 +39599,7 @@ var ContextMenu = ({
39494
39599
  },
39495
39600
  onMouseEnter: () => setHoveredItem("autorotate"),
39496
39601
  onMouseLeave: () => setHoveredItem(null),
39602
+ onTouchStart: () => setHoveredItem("autorotate"),
39497
39603
  children: [
39498
39604
  /* @__PURE__ */ jsx33("span", { style: iconContainerStyles2, children: autoRotate && /* @__PURE__ */ jsx33(CheckIcon, {}) }),
39499
39605
  /* @__PURE__ */ jsx33("span", { style: { display: "flex", alignItems: "center" }, children: "Auto rotate" })
@@ -39513,6 +39619,7 @@ var ContextMenu = ({
39513
39619
  onSelect: onDownloadGltf,
39514
39620
  onMouseEnter: () => setHoveredItem("download"),
39515
39621
  onMouseLeave: () => setHoveredItem(null),
39622
+ onTouchStart: () => setHoveredItem("download"),
39516
39623
  children: /* @__PURE__ */ jsx33("span", { style: { display: "flex", alignItems: "center" }, children: "Download GLTF" })
39517
39624
  }
39518
39625
  ),
@@ -39532,6 +39639,7 @@ var ContextMenu = ({
39532
39639
  },
39533
39640
  onMouseEnter: () => setHoveredItem("engine"),
39534
39641
  onMouseLeave: () => setHoveredItem(null),
39642
+ onTouchStart: () => setHoveredItem("engine"),
39535
39643
  children: [
39536
39644
  /* @__PURE__ */ jsxs9("span", { style: { flex: 1, display: "flex", alignItems: "center" }, children: [
39537
39645
  "Switch to ",
@@ -39594,7 +39702,7 @@ var ContextMenu = ({
39594
39702
  import { jsx as jsx34, jsxs as jsxs10 } from "react/jsx-runtime";
39595
39703
  var CadViewerInner = (props) => {
39596
39704
  const [engine, setEngine] = useState33("manifold");
39597
- const containerRef = useRef22(null);
39705
+ const containerRef = useRef23(null);
39598
39706
  const [autoRotate, setAutoRotate] = useState33(() => {
39599
39707
  const stored = window.localStorage.getItem("cadViewerAutoRotate");
39600
39708
  return stored === "false" ? false : true;
@@ -39605,7 +39713,7 @@ var CadViewerInner = (props) => {
39605
39713
  });
39606
39714
  const [cameraPreset, setCameraPreset] = useState33("Custom");
39607
39715
  const { visibility, toggleLayer } = useLayerVisibility();
39608
- const cameraControllerRef = useRef22(null);
39716
+ const cameraControllerRef = useRef23(null);
39609
39717
  const externalCameraControllerReady = props.onCameraControllerReady;
39610
39718
  const {
39611
39719
  menuVisible,
@@ -39614,10 +39722,10 @@ var CadViewerInner = (props) => {
39614
39722
  contextMenuEventHandlers,
39615
39723
  setMenuVisible
39616
39724
  } = useContextMenu({ containerRef });
39617
- const autoRotateUserToggledRef = useRef22(autoRotateUserToggled);
39725
+ const autoRotateUserToggledRef = useRef23(autoRotateUserToggled);
39618
39726
  autoRotateUserToggledRef.current = autoRotateUserToggled;
39619
- const isAnimatingRef = useRef22(false);
39620
- const lastPresetSelectTime = useRef22(0);
39727
+ const isAnimatingRef = useRef23(false);
39728
+ const lastPresetSelectTime = useRef23(0);
39621
39729
  const PRESET_COOLDOWN = 1e3;
39622
39730
  const handleUserInteraction = useCallback20(() => {
39623
39731
  if (isAnimatingRef.current || Date.now() - lastPresetSelectTime.current < PRESET_COOLDOWN) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/3d-viewer",
3
- "version": "0.0.431",
3
+ "version": "0.0.433",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",