@liveroom-tech/react-immersive 2.0.0 → 2.1.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/README.md CHANGED
@@ -12,10 +12,12 @@ import {
12
12
  material,
13
13
  ModelViewer,
14
14
  patchBindings,
15
+ patchSceneConfig,
15
16
  SimpleModelViewer,
16
17
  useObjectBinding,
17
18
  useObjectBindingIds,
18
19
  useObjectBindings,
20
+ useSceneConfig,
19
21
  useViewerActions,
20
22
  useViewerAnimations,
21
23
  useViewerCamera,
@@ -27,7 +29,7 @@ import {
27
29
  } from "@liveroom-tech/react-immersive";
28
30
  ```
29
31
 
30
- It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindingsController`, `ObjectActionEvent`, `SceneConfig`, `AnimationControls`, `CinematicConfig` / `CinematicWaypoint`, and related shapes used throughout this document.
32
+ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindingsController`, `ObjectActionEvent`, `SceneConfig`, `SceneConfigPatch`, `SceneConfigController`, `AnimationControls`, `CinematicConfig` / `CinematicWaypoint`, and related shapes used throughout this document.
31
33
 
32
34
  `ModelViewer` provides:
33
35
 
@@ -334,7 +336,7 @@ If you want one state owner for binding patches, materials, and visibility, use
334
336
  const {
335
337
  objectBindings,
336
338
  setObjectBindings,
337
- patch,
339
+ updateObjectBindings,
338
340
  setMaterial,
339
341
  setBaseColor,
340
342
  resetMaterial,
@@ -356,7 +358,7 @@ const {
356
358
  />;
357
359
  ```
358
360
 
359
- `useObjectBindings` owns the state used by the viewer. Every setter uses a functional update, so consecutive material, visibility, and general patch calls in the same tick compose instead of overwriting each other.
361
+ `useObjectBindings` owns the state used by the viewer. Every setter uses a functional update, so consecutive material, visibility, and general binding updates in the same tick compose instead of overwriting each other.
360
362
 
361
363
  Object identifiers accepted by its methods can be:
362
364
 
@@ -367,7 +369,7 @@ Object identifiers accepted by its methods can be:
367
369
  The hook returns:
368
370
 
369
371
  - `objectBindings` and `setObjectBindings`
370
- - `patch` for any binding field or atomic edit batch
372
+ - `updateObjectBindings` for any binding field or atomic edit batch
371
373
  - `setMaterial`, `setBaseColor`, `resetMaterial`, and `getMaterial`
372
374
  - `hiddenObjects`: a derived map of hidden binding keys
373
375
  - `hiddenObjectIds`: the currently hidden binding IDs
@@ -375,6 +377,37 @@ The hook returns:
375
377
  - `isObjectHidden`
376
378
  - `clearHiddenObjects`
377
379
 
380
+ For scene-wide state, use `useSceneConfig`:
381
+
382
+ ```tsx
383
+ const {
384
+ sceneConfig,
385
+ setSceneConfig,
386
+ updateSceneConfig,
387
+ resetSceneConfig,
388
+ } = useSceneConfig(initialSceneConfig);
389
+
390
+ const enableUvChecker = () =>
391
+ updateSceneConfig({
392
+ model: { renderer: "uv-checker" },
393
+ lighting: { ambient: { intensity: 0.5 } },
394
+ });
395
+
396
+ <ModelViewer
397
+ modelUrl="/model.glb"
398
+ licenseKey="your-license-key"
399
+ objectBindings={objectBindings}
400
+ sceneConfig={sceneConfig}
401
+ />;
402
+
403
+ <button onClick={enableUvChecker}>Show UV checker</button>;
404
+ ```
405
+
406
+ Nested objects merge recursively, while arrays and tuples are replaced. Every
407
+ update uses a functional state updater, so consecutive calls compose. Use the
408
+ pure `patchSceneConfig(sceneConfig, patch)` utility when a store or parent owns
409
+ the scene state.
410
+
378
411
  If you want to keep hover state in sync with the viewer, the library also exports:
379
412
 
380
413
  ```tsx
package/dist/index.d.mts CHANGED
@@ -664,9 +664,10 @@ declare function useObjectBinding(objectBindings: Record<string, ObjectBinding>,
664
664
 
665
665
  declare function useObjectBindingIds(objectBindings: Record<string, ObjectBinding>): string[];
666
666
 
667
- type DeepPartial<T> = T extends (infer _U)[] ? T : T extends object ? {
667
+ type DeepPartial<T> = T extends readonly unknown[] ? T : T extends object ? {
668
668
  [K in keyof T]?: DeepPartial<T[K]>;
669
669
  } : T;
670
+
670
671
  type ObjectBindingPatch = DeepPartial<ObjectBinding>;
671
672
  type BindingEdit = {
672
673
  ids: string | string[];
@@ -689,7 +690,7 @@ type HiddenObjectsState = Record<string, boolean>;
689
690
  type ObjectBindingsController = {
690
691
  objectBindings: Record<string, ObjectBinding>;
691
692
  setObjectBindings: React.Dispatch<React.SetStateAction<Record<string, ObjectBinding>>>;
692
- patch: {
693
+ updateObjectBindings: {
693
694
  (objectId: string | string[], patch: ObjectBindingPatch): void;
694
695
  (edits: BindingEdit[]): void;
695
696
  };
@@ -714,6 +715,26 @@ type ObjectBindingsController = {
714
715
  */
715
716
  declare function useObjectBindings(initialBindings: Record<string, ObjectBinding> | (() => Record<string, ObjectBinding>)): ObjectBindingsController;
716
717
 
718
+ type SceneConfigPatch = DeepPartial<SceneConfig>;
719
+ /**
720
+ * Applies a nested scene-config patch without mutating either input. Objects
721
+ * are merged recursively, while arrays and tuples are replaced wholesale.
722
+ */
723
+ declare function patchSceneConfig(sceneConfig: SceneConfig, patch: SceneConfigPatch): SceneConfig;
724
+
725
+ type SceneConfigUpdate = SceneConfigPatch | ((current: SceneConfig) => SceneConfigPatch);
726
+ type SceneConfigController = {
727
+ sceneConfig: SceneConfig;
728
+ setSceneConfig: React.Dispatch<React.SetStateAction<SceneConfig>>;
729
+ updateSceneConfig: (update: SceneConfigUpdate) => void;
730
+ resetSceneConfig: () => void;
731
+ };
732
+ /**
733
+ * Owns scene-config state and applies nested patches through functional React
734
+ * updates, so consecutive calls in the same tick compose safely.
735
+ */
736
+ declare function useSceneConfig(initialSceneConfig: SceneConfig | (() => SceneConfig)): SceneConfigController;
737
+
717
738
  type ViewerHoverProps = {
718
739
  hoveredObjectBinding: ObjectBinding | null;
719
740
  handleHoveredObject: (binding: ObjectBinding | null) => void;
@@ -766,4 +787,4 @@ type ShareableViewerStateProps = {
766
787
  };
767
788
  declare function useShareableViewerState({ cameraState, setCameraState, objectBindings, onObjectBindingsChange, selectedObjectBinding, setSelectedObjectBinding, paramKey, debounceMs, autoSync, }: UseShareableViewerStateOptions): ShareableViewerStateProps;
768
789
 
769
- export { type AnimationClipInfo, type AnimationControls, type AnimationLoopMode, type AnimationPlaybackState, BindingBuilder, type BindingEdit, type CaptureImageOptions, type CinematicConfig, type CinematicWaypoint, 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 ObjectBindingPatch, type ObjectBindingTextureSlot, type ObjectBindingsController, type SceneAnimationClipConfig, type SceneAnimationConfig, type SceneCinematicConfig, type SceneCinematicWaypoint, type SceneConfig, type SceneModelRenderer, SimpleModelViewer, type SimpleModelViewerProps, type ViewerActionsOptions, type ViewerActionsProps, type ViewerAnimationsProps, type ViewerCameraProps, type ViewerCameraState, type ViewerModelBounds, type ViewerModelProps, type ViewerReadyState, type ViewerSelectionProps, type XRScaleMode, material, patchBindings, useObjectBinding, useObjectBindingIds, useObjectBindings, useShareableViewerState, useViewerActions, useViewerAnimations, useViewerCamera, useViewerHover, useViewerModel, useViewerSelection };
790
+ export { type AnimationClipInfo, type AnimationControls, type AnimationLoopMode, type AnimationPlaybackState, BindingBuilder, type BindingEdit, type CaptureImageOptions, type CinematicConfig, type CinematicWaypoint, 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 ObjectBindingPatch, type ObjectBindingTextureSlot, type ObjectBindingsController, type SceneAnimationClipConfig, type SceneAnimationConfig, type SceneCinematicConfig, type SceneCinematicWaypoint, type SceneConfig, type SceneConfigController, type SceneConfigPatch, type SceneConfigUpdate, type SceneModelRenderer, SimpleModelViewer, type SimpleModelViewerProps, type ViewerActionsOptions, type ViewerActionsProps, type ViewerAnimationsProps, type ViewerCameraProps, type ViewerCameraState, type ViewerModelBounds, type ViewerModelProps, type ViewerReadyState, type ViewerSelectionProps, type XRScaleMode, material, patchBindings, patchSceneConfig, useObjectBinding, useObjectBindingIds, useObjectBindings, useSceneConfig, useShareableViewerState, useViewerActions, useViewerAnimations, useViewerCamera, useViewerHover, useViewerModel, useViewerSelection };
package/dist/index.d.ts CHANGED
@@ -664,9 +664,10 @@ declare function useObjectBinding(objectBindings: Record<string, ObjectBinding>,
664
664
 
665
665
  declare function useObjectBindingIds(objectBindings: Record<string, ObjectBinding>): string[];
666
666
 
667
- type DeepPartial<T> = T extends (infer _U)[] ? T : T extends object ? {
667
+ type DeepPartial<T> = T extends readonly unknown[] ? T : T extends object ? {
668
668
  [K in keyof T]?: DeepPartial<T[K]>;
669
669
  } : T;
670
+
670
671
  type ObjectBindingPatch = DeepPartial<ObjectBinding>;
671
672
  type BindingEdit = {
672
673
  ids: string | string[];
@@ -689,7 +690,7 @@ type HiddenObjectsState = Record<string, boolean>;
689
690
  type ObjectBindingsController = {
690
691
  objectBindings: Record<string, ObjectBinding>;
691
692
  setObjectBindings: React.Dispatch<React.SetStateAction<Record<string, ObjectBinding>>>;
692
- patch: {
693
+ updateObjectBindings: {
693
694
  (objectId: string | string[], patch: ObjectBindingPatch): void;
694
695
  (edits: BindingEdit[]): void;
695
696
  };
@@ -714,6 +715,26 @@ type ObjectBindingsController = {
714
715
  */
715
716
  declare function useObjectBindings(initialBindings: Record<string, ObjectBinding> | (() => Record<string, ObjectBinding>)): ObjectBindingsController;
716
717
 
718
+ type SceneConfigPatch = DeepPartial<SceneConfig>;
719
+ /**
720
+ * Applies a nested scene-config patch without mutating either input. Objects
721
+ * are merged recursively, while arrays and tuples are replaced wholesale.
722
+ */
723
+ declare function patchSceneConfig(sceneConfig: SceneConfig, patch: SceneConfigPatch): SceneConfig;
724
+
725
+ type SceneConfigUpdate = SceneConfigPatch | ((current: SceneConfig) => SceneConfigPatch);
726
+ type SceneConfigController = {
727
+ sceneConfig: SceneConfig;
728
+ setSceneConfig: React.Dispatch<React.SetStateAction<SceneConfig>>;
729
+ updateSceneConfig: (update: SceneConfigUpdate) => void;
730
+ resetSceneConfig: () => void;
731
+ };
732
+ /**
733
+ * Owns scene-config state and applies nested patches through functional React
734
+ * updates, so consecutive calls in the same tick compose safely.
735
+ */
736
+ declare function useSceneConfig(initialSceneConfig: SceneConfig | (() => SceneConfig)): SceneConfigController;
737
+
717
738
  type ViewerHoverProps = {
718
739
  hoveredObjectBinding: ObjectBinding | null;
719
740
  handleHoveredObject: (binding: ObjectBinding | null) => void;
@@ -766,4 +787,4 @@ type ShareableViewerStateProps = {
766
787
  };
767
788
  declare function useShareableViewerState({ cameraState, setCameraState, objectBindings, onObjectBindingsChange, selectedObjectBinding, setSelectedObjectBinding, paramKey, debounceMs, autoSync, }: UseShareableViewerStateOptions): ShareableViewerStateProps;
768
789
 
769
- export { type AnimationClipInfo, type AnimationControls, type AnimationLoopMode, type AnimationPlaybackState, BindingBuilder, type BindingEdit, type CaptureImageOptions, type CinematicConfig, type CinematicWaypoint, 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 ObjectBindingPatch, type ObjectBindingTextureSlot, type ObjectBindingsController, type SceneAnimationClipConfig, type SceneAnimationConfig, type SceneCinematicConfig, type SceneCinematicWaypoint, type SceneConfig, type SceneModelRenderer, SimpleModelViewer, type SimpleModelViewerProps, type ViewerActionsOptions, type ViewerActionsProps, type ViewerAnimationsProps, type ViewerCameraProps, type ViewerCameraState, type ViewerModelBounds, type ViewerModelProps, type ViewerReadyState, type ViewerSelectionProps, type XRScaleMode, material, patchBindings, useObjectBinding, useObjectBindingIds, useObjectBindings, useShareableViewerState, useViewerActions, useViewerAnimations, useViewerCamera, useViewerHover, useViewerModel, useViewerSelection };
790
+ export { type AnimationClipInfo, type AnimationControls, type AnimationLoopMode, type AnimationPlaybackState, BindingBuilder, type BindingEdit, type CaptureImageOptions, type CinematicConfig, type CinematicWaypoint, 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 ObjectBindingPatch, type ObjectBindingTextureSlot, type ObjectBindingsController, type SceneAnimationClipConfig, type SceneAnimationConfig, type SceneCinematicConfig, type SceneCinematicWaypoint, type SceneConfig, type SceneConfigController, type SceneConfigPatch, type SceneConfigUpdate, type SceneModelRenderer, SimpleModelViewer, type SimpleModelViewerProps, type ViewerActionsOptions, type ViewerActionsProps, type ViewerAnimationsProps, type ViewerCameraProps, type ViewerCameraState, type ViewerModelBounds, type ViewerModelProps, type ViewerReadyState, type ViewerSelectionProps, type XRScaleMode, material, patchBindings, patchSceneConfig, useObjectBinding, useObjectBindingIds, useObjectBindings, useSceneConfig, useShareableViewerState, useViewerActions, useViewerAnimations, useViewerCamera, useViewerHover, useViewerModel, useViewerSelection };
package/dist/index.js CHANGED
@@ -54,9 +54,11 @@ __export(index_exports, {
54
54
  SimpleModelViewer: () => SimpleModelViewer,
55
55
  material: () => material,
56
56
  patchBindings: () => patchBindings,
57
+ patchSceneConfig: () => patchSceneConfig,
57
58
  useObjectBinding: () => useObjectBinding,
58
59
  useObjectBindingIds: () => useObjectBindingIds,
59
60
  useObjectBindings: () => useObjectBindings,
61
+ useSceneConfig: () => useSceneConfig,
60
62
  useShareableViewerState: () => useShareableViewerState,
61
63
  useViewerActions: () => useViewerActions,
62
64
  useViewerAnimations: () => useViewerAnimations,
@@ -17596,15 +17598,12 @@ function useObjectBindingIds(objectBindings) {
17596
17598
  // src/hooks/useObjectBindings.ts
17597
17599
  var import_react44 = require("react");
17598
17600
 
17599
- // src/utils/patchBindings.ts
17601
+ // src/utils/deepMerge.ts
17600
17602
  function isPlainObject2(value) {
17601
17603
  if (typeof value !== "object" || value === null) return false;
17602
17604
  const prototype = Object.getPrototypeOf(value);
17603
17605
  return prototype === Object.prototype || prototype === null;
17604
17606
  }
17605
- function isBindingEditArray(value) {
17606
- return Array.isArray(value) && value.length > 0 && typeof value[0] !== "string";
17607
- }
17608
17607
  function deepMerge(base, patch) {
17609
17608
  if (!isPlainObject2(patch)) return patch;
17610
17609
  const target = isPlainObject2(base) ? { ...base } : {};
@@ -17614,6 +17613,11 @@ function deepMerge(base, patch) {
17614
17613
  }
17615
17614
  return target;
17616
17615
  }
17616
+
17617
+ // src/utils/patchBindings.ts
17618
+ function isBindingEditArray(value) {
17619
+ return Array.isArray(value) && value.length > 0 && typeof value[0] !== "string";
17620
+ }
17617
17621
  function patchBindings(bindings, objectIdOrEdits, patch) {
17618
17622
  const edits = isBindingEditArray(objectIdOrEdits) ? objectIdOrEdits : [{ ids: objectIdOrEdits, patch: patch ?? {} }];
17619
17623
  let next = bindings;
@@ -17643,7 +17647,7 @@ function getHiddenObjects(objectBindings) {
17643
17647
  }
17644
17648
  function useObjectBindings(initialBindings) {
17645
17649
  const [objectBindings, setObjectBindings] = (0, import_react44.useState)(initialBindings);
17646
- const patch = (0, import_react44.useCallback)(
17650
+ const updateObjectBindings = (0, import_react44.useCallback)(
17647
17651
  (objectIdOrEdits, patchValue) => {
17648
17652
  setObjectBindings(
17649
17653
  (current) => patchValue === void 0 ? patchBindings(current, objectIdOrEdits) : patchBindings(
@@ -17656,16 +17660,16 @@ function useObjectBindings(initialBindings) {
17656
17660
  []
17657
17661
  );
17658
17662
  const setMaterial = (0, import_react44.useCallback)(
17659
- (objectId, value) => patch(objectId, material(value)),
17660
- [patch]
17663
+ (objectId, value) => updateObjectBindings(objectId, material(value)),
17664
+ [updateObjectBindings]
17661
17665
  );
17662
17666
  const setBaseColor = (0, import_react44.useCallback)(
17663
17667
  (objectId, color) => setMaterial(objectId, { baseColor: color }),
17664
17668
  [setMaterial]
17665
17669
  );
17666
17670
  const resetMaterial = (0, import_react44.useCallback)(
17667
- (objectId) => patch(objectId, { style: { material: void 0 } }),
17668
- [patch]
17671
+ (objectId) => updateObjectBindings(objectId, { style: { material: void 0 } }),
17672
+ [updateObjectBindings]
17669
17673
  );
17670
17674
  const getMaterial = (0, import_react44.useCallback)(
17671
17675
  (objectId) => {
@@ -17675,12 +17679,12 @@ function useObjectBindings(initialBindings) {
17675
17679
  [objectBindings]
17676
17680
  );
17677
17681
  const hideObject = (0, import_react44.useCallback)(
17678
- (objectId) => patch(objectId, { visible: false }),
17679
- [patch]
17682
+ (objectId) => updateObjectBindings(objectId, { visible: false }),
17683
+ [updateObjectBindings]
17680
17684
  );
17681
17685
  const showObject = (0, import_react44.useCallback)(
17682
- (objectId) => patch(objectId, { visible: true }),
17683
- [patch]
17686
+ (objectId) => updateObjectBindings(objectId, { visible: true }),
17687
+ [updateObjectBindings]
17684
17688
  );
17685
17689
  const toggleObjectVisibility = (0, import_react44.useCallback)(
17686
17690
  (objectId) => {
@@ -17726,7 +17730,7 @@ function useObjectBindings(initialBindings) {
17726
17730
  return {
17727
17731
  objectBindings,
17728
17732
  setObjectBindings,
17729
- patch,
17733
+ updateObjectBindings,
17730
17734
  setMaterial,
17731
17735
  setBaseColor,
17732
17736
  resetMaterial,
@@ -17741,11 +17745,45 @@ function useObjectBindings(initialBindings) {
17741
17745
  };
17742
17746
  }
17743
17747
 
17744
- // src/hooks/useViewerHover.ts
17748
+ // src/hooks/useSceneConfig.ts
17745
17749
  var import_react45 = require("react");
17750
+
17751
+ // src/utils/patchSceneConfig.ts
17752
+ function patchSceneConfig(sceneConfig, patch) {
17753
+ return deepMerge(sceneConfig, patch);
17754
+ }
17755
+
17756
+ // src/hooks/useSceneConfig.ts
17757
+ function useSceneConfig(initialSceneConfig) {
17758
+ const initialSceneConfigRef = (0, import_react45.useRef)(null);
17759
+ const [sceneConfig, setSceneConfig] = (0, import_react45.useState)(() => {
17760
+ const initial = typeof initialSceneConfig === "function" ? initialSceneConfig() : initialSceneConfig;
17761
+ initialSceneConfigRef.current = initial;
17762
+ return initial;
17763
+ });
17764
+ const updateSceneConfig = (0, import_react45.useCallback)((update) => {
17765
+ setSceneConfig((current) => {
17766
+ const patch = typeof update === "function" ? update(current) : update;
17767
+ return patchSceneConfig(current, patch);
17768
+ });
17769
+ }, []);
17770
+ const resetSceneConfig = (0, import_react45.useCallback)(() => {
17771
+ const initial = initialSceneConfigRef.current;
17772
+ if (initial) setSceneConfig(initial);
17773
+ }, []);
17774
+ return {
17775
+ sceneConfig,
17776
+ setSceneConfig,
17777
+ updateSceneConfig,
17778
+ resetSceneConfig
17779
+ };
17780
+ }
17781
+
17782
+ // src/hooks/useViewerHover.ts
17783
+ var import_react46 = require("react");
17746
17784
  function useViewerHover() {
17747
- const [hoveredObjectBinding, setHoveredObjectBinding] = (0, import_react45.useState)(null);
17748
- const handleHoveredObject = (0, import_react45.useCallback)(
17785
+ const [hoveredObjectBinding, setHoveredObjectBinding] = (0, import_react46.useState)(null);
17786
+ const handleHoveredObject = (0, import_react46.useCallback)(
17749
17787
  (binding) => {
17750
17788
  setHoveredObjectBinding(binding);
17751
17789
  },
@@ -17758,18 +17796,18 @@ function useViewerHover() {
17758
17796
  }
17759
17797
 
17760
17798
  // src/hooks/useViewerModel.ts
17761
- var import_react46 = require("react");
17799
+ var import_react47 = require("react");
17762
17800
  var import_three22 = require("three");
17763
17801
  function toTuple2(vector) {
17764
17802
  return [vector.x, vector.y, vector.z];
17765
17803
  }
17766
17804
  function useViewerModel() {
17767
- const [isLoading, setIsLoading] = (0, import_react46.useState)(true);
17768
- const [isReady, setIsReady] = (0, import_react46.useState)(false);
17769
- const [error, setError] = (0, import_react46.useState)(null);
17770
- const [bounds, setBounds] = (0, import_react46.useState)(null);
17771
- const [objectCount, setObjectCount] = (0, import_react46.useState)(0);
17772
- const handleModelLoaded = (0, import_react46.useCallback)((scene) => {
17805
+ const [isLoading, setIsLoading] = (0, import_react47.useState)(true);
17806
+ const [isReady, setIsReady] = (0, import_react47.useState)(false);
17807
+ const [error, setError] = (0, import_react47.useState)(null);
17808
+ const [bounds, setBounds] = (0, import_react47.useState)(null);
17809
+ const [objectCount, setObjectCount] = (0, import_react47.useState)(0);
17810
+ const handleModelLoaded = (0, import_react47.useCallback)((scene) => {
17773
17811
  const box = new import_three22.Box3().setFromObject(scene);
17774
17812
  const min = box.min.clone();
17775
17813
  const max = box.max.clone();
@@ -17794,14 +17832,14 @@ function useViewerModel() {
17794
17832
  setIsLoading(false);
17795
17833
  setIsReady(true);
17796
17834
  }, []);
17797
- const handleLoadError = (0, import_react46.useCallback)((nextError) => {
17835
+ const handleLoadError = (0, import_react47.useCallback)((nextError) => {
17798
17836
  setError(nextError);
17799
17837
  setBounds(null);
17800
17838
  setObjectCount(0);
17801
17839
  setIsLoading(false);
17802
17840
  setIsReady(false);
17803
17841
  }, []);
17804
- const resetModelState = (0, import_react46.useCallback)(() => {
17842
+ const resetModelState = (0, import_react47.useCallback)(() => {
17805
17843
  setIsLoading(true);
17806
17844
  setIsReady(false);
17807
17845
  setError(null);
@@ -17821,10 +17859,10 @@ function useViewerModel() {
17821
17859
  }
17822
17860
 
17823
17861
  // src/hooks/useViewerActions.ts
17824
- var import_react47 = require("react");
17862
+ var import_react48 = require("react");
17825
17863
  function useViewerActions(objectBindings, options = {}) {
17826
17864
  const { onAction, onObjectBindingsChange } = options;
17827
- const getActionsForObject = (0, import_react47.useCallback)(
17865
+ const getActionsForObject = (0, import_react48.useCallback)(
17828
17866
  (objectId) => {
17829
17867
  const bindingKey = resolveBindingKey(objectBindings, objectId);
17830
17868
  if (!bindingKey) {
@@ -17834,7 +17872,7 @@ function useViewerActions(objectBindings, options = {}) {
17834
17872
  },
17835
17873
  [objectBindings]
17836
17874
  );
17837
- const runAction = (0, import_react47.useCallback)(
17875
+ const runAction = (0, import_react48.useCallback)(
17838
17876
  (objectId, actionId) => {
17839
17877
  const bindingKey = resolveBindingKey(objectBindings, objectId);
17840
17878
  if (!bindingKey) {
@@ -17876,7 +17914,7 @@ function useViewerActions(objectBindings, options = {}) {
17876
17914
  }
17877
17915
 
17878
17916
  // src/hooks/useShareableViewerState.ts
17879
- var import_react48 = require("react");
17917
+ var import_react49 = require("react");
17880
17918
  function serializeState(cameraState, objectBindings, selectedObjectBinding) {
17881
17919
  const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
17882
17920
  return {
@@ -17896,8 +17934,8 @@ function useShareableViewerState({
17896
17934
  debounceMs = 400,
17897
17935
  autoSync = true
17898
17936
  }) {
17899
- const lastWrittenRawRef = (0, import_react48.useRef)(null);
17900
- const buildHash = (0, import_react48.useCallback)(() => {
17937
+ const lastWrittenRawRef = (0, import_react49.useRef)(null);
17938
+ const buildHash = (0, import_react49.useCallback)(() => {
17901
17939
  const state = serializeState(
17902
17940
  cameraState,
17903
17941
  objectBindings,
@@ -17905,12 +17943,12 @@ function useShareableViewerState({
17905
17943
  );
17906
17944
  return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
17907
17945
  }, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
17908
- const getShareUrl = (0, import_react48.useCallback)(() => {
17946
+ const getShareUrl = (0, import_react49.useCallback)(() => {
17909
17947
  const url = new URL(window.location.href);
17910
17948
  url.hash = buildHash();
17911
17949
  return url.toString();
17912
17950
  }, [buildHash]);
17913
- const syncUrl = (0, import_react48.useCallback)(() => {
17951
+ const syncUrl = (0, import_react49.useCallback)(() => {
17914
17952
  const url = new URL(window.location.href);
17915
17953
  url.hash = buildHash();
17916
17954
  window.history.replaceState(null, "", url.toString());
@@ -17918,7 +17956,7 @@ function useShareableViewerState({
17918
17956
  paramKey
17919
17957
  );
17920
17958
  }, [buildHash, paramKey]);
17921
- const latestStateRef = (0, import_react48.useRef)({
17959
+ const latestStateRef = (0, import_react49.useRef)({
17922
17960
  cameraState,
17923
17961
  objectBindings,
17924
17962
  selectedObjectBinding
@@ -17928,9 +17966,9 @@ function useShareableViewerState({
17928
17966
  objectBindings,
17929
17967
  selectedObjectBinding
17930
17968
  };
17931
- const [restoreAttempted, setRestoreAttempted] = (0, import_react48.useState)(false);
17932
- const lastSyncedHashRef = (0, import_react48.useRef)(null);
17933
- (0, import_react48.useEffect)(() => {
17969
+ const [restoreAttempted, setRestoreAttempted] = (0, import_react49.useState)(false);
17970
+ const lastSyncedHashRef = (0, import_react49.useRef)(null);
17971
+ (0, import_react49.useEffect)(() => {
17934
17972
  if (!autoSync || !restoreAttempted) return;
17935
17973
  const tick = () => {
17936
17974
  const current = latestStateRef.current;
@@ -17952,9 +17990,9 @@ function useShareableViewerState({
17952
17990
  const id = window.setInterval(tick, debounceMs);
17953
17991
  return () => window.clearInterval(id);
17954
17992
  }, [autoSync, restoreAttempted, debounceMs, paramKey]);
17955
- const lastRestoredRawRef = (0, import_react48.useRef)(null);
17956
- const restoringRawRef = (0, import_react48.useRef)(null);
17957
- const restoreFromUrl = (0, import_react48.useCallback)(async () => {
17993
+ const lastRestoredRawRef = (0, import_react49.useRef)(null);
17994
+ const restoringRawRef = (0, import_react49.useRef)(null);
17995
+ const restoreFromUrl = (0, import_react49.useCallback)(async () => {
17958
17996
  setRestoreAttempted(true);
17959
17997
  const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
17960
17998
  const raw = new URLSearchParams(rawHash).get(paramKey);
@@ -18007,7 +18045,7 @@ function useShareableViewerState({
18007
18045
  setCameraState,
18008
18046
  setSelectedObjectBinding
18009
18047
  ]);
18010
- (0, import_react48.useEffect)(() => {
18048
+ (0, import_react49.useEffect)(() => {
18011
18049
  const handleHashChange = () => {
18012
18050
  void restoreFromUrl();
18013
18051
  };
@@ -18027,9 +18065,11 @@ function useShareableViewerState({
18027
18065
  SimpleModelViewer,
18028
18066
  material,
18029
18067
  patchBindings,
18068
+ patchSceneConfig,
18030
18069
  useObjectBinding,
18031
18070
  useObjectBindingIds,
18032
18071
  useObjectBindings,
18072
+ useSceneConfig,
18033
18073
  useShareableViewerState,
18034
18074
  useViewerActions,
18035
18075
  useViewerAnimations,
package/dist/index.mjs CHANGED
@@ -17697,15 +17697,12 @@ function useObjectBindingIds(objectBindings) {
17697
17697
  // src/hooks/useObjectBindings.ts
17698
17698
  import { useCallback as useCallback12, useMemo as useMemo20, useState as useState28 } from "react";
17699
17699
 
17700
- // src/utils/patchBindings.ts
17700
+ // src/utils/deepMerge.ts
17701
17701
  function isPlainObject2(value) {
17702
17702
  if (typeof value !== "object" || value === null) return false;
17703
17703
  const prototype = Object.getPrototypeOf(value);
17704
17704
  return prototype === Object.prototype || prototype === null;
17705
17705
  }
17706
- function isBindingEditArray(value) {
17707
- return Array.isArray(value) && value.length > 0 && typeof value[0] !== "string";
17708
- }
17709
17706
  function deepMerge(base, patch) {
17710
17707
  if (!isPlainObject2(patch)) return patch;
17711
17708
  const target = isPlainObject2(base) ? { ...base } : {};
@@ -17715,6 +17712,11 @@ function deepMerge(base, patch) {
17715
17712
  }
17716
17713
  return target;
17717
17714
  }
17715
+
17716
+ // src/utils/patchBindings.ts
17717
+ function isBindingEditArray(value) {
17718
+ return Array.isArray(value) && value.length > 0 && typeof value[0] !== "string";
17719
+ }
17718
17720
  function patchBindings(bindings, objectIdOrEdits, patch) {
17719
17721
  const edits = isBindingEditArray(objectIdOrEdits) ? objectIdOrEdits : [{ ids: objectIdOrEdits, patch: patch ?? {} }];
17720
17722
  let next = bindings;
@@ -17744,7 +17746,7 @@ function getHiddenObjects(objectBindings) {
17744
17746
  }
17745
17747
  function useObjectBindings(initialBindings) {
17746
17748
  const [objectBindings, setObjectBindings] = useState28(initialBindings);
17747
- const patch = useCallback12(
17749
+ const updateObjectBindings = useCallback12(
17748
17750
  (objectIdOrEdits, patchValue) => {
17749
17751
  setObjectBindings(
17750
17752
  (current) => patchValue === void 0 ? patchBindings(current, objectIdOrEdits) : patchBindings(
@@ -17757,16 +17759,16 @@ function useObjectBindings(initialBindings) {
17757
17759
  []
17758
17760
  );
17759
17761
  const setMaterial = useCallback12(
17760
- (objectId, value) => patch(objectId, material(value)),
17761
- [patch]
17762
+ (objectId, value) => updateObjectBindings(objectId, material(value)),
17763
+ [updateObjectBindings]
17762
17764
  );
17763
17765
  const setBaseColor = useCallback12(
17764
17766
  (objectId, color) => setMaterial(objectId, { baseColor: color }),
17765
17767
  [setMaterial]
17766
17768
  );
17767
17769
  const resetMaterial = useCallback12(
17768
- (objectId) => patch(objectId, { style: { material: void 0 } }),
17769
- [patch]
17770
+ (objectId) => updateObjectBindings(objectId, { style: { material: void 0 } }),
17771
+ [updateObjectBindings]
17770
17772
  );
17771
17773
  const getMaterial = useCallback12(
17772
17774
  (objectId) => {
@@ -17776,12 +17778,12 @@ function useObjectBindings(initialBindings) {
17776
17778
  [objectBindings]
17777
17779
  );
17778
17780
  const hideObject = useCallback12(
17779
- (objectId) => patch(objectId, { visible: false }),
17780
- [patch]
17781
+ (objectId) => updateObjectBindings(objectId, { visible: false }),
17782
+ [updateObjectBindings]
17781
17783
  );
17782
17784
  const showObject = useCallback12(
17783
- (objectId) => patch(objectId, { visible: true }),
17784
- [patch]
17785
+ (objectId) => updateObjectBindings(objectId, { visible: true }),
17786
+ [updateObjectBindings]
17785
17787
  );
17786
17788
  const toggleObjectVisibility = useCallback12(
17787
17789
  (objectId) => {
@@ -17827,7 +17829,7 @@ function useObjectBindings(initialBindings) {
17827
17829
  return {
17828
17830
  objectBindings,
17829
17831
  setObjectBindings,
17830
- patch,
17832
+ updateObjectBindings,
17831
17833
  setMaterial,
17832
17834
  setBaseColor,
17833
17835
  resetMaterial,
@@ -17842,11 +17844,45 @@ function useObjectBindings(initialBindings) {
17842
17844
  };
17843
17845
  }
17844
17846
 
17847
+ // src/hooks/useSceneConfig.ts
17848
+ import { useCallback as useCallback13, useRef as useRef20, useState as useState29 } from "react";
17849
+
17850
+ // src/utils/patchSceneConfig.ts
17851
+ function patchSceneConfig(sceneConfig, patch) {
17852
+ return deepMerge(sceneConfig, patch);
17853
+ }
17854
+
17855
+ // src/hooks/useSceneConfig.ts
17856
+ function useSceneConfig(initialSceneConfig) {
17857
+ const initialSceneConfigRef = useRef20(null);
17858
+ const [sceneConfig, setSceneConfig] = useState29(() => {
17859
+ const initial = typeof initialSceneConfig === "function" ? initialSceneConfig() : initialSceneConfig;
17860
+ initialSceneConfigRef.current = initial;
17861
+ return initial;
17862
+ });
17863
+ const updateSceneConfig = useCallback13((update) => {
17864
+ setSceneConfig((current) => {
17865
+ const patch = typeof update === "function" ? update(current) : update;
17866
+ return patchSceneConfig(current, patch);
17867
+ });
17868
+ }, []);
17869
+ const resetSceneConfig = useCallback13(() => {
17870
+ const initial = initialSceneConfigRef.current;
17871
+ if (initial) setSceneConfig(initial);
17872
+ }, []);
17873
+ return {
17874
+ sceneConfig,
17875
+ setSceneConfig,
17876
+ updateSceneConfig,
17877
+ resetSceneConfig
17878
+ };
17879
+ }
17880
+
17845
17881
  // src/hooks/useViewerHover.ts
17846
- import { useCallback as useCallback13, useState as useState29 } from "react";
17882
+ import { useCallback as useCallback14, useState as useState30 } from "react";
17847
17883
  function useViewerHover() {
17848
- const [hoveredObjectBinding, setHoveredObjectBinding] = useState29(null);
17849
- const handleHoveredObject = useCallback13(
17884
+ const [hoveredObjectBinding, setHoveredObjectBinding] = useState30(null);
17885
+ const handleHoveredObject = useCallback14(
17850
17886
  (binding) => {
17851
17887
  setHoveredObjectBinding(binding);
17852
17888
  },
@@ -17859,18 +17895,18 @@ function useViewerHover() {
17859
17895
  }
17860
17896
 
17861
17897
  // src/hooks/useViewerModel.ts
17862
- import { useCallback as useCallback14, useState as useState30 } from "react";
17898
+ import { useCallback as useCallback15, useState as useState31 } from "react";
17863
17899
  import { Box3 as Box36, Vector3 as Vector310 } from "three";
17864
17900
  function toTuple2(vector) {
17865
17901
  return [vector.x, vector.y, vector.z];
17866
17902
  }
17867
17903
  function useViewerModel() {
17868
- const [isLoading, setIsLoading] = useState30(true);
17869
- const [isReady, setIsReady] = useState30(false);
17870
- const [error, setError] = useState30(null);
17871
- const [bounds, setBounds] = useState30(null);
17872
- const [objectCount, setObjectCount] = useState30(0);
17873
- const handleModelLoaded = useCallback14((scene) => {
17904
+ const [isLoading, setIsLoading] = useState31(true);
17905
+ const [isReady, setIsReady] = useState31(false);
17906
+ const [error, setError] = useState31(null);
17907
+ const [bounds, setBounds] = useState31(null);
17908
+ const [objectCount, setObjectCount] = useState31(0);
17909
+ const handleModelLoaded = useCallback15((scene) => {
17874
17910
  const box = new Box36().setFromObject(scene);
17875
17911
  const min = box.min.clone();
17876
17912
  const max = box.max.clone();
@@ -17895,14 +17931,14 @@ function useViewerModel() {
17895
17931
  setIsLoading(false);
17896
17932
  setIsReady(true);
17897
17933
  }, []);
17898
- const handleLoadError = useCallback14((nextError) => {
17934
+ const handleLoadError = useCallback15((nextError) => {
17899
17935
  setError(nextError);
17900
17936
  setBounds(null);
17901
17937
  setObjectCount(0);
17902
17938
  setIsLoading(false);
17903
17939
  setIsReady(false);
17904
17940
  }, []);
17905
- const resetModelState = useCallback14(() => {
17941
+ const resetModelState = useCallback15(() => {
17906
17942
  setIsLoading(true);
17907
17943
  setIsReady(false);
17908
17944
  setError(null);
@@ -17922,10 +17958,10 @@ function useViewerModel() {
17922
17958
  }
17923
17959
 
17924
17960
  // src/hooks/useViewerActions.ts
17925
- import { useCallback as useCallback15 } from "react";
17961
+ import { useCallback as useCallback16 } from "react";
17926
17962
  function useViewerActions(objectBindings, options = {}) {
17927
17963
  const { onAction, onObjectBindingsChange } = options;
17928
- const getActionsForObject = useCallback15(
17964
+ const getActionsForObject = useCallback16(
17929
17965
  (objectId) => {
17930
17966
  const bindingKey = resolveBindingKey(objectBindings, objectId);
17931
17967
  if (!bindingKey) {
@@ -17935,7 +17971,7 @@ function useViewerActions(objectBindings, options = {}) {
17935
17971
  },
17936
17972
  [objectBindings]
17937
17973
  );
17938
- const runAction = useCallback15(
17974
+ const runAction = useCallback16(
17939
17975
  (objectId, actionId) => {
17940
17976
  const bindingKey = resolveBindingKey(objectBindings, objectId);
17941
17977
  if (!bindingKey) {
@@ -17977,7 +18013,7 @@ function useViewerActions(objectBindings, options = {}) {
17977
18013
  }
17978
18014
 
17979
18015
  // src/hooks/useShareableViewerState.ts
17980
- import { useCallback as useCallback16, useEffect as useEffect22, useRef as useRef20, useState as useState31 } from "react";
18016
+ import { useCallback as useCallback17, useEffect as useEffect22, useRef as useRef21, useState as useState32 } from "react";
17981
18017
  function serializeState(cameraState, objectBindings, selectedObjectBinding) {
17982
18018
  const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
17983
18019
  return {
@@ -17997,8 +18033,8 @@ function useShareableViewerState({
17997
18033
  debounceMs = 400,
17998
18034
  autoSync = true
17999
18035
  }) {
18000
- const lastWrittenRawRef = useRef20(null);
18001
- const buildHash = useCallback16(() => {
18036
+ const lastWrittenRawRef = useRef21(null);
18037
+ const buildHash = useCallback17(() => {
18002
18038
  const state = serializeState(
18003
18039
  cameraState,
18004
18040
  objectBindings,
@@ -18006,12 +18042,12 @@ function useShareableViewerState({
18006
18042
  );
18007
18043
  return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
18008
18044
  }, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
18009
- const getShareUrl = useCallback16(() => {
18045
+ const getShareUrl = useCallback17(() => {
18010
18046
  const url = new URL(window.location.href);
18011
18047
  url.hash = buildHash();
18012
18048
  return url.toString();
18013
18049
  }, [buildHash]);
18014
- const syncUrl = useCallback16(() => {
18050
+ const syncUrl = useCallback17(() => {
18015
18051
  const url = new URL(window.location.href);
18016
18052
  url.hash = buildHash();
18017
18053
  window.history.replaceState(null, "", url.toString());
@@ -18019,7 +18055,7 @@ function useShareableViewerState({
18019
18055
  paramKey
18020
18056
  );
18021
18057
  }, [buildHash, paramKey]);
18022
- const latestStateRef = useRef20({
18058
+ const latestStateRef = useRef21({
18023
18059
  cameraState,
18024
18060
  objectBindings,
18025
18061
  selectedObjectBinding
@@ -18029,8 +18065,8 @@ function useShareableViewerState({
18029
18065
  objectBindings,
18030
18066
  selectedObjectBinding
18031
18067
  };
18032
- const [restoreAttempted, setRestoreAttempted] = useState31(false);
18033
- const lastSyncedHashRef = useRef20(null);
18068
+ const [restoreAttempted, setRestoreAttempted] = useState32(false);
18069
+ const lastSyncedHashRef = useRef21(null);
18034
18070
  useEffect22(() => {
18035
18071
  if (!autoSync || !restoreAttempted) return;
18036
18072
  const tick = () => {
@@ -18053,9 +18089,9 @@ function useShareableViewerState({
18053
18089
  const id = window.setInterval(tick, debounceMs);
18054
18090
  return () => window.clearInterval(id);
18055
18091
  }, [autoSync, restoreAttempted, debounceMs, paramKey]);
18056
- const lastRestoredRawRef = useRef20(null);
18057
- const restoringRawRef = useRef20(null);
18058
- const restoreFromUrl = useCallback16(async () => {
18092
+ const lastRestoredRawRef = useRef21(null);
18093
+ const restoringRawRef = useRef21(null);
18094
+ const restoreFromUrl = useCallback17(async () => {
18059
18095
  setRestoreAttempted(true);
18060
18096
  const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
18061
18097
  const raw = new URLSearchParams(rawHash).get(paramKey);
@@ -18127,9 +18163,11 @@ export {
18127
18163
  SimpleModelViewer,
18128
18164
  material,
18129
18165
  patchBindings,
18166
+ patchSceneConfig,
18130
18167
  useObjectBinding,
18131
18168
  useObjectBindingIds,
18132
18169
  useObjectBindings,
18170
+ useSceneConfig,
18133
18171
  useShareableViewerState,
18134
18172
  useViewerActions,
18135
18173
  useViewerAnimations,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liveroom-tech/react-immersive",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",