@playcanvas/web-components 0.18.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.
- package/dist/app.d.cts +24 -3
- package/dist/app.d.ts +24 -3
- package/dist/components/anim-clip.d.cts +0 -2
- package/dist/components/anim-clip.d.ts +0 -2
- package/dist/custom-elements.json +72 -2
- package/dist/entity-base.d.cts +4 -3
- package/dist/entity-base.d.ts +4 -3
- package/dist/entity.d.cts +8 -2
- package/dist/entity.d.ts +8 -2
- package/dist/model.d.cts +6 -0
- package/dist/model.d.ts +6 -0
- package/dist/node.d.cts +6 -0
- package/dist/node.d.ts +6 -0
- package/dist/pwc.cjs +322 -183
- package/dist/pwc.cjs.map +1 -1
- package/dist/pwc.js +322 -183
- package/dist/pwc.js.map +1 -1
- package/dist/pwc.min.js +1 -1
- package/dist/pwc.min.js.map +1 -1
- package/dist/pwc.min.mjs +1 -1
- package/dist/pwc.min.mjs.map +1 -1
- package/dist/pwc.mjs +322 -183
- package/dist/pwc.mjs.map +1 -1
- package/dist/scene.d.cts +17 -1
- package/dist/scene.d.ts +17 -1
- package/dist/vscode.html-custom-data.json +24 -4
- package/dist/web-types.json +59 -5
- package/package.json +3 -3
- package/src/app.ts +146 -71
- package/src/components/anim-clip.ts +0 -2
- package/src/entity-base.ts +27 -15
- package/src/entity.ts +11 -4
- package/src/model.ts +9 -2
- package/src/node.ts +9 -2
- package/src/scene.ts +33 -2
package/dist/pwc.js
CHANGED
|
@@ -181,6 +181,136 @@
|
|
|
181
181
|
}
|
|
182
182
|
customElements.define('pc-wasm', WasmElement);
|
|
183
183
|
|
|
184
|
+
/**
|
|
185
|
+
* The event types the containing `<pc-app>` synthesizes on entity-fronting elements via picking:
|
|
186
|
+
* the `pointer*` events, plus `click` — which concludes a primary-button press and release, and
|
|
187
|
+
* is delivered as a `PointerEvent` exactly as modern browsers deliver native clicks.
|
|
188
|
+
* @internal
|
|
189
|
+
*/
|
|
190
|
+
const SYNTHESIZED_EVENTS = [
|
|
191
|
+
'pointerenter',
|
|
192
|
+
'pointerleave',
|
|
193
|
+
'pointerdown',
|
|
194
|
+
'pointerup',
|
|
195
|
+
'pointermove',
|
|
196
|
+
'click'
|
|
197
|
+
];
|
|
198
|
+
const SYNTHESIZED_EVENT_SET = new Set(SYNTHESIZED_EVENTS);
|
|
199
|
+
/**
|
|
200
|
+
* The attribute names of the inline event handlers (`onpointerdown`, `onclick`, ...), shared by
|
|
201
|
+
* every element that fronts an engine entity. Spread into `observedAttributes` by subclasses.
|
|
202
|
+
* @internal
|
|
203
|
+
*/
|
|
204
|
+
const EVENT_ATTRIBUTES = SYNTHESIZED_EVENTS.map((type) => `on${type}`);
|
|
205
|
+
/**
|
|
206
|
+
* The base class for elements that front an engine {@link Entity}: `<pc-entity>` and
|
|
207
|
+
* `<pc-model>`, which create one, and `<pc-node>`, which binds to one inside a model's
|
|
208
|
+
* instantiated hierarchy. It carries what all of them need — the `entity` contract, registration
|
|
209
|
+
* with the owning application (which joins picked scene nodes back to elements by identity,
|
|
210
|
+
* never by name), and the pointer listener bookkeeping that lets the application lazily attach
|
|
211
|
+
* its canvas handlers.
|
|
212
|
+
*/
|
|
213
|
+
class EntityBaseElement extends AsyncElement {
|
|
214
|
+
_entity = null;
|
|
215
|
+
/**
|
|
216
|
+
* The application element this entity is registered with, cached at registration time so the
|
|
217
|
+
* entity can be unregistered even once this element has left the DOM.
|
|
218
|
+
*/
|
|
219
|
+
_appElement = null;
|
|
220
|
+
/**
|
|
221
|
+
* The event listeners registered on the element, by type.
|
|
222
|
+
*/
|
|
223
|
+
_listeners = {};
|
|
224
|
+
/**
|
|
225
|
+
* The event types for which an inline handler attribute (`onpointerdown`, `onclick`, ...)
|
|
226
|
+
* is currently present.
|
|
227
|
+
*/
|
|
228
|
+
_inlineHandlerTypes = new Set();
|
|
229
|
+
/**
|
|
230
|
+
* The PlayCanvas entity instance. `null` until the element is ready, and again once the
|
|
231
|
+
* entity is gone — await {@link whenReady} or the element's `ready()` promise before
|
|
232
|
+
* accessing it.
|
|
233
|
+
* @returns The entity instance, or `null`.
|
|
234
|
+
*/
|
|
235
|
+
get entity() {
|
|
236
|
+
return this._entity;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Registers `entity` as this element's backing entity with the owning application, which
|
|
240
|
+
* joins engine nodes back to elements by identity (never by name).
|
|
241
|
+
*
|
|
242
|
+
* @param entity - The entity to register.
|
|
243
|
+
*/
|
|
244
|
+
_registerEntity(entity) {
|
|
245
|
+
this._appElement = this.closestApp;
|
|
246
|
+
this._appElement?._registerEntityElement(entity, this);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Removes the registration for `entity`.
|
|
250
|
+
*
|
|
251
|
+
* @param entity - The entity to unregister.
|
|
252
|
+
*/
|
|
253
|
+
_unregisterEntity(entity) {
|
|
254
|
+
this._appElement?._unregisterEntityElement(entity);
|
|
255
|
+
this._appElement = null;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Tracks whether an inline handler attribute is present. The browser itself compiles and
|
|
259
|
+
* runs these attributes — they are standard `GlobalEventHandlers`, so setting one replaces
|
|
260
|
+
* the previous handler and removing it removes the handler, exactly like `onclick` on any
|
|
261
|
+
* HTML element. But because they bypass {@link EventTarget.addEventListener}, the connect/disconnect
|
|
262
|
+
* bookkeeping that lets the application lazily attach its canvas pointer handlers must be
|
|
263
|
+
* kept in sync here.
|
|
264
|
+
*
|
|
265
|
+
* @param name - The attribute name (e.g. 'onpointerdown').
|
|
266
|
+
* @param value - The attribute value, or `null` when the attribute has been removed.
|
|
267
|
+
*/
|
|
268
|
+
_updateInlineHandler(name, value) {
|
|
269
|
+
const type = name.substring(2);
|
|
270
|
+
const had = this._inlineHandlerTypes.has(type);
|
|
271
|
+
const has = value !== null;
|
|
272
|
+
if (has && !had) {
|
|
273
|
+
this._inlineHandlerTypes.add(type);
|
|
274
|
+
this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
|
|
275
|
+
}
|
|
276
|
+
else if (!has && had) {
|
|
277
|
+
this._inlineHandlerTypes.delete(type);
|
|
278
|
+
this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
addEventListener(type, listener, options) {
|
|
282
|
+
if (!this._listeners[type]) {
|
|
283
|
+
this._listeners[type] = [];
|
|
284
|
+
}
|
|
285
|
+
this._listeners[type].push(listener);
|
|
286
|
+
super.addEventListener(type, listener, options);
|
|
287
|
+
if (SYNTHESIZED_EVENT_SET.has(type)) {
|
|
288
|
+
this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
removeEventListener(type, listener, options) {
|
|
292
|
+
if (this._listeners[type]) {
|
|
293
|
+
this._listeners[type] = this._listeners[type].filter((l) => l !== listener);
|
|
294
|
+
}
|
|
295
|
+
super.removeEventListener(type, listener, options);
|
|
296
|
+
if (SYNTHESIZED_EVENT_SET.has(type)) {
|
|
297
|
+
this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Whether the element has a listener for an event type, registered either with
|
|
302
|
+
* {@link EventTarget.addEventListener} or with the matching inline handler attribute. Read by the
|
|
303
|
+
* containing `<pc-app>` element to gate event synthesis.
|
|
304
|
+
*
|
|
305
|
+
* @param type - The event type.
|
|
306
|
+
* @returns Whether a listener is registered.
|
|
307
|
+
* @internal
|
|
308
|
+
*/
|
|
309
|
+
_hasListeners(type) {
|
|
310
|
+
return Boolean(this._listeners[type]?.length) || this._inlineHandlerTypes.has(type);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
184
314
|
/** Covers the 0.2s opacity transition; jsdom never fires transitionend, so removal is timed. */
|
|
185
315
|
const REMOVAL_DELAY_MS = 250;
|
|
186
316
|
/**
|
|
@@ -734,14 +864,51 @@
|
|
|
734
864
|
return element?.entity ?? null;
|
|
735
865
|
};
|
|
736
866
|
|
|
737
|
-
/** The pointer event types the application synthesizes on `<pc-entity>` elements via picking. */
|
|
738
|
-
const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'];
|
|
739
867
|
/**
|
|
740
868
|
* The event types whose listeners make an element a hover target. Hover resolution walks past
|
|
741
869
|
* elements listening for none of them, so a silent element never swallows an ancestor's
|
|
742
870
|
* enter/leave pair.
|
|
743
871
|
*/
|
|
744
872
|
const hoverEventTypes = ['pointerenter', 'pointerleave', 'pointermove'];
|
|
873
|
+
/**
|
|
874
|
+
* The canvas listeners each synthesized event type is driven by. Enter and leave are derived
|
|
875
|
+
* from move picks. A click is concluded from the down/up pair, with pointercancel discarding a
|
|
876
|
+
* press the browser takes back (for example a touch that becomes a scroll).
|
|
877
|
+
*/
|
|
878
|
+
const canvasEventsFor = {
|
|
879
|
+
pointermove: ['pointermove'],
|
|
880
|
+
pointerenter: ['pointermove'],
|
|
881
|
+
pointerleave: ['pointermove'],
|
|
882
|
+
pointerdown: ['pointerdown'],
|
|
883
|
+
pointerup: ['pointerup'],
|
|
884
|
+
click: ['pointerdown', 'pointerup', 'pointercancel']
|
|
885
|
+
};
|
|
886
|
+
/**
|
|
887
|
+
* How long after a click a further click on the same target still raises the click count that
|
|
888
|
+
* `detail` carries, approximating the platform's double-click time.
|
|
889
|
+
*/
|
|
890
|
+
const CLICK_CHAIN_MS = 500;
|
|
891
|
+
/**
|
|
892
|
+
* Finds the nearest common inclusive ancestor of two picked nodes - the node a click belongs to
|
|
893
|
+
* when the press and the release picked different geometry, exactly as the DOM assigns a click
|
|
894
|
+
* whose down and up have different targets.
|
|
895
|
+
*
|
|
896
|
+
* @param a - The node the press picked, or `null`.
|
|
897
|
+
* @param b - The node the release picked, or `null`.
|
|
898
|
+
* @returns The nearest common inclusive ancestor, or `null` when there is none.
|
|
899
|
+
*/
|
|
900
|
+
const commonAncestor = (a, b) => {
|
|
901
|
+
const ancestors = new Set();
|
|
902
|
+
for (let node = a; node !== null; node = node.parent) {
|
|
903
|
+
ancestors.add(node);
|
|
904
|
+
}
|
|
905
|
+
for (let node = b; node !== null; node = node.parent) {
|
|
906
|
+
if (ancestors.has(node)) {
|
|
907
|
+
return node;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return null;
|
|
911
|
+
};
|
|
745
912
|
/**
|
|
746
913
|
* Gives `pc-app` the sizing contract of a replaced element (`<video>`, `<img>`): a block-level
|
|
747
914
|
* box that the page's CSS sizes, defaulting to the canvas's own 300x150 intrinsic size, with the
|
|
@@ -826,21 +993,30 @@
|
|
|
826
993
|
*/
|
|
827
994
|
_entityElements = new Map();
|
|
828
995
|
_picker = null;
|
|
829
|
-
_hasPointerListeners = {
|
|
830
|
-
pointerenter: false,
|
|
831
|
-
pointerleave: false,
|
|
832
|
-
pointerdown: false,
|
|
833
|
-
pointerup: false,
|
|
834
|
-
pointermove: false
|
|
835
|
-
};
|
|
836
996
|
_hoveredEntity = null;
|
|
837
997
|
// Identifies the newest in-flight hover pick, so out-of-order results can be discarded
|
|
838
998
|
_pickToken = 0;
|
|
839
999
|
_pointerHandlers = {
|
|
840
1000
|
pointermove: null,
|
|
841
1001
|
pointerdown: null,
|
|
842
|
-
pointerup: null
|
|
1002
|
+
pointerup: null,
|
|
1003
|
+
pointercancel: null
|
|
843
1004
|
};
|
|
1005
|
+
/**
|
|
1006
|
+
* The pick of each pointer's primary-button press, keyed by pointerId and kept while a click
|
|
1007
|
+
* may still conclude it. The promise is stored rather than its result, so a release can
|
|
1008
|
+
* await a press pick that has not resolved yet. Entries are removed by the matching
|
|
1009
|
+
* pointerup or pointercancel, and only ever stored while some element listens for click -
|
|
1010
|
+
* which is also what keeps those two canvas listeners attached.
|
|
1011
|
+
*/
|
|
1012
|
+
_downPicks = new Map();
|
|
1013
|
+
/** Whether any element in the tree listens for click. Maintained by _syncCanvasListeners. */
|
|
1014
|
+
_clickListened = false;
|
|
1015
|
+
/**
|
|
1016
|
+
* The previous click's target, time and count, for chaining successive clicks into the
|
|
1017
|
+
* click count that `detail` carries. `null` until a click has fired.
|
|
1018
|
+
*/
|
|
1019
|
+
_lastClick = null;
|
|
844
1020
|
_app = null;
|
|
845
1021
|
_loadProgress = 0;
|
|
846
1022
|
/**
|
|
@@ -875,12 +1051,12 @@
|
|
|
875
1051
|
*/
|
|
876
1052
|
constructor() {
|
|
877
1053
|
super();
|
|
878
|
-
// Track
|
|
879
|
-
// Registered once here rather than on every boot - the
|
|
880
|
-
// canvas, and a re-booted element must not stack a second set.
|
|
881
|
-
|
|
882
|
-
this.addEventListener(`${type}:connect`, () => this.
|
|
883
|
-
this.addEventListener(`${type}:disconnect`, () => this.
|
|
1054
|
+
// Track listeners for the synthesized events being added to and removed from descendant
|
|
1055
|
+
// entities. Registered once here rather than on every boot - the sync no-ops while there
|
|
1056
|
+
// is no canvas, and a re-booted element must not stack a second set.
|
|
1057
|
+
SYNTHESIZED_EVENTS.forEach((type) => {
|
|
1058
|
+
this.addEventListener(`${type}:connect`, () => this._syncCanvasListeners());
|
|
1059
|
+
this.addEventListener(`${type}:disconnect`, () => this._syncCanvasListeners());
|
|
884
1060
|
});
|
|
885
1061
|
}
|
|
886
1062
|
async connectedCallback() {
|
|
@@ -1198,15 +1374,13 @@
|
|
|
1198
1374
|
this._pointerHandlers.pointermove = listener(this._onPointerMove);
|
|
1199
1375
|
this._pointerHandlers.pointerdown = listener(this._onPointerDown);
|
|
1200
1376
|
this._pointerHandlers.pointerup = listener(this._onPointerUp);
|
|
1201
|
-
|
|
1202
|
-
|
|
1377
|
+
this._pointerHandlers.pointercancel = (event) => {
|
|
1378
|
+
this._downPicks.delete(event.pointerId);
|
|
1379
|
+
};
|
|
1380
|
+
// Attach canvas listeners for element listeners registered before this boot (e.g.
|
|
1381
|
+
// handlers created from inline attributes when their elements were first upgraded, or
|
|
1203
1382
|
// listeners carried over from before a re-boot)
|
|
1204
|
-
|
|
1205
|
-
const anyListeners = Array.from(this.querySelectorAll('pc-entity, pc-model, pc-node')).some((entity) => entity._hasListeners(type));
|
|
1206
|
-
if (anyListeners) {
|
|
1207
|
-
this._onPointerListenerAdded(type);
|
|
1208
|
-
}
|
|
1209
|
-
});
|
|
1383
|
+
this._syncCanvasListeners();
|
|
1210
1384
|
}
|
|
1211
1385
|
_pickerDestroy() {
|
|
1212
1386
|
if (this._canvas) {
|
|
@@ -1221,15 +1395,12 @@
|
|
|
1221
1395
|
this._pointerHandlers = {
|
|
1222
1396
|
pointermove: null,
|
|
1223
1397
|
pointerdown: null,
|
|
1224
|
-
pointerup: null
|
|
1225
|
-
|
|
1226
|
-
this._hasPointerListeners = {
|
|
1227
|
-
pointerenter: false,
|
|
1228
|
-
pointerleave: false,
|
|
1229
|
-
pointerdown: false,
|
|
1230
|
-
pointerup: false,
|
|
1231
|
-
pointermove: false
|
|
1398
|
+
pointerup: null,
|
|
1399
|
+
pointercancel: null
|
|
1232
1400
|
};
|
|
1401
|
+
this._downPicks.clear();
|
|
1402
|
+
this._clickListened = false;
|
|
1403
|
+
this._lastClick = null;
|
|
1233
1404
|
}
|
|
1234
1405
|
/**
|
|
1235
1406
|
* Registers the element that fronts an entity. Called by EntityElement when it creates its
|
|
@@ -1435,7 +1606,15 @@
|
|
|
1435
1606
|
async _onPointerDown(event) {
|
|
1436
1607
|
if (!this._picker || !this.app)
|
|
1437
1608
|
return;
|
|
1438
|
-
const
|
|
1609
|
+
const pick = this._pickNode(event);
|
|
1610
|
+
// A click concludes on the matching pointerup, which needs to know what the press
|
|
1611
|
+
// picked. Primary button only - the only button a click can conclude from - and only
|
|
1612
|
+
// while click is listened for, since it is the click mapping that keeps the pointerup
|
|
1613
|
+
// and pointercancel listeners attached to clean the entry up again.
|
|
1614
|
+
if (this._clickListened && event.button === 0) {
|
|
1615
|
+
this._downPicks.set(event.pointerId, pick);
|
|
1616
|
+
}
|
|
1617
|
+
const node = await pick;
|
|
1439
1618
|
if (!this._picker)
|
|
1440
1619
|
return; // the element disconnected while the pick was in flight
|
|
1441
1620
|
const entityElement = this._elementWithListener(node, 'pointerdown');
|
|
@@ -1446,6 +1625,10 @@
|
|
|
1446
1625
|
async _onPointerUp(event) {
|
|
1447
1626
|
if (!this._picker || !this.app)
|
|
1448
1627
|
return;
|
|
1628
|
+
// The press pick this release may conclude as a click. Claimed synchronously, so the
|
|
1629
|
+
// entry is gone before any other event for this pointer can be handled.
|
|
1630
|
+
const downPick = this._downPicks.get(event.pointerId);
|
|
1631
|
+
this._downPicks.delete(event.pointerId);
|
|
1449
1632
|
const node = await this._pickNode(event);
|
|
1450
1633
|
if (!this._picker)
|
|
1451
1634
|
return; // the element disconnected while the pick was in flight
|
|
@@ -1453,30 +1636,60 @@
|
|
|
1453
1636
|
if (entityElement) {
|
|
1454
1637
|
entityElement.dispatchEvent(new PointerEvent('pointerup', event));
|
|
1455
1638
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1639
|
+
// A click fires where the DOM fires it: at the nearest common inclusive ancestor of
|
|
1640
|
+
// what the press and the release picked, for the primary button only. The press pick
|
|
1641
|
+
// may still be in flight - a quick tap resolves in pick order, not event order.
|
|
1642
|
+
if (!downPick || event.button !== 0)
|
|
1643
|
+
return;
|
|
1644
|
+
const downNode = await downPick;
|
|
1645
|
+
if (!this._picker)
|
|
1646
|
+
return;
|
|
1647
|
+
const clickElement = this._elementWithListener(commonAncestor(downNode, node), 'click');
|
|
1648
|
+
if (clickElement) {
|
|
1649
|
+
const click = new PointerEvent('click', event);
|
|
1650
|
+
// The init above copied pointerup's `detail`, which the Pointer Events spec fixes
|
|
1651
|
+
// at 0 - but click is exempt: its detail is the click count, chained here as the
|
|
1652
|
+
// platform chains it (same target, within the double-click window). Overridden
|
|
1653
|
+
// with defineProperty because an event instance used as an init dict cannot have
|
|
1654
|
+
// single fields replaced.
|
|
1655
|
+
const time = performance.now();
|
|
1656
|
+
const last = this._lastClick;
|
|
1657
|
+
const count = last && last.element === clickElement && time - last.time <= CLICK_CHAIN_MS ? last.count + 1 : 1;
|
|
1658
|
+
this._lastClick = { element: clickElement, time, count };
|
|
1659
|
+
Object.defineProperty(click, 'detail', { value: count });
|
|
1660
|
+
clickElement.dispatchEvent(click);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Attaches exactly the canvas listeners the tree's current element listeners need, and
|
|
1665
|
+
* detaches the rest. Recomputed whenever a listener connects or disconnects anywhere under
|
|
1666
|
+
* this element: several synthesized types can need the same canvas listener (enter, leave
|
|
1667
|
+
* and move all ride the move pick; click rides the down/up pair), so one type's removal
|
|
1668
|
+
* must not detach a listener another type still uses. Re-attaching an attached listener is
|
|
1669
|
+
* a no-op by EventTarget semantics, so no attach state is kept.
|
|
1670
|
+
*/
|
|
1671
|
+
_syncCanvasListeners() {
|
|
1672
|
+
const canvas = this._canvas;
|
|
1673
|
+
if (!canvas)
|
|
1674
|
+
return; // not booted yet: _pickerCreate syncs once the handlers exist
|
|
1675
|
+
const elements = Array.from(this.querySelectorAll('pc-entity, pc-model, pc-node'));
|
|
1676
|
+
const needed = new Set();
|
|
1677
|
+
for (const type of SYNTHESIZED_EVENTS) {
|
|
1678
|
+
if (elements.some((element) => element._hasListeners(type))) {
|
|
1679
|
+
canvasEventsFor[type].forEach((canvasType) => needed.add(canvasType));
|
|
1466
1680
|
}
|
|
1467
1681
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
? this._pointerHandlers.pointermove
|
|
1475
|
-
: this._pointerHandlers[type];
|
|
1476
|
-
if (handler) {
|
|
1477
|
-
this._canvas.removeEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler);
|
|
1682
|
+
this._clickListened = elements.some((element) => element._hasListeners('click'));
|
|
1683
|
+
Object.entries(this._pointerHandlers).forEach(([canvasType, handler]) => {
|
|
1684
|
+
if (!handler)
|
|
1685
|
+
return;
|
|
1686
|
+
if (needed.has(canvasType)) {
|
|
1687
|
+
canvas.addEventListener(canvasType, handler);
|
|
1478
1688
|
}
|
|
1479
|
-
|
|
1689
|
+
else {
|
|
1690
|
+
canvas.removeEventListener(canvasType, handler);
|
|
1691
|
+
}
|
|
1692
|
+
});
|
|
1480
1693
|
}
|
|
1481
1694
|
/**
|
|
1482
1695
|
* Warns that a graphics option was written too late to have any effect. These options are read
|
|
@@ -1645,126 +1858,6 @@
|
|
|
1645
1858
|
}
|
|
1646
1859
|
customElements.define('pc-app', AppElement);
|
|
1647
1860
|
|
|
1648
|
-
/**
|
|
1649
|
-
* The attribute names of the inline `onpointer*` event handlers, shared by every element that
|
|
1650
|
-
* fronts an engine entity. Spread into `observedAttributes` by subclasses.
|
|
1651
|
-
* @internal
|
|
1652
|
-
*/
|
|
1653
|
-
const POINTER_ATTRIBUTES = [
|
|
1654
|
-
'onpointerenter',
|
|
1655
|
-
'onpointerleave',
|
|
1656
|
-
'onpointerdown',
|
|
1657
|
-
'onpointerup',
|
|
1658
|
-
'onpointermove'
|
|
1659
|
-
];
|
|
1660
|
-
/**
|
|
1661
|
-
* The base class for elements that front an engine {@link Entity}: `<pc-entity>` and
|
|
1662
|
-
* `<pc-model>`, which create one, and `<pc-node>`, which binds to one inside a model's
|
|
1663
|
-
* instantiated hierarchy. It carries what all of them need — the `entity` contract, registration
|
|
1664
|
-
* with the owning application (which joins picked scene nodes back to elements by identity,
|
|
1665
|
-
* never by name), and the pointer listener bookkeeping that lets the application lazily attach
|
|
1666
|
-
* its canvas handlers.
|
|
1667
|
-
*/
|
|
1668
|
-
class EntityBaseElement extends AsyncElement {
|
|
1669
|
-
_entity = null;
|
|
1670
|
-
/**
|
|
1671
|
-
* The application element this entity is registered with, cached at registration time so the
|
|
1672
|
-
* entity can be unregistered even once this element has left the DOM.
|
|
1673
|
-
*/
|
|
1674
|
-
_appElement = null;
|
|
1675
|
-
/**
|
|
1676
|
-
* The pointer event listeners for the entity.
|
|
1677
|
-
*/
|
|
1678
|
-
_listeners = {};
|
|
1679
|
-
/**
|
|
1680
|
-
* The event types for which an inline `onpointer*` attribute is currently present.
|
|
1681
|
-
*/
|
|
1682
|
-
_inlineHandlerTypes = new Set();
|
|
1683
|
-
/**
|
|
1684
|
-
* The PlayCanvas entity instance. `null` until the element is ready, and again once the
|
|
1685
|
-
* entity is gone — await {@link whenReady} or the element's `ready()` promise before
|
|
1686
|
-
* accessing it.
|
|
1687
|
-
* @returns The entity instance, or `null`.
|
|
1688
|
-
*/
|
|
1689
|
-
get entity() {
|
|
1690
|
-
return this._entity;
|
|
1691
|
-
}
|
|
1692
|
-
/**
|
|
1693
|
-
* Registers `entity` as this element's backing entity with the owning application, which
|
|
1694
|
-
* joins engine nodes back to elements by identity (never by name).
|
|
1695
|
-
*
|
|
1696
|
-
* @param entity - The entity to register.
|
|
1697
|
-
*/
|
|
1698
|
-
_registerEntity(entity) {
|
|
1699
|
-
this._appElement = this.closestApp;
|
|
1700
|
-
this._appElement?._registerEntityElement(entity, this);
|
|
1701
|
-
}
|
|
1702
|
-
/**
|
|
1703
|
-
* Removes the registration for `entity`.
|
|
1704
|
-
*
|
|
1705
|
-
* @param entity - The entity to unregister.
|
|
1706
|
-
*/
|
|
1707
|
-
_unregisterEntity(entity) {
|
|
1708
|
-
this._appElement?._unregisterEntityElement(entity);
|
|
1709
|
-
this._appElement = null;
|
|
1710
|
-
}
|
|
1711
|
-
/**
|
|
1712
|
-
* Tracks whether an inline `onpointer*` attribute is present. The browser itself compiles and
|
|
1713
|
-
* runs these attributes — they are standard `GlobalEventHandlers`, so setting one replaces
|
|
1714
|
-
* the previous handler and removing it removes the handler, exactly like `onclick` on any
|
|
1715
|
-
* HTML element. But because they bypass {@link EventTarget.addEventListener}, the connect/disconnect
|
|
1716
|
-
* bookkeeping that lets the application lazily attach its canvas pointer handlers must be
|
|
1717
|
-
* kept in sync here.
|
|
1718
|
-
*
|
|
1719
|
-
* @param name - The attribute name (e.g. 'onpointerdown').
|
|
1720
|
-
* @param value - The attribute value, or `null` when the attribute has been removed.
|
|
1721
|
-
*/
|
|
1722
|
-
_updateInlineHandler(name, value) {
|
|
1723
|
-
const type = name.substring(2);
|
|
1724
|
-
const had = this._inlineHandlerTypes.has(type);
|
|
1725
|
-
const has = value !== null;
|
|
1726
|
-
if (has && !had) {
|
|
1727
|
-
this._inlineHandlerTypes.add(type);
|
|
1728
|
-
this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
|
|
1729
|
-
}
|
|
1730
|
-
else if (!has && had) {
|
|
1731
|
-
this._inlineHandlerTypes.delete(type);
|
|
1732
|
-
this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
addEventListener(type, listener, options) {
|
|
1736
|
-
if (!this._listeners[type]) {
|
|
1737
|
-
this._listeners[type] = [];
|
|
1738
|
-
}
|
|
1739
|
-
this._listeners[type].push(listener);
|
|
1740
|
-
super.addEventListener(type, listener, options);
|
|
1741
|
-
if (type.startsWith('pointer')) {
|
|
1742
|
-
this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
removeEventListener(type, listener, options) {
|
|
1746
|
-
if (this._listeners[type]) {
|
|
1747
|
-
this._listeners[type] = this._listeners[type].filter((l) => l !== listener);
|
|
1748
|
-
}
|
|
1749
|
-
super.removeEventListener(type, listener, options);
|
|
1750
|
-
if (type.startsWith('pointer')) {
|
|
1751
|
-
this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
/**
|
|
1755
|
-
* Whether the element has a listener for an event type, registered either with
|
|
1756
|
-
* {@link EventTarget.addEventListener} or with the matching inline `onpointer*` attribute. Read by the
|
|
1757
|
-
* containing `<pc-app>` element to gate pointer event synthesis.
|
|
1758
|
-
*
|
|
1759
|
-
* @param type - The event type.
|
|
1760
|
-
* @returns Whether a listener is registered.
|
|
1761
|
-
* @internal
|
|
1762
|
-
*/
|
|
1763
|
-
_hasListeners(type) {
|
|
1764
|
-
return Boolean(this._listeners[type]?.length) || this._inlineHandlerTypes.has(type);
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
1861
|
/**
|
|
1769
1862
|
* Creates and parents the entities of every descendant entity-owning element of `root`, in two
|
|
1770
1863
|
* passes so that no parent's existence depends on document order. Called wherever a subtree could
|
|
@@ -2032,8 +2125,8 @@
|
|
|
2032
2125
|
*
|
|
2033
2126
|
* The pointer events below are dispatched by the containing `<pc-app>` element when the pointer
|
|
2034
2127
|
* intersects this entity's geometry. They are only generated while the entity has a listener for
|
|
2035
|
-
* them, registered either with {@link EventTarget.addEventListener} or with the matching inline
|
|
2036
|
-
* attribute.
|
|
2128
|
+
* them, registered either with {@link EventTarget.addEventListener} or with the matching inline
|
|
2129
|
+
* attribute (`onpointerdown`, `onclick`, ...).
|
|
2037
2130
|
*
|
|
2038
2131
|
* @elementSummary The `<pc-entity>` element creates an entity: a named, transformable node of the
|
|
2039
2132
|
* scene hierarchy, and the host for component elements such as `<pc-camera>`, `<pc-light>` and
|
|
@@ -2053,11 +2146,17 @@
|
|
|
2053
2146
|
* entity.
|
|
2054
2147
|
* @attribute {string} onpointerup - Script to run when a pointer button is released over the
|
|
2055
2148
|
* entity.
|
|
2149
|
+
* @attribute {string} onclick - Script to run when the entity is clicked: a primary pointer
|
|
2150
|
+
* button pressed and then released over it.
|
|
2056
2151
|
* @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the entity.
|
|
2057
2152
|
* @fires {PointerEvent} pointerleave - Fired when the pointer moves off the entity.
|
|
2058
2153
|
* @fires {PointerEvent} pointermove - Fired when the pointer moves over the entity.
|
|
2059
2154
|
* @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the entity.
|
|
2060
2155
|
* @fires {PointerEvent} pointerup - Fired when a pointer button is released over the entity.
|
|
2156
|
+
* @fires {PointerEvent} click - Fired when a primary pointer button is pressed and then released
|
|
2157
|
+
* over the entity. A press and release that picked different entities fires on their nearest
|
|
2158
|
+
* common ancestor instead, as in the DOM. `detail` carries the click count, so a double click
|
|
2159
|
+
* arrives as a click whose `detail` is 2.
|
|
2061
2160
|
*/
|
|
2062
2161
|
class EntityElement extends EntityOwnerElement {
|
|
2063
2162
|
connectedCallback() {
|
|
@@ -2092,7 +2191,7 @@
|
|
|
2092
2191
|
this._entity?.destroy();
|
|
2093
2192
|
}
|
|
2094
2193
|
static get observedAttributes() {
|
|
2095
|
-
return ['enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...
|
|
2194
|
+
return ['enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...EVENT_ATTRIBUTES];
|
|
2096
2195
|
}
|
|
2097
2196
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
2098
2197
|
switch (name) {
|
|
@@ -2119,6 +2218,7 @@
|
|
|
2119
2218
|
case 'onpointerdown':
|
|
2120
2219
|
case 'onpointerup':
|
|
2121
2220
|
case 'onpointermove':
|
|
2221
|
+
case 'onclick':
|
|
2122
2222
|
this._updateInlineHandler(name, newValue);
|
|
2123
2223
|
break;
|
|
2124
2224
|
}
|
|
@@ -3068,11 +3168,17 @@
|
|
|
3068
3168
|
* model.
|
|
3069
3169
|
* @attribute {string} onpointerup - Script to run when a pointer button is released over the
|
|
3070
3170
|
* model.
|
|
3171
|
+
* @attribute {string} onclick - Script to run when the model is clicked: a primary pointer
|
|
3172
|
+
* button pressed and then released over it.
|
|
3071
3173
|
* @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the model.
|
|
3072
3174
|
* @fires {PointerEvent} pointerleave - Fired when the pointer moves off the model.
|
|
3073
3175
|
* @fires {PointerEvent} pointermove - Fired when the pointer moves over the model.
|
|
3074
3176
|
* @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the model.
|
|
3075
3177
|
* @fires {PointerEvent} pointerup - Fired when a pointer button is released over the model.
|
|
3178
|
+
* @fires {PointerEvent} click - Fired when a primary pointer button is pressed and then released
|
|
3179
|
+
* over the model. A press and release that picked different entities fires on their nearest
|
|
3180
|
+
* common ancestor instead, as in the DOM. `detail` carries the click count, so a double click
|
|
3181
|
+
* arrives as a click whose `detail` is 2.
|
|
3076
3182
|
* @fires {Event} load - Fired each time a container asset finishes instantiating, including
|
|
3077
3183
|
* re-instantiation after `asset` changes. Does not bubble — listen on this element, or use a
|
|
3078
3184
|
* capture-phase listener on an ancestor.
|
|
@@ -3321,7 +3427,7 @@
|
|
|
3321
3427
|
return this._asset;
|
|
3322
3428
|
}
|
|
3323
3429
|
static get observedAttributes() {
|
|
3324
|
-
return ['asset', 'enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...
|
|
3430
|
+
return ['asset', 'enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...EVENT_ATTRIBUTES];
|
|
3325
3431
|
}
|
|
3326
3432
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
3327
3433
|
switch (name) {
|
|
@@ -3351,6 +3457,7 @@
|
|
|
3351
3457
|
case 'onpointerdown':
|
|
3352
3458
|
case 'onpointerup':
|
|
3353
3459
|
case 'onpointermove':
|
|
3460
|
+
case 'onclick':
|
|
3354
3461
|
this._updateInlineHandler(name, newValue);
|
|
3355
3462
|
break;
|
|
3356
3463
|
}
|
|
@@ -4214,8 +4321,6 @@
|
|
|
4214
4321
|
* @elementSummary The `<pc-anim-clip>` element declares one named animation clip on its parent
|
|
4215
4322
|
* `<pc-anim>`, taken from the `asset` it names or, without one, from the enclosing `<pc-model>`'s
|
|
4216
4323
|
* own animations. Must be a direct child of `<pc-anim>`.
|
|
4217
|
-
*
|
|
4218
|
-
* @category Components
|
|
4219
4324
|
*/
|
|
4220
4325
|
class AnimClipElement extends AsyncElement {
|
|
4221
4326
|
/**
|
|
@@ -13344,11 +13449,17 @@
|
|
|
13344
13449
|
* node.
|
|
13345
13450
|
* @attribute {string} onpointerup - Script to run when a pointer button is released over the
|
|
13346
13451
|
* node.
|
|
13452
|
+
* @attribute {string} onclick - Script to run when the node is clicked: a primary pointer
|
|
13453
|
+
* button pressed and then released over it.
|
|
13347
13454
|
* @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the node.
|
|
13348
13455
|
* @fires {PointerEvent} pointerleave - Fired when the pointer moves off the node.
|
|
13349
13456
|
* @fires {PointerEvent} pointermove - Fired when the pointer moves over the node.
|
|
13350
13457
|
* @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the node.
|
|
13351
13458
|
* @fires {PointerEvent} pointerup - Fired when a pointer button is released over the node.
|
|
13459
|
+
* @fires {PointerEvent} click - Fired when a primary pointer button is pressed and then released
|
|
13460
|
+
* over the node. A press and release that picked different entities fires on their nearest
|
|
13461
|
+
* common ancestor instead, as in the DOM. `detail` carries the click count, so a double click
|
|
13462
|
+
* arrives as a click whose `detail` is 2.
|
|
13352
13463
|
*/
|
|
13353
13464
|
class NodeElement extends EntityBaseElement {
|
|
13354
13465
|
_name = '';
|
|
@@ -13998,7 +14109,7 @@
|
|
|
13998
14109
|
'rotation',
|
|
13999
14110
|
'scale',
|
|
14000
14111
|
'tags',
|
|
14001
|
-
...
|
|
14112
|
+
...EVENT_ATTRIBUTES
|
|
14002
14113
|
];
|
|
14003
14114
|
}
|
|
14004
14115
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
@@ -14048,6 +14159,7 @@
|
|
|
14048
14159
|
case 'onpointerdown':
|
|
14049
14160
|
case 'onpointerup':
|
|
14050
14161
|
case 'onpointermove':
|
|
14162
|
+
case 'onclick':
|
|
14051
14163
|
this._updateInlineHandler(name, newValue);
|
|
14052
14164
|
break;
|
|
14053
14165
|
}
|
|
@@ -14062,9 +14174,14 @@
|
|
|
14062
14174
|
* {@link HTMLElement} interface.
|
|
14063
14175
|
*
|
|
14064
14176
|
* @elementSummary The `<pc-scene>` element holds the entity hierarchy the application renders,
|
|
14065
|
-
* along with the scene-wide fog and gravity settings. Must be a direct child of
|
|
14177
|
+
* along with the scene-wide fog, exposure and gravity settings. Must be a direct child of
|
|
14178
|
+
* `<pc-app>`.
|
|
14066
14179
|
*/
|
|
14067
14180
|
class SceneElement extends AsyncElement {
|
|
14181
|
+
/**
|
|
14182
|
+
* The exposure of the scene.
|
|
14183
|
+
*/
|
|
14184
|
+
_exposure = 1;
|
|
14068
14185
|
/**
|
|
14069
14186
|
* The fog type of the scene.
|
|
14070
14187
|
*/
|
|
@@ -14132,6 +14249,7 @@
|
|
|
14132
14249
|
}
|
|
14133
14250
|
_updateSceneSettings() {
|
|
14134
14251
|
if (this._scene) {
|
|
14252
|
+
this._scene.exposure = this._exposure;
|
|
14135
14253
|
this._scene.fog.type = this._fog;
|
|
14136
14254
|
this._scene.fog.color = this._fogColor;
|
|
14137
14255
|
this._scene.fog.density = this._fogDensity;
|
|
@@ -14150,6 +14268,24 @@
|
|
|
14150
14268
|
_applyGravity(value) {
|
|
14151
14269
|
this.closestApp?.app?.systems.rigidbody?.gravity.copy(value);
|
|
14152
14270
|
}
|
|
14271
|
+
/**
|
|
14272
|
+
* Sets the exposure of the scene, which tweaks the overall brightness of the rendered image.
|
|
14273
|
+
* Ignored if the scene is using physical units. Defaults to 1.
|
|
14274
|
+
* @param value - The exposure.
|
|
14275
|
+
*/
|
|
14276
|
+
set exposure(value) {
|
|
14277
|
+
this._exposure = value;
|
|
14278
|
+
if (this.scene) {
|
|
14279
|
+
this.scene.exposure = value;
|
|
14280
|
+
}
|
|
14281
|
+
}
|
|
14282
|
+
/**
|
|
14283
|
+
* Gets the exposure of the scene.
|
|
14284
|
+
* @returns The exposure.
|
|
14285
|
+
*/
|
|
14286
|
+
get exposure() {
|
|
14287
|
+
return this._exposure;
|
|
14288
|
+
}
|
|
14153
14289
|
/**
|
|
14154
14290
|
* Sets the fog type of the scene. Can be `none`, `linear`, `exp` or `exp2`. Defaults to
|
|
14155
14291
|
* `none`.
|
|
@@ -14254,10 +14390,13 @@
|
|
|
14254
14390
|
return this._gravity;
|
|
14255
14391
|
}
|
|
14256
14392
|
static get observedAttributes() {
|
|
14257
|
-
return ['fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end', 'gravity'];
|
|
14393
|
+
return ['exposure', 'fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end', 'gravity'];
|
|
14258
14394
|
}
|
|
14259
14395
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
14260
14396
|
switch (name) {
|
|
14397
|
+
case 'exposure':
|
|
14398
|
+
this.exposure = parseNumber(newValue, 1, name);
|
|
14399
|
+
break;
|
|
14261
14400
|
case 'fog':
|
|
14262
14401
|
this.fog = parseEnum(newValue, ['none', 'linear', 'exp', 'exp2'], 'none', name);
|
|
14263
14402
|
break;
|