@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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { basisInitialize, WasmModule, Vec3, Color, Vec4, Quat, Vec2, createGraphicsDevice, AppOptions, Keyboard, Mouse, ElementInput, AnimComponentSystem, AnimationComponentSystem, AudioListenerComponentSystem, ButtonComponentSystem, CameraComponentSystem, CollisionComponentSystem, ElementComponentSystem, GSplatComponentSystem, JointComponentSystem, LayoutChildComponentSystem, LayoutGroupComponentSystem, LightComponentSystem, ModelComponentSystem, ParticleSystemComponentSystem, RenderComponentSystem, RigidBodyComponentSystem, ScreenComponentSystem, ScriptComponentSystem, ScrollbarComponentSystem, ScrollViewComponentSystem, SoundComponentSystem, SpriteComponentSystem, ZoneComponentSystem, AnimClipHandler, AnimationHandler, AnimStateGraphHandler, AudioHandler, BinaryHandler, CssHandler, ContainerHandler, CubemapHandler, FolderHandler, FontHandler, GSplatHandler, HierarchyHandler, HtmlHandler, JsonHandler, MaterialHandler, ModelHandler, RenderHandler, ScriptHandler, SceneHandler, ShaderHandler, SpriteHandler, TemplateHandler, TextHandler, TextureAtlasHandler, TextureHandler, SoundManager, Lightmapper, BatchManager, XrManager, AppBase, FILLMODE_FILL_WINDOW, RESOLUTION_AUTO, Picker, MeshInstance, Entity, Asset, SPRITE_RENDERMODE_SIMPLE, FILTER_LINEAR_MIPMAP_LINEAR, FILTER_LINEAR, ADDRESS_REPEAT, SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED, ADDRESS_CLAMP_TO_EDGE, ADDRESS_MIRRORED_REPEAT, FILTER_NEAREST, FILTER_NEAREST_MIPMAP_NEAREST, FILTER_LINEAR_MIPMAP_NEAREST, FILTER_NEAREST_MIPMAP_LINEAR, BUTTON_TRANSITION_MODE_TINT, BUTTON_TRANSITION_MODE_SPRITE_CHANGE, TONEMAP_NONE, PROJECTION_PERSPECTIVE, GAMMA_SRGB, GAMMA_NONE, XRTYPE_VR, TONEMAP_LINEAR, TONEMAP_FILMIC, TONEMAP_HEJL, TONEMAP_ACES, TONEMAP_ACES2, TONEMAP_NEUTRAL, PROJECTION_ORTHOGRAPHIC, ORIENTATION_HORIZONTAL, FITTING_NONE, FITTING_STRETCH, FITTING_SHRINK, FITTING_BOTH, ORIENTATION_VERTICAL, SHADOW_PCF3_32F, SHADOW_PCF1_16F, SHADOW_PCF1_32F, SHADOW_PCF3_16F, SHADOW_PCF5_16F, SHADOW_PCF5_32F, SHADOW_VSM_16F, SHADOW_VSM_32F, SHADOW_PCSS_32F, StandardMaterial, BLEND_NONE, CULLFACE_BACK, FRESNEL_SCHLICK, SPECOCC_AO, BLEND_NORMAL, BLEND_ADDITIVE, BLEND_ADDITIVEALPHA, BLEND_PREMULTIPLIED, BLEND_MULTIPLICATIVE, BLEND_MULTIPLICATIVE2X, BLEND_SCREEN, BLEND_MIN, BLEND_MAX, BLEND_SUBTRACTIVE, CULLFACE_NONE, CULLFACE_FRONT, CULLFACE_FRONTANDBACK, FRESNEL_NONE, SPECOCC_NONE, SPECOCC_GLOSSDEPENDENT, SCALEMODE_NONE, SCALEMODE_BLEND, SCROLL_MODE_BOUNCE, SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED, SCROLLBAR_VISIBILITY_SHOW_ALWAYS, SCROLL_MODE_CLAMP, SCROLL_MODE_INFINITE, EnvLighting, LAYERID_SKYBOX } from 'playcanvas';
1
+ import { basisInitialize, WasmModule, Vec3, Color, Vec4, Quat, Vec2, createGraphicsDevice, AppOptions, Keyboard, Mouse, ElementInput, AnimComponentSystem, AnimationComponentSystem, AudioListenerComponentSystem, ButtonComponentSystem, CameraComponentSystem, CollisionComponentSystem, ElementComponentSystem, GSplatComponentSystem, JointComponentSystem, LayoutChildComponentSystem, LayoutGroupComponentSystem, LightComponentSystem, ModelComponentSystem, ParticleSystemComponentSystem, RenderComponentSystem, RigidBodyComponentSystem, ScreenComponentSystem, ScriptComponentSystem, ScrollbarComponentSystem, ScrollViewComponentSystem, SoundComponentSystem, SpriteComponentSystem, ZoneComponentSystem, AnimClipHandler, AnimationHandler, AnimStateGraphHandler, AudioHandler, BinaryHandler, CssHandler, ContainerHandler, CubemapHandler, FolderHandler, FontHandler, GSplatHandler, HierarchyHandler, HtmlHandler, JsonHandler, MaterialHandler, ModelHandler, RenderHandler, ScriptHandler, SceneHandler, ShaderHandler, SpriteHandler, TemplateHandler, TextHandler, TextureAtlasHandler, TextureHandler, SoundManager, Lightmapper, BatchManager, XrManager, AppBase, FILLMODE_NONE, RESOLUTION_AUTO, Picker, MeshInstance, Entity, Asset, SPRITE_RENDERMODE_SIMPLE, FILTER_LINEAR_MIPMAP_LINEAR, FILTER_LINEAR, ADDRESS_REPEAT, SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED, ADDRESS_CLAMP_TO_EDGE, ADDRESS_MIRRORED_REPEAT, FILTER_NEAREST, FILTER_NEAREST_MIPMAP_NEAREST, FILTER_LINEAR_MIPMAP_NEAREST, FILTER_NEAREST_MIPMAP_LINEAR, BUTTON_TRANSITION_MODE_TINT, BUTTON_TRANSITION_MODE_SPRITE_CHANGE, TONEMAP_NONE, PROJECTION_PERSPECTIVE, GAMMA_SRGB, GAMMA_NONE, XRTYPE_VR, TONEMAP_LINEAR, TONEMAP_FILMIC, TONEMAP_HEJL, TONEMAP_ACES, TONEMAP_ACES2, TONEMAP_NEUTRAL, PROJECTION_ORTHOGRAPHIC, ORIENTATION_HORIZONTAL, FITTING_NONE, FITTING_STRETCH, FITTING_SHRINK, FITTING_BOTH, ORIENTATION_VERTICAL, SHADOW_PCF3_32F, SHADOW_PCF1_16F, SHADOW_PCF1_32F, SHADOW_PCF3_16F, SHADOW_PCF5_16F, SHADOW_PCF5_32F, SHADOW_VSM_16F, SHADOW_VSM_32F, SHADOW_PCSS_32F, StandardMaterial, BLEND_NONE, CULLFACE_BACK, FRESNEL_SCHLICK, SPECOCC_AO, BLEND_NORMAL, BLEND_ADDITIVE, BLEND_ADDITIVEALPHA, BLEND_PREMULTIPLIED, BLEND_MULTIPLICATIVE, BLEND_MULTIPLICATIVE2X, BLEND_SCREEN, BLEND_MIN, BLEND_MAX, BLEND_SUBTRACTIVE, CULLFACE_NONE, CULLFACE_FRONT, CULLFACE_FRONTANDBACK, FRESNEL_NONE, SPECOCC_NONE, SPECOCC_GLOSSDEPENDENT, SCALEMODE_NONE, SCALEMODE_BLEND, SCROLL_MODE_BOUNCE, SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED, SCROLLBAR_VISIBILITY_SHOW_ALWAYS, SCROLL_MODE_CLAMP, SCROLL_MODE_INFINITE, EnvLighting, LAYERID_SKYBOX } from 'playcanvas';
2
2
 
3
3
  /**
4
4
  * Base class for all PlayCanvas Web Components that initialize asynchronously.
@@ -108,12 +108,18 @@ async function whenReady(target) {
108
108
 
109
109
  /**
110
110
  * The ModuleElement interface provides properties and methods for manipulating
111
- * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-module/ | `<pc-module>`} elements.
112
- * The ModuleElement interface also inherits the properties and methods of the
113
- * {@link HTMLElement} interface.
111
+ * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-module/ | `<pc-module>`}
112
+ * elements. The ModuleElement interface also inherits the properties and methods of the
113
+ * {@link AsyncElement} interface.
114
+ *
115
+ * The attributes are read once, when the module starts loading - on the element's first
116
+ * connection, or earlier if a containing `<pc-app>` boots first and collects it - so changing
117
+ * them later has no effect. The element becomes ready once the module has loaded. WebAssembly
118
+ * modules configure engine-global state that never unloads, so readiness is not re-armed by
119
+ * removing the element, and a re-inserted element does not load again.
114
120
  *
115
- * Note that these attributes are read once when the element is created, so changing them later
116
- * has no effect.
121
+ * A `<pc-module>` without a `name` warns and never becomes ready; a containing `<pc-app>` still
122
+ * boots.
117
123
  *
118
124
  * @attribute {string} name - The name of the WebAssembly module to configure, e.g. `Basis` or
119
125
  * `Ammo`.
@@ -122,19 +128,22 @@ async function whenReady(target) {
122
128
  * @attribute {string} fallback - The URL of the module's asm.js fallback script, used when
123
129
  * WebAssembly is unavailable.
124
130
  */
125
- class ModuleElement extends HTMLElement {
126
- loadPromise;
127
- /** @ignore */
128
- constructor() {
129
- super();
130
- this.loadPromise = this.loadModule();
131
+ class ModuleElement extends AsyncElement {
132
+ _loadPromise = null;
133
+ connectedCallback() {
134
+ this._getLoadPromise();
131
135
  }
132
- async loadModule() {
136
+ async _loadModule() {
133
137
  const name = this.getAttribute('name');
134
- const glueUrl = this.getAttribute('glue');
135
- const wasmUrl = this.getAttribute('wasm');
136
- const fallbackUrl = this.getAttribute('fallback');
137
- const config = { glueUrl, wasmUrl, fallbackUrl };
138
+ if (!name) {
139
+ console.warn("pc-module requires a 'name' attribute - no module was configured");
140
+ return;
141
+ }
142
+ const config = {
143
+ glueUrl: this.getAttribute('glue') ?? undefined,
144
+ wasmUrl: this.getAttribute('wasm') ?? undefined,
145
+ fallbackUrl: this.getAttribute('fallback') ?? undefined
146
+ };
138
147
  if (name === 'Basis') {
139
148
  basisInitialize(config);
140
149
  }
@@ -144,16 +153,22 @@ class ModuleElement extends HTMLElement {
144
153
  WasmModule.getInstance(name, () => resolve());
145
154
  });
146
155
  }
156
+ this._onReady();
147
157
  }
148
158
  /**
149
- * Returns the promise that settles when the module has loaded. Awaited by the containing
150
- * `<pc-app>` element before it creates its graphics device.
159
+ * Returns the promise that settles when the module has loaded, starting the load if it has
160
+ * not already started - a containing `<pc-app>` boots in document order, so it may collect
161
+ * this element before the element's own connectedCallback has run. A missing `name` resolves
162
+ * the promise without configuring anything, so a misconfigured module never blocks the app.
151
163
  *
152
164
  * @returns The load promise.
153
165
  * @internal
154
166
  */
155
167
  _getLoadPromise() {
156
- return this.loadPromise;
168
+ if (!this._loadPromise) {
169
+ this._loadPromise = this._loadModule();
170
+ }
171
+ return this._loadPromise;
157
172
  }
158
173
  }
159
174
  customElements.define('pc-module', ModuleElement);
@@ -183,9 +198,9 @@ class LoadingBar {
183
198
  this._track.setAttribute('aria-label', 'Loading');
184
199
  this._track.setAttribute('aria-valuemin', '0');
185
200
  this._track.setAttribute('aria-valuemax', '100');
186
- // Fixed positioning matches the canvas, which always fills the window (FILLMODE_FILL_WINDOW)
201
+ // Anchored to the pc-app element, which the library's base styles make a positioned box
187
202
  this._track.style.cssText = [
188
- 'position: fixed',
203
+ 'position: absolute',
189
204
  'top: 0',
190
205
  'left: 0',
191
206
  'width: 100%',
@@ -697,16 +712,44 @@ const getEntity = (ref) => {
697
712
 
698
713
  /** The pointer event types the application synthesizes on `<pc-entity>` elements via picking. */
699
714
  const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'];
715
+ /**
716
+ * Gives `pc-app` the sizing contract of a replaced element (`<video>`, `<img>`): a block-level
717
+ * box that the page's CSS sizes, defaulting to the canvas's own 300x150 intrinsic size, with the
718
+ * canvas and loading bar anchored to it. `:where()` keeps every declaration at zero specificity,
719
+ * so any page rule - however plain - overrides these defaults.
720
+ */
721
+ const ensureBaseStyles = () => {
722
+ const id = 'pc-app-styles';
723
+ if (document.getElementById(id)) {
724
+ return;
725
+ }
726
+ const style = document.createElement('style');
727
+ style.id = id;
728
+ style.textContent = ':where(pc-app) { display: block; position: relative; width: 300px; height: 150px; }';
729
+ document.head.appendChild(style);
730
+ };
700
731
  /**
701
732
  * The AppElement interface provides properties and methods for manipulating
702
733
  * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-app/ | `<pc-app>`} elements.
703
734
  * The AppElement interface also inherits the properties and methods of the
704
735
  * {@link HTMLElement} interface.
705
736
  *
737
+ * The element is sized like a replaced element such as `<video>`: a block-level box that the
738
+ * page's CSS controls, 300x150 by default. The application's canvas always fills the element,
739
+ * and the drawing buffer resolution follows the element's size (capped by `max-pixel-ratio`),
740
+ * tracked live via a ResizeObserver — so the element can be embedded at any size, resized by
741
+ * its container, or made fullscreen with ordinary CSS such as `width: 100vw; height: 100dvh`.
742
+ *
706
743
  * @fires {ProgressEvent} progress - Fired while the application preloads its assets. `loaded` and
707
744
  * `total` are asset counts, not bytes, and an asset that fails to load still counts as loaded.
708
745
  * Fired at least once per boot, and the final event always has `loaded` equal to `total`. Does
709
746
  * not bubble.
747
+ *
748
+ * @fires {ErrorEvent} error - Fired when the application cannot boot because no graphics device
749
+ * could be created (for example, a browser with WebGL disabled). `message` names the requested
750
+ * backends and `error` holds the underlying failure. The element never becomes ready
751
+ * and `app` stays `null` — listen for this event to show a fallback UI. Removing the element and
752
+ * re-inserting it retries the boot with its current attributes. Does not bubble.
710
753
  */
711
754
  class AppElement extends AsyncElement {
712
755
  /**
@@ -766,6 +809,12 @@ class AppElement extends AsyncElement {
766
809
  };
767
810
  _app = null;
768
811
  _loadProgress = 0;
812
+ /**
813
+ * Tracks the element's box so the drawing buffer and picker follow it. Created per boot once
814
+ * the application exists, and disconnected on teardown. `null` where ResizeObserver is
815
+ * unavailable (jsdom), where the boot-time resolution set is the only sizing that happens.
816
+ */
817
+ _resizeObserver = null;
769
818
  /**
770
819
  * The PlayCanvas application instance. `null` until the element is ready, and again once it
771
820
  * has been removed from the document — await {@link whenReady} or the element's `ready()`
@@ -792,8 +841,6 @@ class AppElement extends AsyncElement {
792
841
  */
793
842
  constructor() {
794
843
  super();
795
- // Bind methods to maintain 'this' context
796
- this._onWindowResize = this._onWindowResize.bind(this);
797
844
  // Track pointer listeners being added to and removed from descendant entities.
798
845
  // Registered once here rather than on every boot - the handlers no-op while there is no
799
846
  // canvas, and a re-booted element must not stack a second set.
@@ -804,6 +851,9 @@ class AppElement extends AsyncElement {
804
851
  }
805
852
  async connectedCallback() {
806
853
  const generation = ++this._bootGeneration;
854
+ // Installed before the loading bar is created: the bar anchors to this element, which
855
+ // these styles make a positioned block box
856
+ ensureBaseStyles();
807
857
  // Created before the first await, so the bar is visible while modules and the graphics
808
858
  // device are created, and exists before any disconnect could need to clean it up
809
859
  if (this._loadingBar && !this._bar) {
@@ -818,8 +868,11 @@ class AppElement extends AsyncElement {
818
868
  if (generation !== this._bootGeneration) {
819
869
  return;
820
870
  }
821
- // Create and append the canvas to the element
871
+ // Create and append the canvas, filling the element's content box - the page sizes the
872
+ // element, and everything else follows. touch-action: none keeps touch drags driving the
873
+ // engine's input handlers instead of scrolling the page.
822
874
  this._canvas = document.createElement('canvas');
875
+ this._canvas.style.cssText = 'display: block; width: 100%; height: 100%; touch-action: none;';
823
876
  this.appendChild(this._canvas);
824
877
  // Configure device types based on backend selection
825
878
  const backendToDeviceTypes = {
@@ -829,14 +882,44 @@ class AppElement extends AsyncElement {
829
882
  };
830
883
  const deviceTypes = backendToDeviceTypes[this._backend] || [];
831
884
  this._optionsLocked = true;
832
- const device = await createGraphicsDevice(this._canvas, {
833
- // @ts-ignore - alpha needs to be documented
834
- alpha: this._alpha,
835
- antialias: this._antialias,
836
- depth: this._depthBuffer,
837
- deviceTypes: deviceTypes,
838
- stencil: this._stencilBuffer
839
- });
885
+ // createGraphicsDevice appends its final null-device fallback to the array in place, so
886
+ // the requested list is captured now for the failure message.
887
+ const requested = deviceTypes.join(', ');
888
+ let device;
889
+ try {
890
+ device = await createGraphicsDevice(this._canvas, {
891
+ // @ts-ignore - alpha needs to be documented
892
+ alpha: this._alpha,
893
+ antialias: this._antialias,
894
+ depth: this._depthBuffer,
895
+ deviceTypes: deviceTypes,
896
+ stencil: this._stencilBuffer
897
+ });
898
+ }
899
+ catch (error) {
900
+ // The element may have been removed while device creation was failing. The teardown
901
+ // has already cleaned up, and the failure belongs to a boot that no longer owns the
902
+ // element.
903
+ if (generation !== this._bootGeneration) {
904
+ return;
905
+ }
906
+ // Return the element to its pre-boot state - no dead canvas, no loading bar stuck at
907
+ // zero - before announcing the failure. Readiness deliberately stays pending: nothing
908
+ // it would announce (the app, the entity hierarchy) exists, so a device-less element
909
+ // joins the documented never-ready cases and the failure surfaces through the error
910
+ // event instead.
911
+ if (this._canvas && this.contains(this._canvas)) {
912
+ this.removeChild(this._canvas);
913
+ }
914
+ this._canvas = null;
915
+ this._bar?.destroy();
916
+ this._bar = null;
917
+ const reason = error instanceof Error ? error.message : String(error);
918
+ const message = `pc-app failed to create a graphics device (${requested}) - ${reason}`;
919
+ console.error(message, error);
920
+ this.dispatchEvent(new ErrorEvent('error', { message, error }));
921
+ return;
922
+ }
840
923
  // The element may have been removed while the device was created. disconnectedCallback
841
924
  // has already cleaned up the canvas; the device was created inside the await, so it is
842
925
  // this boot's to release.
@@ -915,9 +998,19 @@ class AppElement extends AsyncElement {
915
998
  const app = new AppBase(this._canvas);
916
999
  this._app = app;
917
1000
  app.init(createOptions);
918
- app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
1001
+ // FILLMODE_NONE leaves the canvas's CSS sizing alone (the engine's other fill modes
1002
+ // stamp window-derived pixel sizes onto it); RESOLUTION_AUTO sizes the drawing buffer
1003
+ // from the canvas's client size
1004
+ app.setCanvasFillMode(FILLMODE_NONE);
919
1005
  app.setCanvasResolution(RESOLUTION_AUTO);
920
1006
  this._pickerCreate();
1007
+ // Track the element's box rather than the window: containers resize without any window
1008
+ // event (splitter drags, flex reflow, animations). Guarded because jsdom has no
1009
+ // ResizeObserver - there, the resolution set above is the only sizing that happens.
1010
+ if (typeof ResizeObserver !== 'undefined') {
1011
+ this._resizeObserver = new ResizeObserver(() => this._syncCanvasSize());
1012
+ this._resizeObserver.observe(this);
1013
+ }
921
1014
  // Get all pc-asset elements that are direct children of the pc-app element
922
1015
  const assetElements = this.querySelectorAll(':scope > pc-asset');
923
1016
  for (const assetElement of Array.from(assetElements)) {
@@ -993,8 +1086,6 @@ class AppElement extends AsyncElement {
993
1086
  // Dismiss the bar only once a frame has actually rendered; ready fires before the
994
1087
  // first rAF tick
995
1088
  app.once('frameend', () => this._bar?.complete());
996
- // Handle window resize to keep the canvas responsive
997
- window.addEventListener('resize', this._onWindowResize);
998
1089
  this._onReady();
999
1090
  });
1000
1091
  }
@@ -1019,18 +1110,27 @@ class AppElement extends AsyncElement {
1019
1110
  // no longer holds.
1020
1111
  this._hierarchyReady = false;
1021
1112
  this._resetReady();
1022
- // Remove event listeners
1023
- window.removeEventListener('resize', this._onWindowResize);
1113
+ // Stop tracking the element's size
1114
+ this._resizeObserver?.disconnect();
1115
+ this._resizeObserver = null;
1024
1116
  // Remove the canvas
1025
1117
  if (this._canvas && this.contains(this._canvas)) {
1026
1118
  this.removeChild(this._canvas);
1027
1119
  this._canvas = null;
1028
1120
  }
1029
1121
  }
1030
- _onWindowResize() {
1031
- if (this.app) {
1032
- this.app.resizeCanvas();
1122
+ /**
1123
+ * Syncs the drawing buffer and the picker to the canvas's current CSS size. The picker must
1124
+ * track the buffer, or picks would land at stale coordinates after a resize. Skipped while
1125
+ * an XR session presents - the session owns the buffer size.
1126
+ */
1127
+ _syncCanvasSize() {
1128
+ if (!this.app || this.app.xr?.active) {
1129
+ return;
1033
1130
  }
1131
+ this.app.updateCanvasSize();
1132
+ const { width, height } = this.app.graphicsDevice;
1133
+ this._picker?.resize(width, height);
1034
1134
  }
1035
1135
  _pickerCreate() {
1036
1136
  const { width, height } = this.app.graphicsDevice;
@@ -1368,7 +1468,7 @@ class AppElement extends AsyncElement {
1368
1468
  this._maxPixelRatio = value;
1369
1469
  if (this.app) {
1370
1470
  this.app.graphicsDevice.maxPixelRatio = value;
1371
- this.app.resizeCanvas();
1471
+ this._syncCanvasSize();
1372
1472
  }
1373
1473
  }
1374
1474
  /**