@webspatial/react-sdk 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/web/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  (function(){
3
3
  if(typeof window === 'undefined') return;
4
4
  if(!window.__webspatialsdk__) window.__webspatialsdk__ = {}
5
- window.__webspatialsdk__['react-sdk-version'] = "1.4.0"
5
+ window.__webspatialsdk__['react-sdk-version'] = "1.6.0"
6
6
  window.__webspatialsdk__['XR_ENV'] = "web"
7
7
  })()
8
8
 
@@ -145,11 +145,20 @@ function joinToCSSText(cssKV) {
145
145
  }
146
146
 
147
147
  // src/spatialized-container/hooks/useDomProxy.ts
148
- function makeOriginalKey(key) {
149
- return `__original_${key}`;
150
- }
151
148
  var SpatialContainerRefProxy = class {
152
149
  transformVisibilityTaskContainerDom = null;
150
+ /** Raw Standard host element (styled root). Used to mirror class onto the transform probe. */
151
+ standardRawDom = null;
152
+ standardClassObserver = null;
153
+ /**
154
+ * When set, Standard's DOM className is forwarded here so TransformVisibilityTaskContainer
155
+ * can render it from React state (avoids React clobbering imperative class updates).
156
+ */
157
+ mirrorClassNotify = null;
158
+ /** Last class string applied to the probe + used to skip redundant syncs. */
159
+ lastMirroredClassName = null;
160
+ /** Coalesce multiple class sync triggers in the same turn (Observer + classList, etc.). */
161
+ classSyncMicrotaskQueued = false;
153
162
  ref;
154
163
  domProxy;
155
164
  styleProxy;
@@ -159,211 +168,263 @@ var SpatialContainerRefProxy = class {
159
168
  this.ref = ref;
160
169
  this.extraRefProps = extraRefProps;
161
170
  }
171
+ setMirrorClassNotify(fn) {
172
+ this.mirrorClassNotify = fn;
173
+ if (fn && this.standardRawDom) {
174
+ this.flushSyncTransformClassFromStandard(true);
175
+ }
176
+ }
177
+ disconnectStandardClassObserver() {
178
+ this.standardClassObserver?.disconnect();
179
+ this.standardClassObserver = null;
180
+ }
181
+ attachStandardClassObserver() {
182
+ this.disconnectStandardClassObserver();
183
+ if (!this.standardRawDom) {
184
+ return;
185
+ }
186
+ this.standardClassObserver = new MutationObserver(() => {
187
+ this.scheduleSyncTransformClassFromStandard();
188
+ });
189
+ this.standardClassObserver.observe(this.standardRawDom, {
190
+ attributes: true,
191
+ attributeFilter: ["class"]
192
+ });
193
+ }
194
+ /**
195
+ * Merge multiple sync requests (e.g. classList hook + MutationObserver) into one microtask.
196
+ */
197
+ scheduleSyncTransformClassFromStandard() {
198
+ if (this.classSyncMicrotaskQueued) {
199
+ return;
200
+ }
201
+ this.classSyncMicrotaskQueued = true;
202
+ queueMicrotask(() => {
203
+ this.classSyncMicrotaskQueued = false;
204
+ this.flushSyncTransformClassFromStandard(false);
205
+ });
206
+ }
207
+ /**
208
+ * Source of truth: Standard host DOM (incl. styled-components runtime class changes).
209
+ * @param force when true, skip same-string short-circuit (e.g. mirror notify just registered).
210
+ */
211
+ flushSyncTransformClassFromStandard(force) {
212
+ if (!this.standardRawDom) {
213
+ return;
214
+ }
215
+ const name = this.standardRawDom.className;
216
+ const probe = this.transformVisibilityTaskContainerDom;
217
+ if (!force && probe && probe.className === name && this.lastMirroredClassName === name) {
218
+ return;
219
+ }
220
+ this.lastMirroredClassName = name;
221
+ if (probe) {
222
+ probe.className = name;
223
+ }
224
+ this.mirrorClassNotify?.(name);
225
+ }
162
226
  updateStandardSpatializedContainerDom(dom) {
163
227
  const self = this;
164
- if (dom) {
165
- let cacheExtraRefProps;
166
- const domProxy = new Proxy(
167
- dom,
168
- {
169
- get(target, prop) {
170
- if (prop === "__raw") {
171
- return target;
172
- }
173
- if (prop === "xrClientDepth") {
174
- return target.style.getPropertyValue(SpatialCustomStyleVars.depth);
175
- }
176
- if (prop === "xrOffsetBack") {
177
- return target.style.getPropertyValue(SpatialCustomStyleVars.back);
178
- }
179
- if (prop === "style") {
180
- if (!self.styleProxy) {
181
- self.styleProxy = new Proxy(target.style, {
182
- get(target2, prop2) {
183
- if (prop2 === "visibility" || prop2 === "transform") {
184
- return self.transformVisibilityTaskContainerDom?.style.getPropertyValue(
185
- prop2
186
- );
187
- }
188
- const value2 = Reflect.get(target2, prop2);
189
- if (typeof value2 === "function") {
190
- if (prop2 === "setProperty" || prop2 === "removeProperty" || prop2 === "getPropertyValue") {
191
- return function(...args) {
192
- const validProperties = ["visibility", "transform"];
193
- const [property] = args;
194
- if (validProperties.includes(property)) {
195
- if (prop2 === "setProperty") {
196
- const [, kValue] = args;
197
- self.transformVisibilityTaskContainerDom?.style.setProperty(
198
- property,
199
- kValue
200
- );
201
- } else if (prop2 === "removeProperty") {
202
- self.transformVisibilityTaskContainerDom?.style.removeProperty(
203
- property
204
- );
205
- } else if (prop2 === "getPropertyValue") {
206
- return self.transformVisibilityTaskContainerDom?.style.getPropertyValue(
207
- property
208
- );
209
- }
210
- } else {
211
- return value2.apply(this, args);
228
+ if (!dom) {
229
+ this.disconnectStandardClassObserver();
230
+ this.standardRawDom = null;
231
+ this.lastMirroredClassName = null;
232
+ this.domProxy = void 0;
233
+ this.styleProxy = void 0;
234
+ this.updateDomProxyToRef();
235
+ return;
236
+ }
237
+ this.standardRawDom = dom;
238
+ let cacheExtraRefProps;
239
+ const domProxy = new Proxy(
240
+ dom,
241
+ {
242
+ get(target, prop) {
243
+ if (prop === "__raw") {
244
+ return target;
245
+ }
246
+ if (prop === "xrClientDepth") {
247
+ return target.style.getPropertyValue(SpatialCustomStyleVars.depth);
248
+ }
249
+ if (prop === "xrOffsetBack") {
250
+ return target.style.getPropertyValue(SpatialCustomStyleVars.back);
251
+ }
252
+ if (prop === "style") {
253
+ if (!self.styleProxy) {
254
+ self.styleProxy = new Proxy(target.style, {
255
+ get(target2, prop2) {
256
+ if (prop2 === "visibility" || prop2 === "transform") {
257
+ return self.transformVisibilityTaskContainerDom?.style.getPropertyValue(
258
+ prop2
259
+ );
260
+ }
261
+ const value2 = Reflect.get(target2, prop2);
262
+ if (typeof value2 === "function") {
263
+ if (prop2 === "setProperty" || prop2 === "removeProperty" || prop2 === "getPropertyValue") {
264
+ return function(...args) {
265
+ const validProperties = ["visibility", "transform"];
266
+ const [property] = args;
267
+ if (validProperties.includes(property)) {
268
+ if (prop2 === "setProperty") {
269
+ const [, kValue] = args;
270
+ self.transformVisibilityTaskContainerDom?.style.setProperty(
271
+ property,
272
+ kValue
273
+ );
274
+ } else if (prop2 === "removeProperty") {
275
+ self.transformVisibilityTaskContainerDom?.style.removeProperty(
276
+ property
277
+ );
278
+ } else if (prop2 === "getPropertyValue") {
279
+ return self.transformVisibilityTaskContainerDom?.style.getPropertyValue(
280
+ property
281
+ );
212
282
  }
213
- }.bind(target2);
214
- } else {
215
- return value2.bind(target2);
216
- }
217
- } else {
218
- return value2;
219
- }
220
- },
221
- set(target2, prop2, value2) {
222
- if (prop2 === "visibility") {
223
- self.transformVisibilityTaskContainerDom?.style.setProperty(
224
- "visibility",
225
- value2
226
- );
227
- return true;
228
- }
229
- if (prop2 === "transform") {
230
- self.transformVisibilityTaskContainerDom?.style.setProperty(
231
- "transform",
232
- value2
233
- );
234
- return true;
235
- }
236
- if (prop2 === SpatialCustomStyleVars.backgroundMaterial) {
237
- target2.setProperty(
238
- SpatialCustomStyleVars.backgroundMaterial,
239
- value2
240
- );
241
- } else if (prop2 === SpatialCustomStyleVars.back) {
242
- target2.setProperty(
243
- SpatialCustomStyleVars.back,
244
- value2
245
- );
246
- } else if (prop2 === SpatialCustomStyleVars.xrZIndex) {
247
- target2.setProperty(
248
- SpatialCustomStyleVars.xrZIndex,
249
- value2
250
- );
251
- } else if (prop2 === SpatialCustomStyleVars.depth) {
252
- target2.setProperty(
253
- SpatialCustomStyleVars.depth,
254
- value2
255
- );
256
- } else if (prop2 === "cssText") {
257
- const toFilteredCSSProperties = [
258
- "transform",
259
- "visibility"
260
- ];
261
- const { extractedValues, filteredCssText } = extractAndRemoveCustomProperties(
262
- value2,
263
- toFilteredCSSProperties
264
- );
265
- toFilteredCSSProperties.forEach((key) => {
266
- if (extractedValues[key]) {
267
- self.transformVisibilityTaskContainerDom?.style.setProperty(
268
- key,
269
- extractedValues[key]
270
- );
271
283
  } else {
272
- target2.removeProperty(key);
284
+ return value2.apply(this, args);
273
285
  }
274
- });
275
- const appendedCSSText = joinToCSSText({
276
- transform: "none",
277
- visibility: "hidden"
278
- });
279
- return Reflect.set(
280
- target2,
281
- prop2,
282
- [appendedCSSText, filteredCssText].join(";")
283
- );
286
+ }.bind(target2);
287
+ } else {
288
+ return value2.bind(target2);
284
289
  }
285
- return Reflect.set(target2, prop2, value2);
290
+ } else {
291
+ return value2;
286
292
  }
287
- });
288
- }
289
- return self.styleProxy;
290
- }
291
- if (typeof prop === "string" && self.extraRefProps) {
292
- if (!cacheExtraRefProps) {
293
- cacheExtraRefProps = self.extraRefProps(domProxy);
294
- }
295
- const extraProps = cacheExtraRefProps;
296
- if (extraProps.hasOwnProperty(prop)) {
297
- return extraProps[prop];
298
- }
299
- }
300
- const value = Reflect.get(target, prop);
301
- if (typeof value === "function") {
302
- if ("removeAttribute" === prop) {
303
- return function(...args) {
304
- const [property] = args;
305
- if (property === "style") {
306
- dom.style.cssText = "visibility: hidden; transition: none; transform: none;";
307
- if (self.transformVisibilityTaskContainerDom) {
308
- self.transformVisibilityTaskContainerDom.style.visibility = "";
309
- self.transformVisibilityTaskContainerDom.style.transform = "";
310
- }
293
+ },
294
+ set(target2, prop2, value2) {
295
+ if (prop2 === "visibility") {
296
+ self.transformVisibilityTaskContainerDom?.style.setProperty(
297
+ "visibility",
298
+ value2
299
+ );
311
300
  return true;
312
301
  }
313
- if (property === "class") {
314
- domProxy.className = "xr-spatial-default";
302
+ if (prop2 === "transform") {
303
+ self.transformVisibilityTaskContainerDom?.style.setProperty(
304
+ "transform",
305
+ value2
306
+ );
315
307
  return true;
316
308
  }
317
- };
318
- }
319
- return value.bind(target);
309
+ if (prop2 === SpatialCustomStyleVars.backgroundMaterial) {
310
+ target2.setProperty(
311
+ SpatialCustomStyleVars.backgroundMaterial,
312
+ value2
313
+ );
314
+ } else if (prop2 === SpatialCustomStyleVars.back) {
315
+ target2.setProperty(
316
+ SpatialCustomStyleVars.back,
317
+ value2
318
+ );
319
+ } else if (prop2 === SpatialCustomStyleVars.xrZIndex) {
320
+ target2.setProperty(
321
+ SpatialCustomStyleVars.xrZIndex,
322
+ value2
323
+ );
324
+ } else if (prop2 === SpatialCustomStyleVars.depth) {
325
+ target2.setProperty(
326
+ SpatialCustomStyleVars.depth,
327
+ value2
328
+ );
329
+ } else if (prop2 === "cssText") {
330
+ const toFilteredCSSProperties = ["transform", "visibility"];
331
+ const { extractedValues, filteredCssText } = extractAndRemoveCustomProperties(
332
+ value2,
333
+ toFilteredCSSProperties
334
+ );
335
+ toFilteredCSSProperties.forEach((key) => {
336
+ if (extractedValues[key]) {
337
+ self.transformVisibilityTaskContainerDom?.style.setProperty(
338
+ key,
339
+ extractedValues[key]
340
+ );
341
+ } else {
342
+ target2.removeProperty(key);
343
+ }
344
+ });
345
+ const appendedCSSText = joinToCSSText({
346
+ transform: "none",
347
+ visibility: "hidden"
348
+ });
349
+ return Reflect.set(
350
+ target2,
351
+ prop2,
352
+ [appendedCSSText, filteredCssText].join(";")
353
+ );
354
+ }
355
+ return Reflect.set(target2, prop2, value2);
356
+ }
357
+ });
320
358
  }
321
- return value;
322
- },
323
- set(target, prop, value) {
324
- if (prop === "className") {
325
- if (value && value.indexOf("xr-spatial-default") === -1) {
326
- value = value + " xr-spatial-default";
327
- }
328
- if (self.transformVisibilityTaskContainerDom) {
329
- self.transformVisibilityTaskContainerDom.className = value;
330
- }
359
+ return self.styleProxy;
360
+ }
361
+ if (typeof prop === "string" && self.extraRefProps) {
362
+ if (!cacheExtraRefProps) {
363
+ cacheExtraRefProps = self.extraRefProps(domProxy);
331
364
  }
332
- if (typeof prop === "string" && self.extraRefProps) {
333
- if (!cacheExtraRefProps) {
334
- cacheExtraRefProps = self.extraRefProps(domProxy);
335
- }
336
- cacheExtraRefProps[prop] = value;
365
+ const extraProps = cacheExtraRefProps;
366
+ if (extraProps.hasOwnProperty(prop)) {
367
+ return extraProps[prop];
337
368
  }
338
- return Reflect.set(target, prop, value);
339
369
  }
340
- }
341
- );
342
- this.domProxy = domProxy;
343
- const domClassList = dom.classList;
344
- const domClassMethodKeys = ["add", "remove", "toggle", "replace"];
345
- domClassMethodKeys.forEach((key) => {
346
- const hiddenKey = makeOriginalKey(key);
347
- const hiddenKeyExist = domClassList[hiddenKey] !== void 0;
348
- const originalMethod = hiddenKeyExist ? domClassList[hiddenKey] : domClassList[key].bind(domClassList);
349
- domClassList[hiddenKey] = originalMethod;
350
- domClassList[key] = function(...args) {
351
- const result = originalMethod(...args);
352
- if (self.transformVisibilityTaskContainerDom) {
353
- self.transformVisibilityTaskContainerDom.className = dom.className;
370
+ const value = Reflect.get(target, prop);
371
+ if (typeof value === "function") {
372
+ if ("removeAttribute" === prop) {
373
+ return function(...args) {
374
+ const [property] = args;
375
+ if (property === "style") {
376
+ dom.style.cssText = "visibility: hidden; transition: none; transform: none;";
377
+ if (self.transformVisibilityTaskContainerDom) {
378
+ self.transformVisibilityTaskContainerDom.style.visibility = "";
379
+ self.transformVisibilityTaskContainerDom.style.transform = "";
380
+ }
381
+ return true;
382
+ }
383
+ if (property === "class") {
384
+ domProxy.className = "xr-spatial-default";
385
+ return true;
386
+ }
387
+ };
388
+ }
389
+ return value.bind(target);
354
390
  }
355
- return result;
356
- };
357
- });
358
- this.styleProxy = void 0;
359
- this.updateDomProxyToRef();
360
- Object.assign(dom, {
361
- __targetProxy: domProxy
362
- });
363
- }
391
+ return value;
392
+ },
393
+ set(target, prop, value) {
394
+ if (prop === "className") {
395
+ if (value && String(value).indexOf("xr-spatial-default") === -1) {
396
+ value = value + " xr-spatial-default";
397
+ }
398
+ }
399
+ if (typeof prop === "string" && self.extraRefProps) {
400
+ if (!cacheExtraRefProps) {
401
+ cacheExtraRefProps = self.extraRefProps(domProxy);
402
+ }
403
+ cacheExtraRefProps[prop] = value;
404
+ }
405
+ const ok = Reflect.set(target, prop, value);
406
+ if (ok && prop === "className") {
407
+ self.scheduleSyncTransformClassFromStandard();
408
+ }
409
+ return ok;
410
+ }
411
+ }
412
+ );
413
+ this.domProxy = domProxy;
414
+ this.styleProxy = void 0;
415
+ this.updateDomProxyToRef();
416
+ Object.assign(dom, {
417
+ __targetProxy: domProxy
418
+ });
419
+ this.attachStandardClassObserver();
420
+ this.scheduleSyncTransformClassFromStandard();
364
421
  }
365
422
  updateTransformVisibilityTaskContainerDom(dom) {
366
423
  this.transformVisibilityTaskContainerDom = dom;
424
+ if (!dom) {
425
+ this.lastMirroredClassName = null;
426
+ }
427
+ this.scheduleSyncTransformClassFromStandard();
367
428
  this.updateDomProxyToRef();
368
429
  }
369
430
  updateDomProxyToRef() {
@@ -843,13 +904,20 @@ var TransformVisibilityTaskContainer = forwardRef2(
843
904
  );
844
905
 
845
906
  // src/spatialized-container/SpatializedContainer.tsx
846
- import { forwardRef as forwardRef4, useContext as useContext7, useEffect as useEffect10, useMemo as useMemo2 } from "react";
907
+ import {
908
+ forwardRef as forwardRef4,
909
+ useCallback as useCallback6,
910
+ useContext as useContext7,
911
+ useEffect as useEffect10,
912
+ useMemo as useMemo2,
913
+ useState as useState6
914
+ } from "react";
847
915
 
848
916
  // src/noRuntime.ts
849
917
  var Spatial = class {
850
918
  /**
851
919
  * Requests a session object from the browser
852
- * @returns The session or null if not availible in the current browser
920
+ * @returns The session or null if not available in the current browser
853
921
  * [TODO] discuss implications of this not being async
854
922
  */
855
923
  requestSession() {
@@ -871,14 +939,14 @@ var Spatial = class {
871
939
  }
872
940
  /**
873
941
  * Gets the native version, format is "x.x.x"
874
- * @returns native version string
942
+ * @returns native version string, or null when runtime is unavailable
875
943
  */
876
944
  getNativeVersion() {
877
945
  return null;
878
946
  }
879
947
  /**
880
948
  * Gets the client version, format is "x.x.x"
881
- * @returns client version string
949
+ * @returns client version string, or null when runtime is unavailable
882
950
  */
883
951
  getClientVersion() {
884
952
  return null;
@@ -898,7 +966,7 @@ var PhysicalMetrics = {
898
966
  meterToPtUnscaled: 1360,
899
967
  meterToPtScaled: 1360
900
968
  }),
901
- subscribe: (cb) => {
969
+ subscribe: (cb) => () => {
902
970
  }
903
971
  };
904
972
 
@@ -1203,7 +1271,7 @@ function renderPlaceholderInSubPortal(portalInstanceObject, El) {
1203
1271
  return /* @__PURE__ */ jsx3(Fragment, {});
1204
1272
  }
1205
1273
  const { width, height } = portalInstanceObject.domRect;
1206
- const display = portalInstanceObject.computedStyle.getPropertyPriority("display");
1274
+ const display = portalInstanceObject.computedStyle.getPropertyValue("display");
1207
1275
  const spatialIdProps = { [SpatialID]: spatialId };
1208
1276
  return /* @__PURE__ */ jsx3(
1209
1277
  El,
@@ -1648,6 +1716,20 @@ function SpatializedContainerBase(inprops, ref) {
1648
1716
  standardSpatializedContainerCallback,
1649
1717
  spatialContainerRefProxy
1650
1718
  } = useDomProxy(ref, extraRefProps);
1719
+ const [probeClassName, setProbeClassName] = useState6(
1720
+ () => props.className ?? ""
1721
+ );
1722
+ const notifyProbeClass = useCallback6((name) => {
1723
+ setProbeClassName((prev) => prev === name ? prev : name);
1724
+ }, []);
1725
+ useEffect10(() => {
1726
+ spatialContainerRefProxy.current.setMirrorClassNotify?.(
1727
+ notifyProbeClass
1728
+ );
1729
+ return () => {
1730
+ spatialContainerRefProxy.current.setMirrorClassNotify?.(null);
1731
+ };
1732
+ }, [spatialContainerRefProxy, notifyProbeClass]);
1651
1733
  useEffect10(() => {
1652
1734
  rootSpatializedContainerObject.updateSpatialContainerRefProxyInfo(
1653
1735
  spatialId,
@@ -1676,7 +1758,7 @@ function SpatializedContainerBase(inprops, ref) {
1676
1758
  {
1677
1759
  ref: transformVisibilityTaskContainerCallback,
1678
1760
  ...spatialIdProps,
1679
- className: props.className,
1761
+ className: probeClassName,
1680
1762
  style: props.style
1681
1763
  }
1682
1764
  )
@@ -1688,6 +1770,18 @@ function SpatializedContainerBase(inprops, ref) {
1688
1770
  standardSpatializedContainerCallback,
1689
1771
  spatialContainerRefProxy
1690
1772
  } = useDomProxy(ref, extraRefProps);
1773
+ const [probeClassName, setProbeClassName] = useState6(
1774
+ () => props.className ?? ""
1775
+ );
1776
+ const notifyProbeClass = useCallback6((name) => {
1777
+ setProbeClassName((prev) => prev === name ? prev : name);
1778
+ }, []);
1779
+ useEffect10(() => {
1780
+ spatialContainerRefProxy.current.setMirrorClassNotify?.(notifyProbeClass);
1781
+ return () => {
1782
+ spatialContainerRefProxy.current.setMirrorClassNotify?.(null);
1783
+ };
1784
+ }, [spatialContainerRefProxy, notifyProbeClass]);
1691
1785
  const spatialEvents = useSpatialEvents(
1692
1786
  {
1693
1787
  onSpatialTap,
@@ -1739,7 +1833,7 @@ function SpatializedContainerBase(inprops, ref) {
1739
1833
  {
1740
1834
  ref: transformVisibilityTaskContainerCallback,
1741
1835
  ...spatialIdProps,
1742
- className: props.className,
1836
+ className: probeClassName,
1743
1837
  style: props.style
1744
1838
  }
1745
1839
  )
@@ -2019,8 +2113,10 @@ var Spatialized2DElementContainer = forwardRef5(
2019
2113
 
2020
2114
  // src/spatialized-container/SpatializedStatic3DElementContainer.tsx
2021
2115
  import {
2116
+ Children,
2022
2117
  forwardRef as forwardRef6,
2023
- useCallback as useCallback6,
2118
+ isValidElement,
2119
+ useCallback as useCallback7,
2024
2120
  useContext as useContext9,
2025
2121
  useEffect as useEffect13,
2026
2122
  useMemo as useMemo3,
@@ -2028,9 +2124,7 @@ import {
2028
2124
  } from "react";
2029
2125
  import { Fragment as Fragment2, jsx as jsx8 } from "react/jsx-runtime";
2030
2126
  function getAbsoluteURL(url) {
2031
- if (!url) {
2032
- return "";
2033
- }
2127
+ if (!url) return url;
2034
2128
  try {
2035
2129
  return new URL(url, document.baseURI).toString();
2036
2130
  } catch {
@@ -2058,21 +2152,34 @@ function createLoadFailureEvent(targetGetter) {
2058
2152
  function createLoadSuccessEvent(targetGetter) {
2059
2153
  return createLoadEvent("modelloaded", targetGetter);
2060
2154
  }
2155
+ function collectSources(children) {
2156
+ const sources = [];
2157
+ Children.forEach(children, (child) => {
2158
+ if (isValidElement(child) && child.type === "source") {
2159
+ const { src, type } = child.props;
2160
+ if (src) {
2161
+ sources.push({ src: getAbsoluteURL(src), type });
2162
+ }
2163
+ }
2164
+ });
2165
+ return sources;
2166
+ }
2061
2167
  function SpatializedContent2(props) {
2062
- const { src, spatializedElement, onLoad, onError } = props;
2063
- const spatializedStatic3DElement = spatializedElement;
2064
- const portalInstanceObject = useContext9(
2065
- PortalInstanceContext
2066
- );
2067
- const currentSrc = useMemo3(() => getAbsoluteURL(src), [src]);
2168
+ const { src, children, spatializedElement, onLoad, onError, autoPlay, loop } = props;
2169
+ const portalInstanceObject = useContext9(PortalInstanceContext);
2170
+ const modelURL = useMemo3(() => getAbsoluteURL(src), [src]);
2171
+ const sources = useMemo3(() => collectSources(children), [children]);
2068
2172
  useEffect13(() => {
2069
- if (src) {
2070
- spatializedStatic3DElement.updateProperties({ modelURL: currentSrc });
2071
- }
2072
- }, [currentSrc]);
2173
+ spatializedElement.updateProperties({
2174
+ modelURL: modelURL ?? "",
2175
+ sources,
2176
+ autoplay: autoPlay,
2177
+ loop
2178
+ });
2179
+ }, [modelURL, JSON.stringify(sources), autoPlay, loop]);
2073
2180
  useEffect13(() => {
2074
2181
  if (onLoad) {
2075
- spatializedStatic3DElement.onLoadCallback = () => {
2182
+ spatializedElement.onLoadCallback = () => {
2076
2183
  onLoad(
2077
2184
  createLoadSuccessEvent(
2078
2185
  () => portalInstanceObject.dom.__targetProxy
@@ -2080,12 +2187,12 @@ function SpatializedContent2(props) {
2080
2187
  );
2081
2188
  };
2082
2189
  } else {
2083
- spatializedStatic3DElement.onLoadCallback = void 0;
2190
+ spatializedElement.onLoadCallback = void 0;
2084
2191
  }
2085
2192
  }, [onLoad]);
2086
2193
  useEffect13(() => {
2087
2194
  if (onError) {
2088
- spatializedStatic3DElement.onLoadFailureCallback = () => {
2195
+ spatializedElement.onLoadFailureCallback = () => {
2089
2196
  onError(
2090
2197
  createLoadFailureEvent(
2091
2198
  () => portalInstanceObject.dom.__targetProxy
@@ -2093,24 +2200,27 @@ function SpatializedContent2(props) {
2093
2200
  );
2094
2201
  };
2095
2202
  } else {
2096
- spatializedStatic3DElement.onLoadFailureCallback = void 0;
2203
+ spatializedElement.onLoadFailureCallback = void 0;
2097
2204
  }
2098
2205
  }, [onError]);
2099
2206
  return /* @__PURE__ */ jsx8(Fragment2, {});
2100
2207
  }
2101
2208
  function SpatializedStatic3DElementContainerBase(props, ref) {
2102
2209
  const promiseRef = useRef5(null);
2103
- const createSpatializedElement2 = useCallback6(() => {
2104
- const url = getAbsoluteURL(props.src);
2105
- promiseRef.current = getSession().createSpatializedStatic3DElement(url);
2210
+ const createSpatializedElement2 = useCallback7(() => {
2211
+ promiseRef.current = getSession().createSpatializedStatic3DElement(
2212
+ getAbsoluteURL(props.src),
2213
+ collectSources(props.children)
2214
+ );
2106
2215
  return promiseRef.current;
2107
2216
  }, []);
2108
- const extraRefProps = useCallback6(
2217
+ const extraRefProps = useCallback7(
2109
2218
  (domProxy) => {
2110
2219
  let modelTransform = new DOMMatrixReadOnly();
2111
2220
  return {
2112
2221
  get currentSrc() {
2113
- return getAbsoluteURL(props.src);
2222
+ const spatializedElement = domProxy.__spatializedElement;
2223
+ return spatializedElement?.currentSrc ?? "";
2114
2224
  },
2115
2225
  get ready() {
2116
2226
  return promiseRef.current.then((spatializedElement) => spatializedElement.ready).then((success) => {
@@ -2125,6 +2235,32 @@ function SpatializedStatic3DElementContainerBase(props, ref) {
2125
2235
  modelTransform = value;
2126
2236
  const spatializedElement = domProxy.__spatializedElement;
2127
2237
  spatializedElement?.updateModelTransform(modelTransform);
2238
+ },
2239
+ async play() {
2240
+ const spatializedElement = domProxy.__spatializedElement;
2241
+ await spatializedElement?.play();
2242
+ },
2243
+ async pause() {
2244
+ const spatializedElement = domProxy.__spatializedElement;
2245
+ await spatializedElement?.pause();
2246
+ },
2247
+ get paused() {
2248
+ const spatializedElement = domProxy.__spatializedElement;
2249
+ return spatializedElement?.paused ?? true;
2250
+ },
2251
+ get duration() {
2252
+ const spatializedElement = domProxy.__spatializedElement;
2253
+ return spatializedElement?.duration ?? 0;
2254
+ },
2255
+ get playbackRate() {
2256
+ const spatializedElement = domProxy.__spatializedElement;
2257
+ return spatializedElement?.playbackRate ?? 1;
2258
+ },
2259
+ set playbackRate(value) {
2260
+ const spatializedElement = domProxy.__spatializedElement;
2261
+ if (spatializedElement) {
2262
+ spatializedElement.playbackRate = value;
2263
+ }
2128
2264
  }
2129
2265
  };
2130
2266
  },
@@ -2404,6 +2540,20 @@ function shallowEqualRotation(a, b) {
2404
2540
  if (!a || !b) return false;
2405
2541
  return a.x === b.x && a.y === b.y && a.z === b.z && ("w" in a ? a.w === b.w : true);
2406
2542
  }
2543
+ function shallowEqualObject(a, b) {
2544
+ if (a === b) return true;
2545
+ if (!a || !b) return false;
2546
+ const keysA = Object.keys(a);
2547
+ const keysB = Object.keys(b);
2548
+ if (keysA.length !== keysB.length) return false;
2549
+ return keysA.every((key) => a[key] === b[key]);
2550
+ }
2551
+ function shallowEqualArray(a, b) {
2552
+ if (a === b) return true;
2553
+ if (!a || !b) return false;
2554
+ if (a.length !== b.length) return false;
2555
+ return a.every((val, i) => val === b[i]);
2556
+ }
2407
2557
 
2408
2558
  // src/reality/utils/AbortResourceManager.ts
2409
2559
  var AbortResourceManager = class {
@@ -2548,9 +2698,150 @@ var EntityRef = class {
2548
2698
  }
2549
2699
  };
2550
2700
 
2701
+ // src/reality/hooks/useEntityEvent.tsx
2702
+ function createEventProxy2(ev, instance) {
2703
+ return new Proxy(ev, {
2704
+ get(target, prop) {
2705
+ if (prop === "currentTarget") {
2706
+ return instance;
2707
+ }
2708
+ if (prop === "target") {
2709
+ const origin = target.__origin;
2710
+ if (origin) {
2711
+ return new EntityRef(origin, null);
2712
+ }
2713
+ return instance;
2714
+ }
2715
+ if (prop === "bubbles") {
2716
+ return true;
2717
+ }
2718
+ if (prop === "offsetX") {
2719
+ const type = target.type;
2720
+ if (type === "spatialtap") {
2721
+ return target.detail?.location3D?.x ?? 0;
2722
+ }
2723
+ if (type === "spatialdragstart") {
2724
+ return target.detail?.startLocation3D?.x ?? 0;
2725
+ }
2726
+ return void 0;
2727
+ }
2728
+ if (prop === "offsetY") {
2729
+ const type = target.type;
2730
+ if (type === "spatialtap") {
2731
+ return target.detail?.location3D?.y ?? 0;
2732
+ }
2733
+ if (type === "spatialdragstart") {
2734
+ return target.detail?.startLocation3D?.y ?? 0;
2735
+ }
2736
+ return void 0;
2737
+ }
2738
+ if (prop === "offsetZ") {
2739
+ const type = target.type;
2740
+ if (type === "spatialtap") {
2741
+ return target.detail?.location3D?.z ?? 0;
2742
+ }
2743
+ if (type === "spatialdragstart") {
2744
+ return target.detail?.startLocation3D?.z ?? 0;
2745
+ }
2746
+ return void 0;
2747
+ }
2748
+ if (prop === "translationX") {
2749
+ const type = target.type;
2750
+ if (type === "spatialdrag") {
2751
+ return target.detail?.translation3D?.x ?? 0;
2752
+ }
2753
+ return void 0;
2754
+ }
2755
+ if (prop === "translationY") {
2756
+ const type = target.type;
2757
+ if (type === "spatialdrag") {
2758
+ return target.detail?.translation3D?.y ?? 0;
2759
+ }
2760
+ return void 0;
2761
+ }
2762
+ if (prop === "translationZ") {
2763
+ const type = target.type;
2764
+ if (type === "spatialdrag") {
2765
+ return target.detail?.translation3D?.z ?? 0;
2766
+ }
2767
+ return void 0;
2768
+ }
2769
+ if (prop === "quaternion") {
2770
+ const type = target.type;
2771
+ if (type === "spatialrotate") {
2772
+ return target.detail?.quaternion ?? {
2773
+ x: 0,
2774
+ y: 0,
2775
+ z: 0,
2776
+ w: 1
2777
+ };
2778
+ }
2779
+ return void 0;
2780
+ }
2781
+ if (prop === "magnification") {
2782
+ const type = target.type;
2783
+ if (type === "spatialmagnify") {
2784
+ return target.detail?.magnification ?? 1;
2785
+ }
2786
+ return void 0;
2787
+ }
2788
+ if (prop === "clientX") {
2789
+ const type = target.type;
2790
+ if (type === "spatialtap" || type === "spatialdragstart") {
2791
+ return target.detail?.globalLocation3D?.x ?? 0;
2792
+ }
2793
+ return void 0;
2794
+ }
2795
+ if (prop === "clientY") {
2796
+ const type = target.type;
2797
+ if (type === "spatialtap" || type === "spatialdragstart") {
2798
+ return target.detail?.globalLocation3D?.y ?? 0;
2799
+ }
2800
+ return void 0;
2801
+ }
2802
+ if (prop === "clientZ") {
2803
+ const type = target.type;
2804
+ if (type === "spatialtap" || type === "spatialdragstart") {
2805
+ return target.detail?.globalLocation3D?.z ?? 0;
2806
+ }
2807
+ return void 0;
2808
+ }
2809
+ const val = target[prop];
2810
+ return typeof val === "function" ? val.bind(target) : val;
2811
+ }
2812
+ });
2813
+ }
2814
+ var useEntityEvent = ({ instance, ...handlers }) => {
2815
+ const eventsSetRef = useRef9(/* @__PURE__ */ new Set());
2816
+ useEffect18(() => {
2817
+ const entity = instance.entity;
2818
+ if (!entity) return;
2819
+ Object.entries(eventMap).forEach(([reactKey, spatialEvent]) => {
2820
+ const handlerFn = handlers[reactKey];
2821
+ if (!handlerFn) return;
2822
+ const wrapped = (ev) => handlerFn(createEventProxy2(ev, instance));
2823
+ entity.addEvent(spatialEvent, wrapped);
2824
+ eventsSetRef.current.add(reactKey);
2825
+ });
2826
+ return () => {
2827
+ };
2828
+ }, [instance.entity, ...Object.values(handlers)]);
2829
+ useEffect18(() => {
2830
+ const entity = instance.entity;
2831
+ if (!entity) return;
2832
+ return () => {
2833
+ for (let x of eventsSetRef.current) {
2834
+ entity.removeEvent(x);
2835
+ }
2836
+ eventsSetRef.current.clear();
2837
+ };
2838
+ }, [instance.entity]);
2839
+ return null;
2840
+ };
2841
+
2551
2842
  // src/reality/hooks/useRealityEvents.tsx
2552
2843
  import { useEffect as useEffect19, useRef as useRef10 } from "react";
2553
- function createEventProxy2(ev, instance) {
2844
+ function createEventProxy3(ev, instance) {
2554
2845
  return new Proxy(ev, {
2555
2846
  get(target, prop) {
2556
2847
  if (prop === "currentTarget") {
@@ -2669,7 +2960,7 @@ var useRealityEvents = ({ instance, ...handlers }) => {
2669
2960
  Object.entries(eventMap).forEach(([reactKey, spatialEvent]) => {
2670
2961
  const handlerFn = handlers[reactKey];
2671
2962
  if (!handlerFn) return;
2672
- const wrapped = (ev) => handlerFn(createEventProxy2(ev, instance));
2963
+ const wrapped = (ev) => handlerFn(createEventProxy3(ev, instance));
2673
2964
  instance.addEvent(spatialEvent, wrapped);
2674
2965
  eventsSetRef.current.add(spatialEvent);
2675
2966
  });
@@ -2707,7 +2998,16 @@ var useEntity = ({
2707
2998
  rotation,
2708
2999
  scale,
2709
3000
  enableInput,
2710
- createEntity
3001
+ onSpatialTap,
3002
+ onSpatialDragStart,
3003
+ onSpatialDrag,
3004
+ onSpatialDragEnd,
3005
+ onSpatialRotate,
3006
+ onSpatialRotateEnd,
3007
+ onSpatialMagnify,
3008
+ onSpatialMagnifyEnd,
3009
+ createEntity,
3010
+ recreateKey
2711
3011
  }) => {
2712
3012
  const ctx = useRealityContext();
2713
3013
  const parent = useParentContext();
@@ -2724,6 +3024,11 @@ var useEntity = ({
2724
3024
  ent.destroy();
2725
3025
  return;
2726
3026
  }
3027
+ await ent.updateTransform({ position, rotation, scale });
3028
+ if (controller.signal.aborted) {
3029
+ ent.destroy();
3030
+ return;
3031
+ }
2727
3032
  if (parent) {
2728
3033
  const result = await parent.addEntity(ent);
2729
3034
  if (!result.success) throw new Error("parent.addEntity failed");
@@ -2742,10 +3047,21 @@ var useEntity = ({
2742
3047
  controller.abort();
2743
3048
  instanceRef.current?.destroy();
2744
3049
  };
2745
- }, [ctx, parent]);
3050
+ }, [ctx, parent, recreateKey]);
2746
3051
  useEntityId({ id, entity: instanceRef.current.entity });
2747
3052
  useEntityTransform(instanceRef.current.entity, { position, rotation, scale });
2748
3053
  useEntityRef(ref, instanceRef.current);
3054
+ useEntityEvent({
3055
+ instance: instanceRef.current,
3056
+ onSpatialTap,
3057
+ onSpatialDragStart,
3058
+ onSpatialDrag,
3059
+ onSpatialDragEnd,
3060
+ onSpatialRotate,
3061
+ onSpatialRotateEnd,
3062
+ onSpatialMagnify,
3063
+ onSpatialMagnifyEnd
3064
+ });
2749
3065
  useEffect21(() => {
2750
3066
  const ent = instanceRef.current.entity;
2751
3067
  if (!ent) return;
@@ -2757,20 +3073,21 @@ var useEntity = ({
2757
3073
  };
2758
3074
 
2759
3075
  // src/reality/hooks/useForceUpdate.tsx
2760
- import { useCallback as useCallback7, useState as useState7 } from "react";
3076
+ import { useCallback as useCallback8, useState as useState7 } from "react";
2761
3077
  var useForceUpdate2 = () => {
2762
3078
  const [, setTick] = useState7(0);
2763
- return useCallback7(() => setTick((tick) => tick + 1), []);
3079
+ return useCallback8(() => setTick((tick) => tick + 1), []);
2764
3080
  };
2765
3081
 
2766
3082
  // src/reality/components/BaseEntity.tsx
2767
3083
  import { jsx as jsx12 } from "react/jsx-runtime";
2768
3084
  var BaseEntity = forwardRef10(
2769
- ({ children, createEntity, ...rest }, ref) => {
3085
+ ({ children, createEntity, recreateKey, ...rest }, ref) => {
2770
3086
  const ctx = useRealityContext();
2771
3087
  const entity = useEntity({
2772
3088
  ...rest,
2773
3089
  ref,
3090
+ recreateKey,
2774
3091
  createEntity: (signal) => createEntity(ctx, signal)
2775
3092
  });
2776
3093
  if (!entity) return null;
@@ -2798,35 +3115,102 @@ var Entity = forwardRef11((props, ref) => {
2798
3115
  import { forwardRef as forwardRef13 } from "react";
2799
3116
 
2800
3117
  // src/reality/components/GeometryEntity.tsx
2801
- import { forwardRef as forwardRef12 } from "react";
3118
+ import { forwardRef as forwardRef12, useEffect as useEffect22, useRef as useRef12 } from "react";
2802
3119
  import { jsx as jsx14 } from "react/jsx-runtime";
2803
3120
  var GeometryEntity = forwardRef12(
2804
3121
  ({ id, children, name, materials, geometryOptions, createGeometry, ...rest }, ref) => {
3122
+ const ctx = useRealityContext();
3123
+ const entityRef = useRef12(null);
3124
+ const componentRef = useRef12(null);
3125
+ const mutableRef = useRef12({
3126
+ lastSnapshot: null,
3127
+ rebuildGen: 0
3128
+ });
3129
+ useEffect22(() => {
3130
+ const { lastSnapshot } = mutableRef.current;
3131
+ if (!ctx || !entityRef.current || lastSnapshot === null) return;
3132
+ const geometryChanged = !shallowEqualObject(
3133
+ lastSnapshot.geometryOptions,
3134
+ geometryOptions
3135
+ );
3136
+ const materialsChanged = !shallowEqualArray(
3137
+ lastSnapshot.materials,
3138
+ materials
3139
+ );
3140
+ if (!geometryChanged && !materialsChanged) return;
3141
+ mutableRef.current.lastSnapshot = { geometryOptions, materials };
3142
+ mutableRef.current.rebuildGen += 1;
3143
+ const gen = mutableRef.current.rebuildGen;
3144
+ const rebuild = async () => {
3145
+ const entity = entityRef.current;
3146
+ if (!entity) return;
3147
+ try {
3148
+ const oldComponent = componentRef.current;
3149
+ if (oldComponent) {
3150
+ await entity.removeComponent(oldComponent);
3151
+ await oldComponent.destroy();
3152
+ componentRef.current = null;
3153
+ if (gen !== mutableRef.current.rebuildGen) return;
3154
+ }
3155
+ const geometry = await createGeometry(geometryOptions);
3156
+ if (gen !== mutableRef.current.rebuildGen) {
3157
+ await geometry.destroy();
3158
+ return;
3159
+ }
3160
+ const materialList = await Promise.all(
3161
+ materials?.map((mid) => ctx.resourceRegistry.get(mid)).filter(Boolean) ?? []
3162
+ );
3163
+ if (gen !== mutableRef.current.rebuildGen) {
3164
+ await geometry.destroy();
3165
+ return;
3166
+ }
3167
+ const modelComponent = await ctx.session.createModelComponent({
3168
+ mesh: geometry,
3169
+ materials: materialList
3170
+ });
3171
+ if (gen !== mutableRef.current.rebuildGen) {
3172
+ await modelComponent.destroy();
3173
+ await geometry.destroy();
3174
+ return;
3175
+ }
3176
+ await entity.addComponent(modelComponent);
3177
+ componentRef.current = modelComponent;
3178
+ } catch (error) {
3179
+ if (gen === mutableRef.current.rebuildGen) {
3180
+ console.error("GeometryEntity: rebuild failed", error);
3181
+ }
3182
+ }
3183
+ };
3184
+ rebuild();
3185
+ }, [ctx, geometryOptions, materials, createGeometry]);
2805
3186
  return /* @__PURE__ */ jsx14(
2806
3187
  BaseEntity,
2807
3188
  {
2808
3189
  ...rest,
2809
3190
  id,
2810
3191
  ref,
2811
- createEntity: async (ctx, signal) => {
3192
+ createEntity: async (ctx2, signal) => {
2812
3193
  const manager = new AbortResourceManager(signal);
2813
3194
  try {
2814
3195
  const ent = await manager.addResource(
2815
- () => ctx.session.createEntity({ id, name })
3196
+ () => ctx2.session.createEntity({ id, name })
2816
3197
  );
2817
3198
  const geometry = await manager.addResource(
2818
3199
  () => createGeometry(geometryOptions)
2819
3200
  );
2820
3201
  const materialList = await Promise.all(
2821
- materials?.map((id2) => ctx.resourceRegistry.get(id2)).filter(Boolean) ?? []
3202
+ materials?.map((id2) => ctx2.resourceRegistry.get(id2)).filter(Boolean) ?? []
2822
3203
  );
2823
3204
  const modelComponent = await manager.addResource(
2824
- () => ctx.session.createModelComponent({
3205
+ () => ctx2.session.createModelComponent({
2825
3206
  mesh: geometry,
2826
3207
  materials: materialList
2827
3208
  })
2828
3209
  );
2829
3210
  await ent.addComponent(modelComponent);
3211
+ entityRef.current = ent;
3212
+ componentRef.current = modelComponent;
3213
+ mutableRef.current.lastSnapshot = { geometryOptions, materials };
2830
3214
  return ent;
2831
3215
  } catch (error) {
2832
3216
  await manager.dispose();
@@ -2864,14 +3248,15 @@ var BoxEntity = forwardRef13(
2864
3248
  );
2865
3249
 
2866
3250
  // src/reality/components/UnlitMaterial.tsx
2867
- import { useEffect as useEffect22, useRef as useRef12 } from "react";
3251
+ import { useEffect as useEffect23, useRef as useRef13 } from "react";
2868
3252
  var UnlitMaterial = ({
2869
3253
  children,
2870
3254
  ...options
2871
3255
  }) => {
2872
3256
  const ctx = useRealityContext();
2873
- const materialRef = useRef12();
2874
- useEffect22(() => {
3257
+ const materialRef = useRef13();
3258
+ const isInitializedRef = useRef13(false);
3259
+ useEffect23(() => {
2875
3260
  if (!ctx) return;
2876
3261
  const { session, reality, resourceRegistry } = ctx;
2877
3262
  const init = async () => {
@@ -2880,6 +3265,7 @@ var UnlitMaterial = ({
2880
3265
  try {
2881
3266
  const mat = await materialPromise;
2882
3267
  materialRef.current = mat;
3268
+ isInitializedRef.current = true;
2883
3269
  } catch (error) {
2884
3270
  console.error(" ~ UnlitMaterial ~ error:", error);
2885
3271
  }
@@ -2887,8 +3273,21 @@ var UnlitMaterial = ({
2887
3273
  init();
2888
3274
  return () => {
2889
3275
  resourceRegistry.removeAndDestroy(options.id);
3276
+ materialRef.current = void 0;
3277
+ isInitializedRef.current = false;
2890
3278
  };
2891
3279
  }, [ctx]);
3280
+ useEffect23(() => {
3281
+ if (!isInitializedRef.current || !materialRef.current) return;
3282
+ const updates = {};
3283
+ if (options.color !== void 0) updates.color = options.color;
3284
+ if (options.transparent !== void 0)
3285
+ updates.transparent = options.transparent;
3286
+ if (options.opacity !== void 0) updates.opacity = options.opacity;
3287
+ if (Object.keys(updates).length > 0) {
3288
+ materialRef.current.updateProperties(updates);
3289
+ }
3290
+ }, [options.color, options.transparent, options.opacity]);
2892
3291
  return null;
2893
3292
  };
2894
3293
 
@@ -2987,7 +3386,7 @@ var SceneGraph = ({ children }) => {
2987
3386
  };
2988
3387
 
2989
3388
  // src/reality/components/ModelAsset.tsx
2990
- import { useEffect as useEffect23, useRef as useRef13 } from "react";
3389
+ import { useEffect as useEffect24, useRef as useRef14 } from "react";
2991
3390
  var resolveAssetUrl = (url) => {
2992
3391
  if (url.startsWith("http://") || url.startsWith("https://")) {
2993
3392
  return url;
@@ -2996,8 +3395,8 @@ var resolveAssetUrl = (url) => {
2996
3395
  };
2997
3396
  var ModelAsset = ({ children, ...options }) => {
2998
3397
  const ctx = useRealityContext();
2999
- const materialRef = useRef13();
3000
- useEffect23(() => {
3398
+ const materialRef = useRef14();
3399
+ useEffect24(() => {
3001
3400
  const controller = new AbortController();
3002
3401
  if (!ctx) return;
3003
3402
  const { session, reality, resourceRegistry } = ctx;
@@ -3027,29 +3426,66 @@ var ModelAsset = ({ children, ...options }) => {
3027
3426
  };
3028
3427
 
3029
3428
  // src/reality/components/ModelEntity.tsx
3030
- import { forwardRef as forwardRef18 } from "react";
3429
+ import { forwardRef as forwardRef18, useEffect as useEffect25, useRef as useRef15 } from "react";
3031
3430
  import { jsx as jsx21 } from "react/jsx-runtime";
3032
3431
  var ModelEntity = forwardRef18(
3033
- ({ id, model, children, name, ...rest }, ref) => {
3432
+ ({ id, model, children, name, materials, ...rest }, ref) => {
3433
+ const ctx = useRealityContext();
3434
+ const entityRef = useRef15(null);
3435
+ const lastMaterialsRef = useRef15(void 0);
3436
+ useEffect25(() => {
3437
+ if (!ctx || !entityRef.current) return;
3438
+ const next = materials ?? [];
3439
+ const prev = lastMaterialsRef.current ?? [];
3440
+ if (shallowEqualArray(prev, next)) return;
3441
+ lastMaterialsRef.current = next;
3442
+ const apply = async () => {
3443
+ try {
3444
+ const materialList = (await Promise.all(
3445
+ next.map((mid) => ctx.resourceRegistry.get(mid))
3446
+ )).filter(Boolean);
3447
+ if (entityRef.current) {
3448
+ await entityRef.current.setMaterials(materialList);
3449
+ }
3450
+ } catch (error) {
3451
+ console.error("ModelEntity: failed to set materials", error);
3452
+ }
3453
+ };
3454
+ apply();
3455
+ }, [ctx, materials]);
3034
3456
  return /* @__PURE__ */ jsx21(
3035
3457
  BaseEntity,
3036
3458
  {
3037
3459
  ...rest,
3038
3460
  id,
3039
3461
  ref,
3040
- createEntity: async (ctx, signal) => {
3462
+ recreateKey: model,
3463
+ createEntity: async (ctx2, signal) => {
3041
3464
  try {
3042
- const modelAsset = await ctx.resourceRegistry.get(model);
3465
+ const modelAsset = await ctx2.resourceRegistry.get(model);
3043
3466
  if (!modelAsset)
3044
3467
  throw new Error(`ModelEntity: model not found ${model}`);
3045
3468
  if (signal.aborted) return null;
3046
- return ctx.session.createSpatialModelEntity(
3469
+ const ent = await ctx2.session.createSpatialModelEntity(
3047
3470
  {
3048
3471
  modelAssetId: modelAsset.id,
3049
3472
  name
3050
3473
  },
3051
3474
  { id, name }
3052
3475
  );
3476
+ entityRef.current = ent;
3477
+ if (materials && materials.length > 0) {
3478
+ const materialList = (await Promise.all(
3479
+ materials.map(
3480
+ (mid) => ctx2.resourceRegistry.get(mid)
3481
+ )
3482
+ )).filter(Boolean);
3483
+ if (materialList.length > 0 && !signal.aborted) {
3484
+ await ent.setMaterials(materialList);
3485
+ }
3486
+ }
3487
+ lastMaterialsRef.current = materials ?? [];
3488
+ return ent;
3053
3489
  } catch (error) {
3054
3490
  return null;
3055
3491
  }
@@ -3063,9 +3499,9 @@ var ModelEntity = forwardRef18(
3063
3499
  // src/reality/components/Reality.tsx
3064
3500
  import {
3065
3501
  forwardRef as forwardRef19,
3066
- useCallback as useCallback8,
3067
- useEffect as useEffect24,
3068
- useRef as useRef14,
3502
+ useCallback as useCallback9,
3503
+ useEffect as useEffect26,
3504
+ useRef as useRef16,
3069
3505
  useState as useState8
3070
3506
  } from "react";
3071
3507
  import { Fragment as Fragment3, jsx as jsx22, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -3089,23 +3525,23 @@ var Reality = forwardRef19(
3089
3525
  onSpatialMagnifyEnd,
3090
3526
  ...props
3091
3527
  } = inProps;
3092
- const ctxRef = useRef14(null);
3093
- const creationId = useRef14(0);
3528
+ const ctxRef = useRef16(null);
3529
+ const creationId = useRef16(0);
3094
3530
  const [isReady, setIsReady] = useState8(false);
3095
- const cleanupReality = useCallback8(() => {
3531
+ const cleanupReality = useCallback9(() => {
3096
3532
  ctxRef.current?.attachmentRegistry.destroy();
3097
3533
  ctxRef.current?.resourceRegistry.destroy();
3098
3534
  ctxRef.current?.reality.destroy();
3099
3535
  ctxRef.current = null;
3100
3536
  setIsReady(false);
3101
3537
  }, []);
3102
- useEffect24(() => {
3538
+ useEffect26(() => {
3103
3539
  return () => {
3104
3540
  creationId.current++;
3105
3541
  cleanupReality();
3106
3542
  };
3107
3543
  }, [cleanupReality]);
3108
- const createReality = useCallback8(async () => {
3544
+ const createReality = useCallback9(async () => {
3109
3545
  const id = ++creationId.current;
3110
3546
  const resourceRegistry = new ResourceRegistry();
3111
3547
  const attachmentRegistry = new AttachmentRegistry();
@@ -3148,7 +3584,7 @@ var Reality = forwardRef19(
3148
3584
  return null;
3149
3585
  }
3150
3586
  }, [cleanupReality]);
3151
- const content = useCallback8(() => /* @__PURE__ */ jsx22(Fragment3, {}), []);
3587
+ const content = useCallback9(() => /* @__PURE__ */ jsx22(Fragment3, {}), []);
3152
3588
  useRealityEvents({
3153
3589
  instance: ctxRef.current?.reality ?? null,
3154
3590
  onSpatialTap,
@@ -3177,7 +3613,7 @@ var Reality = forwardRef19(
3177
3613
  );
3178
3614
 
3179
3615
  // src/reality/components/AttachmentAsset.tsx
3180
- import { useEffect as useEffect25, useState as useState9 } from "react";
3616
+ import { useEffect as useEffect27, useState as useState9 } from "react";
3181
3617
  import { createPortal as createPortal3 } from "react-dom";
3182
3618
  import { jsx as jsx23 } from "react/jsx-runtime";
3183
3619
  var AttachmentAsset = ({
@@ -3186,7 +3622,7 @@ var AttachmentAsset = ({
3186
3622
  }) => {
3187
3623
  const ctx = useRealityContext();
3188
3624
  const [containers, setContainers] = useState9([]);
3189
- useEffect25(() => {
3625
+ useEffect27(() => {
3190
3626
  if (!ctx) return;
3191
3627
  return ctx.attachmentRegistry.onContainersChange(name, setContainers);
3192
3628
  }, [ctx, name]);
@@ -3197,7 +3633,7 @@ var AttachmentAsset = ({
3197
3633
  };
3198
3634
 
3199
3635
  // src/reality/components/AttachmentEntity.tsx
3200
- import { useEffect as useEffect26, useRef as useRef15, useState as useState10 } from "react";
3636
+ import { useEffect as useEffect28, useRef as useRef17, useState as useState10 } from "react";
3201
3637
  var instanceCounter = 0;
3202
3638
  var AttachmentEntity = ({
3203
3639
  attachment: attachmentName,
@@ -3206,12 +3642,12 @@ var AttachmentEntity = ({
3206
3642
  }) => {
3207
3643
  const ctx = useRealityContext();
3208
3644
  const parent = useParentContext();
3209
- const attachmentRef = useRef15(null);
3210
- const parentIdRef = useRef15(null);
3211
- const instanceIdRef = useRef15(`att_${++instanceCounter}`);
3212
- const attachmentNameRef = useRef15(attachmentName);
3645
+ const attachmentRef = useRef17(null);
3646
+ const parentIdRef = useRef17(null);
3647
+ const instanceIdRef = useRef17(`att_${++instanceCounter}`);
3648
+ const attachmentNameRef = useRef17(attachmentName);
3213
3649
  const [childWindow, setChildWindow] = useState10(null);
3214
- useEffect26(() => {
3650
+ useEffect28(() => {
3215
3651
  if (!ctx || !parent) return;
3216
3652
  if (attachmentRef.current) return;
3217
3653
  const parentId = parent.id;
@@ -3273,7 +3709,7 @@ var AttachmentEntity = ({
3273
3709
  }
3274
3710
  };
3275
3711
  }, [ctx, parent]);
3276
- useEffect26(() => {
3712
+ useEffect28(() => {
3277
3713
  if (!ctx) return;
3278
3714
  const att = attachmentRef.current;
3279
3715
  const prevName = attachmentNameRef.current;
@@ -3290,7 +3726,7 @@ var AttachmentEntity = ({
3290
3726
  }
3291
3727
  }, [ctx, attachmentName]);
3292
3728
  useSyncHeadStyles(childWindow, { subtree: false });
3293
- useEffect26(() => {
3729
+ useEffect28(() => {
3294
3730
  if (!attachmentRef.current) return;
3295
3731
  attachmentRef.current.update({ position, size });
3296
3732
  }, [position?.[0], position?.[1], position?.[2], size?.width, size?.height]);
@@ -3424,7 +3860,7 @@ async function convertCoordinate(position, { from, to }) {
3424
3860
  }
3425
3861
 
3426
3862
  // src/index.ts
3427
- var version = "1.4.0";
3863
+ var version = "1.6.0";
3428
3864
  if (typeof window !== "undefined") {
3429
3865
  initPolyfill();
3430
3866
  }