@ericchen1990/pano-view 0.2.0 → 1.0.0

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.cjs CHANGED
@@ -22,7 +22,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
24
  AutoRotate: () => AutoRotate,
25
+ DEFAULT_HOTSPOT_TOOLTIP_OFFSET: () => DEFAULT_HOTSPOT_TOOLTIP_OFFSET,
26
+ DEFAULT_PANO_CURSORS: () => DEFAULT_PANO_CURSORS,
25
27
  GraphicHotspot: () => GraphicHotspot,
28
+ IframeHotspot: () => IframeHotspot,
26
29
  ImageHotspot: () => ImageHotspot,
27
30
  KeyboardControls: () => KeyboardControls,
28
31
  MAX_HOTSPOT_PITCH: () => MAX_HOTSPOT_PITCH,
@@ -30,12 +33,13 @@ __export(index_exports, {
30
33
  MouseControls: () => MouseControls,
31
34
  PanoContextMenu: () => PanoContextMenu,
32
35
  PanoEvents: () => PanoEvents,
33
- PanoView: () => PanoView,
34
- PanoramaScenes: () => PanoramaScenes,
36
+ PanoViewer: () => PanoViewer,
35
37
  PolygonHotspot: () => PolygonHotspot,
36
38
  PolylineHotspot: () => PolylineHotspot,
39
+ Scenes: () => Scenes,
37
40
  SequenceHotspot: () => SequenceHotspot,
38
41
  Sphere: () => Sphere,
42
+ TextHotspot: () => TextHotspot,
39
43
  Tile: () => Tile,
40
44
  TouchControls: () => TouchControls,
41
45
  VideoHotspot: () => VideoHotspot,
@@ -56,9 +60,9 @@ __export(index_exports, {
56
60
  });
57
61
  module.exports = __toCommonJS(index_exports);
58
62
 
59
- // src/pano-view.tsx
60
- var import_fiber8 = require("@react-three/fiber");
61
- var import_react11 = require("react");
63
+ // src/pano-viewer.tsx
64
+ var import_fiber9 = require("@react-three/fiber");
65
+ var import_react13 = require("react");
62
66
 
63
67
  // src/auto-rotate.tsx
64
68
  var import_fiber2 = require("@react-three/fiber");
@@ -446,7 +450,7 @@ function AutoRotate({
446
450
  const rotatingRef = (0, import_react3.useRef)(false);
447
451
  const yawAccumulatedRef = (0, import_react3.useRef)(0);
448
452
  if (!controlsRef) {
449
- throw new Error("<AutoRotate> must be rendered inside <PanoView>.");
453
+ throw new Error("<AutoRotate> must be rendered inside <PanoViewer>.");
450
454
  }
451
455
  (0, import_react3.useEffect)(() => {
452
456
  elapsedRef.current = 0;
@@ -502,10 +506,46 @@ function AutoRotate({
502
506
  return null;
503
507
  }
504
508
 
505
- // src/hotspot/accessibility.tsx
509
+ // src/control-claims.ts
506
510
  var import_react4 = require("react");
511
+ var ControlClaimsContext = (0, import_react4.createContext)(null);
512
+ var DefaultControlChannelContext = (0, import_react4.createContext)(false);
513
+ var CHANNEL_COMPONENT = {
514
+ mouse: "MouseControls",
515
+ touch: "TouchControls",
516
+ keyboard: "KeyboardControls"
517
+ };
518
+ function createControlClaims() {
519
+ return { mouse: 0, touch: 0, keyboard: 0 };
520
+ }
521
+ function resetControlClaims(claims) {
522
+ claims.mouse = 0;
523
+ claims.touch = 0;
524
+ claims.keyboard = 0;
525
+ }
526
+ function isProduction() {
527
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
528
+ return nodeEnv === "production";
529
+ }
530
+ function useClaimControlChannel(channel) {
531
+ const claims = (0, import_react4.useContext)(ControlClaimsContext);
532
+ const isDefault = (0, import_react4.useContext)(DefaultControlChannelContext);
533
+ if (!claims || isDefault) {
534
+ return;
535
+ }
536
+ claims[channel] += 1;
537
+ if (!isProduction() && claims[channel] > 1) {
538
+ const name = CHANNEL_COMPONENT[channel];
539
+ console.warn(
540
+ `PanoViewer: multiple <${name}> instances are mounted. Render at most one per channel.`
541
+ );
542
+ }
543
+ }
544
+
545
+ // src/hotspot/accessibility.tsx
546
+ var import_react5 = require("react");
507
547
  var import_jsx_runtime = require("react/jsx-runtime");
508
- var HotspotAccessibilityContext = (0, import_react4.createContext)(null);
548
+ var HotspotAccessibilityContext = (0, import_react5.createContext)(null);
509
549
  var VISUALLY_HIDDEN_STYLE = {
510
550
  border: 0,
511
551
  clip: "rect(0 0 0 0)",
@@ -531,12 +571,12 @@ function useHotspotAccessibility({
531
571
  ariaLabel,
532
572
  onActivate
533
573
  }) {
534
- const registry = (0, import_react4.useContext)(HotspotAccessibilityContext);
535
- const [focused, setFocused] = (0, import_react4.useState)(false);
536
- const activateRef = (0, import_react4.useRef)(onActivate);
574
+ const registry = (0, import_react5.useContext)(HotspotAccessibilityContext);
575
+ const [focused, setFocused] = (0, import_react5.useState)(false);
576
+ const activateRef = (0, import_react5.useRef)(onActivate);
537
577
  const enabled = Boolean(onActivate);
538
578
  activateRef.current = onActivate;
539
- (0, import_react4.useEffect)(() => {
579
+ (0, import_react5.useEffect)(() => {
540
580
  if (!enabled) {
541
581
  setFocused(false);
542
582
  return;
@@ -558,8 +598,8 @@ function useHotspotAccessibility({
558
598
  return focused;
559
599
  }
560
600
  function useHotspotAccessibilityLayer() {
561
- const [entries, setEntries] = (0, import_react4.useState)(() => /* @__PURE__ */ new Map());
562
- const register = (0, import_react4.useCallback)(
601
+ const [entries, setEntries] = (0, import_react5.useState)(() => /* @__PURE__ */ new Map());
602
+ const register = (0, import_react5.useCallback)(
563
603
  (entry) => {
564
604
  const token = Symbol(entry.id);
565
605
  const nextEntry = { ...entry, token };
@@ -581,11 +621,11 @@ function useHotspotAccessibilityLayer() {
581
621
  },
582
622
  []
583
623
  );
584
- const registry = (0, import_react4.useMemo)(
624
+ const registry = (0, import_react5.useMemo)(
585
625
  () => ({ register }),
586
626
  [register]
587
627
  );
588
- const controls = (0, import_react4.useMemo)(
628
+ const controls = (0, import_react5.useMemo)(
589
629
  () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-label": "Panorama hotspots", style: { pointerEvents: "none" }, children: Array.from(entries.values()).map((entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
590
630
  "button",
591
631
  {
@@ -612,7 +652,7 @@ function useHotspotAccessibilityLayer() {
612
652
 
613
653
  // src/keyboard-controls.tsx
614
654
  var import_fiber3 = require("@react-three/fiber");
615
- var import_react5 = require("react");
655
+ var import_react6 = require("react");
616
656
  var DEFAULT_ROTATE_SPEED = 60;
617
657
  var DEFAULT_ZOOM_SPEED = 30;
618
658
  var DEFAULT_SHIFT_MULTIPLIER = 3;
@@ -681,23 +721,24 @@ function KeyboardControls({
681
721
  onPreviousScene,
682
722
  onNextScene
683
723
  }) {
684
- const controlsRef = (0, import_react5.useContext)(PanoramaViewContext);
724
+ useClaimControlChannel("keyboard");
725
+ const controlsRef = (0, import_react6.useContext)(PanoramaViewContext);
685
726
  const { gl } = (0, import_fiber3.useThree)();
686
- const pressedRef = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
687
- const shiftHeldRef = (0, import_react5.useRef)(false);
688
- const invertRef = (0, import_react5.useRef)(invert);
689
- const fovSpeedRef = (0, import_react5.useRef)(fovSpeed);
690
- const onPreviousSceneRef = (0, import_react5.useRef)(onPreviousScene);
691
- const onNextSceneRef = (0, import_react5.useRef)(onNextScene);
692
- const keyMap = (0, import_react5.useMemo)(() => resolveKeyMap(keys), [keys]);
727
+ const pressedRef = (0, import_react6.useRef)(/* @__PURE__ */ new Set());
728
+ const shiftHeldRef = (0, import_react6.useRef)(false);
729
+ const invertRef = (0, import_react6.useRef)(invert);
730
+ const fovSpeedRef = (0, import_react6.useRef)(fovSpeed);
731
+ const onPreviousSceneRef = (0, import_react6.useRef)(onPreviousScene);
732
+ const onNextSceneRef = (0, import_react6.useRef)(onNextScene);
733
+ const keyMap = (0, import_react6.useMemo)(() => resolveKeyMap(keys), [keys]);
693
734
  if (!controlsRef) {
694
- throw new Error("<KeyboardControls> must be rendered inside <PanoView>.");
735
+ throw new Error("<KeyboardControls> must be rendered inside <PanoViewer>.");
695
736
  }
696
737
  invertRef.current = invert;
697
738
  fovSpeedRef.current = fovSpeed;
698
739
  onPreviousSceneRef.current = onPreviousScene;
699
740
  onNextSceneRef.current = onNextScene;
700
- (0, import_react5.useEffect)(() => {
741
+ (0, import_react6.useEffect)(() => {
701
742
  const element = gl.domElement;
702
743
  if (!enabled) {
703
744
  pressedRef.current.clear();
@@ -838,7 +879,7 @@ function KeyboardControls({
838
879
 
839
880
  // src/mouse-controls.tsx
840
881
  var import_fiber4 = require("@react-three/fiber");
841
- var import_react6 = require("react");
882
+ var import_react7 = require("react");
842
883
  var DEFAULT_ROTATE_SPEED2 = 0.35;
843
884
  var DEFAULT_ZOOM_SPEED2 = 0.08;
844
885
  var BUTTON_FLAG = {
@@ -862,12 +903,13 @@ function MouseControls({
862
903
  buttons,
863
904
  fovSpeed
864
905
  }) {
865
- const controlsRef = (0, import_react6.useContext)(PanoramaViewContext);
906
+ useClaimControlChannel("mouse");
907
+ const controlsRef = (0, import_react7.useContext)(PanoramaViewContext);
866
908
  const { gl } = (0, import_fiber4.useThree)();
867
- const pointersRef = (0, import_react6.useRef)(
909
+ const pointersRef = (0, import_react7.useRef)(
868
910
  /* @__PURE__ */ new Map()
869
911
  );
870
- const optionsRef = (0, import_react6.useRef)({
912
+ const optionsRef = (0, import_react7.useRef)({
871
913
  rotateSpeed,
872
914
  zoomSpeed,
873
915
  wheel,
@@ -875,7 +917,7 @@ function MouseControls({
875
917
  fovSpeed
876
918
  });
877
919
  if (!controlsRef) {
878
- throw new Error("<MouseControls> must be rendered inside <PanoView>.");
920
+ throw new Error("<MouseControls> must be rendered inside <PanoViewer>.");
879
921
  }
880
922
  optionsRef.current = {
881
923
  rotateSpeed,
@@ -884,7 +926,7 @@ function MouseControls({
884
926
  invert,
885
927
  fovSpeed
886
928
  };
887
- (0, import_react6.useEffect)(() => {
929
+ (0, import_react7.useEffect)(() => {
888
930
  const element = gl.domElement;
889
931
  const allowedButtons = resolveButtons(buttons);
890
932
  const clearPointers = () => {
@@ -1008,12 +1050,12 @@ function MouseControls({
1008
1050
  }
1009
1051
 
1010
1052
  // src/pano-context-menu.tsx
1011
- var import_react7 = require("react");
1053
+ var import_react8 = require("react");
1012
1054
  var import_jsx_runtime2 = require("react/jsx-runtime");
1013
1055
  var DEFAULT_ICON_SIZE = 16;
1014
1056
  var MENU_EDGE_PADDING = 8;
1015
1057
  var MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY = 0.4;
1016
- var PanoContextMenuOverlayContext = (0, import_react7.createContext)(null);
1058
+ var PanoContextMenuOverlayContext = (0, import_react8.createContext)(null);
1017
1059
  var DEFAULT_APPEARANCE = {
1018
1060
  background: "#161616",
1019
1061
  color: "#f4f4f4",
@@ -1053,11 +1095,11 @@ function DefaultContextMenuPanel({
1053
1095
  selectContext,
1054
1096
  onRequestClose
1055
1097
  }) {
1056
- const menuRef = (0, import_react7.useRef)(null);
1057
- const [activeIndex, setActiveIndex] = (0, import_react7.useState)(0);
1098
+ const menuRef = (0, import_react8.useRef)(null);
1099
+ const [activeIndex, setActiveIndex] = (0, import_react8.useState)(0);
1058
1100
  const resolved = { ...DEFAULT_APPEARANCE, ...appearance };
1059
1101
  const iconSize = Number.isFinite(resolved.iconSize) ? Math.max(0, resolved.iconSize) : DEFAULT_ICON_SIZE;
1060
- const visibleItems = (0, import_react7.useMemo)(
1102
+ const visibleItems = (0, import_react8.useMemo)(
1061
1103
  () => items.filter((item) => {
1062
1104
  if (!isActionItem(item)) {
1063
1105
  return true;
@@ -1066,7 +1108,7 @@ function DefaultContextMenuPanel({
1066
1108
  }),
1067
1109
  [items]
1068
1110
  );
1069
- const focusableIndices = (0, import_react7.useMemo)(() => {
1111
+ const focusableIndices = (0, import_react8.useMemo)(() => {
1070
1112
  const indices = [];
1071
1113
  visibleItems.forEach((item, index) => {
1072
1114
  if (isActionItem(item) && !item.disabled) {
@@ -1075,7 +1117,7 @@ function DefaultContextMenuPanel({
1075
1117
  });
1076
1118
  return indices;
1077
1119
  }, [visibleItems]);
1078
- (0, import_react7.useEffect)(() => {
1120
+ (0, import_react8.useEffect)(() => {
1079
1121
  const first = focusableIndices[0];
1080
1122
  setActiveIndex(first ?? 0);
1081
1123
  const frame = requestAnimationFrame(() => {
@@ -1086,7 +1128,7 @@ function DefaultContextMenuPanel({
1086
1128
  });
1087
1129
  return () => cancelAnimationFrame(frame);
1088
1130
  }, [focusableIndices]);
1089
- const activateItem = (0, import_react7.useCallback)(
1131
+ const activateItem = (0, import_react8.useCallback)(
1090
1132
  (item) => {
1091
1133
  if (item.disabled) {
1092
1134
  return;
@@ -1301,9 +1343,9 @@ function PositionedMenuShell({
1301
1343
  overlayElement,
1302
1344
  children
1303
1345
  }) {
1304
- const shellRef = (0, import_react7.useRef)(null);
1305
- const [offset, setOffset] = (0, import_react7.useState)({ left: 0, top: 0 });
1306
- (0, import_react7.useLayoutEffect)(() => {
1346
+ const shellRef = (0, import_react8.useRef)(null);
1347
+ const [offset, setOffset] = (0, import_react8.useState)({ left: 0, top: 0 });
1348
+ (0, import_react8.useLayoutEffect)(() => {
1307
1349
  const shell = shellRef.current;
1308
1350
  if (!overlayElement || !shell) {
1309
1351
  return;
@@ -1340,20 +1382,20 @@ function PositionedMenuShell({
1340
1382
  );
1341
1383
  }
1342
1384
  function usePanoContextMenuOverlay() {
1343
- const [session, setSession] = (0, import_react7.useState)(null);
1344
- const overlayRef = (0, import_react7.useRef)(null);
1345
- const [overlayElement, setOverlayElement] = (0, import_react7.useState)(
1385
+ const [session, setSession] = (0, import_react8.useState)(null);
1386
+ const overlayRef = (0, import_react8.useRef)(null);
1387
+ const [overlayElement, setOverlayElement] = (0, import_react8.useState)(
1346
1388
  null
1347
1389
  );
1348
- const api = (0, import_react7.useMemo)(
1390
+ const api = (0, import_react8.useMemo)(
1349
1391
  () => ({ setSession, overlayRef }),
1350
1392
  []
1351
1393
  );
1352
- const setOverlayNode = (0, import_react7.useCallback)((node) => {
1394
+ const setOverlayNode = (0, import_react8.useCallback)((node) => {
1353
1395
  overlayRef.current = node;
1354
1396
  setOverlayElement(node);
1355
1397
  }, []);
1356
- (0, import_react7.useEffect)(() => {
1398
+ (0, import_react8.useEffect)(() => {
1357
1399
  if (!session) {
1358
1400
  return;
1359
1401
  }
@@ -1421,13 +1463,13 @@ function PanoContextMenu({
1421
1463
  separatorClassName,
1422
1464
  children
1423
1465
  }) {
1424
- const eventBus = (0, import_react7.useContext)(PanoEventBusContext);
1425
- const controlsRef = (0, import_react7.useContext)(PanoramaViewContext);
1426
- const overlayApi = (0, import_react7.useContext)(PanoContextMenuOverlayContext);
1427
- const actions = (0, import_react7.useContext)(PanoContextMenuActionsContext);
1428
- const instanceId = (0, import_react7.useId)();
1429
- const sessionKeyRef = (0, import_react7.useRef)(0);
1430
- const propsRef = (0, import_react7.useRef)({
1466
+ const eventBus = (0, import_react8.useContext)(PanoEventBusContext);
1467
+ const controlsRef = (0, import_react8.useContext)(PanoramaViewContext);
1468
+ const overlayApi = (0, import_react8.useContext)(PanoContextMenuOverlayContext);
1469
+ const actions = (0, import_react8.useContext)(PanoContextMenuActionsContext);
1470
+ const instanceId = (0, import_react8.useId)();
1471
+ const sessionKeyRef = (0, import_react8.useRef)(0);
1472
+ const propsRef = (0, import_react8.useRef)({
1431
1473
  items,
1432
1474
  prepend,
1433
1475
  append,
@@ -1450,17 +1492,17 @@ function PanoContextMenu({
1450
1492
  children
1451
1493
  };
1452
1494
  if (!eventBus || !controlsRef) {
1453
- throw new Error("<PanoContextMenu> must be rendered inside <PanoView>.");
1495
+ throw new Error("<PanoContextMenu> must be rendered inside <PanoViewer>.");
1454
1496
  }
1455
1497
  if (!overlayApi) {
1456
1498
  throw new Error(
1457
- "<PanoContextMenu> requires the PanoView context-menu overlay host."
1499
+ "<PanoContextMenu> requires the PanoViewer context-menu overlay host."
1458
1500
  );
1459
1501
  }
1460
- const close = (0, import_react7.useCallback)(() => {
1502
+ const close = (0, import_react8.useCallback)(() => {
1461
1503
  overlayApi.setSession(null);
1462
1504
  }, [overlayApi]);
1463
- const openMenu = (0, import_react7.useCallback)(
1505
+ const openMenu = (0, import_react8.useCallback)(
1464
1506
  (event) => {
1465
1507
  event.nativeEvent.preventDefault();
1466
1508
  const view = controlsRef.current?.getView() ?? {
@@ -1509,7 +1551,7 @@ function PanoContextMenu({
1509
1551
  },
1510
1552
  [actions, close, controlsRef, instanceId, overlayApi]
1511
1553
  );
1512
- (0, import_react7.useEffect)(() => {
1554
+ (0, import_react8.useEffect)(() => {
1513
1555
  const unsubscribeContextMenu = eventBus.subscribe(
1514
1556
  "contextmenu",
1515
1557
  openMenu
@@ -1688,11 +1730,156 @@ function composePanoContextMenuItems(props, presets) {
1688
1730
  ...resolvePanoContextMenuEntries(props.append ?? [], presets)
1689
1731
  ];
1690
1732
  }
1691
- var PanoContextMenuActionsContext = (0, import_react7.createContext)(null);
1733
+ var PanoContextMenuActionsContext = (0, import_react8.createContext)(null);
1692
1734
 
1693
- // src/pano-events.tsx
1735
+ // src/pano-cursor.tsx
1694
1736
  var import_fiber5 = require("@react-three/fiber");
1695
- var import_react8 = require("react");
1737
+ var import_react9 = require("react");
1738
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1739
+ var DEFAULT_PANO_CURSORS = {
1740
+ default: "grab",
1741
+ dragging: "grabbing",
1742
+ hotspot: "pointer",
1743
+ hotspotDragging: "move"
1744
+ };
1745
+ var PANO_DRAG_CLAIM_ID = "pano-cursor:dragging";
1746
+ var LAYER_PRIORITY = {
1747
+ dragging: 1,
1748
+ hotspot: 2,
1749
+ hotspotDragging: 3
1750
+ };
1751
+ var PanoCursorContext = (0, import_react9.createContext)(null);
1752
+ function usePanoCursor() {
1753
+ return (0, import_react9.useContext)(PanoCursorContext);
1754
+ }
1755
+ function hotspotCursorClaimId(id) {
1756
+ return `pano-cursor:hotspot:${id}`;
1757
+ }
1758
+ function resolveClaimCursor(claim, cursors) {
1759
+ switch (claim.layer) {
1760
+ case "dragging":
1761
+ return cursors.dragging;
1762
+ case "hotspot":
1763
+ return claim.cursor ?? cursors.hotspot;
1764
+ case "hotspotDragging":
1765
+ return claim.cursor ?? cursors.hotspotDragging;
1766
+ default: {
1767
+ const exhaustive = claim.layer;
1768
+ return exhaustive;
1769
+ }
1770
+ }
1771
+ }
1772
+ function resolveHighestCursor(claims, cursors) {
1773
+ let best = null;
1774
+ let bestPriority = 0;
1775
+ for (const claim of claims.values()) {
1776
+ const priority = LAYER_PRIORITY[claim.layer];
1777
+ if (priority >= bestPriority) {
1778
+ best = claim;
1779
+ bestPriority = priority;
1780
+ }
1781
+ }
1782
+ return best ? resolveClaimCursor(best, cursors) : cursors.default;
1783
+ }
1784
+ function resolvePanoCursors(value) {
1785
+ if (value === false) {
1786
+ return null;
1787
+ }
1788
+ if (value === true || value === void 0) {
1789
+ return DEFAULT_PANO_CURSORS;
1790
+ }
1791
+ return {
1792
+ default: value.default ?? DEFAULT_PANO_CURSORS.default,
1793
+ dragging: value.dragging ?? DEFAULT_PANO_CURSORS.dragging,
1794
+ hotspot: value.hotspot ?? DEFAULT_PANO_CURSORS.hotspot,
1795
+ hotspotDragging: value.hotspotDragging ?? DEFAULT_PANO_CURSORS.hotspotDragging
1796
+ };
1797
+ }
1798
+ function PanoCursorController({
1799
+ cursors,
1800
+ children
1801
+ }) {
1802
+ const { gl } = (0, import_fiber5.useThree)();
1803
+ const eventBus = (0, import_react9.useContext)(PanoEventBusContext);
1804
+ const claimsRef = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
1805
+ const cursorsRef = (0, import_react9.useRef)(cursors);
1806
+ const appliedCursorRef = (0, import_react9.useRef)(null);
1807
+ cursorsRef.current = cursors;
1808
+ const applyCursor = (0, import_react9.useCallback)(() => {
1809
+ const next = resolveHighestCursor(claimsRef.current, cursorsRef.current);
1810
+ const element = gl.domElement;
1811
+ if (appliedCursorRef.current === next && element.style.cursor === next) {
1812
+ return;
1813
+ }
1814
+ element.style.cursor = next;
1815
+ appliedCursorRef.current = next;
1816
+ }, [gl]);
1817
+ const claim = (0, import_react9.useCallback)(
1818
+ (id, layer, cursor) => {
1819
+ claimsRef.current.set(id, { layer, cursor });
1820
+ applyCursor();
1821
+ },
1822
+ [applyCursor]
1823
+ );
1824
+ const release = (0, import_react9.useCallback)(
1825
+ (id) => {
1826
+ if (claimsRef.current.delete(id)) {
1827
+ applyCursor();
1828
+ }
1829
+ },
1830
+ [applyCursor]
1831
+ );
1832
+ const api = (0, import_react9.useMemo)(
1833
+ () => ({
1834
+ cursors,
1835
+ claim,
1836
+ release
1837
+ }),
1838
+ [claim, cursors, release]
1839
+ );
1840
+ (0, import_react9.useEffect)(() => {
1841
+ const element = gl.domElement;
1842
+ const previous = element.style.cursor;
1843
+ return () => {
1844
+ claimsRef.current.clear();
1845
+ appliedCursorRef.current = null;
1846
+ element.style.cursor = previous;
1847
+ };
1848
+ }, [gl]);
1849
+ (0, import_react9.useEffect)(() => {
1850
+ applyCursor();
1851
+ }, [applyCursor, cursors]);
1852
+ (0, import_react9.useEffect)(() => {
1853
+ if (!eventBus) {
1854
+ return;
1855
+ }
1856
+ const unsubscribeStart = eventBus.subscribe(
1857
+ "viewinteractionstart",
1858
+ (event) => {
1859
+ if (event.source !== "mouse") {
1860
+ return;
1861
+ }
1862
+ claim(PANO_DRAG_CLAIM_ID, "dragging");
1863
+ }
1864
+ );
1865
+ const unsubscribeEnd = eventBus.subscribe("viewinteractionend", (event) => {
1866
+ if (event.source !== "mouse") {
1867
+ return;
1868
+ }
1869
+ release(PANO_DRAG_CLAIM_ID);
1870
+ });
1871
+ return () => {
1872
+ unsubscribeStart();
1873
+ unsubscribeEnd();
1874
+ release(PANO_DRAG_CLAIM_ID);
1875
+ };
1876
+ }, [claim, eventBus, release]);
1877
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PanoCursorContext.Provider, { value: api, children });
1878
+ }
1879
+
1880
+ // src/pano-events.tsx
1881
+ var import_fiber6 = require("@react-three/fiber");
1882
+ var import_react10 = require("react");
1696
1883
  var DEFAULT_IDLE_TIME_MS = 2e3;
1697
1884
  function resolveIdleTime(value) {
1698
1885
  return Number.isFinite(value) && value >= 0 ? value : DEFAULT_IDLE_TIME_MS;
@@ -1719,13 +1906,13 @@ function usePanoEvents({
1719
1906
  onAutoRotateStop,
1720
1907
  onAutoRotateOneRound
1721
1908
  }) {
1722
- const eventBus = (0, import_react8.useContext)(PanoEventBusContext);
1723
- const controlsRef = (0, import_react8.useContext)(PanoramaViewContext);
1724
- const { gl } = (0, import_fiber5.useThree)();
1909
+ const eventBus = (0, import_react10.useContext)(PanoEventBusContext);
1910
+ const controlsRef = (0, import_react10.useContext)(PanoramaViewContext);
1911
+ const { gl } = (0, import_fiber6.useThree)();
1725
1912
  if (!eventBus || !controlsRef) {
1726
- throw new Error("usePanoEvents must be used inside <PanoView>.");
1913
+ throw new Error("usePanoEvents must be used inside <PanoViewer>.");
1727
1914
  }
1728
- const callbacksRef = (0, import_react8.useRef)({
1915
+ const callbacksRef = (0, import_react10.useRef)({
1729
1916
  onViewChange,
1730
1917
  onViewSettled,
1731
1918
  onViewInteractionStart,
@@ -1767,15 +1954,15 @@ function usePanoEvents({
1767
1954
  onAutoRotateStop,
1768
1955
  onAutoRotateOneRound
1769
1956
  };
1770
- const idleElapsedRef = (0, import_react8.useRef)(0);
1771
- const isIdleRef = (0, import_react8.useRef)(false);
1772
- const idleTimeRef = (0, import_react8.useRef)(resolveIdleTime(idleTime));
1957
+ const idleElapsedRef = (0, import_react10.useRef)(0);
1958
+ const isIdleRef = (0, import_react10.useRef)(false);
1959
+ const idleTimeRef = (0, import_react10.useRef)(resolveIdleTime(idleTime));
1773
1960
  idleTimeRef.current = resolveIdleTime(idleTime);
1774
- (0, import_react8.useEffect)(() => {
1961
+ (0, import_react10.useEffect)(() => {
1775
1962
  idleElapsedRef.current = 0;
1776
1963
  isIdleRef.current = false;
1777
1964
  }, [idleTime]);
1778
- (0, import_react8.useEffect)(() => {
1965
+ (0, import_react10.useEffect)(() => {
1779
1966
  const unsubscribers = [
1780
1967
  eventBus.subscribe("viewchange", (view) => {
1781
1968
  callbacksRef.current.onViewChange?.(view);
@@ -1823,7 +2010,7 @@ function usePanoEvents({
1823
2010
  }
1824
2011
  };
1825
2012
  }, [eventBus]);
1826
- (0, import_react8.useEffect)(() => {
2013
+ (0, import_react10.useEffect)(() => {
1827
2014
  const element = gl.domElement;
1828
2015
  const handleWheel = (nativeEvent) => {
1829
2016
  callbacksRef.current.onWheel?.({
@@ -1836,7 +2023,7 @@ function usePanoEvents({
1836
2023
  element.removeEventListener("wheel", handleWheel);
1837
2024
  };
1838
2025
  }, [gl]);
1839
- (0, import_react8.useEffect)(() => {
2026
+ (0, import_react10.useEffect)(() => {
1840
2027
  if (typeof document === "undefined") {
1841
2028
  return;
1842
2029
  }
@@ -1865,7 +2052,7 @@ function usePanoEvents({
1865
2052
  document.removeEventListener("fullscreenchange", handleFullscreenChange);
1866
2053
  };
1867
2054
  }, [gl]);
1868
- (0, import_react8.useEffect)(() => {
2055
+ (0, import_react10.useEffect)(() => {
1869
2056
  const element = gl.domElement;
1870
2057
  if (typeof ResizeObserver === "undefined") {
1871
2058
  return;
@@ -1883,7 +2070,7 @@ function usePanoEvents({
1883
2070
  observer.disconnect();
1884
2071
  };
1885
2072
  }, [gl]);
1886
- (0, import_fiber5.useFrame)((_, deltaSeconds) => {
2073
+ (0, import_fiber6.useFrame)((_, deltaSeconds) => {
1887
2074
  const interacting = controlsRef.current?.isUserInteracting() ?? false;
1888
2075
  if (interacting) {
1889
2076
  idleElapsedRef.current = 0;
@@ -1906,8 +2093,8 @@ function PanoEvents(props) {
1906
2093
  }
1907
2094
 
1908
2095
  // src/panorama-event-surface.tsx
1909
- var import_fiber6 = require("@react-three/fiber");
1910
- var import_react9 = require("react");
2096
+ var import_fiber7 = require("@react-three/fiber");
2097
+ var import_react11 = require("react");
1911
2098
  var import_three3 = require("three");
1912
2099
 
1913
2100
  // src/hotspot/coordinates.ts
@@ -1973,7 +2160,7 @@ var DEFAULT_PANORAMA_CAMERA_NEAR = 0.1;
1973
2160
  var DEFAULT_PANORAMA_CAMERA_FAR = DEFAULT_PANORAMA_RADIUS * 2;
1974
2161
 
1975
2162
  // src/panorama-event-surface.tsx
1976
- var import_jsx_runtime3 = require("react/jsx-runtime");
2163
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1977
2164
  var EVENT_SURFACE_RADIUS = DEFAULT_PANORAMA_RADIUS - 1;
1978
2165
  function makePanoramaPointerEvent(event) {
1979
2166
  const nativeEvent = event.nativeEvent;
@@ -1988,9 +2175,9 @@ function makePanoramaPointerEvent(event) {
1988
2175
  };
1989
2176
  }
1990
2177
  function PanoramaEventSurface() {
1991
- const eventBus = (0, import_react9.useContext)(PanoEventBusContext);
1992
- const { camera, gl } = (0, import_fiber6.useThree)();
1993
- (0, import_react9.useEffect)(() => {
2178
+ const eventBus = (0, import_react11.useContext)(PanoEventBusContext);
2179
+ const { camera, gl } = (0, import_fiber7.useThree)();
2180
+ (0, import_react11.useEffect)(() => {
1994
2181
  if (!eventBus) {
1995
2182
  return;
1996
2183
  }
@@ -2031,7 +2218,7 @@ function PanoramaEventSurface() {
2031
2218
  event.stopPropagation();
2032
2219
  eventBus.emit(type, makePanoramaPointerEvent(event));
2033
2220
  };
2034
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2221
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2035
2222
  "mesh",
2036
2223
  {
2037
2224
  renderOrder: -200,
@@ -2041,8 +2228,8 @@ function PanoramaEventSurface() {
2041
2228
  onPointerUp: (event) => emit("pointerup", event),
2042
2229
  onPointerMove: (event) => emit("pointermove", event),
2043
2230
  children: [
2044
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("sphereGeometry", { args: [EVENT_SURFACE_RADIUS, 64, 32] }),
2045
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2231
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("sphereGeometry", { args: [EVENT_SURFACE_RADIUS, 64, 32] }),
2232
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2046
2233
  "meshBasicMaterial",
2047
2234
  {
2048
2235
  colorWrite: false,
@@ -2059,8 +2246,8 @@ function PanoramaEventSurface() {
2059
2246
  }
2060
2247
 
2061
2248
  // src/touch-controls.tsx
2062
- var import_fiber7 = require("@react-three/fiber");
2063
- var import_react10 = require("react");
2249
+ var import_fiber8 = require("@react-three/fiber");
2250
+ var import_react12 = require("react");
2064
2251
  var DEFAULT_ROTATE_SPEED3 = 0.35;
2065
2252
  function resolveSpeed3(value, fallback) {
2066
2253
  return Number.isFinite(value) && value >= 0 ? value : fallback;
@@ -2074,16 +2261,17 @@ function TouchControls({
2074
2261
  invert = false,
2075
2262
  pinchZoom = true
2076
2263
  }) {
2077
- const controlsRef = (0, import_react10.useContext)(PanoramaViewContext);
2078
- const { gl } = (0, import_fiber7.useThree)();
2079
- const pointersRef = (0, import_react10.useRef)(/* @__PURE__ */ new Map());
2080
- const pinchDistanceRef = (0, import_react10.useRef)(null);
2081
- const optionsRef = (0, import_react10.useRef)({ rotateSpeed, invert, pinchZoom });
2264
+ useClaimControlChannel("touch");
2265
+ const controlsRef = (0, import_react12.useContext)(PanoramaViewContext);
2266
+ const { gl } = (0, import_fiber8.useThree)();
2267
+ const pointersRef = (0, import_react12.useRef)(/* @__PURE__ */ new Map());
2268
+ const pinchDistanceRef = (0, import_react12.useRef)(null);
2269
+ const optionsRef = (0, import_react12.useRef)({ rotateSpeed, invert, pinchZoom });
2082
2270
  if (!controlsRef) {
2083
- throw new Error("<TouchControls> must be rendered inside <PanoView>.");
2271
+ throw new Error("<TouchControls> must be rendered inside <PanoViewer>.");
2084
2272
  }
2085
2273
  optionsRef.current = { rotateSpeed, invert, pinchZoom };
2086
- (0, import_react10.useEffect)(() => {
2274
+ (0, import_react12.useEffect)(() => {
2087
2275
  const element = gl.domElement;
2088
2276
  const clearPointers = () => {
2089
2277
  for (const pointerId of pointersRef.current.keys()) {
@@ -2204,8 +2392,8 @@ function TouchControls({
2204
2392
  return null;
2205
2393
  }
2206
2394
 
2207
- // src/pano-view.tsx
2208
- var import_jsx_runtime4 = require("react/jsx-runtime");
2395
+ // src/pano-viewer.tsx
2396
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2209
2397
  var DEFAULT_VIEW = {
2210
2398
  yaw: 0,
2211
2399
  pitch: 0,
@@ -2243,13 +2431,46 @@ function resolveContextMenuChannel(value) {
2243
2431
  }
2244
2432
  return { mount: true, props: { ...value } };
2245
2433
  }
2246
- var PanoView = (0, import_react11.forwardRef)(
2247
- function PanoView2({
2434
+ function PanoCursorTree({
2435
+ cursors,
2436
+ children
2437
+ }) {
2438
+ if (!cursors) {
2439
+ return children;
2440
+ }
2441
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PanoCursorController, { cursors, children });
2442
+ }
2443
+ function DefaultControlChannels({
2444
+ userControlsEnabled,
2445
+ mouseChannel,
2446
+ touchChannel,
2447
+ keyboardChannel,
2448
+ fovSpeed
2449
+ }) {
2450
+ const claims = (0, import_react13.useContext)(ControlClaimsContext);
2451
+ if (!claims) {
2452
+ return null;
2453
+ }
2454
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(DefaultControlChannelContext.Provider, { value: true, children: [
2455
+ userControlsEnabled && mouseChannel.mount && claims.mouse === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MouseControls, { ...mouseChannel.props, fovSpeed }) : null,
2456
+ userControlsEnabled && touchChannel.mount && claims.touch === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TouchControls, { ...touchChannel.props }) : null,
2457
+ userControlsEnabled && keyboardChannel.mount && claims.keyboard === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2458
+ KeyboardControls,
2459
+ {
2460
+ ...keyboardChannel.props,
2461
+ fovSpeed: keyboardChannel.props.fovSpeed ?? fovSpeed
2462
+ }
2463
+ ) : null
2464
+ ] });
2465
+ }
2466
+ var PanoViewer = (0, import_react13.forwardRef)(
2467
+ function PanoViewer2({
2248
2468
  children,
2249
2469
  initialView,
2250
2470
  minFov = 30,
2251
2471
  maxFov = 100,
2252
2472
  controls = true,
2473
+ cursors,
2253
2474
  contextMenu = true,
2254
2475
  onViewChange,
2255
2476
  onPanoramaClick,
@@ -2260,18 +2481,18 @@ var PanoView = (0, import_react11.forwardRef)(
2260
2481
  "aria-label": ariaLabel = "Panorama viewer",
2261
2482
  ...divProps
2262
2483
  }, ref) {
2263
- const rootRef = (0, import_react11.useRef)(null);
2264
- const controlsRef = (0, import_react11.useRef)(null);
2265
- const eventBus = (0, import_react11.useMemo)(() => createPanoEventBus(), []);
2484
+ const rootRef = (0, import_react13.useRef)(null);
2485
+ const controlsRef = (0, import_react13.useRef)(null);
2486
+ const eventBus = (0, import_react13.useMemo)(() => createPanoEventBus(), []);
2266
2487
  const { controls: hotspotAccessibilityControls, registry } = useHotspotAccessibilityLayer();
2267
2488
  const { api: contextMenuOverlayApi, overlay: contextMenuOverlay } = usePanoContextMenuOverlay();
2268
- const fallbackViewRef = (0, import_react11.useRef)(DEFAULT_VIEW);
2489
+ const fallbackViewRef = (0, import_react13.useRef)(DEFAULT_VIEW);
2269
2490
  const normalizedMinFov = Math.max(1, Math.min(minFov, maxFov - 1));
2270
2491
  const normalizedMaxFov = Math.min(
2271
2492
  179,
2272
2493
  Math.max(maxFov, normalizedMinFov + 1)
2273
2494
  );
2274
- const normalizedInitialView = (0, import_react11.useMemo)(
2495
+ const normalizedInitialView = (0, import_react13.useMemo)(
2275
2496
  () => ({
2276
2497
  yaw: initialView?.yaw ?? DEFAULT_VIEW.yaw,
2277
2498
  pitch: clampPanoPitch(initialView?.pitch ?? DEFAULT_VIEW.pitch),
@@ -2289,8 +2510,10 @@ var PanoView = (0, import_react11.forwardRef)(
2289
2510
  ]
2290
2511
  );
2291
2512
  fallbackViewRef.current = normalizedInitialView;
2513
+ const controlClaims = (0, import_react13.useMemo)(() => createControlClaims(), []);
2514
+ resetControlClaims(controlClaims);
2292
2515
  const controlsEnabled = controls !== false;
2293
- const controlOptions = (0, import_react11.useMemo)(
2516
+ const controlOptions = (0, import_react13.useMemo)(
2294
2517
  () => typeof controls === "object" ? controls : {},
2295
2518
  [controls]
2296
2519
  );
@@ -2298,7 +2521,7 @@ var PanoView = (0, import_react11.forwardRef)(
2298
2521
  const allModeInvert = controlOptions.invert === true;
2299
2522
  const topRotateSpeed = controlOptions.rotateSpeed ?? DEFAULT_POINTER_ROTATE_SPEED;
2300
2523
  const topZoomSpeed = controlOptions.zoomSpeed ?? DEFAULT_MOUSE_ZOOM_SPEED;
2301
- const mouseChannel = (0, import_react11.useMemo)(
2524
+ const mouseChannel = (0, import_react13.useMemo)(
2302
2525
  () => resolveChannel(controlOptions.mouse, {
2303
2526
  enabled: true,
2304
2527
  rotateSpeed: topRotateSpeed,
@@ -2314,7 +2537,7 @@ var PanoView = (0, import_react11.forwardRef)(
2314
2537
  topZoomSpeed
2315
2538
  ]
2316
2539
  );
2317
- const touchChannel = (0, import_react11.useMemo)(
2540
+ const touchChannel = (0, import_react13.useMemo)(
2318
2541
  () => resolveChannel(controlOptions.touch, {
2319
2542
  enabled: true,
2320
2543
  rotateSpeed: topRotateSpeed,
@@ -2323,13 +2546,13 @@ var PanoView = (0, import_react11.forwardRef)(
2323
2546
  }),
2324
2547
  [allModeInvert, controlOptions.touch, topRotateSpeed]
2325
2548
  );
2326
- const keyboardChannel = (0, import_react11.useMemo)(
2549
+ const keyboardChannel = (0, import_react13.useMemo)(
2327
2550
  () => resolveChannel(controlOptions.keyboard, {
2328
2551
  enabled: true
2329
2552
  }),
2330
2553
  [controlOptions.keyboard]
2331
2554
  );
2332
- const toggleFullscreen = (0, import_react11.useCallback)(async () => {
2555
+ const toggleFullscreen = (0, import_react13.useCallback)(async () => {
2333
2556
  if (typeof document === "undefined") {
2334
2557
  return;
2335
2558
  }
@@ -2339,8 +2562,8 @@ var PanoView = (0, import_react11.forwardRef)(
2339
2562
  }
2340
2563
  await rootRef.current?.requestFullscreen?.();
2341
2564
  }, []);
2342
- const [isViewerFullscreen, setIsViewerFullscreen] = (0, import_react11.useState)(false);
2343
- (0, import_react11.useEffect)(() => {
2565
+ const [isViewerFullscreen, setIsViewerFullscreen] = (0, import_react13.useState)(false);
2566
+ (0, import_react13.useEffect)(() => {
2344
2567
  if (typeof document === "undefined") {
2345
2568
  return;
2346
2569
  }
@@ -2361,7 +2584,7 @@ var PanoView = (0, import_react11.forwardRef)(
2361
2584
  document.removeEventListener("fullscreenchange", syncFullscreen);
2362
2585
  };
2363
2586
  }, []);
2364
- const contextMenuActions = (0, import_react11.useMemo)(
2587
+ const contextMenuActions = (0, import_react13.useMemo)(
2365
2588
  () => ({
2366
2589
  reset: () => {
2367
2590
  controlsRef.current?.reset();
@@ -2373,18 +2596,22 @@ var PanoView = (0, import_react11.forwardRef)(
2373
2596
  }),
2374
2597
  [isViewerFullscreen, toggleFullscreen]
2375
2598
  );
2376
- const contextMenuChannel = (0, import_react11.useMemo)(
2599
+ const contextMenuChannel = (0, import_react13.useMemo)(
2377
2600
  () => resolveContextMenuChannel(contextMenu),
2378
2601
  [contextMenu]
2379
2602
  );
2603
+ const resolvedCursors = (0, import_react13.useMemo)(
2604
+ () => resolvePanoCursors(cursors),
2605
+ [cursors]
2606
+ );
2380
2607
  const legacyAutoRotateOptions = controlOptions;
2381
- const [legacyAutoRotate, setLegacyAutoRotate] = (0, import_react11.useState)(
2608
+ const [legacyAutoRotate, setLegacyAutoRotate] = (0, import_react13.useState)(
2382
2609
  legacyAutoRotateOptions.autoRotate ?? false
2383
2610
  );
2384
- (0, import_react11.useEffect)(() => {
2611
+ (0, import_react13.useEffect)(() => {
2385
2612
  setLegacyAutoRotate(legacyAutoRotateOptions.autoRotate ?? false);
2386
2613
  }, [legacyAutoRotateOptions.autoRotate]);
2387
- (0, import_react11.useImperativeHandle)(
2614
+ (0, import_react13.useImperativeHandle)(
2388
2615
  ref,
2389
2616
  () => ({
2390
2617
  getView: () => controlsRef.current?.getView() ?? { ...fallbackViewRef.current },
@@ -2404,7 +2631,7 @@ var PanoView = (0, import_react11.forwardRef)(
2404
2631
  }),
2405
2632
  [toggleFullscreen]
2406
2633
  );
2407
- const rootStyle = (0, import_react11.useMemo)(
2634
+ const rootStyle = (0, import_react13.useMemo)(
2408
2635
  () => ({
2409
2636
  overflow: "hidden",
2410
2637
  position: "relative",
@@ -2412,7 +2639,7 @@ var PanoView = (0, import_react11.forwardRef)(
2412
2639
  }),
2413
2640
  [style]
2414
2641
  );
2415
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2642
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2416
2643
  "div",
2417
2644
  {
2418
2645
  ...divProps,
@@ -2420,8 +2647,8 @@ var PanoView = (0, import_react11.forwardRef)(
2420
2647
  "aria-label": ariaLabel,
2421
2648
  style: rootStyle,
2422
2649
  children: [
2423
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2424
- import_fiber8.Canvas,
2650
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2651
+ import_fiber9.Canvas,
2425
2652
  {
2426
2653
  "aria-label": `${ariaLabel} canvas`,
2427
2654
  camera: {
@@ -2443,16 +2670,16 @@ var PanoView = (0, import_react11.forwardRef)(
2443
2670
  touchAction: userControlsEnabled ? "none" : "auto"
2444
2671
  },
2445
2672
  tabIndex: userControlsEnabled ? 0 : void 0,
2446
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HotspotAccessibilityContext.Provider, { value: registry, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanoramaViewContext.Provider, { value: controlsRef, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2673
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HotspotAccessibilityContext.Provider, { value: registry, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PanoCursorTree, { cursors: resolvedCursors, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PanoramaViewContext.Provider, { value: controlsRef, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2447
2674
  PanoContextMenuOverlayContext.Provider,
2448
2675
  {
2449
2676
  value: contextMenuOverlayApi,
2450
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2677
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2451
2678
  PanoContextMenuActionsContext.Provider,
2452
2679
  {
2453
2680
  value: contextMenuActions,
2454
2681
  children: [
2455
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2682
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2456
2683
  PanoramaViewRuntime,
2457
2684
  {
2458
2685
  ref: controlsRef,
@@ -2463,7 +2690,7 @@ var PanoView = (0, import_react11.forwardRef)(
2463
2690
  options: controlOptions
2464
2691
  }
2465
2692
  ),
2466
- (onViewChange || onPanoramaClick || onPanoramaDoubleClick || onPanoramaPointerMove) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2693
+ (onViewChange || onPanoramaClick || onPanoramaDoubleClick || onPanoramaPointerMove) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2467
2694
  PanoEvents,
2468
2695
  {
2469
2696
  onViewChange,
@@ -2472,36 +2699,33 @@ var PanoView = (0, import_react11.forwardRef)(
2472
2699
  onPointerMove: onPanoramaPointerMove
2473
2700
  }
2474
2701
  ),
2475
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2702
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2476
2703
  AutoRotate,
2477
2704
  {
2478
2705
  enabled: legacyAutoRotate,
2479
2706
  speed: legacyAutoRotateOptions.autoRotateSpeed
2480
2707
  }
2481
2708
  ),
2482
- userControlsEnabled && mouseChannel.mount ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2483
- MouseControls,
2484
- {
2485
- ...mouseChannel.props,
2486
- fovSpeed: controlOptions.fovSpeed
2487
- }
2488
- ) : null,
2489
- userControlsEnabled && touchChannel.mount ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TouchControls, { ...touchChannel.props }) : null,
2490
- userControlsEnabled && keyboardChannel.mount ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2491
- KeyboardControls,
2492
- {
2493
- ...keyboardChannel.props,
2494
- fovSpeed: keyboardChannel.props.fovSpeed ?? controlOptions.fovSpeed
2495
- }
2496
- ) : null,
2497
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanoramaEventSurface, {}),
2498
- contextMenuChannel.mount ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanoContextMenu, { ...contextMenuChannel.props }) : null,
2499
- children
2709
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PanoramaEventSurface, {}),
2710
+ contextMenuChannel.mount ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PanoContextMenu, { ...contextMenuChannel.props }) : null,
2711
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ControlClaimsContext.Provider, { value: controlClaims, children: [
2712
+ children,
2713
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2714
+ DefaultControlChannels,
2715
+ {
2716
+ fovSpeed: controlOptions.fovSpeed,
2717
+ keyboardChannel,
2718
+ mouseChannel,
2719
+ touchChannel,
2720
+ userControlsEnabled
2721
+ }
2722
+ )
2723
+ ] })
2500
2724
  ]
2501
2725
  }
2502
2726
  )
2503
2727
  }
2504
- ) }) }) })
2728
+ ) }) }) }) })
2505
2729
  }
2506
2730
  ),
2507
2731
  contextMenuOverlay,
@@ -2513,10 +2737,10 @@ var PanoView = (0, import_react11.forwardRef)(
2513
2737
  );
2514
2738
 
2515
2739
  // src/sphere.tsx
2516
- var import_fiber9 = require("@react-three/fiber");
2517
- var import_react12 = require("react");
2740
+ var import_fiber10 = require("@react-three/fiber");
2741
+ var import_react14 = require("react");
2518
2742
  var import_three4 = require("three");
2519
- var import_jsx_runtime5 = require("react/jsx-runtime");
2743
+ var import_jsx_runtime6 = require("react/jsx-runtime");
2520
2744
  function Sphere({
2521
2745
  src,
2522
2746
  yawOffset = 0,
@@ -2524,13 +2748,13 @@ function Sphere({
2524
2748
  onError,
2525
2749
  visible = true
2526
2750
  }) {
2527
- const gl = (0, import_fiber9.useThree)((state) => state.gl);
2528
- const [texture, setTexture] = (0, import_react12.useState)(null);
2529
- const onLoadRef = (0, import_react12.useRef)(onLoad);
2530
- const onErrorRef = (0, import_react12.useRef)(onError);
2751
+ const gl = (0, import_fiber10.useThree)((state) => state.gl);
2752
+ const [texture, setTexture] = (0, import_react14.useState)(null);
2753
+ const onLoadRef = (0, import_react14.useRef)(onLoad);
2754
+ const onErrorRef = (0, import_react14.useRef)(onError);
2531
2755
  onLoadRef.current = onLoad;
2532
2756
  onErrorRef.current = onError;
2533
- (0, import_react12.useEffect)(() => {
2757
+ (0, import_react14.useEffect)(() => {
2534
2758
  let active = true;
2535
2759
  const loader = new import_three4.TextureLoader();
2536
2760
  setTexture(null);
@@ -2564,7 +2788,7 @@ function Sphere({
2564
2788
  active = false;
2565
2789
  };
2566
2790
  }, [gl, src]);
2567
- (0, import_react12.useEffect)(
2791
+ (0, import_react14.useEffect)(
2568
2792
  () => () => {
2569
2793
  texture?.dispose();
2570
2794
  },
@@ -2573,14 +2797,14 @@ function Sphere({
2573
2797
  if (!texture) {
2574
2798
  return null;
2575
2799
  }
2576
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2800
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2577
2801
  "mesh",
2578
2802
  {
2579
2803
  rotation: [0, import_three4.MathUtils.degToRad(90 + yawOffset), 0],
2580
2804
  visible,
2581
2805
  children: [
2582
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
2583
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2806
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
2807
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2584
2808
  "meshBasicMaterial",
2585
2809
  {
2586
2810
  map: texture,
@@ -2593,14 +2817,14 @@ function Sphere({
2593
2817
  );
2594
2818
  }
2595
2819
 
2596
- // src/panorama-scenes.tsx
2597
- var import_fiber12 = require("@react-three/fiber");
2598
- var import_react15 = require("react");
2820
+ // src/scenes.tsx
2821
+ var import_fiber13 = require("@react-three/fiber");
2822
+ var import_react17 = require("react");
2599
2823
  var import_three8 = require("three");
2600
2824
 
2601
2825
  // src/tile/texture-manager-context.tsx
2602
- var import_fiber10 = require("@react-three/fiber");
2603
- var import_react13 = require("react");
2826
+ var import_fiber11 = require("@react-three/fiber");
2827
+ var import_react15 = require("react");
2604
2828
 
2605
2829
  // src/tile/texture-manager.ts
2606
2830
  var import_three5 = require("three");
@@ -2824,8 +3048,8 @@ var TileTextureManager = class {
2824
3048
  };
2825
3049
 
2826
3050
  // src/tile/texture-manager-context.tsx
2827
- var import_jsx_runtime6 = require("react/jsx-runtime");
2828
- var TileTextureManagerContext = (0, import_react13.createContext)(
3051
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3052
+ var TileTextureManagerContext = (0, import_react15.createContext)(
2829
3053
  null
2830
3054
  );
2831
3055
  function TileTextureManagerProvider({
@@ -2833,9 +3057,9 @@ function TileTextureManagerProvider({
2833
3057
  maxTextureMemoryMb = 128,
2834
3058
  maxConcurrentLoads = 4
2835
3059
  }) {
2836
- const gl = (0, import_fiber10.useThree)((state) => state.gl);
3060
+ const gl = (0, import_fiber11.useThree)((state) => state.gl);
2837
3061
  const anisotropy = gl.capabilities.getMaxAnisotropy();
2838
- const manager = (0, import_react13.useMemo)(
3062
+ const manager = (0, import_react15.useMemo)(
2839
3063
  () => new TileTextureManager({
2840
3064
  anisotropy,
2841
3065
  maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
@@ -2844,19 +3068,19 @@ function TileTextureManagerProvider({
2844
3068
  }),
2845
3069
  [anisotropy, maxConcurrentLoads, maxTextureMemoryMb]
2846
3070
  );
2847
- (0, import_react13.useEffect)(() => {
3071
+ (0, import_react15.useEffect)(() => {
2848
3072
  manager.resume();
2849
3073
  return () => manager.dispose();
2850
3074
  }, [manager]);
2851
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TileTextureManagerContext.Provider, { value: manager, children });
3075
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TileTextureManagerContext.Provider, { value: manager, children });
2852
3076
  }
2853
3077
  function useSharedTileTextureManager() {
2854
- return (0, import_react13.useContext)(TileTextureManagerContext);
3078
+ return (0, import_react15.useContext)(TileTextureManagerContext);
2855
3079
  }
2856
3080
 
2857
3081
  // src/tile/tile.tsx
2858
- var import_fiber11 = require("@react-three/fiber");
2859
- var import_react14 = require("react");
3082
+ var import_fiber12 = require("@react-three/fiber");
3083
+ var import_react16 = require("react");
2860
3084
  var import_three7 = require("three");
2861
3085
 
2862
3086
  // src/tile/face-layout.ts
@@ -3037,7 +3261,7 @@ function resolveTemplateUrl(template, address) {
3037
3261
  }
3038
3262
 
3039
3263
  // src/tile/tile.tsx
3040
- var import_jsx_runtime7 = require("react/jsx-runtime");
3264
+ var import_jsx_runtime8 = require("react/jsx-runtime");
3041
3265
  var MAX_LOD_UPDATE_INTERVAL_MS = 100;
3042
3266
  var FAST_ROTATION_THRESHOLD_RADIANS = Math.PI / 180;
3043
3267
  var FAST_FOV_THRESHOLD_DEGREES = 0.5;
@@ -3046,10 +3270,10 @@ var VISIBILITY_FOV_PADDING_DEGREES = 8;
3046
3270
  var PREVIEW_RADIUS = CUBE_RADIUS + 0.05;
3047
3271
  var SHARED_TILE_GEOMETRY = new import_three7.PlaneGeometry(1, 1);
3048
3272
  function usePreviewTexture(url, anisotropy, onError) {
3049
- const [texture, setTexture] = (0, import_react14.useState)(null);
3050
- const onErrorRef = (0, import_react14.useRef)(onError);
3273
+ const [texture, setTexture] = (0, import_react16.useState)(null);
3274
+ const onErrorRef = (0, import_react16.useRef)(onError);
3051
3275
  onErrorRef.current = onError;
3052
- (0, import_react14.useEffect)(() => {
3276
+ (0, import_react16.useEffect)(() => {
3053
3277
  if (!url) {
3054
3278
  setTexture(null);
3055
3279
  return;
@@ -3086,7 +3310,7 @@ function usePreviewTexture(url, anisotropy, onError) {
3086
3310
  active = false;
3087
3311
  };
3088
3312
  }, [anisotropy, url]);
3089
- (0, import_react14.useEffect)(
3313
+ (0, import_react16.useEffect)(
3090
3314
  () => () => {
3091
3315
  texture?.dispose();
3092
3316
  },
@@ -3099,7 +3323,7 @@ function PreviewFace({
3099
3323
  texture,
3100
3324
  faceOrder
3101
3325
  }) {
3102
- const geometry = (0, import_react14.useMemo)(() => {
3326
+ const geometry = (0, import_react16.useMemo)(() => {
3103
3327
  const nextGeometry = new import_three7.PlaneGeometry(
3104
3328
  PREVIEW_RADIUS * 2,
3105
3329
  PREVIEW_RADIUS * 2
@@ -3117,21 +3341,21 @@ function PreviewFace({
3117
3341
  uv.needsUpdate = true;
3118
3342
  return nextGeometry;
3119
3343
  }, [face, faceOrder]);
3120
- (0, import_react14.useEffect)(
3344
+ (0, import_react16.useEffect)(
3121
3345
  () => () => {
3122
3346
  geometry.dispose();
3123
3347
  },
3124
3348
  [geometry]
3125
3349
  );
3126
3350
  const transform = getFaceTransform(face, PREVIEW_RADIUS);
3127
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3351
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3128
3352
  "mesh",
3129
3353
  {
3130
3354
  geometry,
3131
3355
  position: transform.position,
3132
3356
  renderOrder: -100,
3133
3357
  rotation: transform.rotation,
3134
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3358
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3135
3359
  "meshBasicMaterial",
3136
3360
  {
3137
3361
  depthTest: false,
@@ -3147,7 +3371,7 @@ function PreviewCube({
3147
3371
  texture,
3148
3372
  faceOrder
3149
3373
  }) {
3150
- return CUBE_FACES.map((face) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3374
+ return CUBE_FACES.map((face) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3151
3375
  PreviewFace,
3152
3376
  {
3153
3377
  face,
@@ -3158,14 +3382,14 @@ function PreviewCube({
3158
3382
  ));
3159
3383
  }
3160
3384
  function useManagedTexture(manager, key, url, address) {
3161
- const [managedState, setManagedState] = (0, import_react14.useState)(() => ({
3385
+ const [managedState, setManagedState] = (0, import_react16.useState)(() => ({
3162
3386
  manager,
3163
3387
  key,
3164
3388
  url,
3165
3389
  snapshot: manager.getSnapshot(key)
3166
3390
  }));
3167
3391
  const snapshot = managedState.manager === manager && managedState.key === key && managedState.url === url ? managedState.snapshot : manager.getSnapshot(key);
3168
- (0, import_react14.useEffect)(
3392
+ (0, import_react16.useEffect)(
3169
3393
  () => manager.acquire(key, url, address, (nextSnapshot) => {
3170
3394
  setManagedState({
3171
3395
  manager,
@@ -3178,7 +3402,7 @@ function useManagedTexture(manager, key, url, address) {
3178
3402
  );
3179
3403
  return snapshot;
3180
3404
  }
3181
- var TileMesh = (0, import_react14.memo)(function TileMesh2({
3405
+ var TileMesh = (0, import_react16.memo)(function TileMesh2({
3182
3406
  address,
3183
3407
  manager,
3184
3408
  renderOrder,
@@ -3192,14 +3416,14 @@ var TileMesh = (0, import_react14.memo)(function TileMesh2({
3192
3416
  if (!snapshot.texture) {
3193
3417
  return null;
3194
3418
  }
3195
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("group", { position: transform.position, rotation: transform.rotation, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3419
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("group", { position: transform.position, rotation: transform.rotation, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3196
3420
  "mesh",
3197
3421
  {
3198
3422
  geometry: SHARED_TILE_GEOMETRY,
3199
3423
  position: [layout.x, layout.y, 0],
3200
3424
  renderOrder,
3201
3425
  scale: [layout.width, layout.height, 1],
3202
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3426
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3203
3427
  "meshBasicMaterial",
3204
3428
  {
3205
3429
  depthTest: false,
@@ -3211,12 +3435,12 @@ var TileMesh = (0, import_react14.memo)(function TileMesh2({
3211
3435
  }
3212
3436
  ) });
3213
3437
  });
3214
- var TileLayerMeshes = (0, import_react14.memo)(function TileLayerMeshes2({
3438
+ var TileLayerMeshes = (0, import_react16.memo)(function TileLayerMeshes2({
3215
3439
  layer,
3216
3440
  manager,
3217
3441
  resolveUrl
3218
3442
  }) {
3219
- return layer.addresses.map((address) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3443
+ return layer.addresses.map((address) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3220
3444
  TileMesh,
3221
3445
  {
3222
3446
  address,
@@ -3384,11 +3608,11 @@ function Tile({
3384
3608
  onTileError,
3385
3609
  onLevelChange
3386
3610
  }) {
3387
- const { camera, gl, size } = (0, import_fiber11.useThree)();
3388
- const multiresConfig = (0, import_react14.useMemo)(() => parseMultires(multires), [multires]);
3611
+ const { camera, gl, size } = (0, import_fiber12.useThree)();
3612
+ const multiresConfig = (0, import_react16.useMemo)(() => parseMultires(multires), [multires]);
3389
3613
  const { levels, tileSize } = multiresConfig;
3390
3614
  const multiresToken = `${tileSize}:${levels.join(",")}`;
3391
- const candidatesByLevel = (0, import_react14.useMemo)(
3615
+ const candidatesByLevel = (0, import_react16.useMemo)(
3392
3616
  () => buildTileCandidates(levels, tileSize),
3393
3617
  [levels, tileSize]
3394
3618
  );
@@ -3400,13 +3624,13 @@ function Tile({
3400
3624
  onPreviewError
3401
3625
  );
3402
3626
  const sharedManager = useSharedTileTextureManager();
3403
- const progressRef = (0, import_react14.useRef)(onLoadProgress);
3404
- const errorRef = (0, import_react14.useRef)(onTileError);
3405
- const levelChangeRef = (0, import_react14.useRef)(onLevelChange);
3627
+ const progressRef = (0, import_react16.useRef)(onLoadProgress);
3628
+ const errorRef = (0, import_react16.useRef)(onTileError);
3629
+ const levelChangeRef = (0, import_react16.useRef)(onLevelChange);
3406
3630
  progressRef.current = onLoadProgress;
3407
3631
  errorRef.current = onTileError;
3408
3632
  levelChangeRef.current = onLevelChange;
3409
- const ownedManager = (0, import_react14.useMemo)(
3633
+ const ownedManager = (0, import_react16.useMemo)(
3410
3634
  () => new TileTextureManager({
3411
3635
  anisotropy,
3412
3636
  maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
@@ -3427,7 +3651,7 @@ function Tile({
3427
3651
  ]
3428
3652
  );
3429
3653
  const manager = sharedManager ?? ownedManager;
3430
- (0, import_react14.useEffect)(() => {
3654
+ (0, import_react16.useEffect)(() => {
3431
3655
  manager.resume();
3432
3656
  return () => {
3433
3657
  if (manager === ownedManager) {
@@ -3437,11 +3661,11 @@ function Tile({
3437
3661
  queueMicrotask(() => manager.releaseUnused());
3438
3662
  };
3439
3663
  }, [manager, ownedManager]);
3440
- const readyRef = (0, import_react14.useRef)(false);
3441
- (0, import_react14.useEffect)(() => {
3664
+ const readyRef = (0, import_react16.useRef)(false);
3665
+ (0, import_react16.useEffect)(() => {
3442
3666
  readyRef.current = false;
3443
3667
  }, [effectivePreviewUrl]);
3444
- (0, import_react14.useEffect)(() => {
3668
+ (0, import_react16.useEffect)(() => {
3445
3669
  if (!previewTexture || readyRef.current) {
3446
3670
  return;
3447
3671
  }
@@ -3449,31 +3673,31 @@ function Tile({
3449
3673
  onReady?.();
3450
3674
  }, [onReady, previewTexture]);
3451
3675
  const template = urlTemplate ?? buildDefaultTileUrlTemplate();
3452
- const resolveUrl = (0, import_react14.useMemo)(
3676
+ const resolveUrl = (0, import_react16.useMemo)(
3453
3677
  () => (address) => {
3454
3678
  const relativePath = resolveTileUrl ? resolveTileUrl(address) : resolveTemplateUrl(template, address);
3455
3679
  return resolveRelativeTileUrl(baseUrl, relativePath);
3456
3680
  },
3457
3681
  [baseUrl, resolveTileUrl, template]
3458
3682
  );
3459
- const [layers, setLayers] = (0, import_react14.useState)([]);
3460
- const layerTokenRef = (0, import_react14.useRef)("");
3461
- const currentLevelRef = (0, import_react14.useRef)(1);
3462
- const lastUpdateAtRef = (0, import_react14.useRef)(-Infinity);
3463
- const visibilityDirtyRef = (0, import_react14.useRef)(true);
3464
- const lastVisibilityQuaternionRef = (0, import_react14.useRef)(camera.quaternion.clone());
3465
- const lastVisibilityFovRef = (0, import_react14.useRef)(Number.NaN);
3466
- const lastViewportRef = (0, import_react14.useRef)({ width: 0, height: 0, dpr: 0 });
3467
- const frustumRef = (0, import_react14.useRef)(new import_three7.Frustum());
3468
- const projectionMatrixRef = (0, import_react14.useRef)(new import_three7.Matrix4());
3469
- const visibilityCameraRef = (0, import_react14.useRef)(new import_three7.PerspectiveCamera());
3470
- (0, import_react14.useEffect)(() => {
3683
+ const [layers, setLayers] = (0, import_react16.useState)([]);
3684
+ const layerTokenRef = (0, import_react16.useRef)("");
3685
+ const currentLevelRef = (0, import_react16.useRef)(1);
3686
+ const lastUpdateAtRef = (0, import_react16.useRef)(-Infinity);
3687
+ const visibilityDirtyRef = (0, import_react16.useRef)(true);
3688
+ const lastVisibilityQuaternionRef = (0, import_react16.useRef)(camera.quaternion.clone());
3689
+ const lastVisibilityFovRef = (0, import_react16.useRef)(Number.NaN);
3690
+ const lastViewportRef = (0, import_react16.useRef)({ width: 0, height: 0, dpr: 0 });
3691
+ const frustumRef = (0, import_react16.useRef)(new import_three7.Frustum());
3692
+ const projectionMatrixRef = (0, import_react16.useRef)(new import_three7.Matrix4());
3693
+ const visibilityCameraRef = (0, import_react16.useRef)(new import_three7.PerspectiveCamera());
3694
+ (0, import_react16.useEffect)(() => {
3471
3695
  currentLevelRef.current = 1;
3472
3696
  layerTokenRef.current = "";
3473
3697
  visibilityDirtyRef.current = true;
3474
3698
  setLayers([]);
3475
3699
  }, [manager, multiresToken]);
3476
- (0, import_react14.useEffect)(() => {
3700
+ (0, import_react16.useEffect)(() => {
3477
3701
  if (loadMode !== "preview") {
3478
3702
  return;
3479
3703
  }
@@ -3487,7 +3711,7 @@ function Tile({
3487
3711
  setLayers(nextLayers);
3488
3712
  }
3489
3713
  };
3490
- (0, import_fiber11.useFrame)(() => {
3714
+ (0, import_fiber12.useFrame)(() => {
3491
3715
  if (loadMode === "preview") {
3492
3716
  return;
3493
3717
  }
@@ -3553,15 +3777,15 @@ function Tile({
3553
3777
  };
3554
3778
  commitLayers(nextLayers);
3555
3779
  });
3556
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("group", { visible, children: [
3557
- previewTexture ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3780
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("group", { visible, children: [
3781
+ previewTexture ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3558
3782
  PreviewCube,
3559
3783
  {
3560
3784
  texture: previewTexture,
3561
3785
  faceOrder: previewFaceOrder
3562
3786
  }
3563
3787
  ) : null,
3564
- layers.map((layer) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3788
+ layers.map((layer) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3565
3789
  TileLayerMeshes,
3566
3790
  {
3567
3791
  layer,
@@ -3573,8 +3797,8 @@ function Tile({
3573
3797
  ] });
3574
3798
  }
3575
3799
 
3576
- // src/panorama-scenes.tsx
3577
- var import_jsx_runtime8 = require("react/jsx-runtime");
3800
+ // src/scenes.tsx
3801
+ var import_jsx_runtime9 = require("react/jsx-runtime");
3578
3802
  var TRANSITION_DEFAULTS = {
3579
3803
  none: { duration: 0, krpanoBlend: "NOBLEND" },
3580
3804
  crossfade: { duration: 1, krpanoBlend: "BLEND(1.0, easeInCubic)" },
@@ -3624,7 +3848,7 @@ function SceneSource({
3624
3848
  return null;
3625
3849
  }
3626
3850
  if (scene.type === "sphere") {
3627
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3851
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3628
3852
  Sphere,
3629
3853
  {
3630
3854
  src: scene.src,
@@ -3636,7 +3860,7 @@ function SceneSource({
3636
3860
  );
3637
3861
  }
3638
3862
  const { id: _id, type: _type, ...tileProps } = scene;
3639
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3863
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3640
3864
  Tile,
3641
3865
  {
3642
3866
  ...tileProps,
@@ -3652,17 +3876,17 @@ function SnapshotCapture({
3652
3876
  maxPixels,
3653
3877
  onCaptured
3654
3878
  }) {
3655
- const { camera, gl, scene, size } = (0, import_fiber12.useThree)();
3656
- const capturedRequestRef = (0, import_react15.useRef)(0);
3657
- const renderTargetRef = (0, import_react15.useRef)(null);
3658
- (0, import_react15.useEffect)(
3879
+ const { camera, gl, scene, size } = (0, import_fiber13.useThree)();
3880
+ const capturedRequestRef = (0, import_react17.useRef)(0);
3881
+ const renderTargetRef = (0, import_react17.useRef)(null);
3882
+ (0, import_react17.useEffect)(
3659
3883
  () => () => {
3660
3884
  renderTargetRef.current?.dispose();
3661
3885
  renderTargetRef.current = null;
3662
3886
  },
3663
3887
  []
3664
3888
  );
3665
- (0, import_fiber12.useFrame)(() => {
3889
+ (0, import_fiber13.useFrame)(() => {
3666
3890
  if (requestId === 0 || requestId === capturedRequestRef.current) {
3667
3891
  return;
3668
3892
  }
@@ -3708,12 +3932,12 @@ function SnapshotOverlay({
3708
3932
  runId,
3709
3933
  onFinish
3710
3934
  }) {
3711
- const { camera, size } = (0, import_fiber12.useThree)();
3712
- const meshRef = (0, import_react15.useRef)(null);
3713
- const startRef = (0, import_react15.useRef)(null);
3714
- const finishedRef = (0, import_react15.useRef)(false);
3715
- const directionRef = (0, import_react15.useRef)(new import_three8.Vector3());
3716
- const material = (0, import_react15.useMemo)(
3935
+ const { camera, size } = (0, import_fiber13.useThree)();
3936
+ const meshRef = (0, import_react17.useRef)(null);
3937
+ const startRef = (0, import_react17.useRef)(null);
3938
+ const finishedRef = (0, import_react17.useRef)(false);
3939
+ const directionRef = (0, import_react17.useRef)(new import_three8.Vector3());
3940
+ const material = (0, import_react17.useMemo)(
3717
3941
  () => new import_three8.ShaderMaterial({
3718
3942
  transparent: true,
3719
3943
  depthTest: false,
@@ -3792,13 +4016,13 @@ function SnapshotOverlay({
3792
4016
  }),
3793
4017
  [snapshot.texture, transition.preset]
3794
4018
  );
3795
- (0, import_react15.useEffect)(() => () => material.dispose(), [material]);
3796
- (0, import_react15.useEffect)(() => {
4019
+ (0, import_react17.useEffect)(() => () => material.dispose(), [material]);
4020
+ (0, import_react17.useEffect)(() => {
3797
4021
  startRef.current = null;
3798
4022
  finishedRef.current = false;
3799
4023
  material.uniforms.progress.value = 0;
3800
4024
  }, [material, runId]);
3801
- (0, import_fiber12.useFrame)((state) => {
4025
+ (0, import_fiber13.useFrame)((state) => {
3802
4026
  const mesh = meshRef.current;
3803
4027
  if (!mesh || !camera.isPerspectiveCamera) {
3804
4028
  return;
@@ -3823,13 +4047,13 @@ function SnapshotOverlay({
3823
4047
  onFinish();
3824
4048
  }
3825
4049
  });
3826
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("mesh", { ref: meshRef, renderOrder: 1e4, frustumCulled: false, children: [
3827
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("primitive", { attach: "geometry", object: FULLSCREEN_PLANE }),
3828
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("primitive", { attach: "material", object: material })
4050
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("mesh", { ref: meshRef, renderOrder: 1e4, frustumCulled: false, children: [
4051
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("primitive", { attach: "geometry", object: FULLSCREEN_PLANE }),
4052
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("primitive", { attach: "material", object: material })
3829
4053
  ] });
3830
4054
  }
3831
4055
  var FULLSCREEN_PLANE = new import_three8.PlaneGeometry(1, 1);
3832
- function PanoramaScenesController({
4056
+ function ScenesController({
3833
4057
  scenes,
3834
4058
  activeSceneId,
3835
4059
  transition: transitionInput,
@@ -3838,47 +4062,47 @@ function PanoramaScenesController({
3838
4062
  onTransitionError,
3839
4063
  snapshotMaxPixels = 3686400
3840
4064
  }) {
3841
- const controlsRef = (0, import_react15.useContext)(PanoramaViewContext);
4065
+ const controlsRef = (0, import_react17.useContext)(PanoramaViewContext);
3842
4066
  const manager = useSharedTileTextureManager();
3843
- const transition = (0, import_react15.useMemo)(
4067
+ const transition = (0, import_react17.useMemo)(
3844
4068
  () => resolveTransition(transitionInput),
3845
4069
  [transitionInput]
3846
4070
  );
3847
- const sceneMap = (0, import_react15.useMemo)(
4071
+ const sceneMap = (0, import_react17.useMemo)(
3848
4072
  () => new Map(scenes.map((scene) => [scene.id, scene])),
3849
4073
  [scenes]
3850
4074
  );
3851
4075
  const initialScene = sceneMap.get(activeSceneId) ?? null;
3852
- const [slots, setSlots] = (0, import_react15.useState)([
4076
+ const [slots, setSlots] = (0, import_react17.useState)([
3853
4077
  initialScene,
3854
4078
  null
3855
4079
  ]);
3856
- const [activeSlot, setActiveSlot] = (0, import_react15.useState)(0);
3857
- const [liveSlot, setLiveSlot] = (0, import_react15.useState)(0);
3858
- const [pendingSlot, setPendingSlot] = (0, import_react15.useState)(null);
3859
- const [phase, setPhase] = (0, import_react15.useState)(initialScene ? "idle" : "preloading");
3860
- const [captureRequestId, setCaptureRequestId] = (0, import_react15.useState)(0);
3861
- const [transitionRunId, setTransitionRunId] = (0, import_react15.useState)(0);
3862
- const [snapshot, setSnapshot] = (0, import_react15.useState)(null);
3863
- const [recoveryKey, setRecoveryKey] = (0, import_react15.useState)(0);
3864
- const requestedSceneIdRef = (0, import_react15.useRef)(activeSceneId);
3865
- const captureReasonRef = (0, import_react15.useRef)("start");
3866
- const interactionReleaseRef = (0, import_react15.useRef)(null);
3867
- const slotsRef = (0, import_react15.useRef)(slots);
3868
- const phaseRef = (0, import_react15.useRef)(phase);
3869
- const liveSlotRef = (0, import_react15.useRef)(liveSlot);
3870
- const transitionFromRef = (0, import_react15.useRef)(initialScene);
4080
+ const [activeSlot, setActiveSlot] = (0, import_react17.useState)(0);
4081
+ const [liveSlot, setLiveSlot] = (0, import_react17.useState)(0);
4082
+ const [pendingSlot, setPendingSlot] = (0, import_react17.useState)(null);
4083
+ const [phase, setPhase] = (0, import_react17.useState)(initialScene ? "idle" : "preloading");
4084
+ const [captureRequestId, setCaptureRequestId] = (0, import_react17.useState)(0);
4085
+ const [transitionRunId, setTransitionRunId] = (0, import_react17.useState)(0);
4086
+ const [snapshot, setSnapshot] = (0, import_react17.useState)(null);
4087
+ const [recoveryKey, setRecoveryKey] = (0, import_react17.useState)(0);
4088
+ const requestedSceneIdRef = (0, import_react17.useRef)(activeSceneId);
4089
+ const captureReasonRef = (0, import_react17.useRef)("start");
4090
+ const interactionReleaseRef = (0, import_react17.useRef)(null);
4091
+ const slotsRef = (0, import_react17.useRef)(slots);
4092
+ const phaseRef = (0, import_react17.useRef)(phase);
4093
+ const liveSlotRef = (0, import_react17.useRef)(liveSlot);
4094
+ const transitionFromRef = (0, import_react17.useRef)(initialScene);
3871
4095
  slotsRef.current = slots;
3872
4096
  phaseRef.current = phase;
3873
4097
  liveSlotRef.current = liveSlot;
3874
- const releaseInteraction = (0, import_react15.useCallback)(() => {
4098
+ const releaseInteraction = (0, import_react17.useCallback)(() => {
3875
4099
  interactionReleaseRef.current?.();
3876
4100
  interactionReleaseRef.current = null;
3877
4101
  }, []);
3878
- const reportError = (0, import_react15.useCallback)((sceneId, error) => {
4102
+ const reportError = (0, import_react17.useCallback)((sceneId, error) => {
3879
4103
  onTransitionError?.({ sceneId, error });
3880
4104
  }, [onTransitionError]);
3881
- const startPreload = (0, import_react15.useCallback)((scene) => {
4105
+ const startPreload = (0, import_react17.useCallback)((scene) => {
3882
4106
  const nextSlot = 1 - liveSlotRef.current;
3883
4107
  requestedSceneIdRef.current = scene.id;
3884
4108
  setSlots((current) => {
@@ -3889,7 +4113,7 @@ function PanoramaScenesController({
3889
4113
  setPendingSlot(nextSlot);
3890
4114
  setPhase("preloading");
3891
4115
  }, []);
3892
- (0, import_react15.useEffect)(() => {
4116
+ (0, import_react17.useEffect)(() => {
3893
4117
  const requested = sceneMap.get(activeSceneId);
3894
4118
  if (!requested) {
3895
4119
  reportError(activeSceneId, new Error(`Unknown panorama scene: ${activeSceneId}`));
@@ -3913,7 +4137,7 @@ function PanoramaScenesController({
3913
4137
  }
3914
4138
  startPreload(requested);
3915
4139
  }, [activeSceneId, pendingSlot, reportError, sceneMap, startPreload]);
3916
- const handleReady = (0, import_react15.useCallback)((slot) => {
4140
+ const handleReady = (0, import_react17.useCallback)((slot) => {
3917
4141
  if (slot !== pendingSlot || phaseRef.current !== "preloading") {
3918
4142
  return;
3919
4143
  }
@@ -3944,7 +4168,7 @@ function PanoramaScenesController({
3944
4168
  setPhase("capturing");
3945
4169
  setCaptureRequestId((current) => current + 1);
3946
4170
  }, [controlsRef, onTransitionEnd, pendingSlot, transition.preset]);
3947
- const handleCaptured = (0, import_react15.useCallback)((nextSnapshot) => {
4171
+ const handleCaptured = (0, import_react17.useCallback)((nextSnapshot) => {
3948
4172
  setSnapshot((current) => {
3949
4173
  current?.dispose();
3950
4174
  return nextSnapshot;
@@ -3966,7 +4190,7 @@ function PanoramaScenesController({
3966
4190
  setPhase("transitioning");
3967
4191
  setTransitionRunId((current) => current + 1);
3968
4192
  }, [manager, pendingSlot, sceneMap, startPreload]);
3969
- const finishTransition = (0, import_react15.useCallback)(() => {
4193
+ const finishTransition = (0, import_react17.useCallback)(() => {
3970
4194
  const nextSlot = pendingSlot;
3971
4195
  const previousScene = transitionFromRef.current;
3972
4196
  const nextScene = nextSlot === null ? null : slotsRef.current[nextSlot];
@@ -3991,12 +4215,12 @@ function PanoramaScenesController({
3991
4215
  });
3992
4216
  }
3993
4217
  }, [onTransitionEnd, pendingSlot, releaseInteraction, snapshot, transition.preset]);
3994
- (0, import_react15.useEffect)(() => () => {
4218
+ (0, import_react17.useEffect)(() => () => {
3995
4219
  releaseInteraction();
3996
4220
  snapshot?.dispose();
3997
4221
  }, [releaseInteraction, snapshot]);
3998
- const { gl } = (0, import_fiber12.useThree)();
3999
- (0, import_react15.useEffect)(() => {
4222
+ const { gl } = (0, import_fiber13.useThree)();
4223
+ (0, import_react17.useEffect)(() => {
4000
4224
  const canvas = gl.domElement;
4001
4225
  const onContextLost = (event) => {
4002
4226
  event.preventDefault();
@@ -4020,8 +4244,8 @@ function PanoramaScenesController({
4020
4244
  }, [gl, manager, releaseInteraction, snapshot]);
4021
4245
  const renderedScene = slots[liveSlot];
4022
4246
  const snapshotVisible = snapshot !== null && (phase === "transitioning" || phase === "recapturing" || phase === "preloading");
4023
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react15.Fragment, { children: [
4024
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4247
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react17.Fragment, { children: [
4248
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4025
4249
  SnapshotCapture,
4026
4250
  {
4027
4251
  requestId: captureRequestId,
@@ -4033,7 +4257,7 @@ function PanoramaScenesController({
4033
4257
  const source = slots[slot];
4034
4258
  const isLive = slot === liveSlot;
4035
4259
  const isPending = slot === pendingSlot;
4036
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4260
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4037
4261
  SceneSource,
4038
4262
  {
4039
4263
  scene: source,
@@ -4049,7 +4273,7 @@ function PanoramaScenesController({
4049
4273
  `${slot}:${source?.id ?? "empty"}:${recoveryKey}`
4050
4274
  );
4051
4275
  }),
4052
- snapshotVisible ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4276
+ snapshotVisible ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4053
4277
  SnapshotOverlay,
4054
4278
  {
4055
4279
  snapshot,
@@ -4062,60 +4286,186 @@ function PanoramaScenesController({
4062
4286
  phase === "idle" && renderedScene ? renderHotspots?.(renderedScene) : null
4063
4287
  ] });
4064
4288
  }
4065
- function PanoramaScenes({
4289
+ function Scenes({
4066
4290
  maxTextureMemoryMb,
4067
4291
  maxConcurrentTileLoads,
4068
4292
  ...props
4069
4293
  }) {
4070
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4294
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4071
4295
  TileTextureManagerProvider,
4072
4296
  {
4073
4297
  maxTextureMemoryMb,
4074
4298
  maxConcurrentLoads: maxConcurrentTileLoads,
4075
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PanoramaScenesController, { ...props })
4299
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ScenesController, { ...props })
4076
4300
  }
4077
4301
  );
4078
4302
  }
4079
4303
 
4080
4304
  // src/hotspot/image-hotspot.tsx
4081
- var import_fiber14 = require("@react-three/fiber");
4082
- var import_react17 = require("react");
4083
- var import_three10 = require("three");
4305
+ var import_fiber15 = require("@react-three/fiber");
4306
+ var import_react20 = require("react");
4307
+ var import_three11 = require("three");
4084
4308
 
4085
4309
  // src/hotspot/hotspot-anchor.tsx
4086
- var import_fiber13 = require("@react-three/fiber");
4087
- var import_react16 = require("react");
4310
+ var import_fiber14 = require("@react-three/fiber");
4311
+ var import_react19 = require("react");
4088
4312
  var import_three9 = require("three");
4089
- var import_jsx_runtime9 = require("react/jsx-runtime");
4090
- var DEFAULT_FLOATING_DISTANCE = 10;
4091
- var PANORAMA_RADIUS = DEFAULT_PANORAMA_RADIUS;
4092
- var PANORAMA_SURFACE_INSET = 4;
4093
- var DEFAULT_REFERENCE_FOV = 75;
4094
- var MIN_ANGULAR_SIZE = 0.01;
4095
- var MAX_ANGULAR_SIZE = 179;
4096
- var DRAG_EPSILON_DEGREES = 1e-3;
4097
- var LOCAL_FORWARD = new import_three9.Vector3(0, 0, 1);
4098
- var HOTSPOT_MODE_RENDERING = {
4099
- surface: { orientation: "surface", placement: "surface" },
4100
- billboard: { orientation: "billboard", placement: "floating" }
4313
+
4314
+ // src/hotspot/hotspot-tooltip.tsx
4315
+ var import_drei = require("@react-three/drei");
4316
+ var import_react18 = require("react");
4317
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4318
+ var TOOLTIP_MAX_WIDTH = 220;
4319
+ var TOOLTIP_IMAGE_MAX_HEIGHT = 120;
4320
+ var DEFAULT_HOTSPOT_TOOLTIP_OFFSET = 12;
4321
+ var WRAPPER_STYLE = {
4322
+ pointerEvents: "none"
4101
4323
  };
4102
- function angularSizeToWorldSize(size, radius) {
4103
- const safeSize = Number.isFinite(size) ? import_three9.MathUtils.clamp(Math.abs(size), MIN_ANGULAR_SIZE, MAX_ANGULAR_SIZE) : MIN_ANGULAR_SIZE;
4104
- return 2 * radius * Math.tan(import_three9.MathUtils.degToRad(safeSize) / 2);
4105
- }
4106
- function resolveScale(scale) {
4107
- return Number.isFinite(scale) && scale > 0 ? scale : 1;
4108
- }
4109
- function resolveDistance(placement, distance, width, height) {
4110
- const maximumDistance = maximumHotspotDistance(width, height);
4111
- if (placement === "surface") {
4112
- return maximumDistance;
4324
+ var BUBBLE_STYLE = {
4325
+ background: "rgba(22, 22, 22, 0.72)",
4326
+ border: "1px solid rgba(46, 46, 46, 0.7)",
4327
+ borderRadius: 8,
4328
+ boxShadow: "0 8px 24px rgba(0, 0, 0, 0.35)",
4329
+ boxSizing: "border-box",
4330
+ color: "#f5fbfc",
4331
+ display: "flex",
4332
+ flexDirection: "column",
4333
+ gap: 8,
4334
+ padding: 8,
4335
+ pointerEvents: "none",
4336
+ whiteSpace: "nowrap"
4337
+ };
4338
+ var IMAGE_STYLE = {
4339
+ borderRadius: 4,
4340
+ display: "block",
4341
+ height: "auto",
4342
+ maxHeight: TOOLTIP_IMAGE_MAX_HEIGHT,
4343
+ maxWidth: TOOLTIP_MAX_WIDTH,
4344
+ objectFit: "contain"
4345
+ };
4346
+ var TEXT_STYLE = {
4347
+ fontFamily: "system-ui, sans-serif",
4348
+ fontSize: 12,
4349
+ lineHeight: 1.4,
4350
+ margin: 0,
4351
+ overflow: "visible",
4352
+ whiteSpace: "nowrap"
4353
+ };
4354
+ function resolveHotspotTooltipContent(tooltip) {
4355
+ if (tooltip == null) {
4356
+ return null;
4113
4357
  }
4114
- if (!Number.isFinite(distance)) {
4115
- return DEFAULT_FLOATING_DISTANCE;
4358
+ if (typeof tooltip === "string") {
4359
+ const text2 = tooltip.trim();
4360
+ return text2 ? { text: text2 } : null;
4116
4361
  }
4117
- return import_three9.MathUtils.clamp(Math.abs(distance), 0.1, maximumDistance);
4118
- }
4362
+ const text = tooltip.text?.trim();
4363
+ const image = tooltip.image?.trim();
4364
+ if (!text && !image) {
4365
+ return null;
4366
+ }
4367
+ const imageAlt = tooltip.imageAlt?.trim();
4368
+ return {
4369
+ image: image || void 0,
4370
+ imageAlt: imageAlt || void 0,
4371
+ text: text || void 0
4372
+ };
4373
+ }
4374
+ function resolveHotspotTooltipOffset(offset) {
4375
+ if (!Number.isFinite(offset)) {
4376
+ return DEFAULT_HOTSPOT_TOOLTIP_OFFSET;
4377
+ }
4378
+ return Math.max(0, offset);
4379
+ }
4380
+ function tooltipBubbleTransform(placement, offset) {
4381
+ const gap = `${offset}px`;
4382
+ switch (placement) {
4383
+ case "top":
4384
+ return `translate(-50%, calc(-100% - ${gap}))`;
4385
+ case "bottom":
4386
+ return `translate(-50%, ${gap})`;
4387
+ case "left":
4388
+ return `translate(calc(-100% - ${gap}), -50%)`;
4389
+ case "right":
4390
+ return `translate(${gap}, -50%)`;
4391
+ default: {
4392
+ const exhaustive = placement;
4393
+ return exhaustive;
4394
+ }
4395
+ }
4396
+ }
4397
+ function HotspotTooltip({
4398
+ content,
4399
+ placement = "top",
4400
+ offset = DEFAULT_HOTSPOT_TOOLTIP_OFFSET
4401
+ }) {
4402
+ const [imageFailed, setImageFailed] = (0, import_react18.useState)(false);
4403
+ (0, import_react18.useEffect)(() => {
4404
+ setImageFailed(false);
4405
+ }, [content.image]);
4406
+ const showImage = Boolean(content.image) && !imageFailed;
4407
+ const bubbleStyle = (0, import_react18.useMemo)(
4408
+ () => ({
4409
+ ...BUBBLE_STYLE,
4410
+ transform: tooltipBubbleTransform(
4411
+ placement,
4412
+ resolveHotspotTooltipOffset(offset)
4413
+ )
4414
+ }),
4415
+ [offset, placement]
4416
+ );
4417
+ if (!showImage && !content.text) {
4418
+ return null;
4419
+ }
4420
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_drei.Html, { pointerEvents: "none", style: WRAPPER_STYLE, zIndexRange: [20, 10], children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { role: "tooltip", style: bubbleStyle, children: [
4421
+ showImage ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4422
+ "img",
4423
+ {
4424
+ alt: content.imageAlt ?? content.text ?? "",
4425
+ onError: () => setImageFailed(true),
4426
+ src: content.image,
4427
+ style: IMAGE_STYLE
4428
+ }
4429
+ ) : null,
4430
+ content.text ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: TEXT_STYLE, children: content.text }) : null
4431
+ ] }) });
4432
+ }
4433
+
4434
+ // src/hotspot/hotspot-anchor.tsx
4435
+ var import_jsx_runtime11 = require("react/jsx-runtime");
4436
+ var DEFAULT_FLOATING_DISTANCE = 10;
4437
+ var PANORAMA_RADIUS = DEFAULT_PANORAMA_RADIUS;
4438
+ var PANORAMA_SURFACE_INSET = 4;
4439
+ var DEFAULT_REFERENCE_FOV = 75;
4440
+ var MIN_ANGULAR_SIZE = 0.01;
4441
+ var MAX_ANGULAR_SIZE = 179;
4442
+ var DRAG_EPSILON_DEGREES = 1e-3;
4443
+ var LOCAL_FORWARD = new import_three9.Vector3(0, 0, 1);
4444
+ var TOOLTIP_WORLD_BOX = new import_three9.Box3();
4445
+ var TOOLTIP_LOCAL_BOX = new import_three9.Box3();
4446
+ var TOOLTIP_CORNER = new import_three9.Vector3();
4447
+ var TOOLTIP_INVERSE = new import_three9.Matrix4();
4448
+ var HOTSPOT_MODE_RENDERING = {
4449
+ surface: { orientation: "surface", placement: "surface" },
4450
+ billboard: { orientation: "billboard", placement: "floating" }
4451
+ };
4452
+ function angularSizeToWorldSize(size, radius) {
4453
+ const safeSize = Number.isFinite(size) ? import_three9.MathUtils.clamp(Math.abs(size), MIN_ANGULAR_SIZE, MAX_ANGULAR_SIZE) : MIN_ANGULAR_SIZE;
4454
+ return 2 * radius * Math.tan(import_three9.MathUtils.degToRad(safeSize) / 2);
4455
+ }
4456
+ function resolveScale(scale) {
4457
+ return Number.isFinite(scale) && scale > 0 ? scale : 1;
4458
+ }
4459
+ function resolveDistance(placement, distance, width, height) {
4460
+ const maximumDistance = maximumHotspotDistance(width, height);
4461
+ if (placement === "surface") {
4462
+ return maximumDistance;
4463
+ }
4464
+ if (!Number.isFinite(distance)) {
4465
+ return DEFAULT_FLOATING_DISTANCE;
4466
+ }
4467
+ return import_three9.MathUtils.clamp(Math.abs(distance), 0.1, maximumDistance);
4468
+ }
4119
4469
  function maximumHotspotDistance(width, height) {
4120
4470
  const safeWidth = import_three9.MathUtils.clamp(
4121
4471
  Math.abs(Number.isFinite(width) ? width : MIN_ANGULAR_SIZE),
@@ -4165,6 +4515,100 @@ function angularDistance(a, b) {
4165
4515
  const bVector = panoPositionToVector3(b);
4166
4516
  return import_three9.MathUtils.radToDeg(aVector.angleTo(bVector));
4167
4517
  }
4518
+ function isTooltipOpen(trigger, hovered, focused, pinned) {
4519
+ switch (trigger) {
4520
+ case "always":
4521
+ return true;
4522
+ case "hover":
4523
+ return hovered || focused;
4524
+ case "click":
4525
+ return pinned;
4526
+ default: {
4527
+ const exhaustive = trigger;
4528
+ return exhaustive;
4529
+ }
4530
+ }
4531
+ }
4532
+ function setTooltipAnchorOnUnitPlane(anchor, placement) {
4533
+ switch (placement) {
4534
+ case "top":
4535
+ anchor.position.set(0, 0.5, 0);
4536
+ return;
4537
+ case "bottom":
4538
+ anchor.position.set(0, -0.5, 0);
4539
+ return;
4540
+ case "left":
4541
+ anchor.position.set(-0.5, 0, 0);
4542
+ return;
4543
+ case "right":
4544
+ anchor.position.set(0.5, 0, 0);
4545
+ return;
4546
+ default: {
4547
+ const exhaustive = placement;
4548
+ return exhaustive;
4549
+ }
4550
+ }
4551
+ }
4552
+ function setTooltipAnchorOnLocalBox(anchor, placement, minX, maxX, minY, maxY) {
4553
+ switch (placement) {
4554
+ case "top":
4555
+ anchor.position.set((minX + maxX) / 2, maxY, 0);
4556
+ return;
4557
+ case "bottom":
4558
+ anchor.position.set((minX + maxX) / 2, minY, 0);
4559
+ return;
4560
+ case "left":
4561
+ anchor.position.set(minX, (minY + maxY) / 2, 0);
4562
+ return;
4563
+ case "right":
4564
+ anchor.position.set(maxX, (minY + maxY) / 2, 0);
4565
+ return;
4566
+ default: {
4567
+ const exhaustive = placement;
4568
+ return exhaustive;
4569
+ }
4570
+ }
4571
+ }
4572
+ function updateTooltipAnchor(anchor, group, placement, useAngularScale) {
4573
+ if (useAngularScale) {
4574
+ setTooltipAnchorOnUnitPlane(anchor, placement);
4575
+ return;
4576
+ }
4577
+ group.updateMatrixWorld(true);
4578
+ TOOLTIP_WORLD_BOX.makeEmpty();
4579
+ for (const child of group.children) {
4580
+ if (child === anchor) {
4581
+ continue;
4582
+ }
4583
+ TOOLTIP_WORLD_BOX.expandByObject(child);
4584
+ }
4585
+ if (TOOLTIP_WORLD_BOX.isEmpty()) {
4586
+ setTooltipAnchorOnUnitPlane(anchor, placement);
4587
+ return;
4588
+ }
4589
+ TOOLTIP_INVERSE.copy(group.matrixWorld).invert();
4590
+ TOOLTIP_LOCAL_BOX.makeEmpty();
4591
+ for (let x = 0; x <= 1; x += 1) {
4592
+ for (let y = 0; y <= 1; y += 1) {
4593
+ for (let z = 0; z <= 1; z += 1) {
4594
+ TOOLTIP_CORNER.set(
4595
+ x === 0 ? TOOLTIP_WORLD_BOX.min.x : TOOLTIP_WORLD_BOX.max.x,
4596
+ y === 0 ? TOOLTIP_WORLD_BOX.min.y : TOOLTIP_WORLD_BOX.max.y,
4597
+ z === 0 ? TOOLTIP_WORLD_BOX.min.z : TOOLTIP_WORLD_BOX.max.z
4598
+ ).applyMatrix4(TOOLTIP_INVERSE);
4599
+ TOOLTIP_LOCAL_BOX.expandByPoint(TOOLTIP_CORNER);
4600
+ }
4601
+ }
4602
+ }
4603
+ setTooltipAnchorOnLocalBox(
4604
+ anchor,
4605
+ placement,
4606
+ TOOLTIP_LOCAL_BOX.min.x,
4607
+ TOOLTIP_LOCAL_BOX.max.x,
4608
+ TOOLTIP_LOCAL_BOX.min.y,
4609
+ TOOLTIP_LOCAL_BOX.max.y
4610
+ );
4611
+ }
4168
4612
  function HotspotAnchor({
4169
4613
  id,
4170
4614
  position,
@@ -4184,7 +4628,12 @@ function HotspotAnchor({
4184
4628
  visible = true,
4185
4629
  draggable = false,
4186
4630
  interactive = true,
4631
+ cursor,
4187
4632
  ariaLabel,
4633
+ tooltip,
4634
+ tooltipTrigger = "always",
4635
+ tooltipPlacement = "top",
4636
+ tooltipOffset,
4188
4637
  children,
4189
4638
  onClick,
4190
4639
  onHoverChange,
@@ -4192,10 +4641,20 @@ function HotspotAnchor({
4192
4641
  onPositionChange,
4193
4642
  onDragEnd
4194
4643
  }) {
4195
- const controlsRef = (0, import_react16.useContext)(PanoramaViewContext);
4196
- const groupRef = (0, import_react16.useRef)(null);
4197
- const dragStateRef = (0, import_react16.useRef)(null);
4198
- const suppressNextClickRef = (0, import_react16.useRef)(false);
4644
+ const controlsRef = (0, import_react19.useContext)(PanoramaViewContext);
4645
+ const eventBus = (0, import_react19.useContext)(PanoEventBusContext);
4646
+ const cursorApi = usePanoCursor();
4647
+ const groupRef = (0, import_react19.useRef)(null);
4648
+ const tooltipAnchorRef = (0, import_react19.useRef)(null);
4649
+ const dragStateRef = (0, import_react19.useRef)(null);
4650
+ const suppressNextClickRef = (0, import_react19.useRef)(false);
4651
+ const [hovered, setHovered] = (0, import_react19.useState)(false);
4652
+ const [dragging, setDragging] = (0, import_react19.useState)(false);
4653
+ const [pinned, setPinned] = (0, import_react19.useState)(false);
4654
+ const tooltipContent = (0, import_react19.useMemo)(
4655
+ () => resolveHotspotTooltipContent(tooltip),
4656
+ [tooltip]
4657
+ );
4199
4658
  const normalizedPosition = normalizePanoPosition(position);
4200
4659
  const rendering = internalOrientation && internalPlacement ? { orientation: internalOrientation, placement: internalPlacement } : HOTSPOT_MODE_RENDERING[mode];
4201
4660
  const { orientation: orientation2, placement } = rendering;
@@ -4208,27 +4667,33 @@ function HotspotAnchor({
4208
4667
  scaledWidth,
4209
4668
  scaledHeight
4210
4669
  );
4211
- const worldPosition = (0, import_react16.useMemo)(
4670
+ const worldPosition = (0, import_react19.useMemo)(
4212
4671
  () => panoPositionToVector3(normalizedPosition, resolvedDistance),
4213
4672
  [normalizedPosition.pitch, normalizedPosition.yaw, resolvedDistance]
4214
4673
  );
4215
- const surfaceQuaternion3 = (0, import_react16.useMemo)(
4674
+ const surfaceQuaternion3 = (0, import_react19.useMemo)(
4216
4675
  () => createSurfaceQuaternion(normalizedPosition),
4217
4676
  [normalizedPosition.pitch, normalizedPosition.yaw]
4218
4677
  );
4219
4678
  const worldWidth = angularSizeToWorldSize(scaledWidth, resolvedDistance);
4220
4679
  const worldHeight = angularSizeToWorldSize(scaledHeight, resolvedDistance);
4221
- const focusGeometry = (0, import_react16.useMemo)(() => {
4680
+ const focusGeometry = (0, import_react19.useMemo)(() => {
4222
4681
  const planeGeometry = new import_three9.PlaneGeometry(1, 1);
4223
4682
  const geometry = new import_three9.EdgesGeometry(planeGeometry);
4224
4683
  planeGeometry.dispose();
4225
4684
  return geometry;
4226
4685
  }, []);
4686
+ const canActivateFromKeyboard = Boolean(
4687
+ visible && interactive && (onClick || tooltipTrigger === "click")
4688
+ );
4227
4689
  const focused = useHotspotAccessibility({
4228
4690
  id,
4229
4691
  ariaLabel,
4230
- onActivate: visible && interactive && onClick ? (event) => {
4231
- onClick({
4692
+ onActivate: canActivateFromKeyboard ? (event) => {
4693
+ if (tooltipTrigger === "click") {
4694
+ setPinned((current) => !current);
4695
+ }
4696
+ onClick?.({
4232
4697
  id,
4233
4698
  position: normalizedPosition,
4234
4699
  source: "keyboard",
@@ -4236,15 +4701,59 @@ function HotspotAnchor({
4236
4701
  });
4237
4702
  } : void 0
4238
4703
  });
4239
- (0, import_react16.useEffect)(
4704
+ const tooltipOpen = visible && tooltipContent !== null && isTooltipOpen(tooltipTrigger, hovered, focused, pinned);
4705
+ const cursorClaimId = hotspotCursorClaimId(id);
4706
+ (0, import_react19.useEffect)(
4240
4707
  () => () => {
4241
4708
  dragStateRef.current?.releaseInteractionLock();
4242
4709
  dragStateRef.current = null;
4243
4710
  },
4244
4711
  []
4245
4712
  );
4246
- (0, import_react16.useEffect)(() => () => focusGeometry.dispose(), [focusGeometry]);
4247
- (0, import_fiber13.useFrame)(({ camera }) => {
4713
+ (0, import_react19.useEffect)(() => {
4714
+ if (!cursorApi) {
4715
+ return;
4716
+ }
4717
+ if (interactive && visible && dragging) {
4718
+ cursorApi.claim(cursorClaimId, "hotspotDragging");
4719
+ return;
4720
+ }
4721
+ if (interactive && visible && hovered) {
4722
+ cursorApi.claim(cursorClaimId, "hotspot", cursor);
4723
+ return;
4724
+ }
4725
+ cursorApi.release(cursorClaimId);
4726
+ }, [
4727
+ cursor,
4728
+ cursorApi,
4729
+ cursorClaimId,
4730
+ dragging,
4731
+ hovered,
4732
+ interactive,
4733
+ visible
4734
+ ]);
4735
+ (0, import_react19.useEffect)(
4736
+ () => () => {
4737
+ cursorApi?.release(cursorClaimId);
4738
+ },
4739
+ [cursorApi, cursorClaimId]
4740
+ );
4741
+ (0, import_react19.useEffect)(() => () => focusGeometry.dispose(), [focusGeometry]);
4742
+ (0, import_react19.useEffect)(() => {
4743
+ if (tooltipTrigger !== "click") {
4744
+ setPinned(false);
4745
+ return;
4746
+ }
4747
+ return eventBus?.subscribe("click", () => {
4748
+ setPinned(false);
4749
+ });
4750
+ }, [eventBus, tooltipTrigger]);
4751
+ (0, import_react19.useEffect)(() => {
4752
+ if (tooltipTrigger !== "hover") {
4753
+ setHovered(false);
4754
+ }
4755
+ }, [tooltipTrigger]);
4756
+ (0, import_fiber14.useFrame)(({ camera }) => {
4248
4757
  if (!groupRef.current) {
4249
4758
  return;
4250
4759
  }
@@ -4263,6 +4772,14 @@ function HotspotAnchor({
4263
4772
  (useAngularScale ? worldHeight : 1) * scaleFactor,
4264
4773
  1
4265
4774
  );
4775
+ if (tooltipOpen && tooltipAnchorRef.current) {
4776
+ updateTooltipAnchor(
4777
+ tooltipAnchorRef.current,
4778
+ groupRef.current,
4779
+ tooltipPlacement,
4780
+ useAngularScale
4781
+ );
4782
+ }
4266
4783
  });
4267
4784
  const stopPointerEvent = (event) => {
4268
4785
  event.stopPropagation();
@@ -4289,8 +4806,12 @@ function HotspotAnchor({
4289
4806
  suppressNextClickRef.current = dragState.moved;
4290
4807
  dragState.releaseInteractionLock();
4291
4808
  dragStateRef.current = null;
4809
+ setDragging(false);
4810
+ if (emitDragEnd) {
4811
+ setHovered(true);
4812
+ }
4292
4813
  };
4293
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4814
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4294
4815
  "group",
4295
4816
  {
4296
4817
  ref: groupRef,
@@ -4301,6 +4822,9 @@ function HotspotAnchor({
4301
4822
  onClick: interactive ? (event) => {
4302
4823
  stopPointerEvent(event);
4303
4824
  if (!suppressNextClickRef.current) {
4825
+ if (tooltipTrigger === "click") {
4826
+ setPinned((current) => !current);
4827
+ }
4304
4828
  onClick?.(makeInteractionEvent(id, normalizedPosition, event));
4305
4829
  }
4306
4830
  suppressNextClickRef.current = false;
@@ -4322,6 +4846,8 @@ function HotspotAnchor({
4322
4846
  releaseInteractionLock,
4323
4847
  moved: false
4324
4848
  };
4849
+ setHovered(false);
4850
+ setDragging(true);
4325
4851
  onDragStart?.({
4326
4852
  ...makeInteractionEvent(id, startPosition, event),
4327
4853
  source: "pointer",
@@ -4344,6 +4870,7 @@ function HotspotAnchor({
4344
4870
  } : void 0,
4345
4871
  onPointerOut: interactive ? (event) => {
4346
4872
  if (!dragStateRef.current) {
4873
+ setHovered(false);
4347
4874
  onHoverChange?.(
4348
4875
  false,
4349
4876
  makeInteractionEvent(id, normalizedPosition, event)
@@ -4352,6 +4879,7 @@ function HotspotAnchor({
4352
4879
  } : void 0,
4353
4880
  onPointerOver: interactive ? (event) => {
4354
4881
  if (!dragStateRef.current) {
4882
+ setHovered(true);
4355
4883
  onHoverChange?.(
4356
4884
  true,
4357
4885
  makeInteractionEvent(id, normalizedPosition, event)
@@ -4360,38 +4888,161 @@ function HotspotAnchor({
4360
4888
  } : void 0,
4361
4889
  onPointerUp: interactive ? (event) => releasePointer(event, true) : void 0,
4362
4890
  children: [
4363
- focused ? focusContent ?? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4891
+ focused ? focusContent ?? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4364
4892
  "lineSegments",
4365
4893
  {
4366
4894
  geometry: focusGeometry,
4367
4895
  position: [0, 0, 0.01],
4368
4896
  renderOrder: renderOrder + 1,
4369
4897
  scale: [1.12, 1.12, 1],
4370
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("lineBasicMaterial", { color: "#f5fbfc", depthTest: false })
4898
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("lineBasicMaterial", { color: "#f5fbfc", depthTest: false })
4371
4899
  }
4372
4900
  ) : null,
4373
- children
4901
+ children,
4902
+ tooltipOpen && tooltipContent ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("group", { ref: tooltipAnchorRef, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4903
+ HotspotTooltip,
4904
+ {
4905
+ content: tooltipContent,
4906
+ offset: resolveHotspotTooltipOffset(tooltipOffset),
4907
+ placement: tooltipPlacement
4908
+ }
4909
+ ) }) : null
4374
4910
  ]
4375
4911
  }
4376
4912
  );
4377
4913
  }
4378
4914
 
4379
- // src/hotspot/image-hotspot.tsx
4380
- var import_jsx_runtime10 = require("react/jsx-runtime");
4915
+ // src/hotspot/hotspot-plane.tsx
4916
+ var import_three10 = require("three");
4917
+ var import_jsx_runtime12 = require("react/jsx-runtime");
4918
+ var HOTSPOT_ALPHA_CUTOFF = 0.05;
4919
+ var SAMPLE_UV = new import_three10.Vector2();
4920
+ var RAYCAST_HITS = [];
4921
+ var IMAGE_DATA_CACHE = /* @__PURE__ */ new WeakMap();
4381
4922
  function clampOpacity(opacity) {
4382
4923
  if (!Number.isFinite(opacity)) {
4383
4924
  return 1;
4384
4925
  }
4385
4926
  return Math.max(0, Math.min(opacity, 1));
4386
4927
  }
4928
+ function isSampleableImage(image) {
4929
+ if (image instanceof HTMLImageElement) {
4930
+ return image.naturalWidth > 0 && image.naturalHeight > 0;
4931
+ }
4932
+ if (image instanceof HTMLCanvasElement) {
4933
+ return image.width > 0 && image.height > 0;
4934
+ }
4935
+ if (typeof OffscreenCanvas !== "undefined" && image instanceof OffscreenCanvas) {
4936
+ return image.width > 0 && image.height > 0;
4937
+ }
4938
+ if (typeof ImageBitmap !== "undefined" && image instanceof ImageBitmap) {
4939
+ return image.width > 0 && image.height > 0;
4940
+ }
4941
+ return false;
4942
+ }
4943
+ function imageSize(image) {
4944
+ if (image instanceof HTMLImageElement) {
4945
+ return { width: image.naturalWidth, height: image.naturalHeight };
4946
+ }
4947
+ return { width: image.width, height: image.height };
4948
+ }
4949
+ function readImageData(image) {
4950
+ const cached = IMAGE_DATA_CACHE.get(image);
4951
+ if (cached) {
4952
+ return cached;
4953
+ }
4954
+ const { width, height } = imageSize(image);
4955
+ const canvas = document.createElement("canvas");
4956
+ canvas.width = width;
4957
+ canvas.height = height;
4958
+ const context = canvas.getContext("2d", { willReadFrequently: true });
4959
+ if (!context) {
4960
+ return null;
4961
+ }
4962
+ try {
4963
+ context.drawImage(image, 0, 0);
4964
+ const data = context.getImageData(0, 0, width, height);
4965
+ IMAGE_DATA_CACHE.set(image, data);
4966
+ return data;
4967
+ } catch {
4968
+ return null;
4969
+ }
4970
+ }
4971
+ function sampleTextureAlpha(texture, u, v) {
4972
+ if (!isSampleableImage(texture.image)) {
4973
+ return null;
4974
+ }
4975
+ const data = readImageData(texture.image);
4976
+ if (!data) {
4977
+ return null;
4978
+ }
4979
+ SAMPLE_UV.set(u, v);
4980
+ texture.transformUv(SAMPLE_UV);
4981
+ const pixelX = Math.min(
4982
+ data.width - 1,
4983
+ Math.max(0, Math.floor(SAMPLE_UV.x * data.width))
4984
+ );
4985
+ const pixelY = Math.min(
4986
+ data.height - 1,
4987
+ Math.max(0, Math.floor(SAMPLE_UV.y * data.height))
4988
+ );
4989
+ return data.data[(pixelY * data.width + pixelX) * 4 + 3] / 255;
4990
+ }
4991
+ function hotspotPlaneRaycast(raycaster, intersects2) {
4992
+ const material = Array.isArray(this.material) ? this.material[0] : this.material;
4993
+ if (!(material instanceof import_three10.MeshBasicMaterial)) {
4994
+ import_three10.Mesh.prototype.raycast.call(this, raycaster, intersects2);
4995
+ return;
4996
+ }
4997
+ const cutoff = Math.max(material.alphaTest, HOTSPOT_ALPHA_CUTOFF);
4998
+ if (material.opacity < cutoff) {
4999
+ return;
5000
+ }
5001
+ RAYCAST_HITS.length = 0;
5002
+ import_three10.Mesh.prototype.raycast.call(this, raycaster, RAYCAST_HITS);
5003
+ const map = material.map;
5004
+ for (const hit of RAYCAST_HITS) {
5005
+ if (!map || !hit.uv) {
5006
+ intersects2.push(hit);
5007
+ continue;
5008
+ }
5009
+ const texelAlpha = sampleTextureAlpha(map, hit.uv.x, hit.uv.y);
5010
+ if (texelAlpha === null || texelAlpha * material.opacity >= cutoff) {
5011
+ intersects2.push(hit);
5012
+ }
5013
+ }
5014
+ }
5015
+ function HotspotPlane({
5016
+ map,
5017
+ opacity
5018
+ }) {
5019
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("mesh", { raycast: hotspotPlaneRaycast, children: [
5020
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("planeGeometry", { args: [1, 1] }),
5021
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5022
+ "meshBasicMaterial",
5023
+ {
5024
+ alphaTest: HOTSPOT_ALPHA_CUTOFF,
5025
+ alphaToCoverage: true,
5026
+ map,
5027
+ opacity: map ? clampOpacity(opacity) : 0,
5028
+ side: import_three10.DoubleSide,
5029
+ toneMapped: false,
5030
+ transparent: true
5031
+ }
5032
+ )
5033
+ ] });
5034
+ }
5035
+
5036
+ // src/hotspot/image-hotspot.tsx
5037
+ var import_jsx_runtime13 = require("react/jsx-runtime");
4387
5038
  function useHotspotImageTexture(src, onLoad, onError) {
4388
- const gl = (0, import_fiber14.useThree)((state) => state.gl);
4389
- const [texture, setTexture] = (0, import_react17.useState)(null);
4390
- const onLoadRef = (0, import_react17.useRef)(onLoad);
4391
- const onErrorRef = (0, import_react17.useRef)(onError);
5039
+ const gl = (0, import_fiber15.useThree)((state) => state.gl);
5040
+ const [texture, setTexture] = (0, import_react20.useState)(null);
5041
+ const onLoadRef = (0, import_react20.useRef)(onLoad);
5042
+ const onErrorRef = (0, import_react20.useRef)(onError);
4392
5043
  onLoadRef.current = onLoad;
4393
5044
  onErrorRef.current = onError;
4394
- (0, import_react17.useEffect)(() => {
5045
+ (0, import_react20.useEffect)(() => {
4395
5046
  let active = true;
4396
5047
  setTexture(null);
4397
5048
  if (!src) {
@@ -4399,7 +5050,7 @@ function useHotspotImageTexture(src, onLoad, onError) {
4399
5050
  active = false;
4400
5051
  };
4401
5052
  }
4402
- const loader = new import_three10.TextureLoader();
5053
+ const loader = new import_three11.TextureLoader();
4403
5054
  loader.load(
4404
5055
  src,
4405
5056
  (loadedTexture) => {
@@ -4407,11 +5058,11 @@ function useHotspotImageTexture(src, onLoad, onError) {
4407
5058
  loadedTexture.dispose();
4408
5059
  return;
4409
5060
  }
4410
- loadedTexture.colorSpace = import_three10.SRGBColorSpace;
4411
- loadedTexture.wrapS = import_three10.ClampToEdgeWrapping;
4412
- loadedTexture.wrapT = import_three10.ClampToEdgeWrapping;
4413
- loadedTexture.magFilter = import_three10.LinearFilter;
4414
- loadedTexture.minFilter = import_three10.LinearMipmapLinearFilter;
5061
+ loadedTexture.colorSpace = import_three11.SRGBColorSpace;
5062
+ loadedTexture.wrapS = import_three11.ClampToEdgeWrapping;
5063
+ loadedTexture.wrapT = import_three11.ClampToEdgeWrapping;
5064
+ loadedTexture.magFilter = import_three11.LinearFilter;
5065
+ loadedTexture.minFilter = import_three11.LinearMipmapLinearFilter;
4415
5066
  loadedTexture.generateMipmaps = true;
4416
5067
  loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
4417
5068
  loadedTexture.needsUpdate = true;
@@ -4429,7 +5080,7 @@ function useHotspotImageTexture(src, onLoad, onError) {
4429
5080
  active = false;
4430
5081
  };
4431
5082
  }, [gl, src]);
4432
- (0, import_react17.useEffect)(
5083
+ (0, import_react20.useEffect)(
4433
5084
  () => () => {
4434
5085
  texture?.dispose();
4435
5086
  },
@@ -4447,29 +5098,42 @@ function ImageHotspot({
4447
5098
  ...anchorProps
4448
5099
  }) {
4449
5100
  const texture = useHotspotImageTexture(src, onLoad, onError);
4450
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("mesh", { children: [
4451
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("planeGeometry", { args: [1, 1] }),
4452
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4453
- "meshBasicMaterial",
4454
- {
4455
- map: texture,
4456
- opacity: texture ? clampOpacity(opacity) : 0,
4457
- side: import_three10.DoubleSide,
4458
- toneMapped: false,
4459
- transparent: true
4460
- }
4461
- )
4462
- ] }) });
5101
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(HotspotPlane, { map: texture, opacity }) });
4463
5102
  }
4464
5103
 
4465
5104
  // src/hotspot/polygon-hotspot.tsx
4466
- var import_fiber15 = require("@react-three/fiber");
4467
- var import_react18 = require("react");
4468
- var import_three11 = require("three");
4469
- var import_Line2 = require("three/addons/lines/Line2.js");
4470
- var import_LineGeometry = require("three/addons/lines/LineGeometry.js");
4471
- var import_LineMaterial = require("three/addons/lines/LineMaterial.js");
4472
- var import_jsx_runtime11 = require("react/jsx-runtime");
5105
+ var import_react21 = require("react");
5106
+ var import_three12 = require("three");
5107
+
5108
+ // src/hotspot/hotspot-stroke-line.tsx
5109
+ var import_drei2 = require("@react-three/drei");
5110
+ var import_jsx_runtime14 = require("react/jsx-runtime");
5111
+ function HotspotStrokeLine({
5112
+ points,
5113
+ color,
5114
+ lineWidth,
5115
+ opacity,
5116
+ renderOrder
5117
+ }) {
5118
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5119
+ import_drei2.Line,
5120
+ {
5121
+ alphaToCoverage: true,
5122
+ color,
5123
+ depthTest: true,
5124
+ depthWrite: false,
5125
+ lineWidth: Math.max(0.5, lineWidth),
5126
+ opacity,
5127
+ points,
5128
+ renderOrder,
5129
+ transparent: true,
5130
+ worldUnits: false
5131
+ }
5132
+ );
5133
+ }
5134
+
5135
+ // src/hotspot/polygon-hotspot.tsx
5136
+ var import_jsx_runtime15 = require("react/jsx-runtime");
4473
5137
  var POLYGON_RADIUS = 990;
4474
5138
  var MAX_FILL_SEGMENT_DEGREES = 7;
4475
5139
  function cloneVertices(vertices) {
@@ -4504,7 +5168,7 @@ function intersects(a, b, c, d) {
4504
5168
  }
4505
5169
  function hasSelfIntersection(vertices) {
4506
5170
  const points = vertices.map(
4507
- (vertex) => new import_three11.Vector2(vertex.unwrappedYaw, vertex.pitch)
5171
+ (vertex) => new import_three12.Vector2(vertex.unwrappedYaw, vertex.pitch)
4508
5172
  );
4509
5173
  for (let index = 0; index < points.length; index += 1) {
4510
5174
  const nextIndex = (index + 1) % points.length;
@@ -4541,7 +5205,7 @@ function polygonContainsDirection(vertices, direction) {
4541
5205
  }
4542
5206
  function fitsHemisphere(vertices) {
4543
5207
  const directions = vertices.map((vertex) => panoPositionToVector3(vertex).normalize());
4544
- const candidates = [new import_three11.Vector3()];
5208
+ const candidates = [new import_three12.Vector3()];
4545
5209
  for (const direction of directions) {
4546
5210
  candidates[0].add(direction);
4547
5211
  candidates.push(direction.clone());
@@ -4565,7 +5229,7 @@ function validatePolygonVertices(vertices) {
4565
5229
  if (hasSelfIntersection(unwrapped)) {
4566
5230
  issues.push({ code: "self_intersection", message: "Polygon edges cannot cross." });
4567
5231
  }
4568
- if (polygonContainsDirection(normalized, new import_three11.Vector3(0, 1, 0)) || polygonContainsDirection(normalized, new import_three11.Vector3(0, -1, 0))) {
5232
+ if (polygonContainsDirection(normalized, new import_three12.Vector3(0, 1, 0)) || polygonContainsDirection(normalized, new import_three12.Vector3(0, -1, 0))) {
4569
5233
  issues.push({ code: "contains_pole", message: "Polygons cannot contain a panorama pole." });
4570
5234
  }
4571
5235
  if (!fitsHemisphere(normalized)) {
@@ -4576,7 +5240,7 @@ function validatePolygonVertices(vertices) {
4576
5240
  function polygonCenter(vertices) {
4577
5241
  const sum = vertices.reduce(
4578
5242
  (total, vertex) => total.add(panoPositionToVector3(vertex).normalize()),
4579
- new import_three11.Vector3()
5243
+ new import_three12.Vector3()
4580
5244
  );
4581
5245
  return sum.lengthSq() > 1e-8 ? vector3ToPanoPosition(sum) : normalizePanoPosition(vertices[0]);
4582
5246
  }
@@ -4585,20 +5249,20 @@ function surfaceQuaternion(position) {
4585
5249
  const inward = outward.clone().negate();
4586
5250
  const pitch = position.pitch * Math.PI / 180;
4587
5251
  const yaw = position.yaw * Math.PI / 180;
4588
- const localUp = new import_three11.Vector3(
5252
+ const localUp = new import_three12.Vector3(
4589
5253
  -Math.sin(pitch) * Math.sin(yaw),
4590
5254
  Math.cos(pitch),
4591
5255
  Math.sin(pitch) * Math.cos(yaw)
4592
5256
  ).normalize();
4593
- return new import_three11.Quaternion().setFromRotationMatrix(
4594
- new import_three11.Matrix4().makeBasis(localUp.clone().cross(inward).normalize(), localUp, inward)
5257
+ return new import_three12.Quaternion().setFromRotationMatrix(
5258
+ new import_three12.Matrix4().makeBasis(localUp.clone().cross(inward).normalize(), localUp, inward)
4595
5259
  );
4596
5260
  }
4597
5261
  function makeWorldToLocal(center) {
4598
- return new import_three11.Matrix4().compose(
5262
+ return new import_three12.Matrix4().compose(
4599
5263
  panoPositionToVector3(center, POLYGON_RADIUS),
4600
5264
  surfaceQuaternion(center),
4601
- new import_three11.Vector3(1, 1, 1)
5265
+ new import_three12.Vector3(1, 1, 1)
4602
5266
  ).invert();
4603
5267
  }
4604
5268
  function projectedPoint(a, b, c, weightB, weightC, radius, worldToLocal) {
@@ -4653,8 +5317,8 @@ function addSubdividedTriangle(positions, indices, a, b, c, worldToLocal, steps)
4653
5317
  }
4654
5318
  function makeFillGeometry(vertices, center) {
4655
5319
  const unwrapped = unwrapPolygonVertices(vertices);
4656
- const contour = unwrapped.map((vertex) => new import_three11.Vector2(vertex.unwrappedYaw, vertex.pitch));
4657
- const triangles = import_three11.ShapeUtils.triangulateShape(contour, []);
5320
+ const contour = unwrapped.map((vertex) => new import_three12.Vector2(vertex.unwrappedYaw, vertex.pitch));
5321
+ const triangles = import_three12.ShapeUtils.triangulateShape(contour, []);
4658
5322
  const positions = [];
4659
5323
  const indices = [];
4660
5324
  const worldToLocal = makeWorldToLocal(center);
@@ -4670,10 +5334,10 @@ function makeFillGeometry(vertices, center) {
4670
5334
  steps
4671
5335
  );
4672
5336
  }
4673
- return new import_three11.BufferGeometry().setAttribute("position", new import_three11.Float32BufferAttribute(positions, 3)).setIndex(indices);
5337
+ return new import_three12.BufferGeometry().setAttribute("position", new import_three12.Float32BufferAttribute(positions, 3)).setIndex(indices);
4674
5338
  }
4675
5339
  function makeStrokePositions(vertices, center) {
4676
- const positions = [];
5340
+ const points = [];
4677
5341
  const worldToLocal = makeWorldToLocal(center);
4678
5342
  const unwrapped = unwrapPolygonVertices(vertices);
4679
5343
  const steps = polygonSubdivisionSteps(vertices);
@@ -4689,33 +5353,14 @@ function makeStrokePositions(vertices, center) {
4689
5353
  },
4690
5354
  POLYGON_RADIUS
4691
5355
  ).applyMatrix4(worldToLocal);
4692
- positions.push(point.x, point.y, point.z);
5356
+ points.push([point.x, point.y, point.z]);
4693
5357
  }
4694
5358
  }
4695
- positions.push(...positions.slice(0, 3));
4696
- return positions;
4697
- }
4698
- function makeStrokeLine(positions, color, width, opacity) {
4699
- const geometry = new import_LineGeometry.LineGeometry();
4700
- geometry.setPositions(positions);
4701
- const material = new import_LineMaterial.LineMaterial({
4702
- color,
4703
- depthTest: true,
4704
- depthWrite: false,
4705
- linewidth: Math.max(0.5, width),
4706
- opacity,
4707
- transparent: true,
4708
- worldUnits: false,
4709
- alphaToCoverage: true
4710
- });
4711
- const line = new import_Line2.Line2(geometry, material);
4712
- line.computeLineDistances();
4713
- return line;
4714
- }
4715
- function disposeStrokeLine(line) {
4716
- if (!line) return;
4717
- line.geometry.dispose();
4718
- line.material.dispose();
5359
+ const first = points[0];
5360
+ if (first) {
5361
+ points.push(first);
5362
+ }
5363
+ return points;
4719
5364
  }
4720
5365
  function translateVertices(vertices, from, to) {
4721
5366
  const yawOffset = normalizePanoYaw(to.yaw - from.yaw);
@@ -4742,45 +5387,27 @@ function PolygonHotspot({
4742
5387
  onInvalid,
4743
5388
  ...anchorProps
4744
5389
  }) {
4745
- const gl = (0, import_fiber15.useThree)((state) => state.gl);
4746
- const size = (0, import_fiber15.useThree)((state) => state.size);
4747
- const issues = (0, import_react18.useMemo)(() => validatePolygonVertices(vertices), [vertices]);
5390
+ const issues = (0, import_react21.useMemo)(() => validatePolygonVertices(vertices), [vertices]);
4748
5391
  const valid = issues.length === 0;
4749
- const center = (0, import_react18.useMemo)(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
4750
- const fillGeometry = (0, import_react18.useMemo)(
5392
+ const center = (0, import_react21.useMemo)(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
5393
+ const fillGeometry = (0, import_react21.useMemo)(
4751
5394
  () => valid && center ? makeFillGeometry(vertices, center) : null,
4752
5395
  [center, valid, vertices]
4753
5396
  );
4754
- const strokePositions = (0, import_react18.useMemo)(
5397
+ const strokePoints = (0, import_react21.useMemo)(
4755
5398
  () => valid && center ? makeStrokePositions(vertices, center) : null,
4756
5399
  [center, valid, vertices]
4757
5400
  );
4758
5401
  const resolvedStrokeOpacity = Math.max(0, Math.min(strokeOpacity, 1));
4759
- const devicePixelRatio = gl.getPixelRatio();
4760
- const strokeLine = (0, import_react18.useMemo)(
4761
- () => strokePositions ? makeStrokeLine(strokePositions, stroke, strokeWidth * devicePixelRatio, resolvedStrokeOpacity) : null,
4762
- [devicePixelRatio, resolvedStrokeOpacity, stroke, strokePositions, strokeWidth]
4763
- );
4764
- const focusStrokeLine = (0, import_react18.useMemo)(
4765
- () => strokePositions ? makeStrokeLine(strokePositions, "#75cbd3", (strokeWidth + 2) * devicePixelRatio, 1) : null,
4766
- [devicePixelRatio, strokePositions, strokeWidth]
4767
- );
4768
- const startVerticesRef = (0, import_react18.useRef)(null);
5402
+ const startVerticesRef = (0, import_react21.useRef)(null);
4769
5403
  const issuesKey = issues.map((issue) => issue.code).join(",");
4770
- (0, import_react18.useEffect)(() => {
5404
+ (0, import_react21.useEffect)(() => {
4771
5405
  if (!valid) onInvalid?.(issues);
4772
5406
  }, [issues, issuesKey, onInvalid, valid]);
4773
- (0, import_react18.useEffect)(() => () => fillGeometry?.dispose(), [fillGeometry]);
4774
- (0, import_react18.useEffect)(() => () => disposeStrokeLine(strokeLine), [strokeLine]);
4775
- (0, import_react18.useEffect)(() => () => disposeStrokeLine(focusStrokeLine), [focusStrokeLine]);
4776
- (0, import_react18.useEffect)(() => {
4777
- const width = gl.domElement.width;
4778
- const height = gl.domElement.height;
4779
- for (const line of [strokeLine, focusStrokeLine]) {
4780
- if (line) line.material.resolution.set(width, height);
4781
- }
4782
- }, [focusStrokeLine, gl, size.height, size.width, strokeLine]);
4783
- if (!valid || !center || !fillGeometry || !strokeLine || !focusStrokeLine) return null;
5407
+ (0, import_react21.useEffect)(() => () => fillGeometry?.dispose(), [fillGeometry]);
5408
+ if (!valid || !center || !fillGeometry || !strokePoints || strokePoints.length < 2) {
5409
+ return null;
5410
+ }
4784
5411
  const emitVerticesChange = (event, callback) => {
4785
5412
  const startVertices = startVerticesRef.current ?? cloneVertices(vertices);
4786
5413
  const nextVertices = translateVertices(startVertices, event.startPosition, event.position);
@@ -4792,7 +5419,7 @@ function PolygonHotspot({
4792
5419
  startPosition: event.startPosition
4793
5420
  });
4794
5421
  };
4795
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
5422
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
4796
5423
  HotspotAnchor,
4797
5424
  {
4798
5425
  ...anchorProps,
@@ -4807,7 +5434,16 @@ function PolygonHotspot({
4807
5434
  draggable,
4808
5435
  interactive,
4809
5436
  useAngularScale: false,
4810
- focusContent: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("primitive", { object: focusStrokeLine, renderOrder: renderOrder + 2 }),
5437
+ focusContent: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5438
+ HotspotStrokeLine,
5439
+ {
5440
+ color: "#75cbd3",
5441
+ lineWidth: strokeWidth + 2,
5442
+ opacity: 1,
5443
+ points: strokePoints,
5444
+ renderOrder: renderOrder + 2
5445
+ }
5446
+ ),
4811
5447
  onDragStart: (event) => {
4812
5448
  startVerticesRef.current = cloneVertices(vertices);
4813
5449
  emitVerticesChange(event, onDragStart);
@@ -4818,30 +5454,35 @@ function PolygonHotspot({
4818
5454
  startVerticesRef.current = null;
4819
5455
  },
4820
5456
  children: [
4821
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("mesh", { geometry: fillGeometry, renderOrder, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5457
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("mesh", { geometry: fillGeometry, renderOrder, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
4822
5458
  "meshBasicMaterial",
4823
5459
  {
4824
5460
  color: fill,
4825
5461
  depthWrite: false,
4826
5462
  opacity: Math.max(0, Math.min(fillOpacity, 1)),
4827
- side: import_three11.DoubleSide,
5463
+ side: import_three12.DoubleSide,
4828
5464
  transparent: true
4829
5465
  }
4830
5466
  ) }),
4831
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("primitive", { object: strokeLine, renderOrder: renderOrder + 1 })
5467
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5468
+ HotspotStrokeLine,
5469
+ {
5470
+ color: stroke,
5471
+ lineWidth: strokeWidth,
5472
+ opacity: resolvedStrokeOpacity,
5473
+ points: strokePoints,
5474
+ renderOrder: renderOrder + 1
5475
+ }
5476
+ )
4832
5477
  ]
4833
5478
  }
4834
5479
  );
4835
5480
  }
4836
5481
 
4837
5482
  // src/hotspot/polyline-hotspot.tsx
4838
- var import_fiber16 = require("@react-three/fiber");
4839
- var import_react19 = require("react");
4840
- var import_three12 = require("three");
4841
- var import_Line22 = require("three/addons/lines/Line2.js");
4842
- var import_LineGeometry2 = require("three/addons/lines/LineGeometry.js");
4843
- var import_LineMaterial2 = require("three/addons/lines/LineMaterial.js");
4844
- var import_jsx_runtime12 = require("react/jsx-runtime");
5483
+ var import_react22 = require("react");
5484
+ var import_three13 = require("three");
5485
+ var import_jsx_runtime16 = require("react/jsx-runtime");
4845
5486
  var POLYLINE_RADIUS = 990;
4846
5487
  var MAX_SEGMENT_DEGREES = 7;
4847
5488
  function cloneVertices2(vertices) {
@@ -4866,29 +5507,29 @@ function unwrapVertices(vertices) {
4866
5507
  function polylineCenter(vertices) {
4867
5508
  const sum = vertices.reduce(
4868
5509
  (total, vertex) => total.add(panoPositionToVector3(vertex).normalize()),
4869
- new import_three12.Vector3()
5510
+ new import_three13.Vector3()
4870
5511
  );
4871
5512
  return sum.lengthSq() > 1e-8 ? vector3ToPanoPosition(sum) : normalizePanoPosition(vertices[0]);
4872
5513
  }
4873
5514
  function surfaceQuaternion2(position) {
4874
5515
  const outward = panoPositionToVector3(position).normalize();
4875
5516
  const inward = outward.clone().negate();
4876
- const pitch = import_three12.MathUtils.degToRad(position.pitch);
4877
- const yaw = import_three12.MathUtils.degToRad(position.yaw);
4878
- const localUp = new import_three12.Vector3(
5517
+ const pitch = import_three13.MathUtils.degToRad(position.pitch);
5518
+ const yaw = import_three13.MathUtils.degToRad(position.yaw);
5519
+ const localUp = new import_three13.Vector3(
4879
5520
  -Math.sin(pitch) * Math.sin(yaw),
4880
5521
  Math.cos(pitch),
4881
5522
  Math.sin(pitch) * Math.cos(yaw)
4882
5523
  ).normalize();
4883
- return new import_three12.Quaternion().setFromRotationMatrix(
4884
- new import_three12.Matrix4().makeBasis(localUp.clone().cross(inward).normalize(), localUp, inward)
5524
+ return new import_three13.Quaternion().setFromRotationMatrix(
5525
+ new import_three13.Matrix4().makeBasis(localUp.clone().cross(inward).normalize(), localUp, inward)
4885
5526
  );
4886
5527
  }
4887
5528
  function makeWorldToLocal2(center) {
4888
- return new import_three12.Matrix4().compose(
5529
+ return new import_three13.Matrix4().compose(
4889
5530
  panoPositionToVector3(center, POLYLINE_RADIUS),
4890
5531
  surfaceQuaternion2(center),
4891
- new import_three12.Vector3(1, 1, 1)
5532
+ new import_three13.Vector3(1, 1, 1)
4892
5533
  ).invert();
4893
5534
  }
4894
5535
  function subdivisionSteps(vertices) {
@@ -4903,11 +5544,11 @@ function subdivisionSteps(vertices) {
4903
5544
  }
4904
5545
  return Math.min(
4905
5546
  8,
4906
- Math.max(1, Math.ceil(import_three12.MathUtils.radToDeg(maxAngle) / MAX_SEGMENT_DEGREES))
5547
+ Math.max(1, Math.ceil(import_three13.MathUtils.radToDeg(maxAngle) / MAX_SEGMENT_DEGREES))
4907
5548
  );
4908
5549
  }
4909
5550
  function makeLinePositions(vertices, center) {
4910
- const positions = [];
5551
+ const points = [];
4911
5552
  const worldToLocal = makeWorldToLocal2(center);
4912
5553
  const unwrapped = unwrapVertices(vertices);
4913
5554
  const steps = subdivisionSteps(vertices);
@@ -4923,35 +5564,13 @@ function makeLinePositions(vertices, center) {
4923
5564
  },
4924
5565
  POLYLINE_RADIUS
4925
5566
  ).applyMatrix4(worldToLocal);
4926
- positions.push(point.x, point.y, point.z);
5567
+ points.push([point.x, point.y, point.z]);
4927
5568
  }
4928
5569
  }
4929
5570
  const last = unwrapped[unwrapped.length - 1];
4930
5571
  const lastPoint = panoPositionToVector3(last, POLYLINE_RADIUS).applyMatrix4(worldToLocal);
4931
- positions.push(lastPoint.x, lastPoint.y, lastPoint.z);
4932
- return positions;
4933
- }
4934
- function makeLine(positions, color, width, opacity) {
4935
- const geometry = new import_LineGeometry2.LineGeometry();
4936
- geometry.setPositions(positions);
4937
- const material = new import_LineMaterial2.LineMaterial({
4938
- color,
4939
- depthTest: true,
4940
- depthWrite: false,
4941
- linewidth: Math.max(0.5, width),
4942
- opacity,
4943
- transparent: true,
4944
- worldUnits: false,
4945
- alphaToCoverage: true
4946
- });
4947
- const line = new import_Line22.Line2(geometry, material);
4948
- line.computeLineDistances();
4949
- return line;
4950
- }
4951
- function disposeLine(line) {
4952
- if (!line) return;
4953
- line.geometry.dispose();
4954
- line.material.dispose();
5572
+ points.push([lastPoint.x, lastPoint.y, lastPoint.z]);
5573
+ return points;
4955
5574
  }
4956
5575
  function translateVertices2(vertices, from, to) {
4957
5576
  const yawOffset = normalizePanoYaw(to.yaw - from.yaw);
@@ -4978,39 +5597,19 @@ function PolylineHotspot({
4978
5597
  onInvalid,
4979
5598
  ...anchorProps
4980
5599
  }) {
4981
- const gl = (0, import_fiber16.useThree)((state) => state.gl);
4982
- const size = (0, import_fiber16.useThree)((state) => state.size);
4983
- const issues = (0, import_react19.useMemo)(() => validatePolylineVertices(vertices), [vertices]);
5600
+ const issues = (0, import_react22.useMemo)(() => validatePolylineVertices(vertices), [vertices]);
4984
5601
  const valid = issues.length === 0;
4985
- const center = (0, import_react19.useMemo)(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
4986
- const linePositions = (0, import_react19.useMemo)(
5602
+ const center = (0, import_react22.useMemo)(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
5603
+ const linePoints = (0, import_react22.useMemo)(
4987
5604
  () => valid && center ? makeLinePositions(vertices, center) : null,
4988
5605
  [center, valid, vertices]
4989
5606
  );
4990
- const devicePixelRatio = gl.getPixelRatio();
4991
5607
  const opacity = Math.max(0, Math.min(strokeOpacity, 1));
4992
- const line = (0, import_react19.useMemo)(
4993
- () => linePositions ? makeLine(linePositions, stroke, strokeWidth * devicePixelRatio, opacity) : null,
4994
- [devicePixelRatio, linePositions, opacity, stroke, strokeWidth]
4995
- );
4996
- const focusLine = (0, import_react19.useMemo)(
4997
- () => linePositions ? makeLine(linePositions, "#75cbd3", (strokeWidth + 2) * devicePixelRatio, 1) : null,
4998
- [devicePixelRatio, linePositions, strokeWidth]
4999
- );
5000
- const startVerticesRef = (0, import_react19.useRef)(null);
5001
- (0, import_react19.useEffect)(() => {
5608
+ const startVerticesRef = (0, import_react22.useRef)(null);
5609
+ (0, import_react22.useEffect)(() => {
5002
5610
  if (!valid) onInvalid?.(issues);
5003
5611
  }, [issues, onInvalid, valid]);
5004
- (0, import_react19.useEffect)(() => () => disposeLine(line), [line]);
5005
- (0, import_react19.useEffect)(() => () => disposeLine(focusLine), [focusLine]);
5006
- (0, import_react19.useEffect)(() => {
5007
- const width = gl.domElement.width;
5008
- const height = gl.domElement.height;
5009
- for (const nextLine of [line, focusLine]) {
5010
- if (nextLine) nextLine.material.resolution.set(width, height);
5011
- }
5012
- }, [focusLine, gl, line, size.height, size.width]);
5013
- if (!valid || !center || !line || !focusLine) return null;
5612
+ if (!valid || !center || !linePoints || linePoints.length < 2) return null;
5014
5613
  const emitVerticesChange = (event, callback) => {
5015
5614
  const startVertices = startVerticesRef.current ?? cloneVertices2(vertices);
5016
5615
  callback?.({
@@ -5021,7 +5620,7 @@ function PolylineHotspot({
5021
5620
  startPosition: event.startPosition
5022
5621
  });
5023
5622
  };
5024
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5623
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5025
5624
  HotspotAnchor,
5026
5625
  {
5027
5626
  ...anchorProps,
@@ -5035,7 +5634,16 @@ function PolylineHotspot({
5035
5634
  renderOrder,
5036
5635
  draggable,
5037
5636
  useAngularScale: false,
5038
- focusContent: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("primitive", { object: focusLine, renderOrder: renderOrder + 2 }),
5637
+ focusContent: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5638
+ HotspotStrokeLine,
5639
+ {
5640
+ color: "#75cbd3",
5641
+ lineWidth: strokeWidth + 2,
5642
+ opacity: 1,
5643
+ points: linePoints,
5644
+ renderOrder: renderOrder + 2
5645
+ }
5646
+ ),
5039
5647
  onDragStart: (event) => {
5040
5648
  startVerticesRef.current = cloneVertices2(vertices);
5041
5649
  emitVerticesChange(event, onDragStart);
@@ -5045,22 +5653,25 @@ function PolylineHotspot({
5045
5653
  emitVerticesChange(event, onDragEnd);
5046
5654
  startVerticesRef.current = null;
5047
5655
  },
5048
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("primitive", { object: line, renderOrder: renderOrder + 1 })
5656
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5657
+ HotspotStrokeLine,
5658
+ {
5659
+ color: stroke,
5660
+ lineWidth: strokeWidth,
5661
+ opacity,
5662
+ points: linePoints,
5663
+ renderOrder: renderOrder + 1
5664
+ }
5665
+ )
5049
5666
  }
5050
5667
  );
5051
5668
  }
5052
5669
 
5053
5670
  // src/hotspot/graphic-hotspot.tsx
5054
- var import_react20 = require("react");
5055
- var import_three13 = require("three");
5056
- var import_jsx_runtime13 = require("react/jsx-runtime");
5671
+ var import_react23 = require("react");
5672
+ var import_three14 = require("three");
5673
+ var import_jsx_runtime17 = require("react/jsx-runtime");
5057
5674
  var GRAPHIC_TEXTURE_SIZE = 512;
5058
- function clampOpacity2(opacity) {
5059
- if (!Number.isFinite(opacity)) {
5060
- return 1;
5061
- }
5062
- return Math.max(0, Math.min(opacity, 1));
5063
- }
5064
5675
  function resolveStrokeWidth(value) {
5065
5676
  return Number.isFinite(value) ? Math.max(value, 0) : 8;
5066
5677
  }
@@ -5237,23 +5848,23 @@ function createGraphicTexture(graphic, aspectWidth, aspectHeight) {
5237
5848
  throw new Error("Canvas 2D is unavailable for this graphic hotspot.");
5238
5849
  }
5239
5850
  drawGraphic(context, graphic, aspectWidth, aspectHeight);
5240
- const texture = new import_three13.CanvasTexture(canvas);
5241
- texture.colorSpace = import_three13.SRGBColorSpace;
5242
- texture.wrapS = import_three13.ClampToEdgeWrapping;
5243
- texture.wrapT = import_three13.ClampToEdgeWrapping;
5244
- texture.magFilter = import_three13.LinearFilter;
5245
- texture.minFilter = import_three13.LinearMipmapLinearFilter;
5851
+ const texture = new import_three14.CanvasTexture(canvas);
5852
+ texture.colorSpace = import_three14.SRGBColorSpace;
5853
+ texture.wrapS = import_three14.ClampToEdgeWrapping;
5854
+ texture.wrapT = import_three14.ClampToEdgeWrapping;
5855
+ texture.magFilter = import_three14.LinearFilter;
5856
+ texture.minFilter = import_three14.LinearMipmapLinearFilter;
5246
5857
  texture.generateMipmaps = true;
5247
5858
  texture.needsUpdate = true;
5248
5859
  return texture;
5249
5860
  }
5250
5861
  function useGraphicTexture(graphic, aspectWidth, aspectHeight, onLoad, onError) {
5251
- const [texture, setTexture] = (0, import_react20.useState)(null);
5252
- const onLoadRef = (0, import_react20.useRef)(onLoad);
5253
- const onErrorRef = (0, import_react20.useRef)(onError);
5862
+ const [texture, setTexture] = (0, import_react23.useState)(null);
5863
+ const onLoadRef = (0, import_react23.useRef)(onLoad);
5864
+ const onErrorRef = (0, import_react23.useRef)(onError);
5254
5865
  onLoadRef.current = onLoad;
5255
5866
  onErrorRef.current = onError;
5256
- (0, import_react20.useEffect)(() => {
5867
+ (0, import_react23.useEffect)(() => {
5257
5868
  let nextTexture = null;
5258
5869
  try {
5259
5870
  nextTexture = createGraphicTexture(graphic, aspectWidth, aspectHeight);
@@ -5279,41 +5890,23 @@ function CanvasGraphicHotspot({
5279
5890
  ...anchorProps
5280
5891
  }) {
5281
5892
  const texture = useGraphicTexture(graphic, width, height, onLoad, onError);
5282
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("mesh", { children: [
5283
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("planeGeometry", { args: [1, 1] }),
5284
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5285
- "meshBasicMaterial",
5286
- {
5287
- map: texture,
5288
- opacity: texture ? clampOpacity2(opacity) : 0,
5289
- side: import_three13.DoubleSide,
5290
- toneMapped: false,
5291
- transparent: true
5292
- }
5293
- )
5294
- ] }) });
5893
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(HotspotPlane, { map: texture, opacity }) });
5295
5894
  }
5296
5895
  function GraphicHotspot(props) {
5297
5896
  const { graphic, ...restProps } = props;
5298
5897
  if (graphic.kind === "svg") {
5299
5898
  const imageProps = restProps;
5300
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ImageHotspot, { ...imageProps, src: graphic.src });
5899
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ImageHotspot, { ...imageProps, src: graphic.src });
5301
5900
  }
5302
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CanvasGraphicHotspot, { ...restProps, graphic });
5901
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CanvasGraphicHotspot, { ...restProps, graphic });
5303
5902
  }
5304
5903
 
5305
5904
  // src/hotspot/sequence-hotspot.tsx
5306
- var import_fiber17 = require("@react-three/fiber");
5307
- var import_react21 = require("react");
5308
- var import_three14 = require("three");
5309
- var import_jsx_runtime14 = require("react/jsx-runtime");
5905
+ var import_fiber16 = require("@react-three/fiber");
5906
+ var import_react24 = require("react");
5907
+ var import_three15 = require("three");
5908
+ var import_jsx_runtime18 = require("react/jsx-runtime");
5310
5909
  var DEFAULT_SEQUENCE_FPS = 12;
5311
- function clampOpacity3(opacity) {
5312
- if (!Number.isFinite(opacity)) {
5313
- return 1;
5314
- }
5315
- return Math.max(0, Math.min(opacity, 1));
5316
- }
5317
5910
  function resolveFps(value) {
5318
5911
  return Number.isFinite(value) && value > 0 ? value : DEFAULT_SEQUENCE_FPS;
5319
5912
  }
@@ -5326,13 +5919,13 @@ function useSpriteTexture({
5326
5919
  onError,
5327
5920
  onLoadProgress
5328
5921
  }) {
5329
- const gl = (0, import_fiber17.useThree)((state) => state.gl);
5330
- const [texture, setTexture] = (0, import_react21.useState)(null);
5331
- const onErrorRef = (0, import_react21.useRef)(onError);
5332
- const onLoadProgressRef = (0, import_react21.useRef)(onLoadProgress);
5922
+ const gl = (0, import_fiber16.useThree)((state) => state.gl);
5923
+ const [texture, setTexture] = (0, import_react24.useState)(null);
5924
+ const onErrorRef = (0, import_react24.useRef)(onError);
5925
+ const onLoadProgressRef = (0, import_react24.useRef)(onLoadProgress);
5333
5926
  onErrorRef.current = onError;
5334
5927
  onLoadProgressRef.current = onLoadProgress;
5335
- (0, import_react21.useEffect)(() => {
5928
+ (0, import_react24.useEffect)(() => {
5336
5929
  let active = true;
5337
5930
  setTexture(null);
5338
5931
  if (!src) {
@@ -5341,7 +5934,7 @@ function useSpriteTexture({
5341
5934
  active = false;
5342
5935
  };
5343
5936
  }
5344
- const loader = new import_three14.TextureLoader();
5937
+ const loader = new import_three15.TextureLoader();
5345
5938
  loader.load(
5346
5939
  src,
5347
5940
  (loadedTexture) => {
@@ -5349,11 +5942,11 @@ function useSpriteTexture({
5349
5942
  loadedTexture.dispose();
5350
5943
  return;
5351
5944
  }
5352
- loadedTexture.colorSpace = import_three14.SRGBColorSpace;
5353
- loadedTexture.wrapS = import_three14.ClampToEdgeWrapping;
5354
- loadedTexture.wrapT = import_three14.ClampToEdgeWrapping;
5355
- loadedTexture.magFilter = import_three14.LinearFilter;
5356
- loadedTexture.minFilter = import_three14.LinearMipmapLinearFilter;
5945
+ loadedTexture.colorSpace = import_three15.SRGBColorSpace;
5946
+ loadedTexture.wrapS = import_three15.ClampToEdgeWrapping;
5947
+ loadedTexture.wrapT = import_three15.ClampToEdgeWrapping;
5948
+ loadedTexture.magFilter = import_three15.LinearFilter;
5949
+ loadedTexture.minFilter = import_three15.LinearMipmapLinearFilter;
5357
5950
  loadedTexture.generateMipmaps = true;
5358
5951
  loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
5359
5952
  loadedTexture.needsUpdate = true;
@@ -5373,7 +5966,7 @@ function useSpriteTexture({
5373
5966
  active = false;
5374
5967
  };
5375
5968
  }, [gl, id, src]);
5376
- (0, import_react21.useEffect)(
5969
+ (0, import_react24.useEffect)(
5377
5970
  () => () => {
5378
5971
  texture?.dispose();
5379
5972
  },
@@ -5402,24 +5995,24 @@ function SequenceHotspot({
5402
5995
  const texture = useSpriteTexture({ id, src, onError, onLoadProgress });
5403
5996
  const frameCount = resolveFrameCount(requestedFrameCount);
5404
5997
  const safeFps = resolveFps(fps);
5405
- const [frameIndex, setFrameIndex] = (0, import_react21.useState)(0);
5406
- const elapsedRef = (0, import_react21.useRef)(0);
5407
- const frameIndexRef = (0, import_react21.useRef)(0);
5408
- const endedRef = (0, import_react21.useRef)(false);
5409
- const lastPlaybackStateRef = (0, import_react21.useRef)(null);
5410
- const onFrameChangeRef = (0, import_react21.useRef)(onFrameChange);
5411
- const onPlaybackStateChangeRef = (0, import_react21.useRef)(onPlaybackStateChange);
5412
- const onEndedRef = (0, import_react21.useRef)(onEnded);
5998
+ const [frameIndex, setFrameIndex] = (0, import_react24.useState)(0);
5999
+ const elapsedRef = (0, import_react24.useRef)(0);
6000
+ const frameIndexRef = (0, import_react24.useRef)(0);
6001
+ const endedRef = (0, import_react24.useRef)(false);
6002
+ const lastPlaybackStateRef = (0, import_react24.useRef)(null);
6003
+ const onFrameChangeRef = (0, import_react24.useRef)(onFrameChange);
6004
+ const onPlaybackStateChangeRef = (0, import_react24.useRef)(onPlaybackStateChange);
6005
+ const onEndedRef = (0, import_react24.useRef)(onEnded);
5413
6006
  onFrameChangeRef.current = onFrameChange;
5414
6007
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
5415
6008
  onEndedRef.current = onEnded;
5416
- (0, import_react21.useEffect)(() => {
6009
+ (0, import_react24.useEffect)(() => {
5417
6010
  elapsedRef.current = 0;
5418
6011
  frameIndexRef.current = 0;
5419
6012
  endedRef.current = false;
5420
6013
  setFrameIndex(0);
5421
6014
  }, [frameCount, frameDirection, src]);
5422
- (0, import_react21.useEffect)(() => {
6015
+ (0, import_react24.useEffect)(() => {
5423
6016
  if (!texture) {
5424
6017
  return;
5425
6018
  }
@@ -5433,13 +6026,13 @@ function SequenceHotspot({
5433
6026
  );
5434
6027
  texture.needsUpdate = true;
5435
6028
  }, [frameCount, frameDirection, frameIndex, texture]);
5436
- (0, import_react21.useEffect)(() => {
6029
+ (0, import_react24.useEffect)(() => {
5437
6030
  if (!texture) {
5438
6031
  return;
5439
6032
  }
5440
6033
  onFrameChangeRef.current?.({ id, index: 0, src });
5441
6034
  }, [id, src, texture]);
5442
- (0, import_react21.useEffect)(() => {
6035
+ (0, import_react24.useEffect)(() => {
5443
6036
  if (!texture) {
5444
6037
  return;
5445
6038
  }
@@ -5458,7 +6051,7 @@ function SequenceHotspot({
5458
6051
  onPlaybackStateChangeRef.current?.(nextState);
5459
6052
  }
5460
6053
  }, [playing, texture]);
5461
- (0, import_fiber17.useFrame)((_, deltaSeconds) => {
6054
+ (0, import_fiber16.useFrame)((_, deltaSeconds) => {
5462
6055
  if (!texture || !playing || endedRef.current) {
5463
6056
  return;
5464
6057
  }
@@ -5488,32 +6081,14 @@ function SequenceHotspot({
5488
6081
  onFrameChangeRef.current?.({ id, index: nextIndex, src });
5489
6082
  }
5490
6083
  });
5491
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("mesh", { children: [
5492
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("planeGeometry", { args: [1, 1] }),
5493
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5494
- "meshBasicMaterial",
5495
- {
5496
- map: texture,
5497
- opacity: texture ? clampOpacity3(opacity) : 0,
5498
- side: import_three14.DoubleSide,
5499
- toneMapped: false,
5500
- transparent: true
5501
- }
5502
- )
5503
- ] }) });
6084
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(HotspotPlane, { map: texture, opacity }) });
5504
6085
  }
5505
6086
 
5506
6087
  // src/hotspot/video-hotspot.tsx
5507
- var import_fiber18 = require("@react-three/fiber");
5508
- var import_react22 = require("react");
5509
- var import_three15 = require("three");
5510
- var import_jsx_runtime15 = require("react/jsx-runtime");
5511
- function clampOpacity4(opacity) {
5512
- if (!Number.isFinite(opacity)) {
5513
- return 1;
5514
- }
5515
- return Math.max(0, Math.min(opacity, 1));
5516
- }
6088
+ var import_fiber17 = require("@react-three/fiber");
6089
+ var import_react25 = require("react");
6090
+ var import_three16 = require("three");
6091
+ var import_jsx_runtime19 = require("react/jsx-runtime");
5517
6092
  function clampVolume(volume) {
5518
6093
  if (!Number.isFinite(volume)) {
5519
6094
  return 1;
@@ -5521,11 +6096,11 @@ function clampVolume(volume) {
5521
6096
  return Math.max(0, Math.min(volume, 1));
5522
6097
  }
5523
6098
  function usePosterTexture(id, poster, onError) {
5524
- const gl = (0, import_fiber18.useThree)((state) => state.gl);
5525
- const [texture, setTexture] = (0, import_react22.useState)(null);
5526
- const onErrorRef = (0, import_react22.useRef)(onError);
6099
+ const gl = (0, import_fiber17.useThree)((state) => state.gl);
6100
+ const [texture, setTexture] = (0, import_react25.useState)(null);
6101
+ const onErrorRef = (0, import_react25.useRef)(onError);
5527
6102
  onErrorRef.current = onError;
5528
- (0, import_react22.useEffect)(() => {
6103
+ (0, import_react25.useEffect)(() => {
5529
6104
  let active = true;
5530
6105
  setTexture(null);
5531
6106
  if (!poster) {
@@ -5533,7 +6108,7 @@ function usePosterTexture(id, poster, onError) {
5533
6108
  active = false;
5534
6109
  };
5535
6110
  }
5536
- const loader = new import_three15.TextureLoader();
6111
+ const loader = new import_three16.TextureLoader();
5537
6112
  loader.load(
5538
6113
  poster,
5539
6114
  (loadedTexture) => {
@@ -5541,11 +6116,11 @@ function usePosterTexture(id, poster, onError) {
5541
6116
  loadedTexture.dispose();
5542
6117
  return;
5543
6118
  }
5544
- loadedTexture.colorSpace = import_three15.SRGBColorSpace;
5545
- loadedTexture.wrapS = import_three15.ClampToEdgeWrapping;
5546
- loadedTexture.wrapT = import_three15.ClampToEdgeWrapping;
5547
- loadedTexture.magFilter = import_three15.LinearFilter;
5548
- loadedTexture.minFilter = import_three15.LinearMipmapLinearFilter;
6119
+ loadedTexture.colorSpace = import_three16.SRGBColorSpace;
6120
+ loadedTexture.wrapS = import_three16.ClampToEdgeWrapping;
6121
+ loadedTexture.wrapT = import_three16.ClampToEdgeWrapping;
6122
+ loadedTexture.magFilter = import_three16.LinearFilter;
6123
+ loadedTexture.minFilter = import_three16.LinearMipmapLinearFilter;
5549
6124
  loadedTexture.generateMipmaps = true;
5550
6125
  loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
5551
6126
  loadedTexture.needsUpdate = true;
@@ -5562,7 +6137,7 @@ function usePosterTexture(id, poster, onError) {
5562
6137
  active = false;
5563
6138
  };
5564
6139
  }, [gl, id, poster]);
5565
- (0, import_react22.useEffect)(
6140
+ (0, import_react25.useEffect)(
5566
6141
  () => () => {
5567
6142
  texture?.dispose();
5568
6143
  },
@@ -5580,14 +6155,14 @@ function useVideoResource({
5580
6155
  poster,
5581
6156
  src
5582
6157
  }) {
5583
- const [resource, setResource] = (0, import_react22.useState)(null);
5584
- const onEndedRef = (0, import_react22.useRef)(onEnded);
5585
- const onErrorRef = (0, import_react22.useRef)(onError);
5586
- const onPlaybackStateChangeRef = (0, import_react22.useRef)(onPlaybackStateChange);
6158
+ const [resource, setResource] = (0, import_react25.useState)(null);
6159
+ const onEndedRef = (0, import_react25.useRef)(onEnded);
6160
+ const onErrorRef = (0, import_react25.useRef)(onError);
6161
+ const onPlaybackStateChangeRef = (0, import_react25.useRef)(onPlaybackStateChange);
5587
6162
  onEndedRef.current = onEnded;
5588
6163
  onErrorRef.current = onError;
5589
6164
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
5590
- (0, import_react22.useEffect)(() => {
6165
+ (0, import_react25.useEffect)(() => {
5591
6166
  if (!src) {
5592
6167
  setResource(null);
5593
6168
  return;
@@ -5599,12 +6174,12 @@ function useVideoResource({
5599
6174
  }
5600
6175
  video.playsInline = playsInline ?? true;
5601
6176
  video.poster = poster ?? "";
5602
- const texture = new import_three15.VideoTexture(video);
5603
- texture.colorSpace = import_three15.SRGBColorSpace;
5604
- texture.wrapS = import_three15.ClampToEdgeWrapping;
5605
- texture.wrapT = import_three15.ClampToEdgeWrapping;
5606
- texture.magFilter = import_three15.LinearFilter;
5607
- texture.minFilter = import_three15.LinearFilter;
6177
+ const texture = new import_three16.VideoTexture(video);
6178
+ texture.colorSpace = import_three16.SRGBColorSpace;
6179
+ texture.wrapS = import_three16.ClampToEdgeWrapping;
6180
+ texture.wrapT = import_three16.ClampToEdgeWrapping;
6181
+ texture.magFilter = import_three16.LinearFilter;
6182
+ texture.minFilter = import_three16.LinearFilter;
5608
6183
  texture.generateMipmaps = false;
5609
6184
  const notifyState = (state) => {
5610
6185
  if (active) {
@@ -5688,11 +6263,11 @@ function VideoHotspot({
5688
6263
  src
5689
6264
  });
5690
6265
  const posterTexture = usePosterTexture(id, poster, onError);
5691
- const onPlaybackErrorRef = (0, import_react22.useRef)(onPlaybackError);
5692
- const onPlaybackStateChangeRef = (0, import_react22.useRef)(onPlaybackStateChange);
6266
+ const onPlaybackErrorRef = (0, import_react25.useRef)(onPlaybackError);
6267
+ const onPlaybackStateChangeRef = (0, import_react25.useRef)(onPlaybackStateChange);
5693
6268
  onPlaybackErrorRef.current = onPlaybackError;
5694
6269
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
5695
- (0, import_react22.useEffect)(() => {
6270
+ (0, import_react25.useEffect)(() => {
5696
6271
  if (!resource) {
5697
6272
  return;
5698
6273
  }
@@ -5702,7 +6277,7 @@ function VideoHotspot({
5702
6277
  resource.video.playsInline = playsInline;
5703
6278
  resource.video.preload = preload;
5704
6279
  }, [loop, muted, playsInline, preload, resource, volume]);
5705
- (0, import_react22.useEffect)(() => {
6280
+ (0, import_react25.useEffect)(() => {
5706
6281
  if (!resource) {
5707
6282
  return;
5708
6283
  }
@@ -5725,24 +6300,405 @@ function VideoHotspot({
5725
6300
  };
5726
6301
  }, [playing, resource]);
5727
6302
  const activeTexture = resource?.ready ? resource.texture : posterTexture;
5728
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("mesh", { children: [
5729
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("planeGeometry", { args: [1, 1] }),
5730
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5731
- "meshBasicMaterial",
6303
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(HotspotPlane, { map: activeTexture, opacity }) });
6304
+ }
6305
+
6306
+ // src/hotspot/text-hotspot.tsx
6307
+ var import_react26 = require("react");
6308
+ var import_three17 = require("three");
6309
+ var import_jsx_runtime20 = require("react/jsx-runtime");
6310
+ var TEXT_TEXTURE_LONG_SIDE = 1024;
6311
+ var DEFAULT_FONT_FAMILY = "system-ui, sans-serif";
6312
+ var DEFAULT_COLOR = "#f8fafc";
6313
+ var DEFAULT_BACKGROUND = "#111827";
6314
+ function clamp01(value, fallback) {
6315
+ if (!Number.isFinite(value)) {
6316
+ return fallback;
6317
+ }
6318
+ return Math.max(0, Math.min(value, 1));
6319
+ }
6320
+ function resolvePositive(value, fallback) {
6321
+ return Number.isFinite(value) && value > 0 ? value : fallback;
6322
+ }
6323
+ function resolveCornerRadius2(value) {
6324
+ return Number.isFinite(value) ? Math.max(0, Math.min(value, 0.5)) : 0.08;
6325
+ }
6326
+ function textureSize(width, height) {
6327
+ const safeWidth = resolvePositive(width, 1);
6328
+ const safeHeight = resolvePositive(height, 1);
6329
+ if (safeWidth >= safeHeight) {
6330
+ return {
6331
+ canvasWidth: TEXT_TEXTURE_LONG_SIDE,
6332
+ canvasHeight: Math.max(
6333
+ 1,
6334
+ Math.round(TEXT_TEXTURE_LONG_SIDE * (safeHeight / safeWidth))
6335
+ )
6336
+ };
6337
+ }
6338
+ return {
6339
+ canvasWidth: Math.max(
6340
+ 1,
6341
+ Math.round(TEXT_TEXTURE_LONG_SIDE * (safeWidth / safeHeight))
6342
+ ),
6343
+ canvasHeight: TEXT_TEXTURE_LONG_SIDE
6344
+ };
6345
+ }
6346
+ function roundedRectanglePath2(context, x, y, width, height, radius) {
6347
+ const safeRadius = Math.max(
6348
+ 0,
6349
+ Math.min(radius, width / 2, height / 2)
6350
+ );
6351
+ context.beginPath();
6352
+ if (safeRadius <= 0) {
6353
+ context.rect(x, y, width, height);
6354
+ return;
6355
+ }
6356
+ context.moveTo(x + safeRadius, y);
6357
+ context.lineTo(x + width - safeRadius, y);
6358
+ context.quadraticCurveTo(x + width, y, x + width, y + safeRadius);
6359
+ context.lineTo(x + width, y + height - safeRadius);
6360
+ context.quadraticCurveTo(
6361
+ x + width,
6362
+ y + height,
6363
+ x + width - safeRadius,
6364
+ y + height
6365
+ );
6366
+ context.lineTo(x + safeRadius, y + height);
6367
+ context.quadraticCurveTo(x, y + height, x, y + height - safeRadius);
6368
+ context.lineTo(x, y + safeRadius);
6369
+ context.quadraticCurveTo(x, y, x + safeRadius, y);
6370
+ context.closePath();
6371
+ }
6372
+ function wrapLine(context, line, maxWidth) {
6373
+ if (maxWidth <= 0 || context.measureText(line).width <= maxWidth) {
6374
+ return [line];
6375
+ }
6376
+ const lines = [];
6377
+ let current = "";
6378
+ for (const word of line.split(" ")) {
6379
+ const candidate = current ? `${current} ${word}` : word;
6380
+ if (context.measureText(candidate).width <= maxWidth) {
6381
+ current = candidate;
6382
+ continue;
6383
+ }
6384
+ if (current) {
6385
+ lines.push(current);
6386
+ }
6387
+ if (context.measureText(word).width <= maxWidth) {
6388
+ current = word;
6389
+ continue;
6390
+ }
6391
+ let chunk = "";
6392
+ for (const character of word) {
6393
+ const nextChunk = chunk + character;
6394
+ if (context.measureText(nextChunk).width <= maxWidth || chunk === "") {
6395
+ chunk = nextChunk;
6396
+ } else {
6397
+ lines.push(chunk);
6398
+ chunk = character;
6399
+ }
6400
+ }
6401
+ current = chunk;
6402
+ }
6403
+ if (current) {
6404
+ lines.push(current);
6405
+ }
6406
+ return lines.length > 0 ? lines : [""];
6407
+ }
6408
+ function wrapText(context, text, maxWidth, nowrap) {
6409
+ const paragraphs = text.split("\n");
6410
+ if (nowrap) {
6411
+ return paragraphs;
6412
+ }
6413
+ return paragraphs.flatMap((paragraph) => wrapLine(context, paragraph, maxWidth));
6414
+ }
6415
+ function drawTextHotspot(context, canvasWidth, canvasHeight, text, style) {
6416
+ const shortSide = Math.min(canvasWidth, canvasHeight);
6417
+ const padding = clamp01(style.padding, 0.08) * shortSide;
6418
+ const fontSize = Math.max(1, clamp01(style.fontSize, 0.18) * canvasHeight);
6419
+ const lineHeight = resolvePositive(style.lineHeight, 1.25) * fontSize;
6420
+ const align = style.align ?? "center";
6421
+ const verticalAlign = style.verticalAlign ?? "middle";
6422
+ const fontFamily = style.fontFamily?.trim() || DEFAULT_FONT_FAMILY;
6423
+ const fontWeight = style.fontWeight ?? 600;
6424
+ const fontStyle = style.fontStyle ?? "normal";
6425
+ const contentWidth = Math.max(1, canvasWidth - padding * 2);
6426
+ const contentHeight = Math.max(1, canvasHeight - padding * 2);
6427
+ context.clearRect(0, 0, canvasWidth, canvasHeight);
6428
+ const background = style.background ?? DEFAULT_BACKGROUND;
6429
+ if (background !== "") {
6430
+ roundedRectanglePath2(
6431
+ context,
6432
+ 0,
6433
+ 0,
6434
+ canvasWidth,
6435
+ canvasHeight,
6436
+ resolveCornerRadius2(style.borderRadius) * shortSide
6437
+ );
6438
+ context.save();
6439
+ context.globalAlpha = clamp01(style.backgroundOpacity, 0.72);
6440
+ context.fillStyle = background;
6441
+ context.fill();
6442
+ context.restore();
6443
+ }
6444
+ context.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
6445
+ context.textAlign = align;
6446
+ context.textBaseline = "top";
6447
+ const lines = wrapText(
6448
+ context,
6449
+ text,
6450
+ contentWidth,
6451
+ (style.whiteSpace ?? "normal") === "nowrap"
6452
+ );
6453
+ const maxLines = Math.max(1, Math.floor(contentHeight / lineHeight));
6454
+ const visibleLines = lines.slice(0, maxLines);
6455
+ const blockHeight = visibleLines.length * lineHeight;
6456
+ let startY = padding;
6457
+ if (verticalAlign === "middle") {
6458
+ startY = padding + Math.max(0, (contentHeight - blockHeight) / 2);
6459
+ } else if (verticalAlign === "bottom") {
6460
+ startY = padding + Math.max(0, contentHeight - blockHeight);
6461
+ }
6462
+ const startX = align === "left" ? padding : align === "right" ? canvasWidth - padding : canvasWidth / 2;
6463
+ const color = style.color?.trim() || DEFAULT_COLOR;
6464
+ const strokeWidth = Number.isFinite(style.strokeWidth) ? Math.max(style.strokeWidth, 0) : 0;
6465
+ context.save();
6466
+ context.beginPath();
6467
+ context.rect(padding, padding, contentWidth, contentHeight);
6468
+ context.clip();
6469
+ visibleLines.forEach((line, index) => {
6470
+ const y = startY + index * lineHeight;
6471
+ if (style.stroke && strokeWidth > 0) {
6472
+ context.lineWidth = strokeWidth;
6473
+ context.strokeStyle = style.stroke;
6474
+ context.lineJoin = "round";
6475
+ context.miterLimit = 2;
6476
+ context.strokeText(line, startX, y);
6477
+ }
6478
+ context.fillStyle = color;
6479
+ context.fillText(line, startX, y);
6480
+ });
6481
+ context.restore();
6482
+ }
6483
+ function createTextTexture(text, width, height, style) {
6484
+ const { canvasWidth, canvasHeight } = textureSize(width, height);
6485
+ const canvas = document.createElement("canvas");
6486
+ canvas.width = canvasWidth;
6487
+ canvas.height = canvasHeight;
6488
+ const context = canvas.getContext("2d");
6489
+ if (!context) {
6490
+ throw new Error("Canvas 2D is unavailable for this text hotspot.");
6491
+ }
6492
+ drawTextHotspot(context, canvasWidth, canvasHeight, text, style);
6493
+ const texture = new import_three17.CanvasTexture(canvas);
6494
+ texture.colorSpace = import_three17.SRGBColorSpace;
6495
+ texture.wrapS = import_three17.ClampToEdgeWrapping;
6496
+ texture.wrapT = import_three17.ClampToEdgeWrapping;
6497
+ texture.magFilter = import_three17.LinearFilter;
6498
+ texture.minFilter = import_three17.LinearMipmapLinearFilter;
6499
+ texture.generateMipmaps = true;
6500
+ texture.needsUpdate = true;
6501
+ return texture;
6502
+ }
6503
+ function useTextTexture(text, width, height, style, onLoad, onError) {
6504
+ const [texture, setTexture] = (0, import_react26.useState)(null);
6505
+ const onLoadRef = (0, import_react26.useRef)(onLoad);
6506
+ const onErrorRef = (0, import_react26.useRef)(onError);
6507
+ onLoadRef.current = onLoad;
6508
+ onErrorRef.current = onError;
6509
+ (0, import_react26.useEffect)(() => {
6510
+ let nextTexture = null;
6511
+ try {
6512
+ nextTexture = createTextTexture(text, width, height, style);
6513
+ setTexture(nextTexture);
6514
+ onLoadRef.current?.(nextTexture);
6515
+ } catch (error) {
6516
+ setTexture(null);
6517
+ onErrorRef.current?.(error);
6518
+ }
6519
+ return () => {
6520
+ nextTexture?.dispose();
6521
+ };
6522
+ }, [
6523
+ height,
6524
+ style.align,
6525
+ style.background,
6526
+ style.backgroundOpacity,
6527
+ style.borderRadius,
6528
+ style.color,
6529
+ style.fontFamily,
6530
+ style.fontSize,
6531
+ style.fontStyle,
6532
+ style.fontWeight,
6533
+ style.lineHeight,
6534
+ style.padding,
6535
+ style.stroke,
6536
+ style.strokeWidth,
6537
+ style.verticalAlign,
6538
+ style.whiteSpace,
6539
+ text,
6540
+ width
6541
+ ]);
6542
+ return texture;
6543
+ }
6544
+ function TextHotspot({
6545
+ text,
6546
+ width = 16,
6547
+ height = 6,
6548
+ opacity,
6549
+ fontFamily,
6550
+ fontSize,
6551
+ fontWeight,
6552
+ fontStyle,
6553
+ color,
6554
+ background,
6555
+ backgroundOpacity,
6556
+ padding,
6557
+ align,
6558
+ verticalAlign,
6559
+ lineHeight,
6560
+ whiteSpace,
6561
+ borderRadius,
6562
+ stroke,
6563
+ strokeWidth,
6564
+ onLoad,
6565
+ onError,
6566
+ ...anchorProps
6567
+ }) {
6568
+ const style = {
6569
+ align,
6570
+ background,
6571
+ backgroundOpacity,
6572
+ borderRadius,
6573
+ color,
6574
+ fontFamily,
6575
+ fontSize,
6576
+ fontStyle,
6577
+ fontWeight,
6578
+ lineHeight,
6579
+ padding,
6580
+ stroke,
6581
+ strokeWidth,
6582
+ verticalAlign,
6583
+ whiteSpace
6584
+ };
6585
+ const texture = useTextTexture(text, width, height, style, onLoad, onError);
6586
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(HotspotPlane, { map: texture, opacity }) });
6587
+ }
6588
+
6589
+ // src/hotspot/iframe-hotspot.tsx
6590
+ var import_drei3 = require("@react-three/drei");
6591
+ var import_three18 = require("three");
6592
+ var import_jsx_runtime21 = require("react/jsx-runtime");
6593
+ var IFRAME_LONG_SIDE = 800;
6594
+ var CSS3D_DISTANCE_FACTOR = 400;
6595
+ var DEFAULT_SANDBOX = "allow-scripts allow-popups allow-forms";
6596
+ function clampOpacity2(opacity) {
6597
+ if (!Number.isFinite(opacity)) {
6598
+ return 1;
6599
+ }
6600
+ return Math.max(0, Math.min(opacity, 1));
6601
+ }
6602
+ function resolvePositive2(value, fallback) {
6603
+ return Number.isFinite(value) && value > 0 ? value : fallback;
6604
+ }
6605
+ function iframeCssSize(width, height) {
6606
+ const safeWidth = resolvePositive2(width, 1);
6607
+ const safeHeight = resolvePositive2(height, 1);
6608
+ if (safeWidth >= safeHeight) {
6609
+ return {
6610
+ cssWidth: IFRAME_LONG_SIDE,
6611
+ cssHeight: Math.max(
6612
+ 1,
6613
+ Math.round(IFRAME_LONG_SIDE * (safeHeight / safeWidth))
6614
+ )
6615
+ };
6616
+ }
6617
+ return {
6618
+ cssWidth: Math.max(
6619
+ 1,
6620
+ Math.round(IFRAME_LONG_SIDE * (safeWidth / safeHeight))
6621
+ ),
6622
+ cssHeight: IFRAME_LONG_SIDE
6623
+ };
6624
+ }
6625
+ function IframeHotspot({
6626
+ src,
6627
+ title,
6628
+ sandbox = DEFAULT_SANDBOX,
6629
+ allow,
6630
+ referrerPolicy = "strict-origin-when-cross-origin",
6631
+ loading,
6632
+ pointerPolicy = "hotspot",
6633
+ background,
6634
+ opacity,
6635
+ width = 18,
6636
+ height = 12,
6637
+ onLoad,
6638
+ onError,
6639
+ ...anchorProps
6640
+ }) {
6641
+ const { cssWidth, cssHeight } = iframeCssSize(width, height);
6642
+ const iframeTitle = title ?? anchorProps.ariaLabel;
6643
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(HotspotAnchor, { ...anchorProps, height, width, children: [
6644
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("mesh", { children: [
6645
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("planeGeometry", { args: [1, 1] }),
6646
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6647
+ "meshBasicMaterial",
6648
+ {
6649
+ depthWrite: false,
6650
+ opacity: 0,
6651
+ side: import_three18.DoubleSide,
6652
+ toneMapped: false,
6653
+ transparent: true
6654
+ }
6655
+ )
6656
+ ] }),
6657
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6658
+ import_drei3.Html,
5732
6659
  {
5733
- map: activeTexture,
5734
- opacity: activeTexture ? clampOpacity4(opacity) : 0,
5735
- side: import_three15.DoubleSide,
5736
- toneMapped: false,
5737
- transparent: true
6660
+ distanceFactor: CSS3D_DISTANCE_FACTOR,
6661
+ pointerEvents: pointerPolicy === "content" ? "auto" : "none",
6662
+ scale: [1 / cssWidth, 1 / cssHeight, 1],
6663
+ style: {
6664
+ background: background ?? "transparent",
6665
+ height: cssHeight,
6666
+ opacity: clampOpacity2(opacity),
6667
+ overflow: "hidden",
6668
+ width: cssWidth
6669
+ },
6670
+ transform: true,
6671
+ zIndexRange: [10, 0],
6672
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
6673
+ "iframe",
6674
+ {
6675
+ allow,
6676
+ loading,
6677
+ onError: (event) => onError?.(event.nativeEvent),
6678
+ onLoad,
6679
+ referrerPolicy,
6680
+ sandbox,
6681
+ src,
6682
+ style: {
6683
+ border: 0,
6684
+ display: "block",
6685
+ height: "100%",
6686
+ width: "100%"
6687
+ },
6688
+ title: iframeTitle
6689
+ }
6690
+ )
5738
6691
  }
5739
6692
  )
5740
- ] }) });
6693
+ ] });
5741
6694
  }
5742
6695
  // Annotate the CommonJS export names for ESM import in node:
5743
6696
  0 && (module.exports = {
5744
6697
  AutoRotate,
6698
+ DEFAULT_HOTSPOT_TOOLTIP_OFFSET,
6699
+ DEFAULT_PANO_CURSORS,
5745
6700
  GraphicHotspot,
6701
+ IframeHotspot,
5746
6702
  ImageHotspot,
5747
6703
  KeyboardControls,
5748
6704
  MAX_HOTSPOT_PITCH,
@@ -5750,12 +6706,13 @@ function VideoHotspot({
5750
6706
  MouseControls,
5751
6707
  PanoContextMenu,
5752
6708
  PanoEvents,
5753
- PanoView,
5754
- PanoramaScenes,
6709
+ PanoViewer,
5755
6710
  PolygonHotspot,
5756
6711
  PolylineHotspot,
6712
+ Scenes,
5757
6713
  SequenceHotspot,
5758
6714
  Sphere,
6715
+ TextHotspot,
5759
6716
  Tile,
5760
6717
  TouchControls,
5761
6718
  VideoHotspot,