@webspatial/core-sdk 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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__['core-sdk-version'] = "1.5.0"
5
+ window.__webspatialsdk__['core-sdk-version'] = "1.6.1"
6
6
  })()
7
7
 
8
8
  var __defProp = Object.defineProperty;
@@ -824,6 +824,16 @@ function composeSRT(position, rotation, scale) {
824
824
  m = m.scale(sx, sy, sz);
825
825
  return m;
826
826
  }
827
+ function deepCloneJSON(value) {
828
+ const sc = globalThis.structuredClone;
829
+ if (typeof sc === "function") {
830
+ try {
831
+ return sc(value);
832
+ } catch {
833
+ }
834
+ }
835
+ return JSON.parse(JSON.stringify(value));
836
+ }
827
837
  var init_utils = __esm({
828
838
  "src/utils.ts"() {
829
839
  "use strict";
@@ -1015,14 +1025,16 @@ var init_JSBCommand = __esm({
1015
1025
  }
1016
1026
  };
1017
1027
  CreateSpatializedStatic3DElementCommand = class extends JSBCommand {
1018
- constructor(modelURL) {
1028
+ constructor(modelURL, sources) {
1019
1029
  super();
1020
1030
  this.modelURL = modelURL;
1031
+ this.sources = sources;
1021
1032
  this.modelURL = modelURL;
1033
+ this.sources = sources;
1022
1034
  }
1023
1035
  commandType = "CreateSpatializedStatic3DElement";
1024
1036
  getParams() {
1025
- return { modelURL: this.modelURL };
1037
+ return { modelURL: this.modelURL, sources: this.sources };
1026
1038
  }
1027
1039
  };
1028
1040
  CreateSpatializedDynamic3DElementCommand = class extends JSBCommand {
@@ -1554,6 +1566,69 @@ var CubeInfo = class {
1554
1566
  }
1555
1567
  };
1556
1568
 
1569
+ // src/scene-polyfill.ts
1570
+ init_utils();
1571
+
1572
+ // src/physicalMetrics.ts
1573
+ var physicalMetrics_exports = {};
1574
+ __export(physicalMetrics_exports, {
1575
+ getValue: () => getValue,
1576
+ physicalToPoint: () => physicalToPoint,
1577
+ pointToPhysical: () => pointToPhysical,
1578
+ subscribe: () => subscribe
1579
+ });
1580
+ init_SpatialWebEvent();
1581
+ var snapshot = {
1582
+ meterToPtUnscaled: 1360,
1583
+ meterToPtScaled: 1360
1584
+ };
1585
+ function getWorldScalingCompensation(options) {
1586
+ return options?.worldScalingCompensation ?? "scaled";
1587
+ }
1588
+ function pointToPhysical(point, options) {
1589
+ updateValue();
1590
+ const compensation = getWorldScalingCompensation(options);
1591
+ if (compensation === "unscaled") {
1592
+ return point / snapshot.meterToPtUnscaled;
1593
+ }
1594
+ return point / snapshot.meterToPtScaled;
1595
+ }
1596
+ function physicalToPoint(physical, options) {
1597
+ updateValue();
1598
+ const compensation = getWorldScalingCompensation(options);
1599
+ if (compensation === "unscaled") {
1600
+ return physical * snapshot.meterToPtUnscaled;
1601
+ }
1602
+ return physical * snapshot.meterToPtScaled;
1603
+ }
1604
+ function updateValue() {
1605
+ if (typeof window === "undefined") return;
1606
+ const src = window.__webspatialsdk__?.physicalMetrics;
1607
+ if (!src) return;
1608
+ const next = {
1609
+ meterToPtScaled: src.meterToPtScaled ?? snapshot.meterToPtScaled,
1610
+ meterToPtUnscaled: src.meterToPtUnscaled ?? snapshot.meterToPtUnscaled
1611
+ };
1612
+ if (next.meterToPtScaled !== snapshot.meterToPtScaled || next.meterToPtUnscaled !== snapshot.meterToPtUnscaled) {
1613
+ snapshot = next;
1614
+ }
1615
+ }
1616
+ function getValue() {
1617
+ updateValue();
1618
+ return snapshot;
1619
+ }
1620
+ function subscribe(cb) {
1621
+ if (typeof window === "undefined") return () => {
1622
+ };
1623
+ const handler = () => {
1624
+ cb();
1625
+ };
1626
+ SpatialWebEvent.addEventReceiver("window", handler);
1627
+ return () => {
1628
+ SpatialWebEvent.removeEventReceiver("window");
1629
+ };
1630
+ }
1631
+
1557
1632
  // src/scene-polyfill.ts
1558
1633
  var defaultSceneConfig = {
1559
1634
  defaultSize: {
@@ -1563,15 +1638,47 @@ var defaultSceneConfig = {
1563
1638
  };
1564
1639
  var defaultSceneConfigVolume = {
1565
1640
  defaultSize: {
1566
- width: 0.94,
1567
- height: 0.94,
1568
- depth: 0.94
1641
+ width: "0.94m",
1642
+ height: "0.94m",
1643
+ depth: "0.94m"
1569
1644
  }
1570
1645
  };
1646
+ var xr_window_defaults = {
1647
+ ...defaultSceneConfig
1648
+ };
1649
+ var xr_volume_defaults = {
1650
+ ...defaultSceneConfigVolume
1651
+ };
1571
1652
  var INTERNAL_SCHEMA_PREFIX = "webspatial://";
1653
+ function deepMergePlain(base, over) {
1654
+ if (!over) return { ...base || {} };
1655
+ const out = { ...base || {} };
1656
+ for (const k of Object.keys(over)) {
1657
+ const bv = out[k];
1658
+ const ov = over[k];
1659
+ if (ov && typeof ov === "object" && !Array.isArray(ov) && bv && typeof bv === "object" && !Array.isArray(bv)) {
1660
+ out[k] = deepMergePlain(bv, ov);
1661
+ } else {
1662
+ out[k] = ov;
1663
+ }
1664
+ }
1665
+ return out;
1666
+ }
1667
+ function normalizeXRDefaultsToSceneOptions(src) {
1668
+ const out = { ...src || {} };
1669
+ const ds = src.defaultSize !== void 0 ? src.defaultSize : src.default_size;
1670
+ if (ds !== void 0) {
1671
+ out.defaultSize = ds;
1672
+ }
1673
+ if ("default_size" in out) {
1674
+ delete out.default_size;
1675
+ }
1676
+ return out;
1677
+ }
1572
1678
  var SceneManager = class _SceneManager {
1573
1679
  originalOpen;
1574
1680
  static instance;
1681
+ manifestReady = null;
1575
1682
  static getInstance() {
1576
1683
  if (!_SceneManager.instance) {
1577
1684
  _SceneManager.instance = new _SceneManager();
@@ -1579,15 +1686,23 @@ var SceneManager = class _SceneManager {
1579
1686
  return _SceneManager.instance;
1580
1687
  }
1581
1688
  init(window2) {
1689
+ this.manifestReady = this.setupManifest();
1582
1690
  this.originalOpen = window2.open.bind(window2);
1583
1691
  window2.open = this.open;
1584
1692
  }
1693
+ // Stores the latest formatted config used by the platform (per scene name).
1694
+ // This object contains normalized values and is safe for internal consumption.
1585
1695
  configMap = {};
1586
- // name=>config
1696
+ // Stores the raw callback return value (per scene name) to feed into the next initScene call as `pre`.
1697
+ // We keep this unformatted so developers receive exactly what they last returned.
1698
+ callbackReturnMap = {};
1587
1699
  getConfig(name) {
1588
1700
  if (name === void 0 || !this.configMap[name]) return void 0;
1589
1701
  return this.configMap[name];
1590
1702
  }
1703
+ waitManifest() {
1704
+ return this.manifestReady ?? Promise.resolve();
1705
+ }
1591
1706
  // Ensure URL is absolute; only convert when a relative path is provided
1592
1707
  // - Keep external and special schemes untouched (http, https, data, blob, about, file, mailto, etc.)
1593
1708
  // - Handle protocol-relative URLs (//example.com/path)
@@ -1606,14 +1721,41 @@ var SceneManager = class _SceneManager {
1606
1721
  return raw;
1607
1722
  }
1608
1723
  }
1724
+ async setupManifest() {
1725
+ const manifest = await this.getPWAManifest();
1726
+ try {
1727
+ const xr = manifest?.xr_spatial_scene;
1728
+ if (!xr || typeof xr !== "object") return;
1729
+ const { overrides, ...topLevel } = xr;
1730
+ const windowRaw = deepMergePlain(topLevel, overrides?.window_scene);
1731
+ const volumeRaw = deepMergePlain(topLevel, overrides?.volume_scene);
1732
+ const windowNext = normalizeXRDefaultsToSceneOptions(windowRaw);
1733
+ const volumeNext = normalizeXRDefaultsToSceneOptions(volumeRaw);
1734
+ if (windowNext && Object.keys(windowNext).length > 0) {
1735
+ xr_window_defaults = windowNext;
1736
+ }
1737
+ if (volumeNext && Object.keys(volumeNext).length > 0) {
1738
+ xr_volume_defaults = volumeNext;
1739
+ }
1740
+ } catch (error) {
1741
+ console.warn(
1742
+ "SceneManager.setupManifest failed; using built-in defaults.",
1743
+ error?.message || error
1744
+ );
1745
+ }
1746
+ }
1609
1747
  open = (url, target, features) => {
1610
1748
  if (url?.startsWith(INTERNAL_SCHEMA_PREFIX)) {
1611
- if (url.includes("createSpatialized2DElement")) {
1612
- const token = window.webSpatial?.genToken?.();
1749
+ if (url.includes("createSpatialized2DElement") || url.includes("createAttachment")) {
1750
+ const token = (
1751
+ //@ts-ignore
1752
+ (window.webSpatial || window.__webspatialShell__)?.genToken?.()
1753
+ );
1613
1754
  if (token) {
1755
+ const command = url.includes("createAttachment") ? "createAttachment" : "createSpatialized2DElement";
1614
1756
  const host = window.location.host;
1615
1757
  const protocol = window.location.protocol;
1616
- const finalURL = `${protocol}//${host}/${token}/?command=createSpatialized2DElement`;
1758
+ const finalURL = `${protocol}//${host}/${token}/?command=${command}`;
1617
1759
  const rid = new URL(url).searchParams.get("rid");
1618
1760
  const final = new URL(finalURL);
1619
1761
  if (rid) final.searchParams.set("rid", rid);
@@ -1627,7 +1769,12 @@ var SceneManager = class _SceneManager {
1627
1769
  const newWindow = this.originalOpen(url, target, features);
1628
1770
  return newWindow;
1629
1771
  }
1630
- const cfg = target ? this.getConfig(target) : void 0;
1772
+ let cfg = target ? this.getConfig(target) : void 0;
1773
+ if (cfg === void 0) {
1774
+ const preFormatted = deepCloneJSON(getSceneDefaultConfig("window"));
1775
+ const [ans] = formatSceneConfig(preFormatted, "window");
1776
+ cfg = { ...ans, type: "window" };
1777
+ }
1631
1778
  const cmd = new createSpatialSceneCommand(url, cfg, target, features);
1632
1779
  const result = cmd.executeSync();
1633
1780
  const id = result.data?.id;
@@ -1639,23 +1786,116 @@ var SceneManager = class _SceneManager {
1639
1786
  };
1640
1787
  initScene(name, callback, options) {
1641
1788
  const sceneType = options?.type ?? "window";
1642
- const defaultConfig = getSceneDefaultConfig(sceneType);
1643
- const rawReturnVal = callback({ ...defaultConfig });
1644
- const [formattedConfig, errors] = formatSceneConfig(rawReturnVal, sceneType);
1789
+ const defaultConfigRaw = getSceneDefaultConfig(sceneType);
1790
+ const previousOrDefault = this.callbackReturnMap[name] ?? (() => {
1791
+ const cloned = deepCloneJSON(defaultConfigRaw);
1792
+ return cloned;
1793
+ })();
1794
+ const rawReturnVal = callback(previousOrDefault);
1795
+ const sanitizedReturnVal = sanitizeSceneOptionsUnits(
1796
+ deepCloneJSON(rawReturnVal)
1797
+ );
1798
+ const clonedForFormat = deepCloneJSON(sanitizedReturnVal);
1799
+ const baseDefaults = deepCloneJSON(getSceneDefaultConfig(sceneType));
1800
+ const mergedForFormat = deepMergePlain(baseDefaults, clonedForFormat);
1801
+ const [formattedConfig, errors] = formatSceneConfig(
1802
+ mergedForFormat,
1803
+ sceneType
1804
+ );
1645
1805
  if (errors.length > 0) {
1646
1806
  console.warn(`initScene ${name} with errors: ${errors.join(", ")}`);
1647
1807
  }
1808
+ this.callbackReturnMap[name] = sanitizedReturnVal;
1648
1809
  this.configMap[name] = {
1649
1810
  ...formattedConfig,
1650
1811
  type: sceneType
1651
1812
  };
1652
1813
  }
1814
+ /**
1815
+ * Resolve and load a PWA manifest as JSON:
1816
+ * 1) Determine href:
1817
+ * - Prefer explicit manifestUrl if provided;
1818
+ * - Fallback to <link rel="manifest">, preferring the raw attribute over computed href.
1819
+ * 2) Normalize href to absolute using ensureAbsoluteUrl (respects <base href>).
1820
+ * 3) Handle data URLs inline:
1821
+ * - data:...;base64,... → atob then JSON.parse
1822
+ * - data:...,... → decodeURIComponent then JSON.parse
1823
+ * 4) Fetch with credentials same-origin first; if that fails (e.g., CORS), attempt unauthenticated fetch.
1824
+ * 5) Parse as JSON; if response body is text, parse the text as JSON.
1825
+ */
1826
+ async getPWAManifest(manifestUrl) {
1827
+ let href = manifestUrl;
1828
+ if (!href) {
1829
+ const el = document.querySelector(
1830
+ 'link[rel="manifest"]'
1831
+ );
1832
+ href = el?.getAttribute("href") || el?.href;
1833
+ }
1834
+ if (!href) return;
1835
+ href = this.ensureAbsoluteUrl(href);
1836
+ if (!href) return;
1837
+ if (href.startsWith("data:")) {
1838
+ try {
1839
+ const comma = href.indexOf(",");
1840
+ if (comma < 0) return;
1841
+ const meta = href.slice(5, comma);
1842
+ const data = href.slice(comma + 1);
1843
+ const isBase64 = /;base64/i.test(meta);
1844
+ const decoded = isBase64 ? atob(data) : decodeURIComponent(data);
1845
+ return JSON.parse(decoded);
1846
+ } catch {
1847
+ return;
1848
+ }
1849
+ }
1850
+ try {
1851
+ const res = await fetch(href, { credentials: "same-origin" });
1852
+ if (!res.ok) throw new Error(String(res.status));
1853
+ try {
1854
+ return await res.json();
1855
+ } catch {
1856
+ const t = await res.text();
1857
+ return JSON.parse(t);
1858
+ }
1859
+ } catch {
1860
+ try {
1861
+ const res = await fetch(href);
1862
+ if (!res.ok) return;
1863
+ try {
1864
+ return await res.json();
1865
+ } catch {
1866
+ const t = await res.text();
1867
+ return JSON.parse(t);
1868
+ }
1869
+ } catch {
1870
+ return;
1871
+ }
1872
+ }
1873
+ }
1653
1874
  };
1875
+ function sanitizeSceneOptionsUnits(val) {
1876
+ if (val?.defaultSize) {
1877
+ const keys = ["width", "height", "depth"];
1878
+ for (const k of keys) {
1879
+ if (k in val.defaultSize && !isValidSceneUnit(val.defaultSize[k])) {
1880
+ delete val.defaultSize[k];
1881
+ }
1882
+ }
1883
+ }
1884
+ if (val?.resizability) {
1885
+ const keys = ["minWidth", "minHeight", "maxWidth", "maxHeight"];
1886
+ for (const k of keys) {
1887
+ if (k in val.resizability && !isValidSceneUnit(val.resizability[k])) {
1888
+ delete val.resizability[k];
1889
+ }
1890
+ }
1891
+ }
1892
+ return val;
1893
+ }
1654
1894
  function pxToMeter(px) {
1655
- return px / 1360;
1895
+ return pointToPhysical(px);
1656
1896
  }
1657
1897
  function meterToPx(meter) {
1658
- return meter * 1360;
1898
+ return physicalToPoint(meter);
1659
1899
  }
1660
1900
  function formatToNumber(str, targetUnit, defaultUnit) {
1661
1901
  if (typeof str === "number") {
@@ -1705,11 +1945,10 @@ function formatSceneConfig(config, sceneType) {
1705
1945
  config.defaultSize[k] = formatToNumber(
1706
1946
  config.defaultSize[k],
1707
1947
  isWindow ? "px" : "m",
1708
- isWindow ? "px" : "m"
1948
+ "px"
1709
1949
  );
1710
1950
  } else {
1711
- ;
1712
- config.defaultSize[k] = defaultSceneConfig2.defaultSize[k];
1951
+ delete config.defaultSize[k];
1713
1952
  errors.push(`defaultSize.${k}`);
1714
1953
  }
1715
1954
  }
@@ -1723,11 +1962,10 @@ function formatSceneConfig(config, sceneType) {
1723
1962
  config.resizability[k] = formatToNumber(
1724
1963
  config.resizability[k],
1725
1964
  "px",
1726
- isWindow ? "px" : "m"
1965
+ "px"
1727
1966
  );
1728
1967
  } else {
1729
- ;
1730
- config.resizability[k] = void 0;
1968
+ delete config.resizability[k];
1731
1969
  errors.push(`resizability.${k}`);
1732
1970
  }
1733
1971
  }
@@ -1761,15 +1999,14 @@ function hijackWindowOpen(window2) {
1761
1999
  function hijackWindowATag(openedWindow) {
1762
2000
  openedWindow.document.onclick = function(e) {
1763
2001
  let element = e.target;
1764
- let found = false;
1765
- while (!found) {
1766
- if (element && element.tagName == "A") {
1767
- if (handleATag(e)) {
2002
+ while (element) {
2003
+ if (element.tagName == "A") {
2004
+ if (handleATag(e, element)) {
1768
2005
  return false;
1769
2006
  }
1770
2007
  return true;
1771
2008
  }
1772
- if (element && element.parentElement) {
2009
+ if (element.parentElement) {
1773
2010
  element = element.parentElement;
1774
2011
  } else {
1775
2012
  break;
@@ -1777,21 +2014,18 @@ function hijackWindowATag(openedWindow) {
1777
2014
  }
1778
2015
  };
1779
2016
  }
1780
- function handleATag(event) {
1781
- const targetElement = event.target;
1782
- if (targetElement.tagName === "A") {
1783
- const link = targetElement;
1784
- const target = link.target;
1785
- const url = link.href;
1786
- if (target && target !== "_self") {
1787
- event.preventDefault();
1788
- window.open(url, target);
1789
- return true;
1790
- }
2017
+ function handleATag(event, link) {
2018
+ if (event.defaultPrevented) return false;
2019
+ const target = link.target;
2020
+ const url = link.href;
2021
+ if (target && target !== "_self") {
2022
+ event.preventDefault();
2023
+ window.open(url, target);
2024
+ return true;
1791
2025
  }
1792
2026
  }
1793
2027
  function getSceneDefaultConfig(sceneType) {
1794
- return sceneType === "window" ? defaultSceneConfig : defaultSceneConfigVolume;
2028
+ return sceneType === "window" ? xr_window_defaults || defaultSceneConfig : xr_volume_defaults || defaultSceneConfigVolume;
1795
2029
  }
1796
2030
  async function injectScenePolyfill() {
1797
2031
  if (!window.opener) return;
@@ -1805,13 +2039,14 @@ async function injectScenePolyfill() {
1805
2039
  }
1806
2040
  }
1807
2041
  onContentLoaded(async () => {
1808
- let provideDefaultSceneConfig = getSceneDefaultConfig(
1809
- window.xrCurrentSceneType ?? "window"
1810
- );
1811
- let cfg = provideDefaultSceneConfig;
2042
+ await SceneManager.getInstance().waitManifest();
2043
+ const sceneType = window.xrCurrentSceneType ?? "window";
2044
+ const rawDefault = getSceneDefaultConfig(sceneType);
2045
+ const pre = deepCloneJSON(rawDefault);
2046
+ let cfg = pre;
1812
2047
  if (typeof window.xrCurrentSceneDefaults === "function") {
1813
2048
  try {
1814
- cfg = await window.xrCurrentSceneDefaults?.(provideDefaultSceneConfig);
2049
+ cfg = await window.xrCurrentSceneDefaults?.(pre);
1815
2050
  } catch (error) {
1816
2051
  console.error(error);
1817
2052
  }
@@ -1821,17 +2056,18 @@ async function injectScenePolyfill() {
1821
2056
  resolve(null);
1822
2057
  }, 1e3);
1823
2058
  });
1824
- const sceneType = window.xrCurrentSceneType ?? "window";
1825
- const [formattedConfig, errors] = formatSceneConfig(cfg, sceneType);
2059
+ const mergedCfg = deepMergePlain(deepCloneJSON(rawDefault), cfg);
2060
+ const [formattedConfig, errors] = formatSceneConfig(mergedCfg, sceneType);
1826
2061
  if (errors.length > 0) {
1827
2062
  console.warn(
1828
2063
  `window.xrCurrentSceneDefaults with errors: ${errors.join(", ")}`
1829
2064
  );
1830
2065
  }
1831
- await SpatialScene.getInstance().updateSceneCreationConfig({
2066
+ const finalCfg = {
1832
2067
  ...formattedConfig,
1833
2068
  type: sceneType
1834
- });
2069
+ };
2070
+ await SpatialScene.getInstance().updateSceneCreationConfig(finalCfg);
1835
2071
  });
1836
2072
  }
1837
2073
  function injectSceneHook() {
@@ -2082,10 +2318,12 @@ var SpatializedStatic3DElement = class extends SpatializedElement {
2082
2318
  * Registers the element to receive spatial events.
2083
2319
  * @param id Unique identifier for this element
2084
2320
  * @param modelURL URL of the 3D model
2321
+ * @param sources Optional fallback model sources
2085
2322
  */
2086
- constructor(id, modelURL) {
2323
+ constructor(id, modelURL, sources) {
2087
2324
  super(id);
2088
2325
  this.modelURL = modelURL;
2326
+ this.sources = sources;
2089
2327
  }
2090
2328
  /**
2091
2329
  * Promise resolver for the ready state.
@@ -2097,6 +2335,17 @@ var SpatializedStatic3DElement = class extends SpatializedElement {
2097
2335
  * Used to reset the ready promise when the model URL changes.
2098
2336
  */
2099
2337
  modelURL;
2338
+ /**
2339
+ * Caches the last sources array to detect changes.
2340
+ */
2341
+ sources;
2342
+ /**
2343
+ * The model URL that was successfully loaded by the native runtime.
2344
+ */
2345
+ _currentSrc = "";
2346
+ get currentSrc() {
2347
+ return this._currentSrc;
2348
+ }
2100
2349
  /**
2101
2350
  * Creates a new promise for tracking the ready state of the model.
2102
2351
  * @returns Promise that resolves when the model is loaded (true) or fails to load (false)
@@ -2119,17 +2368,100 @@ var SpatializedStatic3DElement = class extends SpatializedElement {
2119
2368
  * @returns Promise resolving when the update is complete
2120
2369
  */
2121
2370
  async updateProperties(properties) {
2371
+ let needsReadyReset = false;
2122
2372
  if (properties.modelURL !== void 0) {
2123
2373
  if (this.modelURL !== properties.modelURL) {
2124
2374
  this.modelURL = properties.modelURL;
2125
- this.ready = this.createReadyPromise();
2375
+ needsReadyReset = true;
2126
2376
  }
2127
2377
  }
2378
+ if (properties.sources !== void 0) {
2379
+ const prevJson = JSON.stringify(this.sources);
2380
+ const nextJson = JSON.stringify(properties.sources);
2381
+ if (prevJson !== nextJson) {
2382
+ this.sources = properties.sources;
2383
+ needsReadyReset = true;
2384
+ }
2385
+ }
2386
+ if (needsReadyReset) {
2387
+ this.ready = this.createReadyPromise();
2388
+ }
2389
+ if (properties.autoplay !== void 0) {
2390
+ this._autoplay = properties.autoplay;
2391
+ }
2392
+ if (properties.loop !== void 0) {
2393
+ this._loop = properties.loop;
2394
+ }
2395
+ if (properties.playbackRate !== void 0) {
2396
+ this._playbackRate = properties.playbackRate;
2397
+ }
2128
2398
  return new UpdateSpatializedStatic3DElementProperties(
2129
2399
  this,
2130
2400
  properties
2131
2401
  ).execute();
2132
2402
  }
2403
+ /**
2404
+ * Total animation duration in seconds, synced from native.
2405
+ */
2406
+ _duration = 0;
2407
+ /**
2408
+ * Returns the total animation duration in seconds.
2409
+ */
2410
+ get duration() {
2411
+ return this._duration;
2412
+ }
2413
+ /**
2414
+ * Playback speed multiplier.
2415
+ */
2416
+ _playbackRate = 1;
2417
+ /**
2418
+ * Returns the current playback rate.
2419
+ */
2420
+ get playbackRate() {
2421
+ return this._playbackRate;
2422
+ }
2423
+ /**
2424
+ * Sets the playback rate and sends it to native.
2425
+ */
2426
+ set playbackRate(value) {
2427
+ this.updateProperties({ playbackRate: value });
2428
+ }
2429
+ /**
2430
+ * Whether the animation is currently paused.
2431
+ */
2432
+ _paused = true;
2433
+ /**
2434
+ * Returns whether the animation is currently paused.
2435
+ */
2436
+ get paused() {
2437
+ return this._paused;
2438
+ }
2439
+ /**
2440
+ * Callback for animation state changes.
2441
+ */
2442
+ _onAnimationStateChangeCallback;
2443
+ /**
2444
+ * Sets the callback for animation state changes.
2445
+ */
2446
+ set onAnimationStateChangeCallback(callback) {
2447
+ this._onAnimationStateChangeCallback = callback;
2448
+ }
2449
+ /**
2450
+ * Starts or resumes animation playback.
2451
+ * @returns Promise resolving when the command is sent
2452
+ */
2453
+ async play() {
2454
+ this._paused = false;
2455
+ await this.updateProperties({ animationPaused: false });
2456
+ }
2457
+ /**
2458
+ * Pauses animation playback.
2459
+ * @returns Promise resolving when the command is sent
2460
+ */
2461
+ async pause() {
2462
+ this._paused = true;
2463
+ await this.updateProperties({ animationPaused: true });
2464
+ }
2133
2465
  /**
2134
2466
  * Processes events received from the WebSpatial environment.
2135
2467
  * Handles model loading events in addition to base spatial events.
@@ -2137,15 +2469,40 @@ var SpatializedStatic3DElement = class extends SpatializedElement {
2137
2469
  */
2138
2470
  onReceiveEvent(data) {
2139
2471
  if (data.type === "modelloaded" /* modelloaded */) {
2472
+ this._currentSrc = data.detail?.src ?? this.modelURL ?? "";
2140
2473
  this._onLoadCallback?.();
2141
2474
  this._readyResolve?.(true);
2142
2475
  } else if (data.type === "modelloadfailed" /* modelloadfailed */) {
2143
2476
  this._onLoadFailureCallback?.();
2144
2477
  this._readyResolve?.(false);
2478
+ } else if (data.type === "animationstatechange" /* animationstatechange */) {
2479
+ this._paused = data.detail.paused;
2480
+ this._duration = data.detail.duration;
2481
+ this._onAnimationStateChangeCallback?.(data.detail);
2145
2482
  } else {
2146
2483
  super.onReceiveEvent(data);
2147
2484
  }
2148
2485
  }
2486
+ /**
2487
+ * Whether the model should automatically play its first animation on load.
2488
+ */
2489
+ _autoplay = false;
2490
+ /**
2491
+ * Returns whether autoplay is enabled for this element.
2492
+ */
2493
+ get autoplay() {
2494
+ return this._autoplay;
2495
+ }
2496
+ /**
2497
+ * Whether the model animation should loop continuously.
2498
+ */
2499
+ _loop = false;
2500
+ /**
2501
+ * Returns whether loop is enabled for this element.
2502
+ */
2503
+ get loop() {
2504
+ return this._loop;
2505
+ }
2149
2506
  /**
2150
2507
  * Callback function for successful model loading.
2151
2508
  */
@@ -2219,15 +2576,16 @@ async function createSpatialized2DElement() {
2219
2576
  return new Spatialized2DElement(id, windowProxy);
2220
2577
  }
2221
2578
  }
2222
- async function createSpatializedStatic3DElement(modelURL) {
2579
+ async function createSpatializedStatic3DElement(modelURL, sources) {
2223
2580
  const result = await new CreateSpatializedStatic3DElementCommand(
2224
- modelURL
2581
+ modelURL,
2582
+ sources
2225
2583
  ).execute();
2226
2584
  if (!result.success) {
2227
2585
  throw new Error("createSpatializedStatic3DElement failed");
2228
2586
  } else {
2229
2587
  const { id } = result.data;
2230
- return new SpatializedStatic3DElement(id, modelURL);
2588
+ return new SpatializedStatic3DElement(id, modelURL, sources);
2231
2589
  }
2232
2590
  }
2233
2591
  async function createSpatializedDynamic3DElement() {
@@ -2675,8 +3033,8 @@ var SpatialSession = class {
2675
3033
  * @param modelURL Optional URL to the 3D model to load
2676
3034
  * @returns Promise resolving to a new SpatializedStatic3DElement instance
2677
3035
  */
2678
- createSpatializedStatic3DElement(modelURL) {
2679
- return createSpatializedStatic3DElement(modelURL);
3036
+ createSpatializedStatic3DElement(modelURL, sources) {
3037
+ return createSpatializedStatic3DElement(modelURL, sources);
2680
3038
  }
2681
3039
  /**
2682
3040
  * Initializes the spatial scene with custom configuration.
@@ -2811,7 +3169,7 @@ var Spatial = class {
2811
3169
  * @returns True if running in a spatial web environment, false otherwise
2812
3170
  */
2813
3171
  runInSpatialWeb() {
2814
- if (navigator.userAgent.indexOf("WebSpatial/") > 0) {
3172
+ if (navigator.userAgent.indexOf("WebSpatial/") >= 0) {
2815
3173
  return true;
2816
3174
  }
2817
3175
  return false;
@@ -2855,70 +3213,10 @@ var Spatial = class {
2855
3213
  * @returns Client SDK version string in format "x.x.x"
2856
3214
  */
2857
3215
  getClientVersion() {
2858
- return "1.5.0";
3216
+ return "1.6.1";
2859
3217
  }
2860
3218
  };
2861
3219
 
2862
- // src/physicalMetrics.ts
2863
- var physicalMetrics_exports = {};
2864
- __export(physicalMetrics_exports, {
2865
- getValue: () => getValue,
2866
- physicalToPoint: () => physicalToPoint,
2867
- pointToPhysical: () => pointToPhysical,
2868
- subscribe: () => subscribe
2869
- });
2870
- init_SpatialWebEvent();
2871
- var snapshot = {
2872
- meterToPtUnscaled: 1360,
2873
- meterToPtScaled: 1360
2874
- };
2875
- function getWorldScalingCompensation(options) {
2876
- return options?.worldScalingCompensation ?? "scaled";
2877
- }
2878
- function pointToPhysical(point, options) {
2879
- updateValue();
2880
- const compensation = getWorldScalingCompensation(options);
2881
- if (compensation === "unscaled") {
2882
- return point / snapshot.meterToPtUnscaled;
2883
- }
2884
- return point / snapshot.meterToPtScaled;
2885
- }
2886
- function physicalToPoint(physical, options) {
2887
- updateValue();
2888
- const compensation = getWorldScalingCompensation(options);
2889
- if (compensation === "unscaled") {
2890
- return physical * snapshot.meterToPtUnscaled;
2891
- }
2892
- return physical * snapshot.meterToPtScaled;
2893
- }
2894
- function updateValue() {
2895
- if (typeof window === "undefined") return;
2896
- const src = window.__webspatialsdk__?.physicalMetrics;
2897
- if (!src) return;
2898
- const next = {
2899
- meterToPtScaled: src.meterToPtScaled ?? snapshot.meterToPtScaled,
2900
- meterToPtUnscaled: src.meterToPtUnscaled ?? snapshot.meterToPtUnscaled
2901
- };
2902
- if (next.meterToPtScaled !== snapshot.meterToPtScaled || next.meterToPtUnscaled !== snapshot.meterToPtUnscaled) {
2903
- snapshot = next;
2904
- }
2905
- }
2906
- function getValue() {
2907
- updateValue();
2908
- return snapshot;
2909
- }
2910
- function subscribe(cb) {
2911
- if (typeof window === "undefined") return () => {
2912
- };
2913
- const handler = () => {
2914
- cb();
2915
- };
2916
- SpatialWebEvent.addEventReceiver("window", handler);
2917
- return () => {
2918
- SpatialWebEvent.removeEventReceiver("window");
2919
- };
2920
- }
2921
-
2922
3220
  // src/index.ts
2923
3221
  init_ssr_polyfill();
2924
3222