@tscircuit/3d-viewer 0.0.352 → 0.0.354

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 +126 -20
  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 useState15, useCallback as useCallback7, useRef as useRef9, useEffect as useEffect22 } from "react";
14231
+ import { useState as useState15, useCallback as useCallback8, useRef as useRef9, useEffect as useEffect22 } from "react";
14232
14232
 
14233
14233
  // src/CadViewerJscad.tsx
14234
14234
  import { su as su4 } from "@tscircuit/circuit-json-util";
@@ -25583,7 +25583,7 @@ import * as THREE10 from "three";
25583
25583
  // package.json
25584
25584
  var package_default = {
25585
25585
  name: "@tscircuit/3d-viewer",
25586
- version: "0.0.351",
25586
+ version: "0.0.353",
25587
25587
  main: "./dist/index.js",
25588
25588
  module: "./dist/index.js",
25589
25589
  type: "module",
@@ -25912,6 +25912,7 @@ var Canvas = forwardRef(
25912
25912
  }
25913
25913
  camera.lookAt(0, 0, 0);
25914
25914
  scene.add(rootObject.current);
25915
+ window.__TSCIRCUIT_THREE_OBJECT = rootObject.current;
25915
25916
  setContextState({
25916
25917
  scene,
25917
25918
  camera,
@@ -25949,6 +25950,9 @@ var Canvas = forwardRef(
25949
25950
  }
25950
25951
  renderer.dispose();
25951
25952
  scene.remove(rootObject.current);
25953
+ if (window.__TSCIRCUIT_THREE_OBJECT === rootObject.current) {
25954
+ window.__TSCIRCUIT_THREE_OBJECT = void 0;
25955
+ }
25952
25956
  };
25953
25957
  }, [scene, addFrameListener, removeFrameListener]);
25954
25958
  return /* @__PURE__ */ jsx9("div", { ref: mountRef, style: { width: "100%", height: "100%", ...style }, children: contextState && /* @__PURE__ */ jsx9(ThreeContext.Provider, { value: contextState, children: /* @__PURE__ */ jsx9(HoverProvider, { children }) }) });
@@ -29055,11 +29059,23 @@ var useContextMenu = ({ containerRef }) => {
29055
29059
  });
29056
29060
  const menuRef = useRef8(null);
29057
29061
  const interactionOriginPosRef = useRef8(null);
29062
+ const longPressTimeoutRef = useRef8(null);
29063
+ const ignoreNextContextMenuRef = useRef8(false);
29064
+ const clearLongPressTimeout = () => {
29065
+ if (longPressTimeoutRef.current !== null) {
29066
+ clearTimeout(longPressTimeoutRef.current);
29067
+ longPressTimeoutRef.current = null;
29068
+ }
29069
+ };
29058
29070
  const handleContextMenu = useCallback6(
29059
29071
  (e) => {
29060
29072
  e.preventDefault();
29061
29073
  const eventX = typeof e.clientX === "number" ? e.clientX : 0;
29062
29074
  const eventY = typeof e.clientY === "number" ? e.clientY : 0;
29075
+ if (ignoreNextContextMenuRef.current) {
29076
+ ignoreNextContextMenuRef.current = false;
29077
+ return;
29078
+ }
29063
29079
  if (!interactionOriginPosRef.current) {
29064
29080
  return;
29065
29081
  }
@@ -29077,18 +29093,39 @@ var useContextMenu = ({ containerRef }) => {
29077
29093
  },
29078
29094
  [setMenuPos, setMenuVisible]
29079
29095
  );
29080
- const handleTouchStart = useCallback6((e) => {
29081
- if (e.touches.length === 1) {
29082
- const touch = e.touches[0];
29083
- if (touch) {
29084
- interactionOriginPosRef.current = { x: touch.clientX, y: touch.clientY };
29096
+ const handleTouchStart = useCallback6(
29097
+ (e) => {
29098
+ if (e.touches.length === 1) {
29099
+ const touch = e.touches[0];
29100
+ if (touch) {
29101
+ interactionOriginPosRef.current = {
29102
+ x: touch.clientX,
29103
+ y: touch.clientY
29104
+ };
29105
+ clearLongPressTimeout();
29106
+ longPressTimeoutRef.current = window.setTimeout(() => {
29107
+ if (!interactionOriginPosRef.current) return;
29108
+ if (containerRef.current) {
29109
+ const rect = containerRef.current.getBoundingClientRect();
29110
+ setMenuPos({
29111
+ x: rect.left + rect.width / 2,
29112
+ y: rect.top + rect.height / 2
29113
+ });
29114
+ setMenuVisible(true);
29115
+ ignoreNextContextMenuRef.current = true;
29116
+ }
29117
+ interactionOriginPosRef.current = null;
29118
+ }, 600);
29119
+ } else {
29120
+ interactionOriginPosRef.current = null;
29121
+ }
29085
29122
  } else {
29086
29123
  interactionOriginPosRef.current = null;
29124
+ clearLongPressTimeout();
29087
29125
  }
29088
- } else {
29089
- interactionOriginPosRef.current = null;
29090
- }
29091
- }, []);
29126
+ },
29127
+ [containerRef]
29128
+ );
29092
29129
  const handleTouchMove = useCallback6((e) => {
29093
29130
  if (!interactionOriginPosRef.current || e.touches.length !== 1) {
29094
29131
  return;
@@ -29100,12 +29137,15 @@ var useContextMenu = ({ containerRef }) => {
29100
29137
  const swipeThreshold = 10;
29101
29138
  if (dx > swipeThreshold || dy > swipeThreshold) {
29102
29139
  interactionOriginPosRef.current = null;
29140
+ clearLongPressTimeout();
29103
29141
  }
29104
29142
  } else {
29105
29143
  interactionOriginPosRef.current = null;
29144
+ clearLongPressTimeout();
29106
29145
  }
29107
29146
  }, []);
29108
29147
  const handleTouchEnd = useCallback6(() => {
29148
+ clearLongPressTimeout();
29109
29149
  setTimeout(() => {
29110
29150
  if (interactionOriginPosRef.current) {
29111
29151
  interactionOriginPosRef.current = null;
@@ -29121,7 +29161,11 @@ var useContextMenu = ({ containerRef }) => {
29121
29161
  useEffect21(() => {
29122
29162
  if (menuVisible) {
29123
29163
  document.addEventListener("mousedown", handleClickAway);
29124
- return () => document.removeEventListener("mousedown", handleClickAway);
29164
+ document.addEventListener("touchstart", handleClickAway);
29165
+ return () => {
29166
+ document.removeEventListener("mousedown", handleClickAway);
29167
+ document.removeEventListener("touchstart", handleClickAway);
29168
+ };
29125
29169
  }
29126
29170
  }, [menuVisible, handleClickAway]);
29127
29171
  const contextMenuEventHandlers = {
@@ -29147,6 +29191,35 @@ var useContextMenu = ({ containerRef }) => {
29147
29191
  };
29148
29192
  };
29149
29193
 
29194
+ // src/hooks/useGlobalDownloadGltf.ts
29195
+ import { useCallback as useCallback7 } from "react";
29196
+ import { GLTFExporter } from "three-stdlib";
29197
+ var useGlobalDownloadGltf = () => {
29198
+ return useCallback7(() => {
29199
+ const root = window.__TSCIRCUIT_THREE_OBJECT;
29200
+ if (!root) return;
29201
+ const exporter = new GLTFExporter();
29202
+ exporter.parse(
29203
+ root,
29204
+ (gltf) => {
29205
+ const blob = new Blob(
29206
+ [gltf instanceof ArrayBuffer ? gltf : JSON.stringify(gltf)],
29207
+ { type: "model/gltf+json" }
29208
+ );
29209
+ const url = URL.createObjectURL(blob);
29210
+ const link = document.createElement("a");
29211
+ link.href = url;
29212
+ link.download = "scene.gltf";
29213
+ link.click();
29214
+ URL.revokeObjectURL(url);
29215
+ },
29216
+ (err) => {
29217
+ console.error("Failed to export GLTF", err);
29218
+ }
29219
+ );
29220
+ }, []);
29221
+ };
29222
+
29150
29223
  // src/CadViewer.tsx
29151
29224
  import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
29152
29225
  var CadViewer = (props) => {
@@ -29163,15 +29236,16 @@ var CadViewer = (props) => {
29163
29236
  } = useContextMenu({ containerRef });
29164
29237
  const autoRotateUserToggledRef = useRef9(autoRotateUserToggled);
29165
29238
  autoRotateUserToggledRef.current = autoRotateUserToggled;
29166
- const handleUserInteraction = useCallback7(() => {
29239
+ const handleUserInteraction = useCallback8(() => {
29167
29240
  if (!autoRotateUserToggledRef.current) {
29168
29241
  setAutoRotate(false);
29169
29242
  }
29170
29243
  }, []);
29171
- const toggleAutoRotate = useCallback7(() => {
29244
+ const toggleAutoRotate = useCallback8(() => {
29172
29245
  setAutoRotate((prev) => !prev);
29173
29246
  setAutoRotateUserToggled(true);
29174
29247
  }, []);
29248
+ const downloadGltf = useGlobalDownloadGltf();
29175
29249
  const handleMenuClick = (newEngine) => {
29176
29250
  setEngine(newEngine);
29177
29251
  setMenuVisible(false);
@@ -29190,7 +29264,16 @@ var CadViewer = (props) => {
29190
29264
  "div",
29191
29265
  {
29192
29266
  ref: containerRef,
29193
- style: { width: "100%", height: "100%", position: "relative" },
29267
+ style: {
29268
+ width: "100%",
29269
+ height: "100%",
29270
+ position: "relative",
29271
+ userSelect: "none",
29272
+ MozUserSelect: "none",
29273
+ msUserSelect: "none",
29274
+ WebkitUserSelect: "none",
29275
+ WebkitTouchCallout: "none"
29276
+ },
29194
29277
  ...contextMenuEventHandlers,
29195
29278
  children: [
29196
29279
  engine === "jscad" ? /* @__PURE__ */ jsx14(
@@ -29312,6 +29395,29 @@ var CadViewer = (props) => {
29312
29395
  ]
29313
29396
  }
29314
29397
  ),
29398
+ /* @__PURE__ */ jsx14(
29399
+ "div",
29400
+ {
29401
+ style: {
29402
+ padding: "12px 18px",
29403
+ cursor: "pointer",
29404
+ display: "flex",
29405
+ alignItems: "center",
29406
+ gap: 10,
29407
+ color: "#f5f6fa",
29408
+ fontWeight: 500,
29409
+ borderRadius: 6,
29410
+ transition: "background 0.1s"
29411
+ },
29412
+ onClick: () => {
29413
+ downloadGltf();
29414
+ setMenuVisible(false);
29415
+ },
29416
+ onMouseOver: (e) => e.currentTarget.style.background = "#2d313a",
29417
+ onMouseOut: (e) => e.currentTarget.style.background = "transparent",
29418
+ children: "Download GLTF"
29419
+ }
29420
+ ),
29315
29421
  /* @__PURE__ */ jsx14(
29316
29422
  "div",
29317
29423
  {
@@ -29616,8 +29722,8 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
29616
29722
  }
29617
29723
 
29618
29724
  // src/hooks/exporter/gltf.ts
29619
- import { GLTFExporter } from "three-stdlib";
29620
- import { useEffect as useEffect23, useState as useState16, useMemo as useMemo19, useCallback as useCallback8 } from "react";
29725
+ import { GLTFExporter as GLTFExporter2 } from "three-stdlib";
29726
+ import { useEffect as useEffect23, useState as useState16, useMemo as useMemo19, useCallback as useCallback9 } from "react";
29621
29727
  function useSaveGltfAs(options = {}) {
29622
29728
  const parse = useParser(options);
29623
29729
  const link = useMemo19(() => document.createElement("a"), []);
@@ -29638,7 +29744,7 @@ function useSaveGltfAs(options = {}) {
29638
29744
  []
29639
29745
  );
29640
29746
  let instance;
29641
- const ref = useCallback8((obj3D) => {
29747
+ const ref = useCallback9((obj3D) => {
29642
29748
  instance = obj3D;
29643
29749
  }, []);
29644
29750
  return [ref, saveAs];
@@ -29647,7 +29753,7 @@ function useExportGltfUrl(options = {}) {
29647
29753
  const parse = useParser(options);
29648
29754
  const [url, setUrl] = useState16();
29649
29755
  const [error, setError] = useState16();
29650
- const ref = useCallback8(
29756
+ const ref = useCallback9(
29651
29757
  (instance) => parse(instance).then(setUrl).catch(setError),
29652
29758
  []
29653
29759
  );
@@ -29655,7 +29761,7 @@ function useExportGltfUrl(options = {}) {
29655
29761
  return [ref, url, error];
29656
29762
  }
29657
29763
  function useParser(options = {}) {
29658
- const exporter = useMemo19(() => new GLTFExporter(), []);
29764
+ const exporter = useMemo19(() => new GLTFExporter2(), []);
29659
29765
  return (instance) => {
29660
29766
  const { promise, resolve, reject } = Promise.withResolvers();
29661
29767
  exporter.parse(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/3d-viewer",
3
- "version": "0.0.352",
3
+ "version": "0.0.354",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",