@ericchen1990/pano-view 3.5.0 → 3.6.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
@@ -2238,18 +2238,83 @@ var require_howler = __commonJS({
2238
2238
  }
2239
2239
  });
2240
2240
 
2241
+ // src/view-limits.ts
2242
+ function parseViewLimits(value) {
2243
+ if (value === void 0) return void 0;
2244
+ if (!value || typeof value !== "object" || Array.isArray(value))
2245
+ throw new Error("viewLimits: expected an object");
2246
+ const result = {};
2247
+ for (const axis of ["yaw", "pitch", "fov"]) {
2248
+ const input = value[axis];
2249
+ if (input === void 0) continue;
2250
+ if (!input || typeof input !== "object" || Array.isArray(input))
2251
+ throw new Error(`viewLimits.${axis}: expected min and max`);
2252
+ const { min, max } = input;
2253
+ const lower = axis === "yaw" ? -180 : axis === "pitch" ? -90 : 1;
2254
+ const upper = axis === "yaw" ? 180 : axis === "pitch" ? 90 : 179;
2255
+ if (typeof min !== "number" || typeof max !== "number" || !Number.isFinite(min) || !Number.isFinite(max) || min < lower || min > upper || max < lower || max > upper)
2256
+ throw new Error(`viewLimits.${axis}: expected finite min and max from ${lower} to ${upper}`);
2257
+ if (axis !== "yaw" && min > max)
2258
+ throw new Error(`viewLimits.${axis}: min must not exceed max`);
2259
+ result[axis] = { min, max };
2260
+ }
2261
+ return result;
2262
+ }
2263
+ function resolveViewLimits(base, scene, minFov = 30, maxFov = 100) {
2264
+ const pitch = scene?.pitch ?? base?.pitch;
2265
+ const fov = scene?.fov ?? base?.fov;
2266
+ return {
2267
+ yaw: scene?.yaw ?? base?.yaw,
2268
+ pitch: pitch ?? { min: -90, max: 90 },
2269
+ fov: fov ?? { min: minFov, max: maxFov },
2270
+ hardPitch: pitch !== void 0,
2271
+ hardFov: fov !== void 0
2272
+ };
2273
+ }
2274
+ function clampViewAxis(value, range) {
2275
+ return Math.max(range.min, Math.min(range.max, value));
2276
+ }
2277
+ function normalizeViewYaw(yaw) {
2278
+ return ((yaw + 180) % 360 + 360) % 360 - 180;
2279
+ }
2280
+ function yawWidth(range) {
2281
+ if (!range || range.min === -180 && range.max === 180) return 360;
2282
+ return ((range.max - range.min) % 360 + 360) % 360;
2283
+ }
2284
+ function yawOffset(yaw, range) {
2285
+ const width = yawWidth(range);
2286
+ const offset = ((yaw - range.min) % 360 + 360) % 360;
2287
+ if (offset <= width) return offset;
2288
+ return offset - width < 360 - offset ? width : 0;
2289
+ }
2290
+ function constrainViewYaw(yaw, range) {
2291
+ if (yawWidth(range) === 360) return normalizeViewYaw(yaw);
2292
+ return normalizeViewYaw(range.min + yawOffset(yaw, range));
2293
+ }
2294
+ function applyLimitedYawDelta(yaw, delta, range) {
2295
+ const width = yawWidth(range);
2296
+ if (width === 360) return normalizeViewYaw(yaw + delta);
2297
+ const offset = clampViewAxis(yawOffset(yaw, range) + delta, { min: 0, max: width });
2298
+ return normalizeViewYaw(range.min + offset);
2299
+ }
2300
+ function limitedYawDelta(from, to, range, shortestPath = true) {
2301
+ if (yawWidth(range) === 360)
2302
+ return shortestPath ? normalizeViewYaw(to - from) : to - from;
2303
+ return yawOffset(to, range) - yawOffset(from, range);
2304
+ }
2305
+
2241
2306
  // src/pano-viewer.tsx
2242
2307
  import { Canvas } from "@react-three/fiber";
2243
2308
  import { createXRStore, XR } from "@react-three/xr";
2244
2309
  import {
2245
2310
  forwardRef as forwardRef2,
2246
2311
  useCallback as useCallback6,
2247
- useContext as useContext15,
2312
+ useContext as useContext16,
2248
2313
  useEffect as useEffect13,
2249
2314
  useImperativeHandle as useImperativeHandle2,
2250
- useMemo as useMemo7,
2315
+ useMemo as useMemo8,
2251
2316
  useRef as useRef13,
2252
- useState as useState5,
2317
+ useState as useState6,
2253
2318
  useSyncExternalStore as useSyncExternalStore6
2254
2319
  } from "react";
2255
2320
 
@@ -2337,6 +2402,7 @@ function sourceList(src) {
2337
2402
  }
2338
2403
  function createUnlockingHowl({
2339
2404
  src,
2405
+ format,
2340
2406
  loop,
2341
2407
  crossOrigin,
2342
2408
  isActive,
@@ -2358,6 +2424,7 @@ function createUnlockingHowl({
2358
2424
  };
2359
2425
  const instance = new import_howler.Howl({
2360
2426
  src: [...src],
2427
+ format: format ? [...format] : void 0,
2361
2428
  html5: false,
2362
2429
  preload: true,
2363
2430
  loop,
@@ -2467,6 +2534,7 @@ import {
2467
2534
  forwardRef,
2468
2535
  useEffect,
2469
2536
  useImperativeHandle,
2537
+ useLayoutEffect,
2470
2538
  useRef
2471
2539
  } from "react";
2472
2540
  import { Euler, MathUtils, PerspectiveCamera } from "three";
@@ -2480,6 +2548,7 @@ var DEFAULT_ZOOM_DAMPING = 16;
2480
2548
  var DEFAULT_FRICTION_STOP = 0.01;
2481
2549
  var VIEW_SETTLE_EPSILON = 1e-3;
2482
2550
  var DEFAULT_NAVIGATION_DURATION = 700;
2551
+ var POINTER_VELOCITY_MAX_AGE_MS = 100;
2483
2552
  var PanoramaViewContext = createContext3(null);
2484
2553
  function clamp(value, min, max) {
2485
2554
  return Math.max(min, Math.min(max, value));
@@ -2499,6 +2568,9 @@ function resolveDamping(value, fallback) {
2499
2568
  function resolveFrictionStop(value) {
2500
2569
  return Number.isFinite(value) && value >= 0 ? value : DEFAULT_FRICTION_STOP;
2501
2570
  }
2571
+ function shouldKeepPointerInertia(sampledAt, releasedAt, cancelled = false) {
2572
+ return !cancelled && sampledAt !== null && releasedAt >= sampledAt && releasedAt - sampledAt <= POINTER_VELOCITY_MAX_AGE_MS;
2573
+ }
2502
2574
  function resolveNavigationDuration(value) {
2503
2575
  return Number.isFinite(value) && value >= 0 ? value : DEFAULT_NAVIGATION_DURATION;
2504
2576
  }
@@ -2511,7 +2583,7 @@ function applyNavigationEasing(progress, easing) {
2511
2583
  function viewsEqual(a, b) {
2512
2584
  return a.yaw === b.yaw && a.pitch === b.pitch && a.fov === b.fov;
2513
2585
  }
2514
- var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView, minFov, maxFov, options, eventBus }, ref) {
2586
+ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView, viewLimits, minFov, maxFov, options, eventBus }, ref) {
2515
2587
  const { camera, gl } = useThree();
2516
2588
  const xrSession = useXR((state) => state.session);
2517
2589
  const xrSessionRef = useRef(xrSession);
@@ -2539,48 +2611,46 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2539
2611
  const lastEmittedViewRef = useRef(null);
2540
2612
  const eulerRef = useRef(new Euler(0, 0, 0, "YXZ"));
2541
2613
  const xrEulerRef = useRef(new Euler(0, 0, 0, "YXZ"));
2542
- const minFovRef = useRef(minFov);
2543
- const maxFovRef = useRef(maxFov);
2614
+ const baseLimitsRef = useRef(viewLimits);
2615
+ const sceneLimitsRef = useRef(void 0);
2616
+ const legacyFovRef = useRef({ minFov, maxFov });
2617
+ const limitsRef = useRef(resolveViewLimits(viewLimits, void 0, minFov, maxFov));
2544
2618
  const activeNavigationRef = useRef(null);
2545
2619
  optionsRef.current = options;
2546
2620
  eventBusRef.current = eventBus;
2547
2621
  xrSessionRef.current = xrSession;
2548
- minFovRef.current = minFov;
2549
- maxFovRef.current = maxFov;
2622
+ baseLimitsRef.current = viewLimits;
2623
+ legacyFovRef.current = { minFov, maxFov };
2550
2624
  const hasInertia = () => yawVelocityRef.current !== 0 || pitchVelocityRef.current !== 0 || zoomVelocityRef.current !== 0;
2551
2625
  const isUserInteracting = () => mouseActiveRef.current || touchActiveRef.current || keyboardActiveRef.current || gyroActiveRef.current || interactionLockCountRef.current > 0 || xrSessionRef.current != null || activeNavigationRef.current != null || hasInertia();
2552
2626
  const syncInteracting = () => {
2553
2627
  interactingRef.current = mouseActiveRef.current || touchActiveRef.current;
2554
2628
  };
2555
2629
  const hardConstrainView = (view, current = targetViewRef.current) => {
2630
+ const limits = limitsRef.current;
2556
2631
  return {
2557
- yaw: normalizeYaw(view.yaw ?? current.yaw),
2558
- pitch: clamp(view.pitch ?? current.pitch, -MAX_PITCH, MAX_PITCH),
2559
- fov: clamp(
2560
- view.fov ?? current.fov,
2561
- minFovRef.current,
2562
- maxFovRef.current
2563
- )
2632
+ yaw: constrainViewYaw(Number.isFinite(view.yaw) ? view.yaw : current.yaw, limits.yaw),
2633
+ pitch: clampViewAxis(Number.isFinite(view.pitch) ? view.pitch : current.pitch, limits.pitch),
2634
+ fov: clampViewAxis(Number.isFinite(view.fov) ? view.fov : current.fov, limits.fov)
2564
2635
  };
2565
2636
  };
2566
2637
  const softConstrainView = (view, current = targetViewRef.current) => {
2567
- const bouncing = optionsRef.current.bouncingLimits === true;
2568
- if (!bouncing || !interactingRef.current) {
2569
- return hardConstrainView(view, current);
2570
- }
2571
- const fovRange = Math.max(maxFovRef.current - minFovRef.current, 1);
2572
- const fovOvershoot = fovRange * BOUNCE_OVERSHOOT_FOV_RATIO;
2638
+ const constrained = hardConstrainView(view, current);
2639
+ if (optionsRef.current.bouncingLimits !== true || !interactingRef.current)
2640
+ return constrained;
2641
+ const limits = limitsRef.current;
2642
+ const fovOvershoot = Math.max(limits.fov.max - limits.fov.min, 1) * BOUNCE_OVERSHOOT_FOV_RATIO;
2573
2643
  return {
2574
- yaw: normalizeYaw(view.yaw ?? current.yaw),
2575
- pitch: clamp(
2644
+ yaw: constrained.yaw,
2645
+ pitch: limits.hardPitch ? constrained.pitch : clamp(
2576
2646
  view.pitch ?? current.pitch,
2577
2647
  -MAX_PITCH - BOUNCE_OVERSHOOT_PITCH,
2578
2648
  MAX_PITCH + BOUNCE_OVERSHOOT_PITCH
2579
2649
  ),
2580
- fov: clamp(
2650
+ fov: limits.hardFov ? constrained.fov : clamp(
2581
2651
  view.fov ?? current.fov,
2582
- minFovRef.current - fovOvershoot,
2583
- maxFovRef.current + fovOvershoot
2652
+ limits.fov.min - fovOvershoot,
2653
+ limits.fov.max + fovOvershoot
2584
2654
  )
2585
2655
  };
2586
2656
  };
@@ -2634,6 +2704,27 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2634
2704
  );
2635
2705
  };
2636
2706
  };
2707
+ const refreshLimits = () => {
2708
+ const next = resolveViewLimits(
2709
+ baseLimitsRef.current,
2710
+ sceneLimitsRef.current,
2711
+ legacyFovRef.current.minFov,
2712
+ legacyFovRef.current.maxFov
2713
+ );
2714
+ if (JSON.stringify(next) === JSON.stringify(limitsRef.current)) return;
2715
+ limitsRef.current = next;
2716
+ cancelNavigation();
2717
+ stopVelocity();
2718
+ viewRef.current = hardConstrainView(viewRef.current);
2719
+ targetViewRef.current = hardConstrainView(targetViewRef.current);
2720
+ if (gyroActiveRef.current) {
2721
+ gyroOffsetRef.current = {
2722
+ yaw: shortestYawDelta(gyroPoseRef.current.yaw, viewRef.current.yaw),
2723
+ pitch: viewRef.current.pitch - gyroPoseRef.current.pitch
2724
+ };
2725
+ }
2726
+ markDirty();
2727
+ };
2637
2728
  const setView = (view, setOptions) => {
2638
2729
  cancelNavigation();
2639
2730
  const nextView = hardConstrainView(view);
@@ -2664,7 +2755,12 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2664
2755
  const target = hardConstrainView(safeTargetInput, from);
2665
2756
  const duration = resolveNavigationDuration(options2?.duration);
2666
2757
  const rawYawDelta = targetInput.yaw !== void 0 && Number.isFinite(targetInput.yaw) ? safeTargetInput.yaw - from.yaw : 0;
2667
- const yawDelta = options2?.shortestPath === false ? rawYawDelta : shortestYawDelta(from.yaw, target.yaw);
2758
+ const yawDelta = limitedYawDelta(
2759
+ from.yaw,
2760
+ options2?.shortestPath === false ? from.yaw + rawYawDelta : target.yaw,
2761
+ limitsRef.current.yaw,
2762
+ options2?.shortestPath !== false
2763
+ );
2668
2764
  stopVelocity();
2669
2765
  targetViewRef.current = target;
2670
2766
  markDirty();
@@ -2719,7 +2815,7 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2719
2815
  const appliesTouchMode = gyroControlsView && applyOptions?.source === "touch";
2720
2816
  const acceptsYaw = !appliesTouchMode || gyroMode === "horizontaloffset" || gyroMode === "full";
2721
2817
  const acceptsPitch = !appliesTouchMode || gyroMode === "full";
2722
- const nextYaw = acceptsYaw && delta.yaw !== void 0 && Number.isFinite(delta.yaw) ? current.yaw + delta.yaw : current.yaw;
2818
+ const nextYaw = acceptsYaw && delta.yaw !== void 0 && Number.isFinite(delta.yaw) ? applyLimitedYawDelta(current.yaw, delta.yaw, limitsRef.current.yaw) : current.yaw;
2723
2819
  const nextPitch = acceptsPitch && delta.pitch !== void 0 && Number.isFinite(delta.pitch) ? current.pitch + delta.pitch : current.pitch;
2724
2820
  const nextFov = delta.fov !== void 0 && Number.isFinite(delta.fov) ? current.fov + delta.fov : current.fov;
2725
2821
  targetViewRef.current = softConstrainView({
@@ -2729,10 +2825,10 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2729
2825
  });
2730
2826
  if (gyroControlsView) {
2731
2827
  if (acceptsYaw && delta.yaw !== void 0 && Number.isFinite(delta.yaw)) {
2732
- gyroOffsetRef.current.yaw += delta.yaw;
2828
+ gyroOffsetRef.current.yaw = shortestYawDelta(gyroPoseRef.current.yaw, targetViewRef.current.yaw);
2733
2829
  }
2734
2830
  if (acceptsPitch && delta.pitch !== void 0 && Number.isFinite(delta.pitch)) {
2735
- gyroOffsetRef.current.pitch += delta.pitch;
2831
+ gyroOffsetRef.current.pitch = targetViewRef.current.pitch - gyroPoseRef.current.pitch;
2736
2832
  }
2737
2833
  }
2738
2834
  markDirty();
@@ -2750,6 +2846,8 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2750
2846
  if (delta.fov !== void 0 && Number.isFinite(delta.fov)) {
2751
2847
  zoomVelocityRef.current = clamp(delta.fov / elapsed, -180, 180);
2752
2848
  }
2849
+ } else if (delta.fov !== void 0 && Number.isFinite(delta.fov)) {
2850
+ zoomVelocityRef.current = 0;
2753
2851
  }
2754
2852
  return true;
2755
2853
  };
@@ -2757,6 +2855,10 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2757
2855
  ref,
2758
2856
  () => ({
2759
2857
  getView: () => ({ ...viewRef.current }),
2858
+ setSceneViewLimits: (limits) => {
2859
+ sceneLimitsRef.current = parseViewLimits(limits);
2860
+ refreshLimits();
2861
+ },
2760
2862
  getTargetView: () => ({ ...targetViewRef.current }),
2761
2863
  setView,
2762
2864
  reset: () => setView(initialViewRef.current),
@@ -2780,7 +2882,7 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2780
2882
  });
2781
2883
  }
2782
2884
  },
2783
- setPointerActive: (source, active) => {
2885
+ setPointerActive: (source, active, releaseOptions) => {
2784
2886
  const wasActive = source === "mouse" ? mouseActiveRef.current : touchActiveRef.current;
2785
2887
  if (source === "mouse") {
2786
2888
  mouseActiveRef.current = active;
@@ -2796,9 +2898,14 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2796
2898
  } else if (!active && wasActive) {
2797
2899
  eventBusRef.current.emit("viewinteractionend", { source });
2798
2900
  }
2799
- if (active) {
2901
+ if (active || !shouldKeepPointerInertia(
2902
+ lastVelocitySampleAtRef.current,
2903
+ performance.now(),
2904
+ releaseOptions?.cancelled
2905
+ )) {
2800
2906
  stopVelocity();
2801
- } else if (!mouseActiveRef.current && !touchActiveRef.current) {
2907
+ }
2908
+ if (!active && !mouseActiveRef.current && !touchActiveRef.current) {
2802
2909
  lastVelocitySampleAtRef.current = null;
2803
2910
  snapTargetToHardLimits();
2804
2911
  }
@@ -2848,7 +2955,9 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2848
2955
  if (!Number.isFinite(yawDelta) || xrSessionRef.current != null || activeNavigationRef.current != null || gyroActiveRef.current || interactionLockCountRef.current > 0 || interactingRef.current || keyboardActiveRef.current || !inertiaFinished) {
2849
2956
  return false;
2850
2957
  }
2851
- targetViewRef.current.yaw += yawDelta;
2958
+ const nextYaw = applyLimitedYawDelta(targetViewRef.current.yaw, yawDelta, limitsRef.current.yaw);
2959
+ if (nextYaw === targetViewRef.current.yaw) return false;
2960
+ targetViewRef.current.yaw = nextYaw;
2852
2961
  markDirty();
2853
2962
  return true;
2854
2963
  }
@@ -2861,6 +2970,9 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2861
2970
  },
2862
2971
  []
2863
2972
  );
2973
+ useLayoutEffect(() => {
2974
+ refreshLimits();
2975
+ }, [viewLimits, minFov, maxFov]);
2864
2976
  useEffect(() => {
2865
2977
  initialViewRef.current = { ...initialView };
2866
2978
  setView(initialView);
@@ -2941,8 +3053,9 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2941
3053
  );
2942
3054
  const zoomDamping2 = Math.exp(-INERTIA_ZOOM_DAMPING * deltaSeconds);
2943
3055
  if (Math.abs(yawVelocityRef.current) >= frictionStop) {
2944
- targetViewRef.current.yaw += yawVelocityRef.current * deltaSeconds;
2945
- yawVelocityRef.current *= rotationDamping;
3056
+ const previousYaw = targetViewRef.current.yaw;
3057
+ targetViewRef.current.yaw = applyLimitedYawDelta(previousYaw, yawVelocityRef.current * deltaSeconds, limitsRef.current.yaw);
3058
+ yawVelocityRef.current = targetViewRef.current.yaw === previousYaw ? 0 : yawVelocityRef.current * rotationDamping;
2946
3059
  markDirty();
2947
3060
  } else {
2948
3061
  yawVelocityRef.current = 0;
@@ -2983,7 +3096,7 @@ var PanoramaViewRuntime = forwardRef(function PanoramaViewRuntime2({ initialView
2983
3096
  );
2984
3097
  const rotationFactor = dampingFactor(rotateDamping, deltaSeconds);
2985
3098
  const zoomFactor = dampingFactor(zoomDamping, deltaSeconds);
2986
- const yawDelta = shortestYawDelta(viewRef.current.yaw, targetView.yaw);
3099
+ const yawDelta = limitedYawDelta(viewRef.current.yaw, targetView.yaw, limitsRef.current.yaw);
2987
3100
  const smoothedView = {
2988
3101
  yaw: normalizeYaw(viewRef.current.yaw + yawDelta * rotationFactor),
2989
3102
  pitch: viewRef.current.pitch + (targetView.pitch - viewRef.current.pitch) * rotationFactor,
@@ -3613,17 +3726,18 @@ function MouseControls({
3613
3726
  }
3614
3727
  }
3615
3728
  pointersRef.current.clear();
3616
- controlsRef.current?.setPointerActive("mouse", false);
3729
+ controlsRef.current?.setPointerActive("mouse", false, { cancelled: true });
3617
3730
  };
3618
3731
  if (!enabled) {
3619
3732
  clearPointers();
3620
3733
  return;
3621
3734
  }
3622
3735
  const isMouseLike = (event) => event.pointerType === "mouse" || event.pointerType === "pen";
3623
- const syncActive = () => {
3736
+ const syncActive = (cancelled = false) => {
3624
3737
  controlsRef.current?.setPointerActive(
3625
3738
  "mouse",
3626
- pointersRef.current.size > 0
3739
+ pointersRef.current.size > 0,
3740
+ { cancelled }
3627
3741
  );
3628
3742
  };
3629
3743
  const onPointerDown = (event) => {
@@ -3684,7 +3798,7 @@ function MouseControls({
3684
3798
  if (element.hasPointerCapture?.(event.pointerId)) {
3685
3799
  element.releasePointerCapture?.(event.pointerId);
3686
3800
  }
3687
- syncActive();
3801
+ syncActive(event.type !== "pointerup");
3688
3802
  };
3689
3803
  const onWheel = (event) => {
3690
3804
  if (!optionsRef.current.wheel) {
@@ -3706,19 +3820,21 @@ function MouseControls({
3706
3820
  }
3707
3821
  controlsRef.current?.applyViewDelta(
3708
3822
  { fov: delta },
3709
- { recordVelocity: true, source: "mouse" }
3823
+ { source: "mouse" }
3710
3824
  );
3711
3825
  };
3712
3826
  element.addEventListener("pointerdown", onPointerDown);
3713
3827
  element.addEventListener("pointermove", onPointerMove);
3714
3828
  element.addEventListener("pointerup", releasePointer);
3715
3829
  element.addEventListener("pointercancel", releasePointer);
3830
+ element.addEventListener("lostpointercapture", releasePointer);
3716
3831
  element.addEventListener("wheel", onWheel, { passive: false });
3717
3832
  return () => {
3718
3833
  element.removeEventListener("pointerdown", onPointerDown);
3719
3834
  element.removeEventListener("pointermove", onPointerMove);
3720
3835
  element.removeEventListener("pointerup", releasePointer);
3721
3836
  element.removeEventListener("pointercancel", releasePointer);
3837
+ element.removeEventListener("lostpointercapture", releasePointer);
3722
3838
  element.removeEventListener("wheel", onWheel);
3723
3839
  clearPointers();
3724
3840
  };
@@ -3901,7 +4017,7 @@ import {
3901
4017
  useContext as useContext8,
3902
4018
  useEffect as useEffect7,
3903
4019
  useId,
3904
- useLayoutEffect,
4020
+ useLayoutEffect as useLayoutEffect2,
3905
4021
  useMemo as useMemo3,
3906
4022
  useRef as useRef7,
3907
4023
  useState as useState2
@@ -4204,7 +4320,7 @@ function PositionedMenuShell({
4204
4320
  viewport?.removeEventListener("resize", refresh);
4205
4321
  };
4206
4322
  }, []);
4207
- useLayoutEffect(() => {
4323
+ useLayoutEffect2(() => {
4208
4324
  const shell = shellRef.current;
4209
4325
  if (!overlayElement || !shell) {
4210
4326
  return;
@@ -4363,6 +4479,7 @@ function PanoContextMenu({
4363
4479
  const openMenu = useCallback2(
4364
4480
  (event) => {
4365
4481
  event.nativeEvent.preventDefault();
4482
+ if (controlsRef.current?.isInteractionLocked()) return;
4366
4483
  const view = controlsRef.current?.getView() ?? {
4367
4484
  yaw: 0,
4368
4485
  pitch: 0,
@@ -5023,9 +5140,142 @@ function PanoEvents(props) {
5023
5140
  return null;
5024
5141
  }
5025
5142
 
5143
+ // src/overlay/modal-host.ts
5144
+ function createPanoModalHost() {
5145
+ const leases = /* @__PURE__ */ new Set();
5146
+ const listeners = /* @__PURE__ */ new Set();
5147
+ let runtime = null;
5148
+ let releaseRuntime;
5149
+ const syncRuntime = () => {
5150
+ if (leases.size > 0 && runtime) {
5151
+ releaseRuntime ?? (releaseRuntime = runtime.acquireInteractionLock());
5152
+ } else {
5153
+ releaseRuntime?.();
5154
+ releaseRuntime = void 0;
5155
+ }
5156
+ };
5157
+ const notify2 = () => {
5158
+ syncRuntime();
5159
+ for (const listener of listeners) listener();
5160
+ };
5161
+ return {
5162
+ getSnapshot: () => leases.size > 0,
5163
+ subscribe(listener) {
5164
+ listeners.add(listener);
5165
+ return () => {
5166
+ listeners.delete(listener);
5167
+ };
5168
+ },
5169
+ connect(next) {
5170
+ if (runtime === next) return;
5171
+ releaseRuntime?.();
5172
+ releaseRuntime = void 0;
5173
+ runtime = next;
5174
+ syncRuntime();
5175
+ },
5176
+ acquire() {
5177
+ const token = /* @__PURE__ */ Symbol();
5178
+ leases.add(token);
5179
+ notify2();
5180
+ return () => {
5181
+ if (leases.delete(token)) notify2();
5182
+ };
5183
+ }
5184
+ };
5185
+ }
5186
+
5187
+ // src/overlay/pano-overlay.tsx
5188
+ import {
5189
+ createContext as createContext11,
5190
+ useContext as useContext11,
5191
+ useLayoutEffect as useLayoutEffect3,
5192
+ useMemo as useMemo5,
5193
+ useState as useState3
5194
+ } from "react";
5195
+
5196
+ // src/overlay/visible-rect.ts
5197
+ function getOverlayVisibleRect(rect, viewport) {
5198
+ const left = Math.min(rect.width, Math.max(0, viewport.left - rect.left));
5199
+ const top = Math.min(rect.height, Math.max(0, viewport.top - rect.top));
5200
+ const right = Math.min(rect.width, Math.max(0, viewport.left + viewport.width - rect.left));
5201
+ const bottom = Math.min(rect.height, Math.max(0, viewport.top + viewport.height - rect.top));
5202
+ return { left, top, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
5203
+ }
5204
+
5205
+ // src/overlay/pano-overlay.tsx
5206
+ import { jsx as jsx4 } from "react/jsx-runtime";
5207
+ var PanoOverlayContext = createContext11(null);
5208
+ var PanoModalHostContext = createContext11(null);
5209
+ function usePanoOverlay() {
5210
+ const context = useContext11(PanoOverlayContext);
5211
+ if (!context) throw new Error("usePanoOverlay must be used inside PanoViewer.renderOverlay.");
5212
+ return context;
5213
+ }
5214
+ function usePanoModal(open) {
5215
+ const host = useContext11(PanoModalHostContext);
5216
+ if (!host) throw new Error("usePanoModal must be used inside PanoViewer.renderOverlay.");
5217
+ useLayoutEffect3(() => open ? host.acquire() : void 0, [host, open]);
5218
+ }
5219
+ function PanoOverlayPortalProvider({
5220
+ container,
5221
+ children
5222
+ }) {
5223
+ const context = usePanoOverlay();
5224
+ const value = useMemo5(() => ({ ...context, portalContainer: container }), [context, container]);
5225
+ return /* @__PURE__ */ jsx4(PanoOverlayContext.Provider, { value, children });
5226
+ }
5227
+ function usePanoOverlayHost(isFullscreen) {
5228
+ const [portalContainer, setPortalContainer] = useState3(null);
5229
+ const [geometry, setGeometry] = useState3({
5230
+ size: { width: 0, height: 0 },
5231
+ visibleRect: { left: 0, top: 0, width: 0, height: 0 }
5232
+ });
5233
+ useLayoutEffect3(() => {
5234
+ if (!portalContainer) return;
5235
+ const win = portalContainer.ownerDocument.defaultView;
5236
+ if (!win) return;
5237
+ const measure = () => {
5238
+ const width = portalContainer.clientWidth;
5239
+ const height = portalContainer.clientHeight;
5240
+ const visual = win.visualViewport;
5241
+ const visibleRect = getOverlayVisibleRect(portalContainer.getBoundingClientRect(), {
5242
+ left: visual?.offsetLeft ?? 0,
5243
+ top: visual?.offsetTop ?? 0,
5244
+ width: visual?.width ?? win.innerWidth,
5245
+ height: visual?.height ?? win.innerHeight
5246
+ });
5247
+ setGeometry((previous) => previous.size.width === width && previous.size.height === height && visibleRect.left === previous.visibleRect.left && visibleRect.top === previous.visibleRect.top && visibleRect.width === previous.visibleRect.width && visibleRect.height === previous.visibleRect.height ? previous : { size: { width, height }, visibleRect });
5248
+ };
5249
+ let frame = 0;
5250
+ const scheduleMeasure = () => {
5251
+ if (!frame) frame = win.requestAnimationFrame(() => {
5252
+ frame = 0;
5253
+ measure();
5254
+ });
5255
+ };
5256
+ measure();
5257
+ const observer = new ResizeObserver(scheduleMeasure);
5258
+ observer.observe(portalContainer);
5259
+ win.addEventListener("scroll", scheduleMeasure, true);
5260
+ win.addEventListener("resize", scheduleMeasure);
5261
+ win.visualViewport?.addEventListener("resize", scheduleMeasure);
5262
+ win.visualViewport?.addEventListener("scroll", scheduleMeasure);
5263
+ return () => {
5264
+ observer.disconnect();
5265
+ win.cancelAnimationFrame(frame);
5266
+ win.removeEventListener("scroll", scheduleMeasure, true);
5267
+ win.removeEventListener("resize", scheduleMeasure);
5268
+ win.visualViewport?.removeEventListener("resize", scheduleMeasure);
5269
+ win.visualViewport?.removeEventListener("scroll", scheduleMeasure);
5270
+ };
5271
+ }, [portalContainer]);
5272
+ const context = useMemo5(() => ({ portalContainer, ...geometry, isFullscreen }), [portalContainer, geometry, isFullscreen]);
5273
+ return { context, setPortalContainer };
5274
+ }
5275
+
5026
5276
  // src/panorama-event-surface.tsx
5027
5277
  import { useThree as useThree6 } from "@react-three/fiber";
5028
- import { useContext as useContext11, useEffect as useEffect10 } from "react";
5278
+ import { useContext as useContext12, useEffect as useEffect10 } from "react";
5029
5279
  import { BackSide, Raycaster, Vector2, Vector3 as Vector32 } from "three";
5030
5280
 
5031
5281
  // src/hotspot/coordinates.ts
@@ -5103,7 +5353,7 @@ var DEFAULT_PANORAMA_CAMERA_NEAR = 0.1;
5103
5353
  var DEFAULT_PANORAMA_CAMERA_FAR = DEFAULT_PANORAMA_RADIUS * 2;
5104
5354
 
5105
5355
  // src/panorama-event-surface.tsx
5106
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
5356
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
5107
5357
  var EVENT_SURFACE_RADIUS = DEFAULT_PANORAMA_RADIUS - 1;
5108
5358
  function makePanoramaPointerEvent(event) {
5109
5359
  const nativeEvent = event.nativeEvent;
@@ -5118,7 +5368,7 @@ function makePanoramaPointerEvent(event) {
5118
5368
  };
5119
5369
  }
5120
5370
  function PanoramaEventSurface() {
5121
- const eventBus = useContext11(PanoEventBusContext);
5371
+ const eventBus = useContext12(PanoEventBusContext);
5122
5372
  const { camera, gl } = useThree6();
5123
5373
  useEffect10(() => {
5124
5374
  if (!eventBus) {
@@ -5171,8 +5421,8 @@ function PanoramaEventSurface() {
5171
5421
  onPointerUp: (event) => emit("pointerup", event),
5172
5422
  onPointerMove: (event) => emit("pointermove", event),
5173
5423
  children: [
5174
- /* @__PURE__ */ jsx4("sphereGeometry", { args: [EVENT_SURFACE_RADIUS, 64, 32] }),
5175
- /* @__PURE__ */ jsx4(
5424
+ /* @__PURE__ */ jsx5("sphereGeometry", { args: [EVENT_SURFACE_RADIUS, 64, 32] }),
5425
+ /* @__PURE__ */ jsx5(
5176
5426
  "meshBasicMaterial",
5177
5427
  {
5178
5428
  colorWrite: false,
@@ -5189,8 +5439,8 @@ function PanoramaEventSurface() {
5189
5439
  }
5190
5440
 
5191
5441
  // src/scenes-host.ts
5192
- import { createContext as createContext11 } from "react";
5193
- var ScenesHostContext = createContext11(null);
5442
+ import { createContext as createContext12 } from "react";
5443
+ var ScenesHostContext = createContext12(null);
5194
5444
  function createScenesHost() {
5195
5445
  return {
5196
5446
  controller: null,
@@ -5207,7 +5457,7 @@ function notifyScenesHost(host) {
5207
5457
 
5208
5458
  // src/touch-controls.tsx
5209
5459
  import { useThree as useThree7 } from "@react-three/fiber";
5210
- import { useContext as useContext12, useEffect as useEffect11, useRef as useRef10 } from "react";
5460
+ import { useContext as useContext13, useEffect as useEffect11, useRef as useRef10 } from "react";
5211
5461
  var DEFAULT_ROTATE_SPEED3 = 0.35;
5212
5462
  function resolveSpeed3(value, fallback) {
5213
5463
  return Number.isFinite(value) && value >= 0 ? value : fallback;
@@ -5222,7 +5472,7 @@ function TouchControls({
5222
5472
  pinchZoom = true
5223
5473
  }) {
5224
5474
  useClaimControlChannel("touch");
5225
- const controlsRef = useContext12(PanoramaViewContext);
5475
+ const controlsRef = useContext13(PanoramaViewContext);
5226
5476
  const { gl } = useThree7();
5227
5477
  const pointersRef = useRef10(/* @__PURE__ */ new Map());
5228
5478
  const pinchDistanceRef = useRef10(null);
@@ -5241,16 +5491,17 @@ function TouchControls({
5241
5491
  }
5242
5492
  pointersRef.current.clear();
5243
5493
  pinchDistanceRef.current = null;
5244
- controlsRef.current?.setPointerActive("touch", false);
5494
+ controlsRef.current?.setPointerActive("touch", false, { cancelled: true });
5245
5495
  };
5246
5496
  if (!enabled) {
5247
5497
  clearPointers();
5248
5498
  return;
5249
5499
  }
5250
- const syncActive = () => {
5500
+ const syncActive = (cancelled = false) => {
5251
5501
  controlsRef.current?.setPointerActive(
5252
5502
  "touch",
5253
- pointersRef.current.size > 0
5503
+ pointersRef.current.size > 0,
5504
+ { cancelled }
5254
5505
  );
5255
5506
  };
5256
5507
  const onPointerDown = (event) => {
@@ -5335,17 +5586,19 @@ function TouchControls({
5335
5586
  if (pointersRef.current.size < 2) {
5336
5587
  pinchDistanceRef.current = null;
5337
5588
  }
5338
- syncActive();
5589
+ syncActive(event.type !== "pointerup");
5339
5590
  };
5340
5591
  element.addEventListener("pointerdown", onPointerDown);
5341
5592
  element.addEventListener("pointermove", onPointerMove);
5342
5593
  element.addEventListener("pointerup", releasePointer);
5343
5594
  element.addEventListener("pointercancel", releasePointer);
5595
+ element.addEventListener("lostpointercapture", releasePointer);
5344
5596
  return () => {
5345
5597
  element.removeEventListener("pointerdown", onPointerDown);
5346
5598
  element.removeEventListener("pointermove", onPointerMove);
5347
5599
  element.removeEventListener("pointerup", releasePointer);
5348
5600
  element.removeEventListener("pointercancel", releasePointer);
5601
+ element.removeEventListener("lostpointercapture", releasePointer);
5349
5602
  clearPointers();
5350
5603
  };
5351
5604
  }, [controlsRef, enabled, gl]);
@@ -5353,21 +5606,21 @@ function TouchControls({
5353
5606
  }
5354
5607
 
5355
5608
  // src/video/chrome-overlay.tsx
5356
- import { createContext as createContext12, useCallback as useCallback4, useMemo as useMemo5, useState as useState3 } from "react";
5357
- import { jsx as jsx5 } from "react/jsx-runtime";
5358
- var PanoChromeOverlayContext = createContext12(null);
5609
+ import { createContext as createContext13, useCallback as useCallback4, useMemo as useMemo6, useState as useState4 } from "react";
5610
+ import { jsx as jsx6 } from "react/jsx-runtime";
5611
+ var PanoChromeOverlayContext = createContext13(null);
5359
5612
  function usePanoChromeOverlay() {
5360
- const [overlayElement, setOverlayElement] = useState3(
5613
+ const [overlayElement, setOverlayElement] = useState4(
5361
5614
  null
5362
5615
  );
5363
5616
  const setOverlayNode = useCallback4((node) => {
5364
5617
  setOverlayElement(node);
5365
5618
  }, []);
5366
- const api = useMemo5(
5619
+ const api = useMemo6(
5367
5620
  () => ({ overlayElement }),
5368
5621
  [overlayElement]
5369
5622
  );
5370
- const overlay = /* @__PURE__ */ jsx5(
5623
+ const overlay = /* @__PURE__ */ jsx6(
5371
5624
  "div",
5372
5625
  {
5373
5626
  className: "pano-chrome-overlay",
@@ -5379,9 +5632,9 @@ function usePanoChromeOverlay() {
5379
5632
  }
5380
5633
 
5381
5634
  // src/video/host.ts
5382
- import { createContext as createContext13, useContext as useContext13 } from "react";
5383
- var PanoVideoHostContext = createContext13(null);
5384
- var DefaultPanoVideoControlsContext = createContext13(false);
5635
+ import { createContext as createContext14, useContext as useContext14 } from "react";
5636
+ var PanoVideoHostContext = createContext14(null);
5637
+ var DefaultPanoVideoControlsContext = createContext14(false);
5385
5638
  function createPanoVideoHost() {
5386
5639
  return {
5387
5640
  controller: null,
@@ -5426,8 +5679,8 @@ function isProduction3() {
5426
5679
  return nodeEnv === "production";
5427
5680
  }
5428
5681
  function useClaimPanoVideoControls() {
5429
- const host = useContext13(PanoVideoHostContext);
5430
- const isDefault = useContext13(DefaultPanoVideoControlsContext);
5682
+ const host = useContext14(PanoVideoHostContext);
5683
+ const isDefault = useContext14(DefaultPanoVideoControlsContext);
5431
5684
  if (!host || isDefault) {
5432
5685
  return;
5433
5686
  }
@@ -5444,7 +5697,7 @@ import { useSyncExternalStore as useSyncExternalStore4 } from "react";
5444
5697
 
5445
5698
  // src/video/pano-video-captions.tsx
5446
5699
  import { useSyncExternalStore as useSyncExternalStore2 } from "react";
5447
- import { jsx as jsx6 } from "react/jsx-runtime";
5700
+ import { jsx as jsx7 } from "react/jsx-runtime";
5448
5701
  var DEFAULT_PANO_VIDEO_CAPTION_APPEARANCE = {
5449
5702
  color: "#ffffff",
5450
5703
  background: "rgba(0, 0, 0, 0.55)",
@@ -5492,13 +5745,13 @@ function PanoVideoCaptionsOverlay({
5492
5745
  textShadow: snapshot.captionAppearance.textShadow,
5493
5746
  whiteSpace: "pre-wrap"
5494
5747
  };
5495
- return /* @__PURE__ */ jsx6(
5748
+ return /* @__PURE__ */ jsx7(
5496
5749
  "div",
5497
5750
  {
5498
5751
  className: "pano-video-captions",
5499
5752
  "data-pano-video-captions": "",
5500
5753
  style: wrapperStyle,
5501
- children: /* @__PURE__ */ jsx6("div", { style: textStyle, children: snapshot.captionText })
5754
+ children: /* @__PURE__ */ jsx7("div", { style: textStyle, children: snapshot.captionText })
5502
5755
  }
5503
5756
  );
5504
5757
  }
@@ -5511,12 +5764,12 @@ function PanoVideoCaptions({
5511
5764
  // src/video/pano-video-controls.tsx
5512
5765
  import {
5513
5766
  useCallback as useCallback5,
5514
- useContext as useContext14,
5767
+ useContext as useContext15,
5515
5768
  useEffect as useEffect12,
5516
5769
  useId as useId2,
5517
- useMemo as useMemo6,
5770
+ useMemo as useMemo7,
5518
5771
  useRef as useRef11,
5519
- useState as useState4,
5772
+ useState as useState5,
5520
5773
  useSyncExternalStore as useSyncExternalStore3
5521
5774
  } from "react";
5522
5775
 
@@ -5650,7 +5903,7 @@ function cueText(cue) {
5650
5903
  }
5651
5904
 
5652
5905
  // src/video/pano-video-controls.tsx
5653
- import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
5906
+ import { Fragment, jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
5654
5907
  var DEFAULT_PANO_VIDEO_CONTROLS_APPEARANCE = {
5655
5908
  background: "rgba(16, 16, 16, 0.88)",
5656
5909
  color: "#f4f4f4",
@@ -5711,7 +5964,7 @@ function volumeFromClientY(clientY, track) {
5711
5964
  return Math.max(0, Math.min(1, (rect.bottom - clientY) / rect.height));
5712
5965
  }
5713
5966
  function Icon({ path }) {
5714
- return /* @__PURE__ */ jsx7(
5967
+ return /* @__PURE__ */ jsx8(
5715
5968
  "svg",
5716
5969
  {
5717
5970
  "aria-hidden": true,
@@ -5723,7 +5976,7 @@ function Icon({ path }) {
5723
5976
  strokeWidth: "1.75",
5724
5977
  viewBox: "0 0 16 16",
5725
5978
  width: "16",
5726
- children: /* @__PURE__ */ jsx7("path", { d: path })
5979
+ children: /* @__PURE__ */ jsx8("path", { d: path })
5727
5980
  }
5728
5981
  );
5729
5982
  }
@@ -5735,7 +5988,7 @@ function ChromeButton({
5735
5988
  label,
5736
5989
  onClick
5737
5990
  }) {
5738
- return /* @__PURE__ */ jsx7(
5991
+ return /* @__PURE__ */ jsx8(
5739
5992
  "button",
5740
5993
  {
5741
5994
  "aria-label": label,
@@ -5761,14 +6014,14 @@ function ChromeMenu({
5761
6014
  labelledBy,
5762
6015
  items
5763
6016
  }) {
5764
- return /* @__PURE__ */ jsx7(
6017
+ return /* @__PURE__ */ jsx8(
5765
6018
  ChromeMenuSurface,
5766
6019
  {
5767
6020
  appearance,
5768
6021
  id,
5769
6022
  labelledBy,
5770
6023
  role: "menu",
5771
- children: items.map((item) => /* @__PURE__ */ jsx7(
6024
+ children: items.map((item) => /* @__PURE__ */ jsx8(
5772
6025
  ChromeMenuRadioItem,
5773
6026
  {
5774
6027
  active: item.active,
@@ -5788,7 +6041,7 @@ function ChromeMenuSurface({
5788
6041
  labelledBy,
5789
6042
  role
5790
6043
  }) {
5791
- return /* @__PURE__ */ jsx7(
6044
+ return /* @__PURE__ */ jsx8(
5792
6045
  "div",
5793
6046
  {
5794
6047
  "aria-labelledby": labelledBy,
@@ -5812,7 +6065,7 @@ function ChromeMenuRadioItem({
5812
6065
  label,
5813
6066
  onClick
5814
6067
  }) {
5815
- return /* @__PURE__ */ jsx7(
6068
+ return /* @__PURE__ */ jsx8(
5816
6069
  "button",
5817
6070
  {
5818
6071
  "aria-checked": active,
@@ -5852,7 +6105,7 @@ function ChromeMenuActionItem({
5852
6105
  },
5853
6106
  type: "button",
5854
6107
  children: [
5855
- /* @__PURE__ */ jsx7("span", { children: label }),
6108
+ /* @__PURE__ */ jsx8("span", { children: label }),
5856
6109
  secondary
5857
6110
  ]
5858
6111
  }
@@ -5861,10 +6114,10 @@ function ChromeMenuActionItem({
5861
6114
  function ChromeMenuSectionLabel({
5862
6115
  children
5863
6116
  }) {
5864
- return /* @__PURE__ */ jsx7("div", { className: "pano-video-chrome-menu-label", children });
6117
+ return /* @__PURE__ */ jsx8("div", { className: "pano-video-chrome-menu-label", children });
5865
6118
  }
5866
6119
  function ChromeMenuDivider() {
5867
- return /* @__PURE__ */ jsx7("div", { className: "pano-video-chrome-menu-divider" });
6120
+ return /* @__PURE__ */ jsx8("div", { className: "pano-video-chrome-menu-divider" });
5868
6121
  }
5869
6122
  function CaptionsMenu({
5870
6123
  appearance,
@@ -5877,7 +6130,7 @@ function CaptionsMenu({
5877
6130
  panel,
5878
6131
  snapshot
5879
6132
  }) {
5880
- return /* @__PURE__ */ jsx7(
6133
+ return /* @__PURE__ */ jsx8(
5881
6134
  ChromeMenuSurface,
5882
6135
  {
5883
6136
  appearance,
@@ -5886,7 +6139,7 @@ function CaptionsMenu({
5886
6139
  role: "menu",
5887
6140
  children: panel === "tracks" ? /* @__PURE__ */ jsxs3(Fragment, { children: [
5888
6141
  /* @__PURE__ */ jsxs3("div", { className: "pano-video-chrome-menu-list", children: [
5889
- /* @__PURE__ */ jsx7(
6142
+ /* @__PURE__ */ jsx8(
5890
6143
  ChromeMenuRadioItem,
5891
6144
  {
5892
6145
  active: snapshot.trackId === null,
@@ -5899,7 +6152,7 @@ function CaptionsMenu({
5899
6152
  ),
5900
6153
  snapshot.tracks.map((track) => {
5901
6154
  const trackItemId = panoVideoTrackId(track);
5902
- return /* @__PURE__ */ jsx7(
6155
+ return /* @__PURE__ */ jsx8(
5903
6156
  ChromeMenuRadioItem,
5904
6157
  {
5905
6158
  active: snapshot.trackId === trackItemId,
@@ -5913,29 +6166,29 @@ function CaptionsMenu({
5913
6166
  );
5914
6167
  })
5915
6168
  ] }),
5916
- /* @__PURE__ */ jsx7(ChromeMenuDivider, {}),
5917
- /* @__PURE__ */ jsx7(
6169
+ /* @__PURE__ */ jsx8(ChromeMenuDivider, {}),
6170
+ /* @__PURE__ */ jsx8(
5918
6171
  ChromeMenuActionItem,
5919
6172
  {
5920
6173
  appearance,
5921
6174
  label: "Settings",
5922
6175
  onClick: onOpenSettings,
5923
- secondary: /* @__PURE__ */ jsx7("span", { "aria-hidden": true, children: "\u203A" })
6176
+ secondary: /* @__PURE__ */ jsx8("span", { "aria-hidden": true, children: "\u203A" })
5924
6177
  }
5925
6178
  )
5926
6179
  ] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
5927
- /* @__PURE__ */ jsx7(
6180
+ /* @__PURE__ */ jsx8(
5928
6181
  ChromeMenuActionItem,
5929
6182
  {
5930
6183
  appearance,
5931
6184
  label: "Back",
5932
6185
  onClick: onShowTracks,
5933
- secondary: /* @__PURE__ */ jsx7("span", { "aria-hidden": true, children: "\u2039" })
6186
+ secondary: /* @__PURE__ */ jsx8("span", { "aria-hidden": true, children: "\u2039" })
5934
6187
  }
5935
6188
  ),
5936
- /* @__PURE__ */ jsx7(ChromeMenuDivider, {}),
5937
- /* @__PURE__ */ jsx7(ChromeMenuSectionLabel, { children: "Font size" }),
5938
- CAPTION_SIZE_OPTIONS.map((option) => /* @__PURE__ */ jsx7(
6189
+ /* @__PURE__ */ jsx8(ChromeMenuDivider, {}),
6190
+ /* @__PURE__ */ jsx8(ChromeMenuSectionLabel, { children: "Font size" }),
6191
+ CAPTION_SIZE_OPTIONS.map((option) => /* @__PURE__ */ jsx8(
5939
6192
  ChromeMenuRadioItem,
5940
6193
  {
5941
6194
  active: Number(snapshot.captionAppearance.fontSize) === option.value,
@@ -5947,9 +6200,9 @@ function CaptionsMenu({
5947
6200
  },
5948
6201
  option.id
5949
6202
  )),
5950
- /* @__PURE__ */ jsx7(ChromeMenuDivider, {}),
5951
- /* @__PURE__ */ jsx7(ChromeMenuSectionLabel, { children: "Background" }),
5952
- CAPTION_BACKGROUND_OPTIONS.map((option) => /* @__PURE__ */ jsx7(
6203
+ /* @__PURE__ */ jsx8(ChromeMenuDivider, {}),
6204
+ /* @__PURE__ */ jsx8(ChromeMenuSectionLabel, { children: "Background" }),
6205
+ CAPTION_BACKGROUND_OPTIONS.map((option) => /* @__PURE__ */ jsx8(
5953
6206
  ChromeMenuRadioItem,
5954
6207
  {
5955
6208
  active: snapshot.captionAppearance.background === option.value,
@@ -6004,7 +6257,7 @@ function VerticalVolumeSlider({
6004
6257
  pointerEvents: "auto"
6005
6258
  },
6006
6259
  children: [
6007
- /* @__PURE__ */ jsx7(
6260
+ /* @__PURE__ */ jsx8(
6008
6261
  "span",
6009
6262
  {
6010
6263
  "aria-hidden": true,
@@ -6016,7 +6269,7 @@ function VerticalVolumeSlider({
6016
6269
  children: percentLabel
6017
6270
  }
6018
6271
  ),
6019
- /* @__PURE__ */ jsx7(
6272
+ /* @__PURE__ */ jsx8(
6020
6273
  "div",
6021
6274
  {
6022
6275
  style: {
@@ -6056,7 +6309,7 @@ function VerticalVolumeSlider({
6056
6309
  },
6057
6310
  tabIndex: 0,
6058
6311
  children: [
6059
- /* @__PURE__ */ jsx7(
6312
+ /* @__PURE__ */ jsx8(
6060
6313
  "div",
6061
6314
  {
6062
6315
  style: {
@@ -6071,7 +6324,7 @@ function VerticalVolumeSlider({
6071
6324
  }
6072
6325
  }
6073
6326
  ),
6074
- /* @__PURE__ */ jsx7(
6327
+ /* @__PURE__ */ jsx8(
6075
6328
  "div",
6076
6329
  {
6077
6330
  style: {
@@ -6107,7 +6360,7 @@ function VolumeControl({
6107
6360
  volumeAdjustable
6108
6361
  }) {
6109
6362
  const rootRef = useRef11(null);
6110
- const [open, setOpen] = useState4(false);
6363
+ const [open, setOpen] = useState5(false);
6111
6364
  const displayVolume = muted ? 0 : volume;
6112
6365
  const mutedOrSilent = muted || volume === 0;
6113
6366
  useEffect12(() => {
@@ -6130,13 +6383,13 @@ function VolumeControl({
6130
6383
  };
6131
6384
  }, [open, volumeAdjustable]);
6132
6385
  if (!volumeAdjustable) {
6133
- return /* @__PURE__ */ jsx7(
6386
+ return /* @__PURE__ */ jsx8(
6134
6387
  ChromeButton,
6135
6388
  {
6136
6389
  appearance,
6137
6390
  label: mutedOrSilent ? "Unmute" : "Mute",
6138
6391
  onClick: onToggleMuted,
6139
- children: mutedOrSilent ? /* @__PURE__ */ jsx7(Icon, { path: "M3 6.5h2.2L8.5 4v8L5.2 9.5H3zM10.2 6.2l3.2 3.6M13.4 6.2l-3.2 3.6" }) : /* @__PURE__ */ jsx7(Icon, { path: "M3 6.5h2.2L8.5 4v8L5.2 9.5H3zM10.5 5.5a3 3 0 0 1 0 5" })
6392
+ children: mutedOrSilent ? /* @__PURE__ */ jsx8(Icon, { path: "M3 6.5h2.2L8.5 4v8L5.2 9.5H3zM10.2 6.2l3.2 3.6M13.4 6.2l-3.2 3.6" }) : /* @__PURE__ */ jsx8(Icon, { path: "M3 6.5h2.2L8.5 4v8L5.2 9.5H3zM10.5 5.5a3 3 0 0 1 0 5" })
6140
6393
  }
6141
6394
  );
6142
6395
  }
@@ -6168,7 +6421,7 @@ function VolumeControl({
6168
6421
  },
6169
6422
  ref: rootRef,
6170
6423
  children: [
6171
- open ? /* @__PURE__ */ jsx7(
6424
+ open ? /* @__PURE__ */ jsx8(
6172
6425
  VerticalVolumeSlider,
6173
6426
  {
6174
6427
  appearance,
@@ -6176,7 +6429,7 @@ function VolumeControl({
6176
6429
  volume: displayVolume
6177
6430
  }
6178
6431
  ) : null,
6179
- /* @__PURE__ */ jsx7(
6432
+ /* @__PURE__ */ jsx8(
6180
6433
  ChromeButton,
6181
6434
  {
6182
6435
  appearance,
@@ -6187,7 +6440,7 @@ function VolumeControl({
6187
6440
  }
6188
6441
  onToggleMuted();
6189
6442
  },
6190
- children: mutedOrSilent ? /* @__PURE__ */ jsx7(Icon, { path: "M3 6.5h2.2L8.5 4v8L5.2 9.5H3zM10.2 6.2l3.2 3.6M13.4 6.2l-3.2 3.6" }) : /* @__PURE__ */ jsx7(Icon, { path: "M3 6.5h2.2L8.5 4v8L5.2 9.5H3zM10.5 5.5a3 3 0 0 1 0 5" })
6443
+ children: mutedOrSilent ? /* @__PURE__ */ jsx8(Icon, { path: "M3 6.5h2.2L8.5 4v8L5.2 9.5H3zM10.2 6.2l3.2 3.6M13.4 6.2l-3.2 3.6" }) : /* @__PURE__ */ jsx8(Icon, { path: "M3 6.5h2.2L8.5 4v8L5.2 9.5H3zM10.5 5.5a3 3 0 0 1 0 5" })
6191
6444
  }
6192
6445
  )
6193
6446
  ]
@@ -6200,7 +6453,7 @@ function PanoVideoControlsHud({
6200
6453
  fullscreen,
6201
6454
  overlayElement
6202
6455
  }) {
6203
- const resolved = useMemo6(
6456
+ const resolved = useMemo7(
6204
6457
  () => resolveControlsAppearance(appearance),
6205
6458
  [appearance]
6206
6459
  );
@@ -6209,11 +6462,11 @@ function PanoVideoControlsHud({
6209
6462
  controller.getSnapshot,
6210
6463
  controller.getSnapshot
6211
6464
  );
6212
- const [menu, setMenu] = useState4(null);
6213
- const [captionsPanel, setCaptionsPanel] = useState4("tracks");
6214
- const [visible, setVisible] = useState4(true);
6215
- const [volumeOpen, setVolumeOpen] = useState4(false);
6216
- const [overlayWidth, setOverlayWidth] = useState4(
6465
+ const [menu, setMenu] = useState5(null);
6466
+ const [captionsPanel, setCaptionsPanel] = useState5("tracks");
6467
+ const [visible, setVisible] = useState5(true);
6468
+ const [volumeOpen, setVolumeOpen] = useState5(false);
6469
+ const [overlayWidth, setOverlayWidth] = useState5(
6217
6470
  () => overlayElement.clientWidth
6218
6471
  );
6219
6472
  const idleTimerRef = useRef11(0);
@@ -6347,7 +6600,7 @@ function PanoVideoControlsHud({
6347
6600
  let menuNode = null;
6348
6601
  switch (menu) {
6349
6602
  case "rate":
6350
- menuNode = /* @__PURE__ */ jsx7(
6603
+ menuNode = /* @__PURE__ */ jsx8(
6351
6604
  ChromeMenu,
6352
6605
  {
6353
6606
  appearance: resolved,
@@ -6366,7 +6619,7 @@ function PanoVideoControlsHud({
6366
6619
  );
6367
6620
  break;
6368
6621
  case "variant":
6369
- menuNode = /* @__PURE__ */ jsx7(
6622
+ menuNode = /* @__PURE__ */ jsx8(
6370
6623
  ChromeMenu,
6371
6624
  {
6372
6625
  appearance: resolved,
@@ -6385,7 +6638,7 @@ function PanoVideoControlsHud({
6385
6638
  );
6386
6639
  break;
6387
6640
  case "captions":
6388
- menuNode = /* @__PURE__ */ jsx7(
6641
+ menuNode = /* @__PURE__ */ jsx8(
6389
6642
  CaptionsMenu,
6390
6643
  {
6391
6644
  appearance: resolved,
@@ -6409,7 +6662,7 @@ function PanoVideoControlsHud({
6409
6662
  );
6410
6663
  break;
6411
6664
  case "more":
6412
- menuNode = /* @__PURE__ */ jsx7(
6665
+ menuNode = /* @__PURE__ */ jsx8(
6413
6666
  ChromeMenu,
6414
6667
  {
6415
6668
  appearance: resolved,
@@ -6477,7 +6730,7 @@ function PanoVideoControlsHud({
6477
6730
  };
6478
6731
  const rateButton = /* @__PURE__ */ jsxs3("div", { className: "pano-video-menu", "data-pano-video-menu": "rate", children: [
6479
6732
  !compact && menu === "rate" ? menuNode : null,
6480
- /* @__PURE__ */ jsx7(
6733
+ /* @__PURE__ */ jsx8(
6481
6734
  ChromeButton,
6482
6735
  {
6483
6736
  appearance: resolved,
@@ -6485,13 +6738,13 @@ function PanoVideoControlsHud({
6485
6738
  onClick: () => {
6486
6739
  toggleMenu("rate");
6487
6740
  },
6488
- children: /* @__PURE__ */ jsx7("span", { className: "pano-video-chrome-label", id: `${barId}-rate-btn`, children: snapshot.playbackRate === 1 ? "1x" : `${snapshot.playbackRate}x` })
6741
+ children: /* @__PURE__ */ jsx8("span", { className: "pano-video-chrome-label", id: `${barId}-rate-btn`, children: snapshot.playbackRate === 1 ? "1x" : `${snapshot.playbackRate}x` })
6489
6742
  }
6490
6743
  )
6491
6744
  ] });
6492
6745
  const variantButton = canSwitchVariant ? /* @__PURE__ */ jsxs3("div", { className: "pano-video-menu", "data-pano-video-menu": "variant", children: [
6493
6746
  !compact && menu === "variant" ? menuNode : null,
6494
- /* @__PURE__ */ jsx7(
6747
+ /* @__PURE__ */ jsx8(
6495
6748
  ChromeButton,
6496
6749
  {
6497
6750
  appearance: resolved,
@@ -6499,13 +6752,13 @@ function PanoVideoControlsHud({
6499
6752
  onClick: () => {
6500
6753
  toggleMenu("variant");
6501
6754
  },
6502
- children: /* @__PURE__ */ jsx7("span", { className: "pano-video-chrome-label", id: `${barId}-variant-btn`, children: snapshot.variants.find((variant) => variant.id === snapshot.variantId)?.label ?? "Quality" })
6755
+ children: /* @__PURE__ */ jsx8("span", { className: "pano-video-chrome-label", id: `${barId}-variant-btn`, children: snapshot.variants.find((variant) => variant.id === snapshot.variantId)?.label ?? "Quality" })
6503
6756
  }
6504
6757
  )
6505
6758
  ] }) : null;
6506
6759
  const captionsButton = showCaptionsMenu ? /* @__PURE__ */ jsxs3("div", { className: "pano-video-menu", "data-pano-video-menu": "captions", children: [
6507
6760
  !compact && menu === "captions" ? menuNode : null,
6508
- /* @__PURE__ */ jsx7(
6761
+ /* @__PURE__ */ jsx8(
6509
6762
  ChromeButton,
6510
6763
  {
6511
6764
  appearance: resolved,
@@ -6513,7 +6766,7 @@ function PanoVideoControlsHud({
6513
6766
  onClick: () => {
6514
6767
  toggleMenu("captions");
6515
6768
  },
6516
- children: /* @__PURE__ */ jsx7("span", { id: `${barId}-captions-btn`, children: /* @__PURE__ */ jsx7(Icon, { path: "M2.5 4.5h11v7h-11zM5 7.2h2.4M5 9.2h5.5" }) })
6769
+ children: /* @__PURE__ */ jsx8("span", { id: `${barId}-captions-btn`, children: /* @__PURE__ */ jsx8(Icon, { path: "M2.5 4.5h11v7h-11zM5 7.2h2.4M5 9.2h5.5" }) })
6517
6770
  }
6518
6771
  )
6519
6772
  ] }) : null;
@@ -6524,7 +6777,7 @@ function PanoVideoControlsHud({
6524
6777
  "data-pano-video-controls": "",
6525
6778
  style: barStyle,
6526
6779
  children: [
6527
- /* @__PURE__ */ jsx7(
6780
+ /* @__PURE__ */ jsx8(
6528
6781
  ChromeButton,
6529
6782
  {
6530
6783
  appearance: resolved,
@@ -6532,11 +6785,11 @@ function PanoVideoControlsHud({
6532
6785
  onClick: () => {
6533
6786
  controller.togglePlay();
6534
6787
  },
6535
- children: snapshot.playing ? /* @__PURE__ */ jsx7(Icon, { path: "M5 3.5h2.2v9H5zM8.8 3.5H11v9H8.8z" }) : /* @__PURE__ */ jsx7(Icon, { path: "M5 3.2v9.6L13 8z" })
6788
+ children: snapshot.playing ? /* @__PURE__ */ jsx8(Icon, { path: "M5 3.5h2.2v9H5zM8.8 3.5H11v9H8.8z" }) : /* @__PURE__ */ jsx8(Icon, { path: "M5 3.2v9.6L13 8z" })
6536
6789
  }
6537
6790
  ),
6538
- hideTime ? null : /* @__PURE__ */ jsx7("span", { className: "pano-video-time", children: formatMediaTime(snapshot.currentTime) }),
6539
- /* @__PURE__ */ jsx7(
6791
+ hideTime ? null : /* @__PURE__ */ jsx8("span", { className: "pano-video-time", children: formatMediaTime(snapshot.currentTime) }),
6792
+ /* @__PURE__ */ jsx8(
6540
6793
  "input",
6541
6794
  {
6542
6795
  "aria-label": "Seek",
@@ -6558,8 +6811,8 @@ function PanoVideoControlsHud({
6558
6811
  value: duration > 0 ? snapshot.currentTime : 0
6559
6812
  }
6560
6813
  ),
6561
- hideTime ? null : /* @__PURE__ */ jsx7("span", { className: "pano-video-time", children: formatMediaTime(duration) }),
6562
- /* @__PURE__ */ jsx7(
6814
+ hideTime ? null : /* @__PURE__ */ jsx8("span", { className: "pano-video-time", children: formatMediaTime(duration) }),
6815
+ /* @__PURE__ */ jsx8(
6563
6816
  VolumeControl,
6564
6817
  {
6565
6818
  appearance: resolved,
@@ -6577,7 +6830,7 @@ function PanoVideoControlsHud({
6577
6830
  ),
6578
6831
  compact ? /* @__PURE__ */ jsxs3("div", { className: "pano-video-menu", "data-pano-video-menu": "more", children: [
6579
6832
  menu !== null ? menuNode : null,
6580
- /* @__PURE__ */ jsx7(
6833
+ /* @__PURE__ */ jsx8(
6581
6834
  ChromeButton,
6582
6835
  {
6583
6836
  appearance: resolved,
@@ -6586,7 +6839,7 @@ function PanoVideoControlsHud({
6586
6839
  onClick: () => {
6587
6840
  toggleMenu("more");
6588
6841
  },
6589
- children: /* @__PURE__ */ jsx7(Icon, { path: "M3 8a1 1 0 1 0 2 0 1 1 0 1 0-2 0M7 8a1 1 0 1 0 2 0 1 1 0 1 0-2 0M11 8a1 1 0 1 0 2 0 1 1 0 1 0-2 0" })
6842
+ children: /* @__PURE__ */ jsx8(Icon, { path: "M3 8a1 1 0 1 0 2 0 1 1 0 1 0-2 0M7 8a1 1 0 1 0 2 0 1 1 0 1 0-2 0M11 8a1 1 0 1 0 2 0 1 1 0 1 0-2 0" })
6590
6843
  }
6591
6844
  )
6592
6845
  ] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
@@ -6594,7 +6847,7 @@ function PanoVideoControlsHud({
6594
6847
  variantButton,
6595
6848
  captionsButton
6596
6849
  ] }),
6597
- showFullscreen ? /* @__PURE__ */ jsx7(
6850
+ showFullscreen ? /* @__PURE__ */ jsx8(
6598
6851
  ChromeButton,
6599
6852
  {
6600
6853
  appearance: resolved,
@@ -6602,7 +6855,7 @@ function PanoVideoControlsHud({
6602
6855
  onClick: () => {
6603
6856
  fullscreen?.toggleFullscreen();
6604
6857
  },
6605
- children: fullscreen?.isFullscreen ? /* @__PURE__ */ jsx7(Icon, { path: "M6 3.5H3.5V6M10 3.5h2.5V6M6 12.5H3.5V10M10 12.5h2.5V10" }) : /* @__PURE__ */ jsx7(Icon, { path: "M3.5 6V3.5H6M10 3.5h2.5V6M3.5 10v2.5H6M10 12.5h2.5V10" })
6858
+ children: fullscreen?.isFullscreen ? /* @__PURE__ */ jsx8(Icon, { path: "M6 3.5H3.5V6M10 3.5h2.5V6M6 12.5H3.5V10M10 12.5h2.5V10" }) : /* @__PURE__ */ jsx8(Icon, { path: "M3.5 6V3.5H6M10 3.5h2.5V6M3.5 10v2.5H6M10 12.5h2.5V10" })
6606
6859
  }
6607
6860
  ) : null
6608
6861
  ]
@@ -6611,7 +6864,7 @@ function PanoVideoControlsHud({
6611
6864
  }
6612
6865
  function PanoVideoControls({ appearance }) {
6613
6866
  useClaimPanoVideoControls();
6614
- const host = useContext14(PanoVideoHostContext);
6867
+ const host = useContext15(PanoVideoHostContext);
6615
6868
  if (host && appearance) {
6616
6869
  host.controls = appearance;
6617
6870
  }
@@ -6619,7 +6872,7 @@ function PanoVideoControls({ appearance }) {
6619
6872
  }
6620
6873
 
6621
6874
  // src/video/pano-video-chrome-bridge.tsx
6622
- import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
6875
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
6623
6876
  function PanoVideoChromeBridge({
6624
6877
  fullscreen,
6625
6878
  host,
@@ -6635,7 +6888,7 @@ function PanoVideoChromeBridge({
6635
6888
  }
6636
6889
  const showDefaultControls = host.controller !== null && host.controls !== false && host.controlClaims === 0;
6637
6890
  return /* @__PURE__ */ jsxs4(Fragment2, { children: [
6638
- showDefaultControls ? /* @__PURE__ */ jsx8(
6891
+ showDefaultControls ? /* @__PURE__ */ jsx9(
6639
6892
  PanoVideoControlsHud,
6640
6893
  {
6641
6894
  appearance: typeof host.controls === "object" ? host.controls : void 0,
@@ -6644,7 +6897,7 @@ function PanoVideoChromeBridge({
6644
6897
  overlayElement
6645
6898
  }
6646
6899
  ) : null,
6647
- host.captions !== false && host.controller ? /* @__PURE__ */ jsx8(PanoVideoCaptionsOverlay, { controller: host.controller }) : null
6900
+ host.captions !== false && host.controller ? /* @__PURE__ */ jsx9(PanoVideoCaptionsOverlay, { controller: host.controller }) : null
6648
6901
  ] });
6649
6902
  }
6650
6903
 
@@ -6652,8 +6905,8 @@ function PanoVideoChromeBridge({
6652
6905
  import { useSyncExternalStore as useSyncExternalStore5 } from "react";
6653
6906
 
6654
6907
  // src/webvr/host.ts
6655
- import { createContext as createContext14 } from "react";
6656
- var WebVRRuntimeContext = createContext14(null);
6908
+ import { createContext as createContext15 } from "react";
6909
+ var WebVRRuntimeContext = createContext15(null);
6657
6910
  function createWebVRHost() {
6658
6911
  return {
6659
6912
  controller: null,
@@ -6797,7 +7050,7 @@ function clamp2(value, min, max) {
6797
7050
  }
6798
7051
 
6799
7052
  // src/webvr/chrome.tsx
6800
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
7053
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
6801
7054
  function resolveAppearance(value) {
6802
7055
  return {
6803
7056
  ...DEFAULT_WEBVR_CHROME_APPEARANCE,
@@ -6809,7 +7062,7 @@ function ChromeButton2({
6809
7062
  children,
6810
7063
  onClick
6811
7064
  }) {
6812
- return /* @__PURE__ */ jsx9(
7065
+ return /* @__PURE__ */ jsx10(
6813
7066
  "button",
6814
7067
  {
6815
7068
  className: "pano-webvr-button",
@@ -6854,11 +7107,11 @@ function SetupPanel({
6854
7107
  role: "dialog",
6855
7108
  style: panelStyle,
6856
7109
  children: [
6857
- /* @__PURE__ */ jsx9("h2", { className: "pano-webvr-setup-title", children: appearance.setupTitle ?? "MobileVR Setup" }),
7110
+ /* @__PURE__ */ jsx10("h2", { className: "pano-webvr-setup-title", children: appearance.setupTitle ?? "MobileVR Setup" }),
6858
7111
  /* @__PURE__ */ jsxs5("label", { className: "pano-webvr-setup-field", children: [
6859
7112
  "Screen size (inch)",
6860
7113
  /* @__PURE__ */ jsxs5("div", { className: "pano-webvr-setup-row", children: [
6861
- /* @__PURE__ */ jsx9(
7114
+ /* @__PURE__ */ jsx10(
6862
7115
  "button",
6863
7116
  {
6864
7117
  "aria-label": "Decrease screen size",
@@ -6870,8 +7123,8 @@ function SetupPanel({
6870
7123
  children: "\u2039"
6871
7124
  }
6872
7125
  ),
6873
- /* @__PURE__ */ jsx9("strong", { className: "pano-webvr-setup-value", children: settings.screensize === "auto" ? "Auto" : settings.screensize.toFixed(1) }),
6874
- /* @__PURE__ */ jsx9(
7126
+ /* @__PURE__ */ jsx10("strong", { className: "pano-webvr-setup-value", children: settings.screensize === "auto" ? "Auto" : settings.screensize.toFixed(1) }),
7127
+ /* @__PURE__ */ jsx10(
6875
7128
  "button",
6876
7129
  {
6877
7130
  "aria-label": "Increase screen size",
@@ -6886,9 +7139,9 @@ function SetupPanel({
6886
7139
  ] })
6887
7140
  ] }),
6888
7141
  /* @__PURE__ */ jsxs5("fieldset", { className: "pano-webvr-setup-fieldset", children: [
6889
- /* @__PURE__ */ jsx9("legend", { className: "pano-webvr-setup-legend", children: "VR headset preset" }),
7142
+ /* @__PURE__ */ jsx10("legend", { className: "pano-webvr-setup-legend", children: "VR headset preset" }),
6890
7143
  /* @__PURE__ */ jsxs5("div", { className: "pano-webvr-setup-row", children: [
6891
- /* @__PURE__ */ jsx9(
7144
+ /* @__PURE__ */ jsx10(
6892
7145
  "button",
6893
7146
  {
6894
7147
  "aria-label": "Previous headset preset",
@@ -6898,8 +7151,8 @@ function SetupPanel({
6898
7151
  children: "\u2039"
6899
7152
  }
6900
7153
  ),
6901
- /* @__PURE__ */ jsx9("strong", { className: "pano-webvr-setup-value-wide", children: WEBVR_PROFILES[settings.profileId].label }),
6902
- /* @__PURE__ */ jsx9(
7154
+ /* @__PURE__ */ jsx10("strong", { className: "pano-webvr-setup-value-wide", children: WEBVR_PROFILES[settings.profileId].label }),
7155
+ /* @__PURE__ */ jsx10(
6903
7156
  "button",
6904
7157
  {
6905
7158
  "aria-label": "Next headset preset",
@@ -6911,7 +7164,7 @@ function SetupPanel({
6911
7164
  )
6912
7165
  ] })
6913
7166
  ] }),
6914
- /* @__PURE__ */ jsx9(
7167
+ /* @__PURE__ */ jsx10(
6915
7168
  ChromeButton2,
6916
7169
  {
6917
7170
  appearance,
@@ -6935,8 +7188,8 @@ function WebVRChromeBridge({ host }) {
6935
7188
  }
6936
7189
  const appearance = resolveAppearance(chrome);
6937
7190
  return /* @__PURE__ */ jsxs5(Fragment3, { children: [
6938
- /* @__PURE__ */ jsx9("div", { className: "pano-webvr-toolbar", children: mode ? /* @__PURE__ */ jsxs5("div", { className: "pano-webvr-toolbar-actions", children: [
6939
- mode !== "webxr" ? /* @__PURE__ */ jsx9(
7191
+ /* @__PURE__ */ jsx10("div", { className: "pano-webvr-toolbar", children: mode ? /* @__PURE__ */ jsxs5("div", { className: "pano-webvr-toolbar-actions", children: [
7192
+ mode !== "webxr" ? /* @__PURE__ */ jsx10(
6940
7193
  ChromeButton2,
6941
7194
  {
6942
7195
  appearance,
@@ -6944,7 +7197,7 @@ function WebVRChromeBridge({ host }) {
6944
7197
  children: "VR Setup"
6945
7198
  }
6946
7199
  ) : null,
6947
- /* @__PURE__ */ jsx9(
7200
+ /* @__PURE__ */ jsx10(
6948
7201
  ChromeButton2,
6949
7202
  {
6950
7203
  appearance,
@@ -6952,7 +7205,7 @@ function WebVRChromeBridge({ host }) {
6952
7205
  children: "Exit VR"
6953
7206
  }
6954
7207
  )
6955
- ] }) : /* @__PURE__ */ jsx9(
7208
+ ] }) : /* @__PURE__ */ jsx10(
6956
7209
  ChromeButton2,
6957
7210
  {
6958
7211
  appearance,
@@ -6960,7 +7213,7 @@ function WebVRChromeBridge({ host }) {
6960
7213
  children: available ? "Enter VR" : "VR unavailable"
6961
7214
  }
6962
7215
  ) }),
6963
- mode === "fake" ? /* @__PURE__ */ jsx9(
7216
+ mode === "fake" ? /* @__PURE__ */ jsx10(
6964
7217
  "p",
6965
7218
  {
6966
7219
  className: "pano-webvr-hint",
@@ -6968,7 +7221,7 @@ function WebVRChromeBridge({ host }) {
6968
7221
  children: "Simulated VR Mode \xB7 Click the view to lock the mouse, then move to look around. Use a headset or phone for real VR."
6969
7222
  }
6970
7223
  ) : null,
6971
- setupOpen ? /* @__PURE__ */ jsx9(
7224
+ setupOpen ? /* @__PURE__ */ jsx10(
6972
7225
  SetupPanel,
6973
7226
  {
6974
7227
  appearance,
@@ -7010,7 +7263,7 @@ import { useFrame as useFrame5, useThree as useThree8 } from "@react-three/fiber
7010
7263
  import { useXR as useXR2, XROrigin } from "@react-three/xr";
7011
7264
  import { useRef as useRef12 } from "react";
7012
7265
  import { Vector3 as Vector34 } from "three";
7013
- import { jsx as jsx10 } from "react/jsx-runtime";
7266
+ import { jsx as jsx11 } from "react/jsx-runtime";
7014
7267
  function PanoramaXROrigin() {
7015
7268
  const originRef = useRef12(null);
7016
7269
  const localHeadPositionRef = useRef12(new Vector34());
@@ -7026,11 +7279,11 @@ function PanoramaXROrigin() {
7026
7279
  origin.position.copy(localHeadPositionRef.current).multiplyScalar(-1);
7027
7280
  origin.updateMatrixWorld();
7028
7281
  }, -100);
7029
- return /* @__PURE__ */ jsx10(XROrigin, { ref: originRef });
7282
+ return /* @__PURE__ */ jsx11(XROrigin, { ref: originRef });
7030
7283
  }
7031
7284
 
7032
7285
  // src/pano-viewer.tsx
7033
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
7286
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
7034
7287
  var DEFAULT_VIEW = {
7035
7288
  yaw: 0,
7036
7289
  pitch: 0,
@@ -7075,10 +7328,10 @@ function PanoCursorTree({
7075
7328
  if (!cursors) {
7076
7329
  return children;
7077
7330
  }
7078
- return /* @__PURE__ */ jsx11(PanoCursorController, { cursors, children });
7331
+ return /* @__PURE__ */ jsx12(PanoCursorController, { cursors, children });
7079
7332
  }
7080
7333
  function PanoVideoHostReset() {
7081
- const host = useContext15(PanoVideoHostContext);
7334
+ const host = useContext16(PanoVideoHostContext);
7082
7335
  if (host) {
7083
7336
  host.controlClaims = 0;
7084
7337
  }
@@ -7091,14 +7344,14 @@ function DefaultControlChannels({
7091
7344
  keyboardChannel,
7092
7345
  fovSpeed
7093
7346
  }) {
7094
- const claims = useContext15(ControlClaimsContext);
7347
+ const claims = useContext16(ControlClaimsContext);
7095
7348
  if (!claims) {
7096
7349
  return null;
7097
7350
  }
7098
7351
  return /* @__PURE__ */ jsxs6(DefaultControlChannelContext.Provider, { value: true, children: [
7099
- userControlsEnabled && mouseChannel.mount && claims.mouse === 0 ? /* @__PURE__ */ jsx11(MouseControls, { ...mouseChannel.props, fovSpeed }) : null,
7100
- userControlsEnabled && touchChannel.mount && claims.touch === 0 ? /* @__PURE__ */ jsx11(TouchControls, { ...touchChannel.props }) : null,
7101
- userControlsEnabled && keyboardChannel.mount && claims.keyboard === 0 ? /* @__PURE__ */ jsx11(
7352
+ userControlsEnabled && mouseChannel.mount && claims.mouse === 0 ? /* @__PURE__ */ jsx12(MouseControls, { ...mouseChannel.props, fovSpeed }) : null,
7353
+ userControlsEnabled && touchChannel.mount && claims.touch === 0 ? /* @__PURE__ */ jsx12(TouchControls, { ...touchChannel.props }) : null,
7354
+ userControlsEnabled && keyboardChannel.mount && claims.keyboard === 0 ? /* @__PURE__ */ jsx12(
7102
7355
  KeyboardControls,
7103
7356
  {
7104
7357
  ...keyboardChannel.props,
@@ -7110,7 +7363,9 @@ function DefaultControlChannels({
7110
7363
  var PanoViewer = forwardRef2(
7111
7364
  function PanoViewer2({
7112
7365
  children,
7366
+ renderOverlay,
7113
7367
  initialView,
7368
+ viewLimits,
7114
7369
  minFov = 30,
7115
7370
  maxFov = 100,
7116
7371
  controls = true,
@@ -7128,18 +7383,24 @@ var PanoViewer = forwardRef2(
7128
7383
  }, ref) {
7129
7384
  const rootRef = useRef13(null);
7130
7385
  const controlsRef = useRef13(null);
7131
- const eventBus = useMemo7(() => createPanoEventBus(), []);
7386
+ const modalHost = useMemo8(() => createPanoModalHost(), []);
7387
+ const modalOpen = useSyncExternalStore6(modalHost.subscribe, modalHost.getSnapshot, () => false);
7388
+ const setControlsRuntime = useCallback6((runtime) => {
7389
+ controlsRef.current = runtime;
7390
+ modalHost.connect(runtime);
7391
+ }, [modalHost]);
7392
+ const eventBus = useMemo8(() => createPanoEventBus(), []);
7132
7393
  const { controls: hotspotAccessibilityControls, registry } = useHotspotAccessibilityLayer();
7133
- const hotspotTargetRegistry = useMemo7(() => createHotspotTargetRegistry(), []);
7394
+ const hotspotTargetRegistry = useMemo8(() => createHotspotTargetRegistry(), []);
7134
7395
  const { api: contextMenuOverlayApi, overlay: contextMenuOverlay } = usePanoContextMenuOverlay();
7135
7396
  const { api: chromeOverlayApi, setOverlayNode, overlayElement } = usePanoChromeOverlay();
7136
- const videoHost = useMemo7(() => createPanoVideoHost(), []);
7137
- const filterHost = useMemo7(() => createPanoFilterHost(), []);
7138
- const webVRHost = useMemo7(() => createWebVRHost(), []);
7139
- const scenesHost = useMemo7(() => createScenesHost(), []);
7140
- const bgmHost = useMemo7(() => createBackgroundAudioHost(), []);
7141
- const mediaActivationHost = useMemo7(() => createPanoMediaActivationHost(), []);
7142
- const mediaActivationOptions = useMemo7(
7397
+ const videoHost = useMemo8(() => createPanoVideoHost(), []);
7398
+ const filterHost = useMemo8(() => createPanoFilterHost(), []);
7399
+ const webVRHost = useMemo8(() => createWebVRHost(), []);
7400
+ const scenesHost = useMemo8(() => createScenesHost(), []);
7401
+ const bgmHost = useMemo8(() => createBackgroundAudioHost(), []);
7402
+ const mediaActivationHost = useMemo8(() => createPanoMediaActivationHost(), []);
7403
+ const mediaActivationOptions = useMemo8(
7143
7404
  () => mediaActivation === false ? null : mediaActivation ?? {},
7144
7405
  [mediaActivation]
7145
7406
  );
@@ -7148,9 +7409,9 @@ var PanoViewer = forwardRef2(
7148
7409
  () => getPanoMediaActivationRevision(mediaActivationHost),
7149
7410
  () => getPanoMediaActivationRevision(mediaActivationHost)
7150
7411
  );
7151
- const [mediaActivated, setMediaActivated] = useState5(false);
7152
- const stereoView = useMemo7(() => createWebVRStereoView(), []);
7153
- const xrStore = useMemo7(
7412
+ const [mediaActivated, setMediaActivated] = useState6(false);
7413
+ const stereoView = useMemo8(() => createWebVRStereoView(), []);
7414
+ const xrStore = useMemo8(
7154
7415
  () => createXRStore({
7155
7416
  anchors: false,
7156
7417
  bodyTracking: false,
@@ -7166,7 +7427,7 @@ var PanoViewer = forwardRef2(
7166
7427
  }),
7167
7428
  []
7168
7429
  );
7169
- const mediaActivationControls = useMemo7(
7430
+ const mediaActivationControls = useMemo8(
7170
7431
  () => ({
7171
7432
  resumeAudio: () => resumePanoAudio(),
7172
7433
  playVideo: async ({ unmute = false } = {}) => {
@@ -7269,34 +7530,33 @@ var PanoViewer = forwardRef2(
7269
7530
  shouldActivateMedia,
7270
7531
  videoHost
7271
7532
  ]);
7533
+ const limitsKey = JSON.stringify(viewLimits);
7534
+ const parsedLimits = useMemo8(() => parseViewLimits(viewLimits), [limitsKey]);
7272
7535
  const fallbackViewRef = useRef13(DEFAULT_VIEW);
7273
7536
  const normalizedMinFov = Math.max(1, Math.min(minFov, maxFov - 1));
7274
7537
  const normalizedMaxFov = Math.min(
7275
7538
  179,
7276
7539
  Math.max(maxFov, normalizedMinFov + 1)
7277
7540
  );
7278
- const normalizedInitialView = useMemo7(
7541
+ const normalizedInitialView = useMemo8(
7279
7542
  () => ({
7280
7543
  yaw: initialView?.yaw ?? DEFAULT_VIEW.yaw,
7281
7544
  pitch: clampPanoPitch(initialView?.pitch ?? DEFAULT_VIEW.pitch),
7282
- fov: Math.max(
7283
- normalizedMinFov,
7284
- Math.min(initialView?.fov ?? DEFAULT_VIEW.fov, normalizedMaxFov)
7285
- )
7545
+ // The runtime applies the effective scene/viewer range, including FOVs
7546
+ // outside the legacy minFov/maxFov defaults.
7547
+ fov: initialView?.fov ?? DEFAULT_VIEW.fov
7286
7548
  }),
7287
7549
  [
7288
7550
  initialView?.fov,
7289
7551
  initialView?.pitch,
7290
- initialView?.yaw,
7291
- normalizedMaxFov,
7292
- normalizedMinFov
7552
+ initialView?.yaw
7293
7553
  ]
7294
7554
  );
7295
7555
  fallbackViewRef.current = normalizedInitialView;
7296
- const controlClaims = useMemo7(() => createControlClaims(), []);
7556
+ const controlClaims = useMemo8(() => createControlClaims(), []);
7297
7557
  resetControlClaims(controlClaims);
7298
7558
  const controlsEnabled = controls !== false;
7299
- const controlOptions = useMemo7(
7559
+ const controlOptions = useMemo8(
7300
7560
  () => typeof controls === "object" ? controls : {},
7301
7561
  [controls]
7302
7562
  );
@@ -7304,7 +7564,7 @@ var PanoViewer = forwardRef2(
7304
7564
  const allModeInvert = controlOptions.invert === true;
7305
7565
  const topRotateSpeed = controlOptions.rotateSpeed ?? DEFAULT_POINTER_ROTATE_SPEED;
7306
7566
  const topZoomSpeed = controlOptions.zoomSpeed ?? DEFAULT_MOUSE_ZOOM_SPEED;
7307
- const mouseChannel = useMemo7(
7567
+ const mouseChannel = useMemo8(
7308
7568
  () => resolveChannel(controlOptions.mouse, {
7309
7569
  enabled: true,
7310
7570
  rotateSpeed: topRotateSpeed,
@@ -7320,7 +7580,7 @@ var PanoViewer = forwardRef2(
7320
7580
  topZoomSpeed
7321
7581
  ]
7322
7582
  );
7323
- const touchChannel = useMemo7(
7583
+ const touchChannel = useMemo8(
7324
7584
  () => resolveChannel(controlOptions.touch, {
7325
7585
  enabled: true,
7326
7586
  rotateSpeed: topRotateSpeed,
@@ -7329,7 +7589,7 @@ var PanoViewer = forwardRef2(
7329
7589
  }),
7330
7590
  [allModeInvert, controlOptions.touch, topRotateSpeed]
7331
7591
  );
7332
- const keyboardChannel = useMemo7(
7592
+ const keyboardChannel = useMemo8(
7333
7593
  () => resolveChannel(controlOptions.keyboard, {
7334
7594
  enabled: true
7335
7595
  }),
@@ -7371,7 +7631,11 @@ var PanoViewer = forwardRef2(
7371
7631
  }
7372
7632
  await requestElementFullscreen(root);
7373
7633
  }, []);
7374
- const [isViewerFullscreen, setIsViewerFullscreen] = useState5(false);
7634
+ const [isViewerFullscreen, setIsViewerFullscreen] = useState6(false);
7635
+ const { context: overlayContext, setPortalContainer } = usePanoOverlayHost(isViewerFullscreen);
7636
+ useEffect13(() => {
7637
+ if (modalOpen) contextMenuOverlayApi.setSession(null);
7638
+ }, [contextMenuOverlayApi, modalOpen]);
7375
7639
  useEffect13(() => {
7376
7640
  if (typeof document === "undefined") {
7377
7641
  return;
@@ -7403,7 +7667,7 @@ var PanoViewer = forwardRef2(
7403
7667
  root.removeEventListener("contextmenu", suppressNativeContextMenu, true);
7404
7668
  };
7405
7669
  }, []);
7406
- const contextMenuActions = useMemo7(
7670
+ const contextMenuActions = useMemo8(
7407
7671
  () => ({
7408
7672
  reset: () => {
7409
7673
  controlsRef.current?.reset();
@@ -7415,16 +7679,16 @@ var PanoViewer = forwardRef2(
7415
7679
  }),
7416
7680
  [isViewerFullscreen, toggleFullscreen]
7417
7681
  );
7418
- const contextMenuChannel = useMemo7(
7682
+ const contextMenuChannel = useMemo8(
7419
7683
  () => resolveContextMenuChannel(contextMenu),
7420
7684
  [contextMenu]
7421
7685
  );
7422
- const resolvedCursors = useMemo7(
7686
+ const resolvedCursors = useMemo8(
7423
7687
  () => resolvePanoCursors(cursors),
7424
7688
  [cursors]
7425
7689
  );
7426
7690
  const legacyAutoRotateOptions = controlOptions;
7427
- const [legacyAutoRotate, setLegacyAutoRotate] = useState5(
7691
+ const [legacyAutoRotate, setLegacyAutoRotate] = useState6(
7428
7692
  legacyAutoRotateOptions.autoRotate ?? false
7429
7693
  );
7430
7694
  useEffect13(() => {
@@ -7513,7 +7777,7 @@ var PanoViewer = forwardRef2(
7513
7777
  webVRHost
7514
7778
  ]
7515
7779
  );
7516
- const rootStyle = useMemo7(
7780
+ const rootStyle = useMemo8(
7517
7781
  () => ({
7518
7782
  overflow: "hidden",
7519
7783
  position: "relative",
@@ -7531,118 +7795,130 @@ var PanoViewer = forwardRef2(
7531
7795
  "aria-label": ariaLabel,
7532
7796
  style: rootStyle,
7533
7797
  children: [
7534
- /* @__PURE__ */ jsx11(
7535
- Canvas,
7536
- {
7537
- "aria-label": `${ariaLabel} canvas`,
7538
- resize: { debounce: 0 },
7539
- camera: {
7540
- far: DEFAULT_PANORAMA_CAMERA_FAR,
7541
- fov: normalizedInitialView.fov,
7542
- near: DEFAULT_PANORAMA_CAMERA_NEAR,
7543
- position: [0, 0, 0.01]
7544
- },
7545
- dpr,
7546
- gl: {
7547
- // Hotspots contain transparent textures and curved outlines. Keep
7548
- // the canvas multisampled so their visible edges are smoothed.
7549
- antialias: true,
7550
- powerPreference: "high-performance",
7551
- stencil: false
7552
- },
7553
- style: {
7554
- ...DEFAULT_CANVAS_STYLE,
7555
- touchAction: userControlsEnabled ? "none" : "auto"
7556
- },
7557
- children: /* @__PURE__ */ jsx11(XR, { store: xrStore, children: /* @__PURE__ */ jsxs6(
7558
- WebVRRuntimeContext.Provider,
7559
- {
7560
- value: { host: webVRHost, xrStore, stereoView },
7561
- children: [
7562
- /* @__PURE__ */ jsx11(PanoramaXROrigin, {}),
7563
- /* @__PURE__ */ jsx11(HotspotAccessibilityContext.Provider, { value: registry, children: /* @__PURE__ */ jsx11(HotspotTargetRegistryContext.Provider, { value: hotspotTargetRegistry, children: /* @__PURE__ */ jsx11(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ jsx11(PanoCursorTree, { cursors: resolvedCursors, children: /* @__PURE__ */ jsx11(PanoramaViewContext.Provider, { value: controlsRef, children: /* @__PURE__ */ jsx11(
7564
- PanoContextMenuOverlayContext.Provider,
7565
- {
7566
- value: contextMenuOverlayApi,
7567
- children: /* @__PURE__ */ jsx11(PanoChromeOverlayContext.Provider, { value: chromeOverlayApi, children: /* @__PURE__ */ jsx11(PanoVideoHostContext.Provider, { value: videoHost, children: /* @__PURE__ */ jsx11(PanoFilterHostContext.Provider, { value: filterHost, children: /* @__PURE__ */ jsx11(
7568
- PanoContextMenuActionsContext.Provider,
7569
- {
7570
- value: contextMenuActions,
7571
- children: /* @__PURE__ */ jsx11(ScenesHostContext.Provider, { value: scenesHost, children: /* @__PURE__ */ jsx11(BackgroundAudioHostContext.Provider, { value: bgmHost, children: /* @__PURE__ */ jsxs6(PanoMediaActivationHostContext.Provider, { value: mediaActivationHost, children: [
7572
- /* @__PURE__ */ jsx11(PanoVideoHostReset, {}),
7573
- /* @__PURE__ */ jsx11(
7574
- PanoramaViewRuntime,
7575
- {
7576
- ref: controlsRef,
7577
- eventBus,
7578
- initialView: normalizedInitialView,
7579
- maxFov: normalizedMaxFov,
7580
- minFov: normalizedMinFov,
7581
- options: controlOptions
7582
- }
7583
- ),
7584
- (onViewChange || onPanoramaClick || onPanoramaDoubleClick || onPanoramaPointerMove) && /* @__PURE__ */ jsx11(
7585
- PanoEvents,
7586
- {
7587
- onViewChange,
7588
- onClick: onPanoramaClick,
7589
- onDoubleClick: onPanoramaDoubleClick,
7590
- onPointerMove: onPanoramaPointerMove
7591
- }
7592
- ),
7593
- /* @__PURE__ */ jsx11(
7594
- AutoRotate,
7595
- {
7596
- enabled: legacyAutoRotate,
7597
- speed: legacyAutoRotateOptions.autoRotateSpeed
7598
- }
7599
- ),
7600
- /* @__PURE__ */ jsx11(PanoramaEventSurface, {}),
7601
- contextMenuChannel.mount ? /* @__PURE__ */ jsx11(PanoContextMenu, { ...contextMenuChannel.props }) : null,
7602
- /* @__PURE__ */ jsxs6(ControlClaimsContext.Provider, { value: controlClaims, children: [
7603
- children,
7604
- /* @__PURE__ */ jsx11(
7605
- DefaultControlChannels,
7798
+ /* @__PURE__ */ jsxs6("div", { "data-pano-background": "", inert: modalOpen, style: { position: "absolute", inset: 0 }, children: [
7799
+ /* @__PURE__ */ jsx12(
7800
+ Canvas,
7801
+ {
7802
+ "aria-label": `${ariaLabel} canvas`,
7803
+ resize: { debounce: 0 },
7804
+ camera: {
7805
+ far: DEFAULT_PANORAMA_CAMERA_FAR,
7806
+ fov: normalizedInitialView.fov,
7807
+ near: DEFAULT_PANORAMA_CAMERA_NEAR,
7808
+ position: [0, 0, 0.01]
7809
+ },
7810
+ dpr,
7811
+ gl: {
7812
+ // Hotspots contain transparent textures and curved outlines. Keep
7813
+ // the canvas multisampled so their visible edges are smoothed.
7814
+ antialias: true,
7815
+ powerPreference: "high-performance",
7816
+ stencil: false
7817
+ },
7818
+ style: {
7819
+ ...DEFAULT_CANVAS_STYLE,
7820
+ touchAction: userControlsEnabled ? "none" : "auto"
7821
+ },
7822
+ children: /* @__PURE__ */ jsx12(XR, { store: xrStore, children: /* @__PURE__ */ jsxs6(
7823
+ WebVRRuntimeContext.Provider,
7824
+ {
7825
+ value: { host: webVRHost, xrStore, stereoView },
7826
+ children: [
7827
+ /* @__PURE__ */ jsx12(PanoramaXROrigin, {}),
7828
+ /* @__PURE__ */ jsx12(HotspotAccessibilityContext.Provider, { value: registry, children: /* @__PURE__ */ jsx12(HotspotTargetRegistryContext.Provider, { value: hotspotTargetRegistry, children: /* @__PURE__ */ jsx12(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ jsx12(PanoCursorTree, { cursors: resolvedCursors, children: /* @__PURE__ */ jsx12(PanoramaViewContext.Provider, { value: controlsRef, children: /* @__PURE__ */ jsx12(
7829
+ PanoContextMenuOverlayContext.Provider,
7830
+ {
7831
+ value: contextMenuOverlayApi,
7832
+ children: /* @__PURE__ */ jsx12(PanoChromeOverlayContext.Provider, { value: chromeOverlayApi, children: /* @__PURE__ */ jsx12(PanoVideoHostContext.Provider, { value: videoHost, children: /* @__PURE__ */ jsx12(PanoFilterHostContext.Provider, { value: filterHost, children: /* @__PURE__ */ jsx12(
7833
+ PanoContextMenuActionsContext.Provider,
7834
+ {
7835
+ value: contextMenuActions,
7836
+ children: /* @__PURE__ */ jsx12(ScenesHostContext.Provider, { value: scenesHost, children: /* @__PURE__ */ jsx12(BackgroundAudioHostContext.Provider, { value: bgmHost, children: /* @__PURE__ */ jsxs6(PanoMediaActivationHostContext.Provider, { value: mediaActivationHost, children: [
7837
+ /* @__PURE__ */ jsx12(PanoVideoHostReset, {}),
7838
+ /* @__PURE__ */ jsx12(
7839
+ PanoramaViewRuntime,
7606
7840
  {
7607
- fovSpeed: controlOptions.fovSpeed,
7608
- keyboardChannel,
7609
- mouseChannel,
7610
- touchChannel,
7611
- userControlsEnabled
7841
+ ref: setControlsRuntime,
7842
+ eventBus,
7843
+ initialView: normalizedInitialView,
7844
+ viewLimits: parsedLimits,
7845
+ maxFov: normalizedMaxFov,
7846
+ minFov: normalizedMinFov,
7847
+ options: controlOptions
7612
7848
  }
7613
- )
7614
- ] })
7615
- ] }) }) })
7616
- }
7617
- ) }) }) })
7618
- }
7619
- ) }) }) }) }) })
7620
- ]
7621
- }
7622
- ) })
7623
- }
7624
- ),
7625
- /* @__PURE__ */ jsxs6(
7849
+ ),
7850
+ (onViewChange || onPanoramaClick || onPanoramaDoubleClick || onPanoramaPointerMove) && /* @__PURE__ */ jsx12(
7851
+ PanoEvents,
7852
+ {
7853
+ onViewChange,
7854
+ onClick: onPanoramaClick,
7855
+ onDoubleClick: onPanoramaDoubleClick,
7856
+ onPointerMove: onPanoramaPointerMove
7857
+ }
7858
+ ),
7859
+ /* @__PURE__ */ jsx12(
7860
+ AutoRotate,
7861
+ {
7862
+ enabled: legacyAutoRotate,
7863
+ speed: legacyAutoRotateOptions.autoRotateSpeed
7864
+ }
7865
+ ),
7866
+ /* @__PURE__ */ jsx12(PanoramaEventSurface, {}),
7867
+ contextMenuChannel.mount ? /* @__PURE__ */ jsx12(PanoContextMenu, { ...contextMenuChannel.props }) : null,
7868
+ /* @__PURE__ */ jsxs6(ControlClaimsContext.Provider, { value: controlClaims, children: [
7869
+ children,
7870
+ /* @__PURE__ */ jsx12(
7871
+ DefaultControlChannels,
7872
+ {
7873
+ fovSpeed: controlOptions.fovSpeed,
7874
+ keyboardChannel,
7875
+ mouseChannel,
7876
+ touchChannel,
7877
+ userControlsEnabled
7878
+ }
7879
+ )
7880
+ ] })
7881
+ ] }) }) })
7882
+ }
7883
+ ) }) }) })
7884
+ }
7885
+ ) }) }) }) }) })
7886
+ ]
7887
+ }
7888
+ ) })
7889
+ }
7890
+ ),
7891
+ /* @__PURE__ */ jsxs6(
7892
+ "div",
7893
+ {
7894
+ className: "pano-chrome-overlay",
7895
+ "data-pano-chrome-overlay": "",
7896
+ ref: setOverlayNode,
7897
+ children: [
7898
+ /* @__PURE__ */ jsx12(
7899
+ PanoVideoChromeBridge,
7900
+ {
7901
+ fullscreen: contextMenuActions,
7902
+ host: videoHost,
7903
+ overlayElement
7904
+ }
7905
+ ),
7906
+ /* @__PURE__ */ jsx12(WebVRChromeBridge, { host: webVRHost })
7907
+ ]
7908
+ }
7909
+ ),
7910
+ contextMenuOverlay,
7911
+ hotspotAccessibilityControls
7912
+ ] }),
7913
+ /* @__PURE__ */ jsx12(PanoModalHostContext.Provider, { value: modalHost, children: /* @__PURE__ */ jsx12(PanoOverlayContext.Provider, { value: overlayContext, children: /* @__PURE__ */ jsx12(
7626
7914
  "div",
7627
7915
  {
7628
- className: "pano-chrome-overlay",
7629
- "data-pano-chrome-overlay": "",
7630
- ref: setOverlayNode,
7631
- children: [
7632
- /* @__PURE__ */ jsx11(
7633
- PanoVideoChromeBridge,
7634
- {
7635
- fullscreen: contextMenuActions,
7636
- host: videoHost,
7637
- overlayElement
7638
- }
7639
- ),
7640
- /* @__PURE__ */ jsx11(WebVRChromeBridge, { host: webVRHost })
7641
- ]
7916
+ "data-pano-overlay": "",
7917
+ ref: setPortalContainer,
7918
+ style: { position: "absolute", inset: 0, zIndex: 30, pointerEvents: "none" },
7919
+ children: renderOverlay?.(overlayContext)
7642
7920
  }
7643
- ),
7644
- contextMenuOverlay,
7645
- hotspotAccessibilityControls
7921
+ ) }) })
7646
7922
  ]
7647
7923
  }
7648
7924
  );
@@ -7652,11 +7928,93 @@ var PanoViewer = forwardRef2(
7652
7928
  // src/index.ts
7653
7929
  export * from "./data.mjs";
7654
7930
 
7931
+ // src/asset-url.ts
7932
+ function isAbsoluteAssetUrl(url) {
7933
+ return url.startsWith("/") || url.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(url);
7934
+ }
7935
+ function resolveRelativeAssetUrl(baseUrl, relativePath) {
7936
+ const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
7937
+ const normalizedPath = relativePath.replace(/^\/+/, "");
7938
+ return normalizedBaseUrl ? `${normalizedBaseUrl}/${normalizedPath}` : normalizedPath;
7939
+ }
7940
+ function resolveAssetUrl(baseUrl, path) {
7941
+ return isAbsoluteAssetUrl(path) ? path : resolveRelativeAssetUrl(baseUrl, path);
7942
+ }
7943
+ function resolveUrlAgainstFile(fileUrl, path) {
7944
+ if (isAbsoluteAssetUrl(path)) {
7945
+ return path;
7946
+ }
7947
+ const slash = fileUrl.lastIndexOf("/");
7948
+ const directory = slash === -1 ? "" : fileUrl.slice(0, slash);
7949
+ return resolveRelativeAssetUrl(directory, path);
7950
+ }
7951
+
7952
+ // src/tile/multires.ts
7953
+ function positiveInteger(value, name) {
7954
+ if (!Number.isFinite(value) || value <= 0 || !Number.isInteger(value)) {
7955
+ throw new Error(`${name} must be a positive integer, received ${String(value)}`);
7956
+ }
7957
+ return value;
7958
+ }
7959
+ function ascendingLevels(levels) {
7960
+ for (let index = 1; index < levels.length; index += 1) {
7961
+ if (levels[index] <= levels[index - 1]) {
7962
+ throw new Error("multires face sizes must be strictly ascending");
7963
+ }
7964
+ }
7965
+ return levels;
7966
+ }
7967
+ function parseMultires(multires) {
7968
+ if (typeof multires !== "string") {
7969
+ const tileSize2 = positiveInteger(multires.tileSize, "multires.tileSize");
7970
+ if (multires.levels.length === 0) {
7971
+ throw new Error("multires.levels must contain at least one face size");
7972
+ }
7973
+ const levels2 = ascendingLevels(
7974
+ multires.levels.map(
7975
+ (level, index) => positiveInteger(level, `multires.levels[${index}]`)
7976
+ )
7977
+ );
7978
+ return { tileSize: tileSize2, levels: levels2 };
7979
+ }
7980
+ const values = multires.split(",").map((part) => Number(part.trim()));
7981
+ if (values.length < 2 || values.some((value) => !Number.isFinite(value))) {
7982
+ throw new Error(
7983
+ "multires must contain a tile size followed by at least one face size"
7984
+ );
7985
+ }
7986
+ const tileSize = positiveInteger(values[0], "multires tile size");
7987
+ const levels = ascendingLevels(
7988
+ values.slice(1).map(
7989
+ (level, index) => positiveInteger(level, `multires level ${index + 1}`)
7990
+ )
7991
+ );
7992
+ return { tileSize, levels };
7993
+ }
7994
+ var DEFAULT_TILE_URL_TEMPLATE = "tiles/%s/l%l/%v/l%l_%s_%h_%v.webp";
7995
+ function buildDefaultTileUrlTemplate() {
7996
+ return DEFAULT_TILE_URL_TEMPLATE;
7997
+ }
7998
+ function resolveRelativeTileUrl(baseUrl, relativePath) {
7999
+ return resolveAssetUrl(baseUrl, relativePath);
8000
+ }
8001
+ function resolveTemplateUrl(template, address) {
8002
+ return template.replace(/%s/g, address.face).replace(/%l/g, String(address.level)).replace(/%(0*)([hxucvywr])/g, (_match, zeroes, placeholder) => {
8003
+ const value = "hxuc".includes(placeholder) ? address.col : address.row;
8004
+ return String(value).padStart(zeroes.length + 1, "0");
8005
+ });
8006
+ }
8007
+
8008
+ // src/data/hotspots.ts
8009
+ function hotspotAction(hotspot, trigger) {
8010
+ return hotspot.events?.[trigger] ?? (trigger === "click" ? hotspot.action : void 0);
8011
+ }
8012
+
7655
8013
  // src/project-scenes.tsx
7656
8014
  import {
7657
- useContext as useContext21,
7658
- useMemo as useMemo21,
7659
- useState as useState19
8015
+ useContext as useContext22,
8016
+ useMemo as useMemo22,
8017
+ useState as useState20
7660
8018
  } from "react";
7661
8019
 
7662
8020
  // src/scenes.tsx
@@ -7664,11 +8022,11 @@ import { useFrame as useFrame11, useThree as useThree16 } from "@react-three/fib
7664
8022
  import {
7665
8023
  Fragment as Fragment4,
7666
8024
  useCallback as useCallback8,
7667
- useContext as useContext18,
8025
+ useContext as useContext19,
7668
8026
  useEffect as useEffect22,
7669
- useMemo as useMemo16,
8027
+ useMemo as useMemo17,
7670
8028
  useRef as useRef22,
7671
- useState as useState10
8029
+ useState as useState11
7672
8030
  } from "react";
7673
8031
  import {
7674
8032
  LinearSRGBColorSpace,
@@ -7677,10 +8035,10 @@ import {
7677
8035
  } from "three";
7678
8036
 
7679
8037
  // src/scene-detail.tsx
7680
- import { createContext as createContext15, useContext as useContext16 } from "react";
7681
- var SceneDetailContext = createContext15(null);
8038
+ import { createContext as createContext16, useContext as useContext17 } from "react";
8039
+ var SceneDetailContext = createContext16(null);
7682
8040
  function useSceneDetailReporter() {
7683
- return useContext16(SceneDetailContext);
8041
+ return useContext17(SceneDetailContext);
7684
8042
  }
7685
8043
  function viewTilesAreDrawable(textures) {
7686
8044
  return textures.length > 0 && textures.every((texture) => texture != null);
@@ -7745,7 +8103,7 @@ function isSnapshotTransition(preset) {
7745
8103
 
7746
8104
  // src/scene-transitions/snapshot-hold.tsx
7747
8105
  import { useFrame as useFrame6, useThree as useThree9 } from "@react-three/fiber";
7748
- import { useEffect as useEffect14, useMemo as useMemo8, useRef as useRef14 } from "react";
8106
+ import { useEffect as useEffect14, useMemo as useMemo9, useRef as useRef14 } from "react";
7749
8107
  import { Vector3 as Vector36 } from "three";
7750
8108
 
7751
8109
  // src/scene-transitions/overlay-utils.ts
@@ -8078,12 +8436,12 @@ function createSnapshotMaterial(preset, texture) {
8078
8436
  }
8079
8437
 
8080
8438
  // src/scene-transitions/snapshot-hold.tsx
8081
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
8439
+ import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
8082
8440
  function SnapshotHold({ snapshot }) {
8083
8441
  const { camera, gl } = useThree9();
8084
8442
  const meshRef = useRef14(null);
8085
8443
  const directionRef = useRef14(new Vector36());
8086
- const material = useMemo8(
8444
+ const material = useMemo9(
8087
8445
  () => createSnapshotMaterial("crossfade", snapshot.texture),
8088
8446
  [snapshot.texture]
8089
8447
  );
@@ -8102,8 +8460,8 @@ function SnapshotHold({ snapshot }) {
8102
8460
  material.uniforms.progress.value = 0;
8103
8461
  });
8104
8462
  return /* @__PURE__ */ jsxs7("mesh", { frustumCulled: false, ref: meshRef, renderOrder: OVERLAY_RENDER_ORDER, children: [
8105
- /* @__PURE__ */ jsx12("primitive", { attach: "geometry", object: FULLSCREEN_PLANE }),
8106
- /* @__PURE__ */ jsx12("primitive", { attach: "material", object: material })
8463
+ /* @__PURE__ */ jsx13("primitive", { attach: "geometry", object: FULLSCREEN_PLANE }),
8464
+ /* @__PURE__ */ jsx13("primitive", { attach: "material", object: material })
8107
8465
  ] });
8108
8466
  }
8109
8467
 
@@ -8165,7 +8523,7 @@ function phaseAfterSnapshot(preset, hold) {
8165
8523
 
8166
8524
  // src/scene-transitions/particle-overlay.tsx
8167
8525
  import { useFrame as useFrame7, useThree as useThree10 } from "@react-three/fiber";
8168
- import { useEffect as useEffect15, useMemo as useMemo9, useRef as useRef15 } from "react";
8526
+ import { useEffect as useEffect15, useMemo as useMemo10, useRef as useRef15 } from "react";
8169
8527
  import {
8170
8528
  DoubleSide as DoubleSide2,
8171
8529
  InstancedBufferAttribute,
@@ -8175,7 +8533,7 @@ import {
8175
8533
  Vector2 as Vector23,
8176
8534
  Vector3 as Vector37
8177
8535
  } from "three";
8178
- import { jsx as jsx13 } from "react/jsx-runtime";
8536
+ import { jsx as jsx14 } from "react/jsx-runtime";
8179
8537
  var PARTICLE_GRID = 48;
8180
8538
  var PARTICLE_COUNT = PARTICLE_GRID * PARTICLE_GRID;
8181
8539
  var PARTICLE_VERTEX_SHADER = `
@@ -8256,8 +8614,8 @@ function ParticleOverlay({
8256
8614
  const startRef = useRef15(null);
8257
8615
  const finishedRef = useRef15(false);
8258
8616
  const directionRef = useRef15(new Vector37());
8259
- const geometry = useMemo9(() => createParticleGeometry(), []);
8260
- const material = useMemo9(
8617
+ const geometry = useMemo10(() => createParticleGeometry(), []);
8618
+ const material = useMemo10(
8261
8619
  () => new ShaderMaterial2({
8262
8620
  transparent: true,
8263
8621
  depthTest: false,
@@ -8297,7 +8655,7 @@ function ParticleOverlay({
8297
8655
  onFinish();
8298
8656
  }
8299
8657
  });
8300
- return /* @__PURE__ */ jsx13(
8658
+ return /* @__PURE__ */ jsx14(
8301
8659
  "mesh",
8302
8660
  {
8303
8661
  ref: meshRef,
@@ -8311,9 +8669,9 @@ function ParticleOverlay({
8311
8669
 
8312
8670
  // src/scene-transitions/snapshot-overlay.tsx
8313
8671
  import { useFrame as useFrame8, useThree as useThree11 } from "@react-three/fiber";
8314
- import { useEffect as useEffect16, useMemo as useMemo10, useRef as useRef16 } from "react";
8672
+ import { useEffect as useEffect16, useMemo as useMemo11, useRef as useRef16 } from "react";
8315
8673
  import { Vector3 as Vector38 } from "three";
8316
- import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
8674
+ import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
8317
8675
  function SnapshotOverlay({
8318
8676
  snapshot,
8319
8677
  transition,
@@ -8326,7 +8684,7 @@ function SnapshotOverlay({
8326
8684
  const startRef = useRef16(null);
8327
8685
  const finishedRef = useRef16(false);
8328
8686
  const directionRef = useRef16(new Vector38());
8329
- const material = useMemo10(() => {
8687
+ const material = useMemo11(() => {
8330
8688
  if (!isSnapshotTransition(transition.preset)) {
8331
8689
  return null;
8332
8690
  }
@@ -8363,20 +8721,20 @@ function SnapshotOverlay({
8363
8721
  return null;
8364
8722
  }
8365
8723
  return /* @__PURE__ */ jsxs8("mesh", { ref: meshRef, renderOrder: OVERLAY_RENDER_ORDER, frustumCulled: false, children: [
8366
- /* @__PURE__ */ jsx14("primitive", { attach: "geometry", object: FULLSCREEN_PLANE }),
8367
- /* @__PURE__ */ jsx14("primitive", { attach: "material", object: material })
8724
+ /* @__PURE__ */ jsx15("primitive", { attach: "geometry", object: FULLSCREEN_PLANE }),
8725
+ /* @__PURE__ */ jsx15("primitive", { attach: "material", object: material })
8368
8726
  ] });
8369
8727
  }
8370
8728
 
8371
8729
  // src/scene-transitions/transition-overlay.tsx
8372
- import { jsx as jsx15 } from "react/jsx-runtime";
8730
+ import { jsx as jsx16 } from "react/jsx-runtime";
8373
8731
  function TransitionOverlay(props) {
8374
8732
  const { preset } = props.transition;
8375
8733
  switch (preset) {
8376
8734
  case "none":
8377
8735
  return null;
8378
8736
  case "particles":
8379
- return /* @__PURE__ */ jsx15(ParticleOverlay, { ...props });
8737
+ return /* @__PURE__ */ jsx16(ParticleOverlay, { ...props });
8380
8738
  case "crossfade":
8381
8739
  case "zoom":
8382
8740
  case "blackout":
@@ -8404,7 +8762,7 @@ function TransitionOverlay(props) {
8404
8762
  case "zoomBlur":
8405
8763
  case "hexDissolve":
8406
8764
  case "filmBurn":
8407
- return /* @__PURE__ */ jsx15(SnapshotOverlay, { ...props });
8765
+ return /* @__PURE__ */ jsx16(SnapshotOverlay, { ...props });
8408
8766
  default: {
8409
8767
  const exhaustive = preset;
8410
8768
  void exhaustive;
@@ -8415,7 +8773,7 @@ function TransitionOverlay(props) {
8415
8773
 
8416
8774
  // src/sphere.tsx
8417
8775
  import { useThree as useThree12 } from "@react-three/fiber";
8418
- import { useEffect as useEffect17, useMemo as useMemo12, useRef as useRef18, useState as useState6 } from "react";
8776
+ import { useEffect as useEffect17, useMemo as useMemo13, useRef as useRef18, useState as useState7 } from "react";
8419
8777
  import {
8420
8778
  BackSide as BackSide2,
8421
8779
  LinearFilter,
@@ -8426,29 +8784,8 @@ import {
8426
8784
  TextureLoader
8427
8785
  } from "three";
8428
8786
 
8429
- // src/asset-url.ts
8430
- function isAbsoluteAssetUrl(url) {
8431
- return url.startsWith("/") || url.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(url);
8432
- }
8433
- function resolveRelativeAssetUrl(baseUrl, relativePath) {
8434
- const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
8435
- const normalizedPath = relativePath.replace(/^\/+/, "");
8436
- return normalizedBaseUrl ? `${normalizedBaseUrl}/${normalizedPath}` : normalizedPath;
8437
- }
8438
- function resolveAssetUrl(baseUrl, path) {
8439
- return isAbsoluteAssetUrl(path) ? path : resolveRelativeAssetUrl(baseUrl, path);
8440
- }
8441
- function resolveUrlAgainstFile(fileUrl, path) {
8442
- if (isAbsoluteAssetUrl(path)) {
8443
- return path;
8444
- }
8445
- const slash = fileUrl.lastIndexOf("/");
8446
- const directory = slash === -1 ? "" : fileUrl.slice(0, slash);
8447
- return resolveRelativeAssetUrl(directory, path);
8448
- }
8449
-
8450
8787
  // src/pano-filter/pano-basic-material.tsx
8451
- import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo11, useRef as useRef17 } from "react";
8788
+ import { useLayoutEffect as useLayoutEffect4, useMemo as useMemo12, useRef as useRef17 } from "react";
8452
8789
  import {
8453
8790
  Matrix4,
8454
8791
  Vector4
@@ -8616,7 +8953,7 @@ function injectPanoColorMatrixShader(shader) {
8616
8953
  }
8617
8954
 
8618
8955
  // src/pano-filter/pano-basic-material.tsx
8619
- import { jsx as jsx16 } from "react/jsx-runtime";
8956
+ import { jsx as jsx17 } from "react/jsx-runtime";
8620
8957
  function matrixUniforms(matrix) {
8621
8958
  return {
8622
8959
  matrix: new Matrix4().set(
@@ -8651,7 +8988,7 @@ function PanoBasicMaterial({
8651
8988
  const currentRef = useRef17({ matrix, intensity: snapshot.intensity });
8652
8989
  currentRef.current = { matrix, intensity: snapshot.intensity };
8653
8990
  const shaderRef = useRef17(null);
8654
- const onBeforeCompile = useMemo11(() => {
8991
+ const onBeforeCompile = useMemo12(() => {
8655
8992
  const compile = (shader) => {
8656
8993
  shaderRef.current = shader;
8657
8994
  if (preset !== "none") {
@@ -8671,7 +9008,7 @@ function PanoBasicMaterial({
8671
9008
  compile.toString = () => `pano-filter:${preset}:matrix:${hasMatrix}`;
8672
9009
  return compile;
8673
9010
  }, [preset, hasMatrix]);
8674
- useLayoutEffect2(() => {
9011
+ useLayoutEffect4(() => {
8675
9012
  const shader = shaderRef.current;
8676
9013
  if (!shader) return;
8677
9014
  if (shader.uniforms.uPanoFilterIntensity)
@@ -8683,7 +9020,7 @@ function PanoBasicMaterial({
8683
9020
  shader.uniforms.uPanoColorMatrixIntensity.value = snapshot.intensity;
8684
9021
  }
8685
9022
  }, [matrix, snapshot.intensity]);
8686
- return /* @__PURE__ */ jsx16(
9023
+ return /* @__PURE__ */ jsx17(
8687
9024
  "meshBasicMaterial",
8688
9025
  {
8689
9026
  toneMapped: false,
@@ -8695,9 +9032,9 @@ function PanoBasicMaterial({
8695
9032
  }
8696
9033
 
8697
9034
  // src/sphere.tsx
8698
- import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
9035
+ import { jsx as jsx18, jsxs as jsxs9 } from "react/jsx-runtime";
8699
9036
  function useEquirectTexture(url, anisotropy, generateMipmaps, onLoad, onError) {
8700
- const [texture, setTexture] = useState6(null);
9037
+ const [texture, setTexture] = useState7(null);
8701
9038
  const onLoadRef = useRef18(onLoad);
8702
9039
  const onErrorRef = useRef18(onError);
8703
9040
  onLoadRef.current = onLoad;
@@ -8752,7 +9089,7 @@ function useEquirectTexture(url, anisotropy, generateMipmaps, onLoad, onError) {
8752
9089
  function Sphere({
8753
9090
  src,
8754
9091
  previewUrl,
8755
- yawOffset = 0,
9092
+ yawOffset: yawOffset2 = 0,
8756
9093
  onReady,
8757
9094
  onLoad,
8758
9095
  onError,
@@ -8761,7 +9098,7 @@ function Sphere({
8761
9098
  }) {
8762
9099
  const gl = useThree12((state) => state.gl);
8763
9100
  const anisotropy = gl.capabilities.getMaxAnisotropy();
8764
- const resolvedPreviewUrl = useMemo12(
9101
+ const resolvedPreviewUrl = useMemo13(
8765
9102
  () => resolveUrlAgainstFile(src, previewUrl),
8766
9103
  [previewUrl, src]
8767
9104
  );
@@ -8807,11 +9144,11 @@ function Sphere({
8807
9144
  return /* @__PURE__ */ jsxs9(
8808
9145
  "mesh",
8809
9146
  {
8810
- rotation: [0, MathUtils3.degToRad(90 + yawOffset), 0],
9147
+ rotation: [0, MathUtils3.degToRad(90 + yawOffset2), 0],
8811
9148
  visible,
8812
9149
  children: [
8813
- /* @__PURE__ */ jsx17("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
8814
- /* @__PURE__ */ jsx17(
9150
+ /* @__PURE__ */ jsx18("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
9151
+ /* @__PURE__ */ jsx18(
8815
9152
  PanoBasicMaterial,
8816
9153
  {
8817
9154
  map: displayTexture,
@@ -8826,7 +9163,7 @@ function Sphere({
8826
9163
 
8827
9164
  // src/sphere-tile/sphere-tile.tsx
8828
9165
  import { useFrame as useFrame9, useThree as useThree14 } from "@react-three/fiber";
8829
- import { memo, useCallback as useCallback7, useEffect as useEffect20, useMemo as useMemo14, useRef as useRef20, useState as useState8 } from "react";
9166
+ import { memo, useCallback as useCallback7, useEffect as useEffect20, useMemo as useMemo15, useRef as useRef20, useState as useState9 } from "react";
8830
9167
  import { BackSide as BackSide3, Frustum, Matrix4 as Matrix42, PerspectiveCamera as PerspectiveCamera3 } from "three";
8831
9168
 
8832
9169
  // src/tile/texture-manager.ts
@@ -9062,13 +9399,13 @@ var TileTextureManager = class {
9062
9399
  // src/tile/texture-manager-context.tsx
9063
9400
  import { useThree as useThree13 } from "@react-three/fiber";
9064
9401
  import {
9065
- createContext as createContext16,
9066
- useContext as useContext17,
9402
+ createContext as createContext17,
9403
+ useContext as useContext18,
9067
9404
  useEffect as useEffect18,
9068
- useMemo as useMemo13
9405
+ useMemo as useMemo14
9069
9406
  } from "react";
9070
- import { jsx as jsx18 } from "react/jsx-runtime";
9071
- var TileTextureManagerContext = createContext16(null);
9407
+ import { jsx as jsx19 } from "react/jsx-runtime";
9408
+ var TileTextureManagerContext = createContext17(null);
9072
9409
  function TileTextureManagerProvider({
9073
9410
  children,
9074
9411
  maxTextureMemoryMb = 128,
@@ -9076,7 +9413,7 @@ function TileTextureManagerProvider({
9076
9413
  }) {
9077
9414
  const gl = useThree13((state) => state.gl);
9078
9415
  const anisotropy = gl.capabilities.getMaxAnisotropy();
9079
- const manager = useMemo13(
9416
+ const manager = useMemo14(
9080
9417
  () => new TileTextureManager({
9081
9418
  anisotropy,
9082
9419
  maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
@@ -9089,14 +9426,14 @@ function TileTextureManagerProvider({
9089
9426
  manager.resume();
9090
9427
  return () => manager.dispose();
9091
9428
  }, [manager]);
9092
- return /* @__PURE__ */ jsx18(TileTextureManagerContext.Provider, { value: manager, children });
9429
+ return /* @__PURE__ */ jsx19(TileTextureManagerContext.Provider, { value: manager, children });
9093
9430
  }
9094
9431
  function useSharedTileTextureManager() {
9095
- return useContext17(TileTextureManagerContext);
9432
+ return useContext18(TileTextureManagerContext);
9096
9433
  }
9097
9434
 
9098
9435
  // src/tile/texture-hooks.ts
9099
- import { useEffect as useEffect19, useRef as useRef19, useState as useState7 } from "react";
9436
+ import { useEffect as useEffect19, useRef as useRef19, useState as useState8 } from "react";
9100
9437
  import {
9101
9438
  ClampToEdgeWrapping as ClampToEdgeWrapping2,
9102
9439
  LinearFilter as LinearFilter3,
@@ -9104,7 +9441,7 @@ import {
9104
9441
  TextureLoader as TextureLoader3
9105
9442
  } from "three";
9106
9443
  function usePreviewTexture(url, anisotropy, onError) {
9107
- const [texture, setTexture] = useState7(null);
9444
+ const [texture, setTexture] = useState8(null);
9108
9445
  const onErrorRef = useRef19(onError);
9109
9446
  onErrorRef.current = onError;
9110
9447
  useEffect19(() => {
@@ -9153,7 +9490,7 @@ function usePreviewTexture(url, anisotropy, onError) {
9153
9490
  return texture;
9154
9491
  }
9155
9492
  function useManagedTexture(manager, key, url, address) {
9156
- const [managedState, setManagedState] = useState7(() => ({
9493
+ const [managedState, setManagedState] = useState8(() => ({
9157
9494
  manager,
9158
9495
  key,
9159
9496
  url,
@@ -9182,7 +9519,7 @@ import {
9182
9519
  MathUtils as MathUtils4,
9183
9520
  Vector3 as Vector39
9184
9521
  } from "three";
9185
- function positiveInteger(value, name) {
9522
+ function positiveInteger2(value, name) {
9186
9523
  if (!Number.isSafeInteger(value) || value <= 0) {
9187
9524
  throw new Error(`${name} must be a positive safe integer`);
9188
9525
  }
@@ -9190,7 +9527,7 @@ function positiveInteger(value, name) {
9190
9527
  }
9191
9528
  function parseSphereMultires(input) {
9192
9529
  const parts = typeof input === "string" ? input.split(",").map((part) => part.trim()) : null;
9193
- const tileSize = positiveInteger(
9530
+ const tileSize = positiveInteger2(
9194
9531
  parts ? Number(parts[0]) : input.tileSize,
9195
9532
  "tileSize"
9196
9533
  );
@@ -9203,8 +9540,8 @@ function parseSphereMultires(input) {
9203
9540
  if (levels.length === 0)
9204
9541
  throw new Error("sphere multires needs at least one level");
9205
9542
  levels.forEach((level, index) => {
9206
- positiveInteger(level.width, "level width");
9207
- positiveInteger(level.height, "level height");
9543
+ positiveInteger2(level.width, "level width");
9544
+ positiveInteger2(level.height, "level height");
9208
9545
  if (Math.abs(level.width - level.height * 2) > 2)
9209
9546
  throw new Error("sphere levels must be full 2:1 equirectangular images");
9210
9547
  const previous = levels[index - 1];
@@ -9251,8 +9588,8 @@ function sphereTileRange(address) {
9251
9588
  bottom: (address.row * address.tileSize + address.height) / address.imageHeight
9252
9589
  };
9253
9590
  }
9254
- function spherePoint(u, v, yawOffset = 0, radius = DEFAULT_PANORAMA_RADIUS) {
9255
- const yaw = (u - 0.5) * Math.PI * 2 + MathUtils4.degToRad(yawOffset);
9591
+ function spherePoint(u, v, yawOffset2 = 0, radius = DEFAULT_PANORAMA_RADIUS) {
9592
+ const yaw = (u - 0.5) * Math.PI * 2 + MathUtils4.degToRad(yawOffset2);
9256
9593
  const pitch = (0.5 - v) * Math.PI;
9257
9594
  return new Vector39(
9258
9595
  Math.sin(yaw) * Math.cos(pitch) * radius,
@@ -9260,19 +9597,19 @@ function spherePoint(u, v, yawOffset = 0, radius = DEFAULT_PANORAMA_RADIUS) {
9260
9597
  -Math.cos(yaw) * Math.cos(pitch) * radius
9261
9598
  );
9262
9599
  }
9263
- function sphereTileBounds(address, yawOffset) {
9600
+ function sphereTileBounds(address, yawOffset2) {
9264
9601
  const { left, right, top, bottom } = sphereTileRange(address);
9265
9602
  const us = [left, right];
9266
9603
  for (let i = -4; i <= 8; i++) {
9267
- const u = 0.5 + (i * 90 - yawOffset % 360) / 360;
9604
+ const u = 0.5 + (i * 90 - yawOffset2 % 360) / 360;
9268
9605
  if (u > left && u < right) us.push(u);
9269
9606
  }
9270
9607
  const vs = top < 0.5 && bottom > 0.5 ? [top, 0.5, bottom] : [top, bottom];
9271
9608
  return new Box3().setFromPoints(
9272
- us.flatMap((u) => vs.map((v) => spherePoint(u, v, yawOffset)))
9609
+ us.flatMap((u) => vs.map((v) => spherePoint(u, v, yawOffset2)))
9273
9610
  );
9274
9611
  }
9275
- function sphereTileGeometry(address, yawOffset, radius = DEFAULT_PANORAMA_RADIUS) {
9612
+ function sphereTileGeometry(address, yawOffset2, radius = DEFAULT_PANORAMA_RADIUS) {
9276
9613
  const { left, right, top, bottom } = sphereTileRange(address);
9277
9614
  const columns = Math.max(2, Math.ceil((right - left) * 360 / 3));
9278
9615
  const rows = Math.max(2, Math.ceil((bottom - top) * 180 / 3));
@@ -9283,7 +9620,7 @@ function sphereTileGeometry(address, yawOffset, radius = DEFAULT_PANORAMA_RADIUS
9283
9620
  const point = spherePoint(
9284
9621
  left + (right - left) * u,
9285
9622
  top + (bottom - top) * v,
9286
- yawOffset,
9623
+ yawOffset2,
9287
9624
  radius
9288
9625
  );
9289
9626
  positions.push(point.x, point.y, point.z);
@@ -9308,7 +9645,7 @@ function preferredSphereLevel(levels, height, dpr, verticalFov) {
9308
9645
  }
9309
9646
 
9310
9647
  // src/sphere-tile/sphere-tile.tsx
9311
- import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
9648
+ import { jsx as jsx20, jsxs as jsxs10 } from "react/jsx-runtime";
9312
9649
  function sphereTileViewShouldRefresh(input) {
9313
9650
  if (input.loadModeChanged || input.managerChanged) {
9314
9651
  return true;
@@ -9321,24 +9658,24 @@ function sphereTileViewShouldRefresh(input) {
9321
9658
  function SpherePatch({
9322
9659
  address,
9323
9660
  texture,
9324
- yawOffset,
9661
+ yawOffset: yawOffset2,
9325
9662
  preview = false
9326
9663
  }) {
9327
- const geometry = useMemo14(
9664
+ const geometry = useMemo15(
9328
9665
  () => sphereTileGeometry(
9329
9666
  address,
9330
- yawOffset,
9667
+ yawOffset2,
9331
9668
  DEFAULT_PANORAMA_RADIUS + (preview ? 0.05 : 0)
9332
9669
  ),
9333
- [address, yawOffset, preview]
9670
+ [address, yawOffset2, preview]
9334
9671
  );
9335
9672
  useEffect20(() => () => geometry.dispose(), [geometry]);
9336
- return /* @__PURE__ */ jsx19(
9673
+ return /* @__PURE__ */ jsx20(
9337
9674
  "mesh",
9338
9675
  {
9339
9676
  geometry,
9340
9677
  renderOrder: preview ? -1e3 : -900 + address.level,
9341
- children: /* @__PURE__ */ jsx19(
9678
+ children: /* @__PURE__ */ jsx20(
9342
9679
  PanoBasicMaterial,
9343
9680
  {
9344
9681
  map: texture,
@@ -9355,7 +9692,7 @@ var ManagedPatch = memo(function ManagedPatch2({
9355
9692
  address,
9356
9693
  manager,
9357
9694
  resolveUrl,
9358
- yawOffset,
9695
+ yawOffset: yawOffset2,
9359
9696
  ready
9360
9697
  }) {
9361
9698
  const url = resolveUrl(address);
@@ -9368,12 +9705,12 @@ var ManagedPatch = memo(function ManagedPatch2({
9368
9705
  useEffect20(() => {
9369
9706
  if (snapshot.texture) ready();
9370
9707
  }, [snapshot.texture, ready]);
9371
- return snapshot.texture ? /* @__PURE__ */ jsx19(
9708
+ return snapshot.texture ? /* @__PURE__ */ jsx20(
9372
9709
  SpherePatch,
9373
9710
  {
9374
9711
  address,
9375
9712
  texture: snapshot.texture,
9376
- yawOffset
9713
+ yawOffset: yawOffset2
9377
9714
  }
9378
9715
  ) : null;
9379
9716
  });
@@ -9384,7 +9721,7 @@ function SphereTileSource({
9384
9721
  urlTemplate = "tiles/%l/%h_%v.webp",
9385
9722
  indexBase = 1,
9386
9723
  resolveTileUrl,
9387
- yawOffset = 0,
9724
+ yawOffset: yawOffset2 = 0,
9388
9725
  maxTextureMemoryMb = 128,
9389
9726
  maxConcurrentLoads = 4,
9390
9727
  retryCount = 1,
@@ -9415,7 +9752,7 @@ function SphereTileSource({
9415
9752
  1,
9416
9753
  Number.isFinite(maxTextureMemoryMb) ? maxTextureMemoryMb : 128
9417
9754
  ) * 1024 * 1024;
9418
- const ownedManager = useMemo14(
9755
+ const ownedManager = useMemo15(
9419
9756
  () => new TileTextureManager({
9420
9757
  anisotropy,
9421
9758
  maxBytes: ownedBudget,
@@ -9456,33 +9793,33 @@ function SphereTileSource({
9456
9793
  queueMicrotask(() => manager.releaseUnused());
9457
9794
  };
9458
9795
  }, [manager, ownedManager]);
9459
- const resolveUrl = useMemo14(
9796
+ const resolveUrl = useMemo15(
9460
9797
  () => (address) => resolveAssetUrl(
9461
9798
  baseUrl,
9462
9799
  resolveTileUrl ? resolveTileUrl(address) : resolveSphereTemplateUrl(urlTemplate, address, indexBase)
9463
9800
  ),
9464
9801
  [baseUrl, resolveTileUrl, urlTemplate, indexBase]
9465
9802
  );
9466
- const candidates = useMemo14(
9803
+ const candidates = useMemo15(
9467
9804
  () => config.levels.map(
9468
9805
  (level, index) => sphereTileAddresses(level, index, config.tileSize).map((address) => ({
9469
9806
  address,
9470
- bounds: sphereTileBounds(address, yawOffset)
9807
+ bounds: sphereTileBounds(address, yawOffset2)
9471
9808
  }))
9472
9809
  ),
9473
- [config, yawOffset]
9810
+ [config, yawOffset2]
9474
9811
  );
9475
- const previewAddress = useMemo14(
9812
+ const previewAddress = useMemo15(
9476
9813
  () => sphereTileAddresses({ width: 2, height: 1 }, 0, 2)[0],
9477
9814
  []
9478
9815
  );
9479
- const [layers, setLayers] = useState8([]);
9816
+ const [layers, setLayers] = useState9([]);
9480
9817
  const detailReporter = useSceneDetailReporter();
9481
9818
  const detailReporterRef = useRef20(detailReporter);
9482
9819
  detailReporterRef.current = detailReporter;
9483
9820
  const detailTokenRef = useRef20("");
9484
9821
  const targetKeysRef = useRef20([]);
9485
- const scratch = useMemo14(
9822
+ const scratch = useMemo15(
9486
9823
  () => ({
9487
9824
  camera: new PerspectiveCamera3(),
9488
9825
  frustum: new Frustum(),
@@ -9571,23 +9908,23 @@ function SphereTileSource({
9571
9908
  );
9572
9909
  });
9573
9910
  return /* @__PURE__ */ jsxs10("group", { visible, children: [
9574
- preview ? /* @__PURE__ */ jsx19(
9911
+ preview ? /* @__PURE__ */ jsx20(
9575
9912
  SpherePatch,
9576
9913
  {
9577
9914
  address: previewAddress,
9578
9915
  texture: preview,
9579
- yawOffset,
9916
+ yawOffset: yawOffset2,
9580
9917
  preview: true
9581
9918
  }
9582
9919
  ) : null,
9583
9920
  loadMode !== "preview" ? layers.flatMap(
9584
- (layer) => layer.addresses.map((address) => /* @__PURE__ */ jsx19(
9921
+ (layer) => layer.addresses.map((address) => /* @__PURE__ */ jsx20(
9585
9922
  ManagedPatch,
9586
9923
  {
9587
9924
  address,
9588
9925
  manager,
9589
9926
  resolveUrl,
9590
- yawOffset,
9927
+ yawOffset: yawOffset2,
9591
9928
  ready
9592
9929
  },
9593
9930
  sphereTileKey(address)
@@ -9597,14 +9934,14 @@ function SphereTileSource({
9597
9934
  }
9598
9935
  function SphereTile({ multires, ...props }) {
9599
9936
  const token = typeof multires === "string" ? multires : JSON.stringify(multires);
9600
- const config = useMemo14(() => parseSphereMultires(multires), [token]);
9937
+ const config = useMemo15(() => parseSphereMultires(multires), [token]);
9601
9938
  const sourceKey = `${props.baseUrl}\0${token}\0${props.previewUrl}\0${props.urlTemplate ?? ""}\0${props.indexBase ?? 1}`;
9602
- return /* @__PURE__ */ jsx19(SphereTileSource, { ...props, config }, sourceKey);
9939
+ return /* @__PURE__ */ jsx20(SphereTileSource, { ...props, config }, sourceKey);
9603
9940
  }
9604
9941
 
9605
9942
  // src/tile/tile.tsx
9606
9943
  import { useFrame as useFrame10, useThree as useThree15 } from "@react-three/fiber";
9607
- import { memo as memo2, useEffect as useEffect21, useMemo as useMemo15, useRef as useRef21, useState as useState9 } from "react";
9944
+ import { memo as memo2, useEffect as useEffect21, useMemo as useMemo16, useRef as useRef21, useState as useState10 } from "react";
9608
9945
  import {
9609
9946
  Frustum as Frustum2,
9610
9947
  Matrix4 as Matrix43,
@@ -9738,64 +10075,8 @@ function previewAtlasVRange(face, faceOrder = PREVIEW_FACE_ORDER) {
9738
10075
  };
9739
10076
  }
9740
10077
 
9741
- // src/tile/multires.ts
9742
- function positiveInteger2(value, name) {
9743
- if (!Number.isFinite(value) || value <= 0 || !Number.isInteger(value)) {
9744
- throw new Error(`${name} must be a positive integer, received ${String(value)}`);
9745
- }
9746
- return value;
9747
- }
9748
- function ascendingLevels(levels) {
9749
- for (let index = 1; index < levels.length; index += 1) {
9750
- if (levels[index] <= levels[index - 1]) {
9751
- throw new Error("multires face sizes must be strictly ascending");
9752
- }
9753
- }
9754
- return levels;
9755
- }
9756
- function parseMultires(multires) {
9757
- if (typeof multires !== "string") {
9758
- const tileSize2 = positiveInteger2(multires.tileSize, "multires.tileSize");
9759
- if (multires.levels.length === 0) {
9760
- throw new Error("multires.levels must contain at least one face size");
9761
- }
9762
- const levels2 = ascendingLevels(
9763
- multires.levels.map(
9764
- (level, index) => positiveInteger2(level, `multires.levels[${index}]`)
9765
- )
9766
- );
9767
- return { tileSize: tileSize2, levels: levels2 };
9768
- }
9769
- const values = multires.split(",").map((part) => Number(part.trim()));
9770
- if (values.length < 2 || values.some((value) => !Number.isFinite(value))) {
9771
- throw new Error(
9772
- "multires must contain a tile size followed by at least one face size"
9773
- );
9774
- }
9775
- const tileSize = positiveInteger2(values[0], "multires tile size");
9776
- const levels = ascendingLevels(
9777
- values.slice(1).map(
9778
- (level, index) => positiveInteger2(level, `multires level ${index + 1}`)
9779
- )
9780
- );
9781
- return { tileSize, levels };
9782
- }
9783
- var DEFAULT_TILE_URL_TEMPLATE = "tiles/%s/l%l/%v/l%l_%s_%h_%v.webp";
9784
- function buildDefaultTileUrlTemplate() {
9785
- return DEFAULT_TILE_URL_TEMPLATE;
9786
- }
9787
- function resolveRelativeTileUrl(baseUrl, relativePath) {
9788
- return resolveAssetUrl(baseUrl, relativePath);
9789
- }
9790
- function resolveTemplateUrl(template, address) {
9791
- return template.replace(/%s/g, address.face).replace(/%l/g, String(address.level)).replace(/%(0*)([hxucvywr])/g, (_match, zeroes, placeholder) => {
9792
- const value = "hxuc".includes(placeholder) ? address.col : address.row;
9793
- return String(value).padStart(zeroes.length + 1, "0");
9794
- });
9795
- }
9796
-
9797
10078
  // src/tile/tile.tsx
9798
- import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
10079
+ import { jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
9799
10080
  var MAX_LOD_UPDATE_INTERVAL_MS = 100;
9800
10081
  var FAST_ROTATION_THRESHOLD_RADIANS = Math.PI / 180;
9801
10082
  var FAST_FOV_THRESHOLD_DEGREES = 0.5;
@@ -9824,7 +10105,7 @@ function PreviewFace({
9824
10105
  texture,
9825
10106
  faceOrder
9826
10107
  }) {
9827
- const geometry = useMemo15(() => {
10108
+ const geometry = useMemo16(() => {
9828
10109
  const nextGeometry = new PlaneGeometry3(
9829
10110
  PREVIEW_RADIUS * 2,
9830
10111
  PREVIEW_RADIUS * 2
@@ -9846,14 +10127,14 @@ function PreviewFace({
9846
10127
  [geometry]
9847
10128
  );
9848
10129
  const transform = getFaceTransform(face, PREVIEW_RADIUS);
9849
- return /* @__PURE__ */ jsx20(
10130
+ return /* @__PURE__ */ jsx21(
9850
10131
  "mesh",
9851
10132
  {
9852
10133
  geometry,
9853
10134
  position: transform.position,
9854
10135
  renderOrder: -100,
9855
10136
  rotation: transform.rotation,
9856
- children: /* @__PURE__ */ jsx20(
10137
+ children: /* @__PURE__ */ jsx21(
9857
10138
  PanoBasicMaterial,
9858
10139
  {
9859
10140
  depthTest: false,
@@ -9869,7 +10150,7 @@ function PreviewCube({
9869
10150
  texture,
9870
10151
  faceOrder
9871
10152
  }) {
9872
- return CUBE_FACES.map((face) => /* @__PURE__ */ jsx20(
10153
+ return CUBE_FACES.map((face) => /* @__PURE__ */ jsx21(
9873
10154
  PreviewFace,
9874
10155
  {
9875
10156
  face,
@@ -9893,14 +10174,14 @@ var TileMesh = memo2(function TileMesh2({
9893
10174
  if (!snapshot.texture) {
9894
10175
  return null;
9895
10176
  }
9896
- return /* @__PURE__ */ jsx20("group", { position: transform.position, rotation: transform.rotation, children: /* @__PURE__ */ jsx20(
10177
+ return /* @__PURE__ */ jsx21("group", { position: transform.position, rotation: transform.rotation, children: /* @__PURE__ */ jsx21(
9897
10178
  "mesh",
9898
10179
  {
9899
10180
  geometry: SHARED_TILE_GEOMETRY,
9900
10181
  position: [layout.x, layout.y, 0],
9901
10182
  renderOrder,
9902
10183
  scale: [layout.width, layout.height, 1],
9903
- children: /* @__PURE__ */ jsx20(
10184
+ children: /* @__PURE__ */ jsx21(
9904
10185
  PanoBasicMaterial,
9905
10186
  {
9906
10187
  depthTest: false,
@@ -9917,7 +10198,7 @@ var TileLayerMeshes = memo2(function TileLayerMeshes2({
9917
10198
  manager,
9918
10199
  resolveUrl
9919
10200
  }) {
9920
- return layer.addresses.map((address) => /* @__PURE__ */ jsx20(
10201
+ return layer.addresses.map((address) => /* @__PURE__ */ jsx21(
9921
10202
  TileMesh,
9922
10203
  {
9923
10204
  address,
@@ -10084,10 +10365,10 @@ function Tile({
10084
10365
  onLevelChange
10085
10366
  }) {
10086
10367
  const { camera, gl, size } = useThree15();
10087
- const multiresConfig = useMemo15(() => parseMultires(multires), [multires]);
10368
+ const multiresConfig = useMemo16(() => parseMultires(multires), [multires]);
10088
10369
  const { levels, tileSize } = multiresConfig;
10089
10370
  const multiresToken = `${tileSize}:${levels.join(",")}`;
10090
- const candidatesByLevel = useMemo15(
10371
+ const candidatesByLevel = useMemo16(
10091
10372
  () => buildTileCandidates(levels, tileSize),
10092
10373
  [levels, tileSize]
10093
10374
  );
@@ -10105,7 +10386,7 @@ function Tile({
10105
10386
  progressRef.current = onLoadProgress;
10106
10387
  errorRef.current = onTileError;
10107
10388
  levelChangeRef.current = onLevelChange;
10108
- const ownedManager = useMemo15(
10389
+ const ownedManager = useMemo16(
10109
10390
  () => new TileTextureManager({
10110
10391
  anisotropy,
10111
10392
  maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
@@ -10148,14 +10429,14 @@ function Tile({
10148
10429
  onReady?.();
10149
10430
  }, [onReady, previewTexture]);
10150
10431
  const template = urlTemplate ?? buildDefaultTileUrlTemplate();
10151
- const resolveUrl = useMemo15(
10432
+ const resolveUrl = useMemo16(
10152
10433
  () => (address) => {
10153
10434
  const relativePath = resolveTileUrl ? resolveTileUrl(address) : resolveTemplateUrl(template, address);
10154
10435
  return resolveRelativeTileUrl(baseUrl, relativePath);
10155
10436
  },
10156
10437
  [baseUrl, resolveTileUrl, template]
10157
10438
  );
10158
- const [layers, setLayers] = useState9([]);
10439
+ const [layers, setLayers] = useState10([]);
10159
10440
  const layerTokenRef = useRef21("");
10160
10441
  const currentLevelRef = useRef21(1);
10161
10442
  const lastUpdateAtRef = useRef21(-Infinity);
@@ -10277,8 +10558,8 @@ function Tile({
10277
10558
  );
10278
10559
  });
10279
10560
  return /* @__PURE__ */ jsxs11("group", { visible, children: [
10280
- previewTexture ? /* @__PURE__ */ jsx20(PreviewCube, { texture: previewTexture, faceOrder: previewFaceOrder }) : null,
10281
- layers.map((layer) => /* @__PURE__ */ jsx20(
10561
+ previewTexture ? /* @__PURE__ */ jsx21(PreviewCube, { texture: previewTexture, faceOrder: previewFaceOrder }) : null,
10562
+ layers.map((layer) => /* @__PURE__ */ jsx21(
10282
10563
  TileLayerMeshes,
10283
10564
  {
10284
10565
  layer,
@@ -10291,7 +10572,7 @@ function Tile({
10291
10572
  }
10292
10573
 
10293
10574
  // src/scenes.tsx
10294
- import { jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
10575
+ import { jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
10295
10576
  function SceneSource({
10296
10577
  scene,
10297
10578
  visible,
@@ -10304,7 +10585,7 @@ function SceneSource({
10304
10585
  }
10305
10586
  switch (scene.type) {
10306
10587
  case "sphere":
10307
- return /* @__PURE__ */ jsx21(
10588
+ return /* @__PURE__ */ jsx22(
10308
10589
  Sphere,
10309
10590
  {
10310
10591
  src: scene.src,
@@ -10316,8 +10597,8 @@ function SceneSource({
10316
10597
  }
10317
10598
  );
10318
10599
  case "tile": {
10319
- const { id: _id, type: _type, ...tileProps } = scene;
10320
- return /* @__PURE__ */ jsx21(
10600
+ const { id: _id, type: _type, viewLimits: _limits, ...tileProps } = scene;
10601
+ return /* @__PURE__ */ jsx22(
10321
10602
  Tile,
10322
10603
  {
10323
10604
  ...tileProps,
@@ -10329,8 +10610,8 @@ function SceneSource({
10329
10610
  );
10330
10611
  }
10331
10612
  case "sphereTile": {
10332
- const { id: _id, type: _type, ...tileProps } = scene;
10333
- return /* @__PURE__ */ jsx21(
10613
+ const { id: _id, type: _type, viewLimits: _limits, ...tileProps } = scene;
10614
+ return /* @__PURE__ */ jsx22(
10334
10615
  SphereTile,
10335
10616
  {
10336
10617
  ...tileProps,
@@ -10414,29 +10695,29 @@ function ScenesController({
10414
10695
  transitionWaitForDetailMs,
10415
10696
  phaseRef: externalPhaseRef
10416
10697
  }) {
10417
- const controlsRef = useContext18(PanoramaViewContext);
10698
+ const controlsRef = useContext19(PanoramaViewContext);
10418
10699
  const manager = useSharedTileTextureManager();
10419
- const transition = useMemo16(
10700
+ const transition = useMemo17(
10420
10701
  () => resolveTransition(transitionInput),
10421
10702
  [transitionInput]
10422
10703
  );
10423
- const sceneMap = useMemo16(
10704
+ const sceneMap = useMemo17(
10424
10705
  () => new Map(scenes.map((scene) => [scene.id, scene])),
10425
10706
  [scenes]
10426
10707
  );
10427
10708
  const initialScene = sceneMap.get(activeSceneId) ?? null;
10428
- const [slots, setSlots] = useState10([
10709
+ const [slots, setSlots] = useState11([
10429
10710
  initialScene,
10430
10711
  null
10431
10712
  ]);
10432
- const [activeSlot, setActiveSlot] = useState10(0);
10433
- const [liveSlot, setLiveSlot] = useState10(0);
10434
- const [pendingSlot, setPendingSlot] = useState10(null);
10435
- const [phase, setPhase] = useState10(initialScene ? "idle" : "preloading");
10436
- const [captureRequestId, setCaptureRequestId] = useState10(0);
10437
- const [transitionRunId, setTransitionRunId] = useState10(0);
10438
- const [snapshot, setSnapshot] = useState10(null);
10439
- const [recoveryKey, setRecoveryKey] = useState10(0);
10713
+ const [activeSlot, setActiveSlot] = useState11(0);
10714
+ const [liveSlot, setLiveSlot] = useState11(0);
10715
+ const [pendingSlot, setPendingSlot] = useState11(null);
10716
+ const [phase, setPhase] = useState11(initialScene ? "idle" : "preloading");
10717
+ const [captureRequestId, setCaptureRequestId] = useState11(0);
10718
+ const [transitionRunId, setTransitionRunId] = useState11(0);
10719
+ const [snapshot, setSnapshot] = useState11(null);
10720
+ const [recoveryKey, setRecoveryKey] = useState11(0);
10440
10721
  const requestedSceneIdRef = useRef22(activeSceneId);
10441
10722
  const captureReasonRef = useRef22("start");
10442
10723
  const interactionReleaseRef = useRef22(null);
@@ -10448,7 +10729,7 @@ function ScenesController({
10448
10729
  const waitRef = useRef22(null);
10449
10730
  const waitTimerRef = useRef22(null);
10450
10731
  const detailGenerationRef = useRef22(0);
10451
- const [detailGeneration, setDetailGeneration] = useState10(0);
10732
+ const [detailGeneration, setDetailGeneration] = useState11(0);
10452
10733
  const mountedRef = useRef22(true);
10453
10734
  const waitForDetailMsRef = useRef22(0);
10454
10735
  waitForDetailMsRef.current = normalizeTransitionWaitMs(transitionWaitForDetailMs);
@@ -10458,6 +10739,13 @@ function ScenesController({
10458
10739
  externalPhaseRef.current = phase;
10459
10740
  }
10460
10741
  liveSlotRef.current = liveSlot;
10742
+ useEffect22(() => {
10743
+ const ready = readySceneRef.current;
10744
+ if (ready) controlsRef?.current?.setSceneViewLimits(sceneMap.get(ready.id)?.viewLimits);
10745
+ }, [controlsRef, sceneMap]);
10746
+ useEffect22(() => () => {
10747
+ controlsRef?.current?.setSceneViewLimits(void 0);
10748
+ }, [controlsRef]);
10461
10749
  const releaseInteraction = useCallback8(() => {
10462
10750
  interactionReleaseRef.current?.();
10463
10751
  interactionReleaseRef.current = null;
@@ -10534,8 +10822,9 @@ function ScenesController({
10534
10822
  const notifySceneReady = useCallback8((scene) => {
10535
10823
  if (!scene || readySceneRef.current === scene) return;
10536
10824
  readySceneRef.current = scene;
10825
+ controlsRef?.current?.setSceneViewLimits(sceneMap.get(scene.id)?.viewLimits);
10537
10826
  onSceneReady?.(scene.id);
10538
- }, [onSceneReady]);
10827
+ }, [controlsRef, onSceneReady, sceneMap]);
10539
10828
  const handleReady = useCallback8((slot) => {
10540
10829
  if (slot === liveSlotRef.current && pendingSlot === null && phaseRef.current === "idle") {
10541
10830
  notifySceneReady(slotsRef.current[slot]);
@@ -10755,7 +11044,7 @@ function ScenesController({
10755
11044
  const snapshotVisible = snapshot !== null && (phase === "transitioning" || phase === "recapturing" || phase === "preloading");
10756
11045
  const slotSceneKey = `${slots[0]?.id ?? ""}
10757
11046
  ${slots[1]?.id ?? ""}`;
10758
- const detailReporters = useMemo16(() => {
11047
+ const detailReporters = useMemo17(() => {
10759
11048
  const [firstSceneId = "", secondSceneId = ""] = slotSceneKey.split("\n");
10760
11049
  const sceneIds = [firstSceneId, secondSceneId];
10761
11050
  return sceneIds.map((sceneId) => ({
@@ -10773,7 +11062,7 @@ ${slots[1]?.id ?? ""}`;
10773
11062
  }));
10774
11063
  }, [consumeDetailSignal, detailGeneration, slotSceneKey]);
10775
11064
  return /* @__PURE__ */ jsxs12(Fragment4, { children: [
10776
- /* @__PURE__ */ jsx21(
11065
+ /* @__PURE__ */ jsx22(
10777
11066
  SnapshotCapture,
10778
11067
  {
10779
11068
  requestId: captureRequestId,
@@ -10785,11 +11074,11 @@ ${slots[1]?.id ?? ""}`;
10785
11074
  const source = slots[slot];
10786
11075
  const isLive = slot === liveSlot;
10787
11076
  const isPending = slot === pendingSlot;
10788
- return /* @__PURE__ */ jsx21(
11077
+ return /* @__PURE__ */ jsx22(
10789
11078
  SceneDetailContext.Provider,
10790
11079
  {
10791
11080
  value: detailReporters[slot],
10792
- children: /* @__PURE__ */ jsx21(
11081
+ children: /* @__PURE__ */ jsx22(
10793
11082
  SceneSource,
10794
11083
  {
10795
11084
  scene: source,
@@ -10807,8 +11096,8 @@ ${slots[1]?.id ?? ""}`;
10807
11096
  `${slot}:${source?.id ?? "empty"}:${recoveryKey}`
10808
11097
  );
10809
11098
  }),
10810
- snapshot !== null && phase === "awaitingDetail" ? /* @__PURE__ */ jsx21(SnapshotHold, { snapshot }) : null,
10811
- snapshotVisible ? /* @__PURE__ */ jsx21(
11099
+ snapshot !== null && phase === "awaitingDetail" ? /* @__PURE__ */ jsx22(SnapshotHold, { snapshot }) : null,
11100
+ snapshotVisible ? /* @__PURE__ */ jsx22(
10812
11101
  TransitionOverlay,
10813
11102
  {
10814
11103
  snapshot,
@@ -10831,7 +11120,7 @@ function Scenes({
10831
11120
  ...rest
10832
11121
  }) {
10833
11122
  const isControlled = controlledId !== void 0;
10834
- const [internalId, setInternalId] = useState10(
11123
+ const [internalId, setInternalId] = useState11(
10835
11124
  () => controlledId ?? defaultActiveSceneId ?? scenes[0]?.id ?? ""
10836
11125
  );
10837
11126
  const resolvedId = isControlled ? controlledId : internalId;
@@ -10849,7 +11138,7 @@ function Scenes({
10849
11138
  scenesRef.current = scenes;
10850
11139
  const resolvedIdRef = useRef22(resolvedId);
10851
11140
  resolvedIdRef.current = resolvedId;
10852
- const host = useContext18(ScenesHostContext);
11141
+ const host = useContext19(ScenesHostContext);
10853
11142
  const phaseRef = useRef22("idle");
10854
11143
  useEffect22(() => {
10855
11144
  if (!host) {
@@ -10901,12 +11190,12 @@ function Scenes({
10901
11190
  }
10902
11191
  };
10903
11192
  }, [host, requestSceneChange]);
10904
- return /* @__PURE__ */ jsx21(
11193
+ return /* @__PURE__ */ jsx22(
10905
11194
  TileTextureManagerProvider,
10906
11195
  {
10907
11196
  maxTextureMemoryMb,
10908
11197
  maxConcurrentLoads: maxConcurrentTileLoads,
10909
- children: /* @__PURE__ */ jsx21(
11198
+ children: /* @__PURE__ */ jsx22(
10910
11199
  ScenesController,
10911
11200
  {
10912
11201
  ...rest,
@@ -10920,8 +11209,8 @@ function Scenes({
10920
11209
  }
10921
11210
 
10922
11211
  // src/hotspot/image-hotspot.tsx
10923
- import { useThree as useThree19 } from "@react-three/fiber";
10924
- import { useEffect as useEffect25, useRef as useRef25, useState as useState13 } from "react";
11212
+ import { useThree as useThree20 } from "@react-three/fiber";
11213
+ import { useEffect as useEffect25, useRef as useRef26, useState as useState14 } from "react";
10925
11214
  import {
10926
11215
  ClampToEdgeWrapping as ClampToEdgeWrapping3,
10927
11216
  LinearFilter as LinearFilter5,
@@ -10931,43 +11220,43 @@ import {
10931
11220
  } from "three";
10932
11221
 
10933
11222
  // src/hotspot/hotspot-anchor.tsx
10934
- import { useFrame as useFrame13, useThree as useThree18 } from "@react-three/fiber";
11223
+ import { useFrame as useFrame14, useThree as useThree19 } from "@react-three/fiber";
10935
11224
  import {
10936
- useContext as useContext20,
11225
+ useContext as useContext21,
10937
11226
  useEffect as useEffect24,
10938
- useLayoutEffect as useLayoutEffect4,
10939
- useMemo as useMemo18,
10940
- useRef as useRef24,
10941
- useState as useState12
11227
+ useLayoutEffect as useLayoutEffect7,
11228
+ useMemo as useMemo19,
11229
+ useRef as useRef25,
11230
+ useState as useState13
10942
11231
  } from "react";
10943
11232
  import {
10944
11233
  Box3 as Box34,
10945
11234
  EdgesGeometry,
10946
- MathUtils as MathUtils6,
11235
+ MathUtils as MathUtils7,
10947
11236
  Matrix4 as Matrix44,
10948
11237
  Plane,
10949
11238
  PlaneGeometry as PlaneGeometry4,
10950
11239
  Quaternion as Quaternion3,
10951
11240
  Ray,
10952
- Vector3 as Vector314
11241
+ Vector3 as Vector315
10953
11242
  } from "three";
10954
11243
 
10955
11244
  // src/hotspot/hotspot-tooltip.tsx
10956
- import { useEffect as useEffect23, useMemo as useMemo17, useState as useState11 } from "react";
11245
+ import { useEffect as useEffect23, useMemo as useMemo18, useState as useState12 } from "react";
10957
11246
 
10958
11247
  // src/webvr/stereo-html.tsx
10959
11248
  import { useFrame as useFrame12, useThree as useThree17 } from "@react-three/fiber";
10960
11249
  import {
10961
- useContext as useContext19,
10962
- useLayoutEffect as useLayoutEffect3,
11250
+ useContext as useContext20,
11251
+ useLayoutEffect as useLayoutEffect5,
10963
11252
  useRef as useRef23,
10964
11253
  useSyncExternalStore as useSyncExternalStore7
10965
11254
  } from "react";
10966
11255
  import { createRoot } from "react-dom/client";
10967
11256
  import { Vector3 as Vector312 } from "three";
10968
- import { Fragment as Fragment5, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
11257
+ import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
10969
11258
  function useFallbackStereoView() {
10970
- const runtime = useContext19(WebVRRuntimeContext);
11259
+ const runtime = useContext20(WebVRRuntimeContext);
10971
11260
  const host = runtime?.host ?? null;
10972
11261
  const fallback = useSyncExternalStore7(
10973
11262
  (onStoreChange) => host ? subscribeWebVRHost(host, onStoreChange) : () => void 0,
@@ -11015,7 +11304,7 @@ function ProjectedHtml({
11015
11304
  )
11016
11305
  );
11017
11306
  };
11018
- useLayoutEffect3(() => {
11307
+ useLayoutEffect5(() => {
11019
11308
  if (!target) return;
11020
11309
  const element = document.createElement("div");
11021
11310
  Object.assign(element.style, {
@@ -11033,9 +11322,9 @@ function ProjectedHtml({
11033
11322
  queueMicrotask(() => root.unmount());
11034
11323
  };
11035
11324
  }, [target]);
11036
- useLayoutEffect3(() => {
11325
+ useLayoutEffect5(() => {
11037
11326
  overlay.current?.root.render(
11038
- /* @__PURE__ */ jsx22(
11327
+ /* @__PURE__ */ jsx23(
11039
11328
  "div",
11040
11329
  {
11041
11330
  "aria-hidden": eye === "right" || void 0,
@@ -11047,18 +11336,18 @@ function ProjectedHtml({
11047
11336
  updatePosition();
11048
11337
  });
11049
11338
  useFrame12(updatePosition);
11050
- return /* @__PURE__ */ jsx22("group", { ref: group });
11339
+ return /* @__PURE__ */ jsx23("group", { ref: group });
11051
11340
  }
11052
11341
  function PanoHtml(props) {
11053
11342
  const stereoView = useFallbackStereoView();
11054
11343
  return stereoView ? /* @__PURE__ */ jsxs13(Fragment5, { children: [
11055
- /* @__PURE__ */ jsx22(ProjectedHtml, { ...props, stereoView, eye: "left" }),
11056
- /* @__PURE__ */ jsx22(ProjectedHtml, { ...props, stereoView, eye: "right" })
11057
- ] }) : /* @__PURE__ */ jsx22(ProjectedHtml, { ...props });
11344
+ /* @__PURE__ */ jsx23(ProjectedHtml, { ...props, stereoView, eye: "left" }),
11345
+ /* @__PURE__ */ jsx23(ProjectedHtml, { ...props, stereoView, eye: "right" })
11346
+ ] }) : /* @__PURE__ */ jsx23(ProjectedHtml, { ...props });
11058
11347
  }
11059
11348
 
11060
11349
  // src/hotspot/hotspot-tooltip.tsx
11061
- import { jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
11350
+ import { jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
11062
11351
  var DEFAULT_HOTSPOT_TOOLTIP_OFFSET = 12;
11063
11352
  var DEFAULT_HOTSPOT_TOOLTIP_APPEARANCE = {
11064
11353
  background: "rgba(22, 22, 22, 0.72)",
@@ -11130,16 +11419,16 @@ function HotspotTooltip({
11130
11419
  offset = DEFAULT_HOTSPOT_TOOLTIP_OFFSET,
11131
11420
  appearance
11132
11421
  }) {
11133
- const [imageFailed, setImageFailed] = useState11(false);
11422
+ const [imageFailed, setImageFailed] = useState12(false);
11134
11423
  useEffect23(() => {
11135
11424
  setImageFailed(false);
11136
11425
  }, [content.image]);
11137
11426
  const showImage = Boolean(content.image) && !imageFailed;
11138
- const resolvedAppearance = useMemo17(
11427
+ const resolvedAppearance = useMemo18(
11139
11428
  () => resolveHotspotTooltipAppearance(appearance),
11140
11429
  [appearance]
11141
11430
  );
11142
- const bubbleStyle = useMemo17(
11431
+ const bubbleStyle = useMemo18(
11143
11432
  () => ({
11144
11433
  background: resolvedAppearance.background,
11145
11434
  border: resolvedAppearance.border,
@@ -11161,8 +11450,8 @@ function HotspotTooltip({
11161
11450
  if (!showImage && !content.text) {
11162
11451
  return null;
11163
11452
  }
11164
- return /* @__PURE__ */ jsx23(PanoHtml, { pointerEvents: "none", style: WRAPPER_STYLE, zIndexRange: [20, 10], children: /* @__PURE__ */ jsxs14("div", { className: "pano-hotspot-tooltip", role: "tooltip", style: bubbleStyle, children: [
11165
- showImage ? /* @__PURE__ */ jsx23(
11453
+ return /* @__PURE__ */ jsx24(PanoHtml, { pointerEvents: "none", style: WRAPPER_STYLE, zIndexRange: [20, 10], children: /* @__PURE__ */ jsxs14("div", { className: "pano-hotspot-tooltip", role: "tooltip", style: bubbleStyle, children: [
11454
+ showImage ? /* @__PURE__ */ jsx24(
11166
11455
  "img",
11167
11456
  {
11168
11457
  alt: content.imageAlt ?? content.text ?? "",
@@ -11171,7 +11460,7 @@ function HotspotTooltip({
11171
11460
  src: content.image
11172
11461
  }
11173
11462
  ) : null,
11174
- content.text ? /* @__PURE__ */ jsx23(
11463
+ content.text ? /* @__PURE__ */ jsx24(
11175
11464
  "p",
11176
11465
  {
11177
11466
  className: "pano-hotspot-tooltip-text",
@@ -11221,8 +11510,84 @@ function fixedScaleFactor(cameraFov, referenceFov) {
11221
11510
  return Math.tan(MathUtils5.degToRad(cameraFov) / 2) / Math.tan(MathUtils5.degToRad(safeReferenceFov) / 2);
11222
11511
  }
11223
11512
 
11513
+ // src/hotspot/hotspot-stroke-line.tsx
11514
+ import { Line } from "@react-three/drei";
11515
+ import { useFrame as useFrame13, useThree as useThree18 } from "@react-three/fiber";
11516
+ import { useLayoutEffect as useLayoutEffect6, useRef as useRef24 } from "react";
11517
+ import {
11518
+ MathUtils as MathUtils6,
11519
+ PerspectiveCamera as PerspectiveCamera6,
11520
+ Vector3 as Vector314
11521
+ } from "three";
11522
+ import { jsx as jsx25 } from "react/jsx-runtime";
11523
+ var LINE_WORLD_POSITION = new Vector314();
11524
+ function applyScreenSpaceDashScale(object, camera, viewportHeight) {
11525
+ const material = object?.material;
11526
+ if (!object || typeof material?.dashScale !== "number") {
11527
+ return;
11528
+ }
11529
+ object.getWorldPosition(LINE_WORLD_POSITION);
11530
+ const depth = Math.max(camera.position.distanceTo(LINE_WORLD_POSITION), 1);
11531
+ const fov = camera instanceof PerspectiveCamera6 ? camera.fov : 75;
11532
+ material.dashScale = viewportHeight / (2 * Math.tan(MathUtils6.degToRad(fov) * 0.5) * depth);
11533
+ }
11534
+ function resolveDash(dashSize, gapSize) {
11535
+ if (dashSize === void 0 || !Number.isFinite(dashSize) || dashSize <= 0) {
11536
+ return { dashed: false, dashSize: 1, gapSize: 1 };
11537
+ }
11538
+ return {
11539
+ dashed: true,
11540
+ dashSize,
11541
+ gapSize: Number.isFinite(gapSize) ? Math.max(0, gapSize) : dashSize
11542
+ };
11543
+ }
11544
+ function HotspotStrokeLine({
11545
+ points,
11546
+ color,
11547
+ lineWidth,
11548
+ opacity,
11549
+ renderOrder,
11550
+ dashSize,
11551
+ gapSize
11552
+ }) {
11553
+ const lineRef = useRef24(null);
11554
+ const { camera, size } = useThree18();
11555
+ const dash = resolveDash(dashSize, gapSize);
11556
+ useLayoutEffect6(() => {
11557
+ if (!dash.dashed) {
11558
+ return;
11559
+ }
11560
+ applyScreenSpaceDashScale(lineRef.current, camera, size.height);
11561
+ }, [camera, dash.dashed, dash.dashSize, dash.gapSize, size.height]);
11562
+ useFrame13((state) => {
11563
+ if (!dash.dashed) {
11564
+ return;
11565
+ }
11566
+ applyScreenSpaceDashScale(lineRef.current, state.camera, state.size.height);
11567
+ });
11568
+ return /* @__PURE__ */ jsx25(
11569
+ Line,
11570
+ {
11571
+ ref: lineRef,
11572
+ alphaToCoverage: true,
11573
+ color,
11574
+ dashed: dash.dashed,
11575
+ dashSize: dash.dashSize,
11576
+ depthTest: true,
11577
+ depthWrite: false,
11578
+ gapSize: dash.gapSize,
11579
+ lineWidth: Math.max(0.5, lineWidth),
11580
+ opacity,
11581
+ points,
11582
+ renderOrder,
11583
+ transparent: true,
11584
+ worldUnits: false
11585
+ }
11586
+ );
11587
+ }
11588
+
11224
11589
  // src/hotspot/hotspot-anchor.tsx
11225
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
11590
+ import { jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
11226
11591
  var DEFAULT_FLOATING_DISTANCE = 10;
11227
11592
  var PANORAMA_RADIUS = DEFAULT_PANORAMA_RADIUS;
11228
11593
  var PANORAMA_SURFACE_INSET = 4;
@@ -11230,27 +11595,34 @@ var DEFAULT_REFERENCE_FOV = 75;
11230
11595
  var MIN_ANGULAR_SIZE = 0.01;
11231
11596
  var MAX_ANGULAR_SIZE = 179;
11232
11597
  var DRAG_EPSILON_DEGREES = 1e-3;
11598
+ var SELECTION_BORDER_POINTS = [
11599
+ [-0.5, -0.5, 1e-3],
11600
+ [0.5, -0.5, 1e-3],
11601
+ [0.5, 0.5, 1e-3],
11602
+ [-0.5, 0.5, 1e-3],
11603
+ [-0.5, -0.5, 1e-3]
11604
+ ];
11233
11605
  var TOOLTIP_WORLD_BOX = new Box34();
11234
11606
  var TOOLTIP_LOCAL_BOX = new Box34();
11235
- var TOOLTIP_CORNER = new Vector314();
11607
+ var TOOLTIP_CORNER = new Vector315();
11236
11608
  var TOOLTIP_INVERSE = new Matrix44();
11237
- var TOOLTIP_WORLD_CORNER = new Vector314();
11238
- var TOOLTIP_PROJECTED = new Vector314();
11239
- var TOOLTIP_RAY_ORIGIN = new Vector314();
11240
- var TOOLTIP_RAY_FAR = new Vector314();
11241
- var TOOLTIP_RAY_DIRECTION = new Vector314();
11609
+ var TOOLTIP_WORLD_CORNER = new Vector315();
11610
+ var TOOLTIP_PROJECTED = new Vector315();
11611
+ var TOOLTIP_RAY_ORIGIN = new Vector315();
11612
+ var TOOLTIP_RAY_FAR = new Vector315();
11613
+ var TOOLTIP_RAY_DIRECTION = new Vector315();
11242
11614
  var TOOLTIP_RAY = new Ray();
11243
11615
  var TOOLTIP_PLANE = new Plane();
11244
- var TOOLTIP_INTERSECTION = new Vector314();
11245
- var TOOLTIP_PLANE_NORMAL = new Vector314();
11246
- var TOOLTIP_PLANE_POINT = new Vector314();
11616
+ var TOOLTIP_INTERSECTION = new Vector315();
11617
+ var TOOLTIP_PLANE_NORMAL = new Vector315();
11618
+ var TOOLTIP_PLANE_POINT = new Vector315();
11247
11619
  var HOTSPOT_MODE_RENDERING = {
11248
11620
  surface: { orientation: "surface", placement: "surface" },
11249
11621
  billboard: { orientation: "billboard", placement: "floating" }
11250
11622
  };
11251
11623
  function angularSizeToWorldSize(size, radius) {
11252
- const safeSize = Number.isFinite(size) ? MathUtils6.clamp(Math.abs(size), MIN_ANGULAR_SIZE, MAX_ANGULAR_SIZE) : MIN_ANGULAR_SIZE;
11253
- return 2 * radius * Math.tan(MathUtils6.degToRad(safeSize) / 2);
11624
+ const safeSize = Number.isFinite(size) ? MathUtils7.clamp(Math.abs(size), MIN_ANGULAR_SIZE, MAX_ANGULAR_SIZE) : MIN_ANGULAR_SIZE;
11625
+ return 2 * radius * Math.tan(MathUtils7.degToRad(safeSize) / 2);
11254
11626
  }
11255
11627
  function resolveScale(scale) {
11256
11628
  return Number.isFinite(scale) && scale > 0 ? scale : 1;
@@ -11263,32 +11635,32 @@ function resolveDistance(placement, distance, width, height) {
11263
11635
  if (!Number.isFinite(distance)) {
11264
11636
  return DEFAULT_FLOATING_DISTANCE;
11265
11637
  }
11266
- return MathUtils6.clamp(Math.abs(distance), 0.1, maximumDistance);
11638
+ return MathUtils7.clamp(Math.abs(distance), 0.1, maximumDistance);
11267
11639
  }
11268
11640
  function maximumHotspotDistance(width, height) {
11269
- const safeWidth = MathUtils6.clamp(
11641
+ const safeWidth = MathUtils7.clamp(
11270
11642
  Math.abs(Number.isFinite(width) ? width : MIN_ANGULAR_SIZE),
11271
11643
  MIN_ANGULAR_SIZE,
11272
11644
  MAX_ANGULAR_SIZE
11273
11645
  );
11274
- const safeHeight = MathUtils6.clamp(
11646
+ const safeHeight = MathUtils7.clamp(
11275
11647
  Math.abs(Number.isFinite(height) ? height : MIN_ANGULAR_SIZE),
11276
11648
  MIN_ANGULAR_SIZE,
11277
11649
  MAX_ANGULAR_SIZE
11278
11650
  );
11279
11651
  const cornerFactor = Math.hypot(
11280
11652
  1,
11281
- Math.tan(MathUtils6.degToRad(safeWidth) / 2),
11282
- Math.tan(MathUtils6.degToRad(safeHeight) / 2)
11653
+ Math.tan(MathUtils7.degToRad(safeWidth) / 2),
11654
+ Math.tan(MathUtils7.degToRad(safeHeight) / 2)
11283
11655
  );
11284
11656
  return (PANORAMA_RADIUS - PANORAMA_SURFACE_INSET) / cornerFactor;
11285
11657
  }
11286
11658
  function createSurfaceQuaternion(position) {
11287
11659
  const outward = panoPositionToVector3(position).normalize();
11288
11660
  const inward = outward.clone().negate();
11289
- const pitch = MathUtils6.degToRad(position.pitch);
11290
- const yaw = MathUtils6.degToRad(position.yaw);
11291
- const localUp = new Vector314(
11661
+ const pitch = MathUtils7.degToRad(position.pitch);
11662
+ const yaw = MathUtils7.degToRad(position.yaw);
11663
+ const localUp = new Vector315(
11292
11664
  -Math.sin(pitch) * Math.sin(yaw),
11293
11665
  Math.cos(pitch),
11294
11666
  Math.sin(pitch) * Math.cos(yaw)
@@ -11308,7 +11680,7 @@ function makeInteractionEvent(id, position, event) {
11308
11680
  function angularDistance(a, b) {
11309
11681
  const aVector = panoPositionToVector3(a);
11310
11682
  const bVector = panoPositionToVector3(b);
11311
- return MathUtils6.radToDeg(aVector.angleTo(bVector));
11683
+ return MathUtils7.radToDeg(aVector.angleTo(bVector));
11312
11684
  }
11313
11685
  function isTooltipOpen(trigger, hovered, focused, pinned) {
11314
11686
  switch (trigger) {
@@ -11522,6 +11894,7 @@ function HotspotAnchor({
11522
11894
  rotation = 0,
11523
11895
  renderOrder = 0,
11524
11896
  visible = true,
11897
+ selected = false,
11525
11898
  draggable = false,
11526
11899
  interactive = true,
11527
11900
  pointerEvents = "auto",
@@ -11539,19 +11912,19 @@ function HotspotAnchor({
11539
11912
  onPositionChange,
11540
11913
  onDragEnd
11541
11914
  }) {
11542
- const controlsRef = useContext20(PanoramaViewContext);
11543
- const hotspotTargetRegistry = useContext20(HotspotTargetRegistryContext);
11544
- const eventBus = useContext20(PanoEventBusContext);
11915
+ const controlsRef = useContext21(PanoramaViewContext);
11916
+ const hotspotTargetRegistry = useContext21(HotspotTargetRegistryContext);
11917
+ const eventBus = useContext21(PanoEventBusContext);
11545
11918
  const cursorApi = usePanoCursor();
11546
- const { camera, size } = useThree18();
11547
- const groupRef = useRef24(null);
11548
- const tooltipAnchorRef = useRef24(null);
11549
- const dragStateRef = useRef24(null);
11550
- const suppressNextClickRef = useRef24(false);
11551
- const [hovered, setHovered] = useState12(false);
11552
- const [dragging, setDragging] = useState12(false);
11553
- const [pinned, setPinned] = useState12(false);
11554
- const tooltipContent = useMemo18(
11919
+ const { camera, size } = useThree19();
11920
+ const groupRef = useRef25(null);
11921
+ const tooltipAnchorRef = useRef25(null);
11922
+ const dragStateRef = useRef25(null);
11923
+ const suppressNextClickRef = useRef25(false);
11924
+ const [hovered, setHovered] = useState13(false);
11925
+ const [dragging, setDragging] = useState13(false);
11926
+ const [pinned, setPinned] = useState13(false);
11927
+ const tooltipContent = useMemo19(
11555
11928
  () => resolveHotspotTooltipContent(tooltip),
11556
11929
  [tooltip]
11557
11930
  );
@@ -11573,22 +11946,26 @@ function HotspotAnchor({
11573
11946
  scaledWidth,
11574
11947
  scaledHeight
11575
11948
  );
11576
- const worldPosition = useMemo18(
11949
+ const worldPosition = useMemo19(
11577
11950
  () => panoPositionToVector3(normalizedPosition, resolvedDistance),
11578
11951
  [normalizedPosition.pitch, normalizedPosition.yaw, resolvedDistance]
11579
11952
  );
11580
- const surfaceQuaternion3 = useMemo18(
11953
+ const surfaceQuaternion3 = useMemo19(
11581
11954
  () => createSurfaceQuaternion(normalizedPosition),
11582
11955
  [normalizedPosition.pitch, normalizedPosition.yaw]
11583
11956
  );
11584
11957
  const worldWidth = angularSizeToWorldSize(scaledWidth, resolvedDistance);
11585
11958
  const worldHeight = angularSizeToWorldSize(scaledHeight, resolvedDistance);
11586
- const focusGeometry = useMemo18(() => {
11959
+ const focusGeometry = useMemo19(() => {
11587
11960
  const planeGeometry = new PlaneGeometry4(1, 1);
11588
11961
  const geometry = new EdgesGeometry(planeGeometry);
11589
11962
  planeGeometry.dispose();
11590
11963
  return geometry;
11591
11964
  }, []);
11965
+ const eventHovered = useRef25(false);
11966
+ useEffect24(() => {
11967
+ if (!visible || !interactive || pointerEvents === "none") eventHovered.current = false;
11968
+ }, [visible, interactive, pointerEvents]);
11592
11969
  const acceptsPointer = acceptsHotspotPointerEvents(interactive, pointerEvents);
11593
11970
  const canActivateFromKeyboard = Boolean(
11594
11971
  visible && interactive && (onClick || tooltipTrigger === "click")
@@ -11673,7 +12050,7 @@ function HotspotAnchor({
11673
12050
  dragStateRef.current = null;
11674
12051
  setDragging(false);
11675
12052
  }, [acceptsPointer]);
11676
- useLayoutEffect4(() => {
12053
+ useLayoutEffect7(() => {
11677
12054
  const group = groupRef.current;
11678
12055
  if (!group) {
11679
12056
  return;
@@ -11716,7 +12093,7 @@ function HotspotAnchor({
11716
12093
  worldHeight,
11717
12094
  worldWidth
11718
12095
  ]);
11719
- useFrame13((state) => {
12096
+ useFrame14((state) => {
11720
12097
  const group = groupRef.current;
11721
12098
  if (!group) {
11722
12099
  return;
@@ -11834,6 +12211,13 @@ function HotspotAnchor({
11834
12211
  } : void 0,
11835
12212
  onPointerOut: acceptsPointer ? (event) => {
11836
12213
  if (!dragStateRef.current) {
12214
+ if (!visible || event.nativeEvent.pointerType === "touch" || !eventHovered.current) return;
12215
+ const inside = event.intersections.some(({ object }) => {
12216
+ for (let node = object; node; node = node.parent) if (node === groupRef.current) return true;
12217
+ return false;
12218
+ });
12219
+ if (inside) return;
12220
+ eventHovered.current = false;
11837
12221
  setHovered(false);
11838
12222
  onHoverChange?.(
11839
12223
  false,
@@ -11843,6 +12227,8 @@ function HotspotAnchor({
11843
12227
  } : void 0,
11844
12228
  onPointerOver: acceptsPointer ? (event) => {
11845
12229
  if (!dragStateRef.current) {
12230
+ if (!visible || event.nativeEvent.pointerType === "touch" || eventHovered.current) return;
12231
+ eventHovered.current = true;
11846
12232
  setHovered(true);
11847
12233
  onHoverChange?.(
11848
12234
  true,
@@ -11852,18 +12238,28 @@ function HotspotAnchor({
11852
12238
  } : void 0,
11853
12239
  onPointerUp: acceptsPointer ? (event) => releasePointer(event, true) : void 0,
11854
12240
  children: [
11855
- focused ? focusContent ?? /* @__PURE__ */ jsx24(
12241
+ focused ? focusContent ?? /* @__PURE__ */ jsx26(
11856
12242
  "lineSegments",
11857
12243
  {
11858
12244
  geometry: focusGeometry,
11859
12245
  position: [0, 0, 0.01],
11860
12246
  renderOrder: renderOrder + 1,
11861
12247
  scale: [1.12, 1.12, 1],
11862
- children: /* @__PURE__ */ jsx24("lineBasicMaterial", { color: "#f5fbfc", depthTest: false })
12248
+ children: /* @__PURE__ */ jsx26("lineBasicMaterial", { color: "#f5fbfc", depthTest: false })
11863
12249
  }
11864
12250
  ) : null,
11865
12251
  children,
11866
- tooltipOpen && tooltipContent ? /* @__PURE__ */ jsx24("group", { ref: tooltipAnchorRef, children: /* @__PURE__ */ jsx24(
12252
+ selected && useAngularScale ? /* @__PURE__ */ jsx26(
12253
+ HotspotStrokeLine,
12254
+ {
12255
+ points: SELECTION_BORDER_POINTS,
12256
+ color: "#ffb300",
12257
+ lineWidth: 3,
12258
+ opacity: 1,
12259
+ renderOrder: renderOrder + 1
12260
+ }
12261
+ ) : null,
12262
+ tooltipOpen && tooltipContent ? /* @__PURE__ */ jsx26("group", { ref: tooltipAnchorRef, children: /* @__PURE__ */ jsx26(
11867
12263
  HotspotTooltip,
11868
12264
  {
11869
12265
  appearance: tooltipAppearance,
@@ -11884,7 +12280,7 @@ import {
11884
12280
  MeshBasicMaterial,
11885
12281
  Vector2 as Vector24
11886
12282
  } from "three";
11887
- import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
12283
+ import { jsx as jsx27, jsxs as jsxs16 } from "react/jsx-runtime";
11888
12284
  var HOTSPOT_ALPHA_CUTOFF = 0.05;
11889
12285
  var SAMPLE_UV = new Vector24();
11890
12286
  var RAYCAST_HITS = [];
@@ -11987,8 +12383,8 @@ function HotspotPlane({
11987
12383
  opacity
11988
12384
  }) {
11989
12385
  return /* @__PURE__ */ jsxs16("mesh", { raycast: hotspotPlaneRaycast, children: [
11990
- /* @__PURE__ */ jsx25("planeGeometry", { args: [1, 1] }),
11991
- /* @__PURE__ */ jsx25(
12386
+ /* @__PURE__ */ jsx27("planeGeometry", { args: [1, 1] }),
12387
+ /* @__PURE__ */ jsx27(
11992
12388
  PanoBasicMaterial,
11993
12389
  {
11994
12390
  target: "hotspot",
@@ -12004,12 +12400,12 @@ function HotspotPlane({
12004
12400
  }
12005
12401
 
12006
12402
  // src/hotspot/image-hotspot.tsx
12007
- import { jsx as jsx26 } from "react/jsx-runtime";
12403
+ import { jsx as jsx28 } from "react/jsx-runtime";
12008
12404
  function useHotspotImageTexture(src, onLoad, onError) {
12009
- const gl = useThree19((state) => state.gl);
12010
- const [texture, setTexture] = useState13(null);
12011
- const onLoadRef = useRef25(onLoad);
12012
- const onErrorRef = useRef25(onError);
12405
+ const gl = useThree20((state) => state.gl);
12406
+ const [texture, setTexture] = useState14(null);
12407
+ const onLoadRef = useRef26(onLoad);
12408
+ const onErrorRef = useRef26(onError);
12013
12409
  onLoadRef.current = onLoad;
12014
12410
  onErrorRef.current = onError;
12015
12411
  useEffect25(() => {
@@ -12068,11 +12464,11 @@ function ImageHotspot({
12068
12464
  ...anchorProps
12069
12465
  }) {
12070
12466
  const texture = useHotspotImageTexture(src, onLoad, onError);
12071
- return /* @__PURE__ */ jsx26(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsx26(HotspotPlane, { map: texture, opacity }) });
12467
+ return /* @__PURE__ */ jsx28(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsx28(HotspotPlane, { map: texture, opacity }) });
12072
12468
  }
12073
12469
 
12074
12470
  // src/hotspot/graphic-hotspot.tsx
12075
- import { useEffect as useEffect26, useRef as useRef26, useState as useState14 } from "react";
12471
+ import { useEffect as useEffect26, useRef as useRef27, useState as useState15 } from "react";
12076
12472
  import {
12077
12473
  CanvasTexture,
12078
12474
  ClampToEdgeWrapping as ClampToEdgeWrapping4,
@@ -12080,7 +12476,7 @@ import {
12080
12476
  LinearMipmapLinearFilter as LinearMipmapLinearFilter3,
12081
12477
  SRGBColorSpace as SRGBColorSpace5
12082
12478
  } from "three";
12083
- import { jsx as jsx27 } from "react/jsx-runtime";
12479
+ import { jsx as jsx29 } from "react/jsx-runtime";
12084
12480
  var GRAPHIC_TEXTURE_SIZE = 512;
12085
12481
  function resolveStrokeWidth(value) {
12086
12482
  return Number.isFinite(value) ? Math.max(value, 0) : 8;
@@ -12269,9 +12665,9 @@ function createGraphicTexture(graphic, aspectWidth, aspectHeight) {
12269
12665
  return texture;
12270
12666
  }
12271
12667
  function useGraphicTexture(graphic, aspectWidth, aspectHeight, onLoad, onError) {
12272
- const [texture, setTexture] = useState14(null);
12273
- const onLoadRef = useRef26(onLoad);
12274
- const onErrorRef = useRef26(onError);
12668
+ const [texture, setTexture] = useState15(null);
12669
+ const onLoadRef = useRef27(onLoad);
12670
+ const onErrorRef = useRef27(onError);
12275
12671
  onLoadRef.current = onLoad;
12276
12672
  onErrorRef.current = onError;
12277
12673
  useEffect26(() => {
@@ -12300,20 +12696,20 @@ function CanvasGraphicHotspot({
12300
12696
  ...anchorProps
12301
12697
  }) {
12302
12698
  const texture = useGraphicTexture(graphic, width, height, onLoad, onError);
12303
- return /* @__PURE__ */ jsx27(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsx27(HotspotPlane, { map: texture, opacity }) });
12699
+ return /* @__PURE__ */ jsx29(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsx29(HotspotPlane, { map: texture, opacity }) });
12304
12700
  }
12305
12701
  function GraphicHotspot(props) {
12306
12702
  const { graphic, ...restProps } = props;
12307
12703
  if (graphic.kind === "svg") {
12308
12704
  const imageProps = restProps;
12309
- return /* @__PURE__ */ jsx27(ImageHotspot, { ...imageProps, src: graphic.src });
12705
+ return /* @__PURE__ */ jsx29(ImageHotspot, { ...imageProps, src: graphic.src });
12310
12706
  }
12311
- return /* @__PURE__ */ jsx27(CanvasGraphicHotspot, { ...restProps, graphic });
12707
+ return /* @__PURE__ */ jsx29(CanvasGraphicHotspot, { ...restProps, graphic });
12312
12708
  }
12313
12709
 
12314
12710
  // src/hotspot/sequence-hotspot.tsx
12315
- import { useFrame as useFrame14, useThree as useThree20 } from "@react-three/fiber";
12316
- import { useEffect as useEffect27, useRef as useRef27, useState as useState15 } from "react";
12711
+ import { useFrame as useFrame15, useThree as useThree21 } from "@react-three/fiber";
12712
+ import { useEffect as useEffect27, useRef as useRef28, useState as useState16 } from "react";
12317
12713
  import {
12318
12714
  ClampToEdgeWrapping as ClampToEdgeWrapping5,
12319
12715
  LinearFilter as LinearFilter7,
@@ -12321,7 +12717,7 @@ import {
12321
12717
  SRGBColorSpace as SRGBColorSpace6,
12322
12718
  TextureLoader as TextureLoader5
12323
12719
  } from "three";
12324
- import { jsx as jsx28 } from "react/jsx-runtime";
12720
+ import { jsx as jsx30 } from "react/jsx-runtime";
12325
12721
  var DEFAULT_SEQUENCE_FPS = 12;
12326
12722
  function resolveFps(value) {
12327
12723
  return Number.isFinite(value) && value > 0 ? value : DEFAULT_SEQUENCE_FPS;
@@ -12335,10 +12731,10 @@ function useSpriteTexture({
12335
12731
  onError,
12336
12732
  onLoadProgress
12337
12733
  }) {
12338
- const gl = useThree20((state) => state.gl);
12339
- const [texture, setTexture] = useState15(null);
12340
- const onErrorRef = useRef27(onError);
12341
- const onLoadProgressRef = useRef27(onLoadProgress);
12734
+ const gl = useThree21((state) => state.gl);
12735
+ const [texture, setTexture] = useState16(null);
12736
+ const onErrorRef = useRef28(onError);
12737
+ const onLoadProgressRef = useRef28(onLoadProgress);
12342
12738
  onErrorRef.current = onError;
12343
12739
  onLoadProgressRef.current = onLoadProgress;
12344
12740
  useEffect27(() => {
@@ -12411,14 +12807,14 @@ function SequenceHotspot({
12411
12807
  const texture = useSpriteTexture({ id, src, onError, onLoadProgress });
12412
12808
  const frameCount = resolveFrameCount(requestedFrameCount);
12413
12809
  const safeFps = resolveFps(fps);
12414
- const [frameIndex, setFrameIndex] = useState15(0);
12415
- const elapsedRef = useRef27(0);
12416
- const frameIndexRef = useRef27(0);
12417
- const endedRef = useRef27(false);
12418
- const lastPlaybackStateRef = useRef27(null);
12419
- const onFrameChangeRef = useRef27(onFrameChange);
12420
- const onPlaybackStateChangeRef = useRef27(onPlaybackStateChange);
12421
- const onEndedRef = useRef27(onEnded);
12810
+ const [frameIndex, setFrameIndex] = useState16(0);
12811
+ const elapsedRef = useRef28(0);
12812
+ const frameIndexRef = useRef28(0);
12813
+ const endedRef = useRef28(false);
12814
+ const lastPlaybackStateRef = useRef28(null);
12815
+ const onFrameChangeRef = useRef28(onFrameChange);
12816
+ const onPlaybackStateChangeRef = useRef28(onPlaybackStateChange);
12817
+ const onEndedRef = useRef28(onEnded);
12422
12818
  onFrameChangeRef.current = onFrameChange;
12423
12819
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
12424
12820
  onEndedRef.current = onEnded;
@@ -12467,7 +12863,7 @@ function SequenceHotspot({
12467
12863
  onPlaybackStateChangeRef.current?.(nextState);
12468
12864
  }
12469
12865
  }, [playing, texture]);
12470
- useFrame14((_, deltaSeconds) => {
12866
+ useFrame15((_, deltaSeconds) => {
12471
12867
  if (!texture || !playing || endedRef.current) {
12472
12868
  return;
12473
12869
  }
@@ -12497,12 +12893,12 @@ function SequenceHotspot({
12497
12893
  onFrameChangeRef.current?.({ id, index: nextIndex, src });
12498
12894
  }
12499
12895
  });
12500
- return /* @__PURE__ */ jsx28(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ jsx28(HotspotPlane, { map: texture, opacity }) });
12896
+ return /* @__PURE__ */ jsx30(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ jsx30(HotspotPlane, { map: texture, opacity }) });
12501
12897
  }
12502
12898
 
12503
12899
  // src/hotspot/video-hotspot.tsx
12504
- import { useThree as useThree21 } from "@react-three/fiber";
12505
- import { useEffect as useEffect28, useLayoutEffect as useLayoutEffect5, useRef as useRef28, useState as useState16 } from "react";
12900
+ import { useThree as useThree22 } from "@react-three/fiber";
12901
+ import { useEffect as useEffect28, useLayoutEffect as useLayoutEffect8, useRef as useRef29, useState as useState17 } from "react";
12506
12902
  import {
12507
12903
  ClampToEdgeWrapping as ClampToEdgeWrapping6,
12508
12904
  LinearFilter as LinearFilter8,
@@ -12563,7 +12959,7 @@ function mountTextureVideo(video, canvas) {
12563
12959
  }
12564
12960
 
12565
12961
  // src/hotspot/video-hotspot.tsx
12566
- import { jsx as jsx29 } from "react/jsx-runtime";
12962
+ import { jsx as jsx31 } from "react/jsx-runtime";
12567
12963
  function clampVolume2(volume) {
12568
12964
  if (!Number.isFinite(volume)) {
12569
12965
  return 1;
@@ -12571,9 +12967,9 @@ function clampVolume2(volume) {
12571
12967
  return Math.max(0, Math.min(volume, 1));
12572
12968
  }
12573
12969
  function usePosterTexture(id, poster, onError) {
12574
- const gl = useThree21((state) => state.gl);
12575
- const [texture, setTexture] = useState16(null);
12576
- const onErrorRef = useRef28(onError);
12970
+ const gl = useThree22((state) => state.gl);
12971
+ const [texture, setTexture] = useState17(null);
12972
+ const onErrorRef = useRef29(onError);
12577
12973
  onErrorRef.current = onError;
12578
12974
  useEffect28(() => {
12579
12975
  let active = true;
@@ -12630,11 +13026,11 @@ function useVideoResource({
12630
13026
  poster,
12631
13027
  src
12632
13028
  }) {
12633
- const gl = useThree21((state) => state.gl);
12634
- const [resource, setResource] = useState16(null);
12635
- const onEndedRef = useRef28(onEnded);
12636
- const onErrorRef = useRef28(onError);
12637
- const onPlaybackStateChangeRef = useRef28(onPlaybackStateChange);
13029
+ const gl = useThree22((state) => state.gl);
13030
+ const [resource, setResource] = useState17(null);
13031
+ const onEndedRef = useRef29(onEnded);
13032
+ const onErrorRef = useRef29(onError);
13033
+ const onPlaybackStateChangeRef = useRef29(onPlaybackStateChange);
12638
13034
  onEndedRef.current = onEnded;
12639
13035
  onErrorRef.current = onError;
12640
13036
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
@@ -12708,7 +13104,7 @@ function useVideoResource({
12708
13104
  texture.dispose();
12709
13105
  };
12710
13106
  }, [crossOrigin, gl, id, playsInline, poster, src]);
12711
- useLayoutEffect5(() => {
13107
+ useLayoutEffect8(() => {
12712
13108
  if (!resource) {
12713
13109
  return;
12714
13110
  }
@@ -12751,11 +13147,11 @@ function VideoHotspot({
12751
13147
  src
12752
13148
  });
12753
13149
  const posterTexture = usePosterTexture(id, poster, onError);
12754
- const onPlaybackErrorRef = useRef28(onPlaybackError);
12755
- const onPlaybackStateChangeRef = useRef28(onPlaybackStateChange);
13150
+ const onPlaybackErrorRef = useRef29(onPlaybackError);
13151
+ const onPlaybackStateChangeRef = useRef29(onPlaybackStateChange);
12756
13152
  onPlaybackErrorRef.current = onPlaybackError;
12757
13153
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
12758
- useLayoutEffect5(() => {
13154
+ useLayoutEffect8(() => {
12759
13155
  if (!resource) {
12760
13156
  return;
12761
13157
  }
@@ -12788,12 +13184,12 @@ function VideoHotspot({
12788
13184
  };
12789
13185
  }, [playing, resource]);
12790
13186
  const activeTexture = resource?.ready ? resource.texture : posterTexture;
12791
- return /* @__PURE__ */ jsx29(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ jsx29(HotspotPlane, { map: activeTexture, opacity }) });
13187
+ return /* @__PURE__ */ jsx31(HotspotAnchor, { ...anchorProps, id, height, width, children: /* @__PURE__ */ jsx31(HotspotPlane, { map: activeTexture, opacity }) });
12792
13188
  }
12793
13189
 
12794
13190
  // src/hotspot/audio-hotspot.tsx
12795
- import { useFrame as useFrame15, useThree as useThree22 } from "@react-three/fiber";
12796
- import { useEffect as useEffect29, useRef as useRef29, useState as useState17 } from "react";
13191
+ import { useFrame as useFrame16, useThree as useThree23 } from "@react-three/fiber";
13192
+ import { useEffect as useEffect29, useRef as useRef30, useState as useState18 } from "react";
12797
13193
  import {
12798
13194
  CanvasTexture as CanvasTexture2,
12799
13195
  ClampToEdgeWrapping as ClampToEdgeWrapping7,
@@ -12804,10 +13200,10 @@ import {
12804
13200
  } from "three";
12805
13201
 
12806
13202
  // src/hotspot/audio-spatial.ts
12807
- import { MathUtils as MathUtils7, Vector3 as Vector315 } from "three";
12808
- var LOOK_DIR = new Vector315();
12809
- var SOURCE_DIR = new Vector315();
12810
- var RIGHT_DIR = new Vector315();
13203
+ import { MathUtils as MathUtils8, Vector3 as Vector316 } from "three";
13204
+ var LOOK_DIR = new Vector316();
13205
+ var SOURCE_DIR = new Vector316();
13206
+ var RIGHT_DIR = new Vector316();
12811
13207
  function clampAudioVolume(volume) {
12812
13208
  if (!Number.isFinite(volume)) {
12813
13209
  return 1;
@@ -12832,11 +13228,11 @@ function lookRangeGain(angleDegrees, range) {
12832
13228
  function audioSpatialFromView(camera, position, range) {
12833
13229
  camera.getWorldDirection(LOOK_DIR);
12834
13230
  SOURCE_DIR.copy(panoPositionToVector3(position)).normalize();
12835
- const angleDegrees = MathUtils7.radToDeg(
12836
- Math.acos(MathUtils7.clamp(LOOK_DIR.dot(SOURCE_DIR), -1, 1))
13231
+ const angleDegrees = MathUtils8.radToDeg(
13232
+ Math.acos(MathUtils8.clamp(LOOK_DIR.dot(SOURCE_DIR), -1, 1))
12837
13233
  );
12838
13234
  RIGHT_DIR.set(1, 0, 0).applyQuaternion(camera.quaternion);
12839
- const pan = MathUtils7.clamp(
13235
+ const pan = MathUtils8.clamp(
12840
13236
  Math.atan2(SOURCE_DIR.dot(RIGHT_DIR), SOURCE_DIR.dot(LOOK_DIR)) / (Math.PI / 2),
12841
13237
  -1,
12842
13238
  1
@@ -12848,7 +13244,7 @@ function audioSpatialFromView(camera, position, range) {
12848
13244
  }
12849
13245
 
12850
13246
  // src/hotspot/audio-hotspot.tsx
12851
- import { jsx as jsx30 } from "react/jsx-runtime";
13247
+ import { jsx as jsx32 } from "react/jsx-runtime";
12852
13248
  var SPEAKER_TEXTURE_SIZE = 512;
12853
13249
  function paintSpeakerBody(context, center) {
12854
13250
  context.fillStyle = "#f5fbfc";
@@ -12912,7 +13308,7 @@ function createSpeakerTexture(state) {
12912
13308
  return texture;
12913
13309
  }
12914
13310
  function useSpeakerTextures(enabled) {
12915
- const [textures, setTextures] = useState17({ playing: null, stopped: null });
13311
+ const [textures, setTextures] = useState18({ playing: null, stopped: null });
12916
13312
  useEffect29(() => {
12917
13313
  if (!enabled) {
12918
13314
  setTextures({ playing: null, stopped: null });
@@ -12929,9 +13325,9 @@ function useSpeakerTextures(enabled) {
12929
13325
  return textures;
12930
13326
  }
12931
13327
  function useIconTexture(icon, enabled, onError, id) {
12932
- const gl = useThree22((state) => state.gl);
12933
- const [texture, setTexture] = useState17(null);
12934
- const onErrorRef = useRef29(onError);
13328
+ const gl = useThree23((state) => state.gl);
13329
+ const [texture, setTexture] = useState18(null);
13330
+ const onErrorRef = useRef30(onError);
12935
13331
  onErrorRef.current = onError;
12936
13332
  useEffect29(() => {
12937
13333
  let active = true;
@@ -12987,15 +13383,16 @@ function useAudioHowl({
12987
13383
  onPlaybackError,
12988
13384
  onPlaybackStateChange,
12989
13385
  playing,
12990
- src
13386
+ src,
13387
+ format
12991
13388
  }) {
12992
- const [howl, setHowl] = useState17(null);
12993
- const playingRef = useRef29(playing);
12994
- const loopRef = useRef29(loop ?? false);
12995
- const onEndedRef = useRef29(onEnded);
12996
- const onErrorRef = useRef29(onError);
12997
- const onPlaybackErrorRef = useRef29(onPlaybackError);
12998
- const onPlaybackStateChangeRef = useRef29(onPlaybackStateChange);
13389
+ const [howl, setHowl] = useState18(null);
13390
+ const playingRef = useRef30(playing);
13391
+ const loopRef = useRef30(loop ?? false);
13392
+ const onEndedRef = useRef30(onEnded);
13393
+ const onErrorRef = useRef30(onError);
13394
+ const onPlaybackErrorRef = useRef30(onPlaybackError);
13395
+ const onPlaybackStateChangeRef = useRef30(onPlaybackStateChange);
12999
13396
  playingRef.current = playing;
13000
13397
  loopRef.current = loop ?? false;
13001
13398
  onEndedRef.current = onEnded;
@@ -13003,6 +13400,7 @@ function useAudioHowl({
13003
13400
  onPlaybackErrorRef.current = onPlaybackError;
13004
13401
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
13005
13402
  const sourcesKey = sourceList(src).join("\0");
13403
+ const formatsKey = sourceList(format).join("\0");
13006
13404
  useEffect29(() => {
13007
13405
  const sources = sourcesKey.length > 0 ? sourcesKey.split("\0") : [];
13008
13406
  if (sources.length === 0) {
@@ -13012,6 +13410,7 @@ function useAudioHowl({
13012
13410
  let active = true;
13013
13411
  const handle = createUnlockingHowl({
13014
13412
  src: sources,
13413
+ format: formatsKey ? formatsKey.split("\0") : void 0,
13015
13414
  loop: loopRef.current,
13016
13415
  crossOrigin,
13017
13416
  isActive: () => active,
@@ -13039,7 +13438,7 @@ function useAudioHowl({
13039
13438
  active = false;
13040
13439
  handle.dispose();
13041
13440
  };
13042
- }, [crossOrigin, id, sourcesKey]);
13441
+ }, [crossOrigin, id, sourcesKey, formatsKey]);
13043
13442
  useEffect29(() => {
13044
13443
  howl?.loop(loop ?? false);
13045
13444
  }, [howl, loop]);
@@ -13048,6 +13447,7 @@ function useAudioHowl({
13048
13447
  function AudioHotspot({
13049
13448
  id,
13050
13449
  src,
13450
+ format,
13051
13451
  playing,
13052
13452
  loop = false,
13053
13453
  muted = false,
@@ -13068,7 +13468,7 @@ function AudioHotspot({
13068
13468
  ...anchorProps
13069
13469
  }) {
13070
13470
  usePanoMediaActivationIntent(playing);
13071
- const camera = useThree22((state) => state.camera);
13471
+ const camera = useThree23((state) => state.camera);
13072
13472
  const howl = useAudioHowl({
13073
13473
  crossOrigin,
13074
13474
  id,
@@ -13078,17 +13478,18 @@ function AudioHotspot({
13078
13478
  onPlaybackError,
13079
13479
  onPlaybackStateChange,
13080
13480
  playing,
13081
- src
13481
+ src,
13482
+ format
13082
13483
  });
13083
13484
  const speakerTextures = useSpeakerTextures(marker && !icon);
13084
13485
  const stoppedIconTexture = useIconTexture(icon, marker, onError, id);
13085
13486
  const playingIconTexture = useIconTexture(playingIcon, marker, onError, id);
13086
13487
  const markerTexture = playing ? playingIconTexture ?? stoppedIconTexture ?? speakerTextures.playing : stoppedIconTexture ?? speakerTextures.stopped;
13087
- const playingRef = useRef29(playing);
13488
+ const playingRef = useRef30(playing);
13088
13489
  playingRef.current = playing;
13089
- const mutedRef = useRef29(muted);
13090
- const volumeRef = useRef29(volume);
13091
- const rangeRef = useRef29(range);
13490
+ const mutedRef = useRef30(muted);
13491
+ const volumeRef = useRef30(volume);
13492
+ const rangeRef = useRef30(range);
13092
13493
  mutedRef.current = muted;
13093
13494
  volumeRef.current = volume;
13094
13495
  rangeRef.current = range;
@@ -13120,7 +13521,7 @@ function AudioHotspot({
13120
13521
  document.removeEventListener("visibilitychange", handleVisibility);
13121
13522
  };
13122
13523
  }, [howl, pauseWhenHidden]);
13123
- useFrame15(() => {
13524
+ useFrame16(() => {
13124
13525
  if (!howl) {
13125
13526
  return;
13126
13527
  }
@@ -13133,21 +13534,21 @@ function AudioHotspot({
13133
13534
  howl.mute(mutedRef.current);
13134
13535
  howl.volume(clampAudioVolume(volumeRef.current) * spatial.gain);
13135
13536
  });
13136
- return /* @__PURE__ */ jsx30(HotspotAnchor, { ...anchorProps, id, height, width, children: marker ? /* @__PURE__ */ jsx30(HotspotPlane, { map: markerTexture, opacity }) : null });
13537
+ return /* @__PURE__ */ jsx32(HotspotAnchor, { ...anchorProps, id, height, width, children: marker ? /* @__PURE__ */ jsx32(HotspotPlane, { map: markerTexture, opacity }) : null });
13137
13538
  }
13138
13539
 
13139
13540
  // src/hotspot/text-hotspot.tsx
13140
- import { useEffect as useEffect30, useRef as useRef30, useState as useState18 } from "react";
13141
- import { useFrame as useFrame16, useThree as useThree23 } from "@react-three/fiber";
13541
+ import { useEffect as useEffect30, useRef as useRef31, useState as useState19 } from "react";
13542
+ import { useFrame as useFrame17, useThree as useThree24 } from "@react-three/fiber";
13142
13543
  import {
13143
13544
  CanvasTexture as CanvasTexture3,
13144
13545
  ClampToEdgeWrapping as ClampToEdgeWrapping8,
13145
13546
  LinearFilter as LinearFilter10,
13146
- MathUtils as MathUtils8,
13147
- PerspectiveCamera as PerspectiveCamera6,
13547
+ MathUtils as MathUtils9,
13548
+ PerspectiveCamera as PerspectiveCamera7,
13148
13549
  SRGBColorSpace as SRGBColorSpace9
13149
13550
  } from "three";
13150
- import { jsx as jsx31 } from "react/jsx-runtime";
13551
+ import { jsx as jsx33 } from "react/jsx-runtime";
13151
13552
  var TEXT_TEXTURE_LONG_SIDE = 1024;
13152
13553
  var TEXT_RASTER_SUPERSAMPLE = 2;
13153
13554
  var TEXT_RASTER_MAX_LONG_SIDE = 4096;
@@ -13218,7 +13619,7 @@ function projectedDevicePixels(angleDeg, fovDeg, viewportHeight) {
13218
13619
  }
13219
13620
  const fov = Math.min(179, Math.max(1, resolvePositive(fovDeg, DEFAULT_REFERENCE_FOV2)));
13220
13621
  const angle = Math.min(179, Math.max(0.01, Math.abs(angleDeg)));
13221
- return Math.tan(MathUtils8.degToRad(angle) / 2) / Math.tan(MathUtils8.degToRad(fov) / 2) * viewportHeight;
13622
+ return Math.tan(MathUtils9.degToRad(angle) / 2) / Math.tan(MathUtils9.degToRad(fov) / 2) * viewportHeight;
13222
13623
  }
13223
13624
  function snapRasterLongSide(screenLong, layoutLong) {
13224
13625
  if (!(screenLong > 0)) {
@@ -13414,13 +13815,13 @@ function createTextTexture(text, width, height, style, rasterLong) {
13414
13815
  return texture;
13415
13816
  }
13416
13817
  function useRasterLongSide(width, height, scale, scaleMode, referenceFov) {
13417
- const size = useThree23((state) => state.size);
13418
- const camera = useThree23((state) => state.camera);
13419
- const getPixelRatio = useThree23((state) => state.gl.getPixelRatio);
13818
+ const size = useThree24((state) => state.size);
13819
+ const camera = useThree24((state) => state.camera);
13820
+ const getPixelRatio = useThree24((state) => state.gl.getPixelRatio);
13420
13821
  const measure = () => {
13421
13822
  const pixelRatio = getPixelRatio();
13422
13823
  const viewportHeight = size.height * (Number.isFinite(pixelRatio) && pixelRatio > 0 ? pixelRatio : 1);
13423
- const cameraFov = camera instanceof PerspectiveCamera6 ? camera.fov : DEFAULT_REFERENCE_FOV2;
13824
+ const cameraFov = camera instanceof PerspectiveCamera7 ? camera.fov : DEFAULT_REFERENCE_FOV2;
13424
13825
  const screenLong = Math.max(
13425
13826
  projectedDevicePixels(
13426
13827
  width * resolveScale2(scale),
@@ -13439,12 +13840,12 @@ function useRasterLongSide(width, height, scale, scaleMode, referenceFov) {
13439
13840
  Math.max(layout.canvasWidth, layout.canvasHeight)
13440
13841
  );
13441
13842
  };
13442
- const [rasterLong, setRasterLong] = useState18(measure);
13843
+ const [rasterLong, setRasterLong] = useState19(measure);
13443
13844
  const measured = measure();
13444
13845
  if (measured !== rasterLong) {
13445
13846
  setRasterLong(measured);
13446
13847
  }
13447
- useFrame16(() => {
13848
+ useFrame17(() => {
13448
13849
  const next = measure();
13449
13850
  setRasterLong((current) => current === next ? current : next);
13450
13851
  });
@@ -13452,9 +13853,9 @@ function useRasterLongSide(width, height, scale, scaleMode, referenceFov) {
13452
13853
  }
13453
13854
  function useTextTexture(text, width, height, style, scale, scaleMode, referenceFov, onLoad, onError) {
13454
13855
  const rasterLong = useRasterLongSide(width, height, scale, scaleMode, referenceFov);
13455
- const [texture, setTexture] = useState18(null);
13456
- const onLoadRef = useRef30(onLoad);
13457
- const onErrorRef = useRef30(onError);
13856
+ const [texture, setTexture] = useState19(null);
13857
+ const onLoadRef = useRef31(onLoad);
13858
+ const onErrorRef = useRef31(onError);
13458
13859
  onLoadRef.current = onLoad;
13459
13860
  onErrorRef.current = onError;
13460
13861
  useEffect30(() => {
@@ -13567,13 +13968,13 @@ function TextHotspot({
13567
13968
  onLoad,
13568
13969
  onError
13569
13970
  );
13570
- return /* @__PURE__ */ jsx31(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsx31(HotspotPlane, { map: texture, opacity }) });
13971
+ return /* @__PURE__ */ jsx33(HotspotAnchor, { ...anchorProps, height, width, children: /* @__PURE__ */ jsx33(HotspotPlane, { map: texture, opacity }) });
13571
13972
  }
13572
13973
 
13573
13974
  // src/hotspot/iframe-hotspot.tsx
13574
13975
  import { Html } from "@react-three/drei";
13575
13976
  import { DoubleSide as DoubleSide4 } from "three";
13576
- import { jsx as jsx32, jsxs as jsxs17 } from "react/jsx-runtime";
13977
+ import { jsx as jsx34, jsxs as jsxs17 } from "react/jsx-runtime";
13577
13978
  var IFRAME_LONG_SIDE = 800;
13578
13979
  var CSS3D_DISTANCE_FACTOR = 400;
13579
13980
  var DEFAULT_SANDBOX = "allow-scripts allow-popups allow-forms";
@@ -13640,8 +14041,8 @@ function IframeHotspot({
13640
14041
  width,
13641
14042
  children: [
13642
14043
  /* @__PURE__ */ jsxs17("mesh", { children: [
13643
- /* @__PURE__ */ jsx32("planeGeometry", { args: [1, 1] }),
13644
- /* @__PURE__ */ jsx32(
14044
+ /* @__PURE__ */ jsx34("planeGeometry", { args: [1, 1] }),
14045
+ /* @__PURE__ */ jsx34(
13645
14046
  "meshBasicMaterial",
13646
14047
  {
13647
14048
  depthWrite: false,
@@ -13652,7 +14053,7 @@ function IframeHotspot({
13652
14053
  }
13653
14054
  )
13654
14055
  ] }),
13655
- /* @__PURE__ */ jsx32(
14056
+ /* @__PURE__ */ jsx34(
13656
14057
  Html,
13657
14058
  {
13658
14059
  distanceFactor: CSS3D_DISTANCE_FACTOR,
@@ -13667,7 +14068,7 @@ function IframeHotspot({
13667
14068
  },
13668
14069
  transform: true,
13669
14070
  zIndexRange: [10, 0],
13670
- children: /* @__PURE__ */ jsx32(
14071
+ children: /* @__PURE__ */ jsx34(
13671
14072
  "iframe",
13672
14073
  {
13673
14074
  allow,
@@ -13689,7 +14090,7 @@ function IframeHotspot({
13689
14090
  }
13690
14091
 
13691
14092
  // src/hotspot/polygon-hotspot.tsx
13692
- import { useEffect as useEffect31, useMemo as useMemo19, useRef as useRef32 } from "react";
14093
+ import { useEffect as useEffect31, useMemo as useMemo20, useRef as useRef32 } from "react";
13693
14094
  import {
13694
14095
  BufferGeometry as BufferGeometry2,
13695
14096
  DoubleSide as DoubleSide5,
@@ -13700,85 +14101,7 @@ import {
13700
14101
  Vector2 as Vector25,
13701
14102
  Vector3 as Vector317
13702
14103
  } from "three";
13703
-
13704
- // src/hotspot/hotspot-stroke-line.tsx
13705
- import { Line } from "@react-three/drei";
13706
- import { useFrame as useFrame17, useThree as useThree24 } from "@react-three/fiber";
13707
- import { useLayoutEffect as useLayoutEffect6, useRef as useRef31 } from "react";
13708
- import {
13709
- MathUtils as MathUtils9,
13710
- PerspectiveCamera as PerspectiveCamera7,
13711
- Vector3 as Vector316
13712
- } from "three";
13713
- import { jsx as jsx33 } from "react/jsx-runtime";
13714
- var LINE_WORLD_POSITION = new Vector316();
13715
- function applyScreenSpaceDashScale(object, camera, viewportHeight) {
13716
- const material = object?.material;
13717
- if (!object || typeof material?.dashScale !== "number") {
13718
- return;
13719
- }
13720
- object.getWorldPosition(LINE_WORLD_POSITION);
13721
- const depth = Math.max(camera.position.distanceTo(LINE_WORLD_POSITION), 1);
13722
- const fov = camera instanceof PerspectiveCamera7 ? camera.fov : 75;
13723
- material.dashScale = viewportHeight / (2 * Math.tan(MathUtils9.degToRad(fov) * 0.5) * depth);
13724
- }
13725
- function resolveDash(dashSize, gapSize) {
13726
- if (dashSize === void 0 || !Number.isFinite(dashSize) || dashSize <= 0) {
13727
- return { dashed: false, dashSize: 1, gapSize: 1 };
13728
- }
13729
- return {
13730
- dashed: true,
13731
- dashSize,
13732
- gapSize: Number.isFinite(gapSize) ? Math.max(0, gapSize) : dashSize
13733
- };
13734
- }
13735
- function HotspotStrokeLine({
13736
- points,
13737
- color,
13738
- lineWidth,
13739
- opacity,
13740
- renderOrder,
13741
- dashSize,
13742
- gapSize
13743
- }) {
13744
- const lineRef = useRef31(null);
13745
- const { camera, size } = useThree24();
13746
- const dash = resolveDash(dashSize, gapSize);
13747
- useLayoutEffect6(() => {
13748
- if (!dash.dashed) {
13749
- return;
13750
- }
13751
- applyScreenSpaceDashScale(lineRef.current, camera, size.height);
13752
- }, [camera, dash.dashed, dash.dashSize, dash.gapSize, size.height]);
13753
- useFrame17((state) => {
13754
- if (!dash.dashed) {
13755
- return;
13756
- }
13757
- applyScreenSpaceDashScale(lineRef.current, state.camera, state.size.height);
13758
- });
13759
- return /* @__PURE__ */ jsx33(
13760
- Line,
13761
- {
13762
- ref: lineRef,
13763
- alphaToCoverage: true,
13764
- color,
13765
- dashed: dash.dashed,
13766
- dashSize: dash.dashSize,
13767
- depthTest: true,
13768
- depthWrite: false,
13769
- gapSize: dash.gapSize,
13770
- lineWidth: Math.max(0.5, lineWidth),
13771
- opacity,
13772
- points,
13773
- renderOrder,
13774
- transparent: true,
13775
- worldUnits: false
13776
- }
13777
- );
13778
- }
13779
-
13780
- // src/hotspot/polygon-hotspot.tsx
13781
- import { jsx as jsx34, jsxs as jsxs18 } from "react/jsx-runtime";
14104
+ import { jsx as jsx35, jsxs as jsxs18 } from "react/jsx-runtime";
13782
14105
  var POLYGON_RADIUS = 990;
13783
14106
  var MAX_FILL_SEGMENT_DEGREES = 7;
13784
14107
  function cloneVertices(vertices) {
@@ -14008,10 +14331,10 @@ function makeStrokePositions(vertices, center) {
14008
14331
  return points;
14009
14332
  }
14010
14333
  function translateVertices(vertices, from, to) {
14011
- const yawOffset = normalizePanoYaw(to.yaw - from.yaw);
14334
+ const yawOffset2 = normalizePanoYaw(to.yaw - from.yaw);
14012
14335
  const pitchOffset = to.pitch - from.pitch;
14013
14336
  return vertices.map((vertex) => ({
14014
- yaw: normalizePanoYaw(vertex.yaw + yawOffset),
14337
+ yaw: normalizePanoYaw(vertex.yaw + yawOffset2),
14015
14338
  pitch: clampPanoPitch(vertex.pitch + pitchOffset)
14016
14339
  }));
14017
14340
  }
@@ -14034,14 +14357,14 @@ function PolygonHotspot({
14034
14357
  onInvalid,
14035
14358
  ...anchorProps
14036
14359
  }) {
14037
- const issues = useMemo19(() => validatePolygonVertices(vertices), [vertices]);
14360
+ const issues = useMemo20(() => validatePolygonVertices(vertices), [vertices]);
14038
14361
  const valid = issues.length === 0;
14039
- const center = useMemo19(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
14040
- const fillGeometry = useMemo19(
14362
+ const center = useMemo20(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
14363
+ const fillGeometry = useMemo20(
14041
14364
  () => valid && center ? makeFillGeometry(vertices, center) : null,
14042
14365
  [center, valid, vertices]
14043
14366
  );
14044
- const strokePoints = useMemo19(
14367
+ const strokePoints = useMemo20(
14045
14368
  () => valid && center ? makeStrokePositions(vertices, center) : null,
14046
14369
  [center, valid, vertices]
14047
14370
  );
@@ -14081,7 +14404,7 @@ function PolygonHotspot({
14081
14404
  draggable,
14082
14405
  interactive,
14083
14406
  useAngularScale: false,
14084
- focusContent: /* @__PURE__ */ jsx34(
14407
+ focusContent: /* @__PURE__ */ jsx35(
14085
14408
  HotspotStrokeLine,
14086
14409
  {
14087
14410
  color: "#75cbd3",
@@ -14103,7 +14426,7 @@ function PolygonHotspot({
14103
14426
  startVerticesRef.current = null;
14104
14427
  },
14105
14428
  children: [
14106
- /* @__PURE__ */ jsx34("mesh", { geometry: fillGeometry, renderOrder, children: /* @__PURE__ */ jsx34(
14429
+ /* @__PURE__ */ jsx35("mesh", { geometry: fillGeometry, renderOrder, children: /* @__PURE__ */ jsx35(
14107
14430
  "meshBasicMaterial",
14108
14431
  {
14109
14432
  color: fill,
@@ -14113,7 +14436,7 @@ function PolygonHotspot({
14113
14436
  transparent: true
14114
14437
  }
14115
14438
  ) }),
14116
- /* @__PURE__ */ jsx34(
14439
+ /* @__PURE__ */ jsx35(
14117
14440
  HotspotStrokeLine,
14118
14441
  {
14119
14442
  color: stroke,
@@ -14131,14 +14454,14 @@ function PolygonHotspot({
14131
14454
  }
14132
14455
 
14133
14456
  // src/hotspot/polyline-hotspot.tsx
14134
- import { useEffect as useEffect32, useMemo as useMemo20, useRef as useRef33 } from "react";
14457
+ import { useEffect as useEffect32, useMemo as useMemo21, useRef as useRef33 } from "react";
14135
14458
  import {
14136
14459
  MathUtils as MathUtils10,
14137
14460
  Matrix4 as Matrix46,
14138
14461
  Quaternion as Quaternion5,
14139
14462
  Vector3 as Vector318
14140
14463
  } from "three";
14141
- import { jsx as jsx35 } from "react/jsx-runtime";
14464
+ import { jsx as jsx36 } from "react/jsx-runtime";
14142
14465
  var POLYLINE_RADIUS = 990;
14143
14466
  var MAX_SEGMENT_DEGREES = 7;
14144
14467
  function cloneVertices2(vertices) {
@@ -14229,10 +14552,10 @@ function makeLinePositions(vertices, center) {
14229
14552
  return points;
14230
14553
  }
14231
14554
  function translateVertices2(vertices, from, to) {
14232
- const yawOffset = normalizePanoYaw(to.yaw - from.yaw);
14555
+ const yawOffset2 = normalizePanoYaw(to.yaw - from.yaw);
14233
14556
  const pitchOffset = to.pitch - from.pitch;
14234
14557
  return vertices.map((vertex) => ({
14235
- yaw: normalizePanoYaw(vertex.yaw + yawOffset),
14558
+ yaw: normalizePanoYaw(vertex.yaw + yawOffset2),
14236
14559
  pitch: clampPanoPitch(vertex.pitch + pitchOffset)
14237
14560
  }));
14238
14561
  }
@@ -14255,10 +14578,10 @@ function PolylineHotspot({
14255
14578
  onInvalid,
14256
14579
  ...anchorProps
14257
14580
  }) {
14258
- const issues = useMemo20(() => validatePolylineVertices(vertices), [vertices]);
14581
+ const issues = useMemo21(() => validatePolylineVertices(vertices), [vertices]);
14259
14582
  const valid = issues.length === 0;
14260
- const center = useMemo20(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
14261
- const linePoints = useMemo20(
14583
+ const center = useMemo21(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
14584
+ const linePoints = useMemo21(
14262
14585
  () => valid && center ? makeLinePositions(vertices, center) : null,
14263
14586
  [center, valid, vertices]
14264
14587
  );
@@ -14278,7 +14601,7 @@ function PolylineHotspot({
14278
14601
  startPosition: event.startPosition
14279
14602
  });
14280
14603
  };
14281
- return /* @__PURE__ */ jsx35(
14604
+ return /* @__PURE__ */ jsx36(
14282
14605
  HotspotAnchor,
14283
14606
  {
14284
14607
  ...anchorProps,
@@ -14292,7 +14615,7 @@ function PolylineHotspot({
14292
14615
  renderOrder,
14293
14616
  draggable,
14294
14617
  useAngularScale: false,
14295
- focusContent: /* @__PURE__ */ jsx35(
14618
+ focusContent: /* @__PURE__ */ jsx36(
14296
14619
  HotspotStrokeLine,
14297
14620
  {
14298
14621
  color: "#75cbd3",
@@ -14313,7 +14636,7 @@ function PolylineHotspot({
14313
14636
  emitVerticesChange(event, onDragEnd);
14314
14637
  startVerticesRef.current = null;
14315
14638
  },
14316
- children: /* @__PURE__ */ jsx35(
14639
+ children: /* @__PURE__ */ jsx36(
14317
14640
  HotspotStrokeLine,
14318
14641
  {
14319
14642
  color: stroke,
@@ -14330,13 +14653,14 @@ function PolylineHotspot({
14330
14653
  }
14331
14654
 
14332
14655
  // src/project-scenes.tsx
14333
- import { jsx as jsx36 } from "react/jsx-runtime";
14656
+ import { jsx as jsx37 } from "react/jsx-runtime";
14334
14657
  function isMedia(hotspot) {
14335
14658
  return hotspot.type === "audio" || hotspot.type === "video" || hotspot.type === "sequence";
14336
14659
  }
14337
- function handleProjectHotspotClick(hotspot, sceneId, interaction, callback, navigate, toggle) {
14660
+ function handleProjectHotspotClick(hotspot, sceneId, interaction, callback, navigate, toggle, execute, trigger = "click", onEvent) {
14338
14661
  const nativeWasPrevented = interaction.nativeEvent.defaultPrevented;
14339
14662
  const event = {
14663
+ trigger,
14340
14664
  sceneId,
14341
14665
  hotspot,
14342
14666
  interaction,
@@ -14345,15 +14669,21 @@ function handleProjectHotspotClick(hotspot, sceneId, interaction, callback, navi
14345
14669
  event.defaultPrevented = true;
14346
14670
  }
14347
14671
  };
14348
- callback?.(event);
14672
+ onEvent?.(event);
14673
+ if (trigger === "click") callback?.(event);
14349
14674
  if (event.defaultPrevented || !nativeWasPrevented && interaction.nativeEvent.defaultPrevented)
14350
14675
  return;
14351
- if (hotspot.action) navigate(hotspot.action.sceneId);
14352
- else if (isMedia(hotspot)) toggle();
14676
+ const action = hotspotAction(hotspot, trigger);
14677
+ if (action?.type === "scene") navigate(action.sceneId);
14678
+ else if (action) {
14679
+ if (!execute) throw new Error("No handler for hotspot action");
14680
+ return execute(action, event);
14681
+ } else if (trigger === "click" && isMedia(hotspot)) toggle();
14353
14682
  }
14354
14683
  function hotspotProps({
14355
14684
  type: _type,
14356
14685
  action: _action,
14686
+ events: _events,
14357
14687
  label: _label,
14358
14688
  ...props
14359
14689
  }) {
@@ -14363,13 +14693,14 @@ function ProjectHotspotSource({
14363
14693
  hotspot,
14364
14694
  playing,
14365
14695
  onClick,
14696
+ onHoverChange,
14366
14697
  onEnded,
14367
14698
  onError
14368
14699
  }) {
14369
- const common = { ariaLabel: hotspot.label || hotspot.id, onClick };
14700
+ const common = { ariaLabel: hotspot.label || hotspot.id, onClick, onHoverChange };
14370
14701
  switch (hotspot.type) {
14371
14702
  case "image":
14372
- return /* @__PURE__ */ jsx36(
14703
+ return /* @__PURE__ */ jsx37(
14373
14704
  ImageHotspot,
14374
14705
  {
14375
14706
  ...hotspotProps(hotspot),
@@ -14378,7 +14709,7 @@ function ProjectHotspotSource({
14378
14709
  }
14379
14710
  );
14380
14711
  case "graphic":
14381
- return /* @__PURE__ */ jsx36(
14712
+ return /* @__PURE__ */ jsx37(
14382
14713
  GraphicHotspot,
14383
14714
  {
14384
14715
  ...hotspotProps(hotspot),
@@ -14387,9 +14718,9 @@ function ProjectHotspotSource({
14387
14718
  }
14388
14719
  );
14389
14720
  case "text":
14390
- return /* @__PURE__ */ jsx36(TextHotspot, { ...hotspotProps(hotspot), ...common, onError });
14721
+ return /* @__PURE__ */ jsx37(TextHotspot, { ...hotspotProps(hotspot), ...common, onError });
14391
14722
  case "iframe":
14392
- return /* @__PURE__ */ jsx36(
14723
+ return /* @__PURE__ */ jsx37(
14393
14724
  IframeHotspot,
14394
14725
  {
14395
14726
  ...hotspotProps(hotspot),
@@ -14399,11 +14730,11 @@ function ProjectHotspotSource({
14399
14730
  }
14400
14731
  );
14401
14732
  case "polygon":
14402
- return /* @__PURE__ */ jsx36(PolygonHotspot, { ...hotspotProps(hotspot), ...common });
14733
+ return /* @__PURE__ */ jsx37(PolygonHotspot, { ...hotspotProps(hotspot), ...common });
14403
14734
  case "polyline":
14404
- return /* @__PURE__ */ jsx36(PolylineHotspot, { ...hotspotProps(hotspot), ...common });
14735
+ return /* @__PURE__ */ jsx37(PolylineHotspot, { ...hotspotProps(hotspot), ...common });
14405
14736
  case "sequence":
14406
- return /* @__PURE__ */ jsx36(
14737
+ return /* @__PURE__ */ jsx37(
14407
14738
  SequenceHotspot,
14408
14739
  {
14409
14740
  ...hotspotProps(hotspot),
@@ -14414,7 +14745,7 @@ function ProjectHotspotSource({
14414
14745
  }
14415
14746
  );
14416
14747
  case "video":
14417
- return /* @__PURE__ */ jsx36(
14748
+ return /* @__PURE__ */ jsx37(
14418
14749
  VideoHotspot,
14419
14750
  {
14420
14751
  ...hotspotProps(hotspot),
@@ -14426,7 +14757,7 @@ function ProjectHotspotSource({
14426
14757
  }
14427
14758
  );
14428
14759
  case "audio":
14429
- return /* @__PURE__ */ jsx36(
14760
+ return /* @__PURE__ */ jsx37(
14430
14761
  AudioHotspot,
14431
14762
  {
14432
14763
  ...hotspotProps(hotspot),
@@ -14444,10 +14775,12 @@ function ProjectHotspot({
14444
14775
  sceneId,
14445
14776
  navigate,
14446
14777
  onHotspotClick,
14778
+ onHotspotEvent,
14447
14779
  onHotspotError,
14448
- renderHotspot
14780
+ renderHotspot,
14781
+ execute
14449
14782
  }) {
14450
- const [playing, setPlaying] = useState19(
14783
+ const [playing, setPlaying] = useState20(
14451
14784
  "playing" in hotspot ? hotspot.playing ?? false : false
14452
14785
  );
14453
14786
  const togglePlayback = () => setPlaying((value) => !value);
@@ -14455,20 +14788,26 @@ function ProjectHotspot({
14455
14788
  if (isMedia(hotspot)) setPlaying(false);
14456
14789
  onHotspotError?.({ sceneId, hotspot, error });
14457
14790
  };
14458
- const onClick = (interaction) => handleProjectHotspotClick(
14459
- hotspot,
14460
- sceneId,
14461
- interaction,
14462
- onHotspotClick,
14463
- navigate,
14464
- togglePlayback
14465
- );
14466
- const renderDefault = () => /* @__PURE__ */ jsx36(
14791
+ const dispatch = (trigger, interaction) => {
14792
+ if (hotspot.visible === false || hotspot.interactive === false || interaction.source === "pointer" && hotspot.pointerEvents === "none") return;
14793
+ if (trigger !== "click" && (interaction.source !== "pointer" || "pointerType" in interaction.nativeEvent && interaction.nativeEvent.pointerType === "touch")) return;
14794
+ const failed = (error) => onHotspotError?.({ sceneId, hotspot, error, trigger });
14795
+ try {
14796
+ const result = handleProjectHotspotClick(hotspot, sceneId, interaction, onHotspotClick, navigate, togglePlayback, execute, trigger, onHotspotEvent);
14797
+ if (result) void Promise.resolve(result).catch(failed);
14798
+ } catch (error) {
14799
+ failed(error);
14800
+ }
14801
+ };
14802
+ const onClick = (event) => dispatch("click", event);
14803
+ const onHoverChange = (hovered, event) => dispatch(hovered ? "pointerenter" : "pointerleave", event);
14804
+ const renderDefault = () => /* @__PURE__ */ jsx37(
14467
14805
  ProjectHotspotSource,
14468
14806
  {
14469
14807
  hotspot,
14470
14808
  playing,
14471
14809
  onClick,
14810
+ onHoverChange,
14472
14811
  onEnded: () => setPlaying(false),
14473
14812
  onError: reportError
14474
14813
  }
@@ -14477,6 +14816,7 @@ function ProjectHotspot({
14477
14816
  sceneId,
14478
14817
  playing,
14479
14818
  onClick,
14819
+ onHoverChange,
14480
14820
  togglePlayback,
14481
14821
  reportError,
14482
14822
  renderDefault
@@ -14489,25 +14829,28 @@ function ProjectScenes({
14489
14829
  onActiveSceneIdChange,
14490
14830
  onSceneReady,
14491
14831
  onHotspotClick,
14832
+ onHotspotEvent,
14492
14833
  onHotspotError,
14834
+ onPopupRequest,
14835
+ onCustomAction,
14493
14836
  renderHotspot,
14494
14837
  ...options
14495
14838
  }) {
14496
- const controls = useContext21(PanoramaViewContext);
14839
+ const controls = useContext22(PanoramaViewContext);
14497
14840
  if (!controls)
14498
14841
  throw new Error("<ProjectScenes> must be rendered inside <PanoViewer>.");
14499
- const [selection, setSelection] = useState19({
14842
+ const [selection, setSelection] = useState20({
14500
14843
  project,
14501
14844
  id: project.initialSceneId
14502
14845
  });
14503
14846
  if (selection.project !== project)
14504
14847
  setSelection({ project, id: project.initialSceneId });
14505
14848
  const currentId = activeSceneId ?? (selection.project === project ? selection.id : project.initialSceneId);
14506
- const entries = useMemo21(
14849
+ const entries = useMemo22(
14507
14850
  () => new Map(project.scenes.map((entry) => [entry.scene.id, entry])),
14508
14851
  [project]
14509
14852
  );
14510
- const scenes = useMemo21(
14853
+ const scenes = useMemo22(
14511
14854
  () => project.scenes.map((entry) => entry.scene),
14512
14855
  [project]
14513
14856
  );
@@ -14516,7 +14859,7 @@ function ProjectScenes({
14516
14859
  if (activeSceneId === void 0) setSelection({ project, id });
14517
14860
  onActiveSceneIdChange?.(id);
14518
14861
  };
14519
- return /* @__PURE__ */ jsx36(
14862
+ return /* @__PURE__ */ jsx37(
14520
14863
  Scenes,
14521
14864
  {
14522
14865
  ...options,
@@ -14528,12 +14871,27 @@ function ProjectScenes({
14528
14871
  if (view) controls.current?.setView(view, { immediate: true });
14529
14872
  onSceneReady?.(id);
14530
14873
  },
14531
- renderHotspots: (scene) => entries.get(scene.id)?.hotspots.map((hotspot) => /* @__PURE__ */ jsx36(
14874
+ renderHotspots: (scene) => entries.get(scene.id)?.hotspots.map((hotspot) => /* @__PURE__ */ jsx37(
14532
14875
  ProjectHotspot,
14533
14876
  {
14534
14877
  hotspot,
14535
14878
  sceneId: scene.id,
14536
14879
  navigate,
14880
+ execute: (action, event) => {
14881
+ if (action.type === "view") {
14882
+ return controls.current?.lookTo(action.view, { fov: action.view.fov, duration: action.duration }).then(() => {
14883
+ });
14884
+ } else if (action.type === "custom") {
14885
+ if (!onCustomAction) throw new Error("Custom action requires an onCustomAction handler");
14886
+ return onCustomAction({ ...event, action });
14887
+ } else {
14888
+ const popup = project.popups?.find((entry) => entry.id === action.popupId);
14889
+ if (!popup) throw new Error(`Popup does not exist: ${action.popupId}`);
14890
+ if (!onPopupRequest) throw new Error("Popup action requires an onPopupRequest handler");
14891
+ onPopupRequest({ sceneId: scene.id, hotspot, popup });
14892
+ }
14893
+ },
14894
+ onHotspotEvent,
14537
14895
  onHotspotClick,
14538
14896
  onHotspotError,
14539
14897
  renderHotspot
@@ -14545,7 +14903,7 @@ function ProjectScenes({
14545
14903
  }
14546
14904
 
14547
14905
  // src/background-audio.tsx
14548
- import { useContext as useContext22, useEffect as useEffect33, useMemo as useMemo22, useRef as useRef34, useState as useState20 } from "react";
14906
+ import { useContext as useContext23, useEffect as useEffect33, useMemo as useMemo23, useRef as useRef34, useState as useState21 } from "react";
14549
14907
  var DEFAULT_FADE_MS = 400;
14550
14908
  function resolveFadeMs(value) {
14551
14909
  if (!Number.isFinite(value)) {
@@ -14587,12 +14945,12 @@ function BackgroundAudio({
14587
14945
  onEnded,
14588
14946
  onError
14589
14947
  }) {
14590
- const controlsRef = useContext22(PanoramaViewContext);
14948
+ const controlsRef = useContext23(PanoramaViewContext);
14591
14949
  if (!controlsRef) {
14592
14950
  throw new Error("<BackgroundAudio> must be rendered inside <PanoViewer>.");
14593
14951
  }
14594
14952
  const isControlled = controlledPlaying !== void 0;
14595
- const [internalPlaying, setInternalPlaying] = useState20(defaultPlaying);
14953
+ const [internalPlaying, setInternalPlaying] = useState21(defaultPlaying);
14596
14954
  const playing = isControlled ? controlledPlaying : internalPlaying;
14597
14955
  const urls = resolveBackgroundAudioUrls(src, sources, sceneId);
14598
14956
  usePanoMediaActivationIntent(playing && urls.length > 0);
@@ -14645,7 +15003,7 @@ function BackgroundAudio({
14645
15003
  };
14646
15004
  const isControlledRef = useRef34(isControlled);
14647
15005
  isControlledRef.current = isControlled;
14648
- const controller = useMemo22(
15006
+ const controller = useMemo23(
14649
15007
  () => ({
14650
15008
  subscribe: (onStoreChange) => {
14651
15009
  snapshotListenersRef.current.add(onStoreChange);
@@ -14703,7 +15061,7 @@ function BackgroundAudio({
14703
15061
  }),
14704
15062
  []
14705
15063
  );
14706
- const bgmHost = useContext22(BackgroundAudioHostContext);
15064
+ const bgmHost = useContext23(BackgroundAudioHostContext);
14707
15065
  useEffect33(() => {
14708
15066
  if (!bgmHost) {
14709
15067
  return;
@@ -14893,7 +15251,7 @@ function BackgroundAudio({
14893
15251
  }
14894
15252
 
14895
15253
  // src/pano-filter/pano-filter.tsx
14896
- import { useContext as useContext23, useEffect as useEffect34, useMemo as useMemo23 } from "react";
15254
+ import { useContext as useContext24, useEffect as useEffect34, useMemo as useMemo24 } from "react";
14897
15255
  function PanoFilter({
14898
15256
  preset = "none",
14899
15257
  intensity,
@@ -14901,11 +15259,11 @@ function PanoFilter({
14901
15259
  colorMatrix,
14902
15260
  colorMatrixScope
14903
15261
  }) {
14904
- const host = useContext23(PanoFilterHostContext);
15262
+ const host = useContext24(PanoFilterHostContext);
14905
15263
  if (!host) {
14906
15264
  throw new Error("<PanoFilter> must be rendered inside <PanoViewer>.");
14907
15265
  }
14908
- const snapshot = useMemo23(
15266
+ const snapshot = useMemo24(
14909
15267
  () => resolvePanoFilterSnapshot({
14910
15268
  preset,
14911
15269
  intensity,
@@ -14932,11 +15290,11 @@ import { useFrame as useFrame18, useThree as useThree25 } from "@react-three/fib
14932
15290
  import {
14933
15291
  forwardRef as forwardRef3,
14934
15292
  useCallback as useCallback9,
14935
- useContext as useContext24,
15293
+ useContext as useContext25,
14936
15294
  useEffect as useEffect35,
14937
15295
  useImperativeHandle as useImperativeHandle3,
14938
15296
  useRef as useRef35,
14939
- useState as useState21
15297
+ useState as useState22
14940
15298
  } from "react";
14941
15299
 
14942
15300
  // src/gyro-orientation.ts
@@ -15037,10 +15395,10 @@ var Gyro = forwardRef3(function Gyro2({
15037
15395
  onDisable,
15038
15396
  onDenied
15039
15397
  }, ref) {
15040
- const controlsRef = useContext24(PanoramaViewContext);
15041
- const eventBus = useContext24(PanoEventBusContext);
15398
+ const controlsRef = useContext25(PanoramaViewContext);
15399
+ const eventBus = useContext25(PanoEventBusContext);
15042
15400
  const { gl } = useThree25();
15043
- const [internallyDisabled, setInternallyDisabled] = useState21(false);
15401
+ const [internallyDisabled, setInternallyDisabled] = useState22(false);
15044
15402
  const availableRef = useRef35(false);
15045
15403
  const availabilityReportedRef = useRef35(null);
15046
15404
  const activeRef = useRef35(false);
@@ -15393,12 +15751,12 @@ import { useThree as useThree28 } from "@react-three/fiber";
15393
15751
  import {
15394
15752
  forwardRef as forwardRef4,
15395
15753
  useCallback as useCallback10,
15396
- useContext as useContext26,
15754
+ useContext as useContext27,
15397
15755
  useEffect as useEffect39,
15398
15756
  useImperativeHandle as useImperativeHandle4,
15399
- useMemo as useMemo25,
15757
+ useMemo as useMemo26,
15400
15758
  useRef as useRef39,
15401
- useState as useState22
15759
+ useState as useState23
15402
15760
  } from "react";
15403
15761
 
15404
15762
  // src/webvr/detect.ts
@@ -15429,7 +15787,7 @@ import {
15429
15787
  Vector2 as Vector26,
15430
15788
  Vector3 as Vector320
15431
15789
  } from "three";
15432
- import { jsx as jsx37, jsxs as jsxs19 } from "react/jsx-runtime";
15790
+ import { jsx as jsx38, jsxs as jsxs19 } from "react/jsx-runtime";
15433
15791
  var CENTER_NDC = new Vector26(0, 0);
15434
15792
  var HUD_DISTANCE = 2;
15435
15793
  var RETICLE_SCALE = 1;
@@ -15604,8 +15962,8 @@ function WebVRReticle({
15604
15962
  raycast: noopRaycast,
15605
15963
  renderOrder: RETICLE_RENDER_ORDER + 1,
15606
15964
  children: [
15607
- /* @__PURE__ */ jsx37("boxGeometry", { args: [CROSSHAIR_LENGTH, CROSSHAIR_THICKNESS, 1e-3] }),
15608
- /* @__PURE__ */ jsx37(
15965
+ /* @__PURE__ */ jsx38("boxGeometry", { args: [CROSSHAIR_LENGTH, CROSSHAIR_THICKNESS, 1e-3] }),
15966
+ /* @__PURE__ */ jsx38(
15609
15967
  "meshBasicMaterial",
15610
15968
  {
15611
15969
  color: "#ffffff",
@@ -15625,8 +15983,8 @@ function WebVRReticle({
15625
15983
  raycast: noopRaycast,
15626
15984
  renderOrder: RETICLE_RENDER_ORDER + 1,
15627
15985
  children: [
15628
- /* @__PURE__ */ jsx37("boxGeometry", { args: [CROSSHAIR_THICKNESS, CROSSHAIR_LENGTH, 1e-3] }),
15629
- /* @__PURE__ */ jsx37(
15986
+ /* @__PURE__ */ jsx38("boxGeometry", { args: [CROSSHAIR_THICKNESS, CROSSHAIR_LENGTH, 1e-3] }),
15987
+ /* @__PURE__ */ jsx38(
15630
15988
  "meshBasicMaterial",
15631
15989
  {
15632
15990
  color: "#ffffff",
@@ -15648,7 +16006,7 @@ function WebVRReticle({
15648
16006
  renderOrder: RETICLE_RENDER_ORDER + 2,
15649
16007
  visible: false,
15650
16008
  children: [
15651
- /* @__PURE__ */ jsx37(
16009
+ /* @__PURE__ */ jsx38(
15652
16010
  "ringGeometry",
15653
16011
  {
15654
16012
  args: [
@@ -15661,7 +16019,7 @@ function WebVRReticle({
15661
16019
  ]
15662
16020
  }
15663
16021
  ),
15664
- /* @__PURE__ */ jsx37(
16022
+ /* @__PURE__ */ jsx38(
15665
16023
  "meshBasicMaterial",
15666
16024
  {
15667
16025
  color: "#ffffff",
@@ -15682,7 +16040,7 @@ function WebVRReticle({
15682
16040
 
15683
16041
  // src/webvr/stereo.tsx
15684
16042
  import { useFrame as useFrame20, useThree as useThree27 } from "@react-three/fiber";
15685
- import { useContext as useContext25, useEffect as useEffect36, useMemo as useMemo24, useRef as useRef37 } from "react";
16043
+ import { useContext as useContext26, useEffect as useEffect36, useMemo as useMemo25, useRef as useRef37 } from "react";
15686
16044
  import {
15687
16045
  Mesh as Mesh6,
15688
16046
  OrthographicCamera,
@@ -15768,7 +16126,7 @@ function WebVRStereoRenderer({
15768
16126
  screensize
15769
16127
  }) {
15770
16128
  const { camera, gl } = useThree27();
15771
- const runtime = useContext25(WebVRRuntimeContext);
16129
+ const runtime = useContext26(WebVRRuntimeContext);
15772
16130
  const fallbackStereoRef = useRef37(null);
15773
16131
  if (!fallbackStereoRef.current) {
15774
16132
  fallbackStereoRef.current = new StereoCamera2();
@@ -15776,7 +16134,7 @@ function WebVRStereoRenderer({
15776
16134
  const stereo = runtime?.stereoView.stereo ?? fallbackStereoRef.current;
15777
16135
  const stereoRef = useRef37(stereo);
15778
16136
  stereoRef.current = stereo;
15779
- const resources = useMemo24(createDistortionResources, []);
16137
+ const resources = useMemo25(createDistortionResources, []);
15780
16138
  const drawingSizeRef = useRef37(new Vector27());
15781
16139
  const viewportRef = useRef37(new Vector42());
15782
16140
  const scissorRef = useRef37(new Vector42());
@@ -16108,7 +16466,7 @@ function useWebVRWakeLock(enabled) {
16108
16466
  }
16109
16467
 
16110
16468
  // src/webvr/webvr.tsx
16111
- import { Fragment as Fragment6, jsx as jsx38, jsxs as jsxs20 } from "react/jsx-runtime";
16469
+ import { Fragment as Fragment6, jsx as jsx39, jsxs as jsxs20 } from "react/jsx-runtime";
16112
16470
  function initializeSettings(screensize, profile) {
16113
16471
  const stored = loadWebVRSettings();
16114
16472
  return {
@@ -16137,9 +16495,9 @@ var WebVR = forwardRef4(function WebVR2({
16137
16495
  onDenied,
16138
16496
  onUnknownDevice
16139
16497
  }, ref) {
16140
- const runtime = useContext26(WebVRRuntimeContext);
16141
- const controlsRef = useContext26(PanoramaViewContext);
16142
- const eventBus = useContext26(PanoEventBusContext);
16498
+ const runtime = useContext27(WebVRRuntimeContext);
16499
+ const controlsRef = useContext27(PanoramaViewContext);
16500
+ const eventBus = useContext27(PanoEventBusContext);
16143
16501
  const gl = useThree28((state) => state.gl);
16144
16502
  const gyroRef = useRef39(null);
16145
16503
  const capabilitiesRef = useRef39({
@@ -16150,8 +16508,8 @@ var WebVR = forwardRef4(function WebVR2({
16150
16508
  const availableRef = useRef39(false);
16151
16509
  const modeRef = useRef39(null);
16152
16510
  const webXRSessionRef = useRef39(null);
16153
- const [mode, setMode] = useState22(null);
16154
- const [settings, setSettings] = useState22(
16511
+ const [mode, setMode] = useState23(null);
16512
+ const [settings, setSettings] = useState23(
16155
16513
  () => initializeSettings(screensize, profile)
16156
16514
  );
16157
16515
  const callbacksRef = useRef39({
@@ -16174,7 +16532,7 @@ var WebVR = forwardRef4(function WebVR2({
16174
16532
  throw new Error("<WebVR> must be rendered inside <PanoViewer>.");
16175
16533
  }
16176
16534
  const { host, xrStore } = runtime;
16177
- const resolvedProfile = useMemo25(
16535
+ const resolvedProfile = useMemo26(
16178
16536
  () => resolveWebVRProfile(profile, settings.profileId),
16179
16537
  [profile, settings.profileId]
16180
16538
  );
@@ -16339,7 +16697,7 @@ var WebVR = forwardRef4(function WebVR2({
16339
16697
  setSettings(normalized);
16340
16698
  saveWebVRSettings(normalized);
16341
16699
  }, []);
16342
- const controller = useMemo25(
16700
+ const controller = useMemo26(
16343
16701
  () => ({
16344
16702
  closeSetup: () => updateWebVRHost(host, { setupOpen: false }),
16345
16703
  enterVR,
@@ -16442,7 +16800,7 @@ var WebVR = forwardRef4(function WebVR2({
16442
16800
  wakelock && (mode === "mobilevr" || mode === "fake")
16443
16801
  );
16444
16802
  return /* @__PURE__ */ jsxs20(Fragment6, { children: [
16445
- /* @__PURE__ */ jsx38(
16803
+ /* @__PURE__ */ jsx39(
16446
16804
  Gyro,
16447
16805
  {
16448
16806
  ref: gyroRef,
@@ -16456,7 +16814,7 @@ var WebVR = forwardRef4(function WebVR2({
16456
16814
  touchMode: "off"
16457
16815
  }
16458
16816
  ),
16459
- /* @__PURE__ */ jsx38(
16817
+ /* @__PURE__ */ jsx39(
16460
16818
  WebVRStereoRenderer,
16461
16819
  {
16462
16820
  mode,
@@ -16464,7 +16822,7 @@ var WebVR = forwardRef4(function WebVR2({
16464
16822
  screensize: settings.screensize
16465
16823
  }
16466
16824
  ),
16467
- /* @__PURE__ */ jsx38(
16825
+ /* @__PURE__ */ jsx39(
16468
16826
  WebVRReticle,
16469
16827
  {
16470
16828
  dwellMs: cursorDwellMs,
@@ -16475,7 +16833,7 @@ var WebVR = forwardRef4(function WebVR2({
16475
16833
  });
16476
16834
 
16477
16835
  // src/use-pano-viewer.ts
16478
- import { useMemo as useMemo26, useRef as useRef40 } from "react";
16836
+ import { useMemo as useMemo27, useRef as useRef40 } from "react";
16479
16837
  function createPanoViewerHandle(ref) {
16480
16838
  return {
16481
16839
  getView: () => ref.current?.getView() ?? { yaw: 0, pitch: 0, fov: 75 },
@@ -16558,20 +16916,20 @@ function createPanoViewerHandle(ref) {
16558
16916
  }
16559
16917
  function usePanoViewer() {
16560
16918
  const ref = useRef40(null);
16561
- const handle = useMemo26(() => createPanoViewerHandle(ref), []);
16562
- return useMemo26(() => ({ ref, ...handle }), [handle]);
16919
+ const handle = useMemo27(() => createPanoViewerHandle(ref), []);
16920
+ return useMemo27(() => ({ ref, ...handle }), [handle]);
16563
16921
  }
16564
16922
 
16565
16923
  // src/video/pano-video.tsx
16566
16924
  import { useThree as useThree29 } from "@react-three/fiber";
16567
16925
  import {
16568
16926
  useCallback as useCallback11,
16569
- useContext as useContext27,
16927
+ useContext as useContext28,
16570
16928
  useEffect as useEffect40,
16571
- useLayoutEffect as useLayoutEffect7,
16572
- useMemo as useMemo27,
16929
+ useLayoutEffect as useLayoutEffect9,
16930
+ useMemo as useMemo28,
16573
16931
  useRef as useRef41,
16574
- useState as useState23
16932
+ useState as useState24
16575
16933
  } from "react";
16576
16934
  import {
16577
16935
  BackSide as BackSide4,
@@ -16583,7 +16941,7 @@ import {
16583
16941
  TextureLoader as TextureLoader8,
16584
16942
  VideoTexture as VideoTexture2
16585
16943
  } from "three";
16586
- import { Fragment as Fragment7, jsx as jsx39, jsxs as jsxs21 } from "react/jsx-runtime";
16944
+ import { Fragment as Fragment7, jsx as jsx40, jsxs as jsxs21 } from "react/jsx-runtime";
16587
16945
  var EMPTY_TRACKS = [];
16588
16946
  var EMPTY_RATES = DEFAULT_PANO_VIDEO_PLAYBACK_RATES;
16589
16947
  function createEmptySnapshot() {
@@ -16668,7 +17026,7 @@ function applyEquirectangularLayout(texture, anisotropy) {
16668
17026
  }
16669
17027
  function usePosterTexture2(poster, onError) {
16670
17028
  const gl = useThree29((state) => state.gl);
16671
- const [texture, setTexture] = useState23(null);
17029
+ const [texture, setTexture] = useState24(null);
16672
17030
  const onErrorRef = useRef41(onError);
16673
17031
  onErrorRef.current = onError;
16674
17032
  useEffect40(() => {
@@ -16748,7 +17106,7 @@ function PanoVideo({
16748
17106
  tracks: tracksProp,
16749
17107
  captions = true,
16750
17108
  controls = true,
16751
- yawOffset = 0,
17109
+ yawOffset: yawOffset2 = 0,
16752
17110
  loop = false,
16753
17111
  muted = true,
16754
17112
  volume = 1,
@@ -16767,26 +17125,26 @@ function PanoVideo({
16767
17125
  }) {
16768
17126
  usePanoMediaActivationIntent(autoPlay);
16769
17127
  const gl = useThree29((state) => state.gl);
16770
- const host = useContext27(PanoVideoHostContext);
16771
- const variants = useMemo27(
17128
+ const host = useContext28(PanoVideoHostContext);
17129
+ const variants = useMemo28(
16772
17130
  () => resolvePanoVideoVariants({ poster, src, variants: variantsProp }),
16773
17131
  [poster, src, variantsProp]
16774
17132
  );
16775
17133
  const tracks = tracksProp ?? EMPTY_TRACKS;
16776
17134
  const captionsEnabled = captions !== false;
16777
- const captionAppearance = useMemo27(
17135
+ const captionAppearance = useMemo28(
16778
17136
  () => resolvePanoVideoCaptionAppearance(
16779
17137
  typeof captions === "object" ? captions : void 0
16780
17138
  ),
16781
17139
  [captions]
16782
17140
  );
16783
- const [variantId, setVariantId] = useState23(
17141
+ const [variantId, setVariantId] = useState24(
16784
17142
  () => resolvePanoVideoVariantId(variants, defaultVariantId)
16785
17143
  );
16786
- const [trackId, setTrackId] = useState23(
17144
+ const [trackId, setTrackId] = useState24(
16787
17145
  () => captionsEnabled ? defaultPanoVideoTrackId(tracks) : null
16788
17146
  );
16789
- const [ready, setReady] = useState23(false);
17147
+ const [ready, setReady] = useState24(false);
16790
17148
  const autoPlayRef = useRef41(autoPlay);
16791
17149
  autoPlayRef.current = autoPlay;
16792
17150
  const blockedRef = useRef41(false);
@@ -16839,7 +17197,7 @@ function PanoVideo({
16839
17197
  onEndedRef.current = onEnded;
16840
17198
  onVariantChangeRef.current = onVariantChange;
16841
17199
  onTrackChangeRef.current = onTrackChange;
16842
- const resource = useMemo27(() => {
17200
+ const resource = useMemo28(() => {
16843
17201
  if (typeof document === "undefined") {
16844
17202
  return null;
16845
17203
  }
@@ -16875,7 +17233,7 @@ function PanoVideo({
16875
17233
  }
16876
17234
  resource.texture.anisotropy = gl.capabilities.getMaxAnisotropy();
16877
17235
  }, [gl, resource]);
16878
- useLayoutEffect7(() => {
17236
+ useLayoutEffect9(() => {
16879
17237
  if (!resource) {
16880
17238
  return;
16881
17239
  }
@@ -16943,7 +17301,7 @@ function PanoVideo({
16943
17301
  captionAppearance
16944
17302
  ]
16945
17303
  );
16946
- const controller = useMemo27(
17304
+ const controller = useMemo28(
16947
17305
  () => ({
16948
17306
  subscribe: (onStoreChange) => {
16949
17307
  const store = storeRef.current;
@@ -16995,7 +17353,7 @@ function PanoVideo({
16995
17353
  }),
16996
17354
  []
16997
17355
  );
16998
- useLayoutEffect7(() => {
17356
+ useLayoutEffect9(() => {
16999
17357
  if (!host || variants.length === 0) {
17000
17358
  return;
17001
17359
  }
@@ -17008,7 +17366,7 @@ function PanoVideo({
17008
17366
  notifyPanoVideoHost(host);
17009
17367
  };
17010
17368
  }, [captionsEnabled, controller, controls, host, variants.length]);
17011
- useLayoutEffect7(() => {
17369
+ useLayoutEffect9(() => {
17012
17370
  if (!resource) {
17013
17371
  return;
17014
17372
  }
@@ -17173,7 +17531,7 @@ function PanoVideo({
17173
17531
  }
17174
17532
  };
17175
17533
  }, [emitSnapshot, resource]);
17176
- useLayoutEffect7(() => {
17534
+ useLayoutEffect9(() => {
17177
17535
  if (!resource) {
17178
17536
  return;
17179
17537
  }
@@ -17286,11 +17644,11 @@ function PanoVideo({
17286
17644
  activeTexture ? /* @__PURE__ */ jsxs21(
17287
17645
  "mesh",
17288
17646
  {
17289
- rotation: [0, MathUtils12.degToRad(90 + yawOffset), 0],
17647
+ rotation: [0, MathUtils12.degToRad(90 + yawOffset2), 0],
17290
17648
  visible,
17291
17649
  children: [
17292
- /* @__PURE__ */ jsx39("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
17293
- /* @__PURE__ */ jsx39(
17650
+ /* @__PURE__ */ jsx40("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
17651
+ /* @__PURE__ */ jsx40(
17294
17652
  PanoBasicMaterial,
17295
17653
  {
17296
17654
  map: activeTexture,
@@ -17301,7 +17659,7 @@ function PanoVideo({
17301
17659
  ]
17302
17660
  }
17303
17661
  ) : null,
17304
- captionsEnabled ? /* @__PURE__ */ jsx39(PanoVideoCaptions, { appearance: typeof captions === "object" ? captions : void 0 }) : null
17662
+ captionsEnabled ? /* @__PURE__ */ jsx40(PanoVideoCaptions, { appearance: typeof captions === "object" ? captions : void 0 }) : null
17305
17663
  ] });
17306
17664
  }
17307
17665
  export {
@@ -17328,6 +17686,7 @@ export {
17328
17686
  PanoContextMenu,
17329
17687
  PanoEvents,
17330
17688
  PanoFilter,
17689
+ PanoOverlayPortalProvider,
17331
17690
  PanoVideo,
17332
17691
  PanoVideoControls,
17333
17692
  PanoViewer,
@@ -17356,6 +17715,8 @@ export {
17356
17715
  resolvePanoContextMenuEntries,
17357
17716
  unwrapPolygonVertices,
17358
17717
  usePanoEvents,
17718
+ usePanoModal,
17719
+ usePanoOverlay,
17359
17720
  usePanoViewer,
17360
17721
  validatePolygonVertices,
17361
17722
  validatePolylineVertices,