@playcanvas/web-components 0.12.0 → 0.13.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.
Files changed (85) hide show
  1. package/README.md +1 -1
  2. package/dist/app.d.cts +246 -0
  3. package/dist/app.d.ts +26 -3
  4. package/dist/asset.d.cts +218 -0
  5. package/dist/asset.d.ts +1 -1
  6. package/dist/async-element.d.cts +103 -0
  7. package/dist/async-element.d.ts +6 -4
  8. package/dist/colors.d.cts +1 -0
  9. package/dist/components/button-component.d.cts +186 -0
  10. package/dist/components/button-component.d.ts +1 -1
  11. package/dist/components/camera-component.d.cts +253 -0
  12. package/dist/components/camera-component.d.ts +1 -1
  13. package/dist/components/collision-component.d.cts +73 -0
  14. package/dist/components/collision-component.d.ts +1 -1
  15. package/dist/components/component.d.cts +82 -0
  16. package/dist/components/component.d.ts +1 -1
  17. package/dist/components/element-component.d.cts +316 -0
  18. package/dist/components/element-component.d.ts +1 -1
  19. package/dist/components/gsplat-component.d.cts +108 -0
  20. package/dist/components/gsplat-component.d.ts +1 -1
  21. package/dist/components/layoutchild-component.d.cts +110 -0
  22. package/dist/components/layoutchild-component.d.ts +1 -1
  23. package/dist/components/layoutgroup-component.d.cts +136 -0
  24. package/dist/components/layoutgroup-component.d.ts +1 -1
  25. package/dist/components/light-component.d.cts +264 -0
  26. package/dist/components/light-component.d.ts +1 -1
  27. package/dist/components/listener-component.d.cts +20 -0
  28. package/dist/components/listener-component.d.ts +1 -1
  29. package/dist/components/particlesystem-component.d.cts +52 -0
  30. package/dist/components/particlesystem-component.d.ts +1 -1
  31. package/dist/components/render-component.d.cts +76 -0
  32. package/dist/components/render-component.d.ts +1 -1
  33. package/dist/components/rigidbody-component.d.cts +88 -0
  34. package/dist/components/rigidbody-component.d.ts +1 -1
  35. package/dist/components/screen-component.d.cts +70 -0
  36. package/dist/components/screen-component.d.ts +1 -1
  37. package/dist/components/script-component.d.cts +163 -0
  38. package/dist/components/script-component.d.ts +1 -1
  39. package/dist/components/script.d.cts +94 -0
  40. package/dist/components/script.d.ts +1 -1
  41. package/dist/components/scrollbar-component.d.cts +69 -0
  42. package/dist/components/scrollbar-component.d.ts +1 -1
  43. package/dist/components/scrollview-component.d.cts +178 -0
  44. package/dist/components/scrollview-component.d.ts +1 -1
  45. package/dist/components/sound-component.d.cts +108 -0
  46. package/dist/components/sound-component.d.ts +1 -1
  47. package/dist/components/sound-slot.d.cts +134 -0
  48. package/dist/components/sound-slot.d.ts +2 -2
  49. package/dist/custom-elements.json +79 -21
  50. package/dist/entity-base.d.cts +67 -0
  51. package/dist/entity-base.d.ts +2 -2
  52. package/dist/entity.d.cts +131 -0
  53. package/dist/entity.d.ts +1 -1
  54. package/dist/index.d.cts +79 -0
  55. package/dist/index.d.ts +33 -33
  56. package/dist/loading-bar.d.cts +35 -0
  57. package/dist/material.d.cts +1011 -0
  58. package/dist/model.d.cts +72 -0
  59. package/dist/model.d.ts +1 -1
  60. package/dist/module.d.cts +29 -0
  61. package/dist/module.d.ts +16 -10
  62. package/dist/node.d.cts +253 -0
  63. package/dist/node.d.ts +1 -1
  64. package/dist/parse.d.cts +147 -0
  65. package/dist/pwc.cjs +141 -41
  66. package/dist/pwc.cjs.map +1 -1
  67. package/dist/pwc.js +142 -42
  68. package/dist/pwc.js.map +1 -1
  69. package/dist/pwc.min.js +1 -1
  70. package/dist/pwc.min.js.map +1 -1
  71. package/dist/pwc.min.mjs +1 -1
  72. package/dist/pwc.min.mjs.map +1 -1
  73. package/dist/pwc.mjs +142 -42
  74. package/dist/pwc.mjs.map +1 -1
  75. package/dist/scene.d.cts +117 -0
  76. package/dist/scene.d.ts +1 -1
  77. package/dist/sky.d.cts +121 -0
  78. package/dist/sky.d.ts +1 -1
  79. package/dist/vscode.html-custom-data.json +2 -2
  80. package/dist/web-types.json +41 -5
  81. package/package.json +16 -7
  82. package/src/app.ts +119 -30
  83. package/src/async-element.ts +4 -2
  84. package/src/loading-bar.ts +2 -2
  85. package/src/module.ts +39 -20
package/dist/pwc.cjs CHANGED
@@ -110,12 +110,18 @@ async function whenReady(target) {
110
110
 
111
111
  /**
112
112
  * The ModuleElement interface provides properties and methods for manipulating
113
- * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-module/ | `<pc-module>`} elements.
114
- * The ModuleElement interface also inherits the properties and methods of the
115
- * {@link HTMLElement} interface.
113
+ * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-module/ | `<pc-module>`}
114
+ * elements. The ModuleElement interface also inherits the properties and methods of the
115
+ * {@link AsyncElement} interface.
116
+ *
117
+ * The attributes are read once, when the module starts loading - on the element's first
118
+ * connection, or earlier if a containing `<pc-app>` boots first and collects it - so changing
119
+ * them later has no effect. The element becomes ready once the module has loaded. WebAssembly
120
+ * modules configure engine-global state that never unloads, so readiness is not re-armed by
121
+ * removing the element, and a re-inserted element does not load again.
116
122
  *
117
- * Note that these attributes are read once when the element is created, so changing them later
118
- * has no effect.
123
+ * A `<pc-module>` without a `name` warns and never becomes ready; a containing `<pc-app>` still
124
+ * boots.
119
125
  *
120
126
  * @attribute {string} name - The name of the WebAssembly module to configure, e.g. `Basis` or
121
127
  * `Ammo`.
@@ -124,19 +130,22 @@ async function whenReady(target) {
124
130
  * @attribute {string} fallback - The URL of the module's asm.js fallback script, used when
125
131
  * WebAssembly is unavailable.
126
132
  */
127
- class ModuleElement extends HTMLElement {
128
- loadPromise;
129
- /** @ignore */
130
- constructor() {
131
- super();
132
- this.loadPromise = this.loadModule();
133
+ class ModuleElement extends AsyncElement {
134
+ _loadPromise = null;
135
+ connectedCallback() {
136
+ this._getLoadPromise();
133
137
  }
134
- async loadModule() {
138
+ async _loadModule() {
135
139
  const name = this.getAttribute('name');
136
- const glueUrl = this.getAttribute('glue');
137
- const wasmUrl = this.getAttribute('wasm');
138
- const fallbackUrl = this.getAttribute('fallback');
139
- const config = { glueUrl, wasmUrl, fallbackUrl };
140
+ if (!name) {
141
+ console.warn("pc-module requires a 'name' attribute - no module was configured");
142
+ return;
143
+ }
144
+ const config = {
145
+ glueUrl: this.getAttribute('glue') ?? undefined,
146
+ wasmUrl: this.getAttribute('wasm') ?? undefined,
147
+ fallbackUrl: this.getAttribute('fallback') ?? undefined
148
+ };
140
149
  if (name === 'Basis') {
141
150
  playcanvas.basisInitialize(config);
142
151
  }
@@ -146,16 +155,22 @@ class ModuleElement extends HTMLElement {
146
155
  playcanvas.WasmModule.getInstance(name, () => resolve());
147
156
  });
148
157
  }
158
+ this._onReady();
149
159
  }
150
160
  /**
151
- * Returns the promise that settles when the module has loaded. Awaited by the containing
152
- * `<pc-app>` element before it creates its graphics device.
161
+ * Returns the promise that settles when the module has loaded, starting the load if it has
162
+ * not already started - a containing `<pc-app>` boots in document order, so it may collect
163
+ * this element before the element's own connectedCallback has run. A missing `name` resolves
164
+ * the promise without configuring anything, so a misconfigured module never blocks the app.
153
165
  *
154
166
  * @returns The load promise.
155
167
  * @internal
156
168
  */
157
169
  _getLoadPromise() {
158
- return this.loadPromise;
170
+ if (!this._loadPromise) {
171
+ this._loadPromise = this._loadModule();
172
+ }
173
+ return this._loadPromise;
159
174
  }
160
175
  }
161
176
  customElements.define('pc-module', ModuleElement);
@@ -185,9 +200,9 @@ class LoadingBar {
185
200
  this._track.setAttribute('aria-label', 'Loading');
186
201
  this._track.setAttribute('aria-valuemin', '0');
187
202
  this._track.setAttribute('aria-valuemax', '100');
188
- // Fixed positioning matches the canvas, which always fills the window (FILLMODE_FILL_WINDOW)
203
+ // Anchored to the pc-app element, which the library's base styles make a positioned box
189
204
  this._track.style.cssText = [
190
- 'position: fixed',
205
+ 'position: absolute',
191
206
  'top: 0',
192
207
  'left: 0',
193
208
  'width: 100%',
@@ -699,16 +714,44 @@ const getEntity = (ref) => {
699
714
 
700
715
  /** The pointer event types the application synthesizes on `<pc-entity>` elements via picking. */
701
716
  const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'];
717
+ /**
718
+ * Gives `pc-app` the sizing contract of a replaced element (`<video>`, `<img>`): a block-level
719
+ * box that the page's CSS sizes, defaulting to the canvas's own 300x150 intrinsic size, with the
720
+ * canvas and loading bar anchored to it. `:where()` keeps every declaration at zero specificity,
721
+ * so any page rule - however plain - overrides these defaults.
722
+ */
723
+ const ensureBaseStyles = () => {
724
+ const id = 'pc-app-styles';
725
+ if (document.getElementById(id)) {
726
+ return;
727
+ }
728
+ const style = document.createElement('style');
729
+ style.id = id;
730
+ style.textContent = ':where(pc-app) { display: block; position: relative; width: 300px; height: 150px; }';
731
+ document.head.appendChild(style);
732
+ };
702
733
  /**
703
734
  * The AppElement interface provides properties and methods for manipulating
704
735
  * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-app/ | `<pc-app>`} elements.
705
736
  * The AppElement interface also inherits the properties and methods of the
706
737
  * {@link HTMLElement} interface.
707
738
  *
739
+ * The element is sized like a replaced element such as `<video>`: a block-level box that the
740
+ * page's CSS controls, 300x150 by default. The application's canvas always fills the element,
741
+ * and the drawing buffer resolution follows the element's size (capped by `max-pixel-ratio`),
742
+ * tracked live via a ResizeObserver — so the element can be embedded at any size, resized by
743
+ * its container, or made fullscreen with ordinary CSS such as `width: 100vw; height: 100dvh`.
744
+ *
708
745
  * @fires {ProgressEvent} progress - Fired while the application preloads its assets. `loaded` and
709
746
  * `total` are asset counts, not bytes, and an asset that fails to load still counts as loaded.
710
747
  * Fired at least once per boot, and the final event always has `loaded` equal to `total`. Does
711
748
  * not bubble.
749
+ *
750
+ * @fires {ErrorEvent} error - Fired when the application cannot boot because no graphics device
751
+ * could be created (for example, a browser with WebGL disabled). `message` names the requested
752
+ * backends and `error` holds the underlying failure. The element never becomes ready
753
+ * and `app` stays `null` — listen for this event to show a fallback UI. Removing the element and
754
+ * re-inserting it retries the boot with its current attributes. Does not bubble.
712
755
  */
713
756
  class AppElement extends AsyncElement {
714
757
  /**
@@ -768,6 +811,12 @@ class AppElement extends AsyncElement {
768
811
  };
769
812
  _app = null;
770
813
  _loadProgress = 0;
814
+ /**
815
+ * Tracks the element's box so the drawing buffer and picker follow it. Created per boot once
816
+ * the application exists, and disconnected on teardown. `null` where ResizeObserver is
817
+ * unavailable (jsdom), where the boot-time resolution set is the only sizing that happens.
818
+ */
819
+ _resizeObserver = null;
771
820
  /**
772
821
  * The PlayCanvas application instance. `null` until the element is ready, and again once it
773
822
  * has been removed from the document — await {@link whenReady} or the element's `ready()`
@@ -794,8 +843,6 @@ class AppElement extends AsyncElement {
794
843
  */
795
844
  constructor() {
796
845
  super();
797
- // Bind methods to maintain 'this' context
798
- this._onWindowResize = this._onWindowResize.bind(this);
799
846
  // Track pointer listeners being added to and removed from descendant entities.
800
847
  // Registered once here rather than on every boot - the handlers no-op while there is no
801
848
  // canvas, and a re-booted element must not stack a second set.
@@ -806,6 +853,9 @@ class AppElement extends AsyncElement {
806
853
  }
807
854
  async connectedCallback() {
808
855
  const generation = ++this._bootGeneration;
856
+ // Installed before the loading bar is created: the bar anchors to this element, which
857
+ // these styles make a positioned block box
858
+ ensureBaseStyles();
809
859
  // Created before the first await, so the bar is visible while modules and the graphics
810
860
  // device are created, and exists before any disconnect could need to clean it up
811
861
  if (this._loadingBar && !this._bar) {
@@ -820,8 +870,11 @@ class AppElement extends AsyncElement {
820
870
  if (generation !== this._bootGeneration) {
821
871
  return;
822
872
  }
823
- // Create and append the canvas to the element
873
+ // Create and append the canvas, filling the element's content box - the page sizes the
874
+ // element, and everything else follows. touch-action: none keeps touch drags driving the
875
+ // engine's input handlers instead of scrolling the page.
824
876
  this._canvas = document.createElement('canvas');
877
+ this._canvas.style.cssText = 'display: block; width: 100%; height: 100%; touch-action: none;';
825
878
  this.appendChild(this._canvas);
826
879
  // Configure device types based on backend selection
827
880
  const backendToDeviceTypes = {
@@ -831,14 +884,44 @@ class AppElement extends AsyncElement {
831
884
  };
832
885
  const deviceTypes = backendToDeviceTypes[this._backend] || [];
833
886
  this._optionsLocked = true;
834
- const device = await playcanvas.createGraphicsDevice(this._canvas, {
835
- // @ts-ignore - alpha needs to be documented
836
- alpha: this._alpha,
837
- antialias: this._antialias,
838
- depth: this._depthBuffer,
839
- deviceTypes: deviceTypes,
840
- stencil: this._stencilBuffer
841
- });
887
+ // createGraphicsDevice appends its final null-device fallback to the array in place, so
888
+ // the requested list is captured now for the failure message.
889
+ const requested = deviceTypes.join(', ');
890
+ let device;
891
+ try {
892
+ device = await playcanvas.createGraphicsDevice(this._canvas, {
893
+ // @ts-ignore - alpha needs to be documented
894
+ alpha: this._alpha,
895
+ antialias: this._antialias,
896
+ depth: this._depthBuffer,
897
+ deviceTypes: deviceTypes,
898
+ stencil: this._stencilBuffer
899
+ });
900
+ }
901
+ catch (error) {
902
+ // The element may have been removed while device creation was failing. The teardown
903
+ // has already cleaned up, and the failure belongs to a boot that no longer owns the
904
+ // element.
905
+ if (generation !== this._bootGeneration) {
906
+ return;
907
+ }
908
+ // Return the element to its pre-boot state - no dead canvas, no loading bar stuck at
909
+ // zero - before announcing the failure. Readiness deliberately stays pending: nothing
910
+ // it would announce (the app, the entity hierarchy) exists, so a device-less element
911
+ // joins the documented never-ready cases and the failure surfaces through the error
912
+ // event instead.
913
+ if (this._canvas && this.contains(this._canvas)) {
914
+ this.removeChild(this._canvas);
915
+ }
916
+ this._canvas = null;
917
+ this._bar?.destroy();
918
+ this._bar = null;
919
+ const reason = error instanceof Error ? error.message : String(error);
920
+ const message = `pc-app failed to create a graphics device (${requested}) - ${reason}`;
921
+ console.error(message, error);
922
+ this.dispatchEvent(new ErrorEvent('error', { message, error }));
923
+ return;
924
+ }
842
925
  // The element may have been removed while the device was created. disconnectedCallback
843
926
  // has already cleaned up the canvas; the device was created inside the await, so it is
844
927
  // this boot's to release.
@@ -917,9 +1000,19 @@ class AppElement extends AsyncElement {
917
1000
  const app = new playcanvas.AppBase(this._canvas);
918
1001
  this._app = app;
919
1002
  app.init(createOptions);
920
- app.setCanvasFillMode(playcanvas.FILLMODE_FILL_WINDOW);
1003
+ // FILLMODE_NONE leaves the canvas's CSS sizing alone (the engine's other fill modes
1004
+ // stamp window-derived pixel sizes onto it); RESOLUTION_AUTO sizes the drawing buffer
1005
+ // from the canvas's client size
1006
+ app.setCanvasFillMode(playcanvas.FILLMODE_NONE);
921
1007
  app.setCanvasResolution(playcanvas.RESOLUTION_AUTO);
922
1008
  this._pickerCreate();
1009
+ // Track the element's box rather than the window: containers resize without any window
1010
+ // event (splitter drags, flex reflow, animations). Guarded because jsdom has no
1011
+ // ResizeObserver - there, the resolution set above is the only sizing that happens.
1012
+ if (typeof ResizeObserver !== 'undefined') {
1013
+ this._resizeObserver = new ResizeObserver(() => this._syncCanvasSize());
1014
+ this._resizeObserver.observe(this);
1015
+ }
923
1016
  // Get all pc-asset elements that are direct children of the pc-app element
924
1017
  const assetElements = this.querySelectorAll(':scope > pc-asset');
925
1018
  for (const assetElement of Array.from(assetElements)) {
@@ -995,8 +1088,6 @@ class AppElement extends AsyncElement {
995
1088
  // Dismiss the bar only once a frame has actually rendered; ready fires before the
996
1089
  // first rAF tick
997
1090
  app.once('frameend', () => this._bar?.complete());
998
- // Handle window resize to keep the canvas responsive
999
- window.addEventListener('resize', this._onWindowResize);
1000
1091
  this._onReady();
1001
1092
  });
1002
1093
  }
@@ -1021,18 +1112,27 @@ class AppElement extends AsyncElement {
1021
1112
  // no longer holds.
1022
1113
  this._hierarchyReady = false;
1023
1114
  this._resetReady();
1024
- // Remove event listeners
1025
- window.removeEventListener('resize', this._onWindowResize);
1115
+ // Stop tracking the element's size
1116
+ this._resizeObserver?.disconnect();
1117
+ this._resizeObserver = null;
1026
1118
  // Remove the canvas
1027
1119
  if (this._canvas && this.contains(this._canvas)) {
1028
1120
  this.removeChild(this._canvas);
1029
1121
  this._canvas = null;
1030
1122
  }
1031
1123
  }
1032
- _onWindowResize() {
1033
- if (this.app) {
1034
- this.app.resizeCanvas();
1124
+ /**
1125
+ * Syncs the drawing buffer and the picker to the canvas's current CSS size. The picker must
1126
+ * track the buffer, or picks would land at stale coordinates after a resize. Skipped while
1127
+ * an XR session presents - the session owns the buffer size.
1128
+ */
1129
+ _syncCanvasSize() {
1130
+ if (!this.app || this.app.xr?.active) {
1131
+ return;
1035
1132
  }
1133
+ this.app.updateCanvasSize();
1134
+ const { width, height } = this.app.graphicsDevice;
1135
+ this._picker?.resize(width, height);
1036
1136
  }
1037
1137
  _pickerCreate() {
1038
1138
  const { width, height } = this.app.graphicsDevice;
@@ -1370,7 +1470,7 @@ class AppElement extends AsyncElement {
1370
1470
  this._maxPixelRatio = value;
1371
1471
  if (this.app) {
1372
1472
  this.app.graphicsDevice.maxPixelRatio = value;
1373
- this.app.resizeCanvas();
1473
+ this._syncCanvasSize();
1374
1474
  }
1375
1475
  }
1376
1476
  /**