@liveroom-tech/react-immersive 1.0.0 → 1.0.2

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/README.md CHANGED
@@ -1018,6 +1018,8 @@ Example:
1018
1018
  />
1019
1019
  ```
1020
1020
 
1021
+ Passing an explicit `camera` counts as choosing the initial view yourself, so the mount-time auto-fit (see [`onAutoFit`](#onautofit)) is skipped automatically and your `position`/`fov` stick. Provide `onAutoFit` as well only if you want to run your _own_ fit-to-scene logic instead.
1022
+
1021
1023
  ### `backgroundColor`
1022
1024
 
1023
1025
  Optional background color for the viewer canvas.
@@ -1273,7 +1275,7 @@ true;
1273
1275
 
1274
1276
  ### `onAutoFit`
1275
1277
 
1276
- Optional async callback fired once the model has loaded and the scene is ready. When provided, the viewer calls it so consumers can run their own fit-to-scene behavior (e.g. an animated fit); if omitted, the viewer falls back to its internal `fitScene`.
1278
+ Optional async callback fired once the model has loaded and the scene is ready. When provided, the viewer calls it so consumers can run their own fit-to-scene behavior (e.g. an animated fit); if omitted, the viewer falls back to its internal `fitScene` — unless an explicit [`camera`](#camera) prop was given, in which case that position is treated as the chosen initial view and `fitScene` is skipped.
1277
1279
 
1278
1280
  Current callback shape:
1279
1281
 
package/dist/index.d.mts CHANGED
@@ -669,4 +669,22 @@ type ViewerActionsProps = {
669
669
  };
670
670
  declare function useViewerActions(objectBindings: Record<string, ObjectBinding>, options?: ViewerActionsOptions): ViewerActionsProps;
671
671
 
672
- export { type AnimationClipInfo, type AnimationControls, type AnimationLoopMode, type AnimationPlaybackState, BindingBuilder, type CaptureImageOptions, type CustomObjectBindingDataPanelProps, type CustomSceneObjectsPanelProps, type HiddenObjectsState, type HomeCameraState, MATERIAL_BLENDING_MODES, MATERIAL_SIDES, ModelViewer, type ModelViewerProps, type ObjectActionEvent, type ObjectBinding, type ObjectBindingAction, type ObjectBindingMaterial, type ObjectBindingMaterialBlendingMode, type ObjectBindingMaterialSide, type ObjectBindingTextureSlot, type ObjectVisibilityProps, type SceneAnimationClipConfig, type SceneAnimationConfig, type SceneConfig, SimpleModelViewer, type SimpleModelViewerProps, type ViewerActionsOptions, type ViewerActionsProps, type ViewerAnimationsProps, type ViewerCameraProps, type ViewerCameraState, type ViewerModelBounds, type ViewerModelProps, type ViewerReadyState, type ViewerSelectionProps, useObjectBinding, useObjectBindingIds, useObjectVisibility, useViewerActions, useViewerAnimations, useViewerCamera, useViewerHover, useViewerModel, useViewerSelection };
672
+ type UseShareableViewerStateOptions = {
673
+ cameraState: ViewerCameraState | null;
674
+ setCameraState: (state: ViewerCameraState, enableTransition?: boolean) => Promise<boolean>;
675
+ objectBindings: Record<string, ObjectBinding>;
676
+ onObjectBindingsChange: (next: Record<string, ObjectBinding>) => void;
677
+ selectedObjectBinding: ObjectBinding | null;
678
+ setSelectedObjectBinding: (binding: ObjectBinding | null) => void;
679
+ paramKey?: string;
680
+ debounceMs?: number;
681
+ autoSync?: boolean;
682
+ };
683
+ type ShareableViewerStateProps = {
684
+ getShareUrl: () => string;
685
+ syncUrl: () => void;
686
+ restoreFromUrl: () => Promise<boolean>;
687
+ };
688
+ declare function useShareableViewerState({ cameraState, setCameraState, objectBindings, onObjectBindingsChange, selectedObjectBinding, setSelectedObjectBinding, paramKey, debounceMs, autoSync, }: UseShareableViewerStateOptions): ShareableViewerStateProps;
689
+
690
+ export { type AnimationClipInfo, type AnimationControls, type AnimationLoopMode, type AnimationPlaybackState, BindingBuilder, type CaptureImageOptions, type CustomObjectBindingDataPanelProps, type CustomSceneObjectsPanelProps, type HiddenObjectsState, type HomeCameraState, MATERIAL_BLENDING_MODES, MATERIAL_SIDES, ModelViewer, type ModelViewerProps, type ObjectActionEvent, type ObjectBinding, type ObjectBindingAction, type ObjectBindingMaterial, type ObjectBindingMaterialBlendingMode, type ObjectBindingMaterialSide, type ObjectBindingTextureSlot, type ObjectVisibilityProps, type SceneAnimationClipConfig, type SceneAnimationConfig, type SceneConfig, SimpleModelViewer, type SimpleModelViewerProps, type ViewerActionsOptions, type ViewerActionsProps, type ViewerAnimationsProps, type ViewerCameraProps, type ViewerCameraState, type ViewerModelBounds, type ViewerModelProps, type ViewerReadyState, type ViewerSelectionProps, useObjectBinding, useObjectBindingIds, useObjectVisibility, useShareableViewerState, useViewerActions, useViewerAnimations, useViewerCamera, useViewerHover, useViewerModel, useViewerSelection };
package/dist/index.d.ts CHANGED
@@ -669,4 +669,22 @@ type ViewerActionsProps = {
669
669
  };
670
670
  declare function useViewerActions(objectBindings: Record<string, ObjectBinding>, options?: ViewerActionsOptions): ViewerActionsProps;
671
671
 
672
- export { type AnimationClipInfo, type AnimationControls, type AnimationLoopMode, type AnimationPlaybackState, BindingBuilder, type CaptureImageOptions, type CustomObjectBindingDataPanelProps, type CustomSceneObjectsPanelProps, type HiddenObjectsState, type HomeCameraState, MATERIAL_BLENDING_MODES, MATERIAL_SIDES, ModelViewer, type ModelViewerProps, type ObjectActionEvent, type ObjectBinding, type ObjectBindingAction, type ObjectBindingMaterial, type ObjectBindingMaterialBlendingMode, type ObjectBindingMaterialSide, type ObjectBindingTextureSlot, type ObjectVisibilityProps, type SceneAnimationClipConfig, type SceneAnimationConfig, type SceneConfig, SimpleModelViewer, type SimpleModelViewerProps, type ViewerActionsOptions, type ViewerActionsProps, type ViewerAnimationsProps, type ViewerCameraProps, type ViewerCameraState, type ViewerModelBounds, type ViewerModelProps, type ViewerReadyState, type ViewerSelectionProps, useObjectBinding, useObjectBindingIds, useObjectVisibility, useViewerActions, useViewerAnimations, useViewerCamera, useViewerHover, useViewerModel, useViewerSelection };
672
+ type UseShareableViewerStateOptions = {
673
+ cameraState: ViewerCameraState | null;
674
+ setCameraState: (state: ViewerCameraState, enableTransition?: boolean) => Promise<boolean>;
675
+ objectBindings: Record<string, ObjectBinding>;
676
+ onObjectBindingsChange: (next: Record<string, ObjectBinding>) => void;
677
+ selectedObjectBinding: ObjectBinding | null;
678
+ setSelectedObjectBinding: (binding: ObjectBinding | null) => void;
679
+ paramKey?: string;
680
+ debounceMs?: number;
681
+ autoSync?: boolean;
682
+ };
683
+ type ShareableViewerStateProps = {
684
+ getShareUrl: () => string;
685
+ syncUrl: () => void;
686
+ restoreFromUrl: () => Promise<boolean>;
687
+ };
688
+ declare function useShareableViewerState({ cameraState, setCameraState, objectBindings, onObjectBindingsChange, selectedObjectBinding, setSelectedObjectBinding, paramKey, debounceMs, autoSync, }: UseShareableViewerStateOptions): ShareableViewerStateProps;
689
+
690
+ export { type AnimationClipInfo, type AnimationControls, type AnimationLoopMode, type AnimationPlaybackState, BindingBuilder, type CaptureImageOptions, type CustomObjectBindingDataPanelProps, type CustomSceneObjectsPanelProps, type HiddenObjectsState, type HomeCameraState, MATERIAL_BLENDING_MODES, MATERIAL_SIDES, ModelViewer, type ModelViewerProps, type ObjectActionEvent, type ObjectBinding, type ObjectBindingAction, type ObjectBindingMaterial, type ObjectBindingMaterialBlendingMode, type ObjectBindingMaterialSide, type ObjectBindingTextureSlot, type ObjectVisibilityProps, type SceneAnimationClipConfig, type SceneAnimationConfig, type SceneConfig, SimpleModelViewer, type SimpleModelViewerProps, type ViewerActionsOptions, type ViewerActionsProps, type ViewerAnimationsProps, type ViewerCameraProps, type ViewerCameraState, type ViewerModelBounds, type ViewerModelProps, type ViewerReadyState, type ViewerSelectionProps, useObjectBinding, useObjectBindingIds, useObjectVisibility, useShareableViewerState, useViewerActions, useViewerAnimations, useViewerCamera, useViewerHover, useViewerModel, useViewerSelection };
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@ __export(index_exports, {
38
38
  useObjectBinding: () => useObjectBinding,
39
39
  useObjectBindingIds: () => useObjectBindingIds,
40
40
  useObjectVisibility: () => useObjectVisibility,
41
+ useShareableViewerState: () => useShareableViewerState,
41
42
  useViewerActions: () => useViewerActions,
42
43
  useViewerAnimations: () => useViewerAnimations,
43
44
  useViewerCamera: () => useViewerCamera,
@@ -5025,10 +5026,12 @@ function ModelViewer({
5025
5026
  const sceneRef = (0, import_react21.useRef)(null);
5026
5027
  const fitAttemptedRef = (0, import_react21.useRef)(false);
5027
5028
  const fitInFlightRef = (0, import_react21.useRef)(false);
5029
+ const cameraProvidedRef = (0, import_react21.useRef)(Boolean(camera));
5030
+ cameraProvidedRef.current = Boolean(camera);
5028
5031
  const initialCameraStateRef = (0, import_react21.useRef)(null);
5029
5032
  const [isAutoFraming, setIsAutoFraming] = (0, import_react21.useState)(Boolean(modelUrl));
5030
5033
  const [cameraPositioned, setCameraPositioned] = (0, import_react21.useState)(
5031
- !modelUrl || Boolean(onAutoFit)
5034
+ !modelUrl || Boolean(onAutoFit) || Boolean(camera)
5032
5035
  );
5033
5036
  const [downloadMenuOpen, setDownloadMenuOpen] = (0, import_react21.useState)(false);
5034
5037
  const [modelLoaded, setModelLoaded] = (0, import_react21.useState)(false);
@@ -5846,7 +5849,7 @@ function ModelViewer({
5846
5849
  return;
5847
5850
  }
5848
5851
  setIsAutoFraming(true);
5849
- setCameraPositioned(Boolean(onAutoFit));
5852
+ setCameraPositioned(Boolean(onAutoFit) || cameraProvidedRef.current);
5850
5853
  let cancelled = false;
5851
5854
  let timer = null;
5852
5855
  const scheduleCheck = (delay) => {
@@ -5870,7 +5873,7 @@ function ModelViewer({
5870
5873
  return;
5871
5874
  }
5872
5875
  fitInFlightRef.current = true;
5873
- const fitPromise = onAutoFit ? onAutoFit() : fitScene(true);
5876
+ const fitPromise = onAutoFit ? onAutoFit() : cameraProvidedRef.current ? Promise.resolve(true) : fitScene(true);
5874
5877
  fitPromise.then((didFit) => {
5875
5878
  fitAttemptedRef.current = didFit !== false;
5876
5879
  if (fitAttemptedRef.current) {
@@ -15893,6 +15896,150 @@ function useViewerActions(objectBindings, options = {}) {
15893
15896
  runAction
15894
15897
  };
15895
15898
  }
15899
+
15900
+ // src/hooks/useShareableViewerState.ts
15901
+ var import_react46 = require("react");
15902
+ function serializeState(cameraState, objectBindings, selectedObjectBinding) {
15903
+ const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
15904
+ return {
15905
+ camera: cameraState ?? void 0,
15906
+ hidden,
15907
+ selected: selectedObjectBinding?.id ?? null
15908
+ };
15909
+ }
15910
+ function useShareableViewerState({
15911
+ cameraState,
15912
+ setCameraState,
15913
+ objectBindings,
15914
+ onObjectBindingsChange,
15915
+ selectedObjectBinding,
15916
+ setSelectedObjectBinding,
15917
+ paramKey = "view",
15918
+ debounceMs = 400,
15919
+ autoSync = true
15920
+ }) {
15921
+ const lastWrittenRawRef = (0, import_react46.useRef)(null);
15922
+ const buildHash = (0, import_react46.useCallback)(() => {
15923
+ const state = serializeState(
15924
+ cameraState,
15925
+ objectBindings,
15926
+ selectedObjectBinding
15927
+ );
15928
+ return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
15929
+ }, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
15930
+ const getShareUrl = (0, import_react46.useCallback)(() => {
15931
+ const url = new URL(window.location.href);
15932
+ url.hash = buildHash();
15933
+ return url.toString();
15934
+ }, [buildHash]);
15935
+ const syncUrl = (0, import_react46.useCallback)(() => {
15936
+ const url = new URL(window.location.href);
15937
+ url.hash = buildHash();
15938
+ window.history.replaceState(null, "", url.toString());
15939
+ lastWrittenRawRef.current = new URLSearchParams(url.hash.slice(1)).get(
15940
+ paramKey
15941
+ );
15942
+ }, [buildHash, paramKey]);
15943
+ const latestStateRef = (0, import_react46.useRef)({
15944
+ cameraState,
15945
+ objectBindings,
15946
+ selectedObjectBinding
15947
+ });
15948
+ latestStateRef.current = {
15949
+ cameraState,
15950
+ objectBindings,
15951
+ selectedObjectBinding
15952
+ };
15953
+ const [restoreAttempted, setRestoreAttempted] = (0, import_react46.useState)(false);
15954
+ const lastSyncedHashRef = (0, import_react46.useRef)(null);
15955
+ (0, import_react46.useEffect)(() => {
15956
+ if (!autoSync || !restoreAttempted) return;
15957
+ const tick = () => {
15958
+ const current = latestStateRef.current;
15959
+ const state = serializeState(
15960
+ current.cameraState,
15961
+ current.objectBindings,
15962
+ current.selectedObjectBinding
15963
+ );
15964
+ const hash = `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
15965
+ if (hash === lastSyncedHashRef.current) return;
15966
+ lastSyncedHashRef.current = hash;
15967
+ const url = new URL(window.location.href);
15968
+ url.hash = hash;
15969
+ window.history.replaceState(null, "", url.toString());
15970
+ lastWrittenRawRef.current = new URLSearchParams(url.hash.slice(1)).get(
15971
+ paramKey
15972
+ );
15973
+ };
15974
+ const id = window.setInterval(tick, debounceMs);
15975
+ return () => window.clearInterval(id);
15976
+ }, [autoSync, restoreAttempted, debounceMs, paramKey]);
15977
+ const lastRestoredRawRef = (0, import_react46.useRef)(null);
15978
+ const restoringRawRef = (0, import_react46.useRef)(null);
15979
+ const restoreFromUrl = (0, import_react46.useCallback)(async () => {
15980
+ setRestoreAttempted(true);
15981
+ const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
15982
+ const raw = new URLSearchParams(rawHash).get(paramKey);
15983
+ if (!raw) {
15984
+ lastRestoredRawRef.current = null;
15985
+ return false;
15986
+ }
15987
+ if (raw === lastWrittenRawRef.current || raw === lastRestoredRawRef.current || raw === restoringRawRef.current) {
15988
+ return false;
15989
+ }
15990
+ let state;
15991
+ try {
15992
+ state = JSON.parse(raw);
15993
+ } catch {
15994
+ return false;
15995
+ }
15996
+ restoringRawRef.current = raw;
15997
+ try {
15998
+ if (state.camera) {
15999
+ await setCameraState(state.camera, false);
16000
+ }
16001
+ if (state.hidden) {
16002
+ const hiddenIds = new Set(state.hidden);
16003
+ onObjectBindingsChange(
16004
+ Object.fromEntries(
16005
+ Object.entries(objectBindings).map(([bindingKey, binding]) => [
16006
+ bindingKey,
16007
+ { ...binding, visible: !hiddenIds.has(binding.id) }
16008
+ ])
16009
+ )
16010
+ );
16011
+ }
16012
+ if (state.selected !== void 0) {
16013
+ const bindingKey = state.selected ? resolveBindingKey(objectBindings, state.selected) : null;
16014
+ setSelectedObjectBinding(
16015
+ bindingKey ? objectBindings[bindingKey] : null
16016
+ );
16017
+ }
16018
+ lastRestoredRawRef.current = raw;
16019
+ return true;
16020
+ } finally {
16021
+ if (restoringRawRef.current === raw) {
16022
+ restoringRawRef.current = null;
16023
+ }
16024
+ }
16025
+ }, [
16026
+ objectBindings,
16027
+ onObjectBindingsChange,
16028
+ paramKey,
16029
+ setCameraState,
16030
+ setSelectedObjectBinding
16031
+ ]);
16032
+ (0, import_react46.useEffect)(() => {
16033
+ const handleHashChange = () => {
16034
+ void restoreFromUrl();
16035
+ };
16036
+ window.addEventListener("hashchange", handleHashChange);
16037
+ return () => {
16038
+ window.removeEventListener("hashchange", handleHashChange);
16039
+ };
16040
+ }, [restoreFromUrl]);
16041
+ return { getShareUrl, syncUrl, restoreFromUrl };
16042
+ }
15896
16043
  // Annotate the CommonJS export names for ESM import in node:
15897
16044
  0 && (module.exports = {
15898
16045
  BindingBuilder,
@@ -15903,6 +16050,7 @@ function useViewerActions(objectBindings, options = {}) {
15903
16050
  useObjectBinding,
15904
16051
  useObjectBindingIds,
15905
16052
  useObjectVisibility,
16053
+ useShareableViewerState,
15906
16054
  useViewerActions,
15907
16055
  useViewerAnimations,
15908
16056
  useViewerCamera,
package/dist/index.mjs CHANGED
@@ -5083,10 +5083,12 @@ function ModelViewer({
5083
5083
  const sceneRef = useRef9(null);
5084
5084
  const fitAttemptedRef = useRef9(false);
5085
5085
  const fitInFlightRef = useRef9(false);
5086
+ const cameraProvidedRef = useRef9(Boolean(camera));
5087
+ cameraProvidedRef.current = Boolean(camera);
5086
5088
  const initialCameraStateRef = useRef9(null);
5087
5089
  const [isAutoFraming, setIsAutoFraming] = useState10(Boolean(modelUrl));
5088
5090
  const [cameraPositioned, setCameraPositioned] = useState10(
5089
- !modelUrl || Boolean(onAutoFit)
5091
+ !modelUrl || Boolean(onAutoFit) || Boolean(camera)
5090
5092
  );
5091
5093
  const [downloadMenuOpen, setDownloadMenuOpen] = useState10(false);
5092
5094
  const [modelLoaded, setModelLoaded] = useState10(false);
@@ -5904,7 +5906,7 @@ function ModelViewer({
5904
5906
  return;
5905
5907
  }
5906
5908
  setIsAutoFraming(true);
5907
- setCameraPositioned(Boolean(onAutoFit));
5909
+ setCameraPositioned(Boolean(onAutoFit) || cameraProvidedRef.current);
5908
5910
  let cancelled = false;
5909
5911
  let timer = null;
5910
5912
  const scheduleCheck = (delay) => {
@@ -5928,7 +5930,7 @@ function ModelViewer({
5928
5930
  return;
5929
5931
  }
5930
5932
  fitInFlightRef.current = true;
5931
- const fitPromise = onAutoFit ? onAutoFit() : fitScene(true);
5933
+ const fitPromise = onAutoFit ? onAutoFit() : cameraProvidedRef.current ? Promise.resolve(true) : fitScene(true);
5932
5934
  fitPromise.then((didFit) => {
5933
5935
  fitAttemptedRef.current = didFit !== false;
5934
5936
  if (fitAttemptedRef.current) {
@@ -15989,6 +15991,150 @@ function useViewerActions(objectBindings, options = {}) {
15989
15991
  runAction
15990
15992
  };
15991
15993
  }
15994
+
15995
+ // src/hooks/useShareableViewerState.ts
15996
+ import { useCallback as useCallback15, useEffect as useEffect20, useRef as useRef18, useState as useState29 } from "react";
15997
+ function serializeState(cameraState, objectBindings, selectedObjectBinding) {
15998
+ const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
15999
+ return {
16000
+ camera: cameraState ?? void 0,
16001
+ hidden,
16002
+ selected: selectedObjectBinding?.id ?? null
16003
+ };
16004
+ }
16005
+ function useShareableViewerState({
16006
+ cameraState,
16007
+ setCameraState,
16008
+ objectBindings,
16009
+ onObjectBindingsChange,
16010
+ selectedObjectBinding,
16011
+ setSelectedObjectBinding,
16012
+ paramKey = "view",
16013
+ debounceMs = 400,
16014
+ autoSync = true
16015
+ }) {
16016
+ const lastWrittenRawRef = useRef18(null);
16017
+ const buildHash = useCallback15(() => {
16018
+ const state = serializeState(
16019
+ cameraState,
16020
+ objectBindings,
16021
+ selectedObjectBinding
16022
+ );
16023
+ return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
16024
+ }, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
16025
+ const getShareUrl = useCallback15(() => {
16026
+ const url = new URL(window.location.href);
16027
+ url.hash = buildHash();
16028
+ return url.toString();
16029
+ }, [buildHash]);
16030
+ const syncUrl = useCallback15(() => {
16031
+ const url = new URL(window.location.href);
16032
+ url.hash = buildHash();
16033
+ window.history.replaceState(null, "", url.toString());
16034
+ lastWrittenRawRef.current = new URLSearchParams(url.hash.slice(1)).get(
16035
+ paramKey
16036
+ );
16037
+ }, [buildHash, paramKey]);
16038
+ const latestStateRef = useRef18({
16039
+ cameraState,
16040
+ objectBindings,
16041
+ selectedObjectBinding
16042
+ });
16043
+ latestStateRef.current = {
16044
+ cameraState,
16045
+ objectBindings,
16046
+ selectedObjectBinding
16047
+ };
16048
+ const [restoreAttempted, setRestoreAttempted] = useState29(false);
16049
+ const lastSyncedHashRef = useRef18(null);
16050
+ useEffect20(() => {
16051
+ if (!autoSync || !restoreAttempted) return;
16052
+ const tick = () => {
16053
+ const current = latestStateRef.current;
16054
+ const state = serializeState(
16055
+ current.cameraState,
16056
+ current.objectBindings,
16057
+ current.selectedObjectBinding
16058
+ );
16059
+ const hash = `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
16060
+ if (hash === lastSyncedHashRef.current) return;
16061
+ lastSyncedHashRef.current = hash;
16062
+ const url = new URL(window.location.href);
16063
+ url.hash = hash;
16064
+ window.history.replaceState(null, "", url.toString());
16065
+ lastWrittenRawRef.current = new URLSearchParams(url.hash.slice(1)).get(
16066
+ paramKey
16067
+ );
16068
+ };
16069
+ const id = window.setInterval(tick, debounceMs);
16070
+ return () => window.clearInterval(id);
16071
+ }, [autoSync, restoreAttempted, debounceMs, paramKey]);
16072
+ const lastRestoredRawRef = useRef18(null);
16073
+ const restoringRawRef = useRef18(null);
16074
+ const restoreFromUrl = useCallback15(async () => {
16075
+ setRestoreAttempted(true);
16076
+ const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
16077
+ const raw = new URLSearchParams(rawHash).get(paramKey);
16078
+ if (!raw) {
16079
+ lastRestoredRawRef.current = null;
16080
+ return false;
16081
+ }
16082
+ if (raw === lastWrittenRawRef.current || raw === lastRestoredRawRef.current || raw === restoringRawRef.current) {
16083
+ return false;
16084
+ }
16085
+ let state;
16086
+ try {
16087
+ state = JSON.parse(raw);
16088
+ } catch {
16089
+ return false;
16090
+ }
16091
+ restoringRawRef.current = raw;
16092
+ try {
16093
+ if (state.camera) {
16094
+ await setCameraState(state.camera, false);
16095
+ }
16096
+ if (state.hidden) {
16097
+ const hiddenIds = new Set(state.hidden);
16098
+ onObjectBindingsChange(
16099
+ Object.fromEntries(
16100
+ Object.entries(objectBindings).map(([bindingKey, binding]) => [
16101
+ bindingKey,
16102
+ { ...binding, visible: !hiddenIds.has(binding.id) }
16103
+ ])
16104
+ )
16105
+ );
16106
+ }
16107
+ if (state.selected !== void 0) {
16108
+ const bindingKey = state.selected ? resolveBindingKey(objectBindings, state.selected) : null;
16109
+ setSelectedObjectBinding(
16110
+ bindingKey ? objectBindings[bindingKey] : null
16111
+ );
16112
+ }
16113
+ lastRestoredRawRef.current = raw;
16114
+ return true;
16115
+ } finally {
16116
+ if (restoringRawRef.current === raw) {
16117
+ restoringRawRef.current = null;
16118
+ }
16119
+ }
16120
+ }, [
16121
+ objectBindings,
16122
+ onObjectBindingsChange,
16123
+ paramKey,
16124
+ setCameraState,
16125
+ setSelectedObjectBinding
16126
+ ]);
16127
+ useEffect20(() => {
16128
+ const handleHashChange = () => {
16129
+ void restoreFromUrl();
16130
+ };
16131
+ window.addEventListener("hashchange", handleHashChange);
16132
+ return () => {
16133
+ window.removeEventListener("hashchange", handleHashChange);
16134
+ };
16135
+ }, [restoreFromUrl]);
16136
+ return { getShareUrl, syncUrl, restoreFromUrl };
16137
+ }
15992
16138
  export {
15993
16139
  BindingBuilder,
15994
16140
  MATERIAL_BLENDING_MODES,
@@ -15998,6 +16144,7 @@ export {
15998
16144
  useObjectBinding,
15999
16145
  useObjectBindingIds,
16000
16146
  useObjectVisibility,
16147
+ useShareableViewerState,
16001
16148
  useViewerActions,
16002
16149
  useViewerAnimations,
16003
16150
  useViewerCamera,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liveroom-tech/react-immersive",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -75,7 +75,7 @@
75
75
  "homepage": "https://react-immersive.liveroom.dev",
76
76
  "repository": {
77
77
  "type": "git",
78
- "url": "https://gitlab.liveroom.xyz/team_liveroom/react-immersive-docs.git"
78
+ "url": "https://github.com/liveroom-technologies/react-immersive-docs.git"
79
79
  },
80
80
  "bugs": {
81
81
  "url": "https://github.com/liveroom-technologies/react-immersive-docs/issues"