@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.mjs CHANGED
@@ -1,14 +1,15 @@
1
1
  "use client";
2
2
 
3
- // src/pano-view.tsx
3
+ // src/pano-viewer.tsx
4
4
  import { Canvas } from "@react-three/fiber";
5
5
  import {
6
6
  forwardRef as forwardRef2,
7
- useCallback as useCallback3,
8
- useEffect as useEffect10,
7
+ useCallback as useCallback4,
8
+ useContext as useContext11,
9
+ useEffect as useEffect11,
9
10
  useImperativeHandle as useImperativeHandle2,
10
- useMemo as useMemo4,
11
- useRef as useRef9,
11
+ useMemo as useMemo5,
12
+ useRef as useRef10,
12
13
  useState as useState3
13
14
  } from "react";
14
15
 
@@ -404,7 +405,7 @@ function AutoRotate({
404
405
  const rotatingRef = useRef2(false);
405
406
  const yawAccumulatedRef = useRef2(0);
406
407
  if (!controlsRef) {
407
- throw new Error("<AutoRotate> must be rendered inside <PanoView>.");
408
+ throw new Error("<AutoRotate> must be rendered inside <PanoViewer>.");
408
409
  }
409
410
  useEffect2(() => {
410
411
  elapsedRef.current = 0;
@@ -460,18 +461,54 @@ function AutoRotate({
460
461
  return null;
461
462
  }
462
463
 
464
+ // src/control-claims.ts
465
+ import { createContext as createContext3, useContext as useContext2 } from "react";
466
+ var ControlClaimsContext = createContext3(null);
467
+ var DefaultControlChannelContext = createContext3(false);
468
+ var CHANNEL_COMPONENT = {
469
+ mouse: "MouseControls",
470
+ touch: "TouchControls",
471
+ keyboard: "KeyboardControls"
472
+ };
473
+ function createControlClaims() {
474
+ return { mouse: 0, touch: 0, keyboard: 0 };
475
+ }
476
+ function resetControlClaims(claims) {
477
+ claims.mouse = 0;
478
+ claims.touch = 0;
479
+ claims.keyboard = 0;
480
+ }
481
+ function isProduction() {
482
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
483
+ return nodeEnv === "production";
484
+ }
485
+ function useClaimControlChannel(channel) {
486
+ const claims = useContext2(ControlClaimsContext);
487
+ const isDefault = useContext2(DefaultControlChannelContext);
488
+ if (!claims || isDefault) {
489
+ return;
490
+ }
491
+ claims[channel] += 1;
492
+ if (!isProduction() && claims[channel] > 1) {
493
+ const name = CHANNEL_COMPONENT[channel];
494
+ console.warn(
495
+ `PanoViewer: multiple <${name}> instances are mounted. Render at most one per channel.`
496
+ );
497
+ }
498
+ }
499
+
463
500
  // src/hotspot/accessibility.tsx
464
501
  import {
465
- createContext as createContext3,
502
+ createContext as createContext4,
466
503
  useCallback,
467
- useContext as useContext2,
504
+ useContext as useContext3,
468
505
  useEffect as useEffect3,
469
506
  useMemo,
470
507
  useRef as useRef3,
471
508
  useState
472
509
  } from "react";
473
510
  import { jsx } from "react/jsx-runtime";
474
- var HotspotAccessibilityContext = createContext3(null);
511
+ var HotspotAccessibilityContext = createContext4(null);
475
512
  var VISUALLY_HIDDEN_STYLE = {
476
513
  border: 0,
477
514
  clip: "rect(0 0 0 0)",
@@ -497,7 +534,7 @@ function useHotspotAccessibility({
497
534
  ariaLabel,
498
535
  onActivate
499
536
  }) {
500
- const registry = useContext2(HotspotAccessibilityContext);
537
+ const registry = useContext3(HotspotAccessibilityContext);
501
538
  const [focused, setFocused] = useState(false);
502
539
  const activateRef = useRef3(onActivate);
503
540
  const enabled = Boolean(onActivate);
@@ -578,7 +615,7 @@ function useHotspotAccessibilityLayer() {
578
615
 
579
616
  // src/keyboard-controls.tsx
580
617
  import { useFrame as useFrame3, useThree as useThree2 } from "@react-three/fiber";
581
- import { useContext as useContext3, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4 } from "react";
618
+ import { useContext as useContext4, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4 } from "react";
582
619
  var DEFAULT_ROTATE_SPEED = 60;
583
620
  var DEFAULT_ZOOM_SPEED = 30;
584
621
  var DEFAULT_SHIFT_MULTIPLIER = 3;
@@ -647,7 +684,8 @@ function KeyboardControls({
647
684
  onPreviousScene,
648
685
  onNextScene
649
686
  }) {
650
- const controlsRef = useContext3(PanoramaViewContext);
687
+ useClaimControlChannel("keyboard");
688
+ const controlsRef = useContext4(PanoramaViewContext);
651
689
  const { gl } = useThree2();
652
690
  const pressedRef = useRef4(/* @__PURE__ */ new Set());
653
691
  const shiftHeldRef = useRef4(false);
@@ -657,7 +695,7 @@ function KeyboardControls({
657
695
  const onNextSceneRef = useRef4(onNextScene);
658
696
  const keyMap = useMemo2(() => resolveKeyMap(keys), [keys]);
659
697
  if (!controlsRef) {
660
- throw new Error("<KeyboardControls> must be rendered inside <PanoView>.");
698
+ throw new Error("<KeyboardControls> must be rendered inside <PanoViewer>.");
661
699
  }
662
700
  invertRef.current = invert;
663
701
  fovSpeedRef.current = fovSpeed;
@@ -804,7 +842,7 @@ function KeyboardControls({
804
842
 
805
843
  // src/mouse-controls.tsx
806
844
  import { useThree as useThree3 } from "@react-three/fiber";
807
- import { useContext as useContext4, useEffect as useEffect5, useRef as useRef5 } from "react";
845
+ import { useContext as useContext5, useEffect as useEffect5, useRef as useRef5 } from "react";
808
846
  var DEFAULT_ROTATE_SPEED2 = 0.35;
809
847
  var DEFAULT_ZOOM_SPEED2 = 0.08;
810
848
  var BUTTON_FLAG = {
@@ -828,7 +866,8 @@ function MouseControls({
828
866
  buttons,
829
867
  fovSpeed
830
868
  }) {
831
- const controlsRef = useContext4(PanoramaViewContext);
869
+ useClaimControlChannel("mouse");
870
+ const controlsRef = useContext5(PanoramaViewContext);
832
871
  const { gl } = useThree3();
833
872
  const pointersRef = useRef5(
834
873
  /* @__PURE__ */ new Map()
@@ -841,7 +880,7 @@ function MouseControls({
841
880
  fovSpeed
842
881
  });
843
882
  if (!controlsRef) {
844
- throw new Error("<MouseControls> must be rendered inside <PanoView>.");
883
+ throw new Error("<MouseControls> must be rendered inside <PanoViewer>.");
845
884
  }
846
885
  optionsRef.current = {
847
886
  rotateSpeed,
@@ -975,9 +1014,9 @@ function MouseControls({
975
1014
 
976
1015
  // src/pano-context-menu.tsx
977
1016
  import {
978
- createContext as createContext4,
1017
+ createContext as createContext5,
979
1018
  useCallback as useCallback2,
980
- useContext as useContext5,
1019
+ useContext as useContext6,
981
1020
  useEffect as useEffect6,
982
1021
  useId,
983
1022
  useLayoutEffect,
@@ -989,7 +1028,7 @@ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
989
1028
  var DEFAULT_ICON_SIZE = 16;
990
1029
  var MENU_EDGE_PADDING = 8;
991
1030
  var MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY = 0.4;
992
- var PanoContextMenuOverlayContext = createContext4(null);
1031
+ var PanoContextMenuOverlayContext = createContext5(null);
993
1032
  var DEFAULT_APPEARANCE = {
994
1033
  background: "#161616",
995
1034
  color: "#f4f4f4",
@@ -1397,10 +1436,10 @@ function PanoContextMenu({
1397
1436
  separatorClassName,
1398
1437
  children
1399
1438
  }) {
1400
- const eventBus = useContext5(PanoEventBusContext);
1401
- const controlsRef = useContext5(PanoramaViewContext);
1402
- const overlayApi = useContext5(PanoContextMenuOverlayContext);
1403
- const actions = useContext5(PanoContextMenuActionsContext);
1439
+ const eventBus = useContext6(PanoEventBusContext);
1440
+ const controlsRef = useContext6(PanoramaViewContext);
1441
+ const overlayApi = useContext6(PanoContextMenuOverlayContext);
1442
+ const actions = useContext6(PanoContextMenuActionsContext);
1404
1443
  const instanceId = useId();
1405
1444
  const sessionKeyRef = useRef6(0);
1406
1445
  const propsRef = useRef6({
@@ -1426,11 +1465,11 @@ function PanoContextMenu({
1426
1465
  children
1427
1466
  };
1428
1467
  if (!eventBus || !controlsRef) {
1429
- throw new Error("<PanoContextMenu> must be rendered inside <PanoView>.");
1468
+ throw new Error("<PanoContextMenu> must be rendered inside <PanoViewer>.");
1430
1469
  }
1431
1470
  if (!overlayApi) {
1432
1471
  throw new Error(
1433
- "<PanoContextMenu> requires the PanoView context-menu overlay host."
1472
+ "<PanoContextMenu> requires the PanoViewer context-menu overlay host."
1434
1473
  );
1435
1474
  }
1436
1475
  const close = useCallback2(() => {
@@ -1664,11 +1703,163 @@ function composePanoContextMenuItems(props, presets) {
1664
1703
  ...resolvePanoContextMenuEntries(props.append ?? [], presets)
1665
1704
  ];
1666
1705
  }
1667
- var PanoContextMenuActionsContext = createContext4(null);
1706
+ var PanoContextMenuActionsContext = createContext5(null);
1707
+
1708
+ // src/pano-cursor.tsx
1709
+ import { useThree as useThree4 } from "@react-three/fiber";
1710
+ import {
1711
+ createContext as createContext6,
1712
+ useCallback as useCallback3,
1713
+ useContext as useContext7,
1714
+ useEffect as useEffect7,
1715
+ useMemo as useMemo4,
1716
+ useRef as useRef7
1717
+ } from "react";
1718
+ import { jsx as jsx3 } from "react/jsx-runtime";
1719
+ var DEFAULT_PANO_CURSORS = {
1720
+ default: "grab",
1721
+ dragging: "grabbing",
1722
+ hotspot: "pointer",
1723
+ hotspotDragging: "move"
1724
+ };
1725
+ var PANO_DRAG_CLAIM_ID = "pano-cursor:dragging";
1726
+ var LAYER_PRIORITY = {
1727
+ dragging: 1,
1728
+ hotspot: 2,
1729
+ hotspotDragging: 3
1730
+ };
1731
+ var PanoCursorContext = createContext6(null);
1732
+ function usePanoCursor() {
1733
+ return useContext7(PanoCursorContext);
1734
+ }
1735
+ function hotspotCursorClaimId(id) {
1736
+ return `pano-cursor:hotspot:${id}`;
1737
+ }
1738
+ function resolveClaimCursor(claim, cursors) {
1739
+ switch (claim.layer) {
1740
+ case "dragging":
1741
+ return cursors.dragging;
1742
+ case "hotspot":
1743
+ return claim.cursor ?? cursors.hotspot;
1744
+ case "hotspotDragging":
1745
+ return claim.cursor ?? cursors.hotspotDragging;
1746
+ default: {
1747
+ const exhaustive = claim.layer;
1748
+ return exhaustive;
1749
+ }
1750
+ }
1751
+ }
1752
+ function resolveHighestCursor(claims, cursors) {
1753
+ let best = null;
1754
+ let bestPriority = 0;
1755
+ for (const claim of claims.values()) {
1756
+ const priority = LAYER_PRIORITY[claim.layer];
1757
+ if (priority >= bestPriority) {
1758
+ best = claim;
1759
+ bestPriority = priority;
1760
+ }
1761
+ }
1762
+ return best ? resolveClaimCursor(best, cursors) : cursors.default;
1763
+ }
1764
+ function resolvePanoCursors(value) {
1765
+ if (value === false) {
1766
+ return null;
1767
+ }
1768
+ if (value === true || value === void 0) {
1769
+ return DEFAULT_PANO_CURSORS;
1770
+ }
1771
+ return {
1772
+ default: value.default ?? DEFAULT_PANO_CURSORS.default,
1773
+ dragging: value.dragging ?? DEFAULT_PANO_CURSORS.dragging,
1774
+ hotspot: value.hotspot ?? DEFAULT_PANO_CURSORS.hotspot,
1775
+ hotspotDragging: value.hotspotDragging ?? DEFAULT_PANO_CURSORS.hotspotDragging
1776
+ };
1777
+ }
1778
+ function PanoCursorController({
1779
+ cursors,
1780
+ children
1781
+ }) {
1782
+ const { gl } = useThree4();
1783
+ const eventBus = useContext7(PanoEventBusContext);
1784
+ const claimsRef = useRef7(/* @__PURE__ */ new Map());
1785
+ const cursorsRef = useRef7(cursors);
1786
+ const appliedCursorRef = useRef7(null);
1787
+ cursorsRef.current = cursors;
1788
+ const applyCursor = useCallback3(() => {
1789
+ const next = resolveHighestCursor(claimsRef.current, cursorsRef.current);
1790
+ const element = gl.domElement;
1791
+ if (appliedCursorRef.current === next && element.style.cursor === next) {
1792
+ return;
1793
+ }
1794
+ element.style.cursor = next;
1795
+ appliedCursorRef.current = next;
1796
+ }, [gl]);
1797
+ const claim = useCallback3(
1798
+ (id, layer, cursor) => {
1799
+ claimsRef.current.set(id, { layer, cursor });
1800
+ applyCursor();
1801
+ },
1802
+ [applyCursor]
1803
+ );
1804
+ const release = useCallback3(
1805
+ (id) => {
1806
+ if (claimsRef.current.delete(id)) {
1807
+ applyCursor();
1808
+ }
1809
+ },
1810
+ [applyCursor]
1811
+ );
1812
+ const api = useMemo4(
1813
+ () => ({
1814
+ cursors,
1815
+ claim,
1816
+ release
1817
+ }),
1818
+ [claim, cursors, release]
1819
+ );
1820
+ useEffect7(() => {
1821
+ const element = gl.domElement;
1822
+ const previous = element.style.cursor;
1823
+ return () => {
1824
+ claimsRef.current.clear();
1825
+ appliedCursorRef.current = null;
1826
+ element.style.cursor = previous;
1827
+ };
1828
+ }, [gl]);
1829
+ useEffect7(() => {
1830
+ applyCursor();
1831
+ }, [applyCursor, cursors]);
1832
+ useEffect7(() => {
1833
+ if (!eventBus) {
1834
+ return;
1835
+ }
1836
+ const unsubscribeStart = eventBus.subscribe(
1837
+ "viewinteractionstart",
1838
+ (event) => {
1839
+ if (event.source !== "mouse") {
1840
+ return;
1841
+ }
1842
+ claim(PANO_DRAG_CLAIM_ID, "dragging");
1843
+ }
1844
+ );
1845
+ const unsubscribeEnd = eventBus.subscribe("viewinteractionend", (event) => {
1846
+ if (event.source !== "mouse") {
1847
+ return;
1848
+ }
1849
+ release(PANO_DRAG_CLAIM_ID);
1850
+ });
1851
+ return () => {
1852
+ unsubscribeStart();
1853
+ unsubscribeEnd();
1854
+ release(PANO_DRAG_CLAIM_ID);
1855
+ };
1856
+ }, [claim, eventBus, release]);
1857
+ return /* @__PURE__ */ jsx3(PanoCursorContext.Provider, { value: api, children });
1858
+ }
1668
1859
 
1669
1860
  // src/pano-events.tsx
1670
- import { useFrame as useFrame4, useThree as useThree4 } from "@react-three/fiber";
1671
- import { useContext as useContext6, useEffect as useEffect7, useRef as useRef7 } from "react";
1861
+ import { useFrame as useFrame4, useThree as useThree5 } from "@react-three/fiber";
1862
+ import { useContext as useContext8, useEffect as useEffect8, useRef as useRef8 } from "react";
1672
1863
  var DEFAULT_IDLE_TIME_MS = 2e3;
1673
1864
  function resolveIdleTime(value) {
1674
1865
  return Number.isFinite(value) && value >= 0 ? value : DEFAULT_IDLE_TIME_MS;
@@ -1695,13 +1886,13 @@ function usePanoEvents({
1695
1886
  onAutoRotateStop,
1696
1887
  onAutoRotateOneRound
1697
1888
  }) {
1698
- const eventBus = useContext6(PanoEventBusContext);
1699
- const controlsRef = useContext6(PanoramaViewContext);
1700
- const { gl } = useThree4();
1889
+ const eventBus = useContext8(PanoEventBusContext);
1890
+ const controlsRef = useContext8(PanoramaViewContext);
1891
+ const { gl } = useThree5();
1701
1892
  if (!eventBus || !controlsRef) {
1702
- throw new Error("usePanoEvents must be used inside <PanoView>.");
1893
+ throw new Error("usePanoEvents must be used inside <PanoViewer>.");
1703
1894
  }
1704
- const callbacksRef = useRef7({
1895
+ const callbacksRef = useRef8({
1705
1896
  onViewChange,
1706
1897
  onViewSettled,
1707
1898
  onViewInteractionStart,
@@ -1743,15 +1934,15 @@ function usePanoEvents({
1743
1934
  onAutoRotateStop,
1744
1935
  onAutoRotateOneRound
1745
1936
  };
1746
- const idleElapsedRef = useRef7(0);
1747
- const isIdleRef = useRef7(false);
1748
- const idleTimeRef = useRef7(resolveIdleTime(idleTime));
1937
+ const idleElapsedRef = useRef8(0);
1938
+ const isIdleRef = useRef8(false);
1939
+ const idleTimeRef = useRef8(resolveIdleTime(idleTime));
1749
1940
  idleTimeRef.current = resolveIdleTime(idleTime);
1750
- useEffect7(() => {
1941
+ useEffect8(() => {
1751
1942
  idleElapsedRef.current = 0;
1752
1943
  isIdleRef.current = false;
1753
1944
  }, [idleTime]);
1754
- useEffect7(() => {
1945
+ useEffect8(() => {
1755
1946
  const unsubscribers = [
1756
1947
  eventBus.subscribe("viewchange", (view) => {
1757
1948
  callbacksRef.current.onViewChange?.(view);
@@ -1799,7 +1990,7 @@ function usePanoEvents({
1799
1990
  }
1800
1991
  };
1801
1992
  }, [eventBus]);
1802
- useEffect7(() => {
1993
+ useEffect8(() => {
1803
1994
  const element = gl.domElement;
1804
1995
  const handleWheel = (nativeEvent) => {
1805
1996
  callbacksRef.current.onWheel?.({
@@ -1812,7 +2003,7 @@ function usePanoEvents({
1812
2003
  element.removeEventListener("wheel", handleWheel);
1813
2004
  };
1814
2005
  }, [gl]);
1815
- useEffect7(() => {
2006
+ useEffect8(() => {
1816
2007
  if (typeof document === "undefined") {
1817
2008
  return;
1818
2009
  }
@@ -1841,7 +2032,7 @@ function usePanoEvents({
1841
2032
  document.removeEventListener("fullscreenchange", handleFullscreenChange);
1842
2033
  };
1843
2034
  }, [gl]);
1844
- useEffect7(() => {
2035
+ useEffect8(() => {
1845
2036
  const element = gl.domElement;
1846
2037
  if (typeof ResizeObserver === "undefined") {
1847
2038
  return;
@@ -1882,8 +2073,8 @@ function PanoEvents(props) {
1882
2073
  }
1883
2074
 
1884
2075
  // src/panorama-event-surface.tsx
1885
- import { useThree as useThree5 } from "@react-three/fiber";
1886
- import { useContext as useContext7, useEffect as useEffect8 } from "react";
2076
+ import { useThree as useThree6 } from "@react-three/fiber";
2077
+ import { useContext as useContext9, useEffect as useEffect9 } from "react";
1887
2078
  import { BackSide, Raycaster, Vector2, Vector3 as Vector32 } from "three";
1888
2079
 
1889
2080
  // src/hotspot/coordinates.ts
@@ -1949,7 +2140,7 @@ var DEFAULT_PANORAMA_CAMERA_NEAR = 0.1;
1949
2140
  var DEFAULT_PANORAMA_CAMERA_FAR = DEFAULT_PANORAMA_RADIUS * 2;
1950
2141
 
1951
2142
  // src/panorama-event-surface.tsx
1952
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
2143
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1953
2144
  var EVENT_SURFACE_RADIUS = DEFAULT_PANORAMA_RADIUS - 1;
1954
2145
  function makePanoramaPointerEvent(event) {
1955
2146
  const nativeEvent = event.nativeEvent;
@@ -1964,9 +2155,9 @@ function makePanoramaPointerEvent(event) {
1964
2155
  };
1965
2156
  }
1966
2157
  function PanoramaEventSurface() {
1967
- const eventBus = useContext7(PanoEventBusContext);
1968
- const { camera, gl } = useThree5();
1969
- useEffect8(() => {
2158
+ const eventBus = useContext9(PanoEventBusContext);
2159
+ const { camera, gl } = useThree6();
2160
+ useEffect9(() => {
1970
2161
  if (!eventBus) {
1971
2162
  return;
1972
2163
  }
@@ -2017,8 +2208,8 @@ function PanoramaEventSurface() {
2017
2208
  onPointerUp: (event) => emit("pointerup", event),
2018
2209
  onPointerMove: (event) => emit("pointermove", event),
2019
2210
  children: [
2020
- /* @__PURE__ */ jsx3("sphereGeometry", { args: [EVENT_SURFACE_RADIUS, 64, 32] }),
2021
- /* @__PURE__ */ jsx3(
2211
+ /* @__PURE__ */ jsx4("sphereGeometry", { args: [EVENT_SURFACE_RADIUS, 64, 32] }),
2212
+ /* @__PURE__ */ jsx4(
2022
2213
  "meshBasicMaterial",
2023
2214
  {
2024
2215
  colorWrite: false,
@@ -2035,8 +2226,8 @@ function PanoramaEventSurface() {
2035
2226
  }
2036
2227
 
2037
2228
  // src/touch-controls.tsx
2038
- import { useThree as useThree6 } from "@react-three/fiber";
2039
- import { useContext as useContext8, useEffect as useEffect9, useRef as useRef8 } from "react";
2229
+ import { useThree as useThree7 } from "@react-three/fiber";
2230
+ import { useContext as useContext10, useEffect as useEffect10, useRef as useRef9 } from "react";
2040
2231
  var DEFAULT_ROTATE_SPEED3 = 0.35;
2041
2232
  function resolveSpeed3(value, fallback) {
2042
2233
  return Number.isFinite(value) && value >= 0 ? value : fallback;
@@ -2050,16 +2241,17 @@ function TouchControls({
2050
2241
  invert = false,
2051
2242
  pinchZoom = true
2052
2243
  }) {
2053
- const controlsRef = useContext8(PanoramaViewContext);
2054
- const { gl } = useThree6();
2055
- const pointersRef = useRef8(/* @__PURE__ */ new Map());
2056
- const pinchDistanceRef = useRef8(null);
2057
- const optionsRef = useRef8({ rotateSpeed, invert, pinchZoom });
2244
+ useClaimControlChannel("touch");
2245
+ const controlsRef = useContext10(PanoramaViewContext);
2246
+ const { gl } = useThree7();
2247
+ const pointersRef = useRef9(/* @__PURE__ */ new Map());
2248
+ const pinchDistanceRef = useRef9(null);
2249
+ const optionsRef = useRef9({ rotateSpeed, invert, pinchZoom });
2058
2250
  if (!controlsRef) {
2059
- throw new Error("<TouchControls> must be rendered inside <PanoView>.");
2251
+ throw new Error("<TouchControls> must be rendered inside <PanoViewer>.");
2060
2252
  }
2061
2253
  optionsRef.current = { rotateSpeed, invert, pinchZoom };
2062
- useEffect9(() => {
2254
+ useEffect10(() => {
2063
2255
  const element = gl.domElement;
2064
2256
  const clearPointers = () => {
2065
2257
  for (const pointerId of pointersRef.current.keys()) {
@@ -2180,8 +2372,8 @@ function TouchControls({
2180
2372
  return null;
2181
2373
  }
2182
2374
 
2183
- // src/pano-view.tsx
2184
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2375
+ // src/pano-viewer.tsx
2376
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
2185
2377
  var DEFAULT_VIEW = {
2186
2378
  yaw: 0,
2187
2379
  pitch: 0,
@@ -2219,13 +2411,46 @@ function resolveContextMenuChannel(value) {
2219
2411
  }
2220
2412
  return { mount: true, props: { ...value } };
2221
2413
  }
2222
- var PanoView = forwardRef2(
2223
- function PanoView2({
2414
+ function PanoCursorTree({
2415
+ cursors,
2416
+ children
2417
+ }) {
2418
+ if (!cursors) {
2419
+ return children;
2420
+ }
2421
+ return /* @__PURE__ */ jsx5(PanoCursorController, { cursors, children });
2422
+ }
2423
+ function DefaultControlChannels({
2424
+ userControlsEnabled,
2425
+ mouseChannel,
2426
+ touchChannel,
2427
+ keyboardChannel,
2428
+ fovSpeed
2429
+ }) {
2430
+ const claims = useContext11(ControlClaimsContext);
2431
+ if (!claims) {
2432
+ return null;
2433
+ }
2434
+ return /* @__PURE__ */ jsxs3(DefaultControlChannelContext.Provider, { value: true, children: [
2435
+ userControlsEnabled && mouseChannel.mount && claims.mouse === 0 ? /* @__PURE__ */ jsx5(MouseControls, { ...mouseChannel.props, fovSpeed }) : null,
2436
+ userControlsEnabled && touchChannel.mount && claims.touch === 0 ? /* @__PURE__ */ jsx5(TouchControls, { ...touchChannel.props }) : null,
2437
+ userControlsEnabled && keyboardChannel.mount && claims.keyboard === 0 ? /* @__PURE__ */ jsx5(
2438
+ KeyboardControls,
2439
+ {
2440
+ ...keyboardChannel.props,
2441
+ fovSpeed: keyboardChannel.props.fovSpeed ?? fovSpeed
2442
+ }
2443
+ ) : null
2444
+ ] });
2445
+ }
2446
+ var PanoViewer = forwardRef2(
2447
+ function PanoViewer2({
2224
2448
  children,
2225
2449
  initialView,
2226
2450
  minFov = 30,
2227
2451
  maxFov = 100,
2228
2452
  controls = true,
2453
+ cursors,
2229
2454
  contextMenu = true,
2230
2455
  onViewChange,
2231
2456
  onPanoramaClick,
@@ -2236,18 +2461,18 @@ var PanoView = forwardRef2(
2236
2461
  "aria-label": ariaLabel = "Panorama viewer",
2237
2462
  ...divProps
2238
2463
  }, ref) {
2239
- const rootRef = useRef9(null);
2240
- const controlsRef = useRef9(null);
2241
- const eventBus = useMemo4(() => createPanoEventBus(), []);
2464
+ const rootRef = useRef10(null);
2465
+ const controlsRef = useRef10(null);
2466
+ const eventBus = useMemo5(() => createPanoEventBus(), []);
2242
2467
  const { controls: hotspotAccessibilityControls, registry } = useHotspotAccessibilityLayer();
2243
2468
  const { api: contextMenuOverlayApi, overlay: contextMenuOverlay } = usePanoContextMenuOverlay();
2244
- const fallbackViewRef = useRef9(DEFAULT_VIEW);
2469
+ const fallbackViewRef = useRef10(DEFAULT_VIEW);
2245
2470
  const normalizedMinFov = Math.max(1, Math.min(minFov, maxFov - 1));
2246
2471
  const normalizedMaxFov = Math.min(
2247
2472
  179,
2248
2473
  Math.max(maxFov, normalizedMinFov + 1)
2249
2474
  );
2250
- const normalizedInitialView = useMemo4(
2475
+ const normalizedInitialView = useMemo5(
2251
2476
  () => ({
2252
2477
  yaw: initialView?.yaw ?? DEFAULT_VIEW.yaw,
2253
2478
  pitch: clampPanoPitch(initialView?.pitch ?? DEFAULT_VIEW.pitch),
@@ -2265,8 +2490,10 @@ var PanoView = forwardRef2(
2265
2490
  ]
2266
2491
  );
2267
2492
  fallbackViewRef.current = normalizedInitialView;
2493
+ const controlClaims = useMemo5(() => createControlClaims(), []);
2494
+ resetControlClaims(controlClaims);
2268
2495
  const controlsEnabled = controls !== false;
2269
- const controlOptions = useMemo4(
2496
+ const controlOptions = useMemo5(
2270
2497
  () => typeof controls === "object" ? controls : {},
2271
2498
  [controls]
2272
2499
  );
@@ -2274,7 +2501,7 @@ var PanoView = forwardRef2(
2274
2501
  const allModeInvert = controlOptions.invert === true;
2275
2502
  const topRotateSpeed = controlOptions.rotateSpeed ?? DEFAULT_POINTER_ROTATE_SPEED;
2276
2503
  const topZoomSpeed = controlOptions.zoomSpeed ?? DEFAULT_MOUSE_ZOOM_SPEED;
2277
- const mouseChannel = useMemo4(
2504
+ const mouseChannel = useMemo5(
2278
2505
  () => resolveChannel(controlOptions.mouse, {
2279
2506
  enabled: true,
2280
2507
  rotateSpeed: topRotateSpeed,
@@ -2290,7 +2517,7 @@ var PanoView = forwardRef2(
2290
2517
  topZoomSpeed
2291
2518
  ]
2292
2519
  );
2293
- const touchChannel = useMemo4(
2520
+ const touchChannel = useMemo5(
2294
2521
  () => resolveChannel(controlOptions.touch, {
2295
2522
  enabled: true,
2296
2523
  rotateSpeed: topRotateSpeed,
@@ -2299,13 +2526,13 @@ var PanoView = forwardRef2(
2299
2526
  }),
2300
2527
  [allModeInvert, controlOptions.touch, topRotateSpeed]
2301
2528
  );
2302
- const keyboardChannel = useMemo4(
2529
+ const keyboardChannel = useMemo5(
2303
2530
  () => resolveChannel(controlOptions.keyboard, {
2304
2531
  enabled: true
2305
2532
  }),
2306
2533
  [controlOptions.keyboard]
2307
2534
  );
2308
- const toggleFullscreen = useCallback3(async () => {
2535
+ const toggleFullscreen = useCallback4(async () => {
2309
2536
  if (typeof document === "undefined") {
2310
2537
  return;
2311
2538
  }
@@ -2316,7 +2543,7 @@ var PanoView = forwardRef2(
2316
2543
  await rootRef.current?.requestFullscreen?.();
2317
2544
  }, []);
2318
2545
  const [isViewerFullscreen, setIsViewerFullscreen] = useState3(false);
2319
- useEffect10(() => {
2546
+ useEffect11(() => {
2320
2547
  if (typeof document === "undefined") {
2321
2548
  return;
2322
2549
  }
@@ -2337,7 +2564,7 @@ var PanoView = forwardRef2(
2337
2564
  document.removeEventListener("fullscreenchange", syncFullscreen);
2338
2565
  };
2339
2566
  }, []);
2340
- const contextMenuActions = useMemo4(
2567
+ const contextMenuActions = useMemo5(
2341
2568
  () => ({
2342
2569
  reset: () => {
2343
2570
  controlsRef.current?.reset();
@@ -2349,15 +2576,19 @@ var PanoView = forwardRef2(
2349
2576
  }),
2350
2577
  [isViewerFullscreen, toggleFullscreen]
2351
2578
  );
2352
- const contextMenuChannel = useMemo4(
2579
+ const contextMenuChannel = useMemo5(
2353
2580
  () => resolveContextMenuChannel(contextMenu),
2354
2581
  [contextMenu]
2355
2582
  );
2583
+ const resolvedCursors = useMemo5(
2584
+ () => resolvePanoCursors(cursors),
2585
+ [cursors]
2586
+ );
2356
2587
  const legacyAutoRotateOptions = controlOptions;
2357
2588
  const [legacyAutoRotate, setLegacyAutoRotate] = useState3(
2358
2589
  legacyAutoRotateOptions.autoRotate ?? false
2359
2590
  );
2360
- useEffect10(() => {
2591
+ useEffect11(() => {
2361
2592
  setLegacyAutoRotate(legacyAutoRotateOptions.autoRotate ?? false);
2362
2593
  }, [legacyAutoRotateOptions.autoRotate]);
2363
2594
  useImperativeHandle2(
@@ -2380,7 +2611,7 @@ var PanoView = forwardRef2(
2380
2611
  }),
2381
2612
  [toggleFullscreen]
2382
2613
  );
2383
- const rootStyle = useMemo4(
2614
+ const rootStyle = useMemo5(
2384
2615
  () => ({
2385
2616
  overflow: "hidden",
2386
2617
  position: "relative",
@@ -2396,7 +2627,7 @@ var PanoView = forwardRef2(
2396
2627
  "aria-label": ariaLabel,
2397
2628
  style: rootStyle,
2398
2629
  children: [
2399
- /* @__PURE__ */ jsx4(
2630
+ /* @__PURE__ */ jsx5(
2400
2631
  Canvas,
2401
2632
  {
2402
2633
  "aria-label": `${ariaLabel} canvas`,
@@ -2419,7 +2650,7 @@ var PanoView = forwardRef2(
2419
2650
  touchAction: userControlsEnabled ? "none" : "auto"
2420
2651
  },
2421
2652
  tabIndex: userControlsEnabled ? 0 : void 0,
2422
- children: /* @__PURE__ */ jsx4(HotspotAccessibilityContext.Provider, { value: registry, children: /* @__PURE__ */ jsx4(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ jsx4(PanoramaViewContext.Provider, { value: controlsRef, children: /* @__PURE__ */ jsx4(
2653
+ children: /* @__PURE__ */ jsx5(HotspotAccessibilityContext.Provider, { value: registry, children: /* @__PURE__ */ jsx5(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ jsx5(PanoCursorTree, { cursors: resolvedCursors, children: /* @__PURE__ */ jsx5(PanoramaViewContext.Provider, { value: controlsRef, children: /* @__PURE__ */ jsx5(
2423
2654
  PanoContextMenuOverlayContext.Provider,
2424
2655
  {
2425
2656
  value: contextMenuOverlayApi,
@@ -2428,7 +2659,7 @@ var PanoView = forwardRef2(
2428
2659
  {
2429
2660
  value: contextMenuActions,
2430
2661
  children: [
2431
- /* @__PURE__ */ jsx4(
2662
+ /* @__PURE__ */ jsx5(
2432
2663
  PanoramaViewRuntime,
2433
2664
  {
2434
2665
  ref: controlsRef,
@@ -2439,7 +2670,7 @@ var PanoView = forwardRef2(
2439
2670
  options: controlOptions
2440
2671
  }
2441
2672
  ),
2442
- (onViewChange || onPanoramaClick || onPanoramaDoubleClick || onPanoramaPointerMove) && /* @__PURE__ */ jsx4(
2673
+ (onViewChange || onPanoramaClick || onPanoramaDoubleClick || onPanoramaPointerMove) && /* @__PURE__ */ jsx5(
2443
2674
  PanoEvents,
2444
2675
  {
2445
2676
  onViewChange,
@@ -2448,36 +2679,33 @@ var PanoView = forwardRef2(
2448
2679
  onPointerMove: onPanoramaPointerMove
2449
2680
  }
2450
2681
  ),
2451
- /* @__PURE__ */ jsx4(
2682
+ /* @__PURE__ */ jsx5(
2452
2683
  AutoRotate,
2453
2684
  {
2454
2685
  enabled: legacyAutoRotate,
2455
2686
  speed: legacyAutoRotateOptions.autoRotateSpeed
2456
2687
  }
2457
2688
  ),
2458
- userControlsEnabled && mouseChannel.mount ? /* @__PURE__ */ jsx4(
2459
- MouseControls,
2460
- {
2461
- ...mouseChannel.props,
2462
- fovSpeed: controlOptions.fovSpeed
2463
- }
2464
- ) : null,
2465
- userControlsEnabled && touchChannel.mount ? /* @__PURE__ */ jsx4(TouchControls, { ...touchChannel.props }) : null,
2466
- userControlsEnabled && keyboardChannel.mount ? /* @__PURE__ */ jsx4(
2467
- KeyboardControls,
2468
- {
2469
- ...keyboardChannel.props,
2470
- fovSpeed: keyboardChannel.props.fovSpeed ?? controlOptions.fovSpeed
2471
- }
2472
- ) : null,
2473
- /* @__PURE__ */ jsx4(PanoramaEventSurface, {}),
2474
- contextMenuChannel.mount ? /* @__PURE__ */ jsx4(PanoContextMenu, { ...contextMenuChannel.props }) : null,
2475
- children
2689
+ /* @__PURE__ */ jsx5(PanoramaEventSurface, {}),
2690
+ contextMenuChannel.mount ? /* @__PURE__ */ jsx5(PanoContextMenu, { ...contextMenuChannel.props }) : null,
2691
+ /* @__PURE__ */ jsxs3(ControlClaimsContext.Provider, { value: controlClaims, children: [
2692
+ children,
2693
+ /* @__PURE__ */ jsx5(
2694
+ DefaultControlChannels,
2695
+ {
2696
+ fovSpeed: controlOptions.fovSpeed,
2697
+ keyboardChannel,
2698
+ mouseChannel,
2699
+ touchChannel,
2700
+ userControlsEnabled
2701
+ }
2702
+ )
2703
+ ] })
2476
2704
  ]
2477
2705
  }
2478
2706
  )
2479
2707
  }
2480
- ) }) }) })
2708
+ ) }) }) }) })
2481
2709
  }
2482
2710
  ),
2483
2711
  contextMenuOverlay,
@@ -2489,8 +2717,8 @@ var PanoView = forwardRef2(
2489
2717
  );
2490
2718
 
2491
2719
  // src/sphere.tsx
2492
- import { useThree as useThree7 } from "@react-three/fiber";
2493
- import { useEffect as useEffect11, useRef as useRef10, useState as useState4 } from "react";
2720
+ import { useThree as useThree8 } from "@react-three/fiber";
2721
+ import { useEffect as useEffect12, useRef as useRef11, useState as useState4 } from "react";
2494
2722
  import {
2495
2723
  BackSide as BackSide2,
2496
2724
  LinearFilter,
@@ -2500,7 +2728,7 @@ import {
2500
2728
  SRGBColorSpace,
2501
2729
  TextureLoader
2502
2730
  } from "three";
2503
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2731
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2504
2732
  function Sphere({
2505
2733
  src,
2506
2734
  yawOffset = 0,
@@ -2508,13 +2736,13 @@ function Sphere({
2508
2736
  onError,
2509
2737
  visible = true
2510
2738
  }) {
2511
- const gl = useThree7((state) => state.gl);
2739
+ const gl = useThree8((state) => state.gl);
2512
2740
  const [texture, setTexture] = useState4(null);
2513
- const onLoadRef = useRef10(onLoad);
2514
- const onErrorRef = useRef10(onError);
2741
+ const onLoadRef = useRef11(onLoad);
2742
+ const onErrorRef = useRef11(onError);
2515
2743
  onLoadRef.current = onLoad;
2516
2744
  onErrorRef.current = onError;
2517
- useEffect11(() => {
2745
+ useEffect12(() => {
2518
2746
  let active = true;
2519
2747
  const loader = new TextureLoader();
2520
2748
  setTexture(null);
@@ -2548,7 +2776,7 @@ function Sphere({
2548
2776
  active = false;
2549
2777
  };
2550
2778
  }, [gl, src]);
2551
- useEffect11(
2779
+ useEffect12(
2552
2780
  () => () => {
2553
2781
  texture?.dispose();
2554
2782
  },
@@ -2563,8 +2791,8 @@ function Sphere({
2563
2791
  rotation: [0, MathUtils3.degToRad(90 + yawOffset), 0],
2564
2792
  visible,
2565
2793
  children: [
2566
- /* @__PURE__ */ jsx5("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
2567
- /* @__PURE__ */ jsx5(
2794
+ /* @__PURE__ */ jsx6("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
2795
+ /* @__PURE__ */ jsx6(
2568
2796
  "meshBasicMaterial",
2569
2797
  {
2570
2798
  map: texture,
@@ -2577,15 +2805,15 @@ function Sphere({
2577
2805
  );
2578
2806
  }
2579
2807
 
2580
- // src/panorama-scenes.tsx
2581
- import { useFrame as useFrame6, useThree as useThree10 } from "@react-three/fiber";
2808
+ // src/scenes.tsx
2809
+ import { useFrame as useFrame6, useThree as useThree11 } from "@react-three/fiber";
2582
2810
  import {
2583
2811
  Fragment,
2584
- useCallback as useCallback4,
2585
- useContext as useContext10,
2586
- useEffect as useEffect14,
2587
- useMemo as useMemo7,
2588
- useRef as useRef12,
2812
+ useCallback as useCallback5,
2813
+ useContext as useContext13,
2814
+ useEffect as useEffect15,
2815
+ useMemo as useMemo8,
2816
+ useRef as useRef13,
2589
2817
  useState as useState6
2590
2818
  } from "react";
2591
2819
  import {
@@ -2599,12 +2827,12 @@ import {
2599
2827
  } from "three";
2600
2828
 
2601
2829
  // src/tile/texture-manager-context.tsx
2602
- import { useThree as useThree8 } from "@react-three/fiber";
2830
+ import { useThree as useThree9 } from "@react-three/fiber";
2603
2831
  import {
2604
- createContext as createContext5,
2605
- useContext as useContext9,
2606
- useEffect as useEffect12,
2607
- useMemo as useMemo5
2832
+ createContext as createContext7,
2833
+ useContext as useContext12,
2834
+ useEffect as useEffect13,
2835
+ useMemo as useMemo6
2608
2836
  } from "react";
2609
2837
 
2610
2838
  // src/tile/texture-manager.ts
@@ -2834,8 +3062,8 @@ var TileTextureManager = class {
2834
3062
  };
2835
3063
 
2836
3064
  // src/tile/texture-manager-context.tsx
2837
- import { jsx as jsx6 } from "react/jsx-runtime";
2838
- var TileTextureManagerContext = createContext5(
3065
+ import { jsx as jsx7 } from "react/jsx-runtime";
3066
+ var TileTextureManagerContext = createContext7(
2839
3067
  null
2840
3068
  );
2841
3069
  function TileTextureManagerProvider({
@@ -2843,9 +3071,9 @@ function TileTextureManagerProvider({
2843
3071
  maxTextureMemoryMb = 128,
2844
3072
  maxConcurrentLoads = 4
2845
3073
  }) {
2846
- const gl = useThree8((state) => state.gl);
3074
+ const gl = useThree9((state) => state.gl);
2847
3075
  const anisotropy = gl.capabilities.getMaxAnisotropy();
2848
- const manager = useMemo5(
3076
+ const manager = useMemo6(
2849
3077
  () => new TileTextureManager({
2850
3078
  anisotropy,
2851
3079
  maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
@@ -2854,23 +3082,23 @@ function TileTextureManagerProvider({
2854
3082
  }),
2855
3083
  [anisotropy, maxConcurrentLoads, maxTextureMemoryMb]
2856
3084
  );
2857
- useEffect12(() => {
3085
+ useEffect13(() => {
2858
3086
  manager.resume();
2859
3087
  return () => manager.dispose();
2860
3088
  }, [manager]);
2861
- return /* @__PURE__ */ jsx6(TileTextureManagerContext.Provider, { value: manager, children });
3089
+ return /* @__PURE__ */ jsx7(TileTextureManagerContext.Provider, { value: manager, children });
2862
3090
  }
2863
3091
  function useSharedTileTextureManager() {
2864
- return useContext9(TileTextureManagerContext);
3092
+ return useContext12(TileTextureManagerContext);
2865
3093
  }
2866
3094
 
2867
3095
  // src/tile/tile.tsx
2868
- import { useFrame as useFrame5, useThree as useThree9 } from "@react-three/fiber";
3096
+ import { useFrame as useFrame5, useThree as useThree10 } from "@react-three/fiber";
2869
3097
  import {
2870
3098
  memo,
2871
- useEffect as useEffect13,
2872
- useMemo as useMemo6,
2873
- useRef as useRef11,
3099
+ useEffect as useEffect14,
3100
+ useMemo as useMemo7,
3101
+ useRef as useRef12,
2874
3102
  useState as useState5
2875
3103
  } from "react";
2876
3104
  import {
@@ -3068,7 +3296,7 @@ function resolveTemplateUrl(template, address) {
3068
3296
  }
3069
3297
 
3070
3298
  // src/tile/tile.tsx
3071
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
3299
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
3072
3300
  var MAX_LOD_UPDATE_INTERVAL_MS = 100;
3073
3301
  var FAST_ROTATION_THRESHOLD_RADIANS = Math.PI / 180;
3074
3302
  var FAST_FOV_THRESHOLD_DEGREES = 0.5;
@@ -3078,9 +3306,9 @@ var PREVIEW_RADIUS = CUBE_RADIUS + 0.05;
3078
3306
  var SHARED_TILE_GEOMETRY = new PlaneGeometry(1, 1);
3079
3307
  function usePreviewTexture(url, anisotropy, onError) {
3080
3308
  const [texture, setTexture] = useState5(null);
3081
- const onErrorRef = useRef11(onError);
3309
+ const onErrorRef = useRef12(onError);
3082
3310
  onErrorRef.current = onError;
3083
- useEffect13(() => {
3311
+ useEffect14(() => {
3084
3312
  if (!url) {
3085
3313
  setTexture(null);
3086
3314
  return;
@@ -3117,7 +3345,7 @@ function usePreviewTexture(url, anisotropy, onError) {
3117
3345
  active = false;
3118
3346
  };
3119
3347
  }, [anisotropy, url]);
3120
- useEffect13(
3348
+ useEffect14(
3121
3349
  () => () => {
3122
3350
  texture?.dispose();
3123
3351
  },
@@ -3130,7 +3358,7 @@ function PreviewFace({
3130
3358
  texture,
3131
3359
  faceOrder
3132
3360
  }) {
3133
- const geometry = useMemo6(() => {
3361
+ const geometry = useMemo7(() => {
3134
3362
  const nextGeometry = new PlaneGeometry(
3135
3363
  PREVIEW_RADIUS * 2,
3136
3364
  PREVIEW_RADIUS * 2
@@ -3148,21 +3376,21 @@ function PreviewFace({
3148
3376
  uv.needsUpdate = true;
3149
3377
  return nextGeometry;
3150
3378
  }, [face, faceOrder]);
3151
- useEffect13(
3379
+ useEffect14(
3152
3380
  () => () => {
3153
3381
  geometry.dispose();
3154
3382
  },
3155
3383
  [geometry]
3156
3384
  );
3157
3385
  const transform = getFaceTransform(face, PREVIEW_RADIUS);
3158
- return /* @__PURE__ */ jsx7(
3386
+ return /* @__PURE__ */ jsx8(
3159
3387
  "mesh",
3160
3388
  {
3161
3389
  geometry,
3162
3390
  position: transform.position,
3163
3391
  renderOrder: -100,
3164
3392
  rotation: transform.rotation,
3165
- children: /* @__PURE__ */ jsx7(
3393
+ children: /* @__PURE__ */ jsx8(
3166
3394
  "meshBasicMaterial",
3167
3395
  {
3168
3396
  depthTest: false,
@@ -3178,7 +3406,7 @@ function PreviewCube({
3178
3406
  texture,
3179
3407
  faceOrder
3180
3408
  }) {
3181
- return CUBE_FACES.map((face) => /* @__PURE__ */ jsx7(
3409
+ return CUBE_FACES.map((face) => /* @__PURE__ */ jsx8(
3182
3410
  PreviewFace,
3183
3411
  {
3184
3412
  face,
@@ -3196,7 +3424,7 @@ function useManagedTexture(manager, key, url, address) {
3196
3424
  snapshot: manager.getSnapshot(key)
3197
3425
  }));
3198
3426
  const snapshot = managedState.manager === manager && managedState.key === key && managedState.url === url ? managedState.snapshot : manager.getSnapshot(key);
3199
- useEffect13(
3427
+ useEffect14(
3200
3428
  () => manager.acquire(key, url, address, (nextSnapshot) => {
3201
3429
  setManagedState({
3202
3430
  manager,
@@ -3223,14 +3451,14 @@ var TileMesh = memo(function TileMesh2({
3223
3451
  if (!snapshot.texture) {
3224
3452
  return null;
3225
3453
  }
3226
- return /* @__PURE__ */ jsx7("group", { position: transform.position, rotation: transform.rotation, children: /* @__PURE__ */ jsx7(
3454
+ return /* @__PURE__ */ jsx8("group", { position: transform.position, rotation: transform.rotation, children: /* @__PURE__ */ jsx8(
3227
3455
  "mesh",
3228
3456
  {
3229
3457
  geometry: SHARED_TILE_GEOMETRY,
3230
3458
  position: [layout.x, layout.y, 0],
3231
3459
  renderOrder,
3232
3460
  scale: [layout.width, layout.height, 1],
3233
- children: /* @__PURE__ */ jsx7(
3461
+ children: /* @__PURE__ */ jsx8(
3234
3462
  "meshBasicMaterial",
3235
3463
  {
3236
3464
  depthTest: false,
@@ -3247,7 +3475,7 @@ var TileLayerMeshes = memo(function TileLayerMeshes2({
3247
3475
  manager,
3248
3476
  resolveUrl
3249
3477
  }) {
3250
- return layer.addresses.map((address) => /* @__PURE__ */ jsx7(
3478
+ return layer.addresses.map((address) => /* @__PURE__ */ jsx8(
3251
3479
  TileMesh,
3252
3480
  {
3253
3481
  address,
@@ -3415,11 +3643,11 @@ function Tile({
3415
3643
  onTileError,
3416
3644
  onLevelChange
3417
3645
  }) {
3418
- const { camera, gl, size } = useThree9();
3419
- const multiresConfig = useMemo6(() => parseMultires(multires), [multires]);
3646
+ const { camera, gl, size } = useThree10();
3647
+ const multiresConfig = useMemo7(() => parseMultires(multires), [multires]);
3420
3648
  const { levels, tileSize } = multiresConfig;
3421
3649
  const multiresToken = `${tileSize}:${levels.join(",")}`;
3422
- const candidatesByLevel = useMemo6(
3650
+ const candidatesByLevel = useMemo7(
3423
3651
  () => buildTileCandidates(levels, tileSize),
3424
3652
  [levels, tileSize]
3425
3653
  );
@@ -3431,13 +3659,13 @@ function Tile({
3431
3659
  onPreviewError
3432
3660
  );
3433
3661
  const sharedManager = useSharedTileTextureManager();
3434
- const progressRef = useRef11(onLoadProgress);
3435
- const errorRef = useRef11(onTileError);
3436
- const levelChangeRef = useRef11(onLevelChange);
3662
+ const progressRef = useRef12(onLoadProgress);
3663
+ const errorRef = useRef12(onTileError);
3664
+ const levelChangeRef = useRef12(onLevelChange);
3437
3665
  progressRef.current = onLoadProgress;
3438
3666
  errorRef.current = onTileError;
3439
3667
  levelChangeRef.current = onLevelChange;
3440
- const ownedManager = useMemo6(
3668
+ const ownedManager = useMemo7(
3441
3669
  () => new TileTextureManager({
3442
3670
  anisotropy,
3443
3671
  maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
@@ -3458,7 +3686,7 @@ function Tile({
3458
3686
  ]
3459
3687
  );
3460
3688
  const manager = sharedManager ?? ownedManager;
3461
- useEffect13(() => {
3689
+ useEffect14(() => {
3462
3690
  manager.resume();
3463
3691
  return () => {
3464
3692
  if (manager === ownedManager) {
@@ -3468,11 +3696,11 @@ function Tile({
3468
3696
  queueMicrotask(() => manager.releaseUnused());
3469
3697
  };
3470
3698
  }, [manager, ownedManager]);
3471
- const readyRef = useRef11(false);
3472
- useEffect13(() => {
3699
+ const readyRef = useRef12(false);
3700
+ useEffect14(() => {
3473
3701
  readyRef.current = false;
3474
3702
  }, [effectivePreviewUrl]);
3475
- useEffect13(() => {
3703
+ useEffect14(() => {
3476
3704
  if (!previewTexture || readyRef.current) {
3477
3705
  return;
3478
3706
  }
@@ -3480,7 +3708,7 @@ function Tile({
3480
3708
  onReady?.();
3481
3709
  }, [onReady, previewTexture]);
3482
3710
  const template = urlTemplate ?? buildDefaultTileUrlTemplate();
3483
- const resolveUrl = useMemo6(
3711
+ const resolveUrl = useMemo7(
3484
3712
  () => (address) => {
3485
3713
  const relativePath = resolveTileUrl ? resolveTileUrl(address) : resolveTemplateUrl(template, address);
3486
3714
  return resolveRelativeTileUrl(baseUrl, relativePath);
@@ -3488,23 +3716,23 @@ function Tile({
3488
3716
  [baseUrl, resolveTileUrl, template]
3489
3717
  );
3490
3718
  const [layers, setLayers] = useState5([]);
3491
- const layerTokenRef = useRef11("");
3492
- const currentLevelRef = useRef11(1);
3493
- const lastUpdateAtRef = useRef11(-Infinity);
3494
- const visibilityDirtyRef = useRef11(true);
3495
- const lastVisibilityQuaternionRef = useRef11(camera.quaternion.clone());
3496
- const lastVisibilityFovRef = useRef11(Number.NaN);
3497
- const lastViewportRef = useRef11({ width: 0, height: 0, dpr: 0 });
3498
- const frustumRef = useRef11(new Frustum());
3499
- const projectionMatrixRef = useRef11(new Matrix4());
3500
- const visibilityCameraRef = useRef11(new PerspectiveCamera2());
3501
- useEffect13(() => {
3719
+ const layerTokenRef = useRef12("");
3720
+ const currentLevelRef = useRef12(1);
3721
+ const lastUpdateAtRef = useRef12(-Infinity);
3722
+ const visibilityDirtyRef = useRef12(true);
3723
+ const lastVisibilityQuaternionRef = useRef12(camera.quaternion.clone());
3724
+ const lastVisibilityFovRef = useRef12(Number.NaN);
3725
+ const lastViewportRef = useRef12({ width: 0, height: 0, dpr: 0 });
3726
+ const frustumRef = useRef12(new Frustum());
3727
+ const projectionMatrixRef = useRef12(new Matrix4());
3728
+ const visibilityCameraRef = useRef12(new PerspectiveCamera2());
3729
+ useEffect14(() => {
3502
3730
  currentLevelRef.current = 1;
3503
3731
  layerTokenRef.current = "";
3504
3732
  visibilityDirtyRef.current = true;
3505
3733
  setLayers([]);
3506
3734
  }, [manager, multiresToken]);
3507
- useEffect13(() => {
3735
+ useEffect14(() => {
3508
3736
  if (loadMode !== "preview") {
3509
3737
  return;
3510
3738
  }
@@ -3585,14 +3813,14 @@ function Tile({
3585
3813
  commitLayers(nextLayers);
3586
3814
  });
3587
3815
  return /* @__PURE__ */ jsxs5("group", { visible, children: [
3588
- previewTexture ? /* @__PURE__ */ jsx7(
3816
+ previewTexture ? /* @__PURE__ */ jsx8(
3589
3817
  PreviewCube,
3590
3818
  {
3591
3819
  texture: previewTexture,
3592
3820
  faceOrder: previewFaceOrder
3593
3821
  }
3594
3822
  ) : null,
3595
- layers.map((layer) => /* @__PURE__ */ jsx7(
3823
+ layers.map((layer) => /* @__PURE__ */ jsx8(
3596
3824
  TileLayerMeshes,
3597
3825
  {
3598
3826
  layer,
@@ -3604,8 +3832,8 @@ function Tile({
3604
3832
  ] });
3605
3833
  }
3606
3834
 
3607
- // src/panorama-scenes.tsx
3608
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
3835
+ // src/scenes.tsx
3836
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
3609
3837
  var TRANSITION_DEFAULTS = {
3610
3838
  none: { duration: 0, krpanoBlend: "NOBLEND" },
3611
3839
  crossfade: { duration: 1, krpanoBlend: "BLEND(1.0, easeInCubic)" },
@@ -3655,7 +3883,7 @@ function SceneSource({
3655
3883
  return null;
3656
3884
  }
3657
3885
  if (scene.type === "sphere") {
3658
- return /* @__PURE__ */ jsx8(
3886
+ return /* @__PURE__ */ jsx9(
3659
3887
  Sphere,
3660
3888
  {
3661
3889
  src: scene.src,
@@ -3667,7 +3895,7 @@ function SceneSource({
3667
3895
  );
3668
3896
  }
3669
3897
  const { id: _id, type: _type, ...tileProps } = scene;
3670
- return /* @__PURE__ */ jsx8(
3898
+ return /* @__PURE__ */ jsx9(
3671
3899
  Tile,
3672
3900
  {
3673
3901
  ...tileProps,
@@ -3683,10 +3911,10 @@ function SnapshotCapture({
3683
3911
  maxPixels,
3684
3912
  onCaptured
3685
3913
  }) {
3686
- const { camera, gl, scene, size } = useThree10();
3687
- const capturedRequestRef = useRef12(0);
3688
- const renderTargetRef = useRef12(null);
3689
- useEffect14(
3914
+ const { camera, gl, scene, size } = useThree11();
3915
+ const capturedRequestRef = useRef13(0);
3916
+ const renderTargetRef = useRef13(null);
3917
+ useEffect15(
3690
3918
  () => () => {
3691
3919
  renderTargetRef.current?.dispose();
3692
3920
  renderTargetRef.current = null;
@@ -3739,12 +3967,12 @@ function SnapshotOverlay({
3739
3967
  runId,
3740
3968
  onFinish
3741
3969
  }) {
3742
- const { camera, size } = useThree10();
3743
- const meshRef = useRef12(null);
3744
- const startRef = useRef12(null);
3745
- const finishedRef = useRef12(false);
3746
- const directionRef = useRef12(new Vector35());
3747
- const material = useMemo7(
3970
+ const { camera, size } = useThree11();
3971
+ const meshRef = useRef13(null);
3972
+ const startRef = useRef13(null);
3973
+ const finishedRef = useRef13(false);
3974
+ const directionRef = useRef13(new Vector35());
3975
+ const material = useMemo8(
3748
3976
  () => new ShaderMaterial({
3749
3977
  transparent: true,
3750
3978
  depthTest: false,
@@ -3823,8 +4051,8 @@ function SnapshotOverlay({
3823
4051
  }),
3824
4052
  [snapshot.texture, transition.preset]
3825
4053
  );
3826
- useEffect14(() => () => material.dispose(), [material]);
3827
- useEffect14(() => {
4054
+ useEffect15(() => () => material.dispose(), [material]);
4055
+ useEffect15(() => {
3828
4056
  startRef.current = null;
3829
4057
  finishedRef.current = false;
3830
4058
  material.uniforms.progress.value = 0;
@@ -3855,12 +4083,12 @@ function SnapshotOverlay({
3855
4083
  }
3856
4084
  });
3857
4085
  return /* @__PURE__ */ jsxs6("mesh", { ref: meshRef, renderOrder: 1e4, frustumCulled: false, children: [
3858
- /* @__PURE__ */ jsx8("primitive", { attach: "geometry", object: FULLSCREEN_PLANE }),
3859
- /* @__PURE__ */ jsx8("primitive", { attach: "material", object: material })
4086
+ /* @__PURE__ */ jsx9("primitive", { attach: "geometry", object: FULLSCREEN_PLANE }),
4087
+ /* @__PURE__ */ jsx9("primitive", { attach: "material", object: material })
3860
4088
  ] });
3861
4089
  }
3862
4090
  var FULLSCREEN_PLANE = new PlaneGeometry2(1, 1);
3863
- function PanoramaScenesController({
4091
+ function ScenesController({
3864
4092
  scenes,
3865
4093
  activeSceneId,
3866
4094
  transition: transitionInput,
@@ -3869,13 +4097,13 @@ function PanoramaScenesController({
3869
4097
  onTransitionError,
3870
4098
  snapshotMaxPixels = 3686400
3871
4099
  }) {
3872
- const controlsRef = useContext10(PanoramaViewContext);
4100
+ const controlsRef = useContext13(PanoramaViewContext);
3873
4101
  const manager = useSharedTileTextureManager();
3874
- const transition = useMemo7(
4102
+ const transition = useMemo8(
3875
4103
  () => resolveTransition(transitionInput),
3876
4104
  [transitionInput]
3877
4105
  );
3878
- const sceneMap = useMemo7(
4106
+ const sceneMap = useMemo8(
3879
4107
  () => new Map(scenes.map((scene) => [scene.id, scene])),
3880
4108
  [scenes]
3881
4109
  );
@@ -3892,24 +4120,24 @@ function PanoramaScenesController({
3892
4120
  const [transitionRunId, setTransitionRunId] = useState6(0);
3893
4121
  const [snapshot, setSnapshot] = useState6(null);
3894
4122
  const [recoveryKey, setRecoveryKey] = useState6(0);
3895
- const requestedSceneIdRef = useRef12(activeSceneId);
3896
- const captureReasonRef = useRef12("start");
3897
- const interactionReleaseRef = useRef12(null);
3898
- const slotsRef = useRef12(slots);
3899
- const phaseRef = useRef12(phase);
3900
- const liveSlotRef = useRef12(liveSlot);
3901
- const transitionFromRef = useRef12(initialScene);
4123
+ const requestedSceneIdRef = useRef13(activeSceneId);
4124
+ const captureReasonRef = useRef13("start");
4125
+ const interactionReleaseRef = useRef13(null);
4126
+ const slotsRef = useRef13(slots);
4127
+ const phaseRef = useRef13(phase);
4128
+ const liveSlotRef = useRef13(liveSlot);
4129
+ const transitionFromRef = useRef13(initialScene);
3902
4130
  slotsRef.current = slots;
3903
4131
  phaseRef.current = phase;
3904
4132
  liveSlotRef.current = liveSlot;
3905
- const releaseInteraction = useCallback4(() => {
4133
+ const releaseInteraction = useCallback5(() => {
3906
4134
  interactionReleaseRef.current?.();
3907
4135
  interactionReleaseRef.current = null;
3908
4136
  }, []);
3909
- const reportError = useCallback4((sceneId, error) => {
4137
+ const reportError = useCallback5((sceneId, error) => {
3910
4138
  onTransitionError?.({ sceneId, error });
3911
4139
  }, [onTransitionError]);
3912
- const startPreload = useCallback4((scene) => {
4140
+ const startPreload = useCallback5((scene) => {
3913
4141
  const nextSlot = 1 - liveSlotRef.current;
3914
4142
  requestedSceneIdRef.current = scene.id;
3915
4143
  setSlots((current) => {
@@ -3920,7 +4148,7 @@ function PanoramaScenesController({
3920
4148
  setPendingSlot(nextSlot);
3921
4149
  setPhase("preloading");
3922
4150
  }, []);
3923
- useEffect14(() => {
4151
+ useEffect15(() => {
3924
4152
  const requested = sceneMap.get(activeSceneId);
3925
4153
  if (!requested) {
3926
4154
  reportError(activeSceneId, new Error(`Unknown panorama scene: ${activeSceneId}`));
@@ -3944,7 +4172,7 @@ function PanoramaScenesController({
3944
4172
  }
3945
4173
  startPreload(requested);
3946
4174
  }, [activeSceneId, pendingSlot, reportError, sceneMap, startPreload]);
3947
- const handleReady = useCallback4((slot) => {
4175
+ const handleReady = useCallback5((slot) => {
3948
4176
  if (slot !== pendingSlot || phaseRef.current !== "preloading") {
3949
4177
  return;
3950
4178
  }
@@ -3975,7 +4203,7 @@ function PanoramaScenesController({
3975
4203
  setPhase("capturing");
3976
4204
  setCaptureRequestId((current) => current + 1);
3977
4205
  }, [controlsRef, onTransitionEnd, pendingSlot, transition.preset]);
3978
- const handleCaptured = useCallback4((nextSnapshot) => {
4206
+ const handleCaptured = useCallback5((nextSnapshot) => {
3979
4207
  setSnapshot((current) => {
3980
4208
  current?.dispose();
3981
4209
  return nextSnapshot;
@@ -3997,7 +4225,7 @@ function PanoramaScenesController({
3997
4225
  setPhase("transitioning");
3998
4226
  setTransitionRunId((current) => current + 1);
3999
4227
  }, [manager, pendingSlot, sceneMap, startPreload]);
4000
- const finishTransition = useCallback4(() => {
4228
+ const finishTransition = useCallback5(() => {
4001
4229
  const nextSlot = pendingSlot;
4002
4230
  const previousScene = transitionFromRef.current;
4003
4231
  const nextScene = nextSlot === null ? null : slotsRef.current[nextSlot];
@@ -4022,12 +4250,12 @@ function PanoramaScenesController({
4022
4250
  });
4023
4251
  }
4024
4252
  }, [onTransitionEnd, pendingSlot, releaseInteraction, snapshot, transition.preset]);
4025
- useEffect14(() => () => {
4253
+ useEffect15(() => () => {
4026
4254
  releaseInteraction();
4027
4255
  snapshot?.dispose();
4028
4256
  }, [releaseInteraction, snapshot]);
4029
- const { gl } = useThree10();
4030
- useEffect14(() => {
4257
+ const { gl } = useThree11();
4258
+ useEffect15(() => {
4031
4259
  const canvas = gl.domElement;
4032
4260
  const onContextLost = (event) => {
4033
4261
  event.preventDefault();
@@ -4052,7 +4280,7 @@ function PanoramaScenesController({
4052
4280
  const renderedScene = slots[liveSlot];
4053
4281
  const snapshotVisible = snapshot !== null && (phase === "transitioning" || phase === "recapturing" || phase === "preloading");
4054
4282
  return /* @__PURE__ */ jsxs6(Fragment, { children: [
4055
- /* @__PURE__ */ jsx8(
4283
+ /* @__PURE__ */ jsx9(
4056
4284
  SnapshotCapture,
4057
4285
  {
4058
4286
  requestId: captureRequestId,
@@ -4064,7 +4292,7 @@ function PanoramaScenesController({
4064
4292
  const source = slots[slot];
4065
4293
  const isLive = slot === liveSlot;
4066
4294
  const isPending = slot === pendingSlot;
4067
- return /* @__PURE__ */ jsx8(
4295
+ return /* @__PURE__ */ jsx9(
4068
4296
  SceneSource,
4069
4297
  {
4070
4298
  scene: source,
@@ -4080,7 +4308,7 @@ function PanoramaScenesController({
4080
4308
  `${slot}:${source?.id ?? "empty"}:${recoveryKey}`
4081
4309
  );
4082
4310
  }),
4083
- snapshotVisible ? /* @__PURE__ */ jsx8(
4311
+ snapshotVisible ? /* @__PURE__ */ jsx9(
4084
4312
  SnapshotOverlay,
4085
4313
  {
4086
4314
  snapshot,
@@ -4093,27 +4321,26 @@ function PanoramaScenesController({
4093
4321
  phase === "idle" && renderedScene ? renderHotspots?.(renderedScene) : null
4094
4322
  ] });
4095
4323
  }
4096
- function PanoramaScenes({
4324
+ function Scenes({
4097
4325
  maxTextureMemoryMb,
4098
4326
  maxConcurrentTileLoads,
4099
4327
  ...props
4100
4328
  }) {
4101
- return /* @__PURE__ */ jsx8(
4329
+ return /* @__PURE__ */ jsx9(
4102
4330
  TileTextureManagerProvider,
4103
4331
  {
4104
4332
  maxTextureMemoryMb,
4105
4333
  maxConcurrentLoads: maxConcurrentTileLoads,
4106
- children: /* @__PURE__ */ jsx8(PanoramaScenesController, { ...props })
4334
+ children: /* @__PURE__ */ jsx9(ScenesController, { ...props })
4107
4335
  }
4108
4336
  );
4109
4337
  }
4110
4338
 
4111
4339
  // src/hotspot/image-hotspot.tsx
4112
- import { useThree as useThree11 } from "@react-three/fiber";
4113
- import { useEffect as useEffect16, useRef as useRef14, useState as useState7 } from "react";
4340
+ import { useThree as useThree12 } from "@react-three/fiber";
4341
+ import { useEffect as useEffect18, useRef as useRef15, useState as useState9 } from "react";
4114
4342
  import {
4115
4343
  ClampToEdgeWrapping as ClampToEdgeWrapping3,
4116
- DoubleSide as DoubleSide2,
4117
4344
  LinearFilter as LinearFilter5,
4118
4345
  LinearMipmapLinearFilter as LinearMipmapLinearFilter2,
4119
4346
  SRGBColorSpace as SRGBColorSpace4,
@@ -4123,12 +4350,14 @@ import {
4123
4350
  // src/hotspot/hotspot-anchor.tsx
4124
4351
  import { useFrame as useFrame7 } from "@react-three/fiber";
4125
4352
  import {
4126
- useContext as useContext11,
4127
- useEffect as useEffect15,
4128
- useMemo as useMemo8,
4129
- useRef as useRef13
4353
+ useContext as useContext14,
4354
+ useEffect as useEffect17,
4355
+ useMemo as useMemo10,
4356
+ useRef as useRef14,
4357
+ useState as useState8
4130
4358
  } from "react";
4131
4359
  import {
4360
+ Box3 as Box33,
4132
4361
  EdgesGeometry,
4133
4362
  MathUtils as MathUtils4,
4134
4363
  Matrix4 as Matrix42,
@@ -4137,7 +4366,129 @@ import {
4137
4366
  Quaternion as Quaternion2,
4138
4367
  Vector3 as Vector36
4139
4368
  } from "three";
4140
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
4369
+
4370
+ // src/hotspot/hotspot-tooltip.tsx
4371
+ import { Html } from "@react-three/drei";
4372
+ import { useEffect as useEffect16, useMemo as useMemo9, useState as useState7 } from "react";
4373
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
4374
+ var TOOLTIP_MAX_WIDTH = 220;
4375
+ var TOOLTIP_IMAGE_MAX_HEIGHT = 120;
4376
+ var DEFAULT_HOTSPOT_TOOLTIP_OFFSET = 12;
4377
+ var WRAPPER_STYLE = {
4378
+ pointerEvents: "none"
4379
+ };
4380
+ var BUBBLE_STYLE = {
4381
+ background: "rgba(22, 22, 22, 0.72)",
4382
+ border: "1px solid rgba(46, 46, 46, 0.7)",
4383
+ borderRadius: 8,
4384
+ boxShadow: "0 8px 24px rgba(0, 0, 0, 0.35)",
4385
+ boxSizing: "border-box",
4386
+ color: "#f5fbfc",
4387
+ display: "flex",
4388
+ flexDirection: "column",
4389
+ gap: 8,
4390
+ padding: 8,
4391
+ pointerEvents: "none",
4392
+ whiteSpace: "nowrap"
4393
+ };
4394
+ var IMAGE_STYLE = {
4395
+ borderRadius: 4,
4396
+ display: "block",
4397
+ height: "auto",
4398
+ maxHeight: TOOLTIP_IMAGE_MAX_HEIGHT,
4399
+ maxWidth: TOOLTIP_MAX_WIDTH,
4400
+ objectFit: "contain"
4401
+ };
4402
+ var TEXT_STYLE = {
4403
+ fontFamily: "system-ui, sans-serif",
4404
+ fontSize: 12,
4405
+ lineHeight: 1.4,
4406
+ margin: 0,
4407
+ overflow: "visible",
4408
+ whiteSpace: "nowrap"
4409
+ };
4410
+ function resolveHotspotTooltipContent(tooltip) {
4411
+ if (tooltip == null) {
4412
+ return null;
4413
+ }
4414
+ if (typeof tooltip === "string") {
4415
+ const text2 = tooltip.trim();
4416
+ return text2 ? { text: text2 } : null;
4417
+ }
4418
+ const text = tooltip.text?.trim();
4419
+ const image = tooltip.image?.trim();
4420
+ if (!text && !image) {
4421
+ return null;
4422
+ }
4423
+ const imageAlt = tooltip.imageAlt?.trim();
4424
+ return {
4425
+ image: image || void 0,
4426
+ imageAlt: imageAlt || void 0,
4427
+ text: text || void 0
4428
+ };
4429
+ }
4430
+ function resolveHotspotTooltipOffset(offset) {
4431
+ if (!Number.isFinite(offset)) {
4432
+ return DEFAULT_HOTSPOT_TOOLTIP_OFFSET;
4433
+ }
4434
+ return Math.max(0, offset);
4435
+ }
4436
+ function tooltipBubbleTransform(placement, offset) {
4437
+ const gap = `${offset}px`;
4438
+ switch (placement) {
4439
+ case "top":
4440
+ return `translate(-50%, calc(-100% - ${gap}))`;
4441
+ case "bottom":
4442
+ return `translate(-50%, ${gap})`;
4443
+ case "left":
4444
+ return `translate(calc(-100% - ${gap}), -50%)`;
4445
+ case "right":
4446
+ return `translate(${gap}, -50%)`;
4447
+ default: {
4448
+ const exhaustive = placement;
4449
+ return exhaustive;
4450
+ }
4451
+ }
4452
+ }
4453
+ function HotspotTooltip({
4454
+ content,
4455
+ placement = "top",
4456
+ offset = DEFAULT_HOTSPOT_TOOLTIP_OFFSET
4457
+ }) {
4458
+ const [imageFailed, setImageFailed] = useState7(false);
4459
+ useEffect16(() => {
4460
+ setImageFailed(false);
4461
+ }, [content.image]);
4462
+ const showImage = Boolean(content.image) && !imageFailed;
4463
+ const bubbleStyle = useMemo9(
4464
+ () => ({
4465
+ ...BUBBLE_STYLE,
4466
+ transform: tooltipBubbleTransform(
4467
+ placement,
4468
+ resolveHotspotTooltipOffset(offset)
4469
+ )
4470
+ }),
4471
+ [offset, placement]
4472
+ );
4473
+ if (!showImage && !content.text) {
4474
+ return null;
4475
+ }
4476
+ return /* @__PURE__ */ jsx10(Html, { pointerEvents: "none", style: WRAPPER_STYLE, zIndexRange: [20, 10], children: /* @__PURE__ */ jsxs7("div", { role: "tooltip", style: bubbleStyle, children: [
4477
+ showImage ? /* @__PURE__ */ jsx10(
4478
+ "img",
4479
+ {
4480
+ alt: content.imageAlt ?? content.text ?? "",
4481
+ onError: () => setImageFailed(true),
4482
+ src: content.image,
4483
+ style: IMAGE_STYLE
4484
+ }
4485
+ ) : null,
4486
+ content.text ? /* @__PURE__ */ jsx10("p", { style: TEXT_STYLE, children: content.text }) : null
4487
+ ] }) });
4488
+ }
4489
+
4490
+ // src/hotspot/hotspot-anchor.tsx
4491
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
4141
4492
  var DEFAULT_FLOATING_DISTANCE = 10;
4142
4493
  var PANORAMA_RADIUS = DEFAULT_PANORAMA_RADIUS;
4143
4494
  var PANORAMA_SURFACE_INSET = 4;
@@ -4146,6 +4497,10 @@ var MIN_ANGULAR_SIZE = 0.01;
4146
4497
  var MAX_ANGULAR_SIZE = 179;
4147
4498
  var DRAG_EPSILON_DEGREES = 1e-3;
4148
4499
  var LOCAL_FORWARD = new Vector36(0, 0, 1);
4500
+ var TOOLTIP_WORLD_BOX = new Box33();
4501
+ var TOOLTIP_LOCAL_BOX = new Box33();
4502
+ var TOOLTIP_CORNER = new Vector36();
4503
+ var TOOLTIP_INVERSE = new Matrix42();
4149
4504
  var HOTSPOT_MODE_RENDERING = {
4150
4505
  surface: { orientation: "surface", placement: "surface" },
4151
4506
  billboard: { orientation: "billboard", placement: "floating" }
@@ -4216,6 +4571,100 @@ function angularDistance(a, b) {
4216
4571
  const bVector = panoPositionToVector3(b);
4217
4572
  return MathUtils4.radToDeg(aVector.angleTo(bVector));
4218
4573
  }
4574
+ function isTooltipOpen(trigger, hovered, focused, pinned) {
4575
+ switch (trigger) {
4576
+ case "always":
4577
+ return true;
4578
+ case "hover":
4579
+ return hovered || focused;
4580
+ case "click":
4581
+ return pinned;
4582
+ default: {
4583
+ const exhaustive = trigger;
4584
+ return exhaustive;
4585
+ }
4586
+ }
4587
+ }
4588
+ function setTooltipAnchorOnUnitPlane(anchor, placement) {
4589
+ switch (placement) {
4590
+ case "top":
4591
+ anchor.position.set(0, 0.5, 0);
4592
+ return;
4593
+ case "bottom":
4594
+ anchor.position.set(0, -0.5, 0);
4595
+ return;
4596
+ case "left":
4597
+ anchor.position.set(-0.5, 0, 0);
4598
+ return;
4599
+ case "right":
4600
+ anchor.position.set(0.5, 0, 0);
4601
+ return;
4602
+ default: {
4603
+ const exhaustive = placement;
4604
+ return exhaustive;
4605
+ }
4606
+ }
4607
+ }
4608
+ function setTooltipAnchorOnLocalBox(anchor, placement, minX, maxX, minY, maxY) {
4609
+ switch (placement) {
4610
+ case "top":
4611
+ anchor.position.set((minX + maxX) / 2, maxY, 0);
4612
+ return;
4613
+ case "bottom":
4614
+ anchor.position.set((minX + maxX) / 2, minY, 0);
4615
+ return;
4616
+ case "left":
4617
+ anchor.position.set(minX, (minY + maxY) / 2, 0);
4618
+ return;
4619
+ case "right":
4620
+ anchor.position.set(maxX, (minY + maxY) / 2, 0);
4621
+ return;
4622
+ default: {
4623
+ const exhaustive = placement;
4624
+ return exhaustive;
4625
+ }
4626
+ }
4627
+ }
4628
+ function updateTooltipAnchor(anchor, group, placement, useAngularScale) {
4629
+ if (useAngularScale) {
4630
+ setTooltipAnchorOnUnitPlane(anchor, placement);
4631
+ return;
4632
+ }
4633
+ group.updateMatrixWorld(true);
4634
+ TOOLTIP_WORLD_BOX.makeEmpty();
4635
+ for (const child of group.children) {
4636
+ if (child === anchor) {
4637
+ continue;
4638
+ }
4639
+ TOOLTIP_WORLD_BOX.expandByObject(child);
4640
+ }
4641
+ if (TOOLTIP_WORLD_BOX.isEmpty()) {
4642
+ setTooltipAnchorOnUnitPlane(anchor, placement);
4643
+ return;
4644
+ }
4645
+ TOOLTIP_INVERSE.copy(group.matrixWorld).invert();
4646
+ TOOLTIP_LOCAL_BOX.makeEmpty();
4647
+ for (let x = 0; x <= 1; x += 1) {
4648
+ for (let y = 0; y <= 1; y += 1) {
4649
+ for (let z = 0; z <= 1; z += 1) {
4650
+ TOOLTIP_CORNER.set(
4651
+ x === 0 ? TOOLTIP_WORLD_BOX.min.x : TOOLTIP_WORLD_BOX.max.x,
4652
+ y === 0 ? TOOLTIP_WORLD_BOX.min.y : TOOLTIP_WORLD_BOX.max.y,
4653
+ z === 0 ? TOOLTIP_WORLD_BOX.min.z : TOOLTIP_WORLD_BOX.max.z
4654
+ ).applyMatrix4(TOOLTIP_INVERSE);
4655
+ TOOLTIP_LOCAL_BOX.expandByPoint(TOOLTIP_CORNER);
4656
+ }
4657
+ }
4658
+ }
4659
+ setTooltipAnchorOnLocalBox(
4660
+ anchor,
4661
+ placement,
4662
+ TOOLTIP_LOCAL_BOX.min.x,
4663
+ TOOLTIP_LOCAL_BOX.max.x,
4664
+ TOOLTIP_LOCAL_BOX.min.y,
4665
+ TOOLTIP_LOCAL_BOX.max.y
4666
+ );
4667
+ }
4219
4668
  function HotspotAnchor({
4220
4669
  id,
4221
4670
  position,
@@ -4235,7 +4684,12 @@ function HotspotAnchor({
4235
4684
  visible = true,
4236
4685
  draggable = false,
4237
4686
  interactive = true,
4687
+ cursor,
4238
4688
  ariaLabel,
4689
+ tooltip,
4690
+ tooltipTrigger = "always",
4691
+ tooltipPlacement = "top",
4692
+ tooltipOffset,
4239
4693
  children,
4240
4694
  onClick,
4241
4695
  onHoverChange,
@@ -4243,10 +4697,20 @@ function HotspotAnchor({
4243
4697
  onPositionChange,
4244
4698
  onDragEnd
4245
4699
  }) {
4246
- const controlsRef = useContext11(PanoramaViewContext);
4247
- const groupRef = useRef13(null);
4248
- const dragStateRef = useRef13(null);
4249
- const suppressNextClickRef = useRef13(false);
4700
+ const controlsRef = useContext14(PanoramaViewContext);
4701
+ const eventBus = useContext14(PanoEventBusContext);
4702
+ const cursorApi = usePanoCursor();
4703
+ const groupRef = useRef14(null);
4704
+ const tooltipAnchorRef = useRef14(null);
4705
+ const dragStateRef = useRef14(null);
4706
+ const suppressNextClickRef = useRef14(false);
4707
+ const [hovered, setHovered] = useState8(false);
4708
+ const [dragging, setDragging] = useState8(false);
4709
+ const [pinned, setPinned] = useState8(false);
4710
+ const tooltipContent = useMemo10(
4711
+ () => resolveHotspotTooltipContent(tooltip),
4712
+ [tooltip]
4713
+ );
4250
4714
  const normalizedPosition = normalizePanoPosition(position);
4251
4715
  const rendering = internalOrientation && internalPlacement ? { orientation: internalOrientation, placement: internalPlacement } : HOTSPOT_MODE_RENDERING[mode];
4252
4716
  const { orientation: orientation2, placement } = rendering;
@@ -4259,27 +4723,33 @@ function HotspotAnchor({
4259
4723
  scaledWidth,
4260
4724
  scaledHeight
4261
4725
  );
4262
- const worldPosition = useMemo8(
4726
+ const worldPosition = useMemo10(
4263
4727
  () => panoPositionToVector3(normalizedPosition, resolvedDistance),
4264
4728
  [normalizedPosition.pitch, normalizedPosition.yaw, resolvedDistance]
4265
4729
  );
4266
- const surfaceQuaternion3 = useMemo8(
4730
+ const surfaceQuaternion3 = useMemo10(
4267
4731
  () => createSurfaceQuaternion(normalizedPosition),
4268
4732
  [normalizedPosition.pitch, normalizedPosition.yaw]
4269
4733
  );
4270
4734
  const worldWidth = angularSizeToWorldSize(scaledWidth, resolvedDistance);
4271
4735
  const worldHeight = angularSizeToWorldSize(scaledHeight, resolvedDistance);
4272
- const focusGeometry = useMemo8(() => {
4736
+ const focusGeometry = useMemo10(() => {
4273
4737
  const planeGeometry = new PlaneGeometry3(1, 1);
4274
4738
  const geometry = new EdgesGeometry(planeGeometry);
4275
4739
  planeGeometry.dispose();
4276
4740
  return geometry;
4277
4741
  }, []);
4742
+ const canActivateFromKeyboard = Boolean(
4743
+ visible && interactive && (onClick || tooltipTrigger === "click")
4744
+ );
4278
4745
  const focused = useHotspotAccessibility({
4279
4746
  id,
4280
4747
  ariaLabel,
4281
- onActivate: visible && interactive && onClick ? (event) => {
4282
- onClick({
4748
+ onActivate: canActivateFromKeyboard ? (event) => {
4749
+ if (tooltipTrigger === "click") {
4750
+ setPinned((current) => !current);
4751
+ }
4752
+ onClick?.({
4283
4753
  id,
4284
4754
  position: normalizedPosition,
4285
4755
  source: "keyboard",
@@ -4287,24 +4757,68 @@ function HotspotAnchor({
4287
4757
  });
4288
4758
  } : void 0
4289
4759
  });
4290
- useEffect15(
4760
+ const tooltipOpen = visible && tooltipContent !== null && isTooltipOpen(tooltipTrigger, hovered, focused, pinned);
4761
+ const cursorClaimId = hotspotCursorClaimId(id);
4762
+ useEffect17(
4291
4763
  () => () => {
4292
4764
  dragStateRef.current?.releaseInteractionLock();
4293
4765
  dragStateRef.current = null;
4294
4766
  },
4295
4767
  []
4296
4768
  );
4297
- useEffect15(() => () => focusGeometry.dispose(), [focusGeometry]);
4298
- useFrame7(({ camera }) => {
4299
- if (!groupRef.current) {
4769
+ useEffect17(() => {
4770
+ if (!cursorApi) {
4300
4771
  return;
4301
4772
  }
4302
- if (orientation2 === "billboard") {
4303
- groupRef.current.quaternion.copy(camera.quaternion);
4304
- } else {
4305
- groupRef.current.quaternion.copy(surfaceQuaternion3);
4773
+ if (interactive && visible && dragging) {
4774
+ cursorApi.claim(cursorClaimId, "hotspotDragging");
4775
+ return;
4306
4776
  }
4307
- groupRef.current.rotateOnAxis(
4777
+ if (interactive && visible && hovered) {
4778
+ cursorApi.claim(cursorClaimId, "hotspot", cursor);
4779
+ return;
4780
+ }
4781
+ cursorApi.release(cursorClaimId);
4782
+ }, [
4783
+ cursor,
4784
+ cursorApi,
4785
+ cursorClaimId,
4786
+ dragging,
4787
+ hovered,
4788
+ interactive,
4789
+ visible
4790
+ ]);
4791
+ useEffect17(
4792
+ () => () => {
4793
+ cursorApi?.release(cursorClaimId);
4794
+ },
4795
+ [cursorApi, cursorClaimId]
4796
+ );
4797
+ useEffect17(() => () => focusGeometry.dispose(), [focusGeometry]);
4798
+ useEffect17(() => {
4799
+ if (tooltipTrigger !== "click") {
4800
+ setPinned(false);
4801
+ return;
4802
+ }
4803
+ return eventBus?.subscribe("click", () => {
4804
+ setPinned(false);
4805
+ });
4806
+ }, [eventBus, tooltipTrigger]);
4807
+ useEffect17(() => {
4808
+ if (tooltipTrigger !== "hover") {
4809
+ setHovered(false);
4810
+ }
4811
+ }, [tooltipTrigger]);
4812
+ useFrame7(({ camera }) => {
4813
+ if (!groupRef.current) {
4814
+ return;
4815
+ }
4816
+ if (orientation2 === "billboard") {
4817
+ groupRef.current.quaternion.copy(camera.quaternion);
4818
+ } else {
4819
+ groupRef.current.quaternion.copy(surfaceQuaternion3);
4820
+ }
4821
+ groupRef.current.rotateOnAxis(
4308
4822
  LOCAL_FORWARD,
4309
4823
  MathUtils4.degToRad(-rotation)
4310
4824
  );
@@ -4314,6 +4828,14 @@ function HotspotAnchor({
4314
4828
  (useAngularScale ? worldHeight : 1) * scaleFactor,
4315
4829
  1
4316
4830
  );
4831
+ if (tooltipOpen && tooltipAnchorRef.current) {
4832
+ updateTooltipAnchor(
4833
+ tooltipAnchorRef.current,
4834
+ groupRef.current,
4835
+ tooltipPlacement,
4836
+ useAngularScale
4837
+ );
4838
+ }
4317
4839
  });
4318
4840
  const stopPointerEvent = (event) => {
4319
4841
  event.stopPropagation();
@@ -4340,8 +4862,12 @@ function HotspotAnchor({
4340
4862
  suppressNextClickRef.current = dragState.moved;
4341
4863
  dragState.releaseInteractionLock();
4342
4864
  dragStateRef.current = null;
4865
+ setDragging(false);
4866
+ if (emitDragEnd) {
4867
+ setHovered(true);
4868
+ }
4343
4869
  };
4344
- return /* @__PURE__ */ jsxs7(
4870
+ return /* @__PURE__ */ jsxs8(
4345
4871
  "group",
4346
4872
  {
4347
4873
  ref: groupRef,
@@ -4352,6 +4878,9 @@ function HotspotAnchor({
4352
4878
  onClick: interactive ? (event) => {
4353
4879
  stopPointerEvent(event);
4354
4880
  if (!suppressNextClickRef.current) {
4881
+ if (tooltipTrigger === "click") {
4882
+ setPinned((current) => !current);
4883
+ }
4355
4884
  onClick?.(makeInteractionEvent(id, normalizedPosition, event));
4356
4885
  }
4357
4886
  suppressNextClickRef.current = false;
@@ -4373,6 +4902,8 @@ function HotspotAnchor({
4373
4902
  releaseInteractionLock,
4374
4903
  moved: false
4375
4904
  };
4905
+ setHovered(false);
4906
+ setDragging(true);
4376
4907
  onDragStart?.({
4377
4908
  ...makeInteractionEvent(id, startPosition, event),
4378
4909
  source: "pointer",
@@ -4395,6 +4926,7 @@ function HotspotAnchor({
4395
4926
  } : void 0,
4396
4927
  onPointerOut: interactive ? (event) => {
4397
4928
  if (!dragStateRef.current) {
4929
+ setHovered(false);
4398
4930
  onHoverChange?.(
4399
4931
  false,
4400
4932
  makeInteractionEvent(id, normalizedPosition, event)
@@ -4403,6 +4935,7 @@ function HotspotAnchor({
4403
4935
  } : void 0,
4404
4936
  onPointerOver: interactive ? (event) => {
4405
4937
  if (!dragStateRef.current) {
4938
+ setHovered(true);
4406
4939
  onHoverChange?.(
4407
4940
  true,
4408
4941
  makeInteractionEvent(id, normalizedPosition, event)
@@ -4411,38 +4944,166 @@ function HotspotAnchor({
4411
4944
  } : void 0,
4412
4945
  onPointerUp: interactive ? (event) => releasePointer(event, true) : void 0,
4413
4946
  children: [
4414
- focused ? focusContent ?? /* @__PURE__ */ jsx9(
4947
+ focused ? focusContent ?? /* @__PURE__ */ jsx11(
4415
4948
  "lineSegments",
4416
4949
  {
4417
4950
  geometry: focusGeometry,
4418
4951
  position: [0, 0, 0.01],
4419
4952
  renderOrder: renderOrder + 1,
4420
4953
  scale: [1.12, 1.12, 1],
4421
- children: /* @__PURE__ */ jsx9("lineBasicMaterial", { color: "#f5fbfc", depthTest: false })
4954
+ children: /* @__PURE__ */ jsx11("lineBasicMaterial", { color: "#f5fbfc", depthTest: false })
4422
4955
  }
4423
4956
  ) : null,
4424
- children
4957
+ children,
4958
+ tooltipOpen && tooltipContent ? /* @__PURE__ */ jsx11("group", { ref: tooltipAnchorRef, children: /* @__PURE__ */ jsx11(
4959
+ HotspotTooltip,
4960
+ {
4961
+ content: tooltipContent,
4962
+ offset: resolveHotspotTooltipOffset(tooltipOffset),
4963
+ placement: tooltipPlacement
4964
+ }
4965
+ ) }) : null
4425
4966
  ]
4426
4967
  }
4427
4968
  );
4428
4969
  }
4429
4970
 
4430
- // src/hotspot/image-hotspot.tsx
4431
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
4971
+ // src/hotspot/hotspot-plane.tsx
4972
+ import {
4973
+ DoubleSide as DoubleSide2,
4974
+ Mesh as Mesh2,
4975
+ MeshBasicMaterial,
4976
+ Vector2 as Vector23
4977
+ } from "three";
4978
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
4979
+ var HOTSPOT_ALPHA_CUTOFF = 0.05;
4980
+ var SAMPLE_UV = new Vector23();
4981
+ var RAYCAST_HITS = [];
4982
+ var IMAGE_DATA_CACHE = /* @__PURE__ */ new WeakMap();
4432
4983
  function clampOpacity(opacity) {
4433
4984
  if (!Number.isFinite(opacity)) {
4434
4985
  return 1;
4435
4986
  }
4436
4987
  return Math.max(0, Math.min(opacity, 1));
4437
4988
  }
4989
+ function isSampleableImage(image) {
4990
+ if (image instanceof HTMLImageElement) {
4991
+ return image.naturalWidth > 0 && image.naturalHeight > 0;
4992
+ }
4993
+ if (image instanceof HTMLCanvasElement) {
4994
+ return image.width > 0 && image.height > 0;
4995
+ }
4996
+ if (typeof OffscreenCanvas !== "undefined" && image instanceof OffscreenCanvas) {
4997
+ return image.width > 0 && image.height > 0;
4998
+ }
4999
+ if (typeof ImageBitmap !== "undefined" && image instanceof ImageBitmap) {
5000
+ return image.width > 0 && image.height > 0;
5001
+ }
5002
+ return false;
5003
+ }
5004
+ function imageSize(image) {
5005
+ if (image instanceof HTMLImageElement) {
5006
+ return { width: image.naturalWidth, height: image.naturalHeight };
5007
+ }
5008
+ return { width: image.width, height: image.height };
5009
+ }
5010
+ function readImageData(image) {
5011
+ const cached = IMAGE_DATA_CACHE.get(image);
5012
+ if (cached) {
5013
+ return cached;
5014
+ }
5015
+ const { width, height } = imageSize(image);
5016
+ const canvas = document.createElement("canvas");
5017
+ canvas.width = width;
5018
+ canvas.height = height;
5019
+ const context = canvas.getContext("2d", { willReadFrequently: true });
5020
+ if (!context) {
5021
+ return null;
5022
+ }
5023
+ try {
5024
+ context.drawImage(image, 0, 0);
5025
+ const data = context.getImageData(0, 0, width, height);
5026
+ IMAGE_DATA_CACHE.set(image, data);
5027
+ return data;
5028
+ } catch {
5029
+ return null;
5030
+ }
5031
+ }
5032
+ function sampleTextureAlpha(texture, u, v) {
5033
+ if (!isSampleableImage(texture.image)) {
5034
+ return null;
5035
+ }
5036
+ const data = readImageData(texture.image);
5037
+ if (!data) {
5038
+ return null;
5039
+ }
5040
+ SAMPLE_UV.set(u, v);
5041
+ texture.transformUv(SAMPLE_UV);
5042
+ const pixelX = Math.min(
5043
+ data.width - 1,
5044
+ Math.max(0, Math.floor(SAMPLE_UV.x * data.width))
5045
+ );
5046
+ const pixelY = Math.min(
5047
+ data.height - 1,
5048
+ Math.max(0, Math.floor(SAMPLE_UV.y * data.height))
5049
+ );
5050
+ return data.data[(pixelY * data.width + pixelX) * 4 + 3] / 255;
5051
+ }
5052
+ function hotspotPlaneRaycast(raycaster, intersects2) {
5053
+ const material = Array.isArray(this.material) ? this.material[0] : this.material;
5054
+ if (!(material instanceof MeshBasicMaterial)) {
5055
+ Mesh2.prototype.raycast.call(this, raycaster, intersects2);
5056
+ return;
5057
+ }
5058
+ const cutoff = Math.max(material.alphaTest, HOTSPOT_ALPHA_CUTOFF);
5059
+ if (material.opacity < cutoff) {
5060
+ return;
5061
+ }
5062
+ RAYCAST_HITS.length = 0;
5063
+ Mesh2.prototype.raycast.call(this, raycaster, RAYCAST_HITS);
5064
+ const map = material.map;
5065
+ for (const hit of RAYCAST_HITS) {
5066
+ if (!map || !hit.uv) {
5067
+ intersects2.push(hit);
5068
+ continue;
5069
+ }
5070
+ const texelAlpha = sampleTextureAlpha(map, hit.uv.x, hit.uv.y);
5071
+ if (texelAlpha === null || texelAlpha * material.opacity >= cutoff) {
5072
+ intersects2.push(hit);
5073
+ }
5074
+ }
5075
+ }
5076
+ function HotspotPlane({
5077
+ map,
5078
+ opacity
5079
+ }) {
5080
+ return /* @__PURE__ */ jsxs9("mesh", { raycast: hotspotPlaneRaycast, children: [
5081
+ /* @__PURE__ */ jsx12("planeGeometry", { args: [1, 1] }),
5082
+ /* @__PURE__ */ jsx12(
5083
+ "meshBasicMaterial",
5084
+ {
5085
+ alphaTest: HOTSPOT_ALPHA_CUTOFF,
5086
+ alphaToCoverage: true,
5087
+ map,
5088
+ opacity: map ? clampOpacity(opacity) : 0,
5089
+ side: DoubleSide2,
5090
+ toneMapped: false,
5091
+ transparent: true
5092
+ }
5093
+ )
5094
+ ] });
5095
+ }
5096
+
5097
+ // src/hotspot/image-hotspot.tsx
5098
+ import { jsx as jsx13 } from "react/jsx-runtime";
4438
5099
  function useHotspotImageTexture(src, onLoad, onError) {
4439
- const gl = useThree11((state) => state.gl);
4440
- const [texture, setTexture] = useState7(null);
4441
- const onLoadRef = useRef14(onLoad);
4442
- const onErrorRef = useRef14(onError);
5100
+ const gl = useThree12((state) => state.gl);
5101
+ const [texture, setTexture] = useState9(null);
5102
+ const onLoadRef = useRef15(onLoad);
5103
+ const onErrorRef = useRef15(onError);
4443
5104
  onLoadRef.current = onLoad;
4444
5105
  onErrorRef.current = onError;
4445
- useEffect16(() => {
5106
+ useEffect18(() => {
4446
5107
  let active = true;
4447
5108
  setTexture(null);
4448
5109
  if (!src) {
@@ -4480,7 +5141,7 @@ function useHotspotImageTexture(src, onLoad, onError) {
4480
5141
  active = false;
4481
5142
  };
4482
5143
  }, [gl, src]);
4483
- useEffect16(
5144
+ useEffect18(
4484
5145
  () => () => {
4485
5146
  texture?.dispose();
4486
5147
  },
@@ -4498,24 +5159,11 @@ function ImageHotspot({
4498
5159
  ...anchorProps
4499
5160
  }) {
4500
5161
  const texture = useHotspotImageTexture(src, onLoad, onError);
4501
- return /* @__PURE__ */ jsx10(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsxs8("mesh", { children: [
4502
- /* @__PURE__ */ jsx10("planeGeometry", { args: [1, 1] }),
4503
- /* @__PURE__ */ jsx10(
4504
- "meshBasicMaterial",
4505
- {
4506
- map: texture,
4507
- opacity: texture ? clampOpacity(opacity) : 0,
4508
- side: DoubleSide2,
4509
- toneMapped: false,
4510
- transparent: true
4511
- }
4512
- )
4513
- ] }) });
5162
+ return /* @__PURE__ */ jsx13(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsx13(HotspotPlane, { map: texture, opacity }) });
4514
5163
  }
4515
5164
 
4516
5165
  // src/hotspot/polygon-hotspot.tsx
4517
- import { useThree as useThree12 } from "@react-three/fiber";
4518
- import { useEffect as useEffect17, useMemo as useMemo9, useRef as useRef15 } from "react";
5166
+ import { useEffect as useEffect19, useMemo as useMemo11, useRef as useRef16 } from "react";
4519
5167
  import {
4520
5168
  BufferGeometry,
4521
5169
  DoubleSide as DoubleSide3,
@@ -4523,13 +5171,39 @@ import {
4523
5171
  Matrix4 as Matrix43,
4524
5172
  Quaternion as Quaternion3,
4525
5173
  ShapeUtils,
4526
- Vector2 as Vector23,
5174
+ Vector2 as Vector24,
4527
5175
  Vector3 as Vector37
4528
5176
  } from "three";
4529
- import { Line2 } from "three/addons/lines/Line2.js";
4530
- import { LineGeometry } from "three/addons/lines/LineGeometry.js";
4531
- import { LineMaterial } from "three/addons/lines/LineMaterial.js";
4532
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
5177
+
5178
+ // src/hotspot/hotspot-stroke-line.tsx
5179
+ import { Line } from "@react-three/drei";
5180
+ import { jsx as jsx14 } from "react/jsx-runtime";
5181
+ function HotspotStrokeLine({
5182
+ points,
5183
+ color,
5184
+ lineWidth,
5185
+ opacity,
5186
+ renderOrder
5187
+ }) {
5188
+ return /* @__PURE__ */ jsx14(
5189
+ Line,
5190
+ {
5191
+ alphaToCoverage: true,
5192
+ color,
5193
+ depthTest: true,
5194
+ depthWrite: false,
5195
+ lineWidth: Math.max(0.5, lineWidth),
5196
+ opacity,
5197
+ points,
5198
+ renderOrder,
5199
+ transparent: true,
5200
+ worldUnits: false
5201
+ }
5202
+ );
5203
+ }
5204
+
5205
+ // src/hotspot/polygon-hotspot.tsx
5206
+ import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
4533
5207
  var POLYGON_RADIUS = 990;
4534
5208
  var MAX_FILL_SEGMENT_DEGREES = 7;
4535
5209
  function cloneVertices(vertices) {
@@ -4564,7 +5238,7 @@ function intersects(a, b, c, d) {
4564
5238
  }
4565
5239
  function hasSelfIntersection(vertices) {
4566
5240
  const points = vertices.map(
4567
- (vertex) => new Vector23(vertex.unwrappedYaw, vertex.pitch)
5241
+ (vertex) => new Vector24(vertex.unwrappedYaw, vertex.pitch)
4568
5242
  );
4569
5243
  for (let index = 0; index < points.length; index += 1) {
4570
5244
  const nextIndex = (index + 1) % points.length;
@@ -4713,7 +5387,7 @@ function addSubdividedTriangle(positions, indices, a, b, c, worldToLocal, steps)
4713
5387
  }
4714
5388
  function makeFillGeometry(vertices, center) {
4715
5389
  const unwrapped = unwrapPolygonVertices(vertices);
4716
- const contour = unwrapped.map((vertex) => new Vector23(vertex.unwrappedYaw, vertex.pitch));
5390
+ const contour = unwrapped.map((vertex) => new Vector24(vertex.unwrappedYaw, vertex.pitch));
4717
5391
  const triangles = ShapeUtils.triangulateShape(contour, []);
4718
5392
  const positions = [];
4719
5393
  const indices = [];
@@ -4733,7 +5407,7 @@ function makeFillGeometry(vertices, center) {
4733
5407
  return new BufferGeometry().setAttribute("position", new Float32BufferAttribute(positions, 3)).setIndex(indices);
4734
5408
  }
4735
5409
  function makeStrokePositions(vertices, center) {
4736
- const positions = [];
5410
+ const points = [];
4737
5411
  const worldToLocal = makeWorldToLocal(center);
4738
5412
  const unwrapped = unwrapPolygonVertices(vertices);
4739
5413
  const steps = polygonSubdivisionSteps(vertices);
@@ -4749,33 +5423,14 @@ function makeStrokePositions(vertices, center) {
4749
5423
  },
4750
5424
  POLYGON_RADIUS
4751
5425
  ).applyMatrix4(worldToLocal);
4752
- positions.push(point.x, point.y, point.z);
5426
+ points.push([point.x, point.y, point.z]);
4753
5427
  }
4754
5428
  }
4755
- positions.push(...positions.slice(0, 3));
4756
- return positions;
4757
- }
4758
- function makeStrokeLine(positions, color, width, opacity) {
4759
- const geometry = new LineGeometry();
4760
- geometry.setPositions(positions);
4761
- const material = new LineMaterial({
4762
- color,
4763
- depthTest: true,
4764
- depthWrite: false,
4765
- linewidth: Math.max(0.5, width),
4766
- opacity,
4767
- transparent: true,
4768
- worldUnits: false,
4769
- alphaToCoverage: true
4770
- });
4771
- const line = new Line2(geometry, material);
4772
- line.computeLineDistances();
4773
- return line;
4774
- }
4775
- function disposeStrokeLine(line) {
4776
- if (!line) return;
4777
- line.geometry.dispose();
4778
- line.material.dispose();
5429
+ const first = points[0];
5430
+ if (first) {
5431
+ points.push(first);
5432
+ }
5433
+ return points;
4779
5434
  }
4780
5435
  function translateVertices(vertices, from, to) {
4781
5436
  const yawOffset = normalizePanoYaw(to.yaw - from.yaw);
@@ -4802,45 +5457,27 @@ function PolygonHotspot({
4802
5457
  onInvalid,
4803
5458
  ...anchorProps
4804
5459
  }) {
4805
- const gl = useThree12((state) => state.gl);
4806
- const size = useThree12((state) => state.size);
4807
- const issues = useMemo9(() => validatePolygonVertices(vertices), [vertices]);
5460
+ const issues = useMemo11(() => validatePolygonVertices(vertices), [vertices]);
4808
5461
  const valid = issues.length === 0;
4809
- const center = useMemo9(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
4810
- const fillGeometry = useMemo9(
5462
+ const center = useMemo11(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
5463
+ const fillGeometry = useMemo11(
4811
5464
  () => valid && center ? makeFillGeometry(vertices, center) : null,
4812
5465
  [center, valid, vertices]
4813
5466
  );
4814
- const strokePositions = useMemo9(
5467
+ const strokePoints = useMemo11(
4815
5468
  () => valid && center ? makeStrokePositions(vertices, center) : null,
4816
5469
  [center, valid, vertices]
4817
5470
  );
4818
5471
  const resolvedStrokeOpacity = Math.max(0, Math.min(strokeOpacity, 1));
4819
- const devicePixelRatio = gl.getPixelRatio();
4820
- const strokeLine = useMemo9(
4821
- () => strokePositions ? makeStrokeLine(strokePositions, stroke, strokeWidth * devicePixelRatio, resolvedStrokeOpacity) : null,
4822
- [devicePixelRatio, resolvedStrokeOpacity, stroke, strokePositions, strokeWidth]
4823
- );
4824
- const focusStrokeLine = useMemo9(
4825
- () => strokePositions ? makeStrokeLine(strokePositions, "#75cbd3", (strokeWidth + 2) * devicePixelRatio, 1) : null,
4826
- [devicePixelRatio, strokePositions, strokeWidth]
4827
- );
4828
- const startVerticesRef = useRef15(null);
5472
+ const startVerticesRef = useRef16(null);
4829
5473
  const issuesKey = issues.map((issue) => issue.code).join(",");
4830
- useEffect17(() => {
5474
+ useEffect19(() => {
4831
5475
  if (!valid) onInvalid?.(issues);
4832
5476
  }, [issues, issuesKey, onInvalid, valid]);
4833
- useEffect17(() => () => fillGeometry?.dispose(), [fillGeometry]);
4834
- useEffect17(() => () => disposeStrokeLine(strokeLine), [strokeLine]);
4835
- useEffect17(() => () => disposeStrokeLine(focusStrokeLine), [focusStrokeLine]);
4836
- useEffect17(() => {
4837
- const width = gl.domElement.width;
4838
- const height = gl.domElement.height;
4839
- for (const line of [strokeLine, focusStrokeLine]) {
4840
- if (line) line.material.resolution.set(width, height);
4841
- }
4842
- }, [focusStrokeLine, gl, size.height, size.width, strokeLine]);
4843
- if (!valid || !center || !fillGeometry || !strokeLine || !focusStrokeLine) return null;
5477
+ useEffect19(() => () => fillGeometry?.dispose(), [fillGeometry]);
5478
+ if (!valid || !center || !fillGeometry || !strokePoints || strokePoints.length < 2) {
5479
+ return null;
5480
+ }
4844
5481
  const emitVerticesChange = (event, callback) => {
4845
5482
  const startVertices = startVerticesRef.current ?? cloneVertices(vertices);
4846
5483
  const nextVertices = translateVertices(startVertices, event.startPosition, event.position);
@@ -4852,7 +5489,7 @@ function PolygonHotspot({
4852
5489
  startPosition: event.startPosition
4853
5490
  });
4854
5491
  };
4855
- return /* @__PURE__ */ jsxs9(
5492
+ return /* @__PURE__ */ jsxs10(
4856
5493
  HotspotAnchor,
4857
5494
  {
4858
5495
  ...anchorProps,
@@ -4867,7 +5504,16 @@ function PolygonHotspot({
4867
5504
  draggable,
4868
5505
  interactive,
4869
5506
  useAngularScale: false,
4870
- focusContent: /* @__PURE__ */ jsx11("primitive", { object: focusStrokeLine, renderOrder: renderOrder + 2 }),
5507
+ focusContent: /* @__PURE__ */ jsx15(
5508
+ HotspotStrokeLine,
5509
+ {
5510
+ color: "#75cbd3",
5511
+ lineWidth: strokeWidth + 2,
5512
+ opacity: 1,
5513
+ points: strokePoints,
5514
+ renderOrder: renderOrder + 2
5515
+ }
5516
+ ),
4871
5517
  onDragStart: (event) => {
4872
5518
  startVerticesRef.current = cloneVertices(vertices);
4873
5519
  emitVerticesChange(event, onDragStart);
@@ -4878,7 +5524,7 @@ function PolygonHotspot({
4878
5524
  startVerticesRef.current = null;
4879
5525
  },
4880
5526
  children: [
4881
- /* @__PURE__ */ jsx11("mesh", { geometry: fillGeometry, renderOrder, children: /* @__PURE__ */ jsx11(
5527
+ /* @__PURE__ */ jsx15("mesh", { geometry: fillGeometry, renderOrder, children: /* @__PURE__ */ jsx15(
4882
5528
  "meshBasicMaterial",
4883
5529
  {
4884
5530
  color: fill,
@@ -4888,25 +5534,30 @@ function PolygonHotspot({
4888
5534
  transparent: true
4889
5535
  }
4890
5536
  ) }),
4891
- /* @__PURE__ */ jsx11("primitive", { object: strokeLine, renderOrder: renderOrder + 1 })
5537
+ /* @__PURE__ */ jsx15(
5538
+ HotspotStrokeLine,
5539
+ {
5540
+ color: stroke,
5541
+ lineWidth: strokeWidth,
5542
+ opacity: resolvedStrokeOpacity,
5543
+ points: strokePoints,
5544
+ renderOrder: renderOrder + 1
5545
+ }
5546
+ )
4892
5547
  ]
4893
5548
  }
4894
5549
  );
4895
5550
  }
4896
5551
 
4897
5552
  // src/hotspot/polyline-hotspot.tsx
4898
- import { useThree as useThree13 } from "@react-three/fiber";
4899
- import { useEffect as useEffect18, useMemo as useMemo10, useRef as useRef16 } from "react";
5553
+ import { useEffect as useEffect20, useMemo as useMemo12, useRef as useRef17 } from "react";
4900
5554
  import {
4901
5555
  MathUtils as MathUtils5,
4902
5556
  Matrix4 as Matrix44,
4903
5557
  Quaternion as Quaternion4,
4904
5558
  Vector3 as Vector38
4905
5559
  } from "three";
4906
- import { Line2 as Line22 } from "three/addons/lines/Line2.js";
4907
- import { LineGeometry as LineGeometry2 } from "three/addons/lines/LineGeometry.js";
4908
- import { LineMaterial as LineMaterial2 } from "three/addons/lines/LineMaterial.js";
4909
- import { jsx as jsx12 } from "react/jsx-runtime";
5560
+ import { jsx as jsx16 } from "react/jsx-runtime";
4910
5561
  var POLYLINE_RADIUS = 990;
4911
5562
  var MAX_SEGMENT_DEGREES = 7;
4912
5563
  function cloneVertices2(vertices) {
@@ -4972,7 +5623,7 @@ function subdivisionSteps(vertices) {
4972
5623
  );
4973
5624
  }
4974
5625
  function makeLinePositions(vertices, center) {
4975
- const positions = [];
5626
+ const points = [];
4976
5627
  const worldToLocal = makeWorldToLocal2(center);
4977
5628
  const unwrapped = unwrapVertices(vertices);
4978
5629
  const steps = subdivisionSteps(vertices);
@@ -4988,35 +5639,13 @@ function makeLinePositions(vertices, center) {
4988
5639
  },
4989
5640
  POLYLINE_RADIUS
4990
5641
  ).applyMatrix4(worldToLocal);
4991
- positions.push(point.x, point.y, point.z);
5642
+ points.push([point.x, point.y, point.z]);
4992
5643
  }
4993
5644
  }
4994
5645
  const last = unwrapped[unwrapped.length - 1];
4995
5646
  const lastPoint = panoPositionToVector3(last, POLYLINE_RADIUS).applyMatrix4(worldToLocal);
4996
- positions.push(lastPoint.x, lastPoint.y, lastPoint.z);
4997
- return positions;
4998
- }
4999
- function makeLine(positions, color, width, opacity) {
5000
- const geometry = new LineGeometry2();
5001
- geometry.setPositions(positions);
5002
- const material = new LineMaterial2({
5003
- color,
5004
- depthTest: true,
5005
- depthWrite: false,
5006
- linewidth: Math.max(0.5, width),
5007
- opacity,
5008
- transparent: true,
5009
- worldUnits: false,
5010
- alphaToCoverage: true
5011
- });
5012
- const line = new Line22(geometry, material);
5013
- line.computeLineDistances();
5014
- return line;
5015
- }
5016
- function disposeLine(line) {
5017
- if (!line) return;
5018
- line.geometry.dispose();
5019
- line.material.dispose();
5647
+ points.push([lastPoint.x, lastPoint.y, lastPoint.z]);
5648
+ return points;
5020
5649
  }
5021
5650
  function translateVertices2(vertices, from, to) {
5022
5651
  const yawOffset = normalizePanoYaw(to.yaw - from.yaw);
@@ -5043,39 +5672,19 @@ function PolylineHotspot({
5043
5672
  onInvalid,
5044
5673
  ...anchorProps
5045
5674
  }) {
5046
- const gl = useThree13((state) => state.gl);
5047
- const size = useThree13((state) => state.size);
5048
- const issues = useMemo10(() => validatePolylineVertices(vertices), [vertices]);
5675
+ const issues = useMemo12(() => validatePolylineVertices(vertices), [vertices]);
5049
5676
  const valid = issues.length === 0;
5050
- const center = useMemo10(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
5051
- const linePositions = useMemo10(
5677
+ const center = useMemo12(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
5678
+ const linePoints = useMemo12(
5052
5679
  () => valid && center ? makeLinePositions(vertices, center) : null,
5053
5680
  [center, valid, vertices]
5054
5681
  );
5055
- const devicePixelRatio = gl.getPixelRatio();
5056
5682
  const opacity = Math.max(0, Math.min(strokeOpacity, 1));
5057
- const line = useMemo10(
5058
- () => linePositions ? makeLine(linePositions, stroke, strokeWidth * devicePixelRatio, opacity) : null,
5059
- [devicePixelRatio, linePositions, opacity, stroke, strokeWidth]
5060
- );
5061
- const focusLine = useMemo10(
5062
- () => linePositions ? makeLine(linePositions, "#75cbd3", (strokeWidth + 2) * devicePixelRatio, 1) : null,
5063
- [devicePixelRatio, linePositions, strokeWidth]
5064
- );
5065
- const startVerticesRef = useRef16(null);
5066
- useEffect18(() => {
5683
+ const startVerticesRef = useRef17(null);
5684
+ useEffect20(() => {
5067
5685
  if (!valid) onInvalid?.(issues);
5068
5686
  }, [issues, onInvalid, valid]);
5069
- useEffect18(() => () => disposeLine(line), [line]);
5070
- useEffect18(() => () => disposeLine(focusLine), [focusLine]);
5071
- useEffect18(() => {
5072
- const width = gl.domElement.width;
5073
- const height = gl.domElement.height;
5074
- for (const nextLine of [line, focusLine]) {
5075
- if (nextLine) nextLine.material.resolution.set(width, height);
5076
- }
5077
- }, [focusLine, gl, line, size.height, size.width]);
5078
- if (!valid || !center || !line || !focusLine) return null;
5687
+ if (!valid || !center || !linePoints || linePoints.length < 2) return null;
5079
5688
  const emitVerticesChange = (event, callback) => {
5080
5689
  const startVertices = startVerticesRef.current ?? cloneVertices2(vertices);
5081
5690
  callback?.({
@@ -5086,7 +5695,7 @@ function PolylineHotspot({
5086
5695
  startPosition: event.startPosition
5087
5696
  });
5088
5697
  };
5089
- return /* @__PURE__ */ jsx12(
5698
+ return /* @__PURE__ */ jsx16(
5090
5699
  HotspotAnchor,
5091
5700
  {
5092
5701
  ...anchorProps,
@@ -5100,7 +5709,16 @@ function PolylineHotspot({
5100
5709
  renderOrder,
5101
5710
  draggable,
5102
5711
  useAngularScale: false,
5103
- focusContent: /* @__PURE__ */ jsx12("primitive", { object: focusLine, renderOrder: renderOrder + 2 }),
5712
+ focusContent: /* @__PURE__ */ jsx16(
5713
+ HotspotStrokeLine,
5714
+ {
5715
+ color: "#75cbd3",
5716
+ lineWidth: strokeWidth + 2,
5717
+ opacity: 1,
5718
+ points: linePoints,
5719
+ renderOrder: renderOrder + 2
5720
+ }
5721
+ ),
5104
5722
  onDragStart: (event) => {
5105
5723
  startVerticesRef.current = cloneVertices2(vertices);
5106
5724
  emitVerticesChange(event, onDragStart);
@@ -5110,29 +5728,31 @@ function PolylineHotspot({
5110
5728
  emitVerticesChange(event, onDragEnd);
5111
5729
  startVerticesRef.current = null;
5112
5730
  },
5113
- children: /* @__PURE__ */ jsx12("primitive", { object: line, renderOrder: renderOrder + 1 })
5731
+ children: /* @__PURE__ */ jsx16(
5732
+ HotspotStrokeLine,
5733
+ {
5734
+ color: stroke,
5735
+ lineWidth: strokeWidth,
5736
+ opacity,
5737
+ points: linePoints,
5738
+ renderOrder: renderOrder + 1
5739
+ }
5740
+ )
5114
5741
  }
5115
5742
  );
5116
5743
  }
5117
5744
 
5118
5745
  // src/hotspot/graphic-hotspot.tsx
5119
- import { useEffect as useEffect19, useRef as useRef17, useState as useState8 } from "react";
5746
+ import { useEffect as useEffect21, useRef as useRef18, useState as useState10 } from "react";
5120
5747
  import {
5121
5748
  CanvasTexture,
5122
5749
  ClampToEdgeWrapping as ClampToEdgeWrapping4,
5123
- DoubleSide as DoubleSide4,
5124
5750
  LinearFilter as LinearFilter6,
5125
5751
  LinearMipmapLinearFilter as LinearMipmapLinearFilter3,
5126
5752
  SRGBColorSpace as SRGBColorSpace5
5127
5753
  } from "three";
5128
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
5754
+ import { jsx as jsx17 } from "react/jsx-runtime";
5129
5755
  var GRAPHIC_TEXTURE_SIZE = 512;
5130
- function clampOpacity2(opacity) {
5131
- if (!Number.isFinite(opacity)) {
5132
- return 1;
5133
- }
5134
- return Math.max(0, Math.min(opacity, 1));
5135
- }
5136
5756
  function resolveStrokeWidth(value) {
5137
5757
  return Number.isFinite(value) ? Math.max(value, 0) : 8;
5138
5758
  }
@@ -5320,12 +5940,12 @@ function createGraphicTexture(graphic, aspectWidth, aspectHeight) {
5320
5940
  return texture;
5321
5941
  }
5322
5942
  function useGraphicTexture(graphic, aspectWidth, aspectHeight, onLoad, onError) {
5323
- const [texture, setTexture] = useState8(null);
5324
- const onLoadRef = useRef17(onLoad);
5325
- const onErrorRef = useRef17(onError);
5943
+ const [texture, setTexture] = useState10(null);
5944
+ const onLoadRef = useRef18(onLoad);
5945
+ const onErrorRef = useRef18(onError);
5326
5946
  onLoadRef.current = onLoad;
5327
5947
  onErrorRef.current = onError;
5328
- useEffect19(() => {
5948
+ useEffect21(() => {
5329
5949
  let nextTexture = null;
5330
5950
  try {
5331
5951
  nextTexture = createGraphicTexture(graphic, aspectWidth, aspectHeight);
@@ -5351,48 +5971,29 @@ function CanvasGraphicHotspot({
5351
5971
  ...anchorProps
5352
5972
  }) {
5353
5973
  const texture = useGraphicTexture(graphic, width, height, onLoad, onError);
5354
- return /* @__PURE__ */ jsx13(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsxs10("mesh", { children: [
5355
- /* @__PURE__ */ jsx13("planeGeometry", { args: [1, 1] }),
5356
- /* @__PURE__ */ jsx13(
5357
- "meshBasicMaterial",
5358
- {
5359
- map: texture,
5360
- opacity: texture ? clampOpacity2(opacity) : 0,
5361
- side: DoubleSide4,
5362
- toneMapped: false,
5363
- transparent: true
5364
- }
5365
- )
5366
- ] }) });
5974
+ return /* @__PURE__ */ jsx17(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsx17(HotspotPlane, { map: texture, opacity }) });
5367
5975
  }
5368
5976
  function GraphicHotspot(props) {
5369
5977
  const { graphic, ...restProps } = props;
5370
5978
  if (graphic.kind === "svg") {
5371
5979
  const imageProps = restProps;
5372
- return /* @__PURE__ */ jsx13(ImageHotspot, { ...imageProps, src: graphic.src });
5980
+ return /* @__PURE__ */ jsx17(ImageHotspot, { ...imageProps, src: graphic.src });
5373
5981
  }
5374
- return /* @__PURE__ */ jsx13(CanvasGraphicHotspot, { ...restProps, graphic });
5982
+ return /* @__PURE__ */ jsx17(CanvasGraphicHotspot, { ...restProps, graphic });
5375
5983
  }
5376
5984
 
5377
5985
  // src/hotspot/sequence-hotspot.tsx
5378
- import { useFrame as useFrame8, useThree as useThree14 } from "@react-three/fiber";
5379
- import { useEffect as useEffect20, useRef as useRef18, useState as useState9 } from "react";
5986
+ import { useFrame as useFrame8, useThree as useThree13 } from "@react-three/fiber";
5987
+ import { useEffect as useEffect22, useRef as useRef19, useState as useState11 } from "react";
5380
5988
  import {
5381
5989
  ClampToEdgeWrapping as ClampToEdgeWrapping5,
5382
- DoubleSide as DoubleSide5,
5383
5990
  LinearFilter as LinearFilter7,
5384
5991
  LinearMipmapLinearFilter as LinearMipmapLinearFilter4,
5385
5992
  SRGBColorSpace as SRGBColorSpace6,
5386
5993
  TextureLoader as TextureLoader5
5387
5994
  } from "three";
5388
- import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
5995
+ import { jsx as jsx18 } from "react/jsx-runtime";
5389
5996
  var DEFAULT_SEQUENCE_FPS = 12;
5390
- function clampOpacity3(opacity) {
5391
- if (!Number.isFinite(opacity)) {
5392
- return 1;
5393
- }
5394
- return Math.max(0, Math.min(opacity, 1));
5395
- }
5396
5997
  function resolveFps(value) {
5397
5998
  return Number.isFinite(value) && value > 0 ? value : DEFAULT_SEQUENCE_FPS;
5398
5999
  }
@@ -5405,13 +6006,13 @@ function useSpriteTexture({
5405
6006
  onError,
5406
6007
  onLoadProgress
5407
6008
  }) {
5408
- const gl = useThree14((state) => state.gl);
5409
- const [texture, setTexture] = useState9(null);
5410
- const onErrorRef = useRef18(onError);
5411
- const onLoadProgressRef = useRef18(onLoadProgress);
6009
+ const gl = useThree13((state) => state.gl);
6010
+ const [texture, setTexture] = useState11(null);
6011
+ const onErrorRef = useRef19(onError);
6012
+ const onLoadProgressRef = useRef19(onLoadProgress);
5412
6013
  onErrorRef.current = onError;
5413
6014
  onLoadProgressRef.current = onLoadProgress;
5414
- useEffect20(() => {
6015
+ useEffect22(() => {
5415
6016
  let active = true;
5416
6017
  setTexture(null);
5417
6018
  if (!src) {
@@ -5452,7 +6053,7 @@ function useSpriteTexture({
5452
6053
  active = false;
5453
6054
  };
5454
6055
  }, [gl, id, src]);
5455
- useEffect20(
6056
+ useEffect22(
5456
6057
  () => () => {
5457
6058
  texture?.dispose();
5458
6059
  },
@@ -5481,24 +6082,24 @@ function SequenceHotspot({
5481
6082
  const texture = useSpriteTexture({ id, src, onError, onLoadProgress });
5482
6083
  const frameCount = resolveFrameCount(requestedFrameCount);
5483
6084
  const safeFps = resolveFps(fps);
5484
- const [frameIndex, setFrameIndex] = useState9(0);
5485
- const elapsedRef = useRef18(0);
5486
- const frameIndexRef = useRef18(0);
5487
- const endedRef = useRef18(false);
5488
- const lastPlaybackStateRef = useRef18(null);
5489
- const onFrameChangeRef = useRef18(onFrameChange);
5490
- const onPlaybackStateChangeRef = useRef18(onPlaybackStateChange);
5491
- const onEndedRef = useRef18(onEnded);
6085
+ const [frameIndex, setFrameIndex] = useState11(0);
6086
+ const elapsedRef = useRef19(0);
6087
+ const frameIndexRef = useRef19(0);
6088
+ const endedRef = useRef19(false);
6089
+ const lastPlaybackStateRef = useRef19(null);
6090
+ const onFrameChangeRef = useRef19(onFrameChange);
6091
+ const onPlaybackStateChangeRef = useRef19(onPlaybackStateChange);
6092
+ const onEndedRef = useRef19(onEnded);
5492
6093
  onFrameChangeRef.current = onFrameChange;
5493
6094
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
5494
6095
  onEndedRef.current = onEnded;
5495
- useEffect20(() => {
6096
+ useEffect22(() => {
5496
6097
  elapsedRef.current = 0;
5497
6098
  frameIndexRef.current = 0;
5498
6099
  endedRef.current = false;
5499
6100
  setFrameIndex(0);
5500
6101
  }, [frameCount, frameDirection, src]);
5501
- useEffect20(() => {
6102
+ useEffect22(() => {
5502
6103
  if (!texture) {
5503
6104
  return;
5504
6105
  }
@@ -5512,13 +6113,13 @@ function SequenceHotspot({
5512
6113
  );
5513
6114
  texture.needsUpdate = true;
5514
6115
  }, [frameCount, frameDirection, frameIndex, texture]);
5515
- useEffect20(() => {
6116
+ useEffect22(() => {
5516
6117
  if (!texture) {
5517
6118
  return;
5518
6119
  }
5519
6120
  onFrameChangeRef.current?.({ id, index: 0, src });
5520
6121
  }, [id, src, texture]);
5521
- useEffect20(() => {
6122
+ useEffect22(() => {
5522
6123
  if (!texture) {
5523
6124
  return;
5524
6125
  }
@@ -5567,40 +6168,21 @@ function SequenceHotspot({
5567
6168
  onFrameChangeRef.current?.({ id, index: nextIndex, src });
5568
6169
  }
5569
6170
  });
5570
- return /* @__PURE__ */ jsx14(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ jsxs11("mesh", { children: [
5571
- /* @__PURE__ */ jsx14("planeGeometry", { args: [1, 1] }),
5572
- /* @__PURE__ */ jsx14(
5573
- "meshBasicMaterial",
5574
- {
5575
- map: texture,
5576
- opacity: texture ? clampOpacity3(opacity) : 0,
5577
- side: DoubleSide5,
5578
- toneMapped: false,
5579
- transparent: true
5580
- }
5581
- )
5582
- ] }) });
6171
+ return /* @__PURE__ */ jsx18(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ jsx18(HotspotPlane, { map: texture, opacity }) });
5583
6172
  }
5584
6173
 
5585
6174
  // src/hotspot/video-hotspot.tsx
5586
- import { useThree as useThree15 } from "@react-three/fiber";
5587
- import { useEffect as useEffect21, useRef as useRef19, useState as useState10 } from "react";
6175
+ import { useThree as useThree14 } from "@react-three/fiber";
6176
+ import { useEffect as useEffect23, useRef as useRef20, useState as useState12 } from "react";
5588
6177
  import {
5589
6178
  ClampToEdgeWrapping as ClampToEdgeWrapping6,
5590
- DoubleSide as DoubleSide6,
5591
6179
  LinearFilter as LinearFilter8,
5592
6180
  LinearMipmapLinearFilter as LinearMipmapLinearFilter5,
5593
6181
  SRGBColorSpace as SRGBColorSpace7,
5594
6182
  TextureLoader as TextureLoader6,
5595
6183
  VideoTexture
5596
6184
  } from "three";
5597
- import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
5598
- function clampOpacity4(opacity) {
5599
- if (!Number.isFinite(opacity)) {
5600
- return 1;
5601
- }
5602
- return Math.max(0, Math.min(opacity, 1));
5603
- }
6185
+ import { jsx as jsx19 } from "react/jsx-runtime";
5604
6186
  function clampVolume(volume) {
5605
6187
  if (!Number.isFinite(volume)) {
5606
6188
  return 1;
@@ -5608,11 +6190,11 @@ function clampVolume(volume) {
5608
6190
  return Math.max(0, Math.min(volume, 1));
5609
6191
  }
5610
6192
  function usePosterTexture(id, poster, onError) {
5611
- const gl = useThree15((state) => state.gl);
5612
- const [texture, setTexture] = useState10(null);
5613
- const onErrorRef = useRef19(onError);
6193
+ const gl = useThree14((state) => state.gl);
6194
+ const [texture, setTexture] = useState12(null);
6195
+ const onErrorRef = useRef20(onError);
5614
6196
  onErrorRef.current = onError;
5615
- useEffect21(() => {
6197
+ useEffect23(() => {
5616
6198
  let active = true;
5617
6199
  setTexture(null);
5618
6200
  if (!poster) {
@@ -5649,7 +6231,7 @@ function usePosterTexture(id, poster, onError) {
5649
6231
  active = false;
5650
6232
  };
5651
6233
  }, [gl, id, poster]);
5652
- useEffect21(
6234
+ useEffect23(
5653
6235
  () => () => {
5654
6236
  texture?.dispose();
5655
6237
  },
@@ -5667,14 +6249,14 @@ function useVideoResource({
5667
6249
  poster,
5668
6250
  src
5669
6251
  }) {
5670
- const [resource, setResource] = useState10(null);
5671
- const onEndedRef = useRef19(onEnded);
5672
- const onErrorRef = useRef19(onError);
5673
- const onPlaybackStateChangeRef = useRef19(onPlaybackStateChange);
6252
+ const [resource, setResource] = useState12(null);
6253
+ const onEndedRef = useRef20(onEnded);
6254
+ const onErrorRef = useRef20(onError);
6255
+ const onPlaybackStateChangeRef = useRef20(onPlaybackStateChange);
5674
6256
  onEndedRef.current = onEnded;
5675
6257
  onErrorRef.current = onError;
5676
6258
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
5677
- useEffect21(() => {
6259
+ useEffect23(() => {
5678
6260
  if (!src) {
5679
6261
  setResource(null);
5680
6262
  return;
@@ -5775,11 +6357,11 @@ function VideoHotspot({
5775
6357
  src
5776
6358
  });
5777
6359
  const posterTexture = usePosterTexture(id, poster, onError);
5778
- const onPlaybackErrorRef = useRef19(onPlaybackError);
5779
- const onPlaybackStateChangeRef = useRef19(onPlaybackStateChange);
6360
+ const onPlaybackErrorRef = useRef20(onPlaybackError);
6361
+ const onPlaybackStateChangeRef = useRef20(onPlaybackStateChange);
5780
6362
  onPlaybackErrorRef.current = onPlaybackError;
5781
6363
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
5782
- useEffect21(() => {
6364
+ useEffect23(() => {
5783
6365
  if (!resource) {
5784
6366
  return;
5785
6367
  }
@@ -5789,7 +6371,7 @@ function VideoHotspot({
5789
6371
  resource.video.playsInline = playsInline;
5790
6372
  resource.video.preload = preload;
5791
6373
  }, [loop, muted, playsInline, preload, resource, volume]);
5792
- useEffect21(() => {
6374
+ useEffect23(() => {
5793
6375
  if (!resource) {
5794
6376
  return;
5795
6377
  }
@@ -5812,23 +6394,410 @@ function VideoHotspot({
5812
6394
  };
5813
6395
  }, [playing, resource]);
5814
6396
  const activeTexture = resource?.ready ? resource.texture : posterTexture;
5815
- return /* @__PURE__ */ jsx15(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ jsxs12("mesh", { children: [
5816
- /* @__PURE__ */ jsx15("planeGeometry", { args: [1, 1] }),
5817
- /* @__PURE__ */ jsx15(
5818
- "meshBasicMaterial",
6397
+ return /* @__PURE__ */ jsx19(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ jsx19(HotspotPlane, { map: activeTexture, opacity }) });
6398
+ }
6399
+
6400
+ // src/hotspot/text-hotspot.tsx
6401
+ import { useEffect as useEffect24, useRef as useRef21, useState as useState13 } from "react";
6402
+ import {
6403
+ CanvasTexture as CanvasTexture2,
6404
+ ClampToEdgeWrapping as ClampToEdgeWrapping7,
6405
+ LinearFilter as LinearFilter9,
6406
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter6,
6407
+ SRGBColorSpace as SRGBColorSpace8
6408
+ } from "three";
6409
+ import { jsx as jsx20 } from "react/jsx-runtime";
6410
+ var TEXT_TEXTURE_LONG_SIDE = 1024;
6411
+ var DEFAULT_FONT_FAMILY = "system-ui, sans-serif";
6412
+ var DEFAULT_COLOR = "#f8fafc";
6413
+ var DEFAULT_BACKGROUND = "#111827";
6414
+ function clamp01(value, fallback) {
6415
+ if (!Number.isFinite(value)) {
6416
+ return fallback;
6417
+ }
6418
+ return Math.max(0, Math.min(value, 1));
6419
+ }
6420
+ function resolvePositive(value, fallback) {
6421
+ return Number.isFinite(value) && value > 0 ? value : fallback;
6422
+ }
6423
+ function resolveCornerRadius2(value) {
6424
+ return Number.isFinite(value) ? Math.max(0, Math.min(value, 0.5)) : 0.08;
6425
+ }
6426
+ function textureSize(width, height) {
6427
+ const safeWidth = resolvePositive(width, 1);
6428
+ const safeHeight = resolvePositive(height, 1);
6429
+ if (safeWidth >= safeHeight) {
6430
+ return {
6431
+ canvasWidth: TEXT_TEXTURE_LONG_SIDE,
6432
+ canvasHeight: Math.max(
6433
+ 1,
6434
+ Math.round(TEXT_TEXTURE_LONG_SIDE * (safeHeight / safeWidth))
6435
+ )
6436
+ };
6437
+ }
6438
+ return {
6439
+ canvasWidth: Math.max(
6440
+ 1,
6441
+ Math.round(TEXT_TEXTURE_LONG_SIDE * (safeWidth / safeHeight))
6442
+ ),
6443
+ canvasHeight: TEXT_TEXTURE_LONG_SIDE
6444
+ };
6445
+ }
6446
+ function roundedRectanglePath2(context, x, y, width, height, radius) {
6447
+ const safeRadius = Math.max(
6448
+ 0,
6449
+ Math.min(radius, width / 2, height / 2)
6450
+ );
6451
+ context.beginPath();
6452
+ if (safeRadius <= 0) {
6453
+ context.rect(x, y, width, height);
6454
+ return;
6455
+ }
6456
+ context.moveTo(x + safeRadius, y);
6457
+ context.lineTo(x + width - safeRadius, y);
6458
+ context.quadraticCurveTo(x + width, y, x + width, y + safeRadius);
6459
+ context.lineTo(x + width, y + height - safeRadius);
6460
+ context.quadraticCurveTo(
6461
+ x + width,
6462
+ y + height,
6463
+ x + width - safeRadius,
6464
+ y + height
6465
+ );
6466
+ context.lineTo(x + safeRadius, y + height);
6467
+ context.quadraticCurveTo(x, y + height, x, y + height - safeRadius);
6468
+ context.lineTo(x, y + safeRadius);
6469
+ context.quadraticCurveTo(x, y, x + safeRadius, y);
6470
+ context.closePath();
6471
+ }
6472
+ function wrapLine(context, line, maxWidth) {
6473
+ if (maxWidth <= 0 || context.measureText(line).width <= maxWidth) {
6474
+ return [line];
6475
+ }
6476
+ const lines = [];
6477
+ let current = "";
6478
+ for (const word of line.split(" ")) {
6479
+ const candidate = current ? `${current} ${word}` : word;
6480
+ if (context.measureText(candidate).width <= maxWidth) {
6481
+ current = candidate;
6482
+ continue;
6483
+ }
6484
+ if (current) {
6485
+ lines.push(current);
6486
+ }
6487
+ if (context.measureText(word).width <= maxWidth) {
6488
+ current = word;
6489
+ continue;
6490
+ }
6491
+ let chunk = "";
6492
+ for (const character of word) {
6493
+ const nextChunk = chunk + character;
6494
+ if (context.measureText(nextChunk).width <= maxWidth || chunk === "") {
6495
+ chunk = nextChunk;
6496
+ } else {
6497
+ lines.push(chunk);
6498
+ chunk = character;
6499
+ }
6500
+ }
6501
+ current = chunk;
6502
+ }
6503
+ if (current) {
6504
+ lines.push(current);
6505
+ }
6506
+ return lines.length > 0 ? lines : [""];
6507
+ }
6508
+ function wrapText(context, text, maxWidth, nowrap) {
6509
+ const paragraphs = text.split("\n");
6510
+ if (nowrap) {
6511
+ return paragraphs;
6512
+ }
6513
+ return paragraphs.flatMap((paragraph) => wrapLine(context, paragraph, maxWidth));
6514
+ }
6515
+ function drawTextHotspot(context, canvasWidth, canvasHeight, text, style) {
6516
+ const shortSide = Math.min(canvasWidth, canvasHeight);
6517
+ const padding = clamp01(style.padding, 0.08) * shortSide;
6518
+ const fontSize = Math.max(1, clamp01(style.fontSize, 0.18) * canvasHeight);
6519
+ const lineHeight = resolvePositive(style.lineHeight, 1.25) * fontSize;
6520
+ const align = style.align ?? "center";
6521
+ const verticalAlign = style.verticalAlign ?? "middle";
6522
+ const fontFamily = style.fontFamily?.trim() || DEFAULT_FONT_FAMILY;
6523
+ const fontWeight = style.fontWeight ?? 600;
6524
+ const fontStyle = style.fontStyle ?? "normal";
6525
+ const contentWidth = Math.max(1, canvasWidth - padding * 2);
6526
+ const contentHeight = Math.max(1, canvasHeight - padding * 2);
6527
+ context.clearRect(0, 0, canvasWidth, canvasHeight);
6528
+ const background = style.background ?? DEFAULT_BACKGROUND;
6529
+ if (background !== "") {
6530
+ roundedRectanglePath2(
6531
+ context,
6532
+ 0,
6533
+ 0,
6534
+ canvasWidth,
6535
+ canvasHeight,
6536
+ resolveCornerRadius2(style.borderRadius) * shortSide
6537
+ );
6538
+ context.save();
6539
+ context.globalAlpha = clamp01(style.backgroundOpacity, 0.72);
6540
+ context.fillStyle = background;
6541
+ context.fill();
6542
+ context.restore();
6543
+ }
6544
+ context.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
6545
+ context.textAlign = align;
6546
+ context.textBaseline = "top";
6547
+ const lines = wrapText(
6548
+ context,
6549
+ text,
6550
+ contentWidth,
6551
+ (style.whiteSpace ?? "normal") === "nowrap"
6552
+ );
6553
+ const maxLines = Math.max(1, Math.floor(contentHeight / lineHeight));
6554
+ const visibleLines = lines.slice(0, maxLines);
6555
+ const blockHeight = visibleLines.length * lineHeight;
6556
+ let startY = padding;
6557
+ if (verticalAlign === "middle") {
6558
+ startY = padding + Math.max(0, (contentHeight - blockHeight) / 2);
6559
+ } else if (verticalAlign === "bottom") {
6560
+ startY = padding + Math.max(0, contentHeight - blockHeight);
6561
+ }
6562
+ const startX = align === "left" ? padding : align === "right" ? canvasWidth - padding : canvasWidth / 2;
6563
+ const color = style.color?.trim() || DEFAULT_COLOR;
6564
+ const strokeWidth = Number.isFinite(style.strokeWidth) ? Math.max(style.strokeWidth, 0) : 0;
6565
+ context.save();
6566
+ context.beginPath();
6567
+ context.rect(padding, padding, contentWidth, contentHeight);
6568
+ context.clip();
6569
+ visibleLines.forEach((line, index) => {
6570
+ const y = startY + index * lineHeight;
6571
+ if (style.stroke && strokeWidth > 0) {
6572
+ context.lineWidth = strokeWidth;
6573
+ context.strokeStyle = style.stroke;
6574
+ context.lineJoin = "round";
6575
+ context.miterLimit = 2;
6576
+ context.strokeText(line, startX, y);
6577
+ }
6578
+ context.fillStyle = color;
6579
+ context.fillText(line, startX, y);
6580
+ });
6581
+ context.restore();
6582
+ }
6583
+ function createTextTexture(text, width, height, style) {
6584
+ const { canvasWidth, canvasHeight } = textureSize(width, height);
6585
+ const canvas = document.createElement("canvas");
6586
+ canvas.width = canvasWidth;
6587
+ canvas.height = canvasHeight;
6588
+ const context = canvas.getContext("2d");
6589
+ if (!context) {
6590
+ throw new Error("Canvas 2D is unavailable for this text hotspot.");
6591
+ }
6592
+ drawTextHotspot(context, canvasWidth, canvasHeight, text, style);
6593
+ const texture = new CanvasTexture2(canvas);
6594
+ texture.colorSpace = SRGBColorSpace8;
6595
+ texture.wrapS = ClampToEdgeWrapping7;
6596
+ texture.wrapT = ClampToEdgeWrapping7;
6597
+ texture.magFilter = LinearFilter9;
6598
+ texture.minFilter = LinearMipmapLinearFilter6;
6599
+ texture.generateMipmaps = true;
6600
+ texture.needsUpdate = true;
6601
+ return texture;
6602
+ }
6603
+ function useTextTexture(text, width, height, style, onLoad, onError) {
6604
+ const [texture, setTexture] = useState13(null);
6605
+ const onLoadRef = useRef21(onLoad);
6606
+ const onErrorRef = useRef21(onError);
6607
+ onLoadRef.current = onLoad;
6608
+ onErrorRef.current = onError;
6609
+ useEffect24(() => {
6610
+ let nextTexture = null;
6611
+ try {
6612
+ nextTexture = createTextTexture(text, width, height, style);
6613
+ setTexture(nextTexture);
6614
+ onLoadRef.current?.(nextTexture);
6615
+ } catch (error) {
6616
+ setTexture(null);
6617
+ onErrorRef.current?.(error);
6618
+ }
6619
+ return () => {
6620
+ nextTexture?.dispose();
6621
+ };
6622
+ }, [
6623
+ height,
6624
+ style.align,
6625
+ style.background,
6626
+ style.backgroundOpacity,
6627
+ style.borderRadius,
6628
+ style.color,
6629
+ style.fontFamily,
6630
+ style.fontSize,
6631
+ style.fontStyle,
6632
+ style.fontWeight,
6633
+ style.lineHeight,
6634
+ style.padding,
6635
+ style.stroke,
6636
+ style.strokeWidth,
6637
+ style.verticalAlign,
6638
+ style.whiteSpace,
6639
+ text,
6640
+ width
6641
+ ]);
6642
+ return texture;
6643
+ }
6644
+ function TextHotspot({
6645
+ text,
6646
+ width = 16,
6647
+ height = 6,
6648
+ opacity,
6649
+ fontFamily,
6650
+ fontSize,
6651
+ fontWeight,
6652
+ fontStyle,
6653
+ color,
6654
+ background,
6655
+ backgroundOpacity,
6656
+ padding,
6657
+ align,
6658
+ verticalAlign,
6659
+ lineHeight,
6660
+ whiteSpace,
6661
+ borderRadius,
6662
+ stroke,
6663
+ strokeWidth,
6664
+ onLoad,
6665
+ onError,
6666
+ ...anchorProps
6667
+ }) {
6668
+ const style = {
6669
+ align,
6670
+ background,
6671
+ backgroundOpacity,
6672
+ borderRadius,
6673
+ color,
6674
+ fontFamily,
6675
+ fontSize,
6676
+ fontStyle,
6677
+ fontWeight,
6678
+ lineHeight,
6679
+ padding,
6680
+ stroke,
6681
+ strokeWidth,
6682
+ verticalAlign,
6683
+ whiteSpace
6684
+ };
6685
+ const texture = useTextTexture(text, width, height, style, onLoad, onError);
6686
+ return /* @__PURE__ */ jsx20(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsx20(HotspotPlane, { map: texture, opacity }) });
6687
+ }
6688
+
6689
+ // src/hotspot/iframe-hotspot.tsx
6690
+ import { Html as Html2 } from "@react-three/drei";
6691
+ import { DoubleSide as DoubleSide4 } from "three";
6692
+ import { jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
6693
+ var IFRAME_LONG_SIDE = 800;
6694
+ var CSS3D_DISTANCE_FACTOR = 400;
6695
+ var DEFAULT_SANDBOX = "allow-scripts allow-popups allow-forms";
6696
+ function clampOpacity2(opacity) {
6697
+ if (!Number.isFinite(opacity)) {
6698
+ return 1;
6699
+ }
6700
+ return Math.max(0, Math.min(opacity, 1));
6701
+ }
6702
+ function resolvePositive2(value, fallback) {
6703
+ return Number.isFinite(value) && value > 0 ? value : fallback;
6704
+ }
6705
+ function iframeCssSize(width, height) {
6706
+ const safeWidth = resolvePositive2(width, 1);
6707
+ const safeHeight = resolvePositive2(height, 1);
6708
+ if (safeWidth >= safeHeight) {
6709
+ return {
6710
+ cssWidth: IFRAME_LONG_SIDE,
6711
+ cssHeight: Math.max(
6712
+ 1,
6713
+ Math.round(IFRAME_LONG_SIDE * (safeHeight / safeWidth))
6714
+ )
6715
+ };
6716
+ }
6717
+ return {
6718
+ cssWidth: Math.max(
6719
+ 1,
6720
+ Math.round(IFRAME_LONG_SIDE * (safeWidth / safeHeight))
6721
+ ),
6722
+ cssHeight: IFRAME_LONG_SIDE
6723
+ };
6724
+ }
6725
+ function IframeHotspot({
6726
+ src,
6727
+ title,
6728
+ sandbox = DEFAULT_SANDBOX,
6729
+ allow,
6730
+ referrerPolicy = "strict-origin-when-cross-origin",
6731
+ loading,
6732
+ pointerPolicy = "hotspot",
6733
+ background,
6734
+ opacity,
6735
+ width = 18,
6736
+ height = 12,
6737
+ onLoad,
6738
+ onError,
6739
+ ...anchorProps
6740
+ }) {
6741
+ const { cssWidth, cssHeight } = iframeCssSize(width, height);
6742
+ const iframeTitle = title ?? anchorProps.ariaLabel;
6743
+ return /* @__PURE__ */ jsxs11(HotspotAnchor, { ...anchorProps, height, width, children: [
6744
+ /* @__PURE__ */ jsxs11("mesh", { children: [
6745
+ /* @__PURE__ */ jsx21("planeGeometry", { args: [1, 1] }),
6746
+ /* @__PURE__ */ jsx21(
6747
+ "meshBasicMaterial",
6748
+ {
6749
+ depthWrite: false,
6750
+ opacity: 0,
6751
+ side: DoubleSide4,
6752
+ toneMapped: false,
6753
+ transparent: true
6754
+ }
6755
+ )
6756
+ ] }),
6757
+ /* @__PURE__ */ jsx21(
6758
+ Html2,
5819
6759
  {
5820
- map: activeTexture,
5821
- opacity: activeTexture ? clampOpacity4(opacity) : 0,
5822
- side: DoubleSide6,
5823
- toneMapped: false,
5824
- transparent: true
6760
+ distanceFactor: CSS3D_DISTANCE_FACTOR,
6761
+ pointerEvents: pointerPolicy === "content" ? "auto" : "none",
6762
+ scale: [1 / cssWidth, 1 / cssHeight, 1],
6763
+ style: {
6764
+ background: background ?? "transparent",
6765
+ height: cssHeight,
6766
+ opacity: clampOpacity2(opacity),
6767
+ overflow: "hidden",
6768
+ width: cssWidth
6769
+ },
6770
+ transform: true,
6771
+ zIndexRange: [10, 0],
6772
+ children: /* @__PURE__ */ jsx21(
6773
+ "iframe",
6774
+ {
6775
+ allow,
6776
+ loading,
6777
+ onError: (event) => onError?.(event.nativeEvent),
6778
+ onLoad,
6779
+ referrerPolicy,
6780
+ sandbox,
6781
+ src,
6782
+ style: {
6783
+ border: 0,
6784
+ display: "block",
6785
+ height: "100%",
6786
+ width: "100%"
6787
+ },
6788
+ title: iframeTitle
6789
+ }
6790
+ )
5825
6791
  }
5826
6792
  )
5827
- ] }) });
6793
+ ] });
5828
6794
  }
5829
6795
  export {
5830
6796
  AutoRotate,
6797
+ DEFAULT_HOTSPOT_TOOLTIP_OFFSET,
6798
+ DEFAULT_PANO_CURSORS,
5831
6799
  GraphicHotspot,
6800
+ IframeHotspot,
5832
6801
  ImageHotspot,
5833
6802
  KeyboardControls,
5834
6803
  MAX_HOTSPOT_PITCH,
@@ -5836,12 +6805,13 @@ export {
5836
6805
  MouseControls,
5837
6806
  PanoContextMenu,
5838
6807
  PanoEvents,
5839
- PanoView,
5840
- PanoramaScenes,
6808
+ PanoViewer,
5841
6809
  PolygonHotspot,
5842
6810
  PolylineHotspot,
6811
+ Scenes,
5843
6812
  SequenceHotspot,
5844
6813
  Sphere,
6814
+ TextHotspot,
5845
6815
  Tile,
5846
6816
  TouchControls,
5847
6817
  VideoHotspot,