@ericchen1990/pano-view 2.3.0 → 2.4.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
@@ -2252,6 +2252,29 @@ import {
2252
2252
  useState as useState5
2253
2253
  } from "react";
2254
2254
 
2255
+ // src/background-audio-host.ts
2256
+ import { createContext } from "react";
2257
+ var BackgroundAudioHostContext = createContext(null);
2258
+ function createBackgroundAudioHost() {
2259
+ return {
2260
+ controller: null,
2261
+ revision: 0,
2262
+ listeners: /* @__PURE__ */ new Set()
2263
+ };
2264
+ }
2265
+ function notifyBackgroundAudioHost(host) {
2266
+ host.revision += 1;
2267
+ for (const listener of host.listeners) {
2268
+ listener();
2269
+ }
2270
+ }
2271
+ function subscribeBackgroundAudioHost(host, listener) {
2272
+ host.listeners.add(listener);
2273
+ return () => {
2274
+ host.listeners.delete(listener);
2275
+ };
2276
+ }
2277
+
2255
2278
  // src/cn.ts
2256
2279
  function cn(...values) {
2257
2280
  return values.filter(Boolean).join(" ");
@@ -2262,7 +2285,7 @@ import { useFrame as useFrame2 } from "@react-three/fiber";
2262
2285
  import { useContext, useEffect as useEffect2, useRef as useRef2 } from "react";
2263
2286
 
2264
2287
  // src/pano-event-bus.ts
2265
- import { createContext } from "react";
2288
+ import { createContext as createContext2 } from "react";
2266
2289
  function createPanoEventBus() {
2267
2290
  const listeners = /* @__PURE__ */ new Map();
2268
2291
  return {
@@ -2291,13 +2314,13 @@ function createPanoEventBus() {
2291
2314
  }
2292
2315
  };
2293
2316
  }
2294
- var PanoEventBusContext = createContext(null);
2317
+ var PanoEventBusContext = createContext2(null);
2295
2318
 
2296
2319
  // src/panorama-view-runtime.tsx
2297
2320
  import { useFrame, useThree } from "@react-three/fiber";
2298
2321
  import { useXR } from "@react-three/xr";
2299
2322
  import {
2300
- createContext as createContext2,
2323
+ createContext as createContext3,
2301
2324
  forwardRef,
2302
2325
  useEffect,
2303
2326
  useImperativeHandle,
@@ -2313,7 +2336,7 @@ var DEFAULT_ROTATE_DAMPING = 14;
2313
2336
  var DEFAULT_ZOOM_DAMPING = 16;
2314
2337
  var DEFAULT_FRICTION_STOP = 0.01;
2315
2338
  var VIEW_SETTLE_EPSILON = 1e-3;
2316
- var PanoramaViewContext = createContext2(null);
2339
+ var PanoramaViewContext = createContext3(null);
2317
2340
  function clamp(value, min, max) {
2318
2341
  return Math.max(min, Math.min(max, value));
2319
2342
  }
@@ -2790,9 +2813,9 @@ function AutoRotate({
2790
2813
  }
2791
2814
 
2792
2815
  // src/control-claims.ts
2793
- import { createContext as createContext3, useContext as useContext2 } from "react";
2794
- var ControlClaimsContext = createContext3(null);
2795
- var DefaultControlChannelContext = createContext3(false);
2816
+ import { createContext as createContext4, useContext as useContext2 } from "react";
2817
+ var ControlClaimsContext = createContext4(null);
2818
+ var DefaultControlChannelContext = createContext4(false);
2796
2819
  var CHANNEL_COMPONENT = {
2797
2820
  mouse: "MouseControls",
2798
2821
  touch: "TouchControls",
@@ -2874,7 +2897,7 @@ function subscribeFullscreenChange(onChange) {
2874
2897
 
2875
2898
  // src/hotspot/accessibility.tsx
2876
2899
  import {
2877
- createContext as createContext4,
2900
+ createContext as createContext5,
2878
2901
  useCallback,
2879
2902
  useContext as useContext3,
2880
2903
  useEffect as useEffect3,
@@ -2883,7 +2906,7 @@ import {
2883
2906
  useState
2884
2907
  } from "react";
2885
2908
  import { jsx } from "react/jsx-runtime";
2886
- var HotspotAccessibilityContext = createContext4(null);
2909
+ var HotspotAccessibilityContext = createContext5(null);
2887
2910
  function warnAboutMissingLabel(id) {
2888
2911
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
2889
2912
  if (nodeEnv !== "production") {
@@ -3393,7 +3416,7 @@ function MouseControls({
3393
3416
  }
3394
3417
 
3395
3418
  // src/pano-filter/host.ts
3396
- import { createContext as createContext5, useContext as useContext6, useSyncExternalStore } from "react";
3419
+ import { createContext as createContext6, useContext as useContext6, useSyncExternalStore } from "react";
3397
3420
 
3398
3421
  // src/pano-filter/presets.ts
3399
3422
  var PANO_FILTER_PRESETS = [
@@ -3434,7 +3457,7 @@ function isPanoFilterActive(snapshot) {
3434
3457
  }
3435
3458
 
3436
3459
  // src/pano-filter/host.ts
3437
- var PanoFilterHostContext = createContext5(null);
3460
+ var PanoFilterHostContext = createContext6(null);
3438
3461
  function createPanoFilterHost() {
3439
3462
  return {
3440
3463
  snapshot: { ...DEFAULT_PANO_FILTER_SNAPSHOT },
@@ -3504,7 +3527,7 @@ function isProduction2() {
3504
3527
 
3505
3528
  // src/pano-context-menu.tsx
3506
3529
  import {
3507
- createContext as createContext6,
3530
+ createContext as createContext7,
3508
3531
  useCallback as useCallback2,
3509
3532
  useContext as useContext7,
3510
3533
  useEffect as useEffect6,
@@ -3518,7 +3541,7 @@ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
3518
3541
  var DEFAULT_ICON_SIZE = 16;
3519
3542
  var MENU_EDGE_PADDING = 8;
3520
3543
  var MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY = 0.4;
3521
- var PanoContextMenuOverlayContext = createContext6(null);
3544
+ var PanoContextMenuOverlayContext = createContext7(null);
3522
3545
  var DEFAULT_APPEARANCE = {
3523
3546
  background: "#161616",
3524
3547
  color: "#f4f4f4",
@@ -4183,12 +4206,12 @@ function composePanoContextMenuItems(props, presets) {
4183
4206
  ...resolvePanoContextMenuEntries(props.append ?? [], presets)
4184
4207
  ];
4185
4208
  }
4186
- var PanoContextMenuActionsContext = createContext6(null);
4209
+ var PanoContextMenuActionsContext = createContext7(null);
4187
4210
 
4188
4211
  // src/pano-cursor.tsx
4189
4212
  import { useThree as useThree4 } from "@react-three/fiber";
4190
4213
  import {
4191
- createContext as createContext7,
4214
+ createContext as createContext8,
4192
4215
  useCallback as useCallback3,
4193
4216
  useContext as useContext8,
4194
4217
  useEffect as useEffect7,
@@ -4209,7 +4232,7 @@ var LAYER_PRIORITY = {
4209
4232
  hotspotDragging: 3,
4210
4233
  hidden: 4
4211
4234
  };
4212
- var PanoCursorContext = createContext7(null);
4235
+ var PanoCursorContext = createContext8(null);
4213
4236
  function usePanoCursor() {
4214
4237
  return useContext8(PanoCursorContext);
4215
4238
  }
@@ -4783,6 +4806,23 @@ function PanoramaEventSurface() {
4783
4806
  );
4784
4807
  }
4785
4808
 
4809
+ // src/scenes-host.ts
4810
+ import { createContext as createContext9 } from "react";
4811
+ var ScenesHostContext = createContext9(null);
4812
+ function createScenesHost() {
4813
+ return {
4814
+ controller: null,
4815
+ revision: 0,
4816
+ listeners: /* @__PURE__ */ new Set()
4817
+ };
4818
+ }
4819
+ function notifyScenesHost(host) {
4820
+ host.revision += 1;
4821
+ for (const listener of host.listeners) {
4822
+ listener();
4823
+ }
4824
+ }
4825
+
4786
4826
  // src/touch-controls.tsx
4787
4827
  import { useThree as useThree7 } from "@react-three/fiber";
4788
4828
  import { useContext as useContext11, useEffect as useEffect10, useRef as useRef9 } from "react";
@@ -4931,9 +4971,9 @@ function TouchControls({
4931
4971
  }
4932
4972
 
4933
4973
  // src/video/chrome-overlay.tsx
4934
- import { createContext as createContext8, useCallback as useCallback4, useMemo as useMemo5, useState as useState3 } from "react";
4974
+ import { createContext as createContext10, useCallback as useCallback4, useMemo as useMemo5, useState as useState3 } from "react";
4935
4975
  import { jsx as jsx5 } from "react/jsx-runtime";
4936
- var PanoChromeOverlayContext = createContext8(null);
4976
+ var PanoChromeOverlayContext = createContext10(null);
4937
4977
  function usePanoChromeOverlay() {
4938
4978
  const [overlayElement, setOverlayElement] = useState3(
4939
4979
  null
@@ -4957,9 +4997,9 @@ function usePanoChromeOverlay() {
4957
4997
  }
4958
4998
 
4959
4999
  // src/video/host.ts
4960
- import { createContext as createContext9, useContext as useContext12 } from "react";
4961
- var PanoVideoHostContext = createContext9(null);
4962
- var DefaultPanoVideoControlsContext = createContext9(false);
5000
+ import { createContext as createContext11, useContext as useContext12 } from "react";
5001
+ var PanoVideoHostContext = createContext11(null);
5002
+ var DefaultPanoVideoControlsContext = createContext11(false);
4963
5003
  function createPanoVideoHost() {
4964
5004
  return {
4965
5005
  controller: null,
@@ -6216,8 +6256,8 @@ function PanoVideoChromeBridge({
6216
6256
  import { useSyncExternalStore as useSyncExternalStore5 } from "react";
6217
6257
 
6218
6258
  // src/webvr/host.ts
6219
- import { createContext as createContext10 } from "react";
6220
- var WebVRRuntimeContext = createContext10(null);
6259
+ import { createContext as createContext12 } from "react";
6260
+ var WebVRRuntimeContext = createContext12(null);
6221
6261
  function createWebVRHost() {
6222
6262
  return {
6223
6263
  controller: null,
@@ -6698,6 +6738,8 @@ var PanoViewer = forwardRef2(
6698
6738
  const videoHost = useMemo7(() => createPanoVideoHost(), []);
6699
6739
  const filterHost = useMemo7(() => createPanoFilterHost(), []);
6700
6740
  const webVRHost = useMemo7(() => createWebVRHost(), []);
6741
+ const scenesHost = useMemo7(() => createScenesHost(), []);
6742
+ const bgmHost = useMemo7(() => createBackgroundAudioHost(), []);
6701
6743
  const stereoView = useMemo7(() => createWebVRStereoView(), []);
6702
6744
  const xrStore = useMemo7(
6703
6745
  () => createXRStore({
@@ -6781,6 +6823,28 @@ var PanoViewer = forwardRef2(
6781
6823
  }),
6782
6824
  [controlOptions.keyboard]
6783
6825
  );
6826
+ const enterFullscreen = useCallback6(async () => {
6827
+ if (typeof document === "undefined") {
6828
+ return;
6829
+ }
6830
+ if (getFullscreenElement()) {
6831
+ return;
6832
+ }
6833
+ const root = rootRef.current;
6834
+ if (!root) {
6835
+ return;
6836
+ }
6837
+ await requestElementFullscreen(root);
6838
+ }, []);
6839
+ const exitFullscreen = useCallback6(async () => {
6840
+ if (typeof document === "undefined") {
6841
+ return;
6842
+ }
6843
+ if (!getFullscreenElement()) {
6844
+ return;
6845
+ }
6846
+ await exitElementFullscreen();
6847
+ }, []);
6784
6848
  const toggleFullscreen = useCallback6(async () => {
6785
6849
  if (typeof document === "undefined") {
6786
6850
  return;
@@ -6854,9 +6918,12 @@ var PanoViewer = forwardRef2(
6854
6918
  useEffect12(() => {
6855
6919
  setLegacyAutoRotate(legacyAutoRotateOptions.autoRotate ?? false);
6856
6920
  }, [legacyAutoRotateOptions.autoRotate]);
6921
+ const isFullscreenRef = useRef12(false);
6922
+ isFullscreenRef.current = isViewerFullscreen;
6857
6923
  useImperativeHandle2(
6858
6924
  ref,
6859
6925
  () => ({
6926
+ // -- View --
6860
6927
  getView: () => controlsRef.current?.getView() ?? { ...fallbackViewRef.current },
6861
6928
  setView: (view, options) => {
6862
6929
  controlsRef.current?.setView(view, options);
@@ -6864,15 +6931,65 @@ var PanoViewer = forwardRef2(
6864
6931
  reset: () => {
6865
6932
  controlsRef.current?.reset();
6866
6933
  },
6934
+ // -- Fullscreen --
6935
+ enterFullscreen,
6936
+ exitFullscreen,
6937
+ toggleFullscreen,
6938
+ isFullscreen: () => isFullscreenRef.current,
6939
+ // -- Scenes --
6940
+ setScene: (id) => scenesHost.controller?.setScene(id) ?? false,
6941
+ nextScene: () => scenesHost.controller?.nextScene() ?? false,
6942
+ previousScene: () => scenesHost.controller?.previousScene() ?? false,
6943
+ getActiveSceneId: () => scenesHost.controller?.getActiveSceneId() ?? null,
6944
+ getSceneIds: () => scenesHost.controller?.getSceneIds() ?? [],
6945
+ isSceneTransitioning: () => scenesHost.controller?.isTransitioning() ?? false,
6946
+ // -- WebVR --
6947
+ enterVR: () => webVRHost.controller?.enterVR() ?? Promise.resolve(false),
6948
+ exitVR: () => webVRHost.controller?.exitVR() ?? Promise.resolve(),
6949
+ toggleVR: () => webVRHost.controller?.toggleVR() ?? Promise.resolve(false),
6950
+ isVRAvailable: () => webVRHost.snapshot.available,
6951
+ isVREnabled: () => webVRHost.snapshot.mode !== null,
6952
+ getVRMode: () => webVRHost.snapshot.mode,
6953
+ requestVRPermission: () => webVRHost.controller?.requestPermission() ?? Promise.resolve(false),
6954
+ // -- Video --
6955
+ getVideo: () => videoHost.controller,
6956
+ subscribeVideo: (onStoreChange) => subscribePanoVideoHost(videoHost, onStoreChange),
6957
+ playVideo: () => videoHost.controller?.play() ?? Promise.resolve(),
6958
+ pauseVideo: () => videoHost.controller?.pause(),
6959
+ toggleVideo: () => videoHost.controller?.togglePlay(),
6960
+ seekVideo: (time) => videoHost.controller?.seek(time),
6961
+ setVideoVolume: (vol) => videoHost.controller?.setVolume(vol),
6962
+ setVideoMuted: (m) => videoHost.controller?.setMuted(m),
6963
+ toggleVideoMuted: () => videoHost.controller?.toggleMuted(),
6964
+ // -- Background Audio --
6965
+ getBackgroundAudio: () => bgmHost.controller,
6966
+ subscribeBackgroundAudio: (onStoreChange) => subscribeBackgroundAudioHost(bgmHost, onStoreChange),
6967
+ playBackgroundAudio: () => {
6968
+ bgmHost.controller?.play();
6969
+ return Promise.resolve();
6970
+ },
6971
+ pauseBackgroundAudio: () => bgmHost.controller?.pause(),
6972
+ toggleBackgroundAudio: () => bgmHost.controller?.togglePlay(),
6973
+ setBackgroundAudioVolume: (vol) => bgmHost.controller?.setVolume(vol),
6974
+ setBackgroundAudioMuted: (m) => bgmHost.controller?.setMuted(m),
6975
+ toggleBackgroundAudioMuted: () => bgmHost.controller?.toggleMuted(),
6976
+ // -- Deprecated --
6867
6977
  startAutoRotate: () => {
6868
6978
  setLegacyAutoRotate(true);
6869
6979
  },
6870
6980
  stopAutoRotate: () => {
6871
6981
  setLegacyAutoRotate(false);
6872
- },
6873
- toggleFullscreen
6982
+ }
6874
6983
  }),
6875
- [toggleFullscreen]
6984
+ [
6985
+ bgmHost,
6986
+ enterFullscreen,
6987
+ exitFullscreen,
6988
+ scenesHost,
6989
+ toggleFullscreen,
6990
+ videoHost,
6991
+ webVRHost
6992
+ ]
6876
6993
  );
6877
6994
  const rootStyle = useMemo7(
6878
6995
  () => ({
@@ -6924,11 +7041,11 @@ var PanoViewer = forwardRef2(
6924
7041
  PanoContextMenuOverlayContext.Provider,
6925
7042
  {
6926
7043
  value: contextMenuOverlayApi,
6927
- children: /* @__PURE__ */ jsx11(PanoChromeOverlayContext.Provider, { value: chromeOverlayApi, children: /* @__PURE__ */ jsx11(PanoVideoHostContext.Provider, { value: videoHost, children: /* @__PURE__ */ jsx11(PanoFilterHostContext.Provider, { value: filterHost, children: /* @__PURE__ */ jsxs6(
7044
+ children: /* @__PURE__ */ jsx11(PanoChromeOverlayContext.Provider, { value: chromeOverlayApi, children: /* @__PURE__ */ jsx11(PanoVideoHostContext.Provider, { value: videoHost, children: /* @__PURE__ */ jsx11(PanoFilterHostContext.Provider, { value: filterHost, children: /* @__PURE__ */ jsx11(
6928
7045
  PanoContextMenuActionsContext.Provider,
6929
7046
  {
6930
7047
  value: contextMenuActions,
6931
- children: [
7048
+ children: /* @__PURE__ */ jsx11(ScenesHostContext.Provider, { value: scenesHost, children: /* @__PURE__ */ jsxs6(BackgroundAudioHostContext.Provider, { value: bgmHost, children: [
6932
7049
  /* @__PURE__ */ jsx11(PanoVideoHostReset, {}),
6933
7050
  /* @__PURE__ */ jsx11(
6934
7051
  PanoramaViewRuntime,
@@ -6972,7 +7089,7 @@ var PanoViewer = forwardRef2(
6972
7089
  }
6973
7090
  )
6974
7091
  ] })
6975
- ]
7092
+ ] }) })
6976
7093
  }
6977
7094
  ) }) }) })
6978
7095
  }
@@ -7010,7 +7127,7 @@ var PanoViewer = forwardRef2(
7010
7127
  );
7011
7128
 
7012
7129
  // src/background-audio.tsx
7013
- import { useContext as useContext15, useEffect as useEffect13, useRef as useRef13 } from "react";
7130
+ import { useContext as useContext15, useEffect as useEffect13, useMemo as useMemo8, useRef as useRef13, useState as useState6 } from "react";
7014
7131
 
7015
7132
  // src/audio/howl.ts
7016
7133
  var import_howler = __toESM(require_howler());
@@ -7180,7 +7297,8 @@ function BackgroundAudio({
7180
7297
  src,
7181
7298
  sources,
7182
7299
  sceneId,
7183
- playing,
7300
+ playing: controlledPlaying,
7301
+ defaultPlaying = false,
7184
7302
  loop = true,
7185
7303
  muted = false,
7186
7304
  volume = 1,
@@ -7196,6 +7314,9 @@ function BackgroundAudio({
7196
7314
  if (!controlsRef) {
7197
7315
  throw new Error("<BackgroundAudio> must be rendered inside <PanoViewer>.");
7198
7316
  }
7317
+ const isControlled = controlledPlaying !== void 0;
7318
+ const [internalPlaying, setInternalPlaying] = useState6(defaultPlaying);
7319
+ const playing = isControlled ? controlledPlaying : internalPlaying;
7199
7320
  const urls = resolveBackgroundAudioUrls(src, sources, sceneId);
7200
7321
  const trackKey = urls.join("\0");
7201
7322
  const currentRef = useRef13(null);
@@ -7220,6 +7341,99 @@ function BackgroundAudio({
7220
7341
  onPlaybackErrorRef.current = onPlaybackError;
7221
7342
  onEndedRef.current = onEnded;
7222
7343
  onErrorRef.current = onError;
7344
+ const snapshotRef = useRef13({
7345
+ ready: false,
7346
+ playing: false,
7347
+ blocked: false,
7348
+ ended: false,
7349
+ muted,
7350
+ volume
7351
+ });
7352
+ const snapshotListenersRef = useRef13(/* @__PURE__ */ new Set());
7353
+ const notifySnapshot = (patch) => {
7354
+ const prev = snapshotRef.current;
7355
+ snapshotRef.current = { ...prev, ...patch };
7356
+ for (const listener of snapshotListenersRef.current) {
7357
+ listener();
7358
+ }
7359
+ };
7360
+ const isControlledRef = useRef13(isControlled);
7361
+ isControlledRef.current = isControlled;
7362
+ const controller = useMemo8(
7363
+ () => ({
7364
+ subscribe: (onStoreChange) => {
7365
+ snapshotListenersRef.current.add(onStoreChange);
7366
+ return () => {
7367
+ snapshotListenersRef.current.delete(onStoreChange);
7368
+ };
7369
+ },
7370
+ getSnapshot: () => snapshotRef.current,
7371
+ play: () => {
7372
+ if (isControlledRef.current) {
7373
+ return;
7374
+ }
7375
+ setInternalPlaying(true);
7376
+ },
7377
+ pause: () => {
7378
+ if (isControlledRef.current) {
7379
+ return;
7380
+ }
7381
+ setInternalPlaying(false);
7382
+ },
7383
+ togglePlay: () => {
7384
+ if (isControlledRef.current) {
7385
+ return;
7386
+ }
7387
+ setInternalPlaying((prev) => !prev);
7388
+ },
7389
+ setVolume: (vol) => {
7390
+ if (isControlledRef.current) {
7391
+ return;
7392
+ }
7393
+ const howl = currentRef.current?.handle.howl;
7394
+ if (howl) {
7395
+ howl.volume(clampAudioVolume(vol));
7396
+ }
7397
+ notifySnapshot({ volume: clampAudioVolume(vol) });
7398
+ },
7399
+ setMuted: (m) => {
7400
+ if (isControlledRef.current) {
7401
+ return;
7402
+ }
7403
+ const howl = currentRef.current?.handle.howl;
7404
+ if (howl) {
7405
+ howl.mute(m);
7406
+ }
7407
+ notifySnapshot({ muted: m });
7408
+ },
7409
+ toggleMuted: () => {
7410
+ if (isControlledRef.current) {
7411
+ return;
7412
+ }
7413
+ const next = !snapshotRef.current.muted;
7414
+ const howl = currentRef.current?.handle.howl;
7415
+ if (howl) {
7416
+ howl.mute(next);
7417
+ }
7418
+ notifySnapshot({ muted: next });
7419
+ }
7420
+ }),
7421
+ []
7422
+ );
7423
+ const bgmHost = useContext15(BackgroundAudioHostContext);
7424
+ useEffect13(() => {
7425
+ if (!bgmHost) {
7426
+ return;
7427
+ }
7428
+ bgmHost.controller = controller;
7429
+ notifyBackgroundAudioHost(bgmHost);
7430
+ return () => {
7431
+ if (bgmHost.controller === controller) {
7432
+ bgmHost.controller = null;
7433
+ notifyBackgroundAudioHost(bgmHost);
7434
+ }
7435
+ };
7436
+ }, [bgmHost, controller]);
7223
7437
  useEffect13(() => {
7224
7438
  return () => {
7225
7439
  fadingInRef.current = false;
@@ -7266,6 +7480,7 @@ function BackgroundAudio({
7266
7480
  isActive: () => currentRef.current?.key === trackKey,
7267
7481
  shouldRetryPlay: () => currentRef.current?.key === trackKey && playingRef.current,
7268
7482
  onEnded: () => {
7483
+ notifySnapshot({ playing: false, ended: true });
7269
7484
  onPlaybackStateChangeRef.current?.("ended");
7270
7485
  onEndedRef.current?.();
7271
7486
  },
@@ -7277,15 +7492,18 @@ function BackgroundAudio({
7277
7492
  durationMs
7278
7493
  );
7279
7494
  }
7495
+ notifySnapshot({ ready: true, playing: true, blocked: false, ended: false });
7280
7496
  onPlaybackStateChangeRef.current?.("playing");
7281
7497
  },
7282
7498
  onPause: () => {
7499
+ notifySnapshot({ playing: false });
7283
7500
  onPlaybackStateChangeRef.current?.("paused");
7284
7501
  },
7285
7502
  onLoadError: (error) => {
7286
7503
  onErrorRef.current?.({ error });
7287
7504
  },
7288
7505
  onPlayError: (error) => {
7506
+ notifySnapshot({ blocked: true });
7289
7507
  onPlaybackStateChangeRef.current?.("blocked");
7290
7508
  onPlaybackErrorRef.current?.(error);
7291
7509
  }
@@ -7354,6 +7572,7 @@ function BackgroundAudio({
7354
7572
  if (!fadingInRef.current) {
7355
7573
  slot.handle.howl.volume(clampAudioVolume(volume));
7356
7574
  }
7575
+ notifySnapshot({ muted, volume: clampAudioVolume(volume) });
7357
7576
  }, [loop, muted, trackKey, volume]);
7358
7577
  useEffect13(() => {
7359
7578
  if (!pauseWhenHidden) {
@@ -7381,7 +7600,7 @@ function BackgroundAudio({
7381
7600
  }
7382
7601
 
7383
7602
  // src/pano-filter/pano-filter.tsx
7384
- import { useContext as useContext16, useEffect as useEffect14, useMemo as useMemo8 } from "react";
7603
+ import { useContext as useContext16, useEffect as useEffect14, useMemo as useMemo9 } from "react";
7385
7604
  function PanoFilter({
7386
7605
  preset = "none",
7387
7606
  intensity,
@@ -7391,7 +7610,7 @@ function PanoFilter({
7391
7610
  if (!host) {
7392
7611
  throw new Error("<PanoFilter> must be rendered inside <PanoViewer>.");
7393
7612
  }
7394
- const snapshot = useMemo8(
7613
+ const snapshot = useMemo9(
7395
7614
  () => resolvePanoFilterSnapshot({ preset, intensity, enabled }),
7396
7615
  [enabled, intensity, preset]
7397
7616
  );
@@ -7416,7 +7635,7 @@ import {
7416
7635
  useEffect as useEffect15,
7417
7636
  useImperativeHandle as useImperativeHandle3,
7418
7637
  useRef as useRef14,
7419
- useState as useState6
7638
+ useState as useState7
7420
7639
  } from "react";
7421
7640
 
7422
7641
  // src/gyro-orientation.ts
@@ -7520,7 +7739,7 @@ var Gyro = forwardRef3(function Gyro2({
7520
7739
  const controlsRef = useContext17(PanoramaViewContext);
7521
7740
  const eventBus = useContext17(PanoEventBusContext);
7522
7741
  const { gl } = useThree9();
7523
- const [internallyDisabled, setInternallyDisabled] = useState6(false);
7742
+ const [internallyDisabled, setInternallyDisabled] = useState7(false);
7524
7743
  const availableRef = useRef14(false);
7525
7744
  const availabilityReportedRef = useRef14(null);
7526
7745
  const activeRef = useRef14(false);
@@ -7876,9 +8095,9 @@ import {
7876
8095
  useContext as useContext19,
7877
8096
  useEffect as useEffect19,
7878
8097
  useImperativeHandle as useImperativeHandle4,
7879
- useMemo as useMemo10,
8098
+ useMemo as useMemo11,
7880
8099
  useRef as useRef18,
7881
- useState as useState7
8100
+ useState as useState8
7882
8101
  } from "react";
7883
8102
 
7884
8103
  // src/webvr/detect.ts
@@ -8162,7 +8381,7 @@ function WebVRReticle({
8162
8381
 
8163
8382
  // src/webvr/stereo.tsx
8164
8383
  import { useFrame as useFrame8, useThree as useThree11 } from "@react-three/fiber";
8165
- import { useContext as useContext18, useEffect as useEffect16, useMemo as useMemo9, useRef as useRef16 } from "react";
8384
+ import { useContext as useContext18, useEffect as useEffect16, useMemo as useMemo10, useRef as useRef16 } from "react";
8166
8385
  import {
8167
8386
  Mesh as Mesh2,
8168
8387
  OrthographicCamera,
@@ -8256,7 +8475,7 @@ function WebVRStereoRenderer({
8256
8475
  const stereo = runtime?.stereoView.stereo ?? fallbackStereoRef.current;
8257
8476
  const stereoRef = useRef16(stereo);
8258
8477
  stereoRef.current = stereo;
8259
- const resources = useMemo9(createDistortionResources, []);
8478
+ const resources = useMemo10(createDistortionResources, []);
8260
8479
  const drawingSizeRef = useRef16(new Vector23());
8261
8480
  const viewportRef = useRef16(new Vector4());
8262
8481
  const scissorRef = useRef16(new Vector4());
@@ -8635,8 +8854,8 @@ var WebVR = forwardRef4(function WebVR2({
8635
8854
  const availableRef = useRef18(false);
8636
8855
  const modeRef = useRef18(null);
8637
8856
  const webXRSessionRef = useRef18(null);
8638
- const [mode, setMode] = useState7(null);
8639
- const [settings, setSettings] = useState7(
8857
+ const [mode, setMode] = useState8(null);
8858
+ const [settings, setSettings] = useState8(
8640
8859
  () => initializeSettings(screensize, profile)
8641
8860
  );
8642
8861
  const callbacksRef = useRef18({
@@ -8659,7 +8878,7 @@ var WebVR = forwardRef4(function WebVR2({
8659
8878
  throw new Error("<WebVR> must be rendered inside <PanoViewer>.");
8660
8879
  }
8661
8880
  const { host, xrStore } = runtime;
8662
- const resolvedProfile = useMemo10(
8881
+ const resolvedProfile = useMemo11(
8663
8882
  () => resolveWebVRProfile(profile, settings.profileId),
8664
8883
  [profile, settings.profileId]
8665
8884
  );
@@ -8824,16 +9043,17 @@ var WebVR = forwardRef4(function WebVR2({
8824
9043
  setSettings(normalized);
8825
9044
  saveWebVRSettings(normalized);
8826
9045
  }, []);
8827
- const controller = useMemo10(
9046
+ const controller = useMemo11(
8828
9047
  () => ({
8829
9048
  closeSetup: () => updateWebVRHost(host, { setupOpen: false }),
8830
9049
  enterVR,
8831
9050
  exitVR,
8832
9051
  openSetup: () => updateWebVRHost(host, { setupOpen: true }),
9052
+ requestPermission,
8833
9053
  toggleVR,
8834
9054
  updateSettings
8835
9055
  }),
8836
- [enterVR, exitVR, host, toggleVR, updateSettings]
9056
+ [enterVR, exitVR, host, requestPermission, toggleVR, updateSettings]
8837
9057
  );
8838
9058
  useImperativeHandle4(
8839
9059
  ref,
@@ -8960,7 +9180,7 @@ var WebVR = forwardRef4(function WebVR2({
8960
9180
 
8961
9181
  // src/sphere.tsx
8962
9182
  import { useThree as useThree13 } from "@react-three/fiber";
8963
- import { useEffect as useEffect20, useMemo as useMemo12, useRef as useRef20, useState as useState8 } from "react";
9183
+ import { useEffect as useEffect20, useMemo as useMemo13, useRef as useRef20, useState as useState9 } from "react";
8964
9184
  import {
8965
9185
  BackSide as BackSide2,
8966
9186
  LinearFilter,
@@ -8993,7 +9213,7 @@ function resolveUrlAgainstFile(fileUrl, path) {
8993
9213
  }
8994
9214
 
8995
9215
  // src/pano-filter/pano-basic-material.tsx
8996
- import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo11, useRef as useRef19 } from "react";
9216
+ import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo12, useRef as useRef19 } from "react";
8997
9217
 
8998
9218
  // src/pano-filter/shaders.ts
8999
9219
  var GLSL_FILTER_COMMON = `
@@ -9159,7 +9379,7 @@ function PanoBasicMaterial({
9159
9379
  intensityRef.current = snapshot.intensity;
9160
9380
  const active = isPanoFilterActive(snapshot);
9161
9381
  const preset = snapshot.preset;
9162
- const onBeforeCompile = useMemo11(() => {
9382
+ const onBeforeCompile = useMemo12(() => {
9163
9383
  if (!active || preset === "none") {
9164
9384
  return void 0;
9165
9385
  }
@@ -9209,7 +9429,7 @@ function PanoBasicMaterial({
9209
9429
  // src/sphere.tsx
9210
9430
  import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
9211
9431
  function useEquirectTexture(url, anisotropy, generateMipmaps, onLoad, onError) {
9212
- const [texture, setTexture] = useState8(null);
9432
+ const [texture, setTexture] = useState9(null);
9213
9433
  const onLoadRef = useRef20(onLoad);
9214
9434
  const onErrorRef = useRef20(onError);
9215
9435
  onLoadRef.current = onLoad;
@@ -9273,7 +9493,7 @@ function Sphere({
9273
9493
  }) {
9274
9494
  const gl = useThree13((state) => state.gl);
9275
9495
  const anisotropy = gl.capabilities.getMaxAnisotropy();
9276
- const resolvedPreviewUrl = useMemo12(
9496
+ const resolvedPreviewUrl = useMemo13(
9277
9497
  () => resolveUrlAgainstFile(src, previewUrl),
9278
9498
  [previewUrl, src]
9279
9499
  );
@@ -9333,9 +9553,9 @@ import {
9333
9553
  useContext as useContext20,
9334
9554
  useEffect as useEffect21,
9335
9555
  useLayoutEffect as useLayoutEffect3,
9336
- useMemo as useMemo13,
9556
+ useMemo as useMemo14,
9337
9557
  useRef as useRef21,
9338
- useState as useState9
9558
+ useState as useState10
9339
9559
  } from "react";
9340
9560
  import {
9341
9561
  BackSide as BackSide3,
@@ -9432,7 +9652,7 @@ function applyEquirectangularLayout(texture, anisotropy) {
9432
9652
  }
9433
9653
  function usePosterTexture(poster, onError) {
9434
9654
  const gl = useThree14((state) => state.gl);
9435
- const [texture, setTexture] = useState9(null);
9655
+ const [texture, setTexture] = useState10(null);
9436
9656
  const onErrorRef = useRef21(onError);
9437
9657
  onErrorRef.current = onError;
9438
9658
  useEffect21(() => {
@@ -9532,25 +9752,25 @@ function PanoVideo({
9532
9752
  const gl = useThree14((state) => state.gl);
9533
9753
  const host = useContext20(PanoVideoHostContext);
9534
9754
  const overlay = useContext20(PanoChromeOverlayContext);
9535
- const variants = useMemo13(
9755
+ const variants = useMemo14(
9536
9756
  () => resolvePanoVideoVariants({ poster, src, variants: variantsProp }),
9537
9757
  [poster, src, variantsProp]
9538
9758
  );
9539
9759
  const tracks = tracksProp ?? EMPTY_TRACKS;
9540
9760
  const captionsEnabled = captions !== false;
9541
- const captionAppearance = useMemo13(
9761
+ const captionAppearance = useMemo14(
9542
9762
  () => resolvePanoVideoCaptionAppearance(
9543
9763
  typeof captions === "object" ? captions : void 0
9544
9764
  ),
9545
9765
  [captions]
9546
9766
  );
9547
- const [variantId, setVariantId] = useState9(
9767
+ const [variantId, setVariantId] = useState10(
9548
9768
  () => resolvePanoVideoVariantId(variants, defaultVariantId)
9549
9769
  );
9550
- const [trackId, setTrackId] = useState9(
9770
+ const [trackId, setTrackId] = useState10(
9551
9771
  () => captionsEnabled ? defaultPanoVideoTrackId(tracks) : null
9552
9772
  );
9553
- const [ready, setReady] = useState9(false);
9773
+ const [ready, setReady] = useState10(false);
9554
9774
  const autoPlayRef = useRef21(autoPlay);
9555
9775
  autoPlayRef.current = autoPlay;
9556
9776
  const blockedRef = useRef21(false);
@@ -9603,7 +9823,7 @@ function PanoVideo({
9603
9823
  onEndedRef.current = onEnded;
9604
9824
  onVariantChangeRef.current = onVariantChange;
9605
9825
  onTrackChangeRef.current = onTrackChange;
9606
- const resource = useMemo13(() => {
9826
+ const resource = useMemo14(() => {
9607
9827
  if (typeof document === "undefined") {
9608
9828
  return null;
9609
9829
  }
@@ -9716,7 +9936,7 @@ function PanoVideo({
9716
9936
  captionAppearance
9717
9937
  ]
9718
9938
  );
9719
- const controller = useMemo13(
9939
+ const controller = useMemo14(
9720
9940
  () => ({
9721
9941
  subscribe: (onStoreChange) => {
9722
9942
  const store = storeRef.current;
@@ -10083,9 +10303,9 @@ import {
10083
10303
  useCallback as useCallback10,
10084
10304
  useContext as useContext22,
10085
10305
  useEffect as useEffect26,
10086
- useMemo as useMemo18,
10306
+ useMemo as useMemo19,
10087
10307
  useRef as useRef25,
10088
- useState as useState11
10308
+ useState as useState12
10089
10309
  } from "react";
10090
10310
  import {
10091
10311
  LinearSRGBColorSpace,
@@ -10141,7 +10361,7 @@ function isSnapshotTransition(preset) {
10141
10361
 
10142
10362
  // src/scene-transitions/particle-overlay.tsx
10143
10363
  import { useFrame as useFrame9, useThree as useThree15 } from "@react-three/fiber";
10144
- import { useEffect as useEffect22, useMemo as useMemo14, useRef as useRef22 } from "react";
10364
+ import { useEffect as useEffect22, useMemo as useMemo15, useRef as useRef22 } from "react";
10145
10365
  import {
10146
10366
  DoubleSide as DoubleSide2,
10147
10367
  InstancedBufferAttribute,
@@ -10285,8 +10505,8 @@ function ParticleOverlay({
10285
10505
  const startRef = useRef22(null);
10286
10506
  const finishedRef = useRef22(false);
10287
10507
  const directionRef = useRef22(new Vector39());
10288
- const geometry = useMemo14(() => createParticleGeometry(), []);
10289
- const material = useMemo14(
10508
+ const geometry = useMemo15(() => createParticleGeometry(), []);
10509
+ const material = useMemo15(
10290
10510
  () => new ShaderMaterial2({
10291
10511
  transparent: true,
10292
10512
  depthTest: false,
@@ -10340,7 +10560,7 @@ function ParticleOverlay({
10340
10560
 
10341
10561
  // src/scene-transitions/snapshot-overlay.tsx
10342
10562
  import { useFrame as useFrame10, useThree as useThree16 } from "@react-three/fiber";
10343
- import { useEffect as useEffect23, useMemo as useMemo15, useRef as useRef23 } from "react";
10563
+ import { useEffect as useEffect23, useMemo as useMemo16, useRef as useRef23 } from "react";
10344
10564
  import { Vector3 as Vector310 } from "three";
10345
10565
 
10346
10566
  // src/scene-transitions/snapshot-shaders.ts
@@ -10633,7 +10853,7 @@ function SnapshotOverlay({
10633
10853
  const startRef = useRef23(null);
10634
10854
  const finishedRef = useRef23(false);
10635
10855
  const directionRef = useRef23(new Vector310());
10636
- const material = useMemo15(() => {
10856
+ const material = useMemo16(() => {
10637
10857
  if (!isSnapshotTransition(transition.preset)) {
10638
10858
  return null;
10639
10859
  }
@@ -10723,10 +10943,10 @@ function TransitionOverlay(props) {
10723
10943
  // src/tile/texture-manager-context.tsx
10724
10944
  import { useThree as useThree17 } from "@react-three/fiber";
10725
10945
  import {
10726
- createContext as createContext11,
10946
+ createContext as createContext13,
10727
10947
  useContext as useContext21,
10728
10948
  useEffect as useEffect24,
10729
- useMemo as useMemo16
10949
+ useMemo as useMemo17
10730
10950
  } from "react";
10731
10951
 
10732
10952
  // src/tile/texture-manager.ts
@@ -10957,7 +11177,7 @@ var TileTextureManager = class {
10957
11177
 
10958
11178
  // src/tile/texture-manager-context.tsx
10959
11179
  import { jsx as jsx20 } from "react/jsx-runtime";
10960
- var TileTextureManagerContext = createContext11(
11180
+ var TileTextureManagerContext = createContext13(
10961
11181
  null
10962
11182
  );
10963
11183
  function TileTextureManagerProvider({
@@ -10967,7 +11187,7 @@ function TileTextureManagerProvider({
10967
11187
  }) {
10968
11188
  const gl = useThree17((state) => state.gl);
10969
11189
  const anisotropy = gl.capabilities.getMaxAnisotropy();
10970
- const manager = useMemo16(
11190
+ const manager = useMemo17(
10971
11191
  () => new TileTextureManager({
10972
11192
  anisotropy,
10973
11193
  maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
@@ -10991,9 +11211,9 @@ import { useFrame as useFrame11, useThree as useThree18 } from "@react-three/fib
10991
11211
  import {
10992
11212
  memo,
10993
11213
  useEffect as useEffect25,
10994
- useMemo as useMemo17,
11214
+ useMemo as useMemo18,
10995
11215
  useRef as useRef24,
10996
- useState as useState10
11216
+ useState as useState11
10997
11217
  } from "react";
10998
11218
  import {
10999
11219
  ClampToEdgeWrapping as ClampToEdgeWrapping2,
@@ -11198,7 +11418,7 @@ var VISIBILITY_FOV_PADDING_DEGREES = 8;
11198
11418
  var PREVIEW_RADIUS = CUBE_RADIUS + 0.05;
11199
11419
  var SHARED_TILE_GEOMETRY = new PlaneGeometry4(1, 1);
11200
11420
  function usePreviewTexture(url, anisotropy, onError) {
11201
- const [texture, setTexture] = useState10(null);
11421
+ const [texture, setTexture] = useState11(null);
11202
11422
  const onErrorRef = useRef24(onError);
11203
11423
  onErrorRef.current = onError;
11204
11424
  useEffect25(() => {
@@ -11251,7 +11471,7 @@ function PreviewFace({
11251
11471
  texture,
11252
11472
  faceOrder
11253
11473
  }) {
11254
- const geometry = useMemo17(() => {
11474
+ const geometry = useMemo18(() => {
11255
11475
  const nextGeometry = new PlaneGeometry4(
11256
11476
  PREVIEW_RADIUS * 2,
11257
11477
  PREVIEW_RADIUS * 2
@@ -11310,7 +11530,7 @@ function PreviewCube({
11310
11530
  ));
11311
11531
  }
11312
11532
  function useManagedTexture(manager, key, url, address) {
11313
- const [managedState, setManagedState] = useState10(() => ({
11533
+ const [managedState, setManagedState] = useState11(() => ({
11314
11534
  manager,
11315
11535
  key,
11316
11536
  url,
@@ -11537,10 +11757,10 @@ function Tile({
11537
11757
  onLevelChange
11538
11758
  }) {
11539
11759
  const { camera, gl, size } = useThree18();
11540
- const multiresConfig = useMemo17(() => parseMultires(multires), [multires]);
11760
+ const multiresConfig = useMemo18(() => parseMultires(multires), [multires]);
11541
11761
  const { levels, tileSize } = multiresConfig;
11542
11762
  const multiresToken = `${tileSize}:${levels.join(",")}`;
11543
- const candidatesByLevel = useMemo17(
11763
+ const candidatesByLevel = useMemo18(
11544
11764
  () => buildTileCandidates(levels, tileSize),
11545
11765
  [levels, tileSize]
11546
11766
  );
@@ -11558,7 +11778,7 @@ function Tile({
11558
11778
  progressRef.current = onLoadProgress;
11559
11779
  errorRef.current = onTileError;
11560
11780
  levelChangeRef.current = onLevelChange;
11561
- const ownedManager = useMemo17(
11781
+ const ownedManager = useMemo18(
11562
11782
  () => new TileTextureManager({
11563
11783
  anisotropy,
11564
11784
  maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
@@ -11601,14 +11821,14 @@ function Tile({
11601
11821
  onReady?.();
11602
11822
  }, [onReady, previewTexture]);
11603
11823
  const template = urlTemplate ?? buildDefaultTileUrlTemplate();
11604
- const resolveUrl = useMemo17(
11824
+ const resolveUrl = useMemo18(
11605
11825
  () => (address) => {
11606
11826
  const relativePath = resolveTileUrl ? resolveTileUrl(address) : resolveTemplateUrl(template, address);
11607
11827
  return resolveRelativeTileUrl(baseUrl, relativePath);
11608
11828
  },
11609
11829
  [baseUrl, resolveTileUrl, template]
11610
11830
  );
11611
- const [layers, setLayers] = useState10([]);
11831
+ const [layers, setLayers] = useState11([]);
11612
11832
  const layerTokenRef = useRef24("");
11613
11833
  const currentLevelRef = useRef24(1);
11614
11834
  const lastUpdateAtRef = useRef24(-Infinity);
@@ -11831,31 +12051,32 @@ function ScenesController({
11831
12051
  renderHotspots,
11832
12052
  onTransitionEnd,
11833
12053
  onTransitionError,
11834
- snapshotMaxPixels = 3686400
12054
+ snapshotMaxPixels = 3686400,
12055
+ phaseRef: externalPhaseRef
11835
12056
  }) {
11836
12057
  const controlsRef = useContext22(PanoramaViewContext);
11837
12058
  const manager = useSharedTileTextureManager();
11838
- const transition = useMemo18(
12059
+ const transition = useMemo19(
11839
12060
  () => resolveTransition(transitionInput),
11840
12061
  [transitionInput]
11841
12062
  );
11842
- const sceneMap = useMemo18(
12063
+ const sceneMap = useMemo19(
11843
12064
  () => new Map(scenes.map((scene) => [scene.id, scene])),
11844
12065
  [scenes]
11845
12066
  );
11846
12067
  const initialScene = sceneMap.get(activeSceneId) ?? null;
11847
- const [slots, setSlots] = useState11([
12068
+ const [slots, setSlots] = useState12([
11848
12069
  initialScene,
11849
12070
  null
11850
12071
  ]);
11851
- const [activeSlot, setActiveSlot] = useState11(0);
11852
- const [liveSlot, setLiveSlot] = useState11(0);
11853
- const [pendingSlot, setPendingSlot] = useState11(null);
11854
- const [phase, setPhase] = useState11(initialScene ? "idle" : "preloading");
11855
- const [captureRequestId, setCaptureRequestId] = useState11(0);
11856
- const [transitionRunId, setTransitionRunId] = useState11(0);
11857
- const [snapshot, setSnapshot] = useState11(null);
11858
- const [recoveryKey, setRecoveryKey] = useState11(0);
12072
+ const [activeSlot, setActiveSlot] = useState12(0);
12073
+ const [liveSlot, setLiveSlot] = useState12(0);
12074
+ const [pendingSlot, setPendingSlot] = useState12(null);
12075
+ const [phase, setPhase] = useState12(initialScene ? "idle" : "preloading");
12076
+ const [captureRequestId, setCaptureRequestId] = useState12(0);
12077
+ const [transitionRunId, setTransitionRunId] = useState12(0);
12078
+ const [snapshot, setSnapshot] = useState12(null);
12079
+ const [recoveryKey, setRecoveryKey] = useState12(0);
11859
12080
  const requestedSceneIdRef = useRef25(activeSceneId);
11860
12081
  const captureReasonRef = useRef25("start");
11861
12082
  const interactionReleaseRef = useRef25(null);
@@ -11865,6 +12086,9 @@ function ScenesController({
11865
12086
  const transitionFromRef = useRef25(initialScene);
11866
12087
  slotsRef.current = slots;
11867
12088
  phaseRef.current = phase;
12089
+ if (externalPhaseRef) {
12090
+ externalPhaseRef.current = phase;
12091
+ }
11868
12092
  liveSlotRef.current = liveSlot;
11869
12093
  const releaseInteraction = useCallback10(() => {
11870
12094
  interactionReleaseRef.current?.();
@@ -12060,21 +12284,104 @@ function ScenesController({
12060
12284
  function Scenes({
12061
12285
  maxTextureMemoryMb,
12062
12286
  maxConcurrentTileLoads,
12063
- ...props
12287
+ activeSceneId: controlledId,
12288
+ defaultActiveSceneId,
12289
+ onActiveSceneIdChange,
12290
+ scenes,
12291
+ ...rest
12064
12292
  }) {
12293
+ const isControlled = controlledId !== void 0;
12294
+ const [internalId, setInternalId] = useState12(
12295
+ () => controlledId ?? defaultActiveSceneId ?? scenes[0]?.id ?? ""
12296
+ );
12297
+ const resolvedId = isControlled ? controlledId : internalId;
12298
+ const requestSceneChange = useCallback10(
12299
+ (id) => {
12300
+ if (isControlled) {
12301
+ onActiveSceneIdChange?.(id);
12302
+ } else {
12303
+ setInternalId(id);
12304
+ }
12305
+ },
12306
+ [isControlled, onActiveSceneIdChange]
12307
+ );
12308
+ const scenesRef = useRef25(scenes);
12309
+ scenesRef.current = scenes;
12310
+ const resolvedIdRef = useRef25(resolvedId);
12311
+ resolvedIdRef.current = resolvedId;
12312
+ const host = useContext22(ScenesHostContext);
12313
+ const phaseRef = useRef25("idle");
12314
+ useEffect26(() => {
12315
+ if (!host) {
12316
+ return;
12317
+ }
12318
+ const controller = {
12319
+ setScene: (id) => {
12320
+ const match = scenesRef.current.find((s) => s.id === id);
12321
+ if (!match) {
12322
+ return false;
12323
+ }
12324
+ requestSceneChange(id);
12325
+ return true;
12326
+ },
12327
+ nextScene: () => {
12328
+ const next = cycleSceneId(
12329
+ scenesRef.current,
12330
+ resolvedIdRef.current,
12331
+ 1
12332
+ );
12333
+ if (next === null) {
12334
+ return false;
12335
+ }
12336
+ requestSceneChange(next);
12337
+ return true;
12338
+ },
12339
+ previousScene: () => {
12340
+ const next = cycleSceneId(
12341
+ scenesRef.current,
12342
+ resolvedIdRef.current,
12343
+ -1
12344
+ );
12345
+ if (next === null) {
12346
+ return false;
12347
+ }
12348
+ requestSceneChange(next);
12349
+ return true;
12350
+ },
12351
+ getActiveSceneId: () => resolvedIdRef.current,
12352
+ getSceneIds: () => scenesRef.current.map((s) => s.id),
12353
+ isTransitioning: () => phaseRef.current !== "idle"
12354
+ };
12355
+ host.controller = controller;
12356
+ notifyScenesHost(host);
12357
+ return () => {
12358
+ if (host.controller === controller) {
12359
+ host.controller = null;
12360
+ notifyScenesHost(host);
12361
+ }
12362
+ };
12363
+ }, [host, requestSceneChange]);
12065
12364
  return /* @__PURE__ */ jsx22(
12066
12365
  TileTextureManagerProvider,
12067
12366
  {
12068
12367
  maxTextureMemoryMb,
12069
12368
  maxConcurrentLoads: maxConcurrentTileLoads,
12070
- children: /* @__PURE__ */ jsx22(ScenesController, { ...props })
12369
+ children: /* @__PURE__ */ jsx22(
12370
+ ScenesController,
12371
+ {
12372
+ ...rest,
12373
+ scenes,
12374
+ activeSceneId: resolvedId,
12375
+ phaseRef
12376
+ }
12377
+ )
12071
12378
  }
12072
12379
  );
12073
12380
  }
12074
12381
 
12075
12382
  // src/hotspot/image-hotspot.tsx
12076
12383
  import { useThree as useThree21 } from "@react-three/fiber";
12077
- import { useEffect as useEffect29, useRef as useRef27, useState as useState14 } from "react";
12384
+ import { useEffect as useEffect29, useRef as useRef27, useState as useState15 } from "react";
12078
12385
  import {
12079
12386
  ClampToEdgeWrapping as ClampToEdgeWrapping3,
12080
12387
  LinearFilter as LinearFilter6,
@@ -12089,9 +12396,9 @@ import {
12089
12396
  useContext as useContext24,
12090
12397
  useEffect as useEffect28,
12091
12398
  useLayoutEffect as useLayoutEffect4,
12092
- useMemo as useMemo20,
12399
+ useMemo as useMemo21,
12093
12400
  useRef as useRef26,
12094
- useState as useState13
12401
+ useState as useState14
12095
12402
  } from "react";
12096
12403
  import {
12097
12404
  Box3 as Box33,
@@ -12107,7 +12414,7 @@ import {
12107
12414
  } from "three";
12108
12415
 
12109
12416
  // src/hotspot/hotspot-tooltip.tsx
12110
- import { useEffect as useEffect27, useMemo as useMemo19, useState as useState12 } from "react";
12417
+ import { useEffect as useEffect27, useMemo as useMemo20, useState as useState13 } from "react";
12111
12418
 
12112
12419
  // src/webvr/stereo-html.tsx
12113
12420
  import { Html } from "@react-three/drei";
@@ -12230,16 +12537,16 @@ function HotspotTooltip({
12230
12537
  offset = DEFAULT_HOTSPOT_TOOLTIP_OFFSET,
12231
12538
  appearance
12232
12539
  }) {
12233
- const [imageFailed, setImageFailed] = useState12(false);
12540
+ const [imageFailed, setImageFailed] = useState13(false);
12234
12541
  useEffect27(() => {
12235
12542
  setImageFailed(false);
12236
12543
  }, [content.image]);
12237
12544
  const showImage = Boolean(content.image) && !imageFailed;
12238
- const resolvedAppearance = useMemo19(
12545
+ const resolvedAppearance = useMemo20(
12239
12546
  () => resolveHotspotTooltipAppearance(appearance),
12240
12547
  [appearance]
12241
12548
  );
12242
- const bubbleStyle = useMemo19(
12549
+ const bubbleStyle = useMemo20(
12243
12550
  () => ({
12244
12551
  background: resolvedAppearance.background,
12245
12552
  border: resolvedAppearance.border,
@@ -12626,10 +12933,10 @@ function HotspotAnchor({
12626
12933
  const tooltipAnchorRef = useRef26(null);
12627
12934
  const dragStateRef = useRef26(null);
12628
12935
  const suppressNextClickRef = useRef26(false);
12629
- const [hovered, setHovered] = useState13(false);
12630
- const [dragging, setDragging] = useState13(false);
12631
- const [pinned, setPinned] = useState13(false);
12632
- const tooltipContent = useMemo20(
12936
+ const [hovered, setHovered] = useState14(false);
12937
+ const [dragging, setDragging] = useState14(false);
12938
+ const [pinned, setPinned] = useState14(false);
12939
+ const tooltipContent = useMemo21(
12633
12940
  () => resolveHotspotTooltipContent(tooltip),
12634
12941
  [tooltip]
12635
12942
  );
@@ -12645,17 +12952,17 @@ function HotspotAnchor({
12645
12952
  scaledWidth,
12646
12953
  scaledHeight
12647
12954
  );
12648
- const worldPosition = useMemo20(
12955
+ const worldPosition = useMemo21(
12649
12956
  () => panoPositionToVector3(normalizedPosition, resolvedDistance),
12650
12957
  [normalizedPosition.pitch, normalizedPosition.yaw, resolvedDistance]
12651
12958
  );
12652
- const surfaceQuaternion3 = useMemo20(
12959
+ const surfaceQuaternion3 = useMemo21(
12653
12960
  () => createSurfaceQuaternion(normalizedPosition),
12654
12961
  [normalizedPosition.pitch, normalizedPosition.yaw]
12655
12962
  );
12656
12963
  const worldWidth = angularSizeToWorldSize(scaledWidth, resolvedDistance);
12657
12964
  const worldHeight = angularSizeToWorldSize(scaledHeight, resolvedDistance);
12658
- const focusGeometry = useMemo20(() => {
12965
+ const focusGeometry = useMemo21(() => {
12659
12966
  const planeGeometry = new PlaneGeometry5(1, 1);
12660
12967
  const geometry = new EdgesGeometry(planeGeometry);
12661
12968
  planeGeometry.dispose();
@@ -13076,7 +13383,7 @@ function HotspotPlane({
13076
13383
  import { jsx as jsx27 } from "react/jsx-runtime";
13077
13384
  function useHotspotImageTexture(src, onLoad, onError) {
13078
13385
  const gl = useThree21((state) => state.gl);
13079
- const [texture, setTexture] = useState14(null);
13386
+ const [texture, setTexture] = useState15(null);
13080
13387
  const onLoadRef = useRef27(onLoad);
13081
13388
  const onErrorRef = useRef27(onError);
13082
13389
  onLoadRef.current = onLoad;
@@ -13141,7 +13448,7 @@ function ImageHotspot({
13141
13448
  }
13142
13449
 
13143
13450
  // src/hotspot/polygon-hotspot.tsx
13144
- import { useEffect as useEffect30, useMemo as useMemo21, useRef as useRef29 } from "react";
13451
+ import { useEffect as useEffect30, useMemo as useMemo22, useRef as useRef29 } from "react";
13145
13452
  import {
13146
13453
  BufferGeometry,
13147
13454
  DoubleSide as DoubleSide5,
@@ -13486,14 +13793,14 @@ function PolygonHotspot({
13486
13793
  onInvalid,
13487
13794
  ...anchorProps
13488
13795
  }) {
13489
- const issues = useMemo21(() => validatePolygonVertices(vertices), [vertices]);
13796
+ const issues = useMemo22(() => validatePolygonVertices(vertices), [vertices]);
13490
13797
  const valid = issues.length === 0;
13491
- const center = useMemo21(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
13492
- const fillGeometry = useMemo21(
13798
+ const center = useMemo22(() => valid ? polygonCenter(vertices) : null, [valid, vertices]);
13799
+ const fillGeometry = useMemo22(
13493
13800
  () => valid && center ? makeFillGeometry(vertices, center) : null,
13494
13801
  [center, valid, vertices]
13495
13802
  );
13496
- const strokePoints = useMemo21(
13803
+ const strokePoints = useMemo22(
13497
13804
  () => valid && center ? makeStrokePositions(vertices, center) : null,
13498
13805
  [center, valid, vertices]
13499
13806
  );
@@ -13583,7 +13890,7 @@ function PolygonHotspot({
13583
13890
  }
13584
13891
 
13585
13892
  // src/hotspot/polyline-hotspot.tsx
13586
- import { useEffect as useEffect31, useMemo as useMemo22, useRef as useRef30 } from "react";
13893
+ import { useEffect as useEffect31, useMemo as useMemo23, useRef as useRef30 } from "react";
13587
13894
  import {
13588
13895
  MathUtils as MathUtils9,
13589
13896
  Matrix4 as Matrix44,
@@ -13707,10 +14014,10 @@ function PolylineHotspot({
13707
14014
  onInvalid,
13708
14015
  ...anchorProps
13709
14016
  }) {
13710
- const issues = useMemo22(() => validatePolylineVertices(vertices), [vertices]);
14017
+ const issues = useMemo23(() => validatePolylineVertices(vertices), [vertices]);
13711
14018
  const valid = issues.length === 0;
13712
- const center = useMemo22(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
13713
- const linePoints = useMemo22(
14019
+ const center = useMemo23(() => valid ? polylineCenter(vertices) : null, [valid, vertices]);
14020
+ const linePoints = useMemo23(
13714
14021
  () => valid && center ? makeLinePositions(vertices, center) : null,
13715
14022
  [center, valid, vertices]
13716
14023
  );
@@ -13782,7 +14089,7 @@ function PolylineHotspot({
13782
14089
  }
13783
14090
 
13784
14091
  // src/hotspot/graphic-hotspot.tsx
13785
- import { useEffect as useEffect32, useRef as useRef31, useState as useState15 } from "react";
14092
+ import { useEffect as useEffect32, useRef as useRef31, useState as useState16 } from "react";
13786
14093
  import {
13787
14094
  CanvasTexture,
13788
14095
  ClampToEdgeWrapping as ClampToEdgeWrapping4,
@@ -13979,7 +14286,7 @@ function createGraphicTexture(graphic, aspectWidth, aspectHeight) {
13979
14286
  return texture;
13980
14287
  }
13981
14288
  function useGraphicTexture(graphic, aspectWidth, aspectHeight, onLoad, onError) {
13982
- const [texture, setTexture] = useState15(null);
14289
+ const [texture, setTexture] = useState16(null);
13983
14290
  const onLoadRef = useRef31(onLoad);
13984
14291
  const onErrorRef = useRef31(onError);
13985
14292
  onLoadRef.current = onLoad;
@@ -14023,7 +14330,7 @@ function GraphicHotspot(props) {
14023
14330
 
14024
14331
  // src/hotspot/sequence-hotspot.tsx
14025
14332
  import { useFrame as useFrame15, useThree as useThree23 } from "@react-three/fiber";
14026
- import { useEffect as useEffect33, useRef as useRef32, useState as useState16 } from "react";
14333
+ import { useEffect as useEffect33, useRef as useRef32, useState as useState17 } from "react";
14027
14334
  import {
14028
14335
  ClampToEdgeWrapping as ClampToEdgeWrapping5,
14029
14336
  LinearFilter as LinearFilter8,
@@ -14046,7 +14353,7 @@ function useSpriteTexture({
14046
14353
  onLoadProgress
14047
14354
  }) {
14048
14355
  const gl = useThree23((state) => state.gl);
14049
- const [texture, setTexture] = useState16(null);
14356
+ const [texture, setTexture] = useState17(null);
14050
14357
  const onErrorRef = useRef32(onError);
14051
14358
  const onLoadProgressRef = useRef32(onLoadProgress);
14052
14359
  onErrorRef.current = onError;
@@ -14121,7 +14428,7 @@ function SequenceHotspot({
14121
14428
  const texture = useSpriteTexture({ id, src, onError, onLoadProgress });
14122
14429
  const frameCount = resolveFrameCount(requestedFrameCount);
14123
14430
  const safeFps = resolveFps(fps);
14124
- const [frameIndex, setFrameIndex] = useState16(0);
14431
+ const [frameIndex, setFrameIndex] = useState17(0);
14125
14432
  const elapsedRef = useRef32(0);
14126
14433
  const frameIndexRef = useRef32(0);
14127
14434
  const endedRef = useRef32(false);
@@ -14212,7 +14519,7 @@ function SequenceHotspot({
14212
14519
 
14213
14520
  // src/hotspot/video-hotspot.tsx
14214
14521
  import { useThree as useThree24 } from "@react-three/fiber";
14215
- import { useEffect as useEffect34, useRef as useRef33, useState as useState17 } from "react";
14522
+ import { useEffect as useEffect34, useRef as useRef33, useState as useState18 } from "react";
14216
14523
  import {
14217
14524
  ClampToEdgeWrapping as ClampToEdgeWrapping6,
14218
14525
  LinearFilter as LinearFilter9,
@@ -14230,7 +14537,7 @@ function clampVolume2(volume) {
14230
14537
  }
14231
14538
  function usePosterTexture2(id, poster, onError) {
14232
14539
  const gl = useThree24((state) => state.gl);
14233
- const [texture, setTexture] = useState17(null);
14540
+ const [texture, setTexture] = useState18(null);
14234
14541
  const onErrorRef = useRef33(onError);
14235
14542
  onErrorRef.current = onError;
14236
14543
  useEffect34(() => {
@@ -14288,7 +14595,7 @@ function useVideoResource({
14288
14595
  poster,
14289
14596
  src
14290
14597
  }) {
14291
- const [resource, setResource] = useState17(null);
14598
+ const [resource, setResource] = useState18(null);
14292
14599
  const onEndedRef = useRef33(onEnded);
14293
14600
  const onErrorRef = useRef33(onError);
14294
14601
  const onPlaybackStateChangeRef = useRef33(onPlaybackStateChange);
@@ -14438,7 +14745,7 @@ function VideoHotspot({
14438
14745
 
14439
14746
  // src/hotspot/audio-hotspot.tsx
14440
14747
  import { useFrame as useFrame16, useThree as useThree25 } from "@react-three/fiber";
14441
- import { useEffect as useEffect35, useRef as useRef34, useState as useState18 } from "react";
14748
+ import { useEffect as useEffect35, useRef as useRef34, useState as useState19 } from "react";
14442
14749
  import {
14443
14750
  CanvasTexture as CanvasTexture2,
14444
14751
  ClampToEdgeWrapping as ClampToEdgeWrapping7,
@@ -14511,7 +14818,7 @@ function createSpeakerTexture(state) {
14511
14818
  return texture;
14512
14819
  }
14513
14820
  function useSpeakerTextures(enabled) {
14514
- const [textures, setTextures] = useState18({ playing: null, stopped: null });
14821
+ const [textures, setTextures] = useState19({ playing: null, stopped: null });
14515
14822
  useEffect35(() => {
14516
14823
  if (!enabled) {
14517
14824
  setTextures({ playing: null, stopped: null });
@@ -14529,7 +14836,7 @@ function useSpeakerTextures(enabled) {
14529
14836
  }
14530
14837
  function useIconTexture(icon, enabled, onError, id) {
14531
14838
  const gl = useThree25((state) => state.gl);
14532
- const [texture, setTexture] = useState18(null);
14839
+ const [texture, setTexture] = useState19(null);
14533
14840
  const onErrorRef = useRef34(onError);
14534
14841
  onErrorRef.current = onError;
14535
14842
  useEffect35(() => {
@@ -14588,7 +14895,7 @@ function useAudioHowl({
14588
14895
  playing,
14589
14896
  src
14590
14897
  }) {
14591
- const [howl, setHowl] = useState18(null);
14898
+ const [howl, setHowl] = useState19(null);
14592
14899
  const playingRef = useRef34(playing);
14593
14900
  const loopRef = useRef34(loop ?? false);
14594
14901
  const onEndedRef = useRef34(onEnded);
@@ -14735,7 +15042,7 @@ function AudioHotspot({
14735
15042
  }
14736
15043
 
14737
15044
  // src/hotspot/text-hotspot.tsx
14738
- import { useEffect as useEffect36, useRef as useRef35, useState as useState19 } from "react";
15045
+ import { useEffect as useEffect36, useRef as useRef35, useState as useState20 } from "react";
14739
15046
  import {
14740
15047
  CanvasTexture as CanvasTexture3,
14741
15048
  ClampToEdgeWrapping as ClampToEdgeWrapping8,
@@ -14963,7 +15270,7 @@ function createTextTexture(text, width, height, style) {
14963
15270
  return texture;
14964
15271
  }
14965
15272
  function useTextTexture(text, width, height, style, onLoad, onError) {
14966
- const [texture, setTexture] = useState19(null);
15273
+ const [texture, setTexture] = useState20(null);
14967
15274
  const onLoadRef = useRef35(onLoad);
14968
15275
  const onErrorRef = useRef35(onError);
14969
15276
  onLoadRef.current = onLoad;