@tscircuit/3d-viewer 0.0.439 → 0.0.440

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 +391 -44
  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 useState34, useCallback as useCallback22, useRef as useRef24, useEffect as useEffect40, useMemo as useMemo26 } from "react";
14231
+ import { useState as useState36, useCallback as useCallback22, useRef as useRef26, useEffect as useEffect42, useMemo as useMemo28 } from "react";
14232
14232
  import * as THREE29 from "three";
14233
14233
 
14234
14234
  // src/CadViewerJscad.tsx
@@ -28288,7 +28288,7 @@ import * as THREE15 from "three";
28288
28288
  // package.json
28289
28289
  var package_default = {
28290
28290
  name: "@tscircuit/3d-viewer",
28291
- version: "0.0.438",
28291
+ version: "0.0.439",
28292
28292
  main: "./dist/index.js",
28293
28293
  module: "./dist/index.js",
28294
28294
  type: "module",
@@ -34503,8 +34503,110 @@ var useGlobalDownloadGltf = () => {
34503
34503
  }, []);
34504
34504
  };
34505
34505
 
34506
+ // src/hooks/useRegisteredHotkey.ts
34507
+ import { useEffect as useEffect25, useMemo as useMemo21, useRef as useRef11, useState as useState18 } from "react";
34508
+ var hotkeyRegistry = /* @__PURE__ */ new Map();
34509
+ var subscribers = /* @__PURE__ */ new Set();
34510
+ var isListenerAttached = false;
34511
+ var matchesKey = (eventKey, targetKey) => {
34512
+ if (!eventKey || !targetKey) return false;
34513
+ return eventKey.toLowerCase() === targetKey.toLowerCase();
34514
+ };
34515
+ var matchesModifiers = (event, modifiers) => {
34516
+ if (!modifiers || modifiers.length === 0) {
34517
+ return !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey;
34518
+ }
34519
+ const hasCtrl = modifiers.includes("Ctrl");
34520
+ const hasCmd = modifiers.includes("Cmd");
34521
+ const hasShift = modifiers.includes("Shift");
34522
+ const hasAlt = modifiers.includes("Alt");
34523
+ if (hasCtrl && !event.ctrlKey) return false;
34524
+ if (hasCmd && !event.metaKey) return false;
34525
+ if (hasShift && !event.shiftKey) return false;
34526
+ if (hasAlt && !event.altKey) return false;
34527
+ return true;
34528
+ };
34529
+ var isEditableTarget = (target) => {
34530
+ if (!target || typeof target !== "object") return false;
34531
+ const element = target;
34532
+ const tagName = element.tagName;
34533
+ const editableTags = ["INPUT", "TEXTAREA", "SELECT"];
34534
+ if (editableTags.includes(tagName)) {
34535
+ return true;
34536
+ }
34537
+ return Boolean(element.getAttribute?.("contenteditable"));
34538
+ };
34539
+ var handleKeydown = (event) => {
34540
+ if (isEditableTarget(event.target)) {
34541
+ return;
34542
+ }
34543
+ hotkeyRegistry.forEach((entry) => {
34544
+ if (matchesKey(event.key, entry.key) && matchesModifiers(event, entry.modifiers)) {
34545
+ event.preventDefault();
34546
+ entry.invoke();
34547
+ }
34548
+ });
34549
+ };
34550
+ var notifySubscribers = () => {
34551
+ const entries = Array.from(hotkeyRegistry.values());
34552
+ subscribers.forEach((subscriber) => subscriber(entries));
34553
+ };
34554
+ var ensureListener = () => {
34555
+ if (isListenerAttached) return;
34556
+ if (typeof window === "undefined") return;
34557
+ window.addEventListener("keydown", handleKeydown);
34558
+ isListenerAttached = true;
34559
+ };
34560
+ var registerHotkey = (registration) => {
34561
+ hotkeyRegistry.set(registration.id, registration);
34562
+ notifySubscribers();
34563
+ ensureListener();
34564
+ };
34565
+ var unregisterHotkey = (id) => {
34566
+ if (hotkeyRegistry.delete(id)) {
34567
+ notifySubscribers();
34568
+ }
34569
+ };
34570
+ var subscribeToRegistry = (subscriber) => {
34571
+ subscribers.add(subscriber);
34572
+ subscriber(Array.from(hotkeyRegistry.values()));
34573
+ return () => {
34574
+ subscribers.delete(subscriber);
34575
+ };
34576
+ };
34577
+ var useRegisteredHotkey = (id, handler, metadata) => {
34578
+ const handlerRef = useRef11(handler);
34579
+ handlerRef.current = handler;
34580
+ const normalizedMetadata = useMemo21(
34581
+ () => ({
34582
+ key: metadata.key,
34583
+ description: metadata.description,
34584
+ modifiers: metadata.modifiers
34585
+ }),
34586
+ [metadata.key, metadata.description, metadata.modifiers]
34587
+ );
34588
+ useEffect25(() => {
34589
+ const registration = {
34590
+ id,
34591
+ ...normalizedMetadata,
34592
+ invoke: () => handlerRef.current()
34593
+ };
34594
+ registerHotkey(registration);
34595
+ return () => {
34596
+ unregisterHotkey(id);
34597
+ };
34598
+ }, [id, normalizedMetadata]);
34599
+ };
34600
+ var useHotkeyRegistry = () => {
34601
+ const [entries, setEntries] = useState18(
34602
+ () => Array.from(hotkeyRegistry.values())
34603
+ );
34604
+ useEffect25(() => subscribeToRegistry(setEntries), []);
34605
+ return entries;
34606
+ };
34607
+
34506
34608
  // src/components/ContextMenu.tsx
34507
- import { useState as useState33 } from "react";
34609
+ import { useState as useState34 } from "react";
34508
34610
 
34509
34611
  // node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs
34510
34612
  import * as React43 from "react";
@@ -38132,9 +38234,9 @@ function assignRef(ref, value) {
38132
38234
  }
38133
38235
 
38134
38236
  // node_modules/use-callback-ref/dist/es2015/useRef.js
38135
- import { useState as useState28 } from "react";
38237
+ import { useState as useState29 } from "react";
38136
38238
  function useCallbackRef2(initialValue, callback) {
38137
- var ref = useState28(function() {
38239
+ var ref = useState29(function() {
38138
38240
  return {
38139
38241
  // value
38140
38242
  value: initialValue,
@@ -39858,7 +39960,7 @@ var SubTrigger2 = DropdownMenuSubTrigger;
39858
39960
  var SubContent2 = DropdownMenuSubContent;
39859
39961
 
39860
39962
  // src/components/AppearanceMenu.tsx
39861
- import { useState as useState32 } from "react";
39963
+ import { useState as useState33 } from "react";
39862
39964
 
39863
39965
  // src/components/Icons.tsx
39864
39966
  import { jsx as jsx32 } from "react/jsx-runtime";
@@ -39964,8 +40066,8 @@ var iconContainerStyles = {
39964
40066
  };
39965
40067
  var AppearanceMenu = () => {
39966
40068
  const { visibility, toggleLayer } = useLayerVisibility();
39967
- const [appearanceSubOpen, setAppearanceSubOpen] = useState32(false);
39968
- const [hoveredItem, setHoveredItem] = useState32(null);
40069
+ const [appearanceSubOpen, setAppearanceSubOpen] = useState33(false);
40070
+ const [hoveredItem, setHoveredItem] = useState33(null);
39969
40071
  return /* @__PURE__ */ jsxs7(Fragment9, { children: [
39970
40072
  /* @__PURE__ */ jsx33(Separator2, { style: separatorStyles }),
39971
40073
  /* @__PURE__ */ jsxs7(Sub2, { onOpenChange: setAppearanceSubOpen, children: [
@@ -40216,11 +40318,12 @@ var ContextMenu = ({
40216
40318
  onEngineSwitch,
40217
40319
  onCameraPresetSelect,
40218
40320
  onAutoRotateToggle,
40219
- onDownloadGltf
40321
+ onDownloadGltf,
40322
+ onOpenKeyboardShortcuts
40220
40323
  }) => {
40221
40324
  const { cameraType, setCameraType } = useCameraController();
40222
- const [cameraSubOpen, setCameraSubOpen] = useState33(false);
40223
- const [hoveredItem, setHoveredItem] = useState33(null);
40325
+ const [cameraSubOpen, setCameraSubOpen] = useState34(false);
40326
+ const [hoveredItem, setHoveredItem] = useState34(null);
40224
40327
  return /* @__PURE__ */ jsx34(
40225
40328
  "div",
40226
40329
  {
@@ -40413,6 +40516,35 @@ var ContextMenu = ({
40413
40516
  }
40414
40517
  ),
40415
40518
  /* @__PURE__ */ jsx34(Separator2, { style: separatorStyles2 }),
40519
+ /* @__PURE__ */ jsxs8(
40520
+ Item22,
40521
+ {
40522
+ style: {
40523
+ ...itemStyles2,
40524
+ ...itemPaddingStyles2,
40525
+ backgroundColor: hoveredItem === "shortcuts" ? "#404040" : "transparent"
40526
+ },
40527
+ onSelect: onOpenKeyboardShortcuts,
40528
+ onMouseEnter: () => setHoveredItem("shortcuts"),
40529
+ onMouseLeave: () => setHoveredItem(null),
40530
+ onTouchStart: () => setHoveredItem("shortcuts"),
40531
+ children: [
40532
+ /* @__PURE__ */ jsx34("span", { style: { flex: 1, display: "flex", alignItems: "center" }, children: "Keyboard Shortcuts" }),
40533
+ /* @__PURE__ */ jsx34(
40534
+ "div",
40535
+ {
40536
+ style: {
40537
+ ...badgeStyles,
40538
+ display: "flex",
40539
+ alignItems: "center"
40540
+ },
40541
+ children: "Shift+?"
40542
+ }
40543
+ )
40544
+ ]
40545
+ }
40546
+ ),
40547
+ /* @__PURE__ */ jsx34(Separator2, { style: separatorStyles2 }),
40416
40548
  /* @__PURE__ */ jsx34(
40417
40549
  "div",
40418
40550
  {
@@ -40450,23 +40582,216 @@ var ContextMenu = ({
40450
40582
  );
40451
40583
  };
40452
40584
 
40585
+ // src/components/KeyboardShortcutsDialog.tsx
40586
+ import { useEffect as useEffect41, useMemo as useMemo27, useRef as useRef25, useState as useState35 } from "react";
40587
+ import { Fragment as Fragment10, jsx as jsx35, jsxs as jsxs9 } from "react/jsx-runtime";
40588
+ var KeyboardShortcutsDialog = ({
40589
+ open,
40590
+ onClose
40591
+ }) => {
40592
+ const [query, setQuery] = useState35("");
40593
+ const inputRef = useRef25(null);
40594
+ const hotkeys = useHotkeyRegistry();
40595
+ useEffect41(() => {
40596
+ if (!open) return void 0;
40597
+ const handleKeyDown = (event) => {
40598
+ if (event.key === "Escape") {
40599
+ event.preventDefault();
40600
+ onClose();
40601
+ }
40602
+ };
40603
+ window.addEventListener("keydown", handleKeyDown);
40604
+ return () => window.removeEventListener("keydown", handleKeyDown);
40605
+ }, [open, onClose]);
40606
+ useEffect41(() => {
40607
+ if (open) {
40608
+ setTimeout(() => {
40609
+ inputRef.current?.focus();
40610
+ }, 0);
40611
+ }
40612
+ }, [open]);
40613
+ const filteredHotkeys = useMemo27(() => {
40614
+ const normalizedQuery = query.trim().toLowerCase();
40615
+ if (!normalizedQuery) {
40616
+ return hotkeys;
40617
+ }
40618
+ return hotkeys.filter((hotkey) => {
40619
+ const haystack = `${hotkey.key} ${hotkey.description}`;
40620
+ return haystack.toLowerCase().includes(normalizedQuery);
40621
+ });
40622
+ }, [hotkeys, query]);
40623
+ if (!open) {
40624
+ return null;
40625
+ }
40626
+ return /* @__PURE__ */ jsx35(
40627
+ "div",
40628
+ {
40629
+ role: "dialog",
40630
+ "aria-modal": "true",
40631
+ style: {
40632
+ position: "fixed",
40633
+ inset: 0,
40634
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
40635
+ display: "flex",
40636
+ alignItems: "center",
40637
+ justifyContent: "center",
40638
+ zIndex: 9999
40639
+ },
40640
+ onClick: onClose,
40641
+ children: /* @__PURE__ */ jsxs9(
40642
+ "div",
40643
+ {
40644
+ style: {
40645
+ backgroundColor: "#1f1f23",
40646
+ color: "#f8f8ff",
40647
+ borderRadius: 12,
40648
+ width: "min(640px, 90vw)",
40649
+ maxHeight: "80vh",
40650
+ boxShadow: "0 20px 60px rgba(0, 0, 0, 0.45), 0 8px 20px rgba(0, 0, 0, 0.35)",
40651
+ display: "flex",
40652
+ flexDirection: "column",
40653
+ overflow: "hidden"
40654
+ },
40655
+ onClick: (event) => event.stopPropagation(),
40656
+ children: [
40657
+ /* @__PURE__ */ jsxs9(
40658
+ "header",
40659
+ {
40660
+ style: {
40661
+ padding: "20px 24px 12px",
40662
+ borderBottom: "1px solid rgba(255, 255, 255, 0.08)"
40663
+ },
40664
+ children: [
40665
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
40666
+ /* @__PURE__ */ jsx35(
40667
+ "h2",
40668
+ {
40669
+ style: {
40670
+ margin: 0,
40671
+ fontSize: "1.1rem",
40672
+ fontWeight: 600,
40673
+ letterSpacing: "0.02em"
40674
+ },
40675
+ children: "Keyboard Shortcuts"
40676
+ }
40677
+ ),
40678
+ /* @__PURE__ */ jsx35(
40679
+ "button",
40680
+ {
40681
+ type: "button",
40682
+ onClick: onClose,
40683
+ style: {
40684
+ background: "transparent",
40685
+ border: "none",
40686
+ color: "rgba(255, 255, 255, 0.8)",
40687
+ fontSize: "1rem",
40688
+ cursor: "pointer"
40689
+ },
40690
+ children: "\u2715"
40691
+ }
40692
+ )
40693
+ ] }),
40694
+ /* @__PURE__ */ jsx35(
40695
+ "input",
40696
+ {
40697
+ ref: inputRef,
40698
+ type: "text",
40699
+ placeholder: "Search shortcuts...",
40700
+ value: query,
40701
+ onChange: (event) => setQuery(event.target.value),
40702
+ style: {
40703
+ marginTop: 12,
40704
+ width: "calc(100% - 24px)",
40705
+ padding: "10px 12px",
40706
+ borderRadius: 8,
40707
+ border: "1px solid rgba(255, 255, 255, 0.1)",
40708
+ backgroundColor: "rgba(255, 255, 255, 0.05)",
40709
+ color: "white",
40710
+ fontSize: "0.95rem"
40711
+ }
40712
+ }
40713
+ )
40714
+ ]
40715
+ }
40716
+ ),
40717
+ /* @__PURE__ */ jsx35("div", { style: { overflowY: "auto" }, children: /* @__PURE__ */ jsxs9(
40718
+ "table",
40719
+ {
40720
+ style: {
40721
+ width: "100%",
40722
+ borderCollapse: "collapse",
40723
+ fontSize: "0.95rem"
40724
+ },
40725
+ children: [
40726
+ /* @__PURE__ */ jsx35("thead", { children: /* @__PURE__ */ jsxs9("tr", { style: { textAlign: "left", color: "#a1a1b5" }, children: [
40727
+ /* @__PURE__ */ jsx35("th", { style: { padding: "12px 24px", width: "25%" }, children: "Key" }),
40728
+ /* @__PURE__ */ jsx35("th", { style: { padding: "12px 24px" }, children: "Description" })
40729
+ ] }) }),
40730
+ /* @__PURE__ */ jsx35("tbody", { children: filteredHotkeys.length === 0 ? /* @__PURE__ */ jsx35("tr", { children: /* @__PURE__ */ jsx35(
40731
+ "td",
40732
+ {
40733
+ colSpan: 2,
40734
+ style: { padding: "24px", textAlign: "center" },
40735
+ children: "No shortcuts found"
40736
+ }
40737
+ ) }) : filteredHotkeys.map((hotkey) => /* @__PURE__ */ jsxs9(
40738
+ "tr",
40739
+ {
40740
+ style: { borderTop: "1px solid rgba(255, 255, 255, 0.05)" },
40741
+ children: [
40742
+ /* @__PURE__ */ jsx35("td", { style: { padding: "12px 24px" }, children: /* @__PURE__ */ jsx35(
40743
+ "span",
40744
+ {
40745
+ style: {
40746
+ display: "inline-flex",
40747
+ alignItems: "center",
40748
+ justifyContent: "center",
40749
+ border: "1px solid rgba(255, 255, 255, 0.3)",
40750
+ borderRadius: 6,
40751
+ minWidth: 36,
40752
+ padding: "4px 8px",
40753
+ fontFamily: "monospace",
40754
+ fontSize: "0.95rem"
40755
+ },
40756
+ children: hotkey.modifiers?.length ? /* @__PURE__ */ jsxs9(Fragment10, { children: [
40757
+ hotkey.modifiers.map((mod) => `${mod}+`).join(""),
40758
+ hotkey.key.toUpperCase()
40759
+ ] }) : hotkey.key.toUpperCase()
40760
+ }
40761
+ ) }),
40762
+ /* @__PURE__ */ jsx35("td", { style: { padding: "12px 24px" }, children: hotkey.description })
40763
+ ]
40764
+ },
40765
+ hotkey.id
40766
+ )) })
40767
+ ]
40768
+ }
40769
+ ) })
40770
+ ]
40771
+ }
40772
+ )
40773
+ }
40774
+ );
40775
+ };
40776
+
40453
40777
  // src/CadViewer.tsx
40454
- import { jsx as jsx35, jsxs as jsxs9 } from "react/jsx-runtime";
40778
+ import { jsx as jsx36, jsxs as jsxs10 } from "react/jsx-runtime";
40455
40779
  var CadViewerInner = (props) => {
40456
- const [engine, setEngine] = useState34("manifold");
40457
- const containerRef = useRef24(null);
40458
- const [autoRotate, setAutoRotate] = useState34(() => {
40780
+ const [engine, setEngine] = useState36("manifold");
40781
+ const containerRef = useRef26(null);
40782
+ const [isKeyboardShortcutsDialogOpen, setIsKeyboardShortcutsDialogOpen] = useState36(false);
40783
+ const [autoRotate, setAutoRotate] = useState36(() => {
40459
40784
  const stored = window.localStorage.getItem("cadViewerAutoRotate");
40460
40785
  return stored === "false" ? false : true;
40461
40786
  });
40462
- const [autoRotateUserToggled, setAutoRotateUserToggled] = useState34(() => {
40787
+ const [autoRotateUserToggled, setAutoRotateUserToggled] = useState36(() => {
40463
40788
  const stored = window.localStorage.getItem("cadViewerAutoRotateUserToggled");
40464
40789
  return stored === "true";
40465
40790
  });
40466
- const [cameraPreset, setCameraPreset] = useState34("Custom");
40791
+ const [cameraPreset, setCameraPreset] = useState36("Custom");
40467
40792
  const { cameraType, setCameraType } = useCameraController();
40468
40793
  const { visibility, toggleLayer } = useLayerVisibility();
40469
- const cameraControllerRef = useRef24(null);
40794
+ const cameraControllerRef = useRef26(null);
40470
40795
  const externalCameraControllerReady = props.onCameraControllerReady;
40471
40796
  const {
40472
40797
  menuVisible,
@@ -40475,10 +40800,10 @@ var CadViewerInner = (props) => {
40475
40800
  contextMenuEventHandlers,
40476
40801
  setMenuVisible
40477
40802
  } = useContextMenu({ containerRef });
40478
- const autoRotateUserToggledRef = useRef24(autoRotateUserToggled);
40803
+ const autoRotateUserToggledRef = useRef26(autoRotateUserToggled);
40479
40804
  autoRotateUserToggledRef.current = autoRotateUserToggled;
40480
- const isAnimatingRef = useRef24(false);
40481
- const lastPresetSelectTime = useRef24(0);
40805
+ const isAnimatingRef = useRef26(false);
40806
+ const lastPresetSelectTime = useRef26(0);
40482
40807
  const PRESET_COOLDOWN = 1e3;
40483
40808
  const handleUserInteraction = useCallback22(() => {
40484
40809
  if (isAnimatingRef.current || Date.now() - lastPresetSelectTime.current < PRESET_COOLDOWN) {
@@ -40518,35 +40843,46 @@ var CadViewerInner = (props) => {
40518
40843
  isAnimatingRef,
40519
40844
  lastPresetSelectTime
40520
40845
  });
40521
- useEffect40(() => {
40846
+ useRegisteredHotkey(
40847
+ "open_keyboard_shortcuts_dialog",
40848
+ () => {
40849
+ setIsKeyboardShortcutsDialogOpen(true);
40850
+ },
40851
+ {
40852
+ key: "?",
40853
+ description: "Open keyboard shortcuts",
40854
+ modifiers: ["Shift"]
40855
+ }
40856
+ );
40857
+ useEffect42(() => {
40522
40858
  const stored = window.localStorage.getItem("cadViewerEngine");
40523
40859
  if (stored === "jscad" || stored === "manifold") {
40524
40860
  setEngine(stored);
40525
40861
  }
40526
40862
  }, []);
40527
- useEffect40(() => {
40863
+ useEffect42(() => {
40528
40864
  window.localStorage.setItem("cadViewerEngine", engine);
40529
40865
  }, [engine]);
40530
- useEffect40(() => {
40866
+ useEffect42(() => {
40531
40867
  window.localStorage.setItem("cadViewerAutoRotate", String(autoRotate));
40532
40868
  }, [autoRotate]);
40533
- useEffect40(() => {
40869
+ useEffect42(() => {
40534
40870
  window.localStorage.setItem(
40535
40871
  "cadViewerAutoRotateUserToggled",
40536
40872
  String(autoRotateUserToggled)
40537
40873
  );
40538
40874
  }, [autoRotateUserToggled]);
40539
- useEffect40(() => {
40875
+ useEffect42(() => {
40540
40876
  const stored = window.localStorage.getItem("cadViewerCameraType");
40541
40877
  if (stored === "orthographic" || stored === "perspective") {
40542
40878
  setCameraType(stored);
40543
40879
  }
40544
40880
  }, [setCameraType]);
40545
- useEffect40(() => {
40881
+ useEffect42(() => {
40546
40882
  window.localStorage.setItem("cadViewerCameraType", cameraType);
40547
40883
  }, [cameraType]);
40548
40884
  const viewerKey = props.circuitJson ? JSON.stringify(props.circuitJson) : void 0;
40549
- return /* @__PURE__ */ jsxs9(
40885
+ return /* @__PURE__ */ jsxs10(
40550
40886
  "div",
40551
40887
  {
40552
40888
  ref: containerRef,
@@ -40562,7 +40898,7 @@ var CadViewerInner = (props) => {
40562
40898
  },
40563
40899
  ...contextMenuEventHandlers,
40564
40900
  children: [
40565
- engine === "jscad" ? /* @__PURE__ */ jsx35(
40901
+ engine === "jscad" ? /* @__PURE__ */ jsx36(
40566
40902
  CadViewerJscad,
40567
40903
  {
40568
40904
  ...props,
@@ -40571,7 +40907,7 @@ var CadViewerInner = (props) => {
40571
40907
  onUserInteraction: handleUserInteraction,
40572
40908
  onCameraControllerReady: handleCameraControllerReady
40573
40909
  }
40574
- ) : /* @__PURE__ */ jsx35(
40910
+ ) : /* @__PURE__ */ jsx36(
40575
40911
  CadViewerManifold_default,
40576
40912
  {
40577
40913
  ...props,
@@ -40581,7 +40917,7 @@ var CadViewerInner = (props) => {
40581
40917
  onCameraControllerReady: handleCameraControllerReady
40582
40918
  }
40583
40919
  ),
40584
- /* @__PURE__ */ jsxs9(
40920
+ /* @__PURE__ */ jsxs10(
40585
40921
  "div",
40586
40922
  {
40587
40923
  style: {
@@ -40598,11 +40934,11 @@ var CadViewerInner = (props) => {
40598
40934
  },
40599
40935
  children: [
40600
40936
  "Engine: ",
40601
- /* @__PURE__ */ jsx35("b", { children: engine === "jscad" ? "JSCAD" : "Manifold" })
40937
+ /* @__PURE__ */ jsx36("b", { children: engine === "jscad" ? "JSCAD" : "Manifold" })
40602
40938
  ]
40603
40939
  }
40604
40940
  ),
40605
- menuVisible && /* @__PURE__ */ jsx35(
40941
+ menuVisible && /* @__PURE__ */ jsx36(
40606
40942
  ContextMenu,
40607
40943
  {
40608
40944
  menuRef,
@@ -40622,8 +40958,19 @@ var CadViewerInner = (props) => {
40622
40958
  onDownloadGltf: () => {
40623
40959
  downloadGltf();
40624
40960
  closeMenu();
40961
+ },
40962
+ onOpenKeyboardShortcuts: () => {
40963
+ setIsKeyboardShortcutsDialogOpen(true);
40964
+ closeMenu();
40625
40965
  }
40626
40966
  }
40967
+ ),
40968
+ /* @__PURE__ */ jsx36(
40969
+ KeyboardShortcutsDialog,
40970
+ {
40971
+ open: isKeyboardShortcutsDialogOpen,
40972
+ onClose: () => setIsKeyboardShortcutsDialogOpen(false)
40973
+ }
40627
40974
  )
40628
40975
  ]
40629
40976
  },
@@ -40631,17 +40978,17 @@ var CadViewerInner = (props) => {
40631
40978
  );
40632
40979
  };
40633
40980
  var CadViewer = (props) => {
40634
- const defaultTarget = useMemo26(() => new THREE29.Vector3(0, 0, 0), []);
40635
- const initialCameraPosition = useMemo26(
40981
+ const defaultTarget = useMemo28(() => new THREE29.Vector3(0, 0, 0), []);
40982
+ const initialCameraPosition = useMemo28(
40636
40983
  () => [5, -5, 5],
40637
40984
  []
40638
40985
  );
40639
- return /* @__PURE__ */ jsx35(
40986
+ return /* @__PURE__ */ jsx36(
40640
40987
  CameraControllerProvider,
40641
40988
  {
40642
40989
  defaultTarget,
40643
40990
  initialCameraPosition,
40644
- children: /* @__PURE__ */ jsx35(LayerVisibilityProvider, { children: /* @__PURE__ */ jsx35(CadViewerInner, { ...props }) })
40991
+ children: /* @__PURE__ */ jsx36(LayerVisibilityProvider, { children: /* @__PURE__ */ jsx36(CadViewerInner, { ...props }) })
40645
40992
  }
40646
40993
  );
40647
40994
  };
@@ -40925,10 +41272,10 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
40925
41272
 
40926
41273
  // src/hooks/exporter/gltf.ts
40927
41274
  import { GLTFExporter as GLTFExporter2 } from "three-stdlib";
40928
- import { useEffect as useEffect41, useState as useState35, useMemo as useMemo27, useCallback as useCallback23 } from "react";
41275
+ import { useEffect as useEffect43, useState as useState37, useMemo as useMemo29, useCallback as useCallback23 } from "react";
40929
41276
  function useSaveGltfAs(options = {}) {
40930
41277
  const parse2 = useParser(options);
40931
- const link = useMemo27(() => document.createElement("a"), []);
41278
+ const link = useMemo29(() => document.createElement("a"), []);
40932
41279
  const saveAs = async (filename) => {
40933
41280
  const name = filename ?? options.filename ?? "";
40934
41281
  if (options.binary == null) options.binary = name.endsWith(".glb");
@@ -40938,7 +41285,7 @@ function useSaveGltfAs(options = {}) {
40938
41285
  link.dispatchEvent(new MouseEvent("click"));
40939
41286
  URL.revokeObjectURL(url);
40940
41287
  };
40941
- useEffect41(
41288
+ useEffect43(
40942
41289
  () => () => {
40943
41290
  link.remove();
40944
41291
  instance = null;
@@ -40953,17 +41300,17 @@ function useSaveGltfAs(options = {}) {
40953
41300
  }
40954
41301
  function useExportGltfUrl(options = {}) {
40955
41302
  const parse2 = useParser(options);
40956
- const [url, setUrl] = useState35();
40957
- const [error, setError] = useState35();
41303
+ const [url, setUrl] = useState37();
41304
+ const [error, setError] = useState37();
40958
41305
  const ref = useCallback23(
40959
41306
  (instance) => parse2(instance).then(setUrl).catch(setError),
40960
41307
  []
40961
41308
  );
40962
- useEffect41(() => () => URL.revokeObjectURL(url), [url]);
41309
+ useEffect43(() => () => URL.revokeObjectURL(url), [url]);
40963
41310
  return [ref, url, error];
40964
41311
  }
40965
41312
  function useParser(options = {}) {
40966
- const exporter = useMemo27(() => new GLTFExporter2(), []);
41313
+ const exporter = useMemo29(() => new GLTFExporter2(), []);
40967
41314
  return (instance) => {
40968
41315
  const { promise, resolve, reject } = Promise.withResolvers();
40969
41316
  exporter.parse(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/3d-viewer",
3
- "version": "0.0.439",
3
+ "version": "0.0.440",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",