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