@playcanvas/web-components 0.17.0 → 0.19.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 (117) hide show
  1. package/README.md +5 -3
  2. package/dist/app.d.cts +28 -3
  3. package/dist/app.d.ts +28 -3
  4. package/dist/asset.d.cts +13 -3
  5. package/dist/asset.d.ts +13 -3
  6. package/dist/components/anim-clip.d.cts +3 -1
  7. package/dist/components/anim-clip.d.ts +3 -1
  8. package/dist/components/anim-component.d.cts +5 -0
  9. package/dist/components/anim-component.d.ts +5 -0
  10. package/dist/components/audio-listener-component.d.cts +4 -0
  11. package/dist/components/audio-listener-component.d.ts +4 -0
  12. package/dist/components/button-component.d.cts +5 -0
  13. package/dist/components/button-component.d.ts +5 -0
  14. package/dist/components/camera-component.d.cts +35 -1
  15. package/dist/components/camera-component.d.ts +35 -1
  16. package/dist/components/collision-component.d.cts +4 -0
  17. package/dist/components/collision-component.d.ts +4 -0
  18. package/dist/components/element-component.d.cts +4 -0
  19. package/dist/components/element-component.d.ts +4 -0
  20. package/dist/components/gsplat-component.d.cts +4 -0
  21. package/dist/components/gsplat-component.d.ts +4 -0
  22. package/dist/components/joint-component.d.cts +12 -2
  23. package/dist/components/joint-component.d.ts +12 -2
  24. package/dist/components/layout-child-component.d.cts +4 -0
  25. package/dist/components/layout-child-component.d.ts +4 -0
  26. package/dist/components/layout-group-component.d.cts +4 -0
  27. package/dist/components/layout-group-component.d.ts +4 -0
  28. package/dist/components/light-component.d.cts +50 -1
  29. package/dist/components/light-component.d.ts +50 -1
  30. package/dist/components/particle-system-component.d.cts +4 -0
  31. package/dist/components/particle-system-component.d.ts +4 -0
  32. package/dist/components/render-component.d.cts +5 -0
  33. package/dist/components/render-component.d.ts +5 -0
  34. package/dist/components/rigid-body-component.d.cts +5 -0
  35. package/dist/components/rigid-body-component.d.ts +5 -0
  36. package/dist/components/screen-component.d.cts +4 -0
  37. package/dist/components/screen-component.d.ts +4 -0
  38. package/dist/components/script-component.d.cts +3 -0
  39. package/dist/components/script-component.d.ts +3 -0
  40. package/dist/components/script-instance.d.cts +4 -0
  41. package/dist/components/script-instance.d.ts +4 -0
  42. package/dist/components/scroll-view-component.d.cts +4 -0
  43. package/dist/components/scroll-view-component.d.ts +4 -0
  44. package/dist/components/scrollbar-component.d.cts +4 -0
  45. package/dist/components/scrollbar-component.d.ts +4 -0
  46. package/dist/components/sound-component.d.cts +4 -0
  47. package/dist/components/sound-component.d.ts +4 -0
  48. package/dist/components/sound-slot.d.cts +3 -0
  49. package/dist/components/sound-slot.d.ts +3 -0
  50. package/dist/custom-elements.json +338 -7
  51. package/dist/entity-base.d.cts +5 -4
  52. package/dist/entity-base.d.ts +5 -4
  53. package/dist/entity-owner.d.cts +2 -2
  54. package/dist/entity-owner.d.ts +2 -2
  55. package/dist/entity.d.cts +13 -2
  56. package/dist/entity.d.ts +13 -2
  57. package/dist/index.d.cts +4 -1
  58. package/dist/index.d.ts +4 -1
  59. package/dist/material.d.cts +18 -7
  60. package/dist/material.d.ts +18 -7
  61. package/dist/model.d.cts +11 -0
  62. package/dist/model.d.ts +11 -0
  63. package/dist/node.d.cts +12 -1
  64. package/dist/node.d.ts +12 -1
  65. package/dist/pwc.cjs +601 -206
  66. package/dist/pwc.cjs.map +1 -1
  67. package/dist/pwc.js +601 -206
  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 +602 -207
  74. package/dist/pwc.mjs.map +1 -1
  75. package/dist/scene.d.cts +19 -0
  76. package/dist/scene.d.ts +19 -0
  77. package/dist/sky.d.cts +4 -0
  78. package/dist/sky.d.ts +4 -0
  79. package/dist/vscode.html-custom-data.json +257 -62
  80. package/dist/wasm.d.cts +4 -0
  81. package/dist/wasm.d.ts +4 -0
  82. package/dist/web-types.json +169 -69
  83. package/package.json +3 -3
  84. package/src/app.ts +150 -71
  85. package/src/asset.ts +13 -4
  86. package/src/components/anim-clip.ts +3 -1
  87. package/src/components/anim-component.ts +5 -0
  88. package/src/components/audio-listener-component.ts +4 -0
  89. package/src/components/button-component.ts +5 -0
  90. package/src/components/camera-component.ts +63 -6
  91. package/src/components/collision-component.ts +4 -0
  92. package/src/components/element-component.ts +4 -0
  93. package/src/components/gsplat-component.ts +4 -0
  94. package/src/components/joint-component.ts +12 -2
  95. package/src/components/layout-child-component.ts +4 -0
  96. package/src/components/layout-group-component.ts +4 -0
  97. package/src/components/light-component.ts +108 -17
  98. package/src/components/particle-system-component.ts +4 -0
  99. package/src/components/render-component.ts +5 -0
  100. package/src/components/rigid-body-component.ts +5 -0
  101. package/src/components/screen-component.ts +4 -0
  102. package/src/components/script-component.ts +3 -0
  103. package/src/components/script-instance.ts +4 -0
  104. package/src/components/scroll-view-component.ts +4 -0
  105. package/src/components/scrollbar-component.ts +4 -0
  106. package/src/components/sound-component.ts +4 -0
  107. package/src/components/sound-slot.ts +3 -0
  108. package/src/entity-base.ts +28 -16
  109. package/src/entity-owner.ts +2 -2
  110. package/src/entity.ts +16 -4
  111. package/src/index.ts +12 -1
  112. package/src/material.ts +19 -8
  113. package/src/model.ts +14 -2
  114. package/src/node.ts +15 -3
  115. package/src/scene.ts +35 -1
  116. package/src/sky.ts +4 -0
  117. package/src/wasm.ts +4 -0
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_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, AnimTrack, ANIM_CONTROL_STATES, 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, AnimTrack, ANIM_CONTROL_STATES, BUTTON_TRANSITION_MODE_TINT, BUTTON_TRANSITION_MODE_SPRITE_CHANGE, TONEMAP_NONE, PROJECTION_PERSPECTIVE, GAMMA_SRGB, GAMMA_NONE, XRTYPE_AR, 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.
@@ -121,6 +121,10 @@ async function whenReady(target) {
121
121
  * A `<pc-wasm>` without a `name` warns and never becomes ready; a containing `<pc-app>` still
122
122
  * boots.
123
123
  *
124
+ * @elementSummary The `<pc-wasm>` element loads a WebAssembly module the engine needs before the
125
+ * application starts — `Ammo` for physics, `Basis` or `DracoDecoderModule` for compressed assets.
126
+ * Must be a direct child of `<pc-app>`.
127
+ *
124
128
  * @attribute {string} name - The name of the WebAssembly module to configure, e.g. `Basis` or
125
129
  * `Ammo`.
126
130
  * @attribute {string} glue - The URL of the module's glue script.
@@ -173,6 +177,136 @@ class WasmElement extends AsyncElement {
173
177
  }
174
178
  customElements.define('pc-wasm', WasmElement);
175
179
 
180
+ /**
181
+ * The event types the containing `<pc-app>` synthesizes on entity-fronting elements via picking:
182
+ * the `pointer*` events, plus `click` — which concludes a primary-button press and release, and
183
+ * is delivered as a `PointerEvent` exactly as modern browsers deliver native clicks.
184
+ * @internal
185
+ */
186
+ const SYNTHESIZED_EVENTS = [
187
+ 'pointerenter',
188
+ 'pointerleave',
189
+ 'pointerdown',
190
+ 'pointerup',
191
+ 'pointermove',
192
+ 'click'
193
+ ];
194
+ const SYNTHESIZED_EVENT_SET = new Set(SYNTHESIZED_EVENTS);
195
+ /**
196
+ * The attribute names of the inline event handlers (`onpointerdown`, `onclick`, ...), shared by
197
+ * every element that fronts an engine entity. Spread into `observedAttributes` by subclasses.
198
+ * @internal
199
+ */
200
+ const EVENT_ATTRIBUTES = SYNTHESIZED_EVENTS.map((type) => `on${type}`);
201
+ /**
202
+ * The base class for elements that front an engine {@link Entity}: `<pc-entity>` and
203
+ * `<pc-model>`, which create one, and `<pc-node>`, which binds to one inside a model's
204
+ * instantiated hierarchy. It carries what all of them need — the `entity` contract, registration
205
+ * with the owning application (which joins picked scene nodes back to elements by identity,
206
+ * never by name), and the pointer listener bookkeeping that lets the application lazily attach
207
+ * its canvas handlers.
208
+ */
209
+ class EntityBaseElement extends AsyncElement {
210
+ _entity = null;
211
+ /**
212
+ * The application element this entity is registered with, cached at registration time so the
213
+ * entity can be unregistered even once this element has left the DOM.
214
+ */
215
+ _appElement = null;
216
+ /**
217
+ * The event listeners registered on the element, by type.
218
+ */
219
+ _listeners = {};
220
+ /**
221
+ * The event types for which an inline handler attribute (`onpointerdown`, `onclick`, ...)
222
+ * is currently present.
223
+ */
224
+ _inlineHandlerTypes = new Set();
225
+ /**
226
+ * The PlayCanvas entity instance. `null` until the element is ready, and again once the
227
+ * entity is gone — await {@link whenReady} or the element's `ready()` promise before
228
+ * accessing it.
229
+ * @returns The entity instance, or `null`.
230
+ */
231
+ get entity() {
232
+ return this._entity;
233
+ }
234
+ /**
235
+ * Registers `entity` as this element's backing entity with the owning application, which
236
+ * joins engine nodes back to elements by identity (never by name).
237
+ *
238
+ * @param entity - The entity to register.
239
+ */
240
+ _registerEntity(entity) {
241
+ this._appElement = this.closestApp;
242
+ this._appElement?._registerEntityElement(entity, this);
243
+ }
244
+ /**
245
+ * Removes the registration for `entity`.
246
+ *
247
+ * @param entity - The entity to unregister.
248
+ */
249
+ _unregisterEntity(entity) {
250
+ this._appElement?._unregisterEntityElement(entity);
251
+ this._appElement = null;
252
+ }
253
+ /**
254
+ * Tracks whether an inline handler attribute is present. The browser itself compiles and
255
+ * runs these attributes — they are standard `GlobalEventHandlers`, so setting one replaces
256
+ * the previous handler and removing it removes the handler, exactly like `onclick` on any
257
+ * HTML element. But because they bypass {@link EventTarget.addEventListener}, the connect/disconnect
258
+ * bookkeeping that lets the application lazily attach its canvas pointer handlers must be
259
+ * kept in sync here.
260
+ *
261
+ * @param name - The attribute name (e.g. 'onpointerdown').
262
+ * @param value - The attribute value, or `null` when the attribute has been removed.
263
+ */
264
+ _updateInlineHandler(name, value) {
265
+ const type = name.substring(2);
266
+ const had = this._inlineHandlerTypes.has(type);
267
+ const has = value !== null;
268
+ if (has && !had) {
269
+ this._inlineHandlerTypes.add(type);
270
+ this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
271
+ }
272
+ else if (!has && had) {
273
+ this._inlineHandlerTypes.delete(type);
274
+ this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
275
+ }
276
+ }
277
+ addEventListener(type, listener, options) {
278
+ if (!this._listeners[type]) {
279
+ this._listeners[type] = [];
280
+ }
281
+ this._listeners[type].push(listener);
282
+ super.addEventListener(type, listener, options);
283
+ if (SYNTHESIZED_EVENT_SET.has(type)) {
284
+ this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
285
+ }
286
+ }
287
+ removeEventListener(type, listener, options) {
288
+ if (this._listeners[type]) {
289
+ this._listeners[type] = this._listeners[type].filter((l) => l !== listener);
290
+ }
291
+ super.removeEventListener(type, listener, options);
292
+ if (SYNTHESIZED_EVENT_SET.has(type)) {
293
+ this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
294
+ }
295
+ }
296
+ /**
297
+ * Whether the element has a listener for an event type, registered either with
298
+ * {@link EventTarget.addEventListener} or with the matching inline handler attribute. Read by the
299
+ * containing `<pc-app>` element to gate event synthesis.
300
+ *
301
+ * @param type - The event type.
302
+ * @returns Whether a listener is registered.
303
+ * @internal
304
+ */
305
+ _hasListeners(type) {
306
+ return Boolean(this._listeners[type]?.length) || this._inlineHandlerTypes.has(type);
307
+ }
308
+ }
309
+
176
310
  /** Covers the 0.2s opacity transition; jsdom never fires transitionend, so removal is timed. */
177
311
  const REMOVAL_DELAY_MS = 250;
178
312
  /**
@@ -726,14 +860,51 @@ const getEntity = (ref) => {
726
860
  return element?.entity ?? null;
727
861
  };
728
862
 
729
- /** The pointer event types the application synthesizes on `<pc-entity>` elements via picking. */
730
- const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'];
731
863
  /**
732
864
  * The event types whose listeners make an element a hover target. Hover resolution walks past
733
865
  * elements listening for none of them, so a silent element never swallows an ancestor's
734
866
  * enter/leave pair.
735
867
  */
736
868
  const hoverEventTypes = ['pointerenter', 'pointerleave', 'pointermove'];
869
+ /**
870
+ * The canvas listeners each synthesized event type is driven by. Enter and leave are derived
871
+ * from move picks. A click is concluded from the down/up pair, with pointercancel discarding a
872
+ * press the browser takes back (for example a touch that becomes a scroll).
873
+ */
874
+ const canvasEventsFor = {
875
+ pointermove: ['pointermove'],
876
+ pointerenter: ['pointermove'],
877
+ pointerleave: ['pointermove'],
878
+ pointerdown: ['pointerdown'],
879
+ pointerup: ['pointerup'],
880
+ click: ['pointerdown', 'pointerup', 'pointercancel']
881
+ };
882
+ /**
883
+ * How long after a click a further click on the same target still raises the click count that
884
+ * `detail` carries, approximating the platform's double-click time.
885
+ */
886
+ const CLICK_CHAIN_MS = 500;
887
+ /**
888
+ * Finds the nearest common inclusive ancestor of two picked nodes - the node a click belongs to
889
+ * when the press and the release picked different geometry, exactly as the DOM assigns a click
890
+ * whose down and up have different targets.
891
+ *
892
+ * @param a - The node the press picked, or `null`.
893
+ * @param b - The node the release picked, or `null`.
894
+ * @returns The nearest common inclusive ancestor, or `null` when there is none.
895
+ */
896
+ const commonAncestor = (a, b) => {
897
+ const ancestors = new Set();
898
+ for (let node = a; node !== null; node = node.parent) {
899
+ ancestors.add(node);
900
+ }
901
+ for (let node = b; node !== null; node = node.parent) {
902
+ if (ancestors.has(node)) {
903
+ return node;
904
+ }
905
+ }
906
+ return null;
907
+ };
737
908
  /**
738
909
  * Gives `pc-app` the sizing contract of a replaced element (`<video>`, `<img>`): a block-level
739
910
  * box that the page's CSS sizes, defaulting to the canvas's own 300x150 intrinsic size, with the
@@ -762,6 +933,10 @@ const ensureBaseStyles = () => {
762
933
  * tracked live via a ResizeObserver — so the element can be embedded at any size, resized by
763
934
  * its container, or made fullscreen with ordinary CSS such as `width: 100vw; height: 100dvh`.
764
935
  *
936
+ * @elementSummary The `<pc-app>` element creates a PlayCanvas application and the canvas it renders
937
+ * into, and is the root of every scene. It holds the `<pc-asset>`, `<pc-material>`, `<pc-wasm>` and
938
+ * `<pc-scene>` elements, and the page's CSS sizes it, as it would a `<video>`.
939
+ *
765
940
  * @fires {ProgressEvent} progress - Fired while the application preloads its assets. `loaded` and
766
941
  * `total` are asset counts, not bytes, and an asset that fails to load still counts as loaded.
767
942
  * Fired at least once per boot, and the final event always has `loaded` equal to `total`. Does
@@ -814,21 +989,30 @@ class AppElement extends AsyncElement {
814
989
  */
815
990
  _entityElements = new Map();
816
991
  _picker = null;
817
- _hasPointerListeners = {
818
- pointerenter: false,
819
- pointerleave: false,
820
- pointerdown: false,
821
- pointerup: false,
822
- pointermove: false
823
- };
824
992
  _hoveredEntity = null;
825
993
  // Identifies the newest in-flight hover pick, so out-of-order results can be discarded
826
994
  _pickToken = 0;
827
995
  _pointerHandlers = {
828
996
  pointermove: null,
829
997
  pointerdown: null,
830
- pointerup: null
998
+ pointerup: null,
999
+ pointercancel: null
831
1000
  };
1001
+ /**
1002
+ * The pick of each pointer's primary-button press, keyed by pointerId and kept while a click
1003
+ * may still conclude it. The promise is stored rather than its result, so a release can
1004
+ * await a press pick that has not resolved yet. Entries are removed by the matching
1005
+ * pointerup or pointercancel, and only ever stored while some element listens for click -
1006
+ * which is also what keeps those two canvas listeners attached.
1007
+ */
1008
+ _downPicks = new Map();
1009
+ /** Whether any element in the tree listens for click. Maintained by _syncCanvasListeners. */
1010
+ _clickListened = false;
1011
+ /**
1012
+ * The previous click's target, time and count, for chaining successive clicks into the
1013
+ * click count that `detail` carries. `null` until a click has fired.
1014
+ */
1015
+ _lastClick = null;
832
1016
  _app = null;
833
1017
  _loadProgress = 0;
834
1018
  /**
@@ -863,12 +1047,12 @@ class AppElement extends AsyncElement {
863
1047
  */
864
1048
  constructor() {
865
1049
  super();
866
- // Track pointer listeners being added to and removed from descendant entities.
867
- // Registered once here rather than on every boot - the handlers no-op while there is no
868
- // canvas, and a re-booted element must not stack a second set.
869
- pointerEventTypes.forEach((type) => {
870
- this.addEventListener(`${type}:connect`, () => this._onPointerListenerAdded(type));
871
- this.addEventListener(`${type}:disconnect`, () => this._onPointerListenerRemoved(type));
1050
+ // Track listeners for the synthesized events being added to and removed from descendant
1051
+ // entities. Registered once here rather than on every boot - the sync no-ops while there
1052
+ // is no canvas, and a re-booted element must not stack a second set.
1053
+ SYNTHESIZED_EVENTS.forEach((type) => {
1054
+ this.addEventListener(`${type}:connect`, () => this._syncCanvasListeners());
1055
+ this.addEventListener(`${type}:disconnect`, () => this._syncCanvasListeners());
872
1056
  });
873
1057
  }
874
1058
  async connectedCallback() {
@@ -1186,15 +1370,13 @@ class AppElement extends AsyncElement {
1186
1370
  this._pointerHandlers.pointermove = listener(this._onPointerMove);
1187
1371
  this._pointerHandlers.pointerdown = listener(this._onPointerDown);
1188
1372
  this._pointerHandlers.pointerup = listener(this._onPointerUp);
1189
- // Attach canvas handlers for listeners registered before this boot (e.g. handlers
1190
- // created from onpointer* attributes when their elements were first upgraded, or
1373
+ this._pointerHandlers.pointercancel = (event) => {
1374
+ this._downPicks.delete(event.pointerId);
1375
+ };
1376
+ // Attach canvas listeners for element listeners registered before this boot (e.g.
1377
+ // handlers created from inline attributes when their elements were first upgraded, or
1191
1378
  // listeners carried over from before a re-boot)
1192
- pointerEventTypes.forEach((type) => {
1193
- const anyListeners = Array.from(this.querySelectorAll('pc-entity, pc-model, pc-node')).some((entity) => entity._hasListeners(type));
1194
- if (anyListeners) {
1195
- this._onPointerListenerAdded(type);
1196
- }
1197
- });
1379
+ this._syncCanvasListeners();
1198
1380
  }
1199
1381
  _pickerDestroy() {
1200
1382
  if (this._canvas) {
@@ -1209,15 +1391,12 @@ class AppElement extends AsyncElement {
1209
1391
  this._pointerHandlers = {
1210
1392
  pointermove: null,
1211
1393
  pointerdown: null,
1212
- pointerup: null
1213
- };
1214
- this._hasPointerListeners = {
1215
- pointerenter: false,
1216
- pointerleave: false,
1217
- pointerdown: false,
1218
- pointerup: false,
1219
- pointermove: false
1394
+ pointerup: null,
1395
+ pointercancel: null
1220
1396
  };
1397
+ this._downPicks.clear();
1398
+ this._clickListened = false;
1399
+ this._lastClick = null;
1221
1400
  }
1222
1401
  /**
1223
1402
  * Registers the element that fronts an entity. Called by EntityElement when it creates its
@@ -1423,7 +1602,15 @@ class AppElement extends AsyncElement {
1423
1602
  async _onPointerDown(event) {
1424
1603
  if (!this._picker || !this.app)
1425
1604
  return;
1426
- const node = await this._pickNode(event);
1605
+ const pick = this._pickNode(event);
1606
+ // A click concludes on the matching pointerup, which needs to know what the press
1607
+ // picked. Primary button only - the only button a click can conclude from - and only
1608
+ // while click is listened for, since it is the click mapping that keeps the pointerup
1609
+ // and pointercancel listeners attached to clean the entry up again.
1610
+ if (this._clickListened && event.button === 0) {
1611
+ this._downPicks.set(event.pointerId, pick);
1612
+ }
1613
+ const node = await pick;
1427
1614
  if (!this._picker)
1428
1615
  return; // the element disconnected while the pick was in flight
1429
1616
  const entityElement = this._elementWithListener(node, 'pointerdown');
@@ -1434,6 +1621,10 @@ class AppElement extends AsyncElement {
1434
1621
  async _onPointerUp(event) {
1435
1622
  if (!this._picker || !this.app)
1436
1623
  return;
1624
+ // The press pick this release may conclude as a click. Claimed synchronously, so the
1625
+ // entry is gone before any other event for this pointer can be handled.
1626
+ const downPick = this._downPicks.get(event.pointerId);
1627
+ this._downPicks.delete(event.pointerId);
1437
1628
  const node = await this._pickNode(event);
1438
1629
  if (!this._picker)
1439
1630
  return; // the element disconnected while the pick was in flight
@@ -1441,30 +1632,60 @@ class AppElement extends AsyncElement {
1441
1632
  if (entityElement) {
1442
1633
  entityElement.dispatchEvent(new PointerEvent('pointerup', event));
1443
1634
  }
1444
- }
1445
- _onPointerListenerAdded(type) {
1446
- if (!this._hasPointerListeners[type] && this._canvas) {
1447
- this._hasPointerListeners[type] = true;
1448
- // For enter/leave events, we need the move handler
1449
- const handler = type === 'pointerenter' || type === 'pointerleave'
1450
- ? this._pointerHandlers.pointermove
1451
- : this._pointerHandlers[type];
1452
- if (handler) {
1453
- this._canvas.addEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler);
1635
+ // A click fires where the DOM fires it: at the nearest common inclusive ancestor of
1636
+ // what the press and the release picked, for the primary button only. The press pick
1637
+ // may still be in flight - a quick tap resolves in pick order, not event order.
1638
+ if (!downPick || event.button !== 0)
1639
+ return;
1640
+ const downNode = await downPick;
1641
+ if (!this._picker)
1642
+ return;
1643
+ const clickElement = this._elementWithListener(commonAncestor(downNode, node), 'click');
1644
+ if (clickElement) {
1645
+ const click = new PointerEvent('click', event);
1646
+ // The init above copied pointerup's `detail`, which the Pointer Events spec fixes
1647
+ // at 0 - but click is exempt: its detail is the click count, chained here as the
1648
+ // platform chains it (same target, within the double-click window). Overridden
1649
+ // with defineProperty because an event instance used as an init dict cannot have
1650
+ // single fields replaced.
1651
+ const time = performance.now();
1652
+ const last = this._lastClick;
1653
+ const count = last && last.element === clickElement && time - last.time <= CLICK_CHAIN_MS ? last.count + 1 : 1;
1654
+ this._lastClick = { element: clickElement, time, count };
1655
+ Object.defineProperty(click, 'detail', { value: count });
1656
+ clickElement.dispatchEvent(click);
1657
+ }
1658
+ }
1659
+ /**
1660
+ * Attaches exactly the canvas listeners the tree's current element listeners need, and
1661
+ * detaches the rest. Recomputed whenever a listener connects or disconnects anywhere under
1662
+ * this element: several synthesized types can need the same canvas listener (enter, leave
1663
+ * and move all ride the move pick; click rides the down/up pair), so one type's removal
1664
+ * must not detach a listener another type still uses. Re-attaching an attached listener is
1665
+ * a no-op by EventTarget semantics, so no attach state is kept.
1666
+ */
1667
+ _syncCanvasListeners() {
1668
+ const canvas = this._canvas;
1669
+ if (!canvas)
1670
+ return; // not booted yet: _pickerCreate syncs once the handlers exist
1671
+ const elements = Array.from(this.querySelectorAll('pc-entity, pc-model, pc-node'));
1672
+ const needed = new Set();
1673
+ for (const type of SYNTHESIZED_EVENTS) {
1674
+ if (elements.some((element) => element._hasListeners(type))) {
1675
+ canvasEventsFor[type].forEach((canvasType) => needed.add(canvasType));
1454
1676
  }
1455
1677
  }
1456
- }
1457
- _onPointerListenerRemoved(type) {
1458
- const hasListeners = Array.from(this.querySelectorAll('pc-entity, pc-model, pc-node')).some((entity) => entity._hasListeners(type));
1459
- if (!hasListeners && this._canvas) {
1460
- this._hasPointerListeners[type] = false;
1461
- const handler = type === 'pointerenter' || type === 'pointerleave'
1462
- ? this._pointerHandlers.pointermove
1463
- : this._pointerHandlers[type];
1464
- if (handler) {
1465
- this._canvas.removeEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler);
1678
+ this._clickListened = elements.some((element) => element._hasListeners('click'));
1679
+ Object.entries(this._pointerHandlers).forEach(([canvasType, handler]) => {
1680
+ if (!handler)
1681
+ return;
1682
+ if (needed.has(canvasType)) {
1683
+ canvas.addEventListener(canvasType, handler);
1466
1684
  }
1467
- }
1685
+ else {
1686
+ canvas.removeEventListener(canvasType, handler);
1687
+ }
1688
+ });
1468
1689
  }
1469
1690
  /**
1470
1691
  * Warns that a graphics option was written too late to have any effect. These options are read
@@ -1633,126 +1854,6 @@ class AppElement extends AsyncElement {
1633
1854
  }
1634
1855
  customElements.define('pc-app', AppElement);
1635
1856
 
1636
- /**
1637
- * The attribute names of the inline `onpointer*` event handlers, shared by every element that
1638
- * fronts an engine entity. Spread into `observedAttributes` by subclasses.
1639
- * @internal
1640
- */
1641
- const POINTER_ATTRIBUTES = [
1642
- 'onpointerenter',
1643
- 'onpointerleave',
1644
- 'onpointerdown',
1645
- 'onpointerup',
1646
- 'onpointermove'
1647
- ];
1648
- /**
1649
- * The base class for elements that front an engine {@link Entity}: `<pc-entity>` and
1650
- * `<pc-model>`, which create one, and `<pc-node>`, which binds to one inside a model's
1651
- * instantiated hierarchy. It carries what all of them need — the `entity` contract, registration
1652
- * with the owning application (which joins picked scene nodes back to elements by identity,
1653
- * never by name), and the pointer listener bookkeeping that lets the application lazily attach
1654
- * its canvas handlers.
1655
- */
1656
- class EntityBaseElement extends AsyncElement {
1657
- _entity = null;
1658
- /**
1659
- * The application element this entity is registered with, cached at registration time so the
1660
- * entity can be unregistered even once this element has left the DOM.
1661
- */
1662
- _appElement = null;
1663
- /**
1664
- * The pointer event listeners for the entity.
1665
- */
1666
- _listeners = {};
1667
- /**
1668
- * The event types for which an inline `onpointer*` attribute is currently present.
1669
- */
1670
- _inlineHandlerTypes = new Set();
1671
- /**
1672
- * The PlayCanvas entity instance. `null` until the element is ready, and again once the
1673
- * entity is gone — await {@link whenReady} or the element's `ready()` promise before
1674
- * accessing it.
1675
- * @returns The entity instance, or `null`.
1676
- */
1677
- get entity() {
1678
- return this._entity;
1679
- }
1680
- /**
1681
- * Registers `entity` as this element's backing entity with the owning application, which
1682
- * joins engine nodes back to elements by identity (never by name).
1683
- *
1684
- * @param entity - The entity to register.
1685
- */
1686
- _registerEntity(entity) {
1687
- this._appElement = this.closestApp;
1688
- this._appElement?._registerEntityElement(entity, this);
1689
- }
1690
- /**
1691
- * Removes the registration for `entity`.
1692
- *
1693
- * @param entity - The entity to unregister.
1694
- */
1695
- _unregisterEntity(entity) {
1696
- this._appElement?._unregisterEntityElement(entity);
1697
- this._appElement = null;
1698
- }
1699
- /**
1700
- * Tracks whether an inline `onpointer*` attribute is present. The browser itself compiles and
1701
- * runs these attributes — they are standard `GlobalEventHandlers`, so setting one replaces
1702
- * the previous handler and removing it removes the handler, exactly like `onclick` on any
1703
- * HTML element. But because they bypass {@link addEventListener}, the connect/disconnect
1704
- * bookkeeping that lets the application lazily attach its canvas pointer handlers must be
1705
- * kept in sync here.
1706
- *
1707
- * @param name - The attribute name (e.g. 'onpointerdown').
1708
- * @param value - The attribute value, or `null` when the attribute has been removed.
1709
- */
1710
- _updateInlineHandler(name, value) {
1711
- const type = name.substring(2);
1712
- const had = this._inlineHandlerTypes.has(type);
1713
- const has = value !== null;
1714
- if (has && !had) {
1715
- this._inlineHandlerTypes.add(type);
1716
- this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
1717
- }
1718
- else if (!has && had) {
1719
- this._inlineHandlerTypes.delete(type);
1720
- this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
1721
- }
1722
- }
1723
- addEventListener(type, listener, options) {
1724
- if (!this._listeners[type]) {
1725
- this._listeners[type] = [];
1726
- }
1727
- this._listeners[type].push(listener);
1728
- super.addEventListener(type, listener, options);
1729
- if (type.startsWith('pointer')) {
1730
- this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
1731
- }
1732
- }
1733
- removeEventListener(type, listener, options) {
1734
- if (this._listeners[type]) {
1735
- this._listeners[type] = this._listeners[type].filter((l) => l !== listener);
1736
- }
1737
- super.removeEventListener(type, listener, options);
1738
- if (type.startsWith('pointer')) {
1739
- this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
1740
- }
1741
- }
1742
- /**
1743
- * Whether the element has a listener for an event type, registered either with
1744
- * {@link addEventListener} or with the matching inline `onpointer*` attribute. Read by the
1745
- * containing `<pc-app>` element to gate pointer event synthesis.
1746
- *
1747
- * @param type - The event type.
1748
- * @returns Whether a listener is registered.
1749
- * @internal
1750
- */
1751
- _hasListeners(type) {
1752
- return Boolean(this._listeners[type]?.length) || this._inlineHandlerTypes.has(type);
1753
- }
1754
- }
1755
-
1756
1857
  /**
1757
1858
  * Creates and parents the entities of every descendant entity-owning element of `root`, in two
1758
1859
  * passes so that no parent's existence depends on document order. Called wherever a subtree could
@@ -1810,7 +1911,7 @@ class EntityOwnerElement extends EntityBaseElement {
1810
1911
  */
1811
1912
  _tags = [];
1812
1913
  /**
1813
- * Whether the hierarchy has been built for this entity — set once {@link _buildHierarchy} has
1914
+ * Whether the hierarchy has been built for this entity — set once `_buildHierarchy` has
1814
1915
  * parented it. Read by subclasses that gate work on the entity being in the scene graph.
1815
1916
  */
1816
1917
  _built = false;
@@ -1899,7 +2000,7 @@ class EntityOwnerElement extends EntityBaseElement {
1899
2000
  this._onBuilt();
1900
2001
  }
1901
2002
  /**
1902
- * Called by {@link _buildHierarchy} once the backing entity has been parented — exactly once
2003
+ * Called by `_buildHierarchy` once the backing entity has been parented — exactly once
1903
2004
  * per build cycle. The default announces readiness, which is what a parented `<pc-entity>`
1904
2005
  * means; `<pc-model>` overrides it to start loading content instead, because its readiness
1905
2006
  * tracks the content settling rather than the host entering the scene graph.
@@ -2020,8 +2121,13 @@ class EntityOwnerElement extends EntityBaseElement {
2020
2121
  *
2021
2122
  * The pointer events below are dispatched by the containing `<pc-app>` element when the pointer
2022
2123
  * intersects this entity's geometry. They are only generated while the entity has a listener for
2023
- * them, registered either with {@link addEventListener} or with the matching inline `onpointer*`
2024
- * attribute.
2124
+ * them, registered either with {@link EventTarget.addEventListener} or with the matching inline
2125
+ * attribute (`onpointerdown`, `onclick`, ...).
2126
+ *
2127
+ * @elementSummary The `<pc-entity>` element creates an entity: a named, transformable node of the
2128
+ * scene hierarchy, and the host for component elements such as `<pc-camera>`, `<pc-light>` and
2129
+ * `<pc-render>`. Place it in the `<pc-scene>`, or nest it under another `<pc-entity>`, a
2130
+ * `<pc-model>` or a `<pc-node>` to parent it there.
2025
2131
  *
2026
2132
  * @attribute {boolean} enabled - The enabled state of the entity.
2027
2133
  * @attribute {string} name - The name of the entity.
@@ -2036,11 +2142,17 @@ class EntityOwnerElement extends EntityBaseElement {
2036
2142
  * entity.
2037
2143
  * @attribute {string} onpointerup - Script to run when a pointer button is released over the
2038
2144
  * entity.
2145
+ * @attribute {string} onclick - Script to run when the entity is clicked: a primary pointer
2146
+ * button pressed and then released over it.
2039
2147
  * @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the entity.
2040
2148
  * @fires {PointerEvent} pointerleave - Fired when the pointer moves off the entity.
2041
2149
  * @fires {PointerEvent} pointermove - Fired when the pointer moves over the entity.
2042
2150
  * @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the entity.
2043
2151
  * @fires {PointerEvent} pointerup - Fired when a pointer button is released over the entity.
2152
+ * @fires {PointerEvent} click - Fired when a primary pointer button is pressed and then released
2153
+ * over the entity. A press and release that picked different entities fires on their nearest
2154
+ * common ancestor instead, as in the DOM. `detail` carries the click count, so a double click
2155
+ * arrives as a click whose `detail` is 2.
2044
2156
  */
2045
2157
  class EntityElement extends EntityOwnerElement {
2046
2158
  connectedCallback() {
@@ -2075,7 +2187,7 @@ class EntityElement extends EntityOwnerElement {
2075
2187
  this._entity?.destroy();
2076
2188
  }
2077
2189
  static get observedAttributes() {
2078
- return ['enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...POINTER_ATTRIBUTES];
2190
+ return ['enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...EVENT_ATTRIBUTES];
2079
2191
  }
2080
2192
  attributeChangedCallback(name, _oldValue, newValue) {
2081
2193
  switch (name) {
@@ -2102,6 +2214,7 @@ class EntityElement extends EntityOwnerElement {
2102
2214
  case 'onpointerdown':
2103
2215
  case 'onpointerup':
2104
2216
  case 'onpointermove':
2217
+ case 'onclick':
2105
2218
  this._updateInlineHandler(name, newValue);
2106
2219
  break;
2107
2220
  }
@@ -2403,6 +2516,10 @@ const processBufferView = (gltfBuffer, buffers, continuation) => {
2403
2516
  * Apart from `lazy` and the texture options, these attributes are read once when the asset is
2404
2517
  * created, so changing them later has no effect.
2405
2518
  *
2519
+ * @elementSummary The `<pc-asset>` element declares an asset for the application to load — a model,
2520
+ * texture, font, sound, script or JSON file — under an `id` that other elements reference. Must be
2521
+ * a direct child of `<pc-app>`.
2522
+ *
2406
2523
  * @attribute {string} id - The identifier used to reference the asset from other elements.
2407
2524
  * @attribute {string} src - The URL of the asset to load.
2408
2525
  * @attribute {string} type - The asset type. Inferred from the `src` file extension when omitted.
@@ -3029,6 +3146,11 @@ const formatHierarchy = (root, counts) => {
3029
3146
  * intersects the model's geometry, exactly as for `<pc-entity>` — a hit on a content node that no
3030
3147
  * `pc-node` fronts resolves to this element.
3031
3148
  *
3149
+ * @elementSummary The `<pc-model>` element instantiates a 3D model from a container asset
3150
+ * (typically a GLB) beneath an entity of its own, so the element's transform and tags place the
3151
+ * instance in the scene. Its `<pc-node>` children override what the asset authored. Place it in the
3152
+ * `<pc-scene>`, or nest it under a `<pc-entity>`, another `<pc-model>` or a `<pc-node>`.
3153
+ *
3032
3154
  * @attribute {boolean} enabled - The enabled state of the model.
3033
3155
  * @attribute {string} name - The name of the model.
3034
3156
  * @attribute {string} position - The position of the model.
@@ -3042,11 +3164,17 @@ const formatHierarchy = (root, counts) => {
3042
3164
  * model.
3043
3165
  * @attribute {string} onpointerup - Script to run when a pointer button is released over the
3044
3166
  * model.
3167
+ * @attribute {string} onclick - Script to run when the model is clicked: a primary pointer
3168
+ * button pressed and then released over it.
3045
3169
  * @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the model.
3046
3170
  * @fires {PointerEvent} pointerleave - Fired when the pointer moves off the model.
3047
3171
  * @fires {PointerEvent} pointermove - Fired when the pointer moves over the model.
3048
3172
  * @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the model.
3049
3173
  * @fires {PointerEvent} pointerup - Fired when a pointer button is released over the model.
3174
+ * @fires {PointerEvent} click - Fired when a primary pointer button is pressed and then released
3175
+ * over the model. A press and release that picked different entities fires on their nearest
3176
+ * common ancestor instead, as in the DOM. `detail` carries the click count, so a double click
3177
+ * arrives as a click whose `detail` is 2.
3050
3178
  * @fires {Event} load - Fired each time a container asset finishes instantiating, including
3051
3179
  * re-instantiation after `asset` changes. Does not bubble — listen on this element, or use a
3052
3180
  * capture-phase listener on an ancestor.
@@ -3295,7 +3423,7 @@ class ModelElement extends EntityOwnerElement {
3295
3423
  return this._asset;
3296
3424
  }
3297
3425
  static get observedAttributes() {
3298
- return ['asset', 'enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...POINTER_ATTRIBUTES];
3426
+ return ['asset', 'enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...EVENT_ATTRIBUTES];
3299
3427
  }
3300
3428
  attributeChangedCallback(name, _oldValue, newValue) {
3301
3429
  switch (name) {
@@ -3325,6 +3453,7 @@ class ModelElement extends EntityOwnerElement {
3325
3453
  case 'onpointerdown':
3326
3454
  case 'onpointerup':
3327
3455
  case 'onpointermove':
3456
+ case 'onclick':
3328
3457
  this._updateInlineHandler(name, newValue);
3329
3458
  break;
3330
3459
  }
@@ -3562,6 +3691,11 @@ class ComponentElement extends AsyncElement {
3562
3691
  *
3563
3692
  * Engine component: {@link AnimComponent} (`anim`).
3564
3693
  *
3694
+ * @elementSummary The `<pc-anim>` element plays animation clips over its entity's hierarchy, taken
3695
+ * from `<pc-anim-clip>` children or from the enclosing `<pc-model>`'s own animations. The first
3696
+ * clip plays automatically, and the `clip` attribute switches between them. Must be a child of a
3697
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
3698
+ *
3565
3699
  * @category Components
3566
3700
  */
3567
3701
  class AnimComponentElement extends ComponentElement {
@@ -4180,7 +4314,9 @@ customElements.define('pc-anim', AnimComponentElement);
4180
4314
  * named; in a multi-track source the track named `name` is chosen, falling back to the first
4181
4315
  * with a warning. The element becomes ready once its resolved track is assigned.
4182
4316
  *
4183
- * @category Components
4317
+ * @elementSummary The `<pc-anim-clip>` element declares one named animation clip on its parent
4318
+ * `<pc-anim>`, taken from the `asset` it names or, without one, from the enclosing `<pc-model>`'s
4319
+ * own animations. Must be a direct child of `<pc-anim>`.
4184
4320
  */
4185
4321
  class AnimClipElement extends AsyncElement {
4186
4322
  /**
@@ -4508,6 +4644,10 @@ customElements.define('pc-anim-clip', AnimClipElement);
4508
4644
  *
4509
4645
  * Engine component: {@link AudioListenerComponent} (`audiolistener`).
4510
4646
  *
4647
+ * @elementSummary The `<pc-audio-listener>` element makes its entity the point from which
4648
+ * positional sounds are heard, typically the entity holding the `<pc-camera>`. Must be a child of a
4649
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
4650
+ *
4511
4651
  * @category Components
4512
4652
  */
4513
4653
  class AudioListenerComponentElement extends ComponentElement {
@@ -4537,6 +4677,11 @@ const transitionModes = new Map([
4537
4677
  *
4538
4678
  * Engine component: {@link ButtonComponent} (`button`).
4539
4679
  *
4680
+ * @elementSummary The `<pc-button>` element makes its entity respond to pointer input, tinting or
4681
+ * swapping its image as the pointer hovers, presses and releases it. The entity also needs a
4682
+ * `<pc-element>` with `use-input` set. Must be a child of a `<pc-entity>`, `<pc-model>` or
4683
+ * `<pc-node>`.
4684
+ *
4540
4685
  * @category Components
4541
4686
  */
4542
4687
  class ButtonComponentElement extends ComponentElement {
@@ -4940,13 +5085,18 @@ const tonemaps = new Map([
4940
5085
  *
4941
5086
  * Engine component: {@link CameraComponent} (`camera`).
4942
5087
  *
5088
+ * @elementSummary The `<pc-camera>` element renders the scene from its entity's transform, with
5089
+ * attributes for the projection, field of view, clip planes, clear color and tonemapping. Must be a
5090
+ * child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
5091
+ *
4943
5092
  * @category Components
4944
5093
  */
4945
5094
  class CameraComponentElement extends ComponentElement {
4946
5095
  _clearColor = new Color(0.75, 0.75, 0.75, 1);
4947
5096
  _clearColorBuffer = true;
5097
+ _clearDepth = 1;
4948
5098
  _clearDepthBuffer = true;
4949
- _clearStencilBuffer = false;
5099
+ _clearStencilBuffer = true;
4950
5100
  _cullFaces = true;
4951
5101
  _farClip = 1000;
4952
5102
  _flipFaces = false;
@@ -4969,6 +5119,7 @@ class CameraComponentElement extends ComponentElement {
4969
5119
  return {
4970
5120
  clearColor: this._clearColor,
4971
5121
  clearColorBuffer: this._clearColorBuffer,
5122
+ clearDepth: this._clearDepth,
4972
5123
  clearDepthBuffer: this._clearDepthBuffer,
4973
5124
  clearStencilBuffer: this._clearStencilBuffer,
4974
5125
  cullFaces: this._cullFaces,
@@ -4987,9 +5138,31 @@ class CameraComponentElement extends ComponentElement {
4987
5138
  toneMapping: tonemaps.get(this._tonemap) ?? TONEMAP_NONE
4988
5139
  };
4989
5140
  }
4990
- get xrAvailable() {
5141
+ /**
5142
+ * Whether immersive AR is available. Independent of {@link vrAvailable}: a device can offer
5143
+ * either mode without the other.
5144
+ * @returns Whether immersive AR is available.
5145
+ */
5146
+ get arAvailable() {
5147
+ return this._available(XRTYPE_AR);
5148
+ }
5149
+ /**
5150
+ * Whether immersive VR is available. Independent of {@link arAvailable}: a device can offer
5151
+ * either mode without the other.
5152
+ * @returns Whether immersive VR is available.
5153
+ */
5154
+ get vrAvailable() {
5155
+ return this._available(XRTYPE_VR);
5156
+ }
5157
+ /**
5158
+ * Whether one XR session type is available on this device.
5159
+ *
5160
+ * @param type - The XR session type to test.
5161
+ * @returns Whether that type is available.
5162
+ */
5163
+ _available(type) {
4991
5164
  const xrManager = this.component?.system.app.xr;
4992
- return xrManager && xrManager.supported && xrManager.isAvailable(XRTYPE_VR);
5165
+ return Boolean(xrManager?.supported && xrManager.isAvailable(type));
4993
5166
  }
4994
5167
  /**
4995
5168
  * Starts the camera in XR mode.
@@ -4997,11 +5170,13 @@ class CameraComponentElement extends ComponentElement {
4997
5170
  * @param space - The space to start the camera in.
4998
5171
  */
4999
5172
  startXr(type, space) {
5000
- if (this.component && this.xrAvailable) {
5173
+ // Gated on the mode being started, not on XR in general: a device that offers only
5174
+ // one of the two would otherwise accept a session it cannot serve
5175
+ if (this.component && this._available(type)) {
5001
5176
  this.component.startXr(type, space, {
5002
5177
  callback: (err) => {
5003
5178
  if (err)
5004
- console.error(`WebXR Immersive VR failed to start: ${err.message}`);
5179
+ console.error(`WebXR ${type} failed to start: ${err.message}`);
5005
5180
  }
5006
5181
  });
5007
5182
  }
@@ -5055,6 +5230,23 @@ class CameraComponentElement extends ComponentElement {
5055
5230
  get clearColorBuffer() {
5056
5231
  return this._clearColorBuffer;
5057
5232
  }
5233
+ /**
5234
+ * Sets the depth value the depth buffer is cleared to. Defaults to 1.
5235
+ * @param value - The clear depth value.
5236
+ */
5237
+ set clearDepth(value) {
5238
+ this._clearDepth = value;
5239
+ if (this.component) {
5240
+ this.component.clearDepth = value;
5241
+ }
5242
+ }
5243
+ /**
5244
+ * Gets the depth value the depth buffer is cleared to.
5245
+ * @returns The clear depth value.
5246
+ */
5247
+ get clearDepth() {
5248
+ return this._clearDepth;
5249
+ }
5058
5250
  /**
5059
5251
  * Sets the clear depth buffer of the camera.
5060
5252
  * @param value - The clear depth buffer.
@@ -5333,6 +5525,7 @@ class CameraComponentElement extends ComponentElement {
5333
5525
  ...super.observedAttributes,
5334
5526
  'clear-color',
5335
5527
  'clear-color-buffer',
5528
+ 'clear-depth',
5336
5529
  'clear-depth-buffer',
5337
5530
  'clear-stencil-buffer',
5338
5531
  'cull-faces',
@@ -5360,11 +5553,14 @@ class CameraComponentElement extends ComponentElement {
5360
5553
  case 'clear-color-buffer':
5361
5554
  this.clearColorBuffer = parseBool(newValue, true);
5362
5555
  break;
5556
+ case 'clear-depth':
5557
+ this.clearDepth = parseNumber(newValue, 1, name);
5558
+ break;
5363
5559
  case 'clear-depth-buffer':
5364
5560
  this.clearDepthBuffer = parseBool(newValue, true);
5365
5561
  break;
5366
5562
  case 'clear-stencil-buffer':
5367
- this.clearStencilBuffer = parseBool(newValue, false);
5563
+ this.clearStencilBuffer = parseBool(newValue, true);
5368
5564
  break;
5369
5565
  case 'cull-faces':
5370
5566
  this.cullFaces = parseBool(newValue, true);
@@ -5427,6 +5623,10 @@ customElements.define('pc-camera', CameraComponentElement);
5427
5623
  *
5428
5624
  * Engine component: {@link CollisionComponent} (`collision`).
5429
5625
  *
5626
+ * @elementSummary The `<pc-collision>` element gives its entity a collision shape — a box, sphere,
5627
+ * capsule, cone, cylinder or mesh — for the physics simulation to collide against. Pair it with a
5628
+ * `<pc-rigid-body>`. Must be a child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
5629
+ *
5430
5630
  * @category Components
5431
5631
  */
5432
5632
  class CollisionComponentElement extends ComponentElement {
@@ -5617,6 +5817,10 @@ customElements.define('pc-collision', CollisionComponentElement);
5617
5817
  *
5618
5818
  * Engine component: {@link ElementComponent} (`element`).
5619
5819
  *
5820
+ * @elementSummary The `<pc-element>` element gives its entity a 2D UI rectangle inside a
5821
+ * `<pc-screen>` hierarchy, drawing an image, a line of text or nothing (`type="image"`, `"text"` or
5822
+ * `"group"`). Must be a child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
5823
+ *
5620
5824
  * @category Components
5621
5825
  */
5622
5826
  class ElementComponentElement extends ComponentElement {
@@ -6301,6 +6505,11 @@ customElements.define('pc-element', ElementComponentElement);
6301
6505
  * a rigid body component; leaving `entity-b` empty constrains `entity-a` to a fixed point in world
6302
6506
  * space. The underlying engine component is in alpha, so its API may change.
6303
6507
  *
6508
+ * @elementSummary The `<pc-joint>` element constrains two rigid bodies to each other — a hinged
6509
+ * door, a swinging chain, a sliding drawer. Its entity's transform is the joint frame, and
6510
+ * `entity-a` and `entity-b` name the bodies. Must be a child of a `<pc-entity>`, `<pc-model>` or
6511
+ * `<pc-node>`.
6512
+ *
6304
6513
  * @fires {CustomEvent} break - Fired when the impulse on the joint exceeds `break-impulse` and the
6305
6514
  * constraint breaks. A broken joint no longer constrains its bodies; calling `refreshFrames()` on
6306
6515
  * the underlying component re-attaches it. Bubbles and is composed.
@@ -7171,6 +7380,10 @@ customElements.define('pc-joint', JointComponentElement);
7171
7380
  *
7172
7381
  * Engine component: {@link LayoutChildComponent} (`layoutchild`).
7173
7382
  *
7383
+ * @elementSummary The `<pc-layout-child>` element controls how its entity is sized by the
7384
+ * `<pc-layout-group>` above it, through minimum and maximum sizes and fit proportions. Must be a
7385
+ * child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
7386
+ *
7174
7387
  * @category Components
7175
7388
  */
7176
7389
  class LayoutChildComponentElement extends ComponentElement {
@@ -7383,6 +7596,10 @@ const fittings = new Map([
7383
7596
  *
7384
7597
  * Engine component: {@link LayoutGroupComponent} (`layoutgroup`).
7385
7598
  *
7599
+ * @elementSummary The `<pc-layout-group>` element arranges its entity's children in a row or
7600
+ * column, with spacing, padding, alignment and fitting. Must be a child of a `<pc-entity>`,
7601
+ * `<pc-model>` or `<pc-node>`.
7602
+ *
7386
7603
  * @category Components
7387
7604
  */
7388
7605
  class LayoutGroupComponentElement extends ComponentElement {
@@ -7643,23 +7860,30 @@ const shadowTypes = new Map([
7643
7860
  *
7644
7861
  * Engine component: {@link LightComponent} (`light`).
7645
7862
  *
7863
+ * @elementSummary The `<pc-light>` element lights the scene from its entity — as a directional,
7864
+ * omni or spot light — with attributes for color, intensity, range and shadows. Must be a child of
7865
+ * a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
7866
+ *
7646
7867
  * @category Components
7647
7868
  */
7648
7869
  class LightComponentElement extends ComponentElement {
7870
+ _cascadeBlend = 0;
7871
+ _cascadeDistribution = 0.5;
7649
7872
  _castShadows = false;
7650
7873
  _color = new Color(1, 1, 1);
7651
7874
  _innerConeAngle = 40;
7652
7875
  _intensity = 1;
7653
- _normalOffsetBias = 0.05;
7876
+ _normalOffsetBias = 0;
7877
+ _numCascades = 1;
7654
7878
  _outerConeAngle = 45;
7655
7879
  _range = 10;
7656
- _shadowBias = 0.2;
7657
- _shadowDistance = 16;
7880
+ _shadowBias = 0.05;
7881
+ _shadowDistance = 40;
7658
7882
  _shadowIntensity = 1;
7659
7883
  _shadowResolution = 1024;
7660
7884
  _shadowType = 'pcf3-32f';
7661
7885
  _type = 'directional';
7662
- _vsmBias = 0.01;
7886
+ _vsmBias = 0.0025;
7663
7887
  _vsmBlurSize = 11;
7664
7888
  _penumbraSize = 1;
7665
7889
  _penumbraFalloff = 1;
@@ -7671,11 +7895,14 @@ class LightComponentElement extends ComponentElement {
7671
7895
  }
7672
7896
  getInitialComponentData() {
7673
7897
  return {
7898
+ cascadeBlend: this._cascadeBlend,
7899
+ cascadeDistribution: this._cascadeDistribution,
7674
7900
  castShadows: this._castShadows,
7675
7901
  color: this._color,
7676
7902
  innerConeAngle: this._innerConeAngle,
7677
7903
  intensity: this._intensity,
7678
7904
  normalOffsetBias: this._normalOffsetBias,
7905
+ numCascades: this._numCascades,
7679
7906
  outerConeAngle: this._outerConeAngle,
7680
7907
  penumbraFalloff: this._penumbraFalloff,
7681
7908
  penumbraSize: this._penumbraSize,
@@ -7686,7 +7913,7 @@ class LightComponentElement extends ComponentElement {
7686
7913
  shadowIntensity: this._shadowIntensity,
7687
7914
  shadowResolution: this._shadowResolution,
7688
7915
  shadowSamples: this._shadowSamples,
7689
- shadowType: shadowTypes.get(this._shadowType),
7916
+ shadowType: shadowTypes.get(this._shadowType) ?? SHADOW_PCF3_32F,
7690
7917
  type: this._type,
7691
7918
  vsmBias: this._vsmBias,
7692
7919
  vsmBlurSize: this._vsmBlurSize
@@ -7699,6 +7926,47 @@ class LightComponentElement extends ComponentElement {
7699
7926
  get component() {
7700
7927
  return super.component;
7701
7928
  }
7929
+ /**
7930
+ * Sets the fraction of each shadow cascade that is blended into the next one, from 0 (no
7931
+ * blending) to 1, which applies only to `directional` lights with `num-cascades` greater than
7932
+ * 1. Defaults to 0.
7933
+ * @param value - The cascade blend factor.
7934
+ */
7935
+ set cascadeBlend(value) {
7936
+ this._cascadeBlend = value;
7937
+ if (this.component) {
7938
+ this.component.cascadeBlend = value;
7939
+ }
7940
+ }
7941
+ /**
7942
+ * Gets the cascade blend factor of the light, from 0 (no blending) to 1, which applies only to
7943
+ * `directional` lights with `num-cascades` greater than 1.
7944
+ * @returns The cascade blend factor.
7945
+ */
7946
+ get cascadeBlend() {
7947
+ return this._cascadeBlend;
7948
+ }
7949
+ /**
7950
+ * Sets the distribution of the camera frustum split between shadow cascades, from 0 (linear
7951
+ * split) to 1 (logarithmic split, concentrating shadow resolution near the camera), which
7952
+ * applies only to `directional` lights with `num-cascades` greater than 1. Defaults to 0.5.
7953
+ * @param value - The cascade distribution.
7954
+ */
7955
+ set cascadeDistribution(value) {
7956
+ this._cascadeDistribution = value;
7957
+ if (this.component) {
7958
+ this.component.cascadeDistribution = value;
7959
+ }
7960
+ }
7961
+ /**
7962
+ * Gets the cascade distribution of the light, from 0 (linear split) to 1 (logarithmic split,
7963
+ * concentrating shadow resolution near the camera), which applies only to `directional` lights
7964
+ * with `num-cascades` greater than 1.
7965
+ * @returns The cascade distribution.
7966
+ */
7967
+ get cascadeDistribution() {
7968
+ return this._cascadeDistribution;
7969
+ }
7702
7970
  /**
7703
7971
  * Sets the cast shadows flag of the light.
7704
7972
  * @param value - The cast shadows flag.
@@ -7784,6 +8052,25 @@ class LightComponentElement extends ComponentElement {
7784
8052
  get normalOffsetBias() {
7785
8053
  return this._normalOffsetBias;
7786
8054
  }
8055
+ /**
8056
+ * Sets the number of shadow cascades of the light, an integer from 1 (no cascades) to 4, which
8057
+ * applies only to `directional` lights. Defaults to 1.
8058
+ * @param value - The number of shadow cascades.
8059
+ */
8060
+ set numCascades(value) {
8061
+ this._numCascades = value;
8062
+ if (this.component) {
8063
+ this.component.numCascades = value;
8064
+ }
8065
+ }
8066
+ /**
8067
+ * Gets the number of shadow cascades of the light, an integer from 1 (no cascades) to 4, which
8068
+ * applies only to `directional` lights.
8069
+ * @returns The number of shadow cascades.
8070
+ */
8071
+ get numCascades() {
8072
+ return this._numCascades;
8073
+ }
7787
8074
  /**
7788
8075
  * Sets the outer cone angle of the light.
7789
8076
  * @param value - The outer cone angle.
@@ -8036,11 +8323,14 @@ class LightComponentElement extends ComponentElement {
8036
8323
  static get observedAttributes() {
8037
8324
  return [
8038
8325
  ...super.observedAttributes,
8039
- 'color',
8326
+ 'cascade-blend',
8327
+ 'cascade-distribution',
8040
8328
  'cast-shadows',
8041
- 'intensity',
8329
+ 'color',
8042
8330
  'inner-cone-angle',
8331
+ 'intensity',
8043
8332
  'normal-offset-bias',
8333
+ 'num-cascades',
8044
8334
  'outer-cone-angle',
8045
8335
  'penumbra-falloff',
8046
8336
  'penumbra-size',
@@ -8060,12 +8350,18 @@ class LightComponentElement extends ComponentElement {
8060
8350
  attributeChangedCallback(name, _oldValue, newValue) {
8061
8351
  super.attributeChangedCallback(name, _oldValue, newValue);
8062
8352
  switch (name) {
8063
- case 'color':
8064
- this.color = parseColor(newValue, Color.WHITE, name);
8353
+ case 'cascade-blend':
8354
+ this.cascadeBlend = parseNumber(newValue, 0, name);
8355
+ break;
8356
+ case 'cascade-distribution':
8357
+ this.cascadeDistribution = parseNumber(newValue, 0.5, name);
8065
8358
  break;
8066
8359
  case 'cast-shadows':
8067
8360
  this.castShadows = parseBool(newValue, false);
8068
8361
  break;
8362
+ case 'color':
8363
+ this.color = parseColor(newValue, Color.WHITE, name);
8364
+ break;
8069
8365
  case 'inner-cone-angle':
8070
8366
  this.innerConeAngle = parseNumber(newValue, 40, name);
8071
8367
  break;
@@ -8073,7 +8369,10 @@ class LightComponentElement extends ComponentElement {
8073
8369
  this.intensity = parseNumber(newValue, 1, name);
8074
8370
  break;
8075
8371
  case 'normal-offset-bias':
8076
- this.normalOffsetBias = parseNumber(newValue, 0.05, name);
8372
+ this.normalOffsetBias = parseNumber(newValue, 0, name);
8373
+ break;
8374
+ case 'num-cascades':
8375
+ this.numCascades = parseNumber(newValue, 1, name);
8077
8376
  break;
8078
8377
  case 'outer-cone-angle':
8079
8378
  this.outerConeAngle = parseNumber(newValue, 45, name);
@@ -8088,20 +8387,20 @@ class LightComponentElement extends ComponentElement {
8088
8387
  this.range = parseNumber(newValue, 10, name);
8089
8388
  break;
8090
8389
  case 'shadow-bias':
8091
- this.shadowBias = parseNumber(newValue, 0.2, name);
8092
- break;
8093
- case 'shadow-distance':
8094
- this.shadowDistance = parseNumber(newValue, 16, name);
8390
+ this.shadowBias = parseNumber(newValue, 0.05, name);
8095
8391
  break;
8096
8392
  case 'shadow-blocker-samples':
8097
8393
  this.shadowBlockerSamples = parseNumber(newValue, 16, name);
8098
8394
  break;
8099
- case 'shadow-resolution':
8100
- this.shadowResolution = parseNumber(newValue, 1024, name);
8395
+ case 'shadow-distance':
8396
+ this.shadowDistance = parseNumber(newValue, 40, name);
8101
8397
  break;
8102
8398
  case 'shadow-intensity':
8103
8399
  this.shadowIntensity = parseNumber(newValue, 1, name);
8104
8400
  break;
8401
+ case 'shadow-resolution':
8402
+ this.shadowResolution = parseNumber(newValue, 1024, name);
8403
+ break;
8105
8404
  case 'shadow-samples':
8106
8405
  this.shadowSamples = parseNumber(newValue, 16, name);
8107
8406
  break;
@@ -8112,7 +8411,7 @@ class LightComponentElement extends ComponentElement {
8112
8411
  this.type = parseEnum(newValue, ['directional', 'omni', 'spot'], 'directional', name);
8113
8412
  break;
8114
8413
  case 'vsm-bias':
8115
- this.vsmBias = parseNumber(newValue, 0.01, name);
8414
+ this.vsmBias = parseNumber(newValue, 0.0025, name);
8116
8415
  break;
8117
8416
  case 'vsm-blur-size':
8118
8417
  this.vsmBlurSize = parseNumber(newValue, 11, name);
@@ -8130,6 +8429,10 @@ customElements.define('pc-light', LightComponentElement);
8130
8429
  *
8131
8430
  * Engine component: {@link ParticleSystemComponent} (`particlesystem`).
8132
8431
  *
8432
+ * @elementSummary The `<pc-particle-system>` element emits particles from its entity, with
8433
+ * attributes for the emitter's shape, rate, lifetime, textures and blending. Must be a child of a
8434
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
8435
+ *
8133
8436
  * @category Components
8134
8437
  */
8135
8438
  class ParticleSystemComponentElement extends ComponentElement {
@@ -8278,6 +8581,8 @@ const occludeSpeculars = new Map([
8278
8581
  ['ao', SPECOCC_AO],
8279
8582
  ['gloss-dependent', SPECOCC_GLOSSDEPENDENT]
8280
8583
  ]);
8584
+ // The DITHER_* constants are strings whose values are exactly these names, so a parsed value is
8585
+ // assigned to the material unchanged rather than mapped through a table.
8281
8586
  const opacityDithers = ['none', 'bayer8', 'bluenoise', 'ignnoise'];
8282
8587
  const colorChannels = ['r', 'g', 'b', 'a', 'rgb'];
8283
8588
  const scalarChannels = ['r', 'g', 'b', 'a'];
@@ -8314,6 +8619,10 @@ const roughnessAliases = ['roughness', 'roughness-map'];
8314
8619
  * The two aliases are documented here rather than on an accessor, because they resolve to the
8315
8620
  * `gloss` properties and would otherwise inherit gloss's description - which reads inverted.
8316
8621
  *
8622
+ * @elementSummary The `<pc-material>` element defines a physically based material, which
8623
+ * `<pc-render>` elements apply by naming its `id`. It is metal/rough by default, with `metalness`
8624
+ * starting at 0. Must be a direct child of `<pc-app>`.
8625
+ *
8317
8626
  * @attribute {number} roughness - The roughness of the material, from 0 (shiny) to 1 (rough). An
8318
8627
  * alias for `gloss` that also inverts it, so do not combine it with the `gloss` attributes.
8319
8628
  * @attribute {string} roughness-map - The id of the `pc-asset` to use as the roughness map. An
@@ -10482,6 +10791,11 @@ customElements.define('pc-material', MaterialElement);
10482
10791
  *
10483
10792
  * Engine component: {@link RenderComponent} (`render`).
10484
10793
  *
10794
+ * @elementSummary The `<pc-render>` element renders one of the engine's built-in primitives at its
10795
+ * entity — box, sphere, capsule, cone, cylinder or plane — shaded by the `<pc-material>` its
10796
+ * `material` attribute names. For glTF content, use `<pc-model>` instead. Must be a child of a
10797
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
10798
+ *
10485
10799
  * @category Components
10486
10800
  */
10487
10801
  class RenderComponentElement extends ComponentElement {
@@ -10612,6 +10926,11 @@ customElements.define('pc-render', RenderComponentElement);
10612
10926
  *
10613
10927
  * Engine component: {@link RigidBodyComponent} (`rigidbody`).
10614
10928
  *
10929
+ * @elementSummary The `<pc-rigid-body>` element hands its entity to the physics simulation, with
10930
+ * attributes for its type, mass, friction and restitution. It needs a sibling `<pc-collision>` for
10931
+ * its shape, and `Ammo` loaded through `<pc-wasm>`. Must be a child of a `<pc-entity>`,
10932
+ * `<pc-model>` or `<pc-node>`.
10933
+ *
10615
10934
  * @category Components
10616
10935
  */
10617
10936
  class RigidBodyComponentElement extends ComponentElement {
@@ -10821,6 +11140,10 @@ const scaleModes = new Map([
10821
11140
  *
10822
11141
  * Engine component: {@link ScreenComponent} (`screen`).
10823
11142
  *
11143
+ * @elementSummary The `<pc-screen>` element gives its entity a 2D space — in screen space or in the
11144
+ * world — that a hierarchy of `<pc-element>` descendants lays out inside. Must be a child of a
11145
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
11146
+ *
10824
11147
  * @category Components
10825
11148
  */
10826
11149
  class ScreenComponentElement extends ComponentElement {
@@ -10975,6 +11298,10 @@ const orientations = new Map([
10975
11298
  *
10976
11299
  * Engine component: {@link ScrollbarComponent} (`scrollbar`).
10977
11300
  *
11301
+ * @elementSummary The `<pc-scrollbar>` element gives its entity a draggable handle reporting a
11302
+ * position from 0 to 1, which a `<pc-scroll-view>` references to scroll its content. Must be a
11303
+ * child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
11304
+ *
10978
11305
  * @category Components
10979
11306
  */
10980
11307
  class ScrollbarComponentElement extends ComponentElement {
@@ -11116,6 +11443,10 @@ const visibilities = new Map([
11116
11443
  *
11117
11444
  * Engine component: {@link ScrollViewComponent} (`scrollview`).
11118
11445
  *
11446
+ * @elementSummary The `<pc-scroll-view>` element scrolls a larger content entity within a clipped
11447
+ * viewport at its entity, optionally driven by the `<pc-scrollbar>` elements it references. Must be
11448
+ * a child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
11449
+ *
11119
11450
  * @category Components
11120
11451
  */
11121
11452
  class ScrollViewComponentElement extends ComponentElement {
@@ -11502,6 +11833,10 @@ customElements.define('pc-scroll-view', ScrollViewComponentElement);
11502
11833
  * The element becomes ready once its script instance has been created by the parent
11503
11834
  * `<pc-script>` element.
11504
11835
  *
11836
+ * @elementSummary The `<pc-script-instance>` element attaches one script class, named by `name`, to
11837
+ * the entity of its parent `<pc-script>`. Its other attributes set script attributes of the same
11838
+ * name, and `attributes` takes a JSON object instead. Must be a direct child of `<pc-script>`.
11839
+ *
11505
11840
  * @fires {CustomEvent} scriptattributeschange - Fired when the script's attributes change. The
11506
11841
  * `detail` carries the new `attributes` object. Bubbles.
11507
11842
  * @fires {CustomEvent} scriptenablechange - Fired when the script's enabled state changes. The
@@ -11846,6 +12181,9 @@ const findCaseMatch = (script, key) => {
11846
12181
  *
11847
12182
  * Engine component: {@link ScriptComponent} (`script`).
11848
12183
  *
12184
+ * @elementSummary The `<pc-script>` element holds the `<pc-script-instance>` children that attach
12185
+ * scripts to its entity. Must be a child of a `<pc-entity>`, `<pc-model>` or `<pc-node>`.
12186
+ *
11849
12187
  * @category Components
11850
12188
  */
11851
12189
  class ScriptComponentElement extends ComponentElement {
@@ -12295,6 +12633,10 @@ customElements.define('pc-script', ScriptComponentElement);
12295
12633
  *
12296
12634
  * Engine component: {@link SoundComponent} (`sound`).
12297
12635
  *
12636
+ * @elementSummary The `<pc-sound>` element holds the `<pc-sound-slot>` children that play sounds at
12637
+ * its entity, along with the positional audio settings they share. Must be a child of a
12638
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
12639
+ *
12298
12640
  * @category Components
12299
12641
  */
12300
12642
  class SoundComponentElement extends ComponentElement {
@@ -12491,6 +12833,9 @@ customElements.define('pc-sound', SoundComponentElement);
12491
12833
  * The SoundSlotElement interface provides properties and methods for manipulating
12492
12834
  * `<pc-sound-slot>` elements. The SoundSlotElement interface also inherits the properties and
12493
12835
  * methods of the {@link AsyncElement} interface.
12836
+ *
12837
+ * @elementSummary The `<pc-sound-slot>` element declares one named sound on its parent `<pc-sound>`
12838
+ * — its asset, volume, pitch, looping and autoplay. Must be a direct child of `<pc-sound>`.
12494
12839
  */
12495
12840
  class SoundSlotElement extends AsyncElement {
12496
12841
  _asset = '';
@@ -12772,6 +13117,10 @@ customElements.define('pc-sound-slot', SoundSlotElement);
12772
13117
  *
12773
13118
  * Engine component: {@link GSplatComponent} (`gsplat`).
12774
13119
  *
13120
+ * @elementSummary The `<pc-gsplat>` element renders the 3D Gaussian splats of a `gsplat` asset at
13121
+ * its entity, with attributes for shadow casting and level of detail. Must be a child of a
13122
+ * `<pc-entity>`, `<pc-model>` or `<pc-node>`.
13123
+ *
12775
13124
  * @category Components
12776
13125
  */
12777
13126
  class GSplatComponentElement extends ComponentElement {
@@ -13067,6 +13416,11 @@ const levenshtein = (a, b) => {
13067
13416
  * The pointer events below are dispatched by the containing `<pc-app>` element when the pointer
13068
13417
  * intersects the bound node's geometry, exactly as for `<pc-entity>`.
13069
13418
  *
13419
+ * @elementSummary The `<pc-node>` element binds to a node inside the hierarchy a `<pc-model>`
13420
+ * instantiated and declares overrides against it: a transform, an enabled state, tags, components
13421
+ * to add, or content to attach. Its `name` is a lookup, never a rename. Must be a descendant of
13422
+ * `<pc-model>`.
13423
+ *
13070
13424
  * @attribute {string} name - The name of the node to bind, resolved within the nearest ancestor
13071
13425
  * `pc-model` (or `pc-node`) once it has instantiated.
13072
13426
  * @attribute {number} index - Which match to bind when `name` matches more than one node,
@@ -13091,11 +13445,17 @@ const levenshtein = (a, b) => {
13091
13445
  * node.
13092
13446
  * @attribute {string} onpointerup - Script to run when a pointer button is released over the
13093
13447
  * node.
13448
+ * @attribute {string} onclick - Script to run when the node is clicked: a primary pointer
13449
+ * button pressed and then released over it.
13094
13450
  * @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the node.
13095
13451
  * @fires {PointerEvent} pointerleave - Fired when the pointer moves off the node.
13096
13452
  * @fires {PointerEvent} pointermove - Fired when the pointer moves over the node.
13097
13453
  * @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the node.
13098
13454
  * @fires {PointerEvent} pointerup - Fired when a pointer button is released over the node.
13455
+ * @fires {PointerEvent} click - Fired when a primary pointer button is pressed and then released
13456
+ * over the node. A press and release that picked different entities fires on their nearest
13457
+ * common ancestor instead, as in the DOM. `detail` carries the click count, so a double click
13458
+ * arrives as a click whose `detail` is 2.
13099
13459
  */
13100
13460
  class NodeElement extends EntityBaseElement {
13101
13461
  _name = '';
@@ -13745,7 +14105,7 @@ class NodeElement extends EntityBaseElement {
13745
14105
  'rotation',
13746
14106
  'scale',
13747
14107
  'tags',
13748
- ...POINTER_ATTRIBUTES
14108
+ ...EVENT_ATTRIBUTES
13749
14109
  ];
13750
14110
  }
13751
14111
  attributeChangedCallback(name, _oldValue, newValue) {
@@ -13795,6 +14155,7 @@ class NodeElement extends EntityBaseElement {
13795
14155
  case 'onpointerdown':
13796
14156
  case 'onpointerup':
13797
14157
  case 'onpointermove':
14158
+ case 'onclick':
13798
14159
  this._updateInlineHandler(name, newValue);
13799
14160
  break;
13800
14161
  }
@@ -13807,8 +14168,16 @@ customElements.define('pc-node', NodeElement);
13807
14168
  * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-scene/ | `<pc-scene>`} elements.
13808
14169
  * The SceneElement interface also inherits the properties and methods of the
13809
14170
  * {@link HTMLElement} interface.
14171
+ *
14172
+ * @elementSummary The `<pc-scene>` element holds the entity hierarchy the application renders,
14173
+ * along with the scene-wide fog, exposure and gravity settings. Must be a direct child of
14174
+ * `<pc-app>`.
13810
14175
  */
13811
14176
  class SceneElement extends AsyncElement {
14177
+ /**
14178
+ * The exposure of the scene.
14179
+ */
14180
+ _exposure = 1;
13812
14181
  /**
13813
14182
  * The fog type of the scene.
13814
14183
  */
@@ -13876,6 +14245,7 @@ class SceneElement extends AsyncElement {
13876
14245
  }
13877
14246
  _updateSceneSettings() {
13878
14247
  if (this._scene) {
14248
+ this._scene.exposure = this._exposure;
13879
14249
  this._scene.fog.type = this._fog;
13880
14250
  this._scene.fog.color = this._fogColor;
13881
14251
  this._scene.fog.density = this._fogDensity;
@@ -13894,6 +14264,24 @@ class SceneElement extends AsyncElement {
13894
14264
  _applyGravity(value) {
13895
14265
  this.closestApp?.app?.systems.rigidbody?.gravity.copy(value);
13896
14266
  }
14267
+ /**
14268
+ * Sets the exposure of the scene, which tweaks the overall brightness of the rendered image.
14269
+ * Ignored if the scene is using physical units. Defaults to 1.
14270
+ * @param value - The exposure.
14271
+ */
14272
+ set exposure(value) {
14273
+ this._exposure = value;
14274
+ if (this.scene) {
14275
+ this.scene.exposure = value;
14276
+ }
14277
+ }
14278
+ /**
14279
+ * Gets the exposure of the scene.
14280
+ * @returns The exposure.
14281
+ */
14282
+ get exposure() {
14283
+ return this._exposure;
14284
+ }
13897
14285
  /**
13898
14286
  * Sets the fog type of the scene. Can be `none`, `linear`, `exp` or `exp2`. Defaults to
13899
14287
  * `none`.
@@ -13998,10 +14386,13 @@ class SceneElement extends AsyncElement {
13998
14386
  return this._gravity;
13999
14387
  }
14000
14388
  static get observedAttributes() {
14001
- return ['fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end', 'gravity'];
14389
+ return ['exposure', 'fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end', 'gravity'];
14002
14390
  }
14003
14391
  attributeChangedCallback(name, _oldValue, newValue) {
14004
14392
  switch (name) {
14393
+ case 'exposure':
14394
+ this.exposure = parseNumber(newValue, 1, name);
14395
+ break;
14005
14396
  case 'fog':
14006
14397
  this.fog = parseEnum(newValue, ['none', 'linear', 'exp', 'exp2'], 'none', name);
14007
14398
  break;
@@ -14030,6 +14421,10 @@ customElements.define('pc-scene', SceneElement);
14030
14421
  * The SkyElement interface provides properties and methods for manipulating
14031
14422
  * `<pc-sky>` elements. The SkyElement interface also inherits the properties and
14032
14423
  * methods of the {@link HTMLElement} interface.
14424
+ *
14425
+ * @elementSummary The `<pc-sky>` element renders a skybox from a texture asset, projected as an
14426
+ * infinite background, a box or a dome, and optionally lights the scene from it. Must be a direct
14427
+ * child of `<pc-scene>`.
14033
14428
  */
14034
14429
  class SkyElement extends AsyncElement {
14035
14430
  _asset = '';