@playcanvas/web-components 0.11.0 → 0.11.1
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/README.md +1 -1
- package/dist/app.d.ts +18 -38
- package/dist/asset.d.ts +8 -7
- package/dist/async-element.d.ts +21 -5
- package/dist/components/button-component.d.ts +3 -7
- package/dist/components/camera-component.d.ts +3 -7
- package/dist/components/collision-component.d.ts +3 -7
- package/dist/components/component.d.ts +22 -4
- package/dist/components/element-component.d.ts +4 -8
- package/dist/components/gsplat-component.d.ts +2 -7
- package/dist/components/layoutchild-component.d.ts +2 -7
- package/dist/components/layoutgroup-component.d.ts +3 -7
- package/dist/components/light-component.d.ts +3 -7
- package/dist/components/listener-component.d.ts +1 -6
- package/dist/components/particlesystem-component.d.ts +2 -7
- package/dist/components/render-component.d.ts +2 -7
- package/dist/components/rigidbody-component.d.ts +3 -7
- package/dist/components/screen-component.d.ts +3 -7
- package/dist/components/script-component.d.ts +8 -20
- package/dist/components/script.d.ts +2 -22
- package/dist/components/scrollbar-component.d.ts +2 -7
- package/dist/components/scrollview-component.d.ts +3 -7
- package/dist/components/sound-component.d.ts +2 -7
- package/dist/components/sound-slot.d.ts +8 -6
- package/dist/custom-elements.json +5049 -10696
- package/dist/entity.d.ts +5 -11
- package/dist/index.d.ts +37 -0
- package/dist/material.d.ts +12 -12
- package/dist/model.d.ts +21 -5
- package/dist/module.d.ts +0 -6
- package/dist/parse.d.ts +2 -1
- package/dist/pwc.cjs +496 -136
- package/dist/pwc.cjs.map +1 -1
- package/dist/pwc.js +496 -136
- 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 +496 -136
- package/dist/pwc.mjs.map +1 -1
- package/dist/scene.d.ts +4 -7
- package/dist/sky.d.ts +13 -5
- package/dist/vscode.html-custom-data.json +26 -26
- package/dist/web-types.json +545 -536
- package/package.json +8 -7
- package/src/app.ts +142 -70
- package/src/asset.ts +34 -28
- package/src/async-element.ts +34 -8
- package/src/components/button-component.ts +5 -9
- package/src/components/camera-component.ts +24 -10
- package/src/components/collision-component.ts +26 -15
- package/src/components/component.ts +58 -8
- package/src/components/element-component.ts +26 -30
- package/src/components/gsplat-component.ts +4 -9
- package/src/components/layoutchild-component.ts +4 -9
- package/src/components/layoutgroup-component.ts +14 -9
- package/src/components/light-component.ts +42 -12
- package/src/components/listener-component.ts +1 -7
- package/src/components/particlesystem-component.ts +7 -15
- package/src/components/render-component.ts +5 -10
- package/src/components/rigidbody-component.ts +23 -16
- package/src/components/screen-component.ts +5 -9
- package/src/components/script-component.ts +108 -46
- package/src/components/script.ts +38 -33
- package/src/components/scrollbar-component.ts +6 -16
- package/src/components/scrollview-component.ts +16 -11
- package/src/components/sound-component.ts +10 -15
- package/src/components/sound-slot.ts +30 -20
- package/src/entity.ts +42 -19
- package/src/index.ts +45 -1
- package/src/loading-bar.ts +8 -8
- package/src/material.ts +63 -37
- package/src/model.ts +69 -14
- package/src/module.ts +8 -7
- package/src/parse.ts +62 -17
- package/src/scene.ts +12 -9
- package/src/sky.ts +50 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcanvas/web-components",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"author": "PlayCanvas <support@playcanvas.com>",
|
|
5
5
|
"homepage": "https://playcanvas.com",
|
|
6
6
|
"description": "Web Components for the PlayCanvas Engine",
|
|
@@ -51,7 +51,10 @@
|
|
|
51
51
|
"cem": "cem analyze && node utils/cem/validate.mjs",
|
|
52
52
|
"dev": "concurrently \"npm run watch\" \"npm run serve\"",
|
|
53
53
|
"docs": "typedoc",
|
|
54
|
+
"fmt": "prettier --check eslint.config.mjs prettier.config.mjs examples/js examples/assets/scripts src test vitest.config.ts",
|
|
55
|
+
"fmt:fix": "prettier --write eslint.config.mjs prettier.config.mjs examples/js examples/assets/scripts src test vitest.config.ts",
|
|
54
56
|
"lint": "eslint examples/js examples/assets/scripts src test vitest.config.ts",
|
|
57
|
+
"lint:fix": "eslint examples/js examples/assets/scripts src test vitest.config.ts --fix",
|
|
55
58
|
"serve": "serve",
|
|
56
59
|
"test": "vitest run",
|
|
57
60
|
"test:watch": "vitest",
|
|
@@ -72,27 +75,25 @@
|
|
|
72
75
|
"devDependencies": {
|
|
73
76
|
"@custom-elements-manifest/analyzer": "0.11.0",
|
|
74
77
|
"@mediapipe/tasks-vision": "1.0.1",
|
|
75
|
-
"@playcanvas/eslint-config": "
|
|
78
|
+
"@playcanvas/eslint-config": "3.0.0-beta.8",
|
|
76
79
|
"@rollup/plugin-commonjs": "29.0.3",
|
|
77
80
|
"@rollup/plugin-node-resolve": "16.0.3",
|
|
78
81
|
"@rollup/plugin-terser": "1.0.0",
|
|
79
82
|
"@rollup/plugin-typescript": "12.3.0",
|
|
80
83
|
"@tweenjs/tween.js": "25.0.0",
|
|
81
|
-
"@typescript-eslint/eslint-plugin": "8.65.0",
|
|
82
|
-
"@typescript-eslint/parser": "8.65.0",
|
|
83
84
|
"@vitest/coverage-v8": "4.1.10",
|
|
84
85
|
"concurrently": "10.0.4",
|
|
85
86
|
"custom-element-jet-brains-integration": "1.7.0",
|
|
86
87
|
"custom-element-vs-code-integration": "1.5.0",
|
|
87
88
|
"earcut": "3.2.3",
|
|
88
89
|
"eslint": "9.39.5",
|
|
89
|
-
"eslint-import-resolver-typescript": "4.4.5",
|
|
90
90
|
"globals": "17.9.0",
|
|
91
91
|
"jsdom": "30.0.1",
|
|
92
|
-
"mediabunny": "1.52.
|
|
92
|
+
"mediabunny": "1.52.3",
|
|
93
93
|
"opentype.js": "2.0.0",
|
|
94
94
|
"playcanvas": "2.21.3",
|
|
95
|
-
"
|
|
95
|
+
"prettier": "3.9.6",
|
|
96
|
+
"publint": "0.3.23",
|
|
96
97
|
"rollup": "4.62.4",
|
|
97
98
|
"serve": "14.2.6",
|
|
98
99
|
"tslib": "2.8.1",
|
package/src/app.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
+
import type { CameraComponent, GraphNode, GSplatComponent, Entity } from 'playcanvas';
|
|
1
2
|
import {
|
|
2
3
|
AppBase,
|
|
3
4
|
AppOptions,
|
|
4
|
-
CameraComponent,
|
|
5
5
|
createGraphicsDevice,
|
|
6
6
|
ElementInput,
|
|
7
|
-
Entity,
|
|
8
7
|
FILLMODE_FILL_WINDOW,
|
|
9
|
-
GraphNode,
|
|
10
8
|
Keyboard,
|
|
11
9
|
Mouse,
|
|
12
10
|
Picker,
|
|
@@ -63,18 +61,20 @@ import {
|
|
|
63
61
|
SoundManager,
|
|
64
62
|
Lightmapper,
|
|
65
63
|
XrManager,
|
|
66
|
-
MeshInstance
|
|
67
|
-
GSplatComponent
|
|
64
|
+
MeshInstance
|
|
68
65
|
} from 'playcanvas';
|
|
69
66
|
|
|
70
|
-
import { AssetElement } from './asset';
|
|
67
|
+
import type { AssetElement } from './asset';
|
|
71
68
|
import { AsyncElement } from './async-element';
|
|
72
|
-
import { EntityElement } from './entity';
|
|
69
|
+
import type { EntityElement } from './entity';
|
|
73
70
|
import { LoadingBar } from './loading-bar';
|
|
74
|
-
import { MaterialElement } from './material';
|
|
75
|
-
import { ModuleElement } from './module';
|
|
71
|
+
import type { MaterialElement } from './material';
|
|
72
|
+
import type { ModuleElement } from './module';
|
|
76
73
|
import { parseBool, parseEnum, parseNumber } from './parse';
|
|
77
74
|
|
|
75
|
+
/** The pointer event types the application synthesizes on `<pc-entity>` elements via picking. */
|
|
76
|
+
const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'] as const;
|
|
77
|
+
|
|
78
78
|
/**
|
|
79
79
|
* The AppElement interface provides properties and methods for manipulating
|
|
80
80
|
* {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-app/ | `<pc-app>`} elements.
|
|
@@ -115,7 +115,21 @@ class AppElement extends AsyncElement {
|
|
|
115
115
|
|
|
116
116
|
private _bar: LoadingBar | null = null;
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Whether the application has created its initial entity hierarchy. Read by EntityElement to
|
|
120
|
+
* decide whether a newly connected element must create its entity itself or leave it to the
|
|
121
|
+
* boot sweep.
|
|
122
|
+
* @internal
|
|
123
|
+
*/
|
|
124
|
+
_hierarchyReady = false;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Incremented on every connect and disconnect. Boot captures the value on entry and abandons
|
|
128
|
+
* itself wherever it resumes from an await if the value has moved on — so a boot whose
|
|
129
|
+
* element was removed cannot complete against a torn-down element, and a boot whose element
|
|
130
|
+
* was removed and re-inserted (which starts a boot of its own) cannot race the newer one.
|
|
131
|
+
*/
|
|
132
|
+
private _bootGeneration = 0;
|
|
119
133
|
|
|
120
134
|
/**
|
|
121
135
|
* The elements backing this application's entities, keyed by the entity itself. Registered
|
|
@@ -126,7 +140,7 @@ class AppElement extends AsyncElement {
|
|
|
126
140
|
|
|
127
141
|
private _picker: Picker | null = null;
|
|
128
142
|
|
|
129
|
-
private _hasPointerListeners:
|
|
143
|
+
private _hasPointerListeners: Record<string, boolean> = {
|
|
130
144
|
pointerenter: false,
|
|
131
145
|
pointerleave: false,
|
|
132
146
|
pointerdown: false,
|
|
@@ -139,7 +153,7 @@ class AppElement extends AsyncElement {
|
|
|
139
153
|
// Identifies the newest in-flight hover pick, so out-of-order results can be discarded
|
|
140
154
|
private _pickToken = 0;
|
|
141
155
|
|
|
142
|
-
private _pointerHandlers:
|
|
156
|
+
private _pointerHandlers: Record<string, EventListener | null> = {
|
|
143
157
|
pointermove: null,
|
|
144
158
|
pointerdown: null,
|
|
145
159
|
pointerup: null
|
|
@@ -180,9 +194,19 @@ class AppElement extends AsyncElement {
|
|
|
180
194
|
|
|
181
195
|
// Bind methods to maintain 'this' context
|
|
182
196
|
this._onWindowResize = this._onWindowResize.bind(this);
|
|
197
|
+
|
|
198
|
+
// Track pointer listeners being added to and removed from descendant entities.
|
|
199
|
+
// Registered once here rather than on every boot - the handlers no-op while there is no
|
|
200
|
+
// canvas, and a re-booted element must not stack a second set.
|
|
201
|
+
pointerEventTypes.forEach((type) => {
|
|
202
|
+
this.addEventListener(`${type}:connect`, () => this._onPointerListenerAdded(type));
|
|
203
|
+
this.addEventListener(`${type}:disconnect`, () => this._onPointerListenerRemoved(type));
|
|
204
|
+
});
|
|
183
205
|
}
|
|
184
206
|
|
|
185
207
|
async connectedCallback() {
|
|
208
|
+
const generation = ++this._bootGeneration;
|
|
209
|
+
|
|
186
210
|
// Created before the first await, so the bar is visible while modules and the graphics
|
|
187
211
|
// device are created, and exists before any disconnect could need to clean it up
|
|
188
212
|
if (this._loadingBar && !this._bar) {
|
|
@@ -193,14 +217,20 @@ class AppElement extends AsyncElement {
|
|
|
193
217
|
const moduleElements = this.querySelectorAll<ModuleElement>(':scope > pc-module');
|
|
194
218
|
|
|
195
219
|
// Wait for all modules to load
|
|
196
|
-
await Promise.all(Array.from(moduleElements).map(module => module.
|
|
220
|
+
await Promise.all(Array.from(moduleElements).map((module) => module._getLoadPromise()));
|
|
221
|
+
|
|
222
|
+
// The element may have been removed while the modules loaded. Nothing beyond the loading
|
|
223
|
+
// bar exists yet, and disconnectedCallback has already destroyed that.
|
|
224
|
+
if (generation !== this._bootGeneration) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
197
227
|
|
|
198
228
|
// Create and append the canvas to the element
|
|
199
229
|
this._canvas = document.createElement('canvas');
|
|
200
230
|
this.appendChild(this._canvas);
|
|
201
231
|
|
|
202
232
|
// Configure device types based on backend selection
|
|
203
|
-
const backendToDeviceTypes:
|
|
233
|
+
const backendToDeviceTypes: Record<string, string[]> = {
|
|
204
234
|
webgpu: ['webgpu', 'webgl2'], // fallback to webgl2 if webgpu not available
|
|
205
235
|
webgl2: ['webgl2'],
|
|
206
236
|
null: ['null']
|
|
@@ -218,6 +248,14 @@ class AppElement extends AsyncElement {
|
|
|
218
248
|
stencil: this._stencilBuffer
|
|
219
249
|
});
|
|
220
250
|
|
|
251
|
+
// The element may have been removed while the device was created. disconnectedCallback
|
|
252
|
+
// has already cleaned up the canvas; the device was created inside the await, so it is
|
|
253
|
+
// this boot's to release.
|
|
254
|
+
if (generation !== this._bootGeneration) {
|
|
255
|
+
device.destroy();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
221
259
|
// Assigned rather than resolved to a number here: the engine caps against the live
|
|
222
260
|
// window.devicePixelRatio on every resize, so an uncapped Infinity keeps following the
|
|
223
261
|
// display when a window moves between monitors of differing density.
|
|
@@ -299,31 +337,47 @@ class AppElement extends AsyncElement {
|
|
|
299
337
|
|
|
300
338
|
// Get all pc-asset elements that are direct children of the pc-app element
|
|
301
339
|
const assetElements = this.querySelectorAll<AssetElement>(':scope > pc-asset');
|
|
302
|
-
Array.from(assetElements)
|
|
303
|
-
assetElement.
|
|
340
|
+
for (const assetElement of Array.from(assetElements)) {
|
|
341
|
+
assetElement._createAsset();
|
|
304
342
|
const asset = assetElement.asset;
|
|
305
343
|
if (asset) {
|
|
306
344
|
app.assets.add(asset);
|
|
345
|
+
|
|
346
|
+
// Adding a fileless asset (one built purely from data, such as a sprite)
|
|
347
|
+
// completes it synchronously, dispatching the element's load event - whose
|
|
348
|
+
// listeners may have removed this element. Stop before the next addition
|
|
349
|
+
// reaches the destroyed registry, and before orphan entities are created.
|
|
350
|
+
if (generation !== this._bootGeneration) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
307
353
|
}
|
|
308
|
-
}
|
|
354
|
+
}
|
|
309
355
|
|
|
310
356
|
// Get all pc-material elements that are direct children of the pc-app element
|
|
311
357
|
const materialElements = this.querySelectorAll<MaterialElement>(':scope > pc-material');
|
|
312
358
|
Array.from(materialElements).forEach((materialElement) => {
|
|
313
|
-
materialElement.
|
|
359
|
+
materialElement._createMaterial();
|
|
314
360
|
});
|
|
315
361
|
|
|
316
362
|
// Create all entities
|
|
317
363
|
const entityElements = this.querySelectorAll<EntityElement>('pc-entity');
|
|
318
364
|
Array.from(entityElements).forEach((entityElement) => {
|
|
319
|
-
entityElement.
|
|
365
|
+
entityElement._createEntity(app);
|
|
320
366
|
});
|
|
321
367
|
|
|
322
368
|
// Build hierarchy
|
|
323
369
|
entityElements.forEach((entityElement) => {
|
|
324
|
-
entityElement.
|
|
370
|
+
entityElement._buildHierarchy(app);
|
|
325
371
|
});
|
|
326
372
|
|
|
373
|
+
// Building the hierarchy dispatched each entity's ready event synchronously, and a
|
|
374
|
+
// listener may have removed the element. The sweep itself degrades safely - destroying
|
|
375
|
+
// the application nulls every element's entity, so the remaining builds no-op - but the
|
|
376
|
+
// teardown's reset must not be overwritten here.
|
|
377
|
+
if (generation !== this._bootGeneration) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
327
381
|
this._hierarchyReady = true;
|
|
328
382
|
|
|
329
383
|
// Forward the engine's preload lifecycle as DOM ProgressEvents on this element. The
|
|
@@ -343,8 +397,21 @@ class AppElement extends AsyncElement {
|
|
|
343
397
|
this._bar?.progress(0, total);
|
|
344
398
|
this.dispatchEvent(new ProgressEvent('progress', { lengthComputable: true, loaded: 0, total }));
|
|
345
399
|
|
|
400
|
+
// The progress dispatch above ran listeners synchronously, and one may have removed the
|
|
401
|
+
// element. The application is already destroyed - it must not be asked to preload.
|
|
402
|
+
if (generation !== this._bootGeneration) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
346
406
|
// Load assets before starting the application
|
|
347
407
|
app.preload(() => {
|
|
408
|
+
// The element may have been removed while assets loaded. The application is already
|
|
409
|
+
// destroyed, so it must not be started — and readiness must not be signaled for a
|
|
410
|
+
// boot that no longer owns the element.
|
|
411
|
+
if (generation !== this._bootGeneration) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
348
415
|
// Scope the counter to this preload pass, so a later app.preload() call by user code
|
|
349
416
|
// cannot push `loaded` past `total`
|
|
350
417
|
app.off('preload:progress', onPreloadProgress);
|
|
@@ -365,6 +432,10 @@ class AppElement extends AsyncElement {
|
|
|
365
432
|
}
|
|
366
433
|
|
|
367
434
|
disconnectedCallback() {
|
|
435
|
+
// Invalidate any boot still in flight, so it abandons itself when it next resumes
|
|
436
|
+
// instead of completing against a torn-down element.
|
|
437
|
+
this._bootGeneration++;
|
|
438
|
+
|
|
368
439
|
this._optionsLocked = false;
|
|
369
440
|
this._pickerDestroy();
|
|
370
441
|
|
|
@@ -379,6 +450,12 @@ class AppElement extends AsyncElement {
|
|
|
379
450
|
this._bar?.destroy();
|
|
380
451
|
this._bar = null;
|
|
381
452
|
|
|
453
|
+
// Return the element to its pre-boot state, so re-inserting it boots afresh: descendants
|
|
454
|
+
// must neither see a hierarchy that no longer exists nor resume against a readiness that
|
|
455
|
+
// no longer holds.
|
|
456
|
+
this._hierarchyReady = false;
|
|
457
|
+
this._resetReady();
|
|
458
|
+
|
|
382
459
|
// Remove event listeners
|
|
383
460
|
window.removeEventListener('resize', this._onWindowResize);
|
|
384
461
|
|
|
@@ -389,13 +466,13 @@ class AppElement extends AsyncElement {
|
|
|
389
466
|
}
|
|
390
467
|
}
|
|
391
468
|
|
|
392
|
-
_onWindowResize() {
|
|
469
|
+
private _onWindowResize() {
|
|
393
470
|
if (this.app) {
|
|
394
471
|
this.app.resizeCanvas();
|
|
395
472
|
}
|
|
396
473
|
}
|
|
397
474
|
|
|
398
|
-
_pickerCreate() {
|
|
475
|
+
private _pickerCreate() {
|
|
399
476
|
const { width, height } = this.app!.graphicsDevice;
|
|
400
477
|
this._picker = new Picker(this.app!, width, height);
|
|
401
478
|
|
|
@@ -412,22 +489,20 @@ class AppElement extends AsyncElement {
|
|
|
412
489
|
this._pointerHandlers.pointerdown = listener(this._onPointerDown);
|
|
413
490
|
this._pointerHandlers.pointerup = listener(this._onPointerUp);
|
|
414
491
|
|
|
415
|
-
//
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const anyListeners = Array.from(this.querySelectorAll<EntityElement>('pc-entity'))
|
|
423
|
-
.some(entity => entity.hasListeners(type));
|
|
492
|
+
// Attach canvas handlers for listeners registered before this boot (e.g. handlers
|
|
493
|
+
// created from onpointer* attributes when their elements were first upgraded, or
|
|
494
|
+
// listeners carried over from before a re-boot)
|
|
495
|
+
pointerEventTypes.forEach((type) => {
|
|
496
|
+
const anyListeners = Array.from(this.querySelectorAll<EntityElement>('pc-entity')).some((entity) =>
|
|
497
|
+
entity._hasListeners(type)
|
|
498
|
+
);
|
|
424
499
|
if (anyListeners) {
|
|
425
500
|
this._onPointerListenerAdded(type);
|
|
426
501
|
}
|
|
427
502
|
});
|
|
428
503
|
}
|
|
429
504
|
|
|
430
|
-
_pickerDestroy() {
|
|
505
|
+
private _pickerDestroy() {
|
|
431
506
|
if (this._canvas) {
|
|
432
507
|
Object.entries(this._pointerHandlers).forEach(([type, handler]) => {
|
|
433
508
|
if (handler) {
|
|
@@ -437,6 +512,7 @@ class AppElement extends AsyncElement {
|
|
|
437
512
|
}
|
|
438
513
|
|
|
439
514
|
this._picker = null;
|
|
515
|
+
this._hoveredEntity = null;
|
|
440
516
|
this._pointerHandlers = {
|
|
441
517
|
pointermove: null,
|
|
442
518
|
pointerdown: null,
|
|
@@ -457,7 +533,7 @@ class AppElement extends AsyncElement {
|
|
|
457
533
|
*
|
|
458
534
|
* @param entity - The entity.
|
|
459
535
|
* @param element - The element that created it.
|
|
460
|
-
* @
|
|
536
|
+
* @internal
|
|
461
537
|
*/
|
|
462
538
|
_registerEntityElement(entity: Entity, element: EntityElement) {
|
|
463
539
|
this._entityElements.set(entity, element);
|
|
@@ -467,7 +543,7 @@ class AppElement extends AsyncElement {
|
|
|
467
543
|
* Removes the registration for a destroyed entity. Called by EntityElement.
|
|
468
544
|
*
|
|
469
545
|
* @param entity - The entity.
|
|
470
|
-
* @
|
|
546
|
+
* @internal
|
|
471
547
|
*/
|
|
472
548
|
_unregisterEntityElement(entity: Entity) {
|
|
473
549
|
this._entityElements.delete(entity);
|
|
@@ -516,7 +592,7 @@ class AppElement extends AsyncElement {
|
|
|
516
592
|
private _elementWithListener(node: GraphNode | null, type: string): EntityElement | null {
|
|
517
593
|
while (node !== null) {
|
|
518
594
|
const element = this._entityElements.get(node);
|
|
519
|
-
if (element?.
|
|
595
|
+
if (element?._hasListeners(type)) {
|
|
520
596
|
return element;
|
|
521
597
|
}
|
|
522
598
|
node = node.parent;
|
|
@@ -525,7 +601,7 @@ class AppElement extends AsyncElement {
|
|
|
525
601
|
}
|
|
526
602
|
|
|
527
603
|
// New helper to convert CSS coordinates to canvas (picker) coordinates
|
|
528
|
-
private _getPickerCoordinates(event: PointerEvent): { x: number
|
|
604
|
+
private _getPickerCoordinates(event: PointerEvent): { x: number; y: number } {
|
|
529
605
|
// Get the canvas' bounding rectangle in CSS pixels.
|
|
530
606
|
const canvasRect = this._canvas!.getBoundingClientRect();
|
|
531
607
|
// Compute scale factors based on canvas actual resolution vs. its CSS display size.
|
|
@@ -562,7 +638,7 @@ class AppElement extends AsyncElement {
|
|
|
562
638
|
return item instanceof MeshInstance ? item.node : (item as GSplatComponent).entity;
|
|
563
639
|
}
|
|
564
640
|
|
|
565
|
-
async _onPointerMove(event: PointerEvent) {
|
|
641
|
+
private async _onPointerMove(event: PointerEvent) {
|
|
566
642
|
if (!this._picker || !this.app) return;
|
|
567
643
|
|
|
568
644
|
// Moves arrive faster than a pick resolves, so results can land out of order. Only the
|
|
@@ -578,10 +654,10 @@ class AppElement extends AsyncElement {
|
|
|
578
654
|
|
|
579
655
|
// Handle enter/leave events
|
|
580
656
|
if (this._hoveredEntity !== newHoverEntity) {
|
|
581
|
-
if (this._hoveredEntity && this._hoveredEntity.
|
|
657
|
+
if (this._hoveredEntity && this._hoveredEntity._hasListeners('pointerleave')) {
|
|
582
658
|
this._hoveredEntity.dispatchEvent(new PointerEvent('pointerleave', event));
|
|
583
659
|
}
|
|
584
|
-
if (newHoverEntity && newHoverEntity.
|
|
660
|
+
if (newHoverEntity && newHoverEntity._hasListeners('pointerenter')) {
|
|
585
661
|
newHoverEntity.dispatchEvent(new PointerEvent('pointerenter', event));
|
|
586
662
|
}
|
|
587
663
|
}
|
|
@@ -590,12 +666,12 @@ class AppElement extends AsyncElement {
|
|
|
590
666
|
this._hoveredEntity = newHoverEntity;
|
|
591
667
|
|
|
592
668
|
// Handle pointermove event
|
|
593
|
-
if (newHoverEntity && newHoverEntity.
|
|
669
|
+
if (newHoverEntity && newHoverEntity._hasListeners('pointermove')) {
|
|
594
670
|
newHoverEntity.dispatchEvent(new PointerEvent('pointermove', event));
|
|
595
671
|
}
|
|
596
672
|
}
|
|
597
673
|
|
|
598
|
-
async _onPointerDown(event: PointerEvent) {
|
|
674
|
+
private async _onPointerDown(event: PointerEvent) {
|
|
599
675
|
if (!this._picker || !this.app) return;
|
|
600
676
|
|
|
601
677
|
const node = await this._pickNode(event);
|
|
@@ -607,7 +683,7 @@ class AppElement extends AsyncElement {
|
|
|
607
683
|
}
|
|
608
684
|
}
|
|
609
685
|
|
|
610
|
-
async _onPointerUp(event: PointerEvent) {
|
|
686
|
+
private async _onPointerUp(event: PointerEvent) {
|
|
611
687
|
if (!this._picker || !this.app) return;
|
|
612
688
|
|
|
613
689
|
const node = await this._pickNode(event);
|
|
@@ -619,34 +695,43 @@ class AppElement extends AsyncElement {
|
|
|
619
695
|
}
|
|
620
696
|
}
|
|
621
697
|
|
|
622
|
-
_onPointerListenerAdded(type: string) {
|
|
698
|
+
private _onPointerListenerAdded(type: string) {
|
|
623
699
|
if (!this._hasPointerListeners[type] && this._canvas) {
|
|
624
700
|
this._hasPointerListeners[type] = true;
|
|
625
701
|
|
|
626
702
|
// For enter/leave events, we need the move handler
|
|
627
|
-
const handler =
|
|
628
|
-
|
|
629
|
-
|
|
703
|
+
const handler =
|
|
704
|
+
type === 'pointerenter' || type === 'pointerleave'
|
|
705
|
+
? this._pointerHandlers.pointermove
|
|
706
|
+
: this._pointerHandlers[type];
|
|
630
707
|
|
|
631
708
|
if (handler) {
|
|
632
|
-
this._canvas.addEventListener(
|
|
709
|
+
this._canvas.addEventListener(
|
|
710
|
+
type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type,
|
|
711
|
+
handler
|
|
712
|
+
);
|
|
633
713
|
}
|
|
634
714
|
}
|
|
635
715
|
}
|
|
636
716
|
|
|
637
|
-
_onPointerListenerRemoved(type: string) {
|
|
638
|
-
const hasListeners = Array.from(this.querySelectorAll<EntityElement>('pc-entity'))
|
|
639
|
-
|
|
717
|
+
private _onPointerListenerRemoved(type: string) {
|
|
718
|
+
const hasListeners = Array.from(this.querySelectorAll<EntityElement>('pc-entity')).some((entity) =>
|
|
719
|
+
entity._hasListeners(type)
|
|
720
|
+
);
|
|
640
721
|
|
|
641
722
|
if (!hasListeners && this._canvas) {
|
|
642
723
|
this._hasPointerListeners[type] = false;
|
|
643
724
|
|
|
644
|
-
const handler =
|
|
645
|
-
|
|
646
|
-
|
|
725
|
+
const handler =
|
|
726
|
+
type === 'pointerenter' || type === 'pointerleave'
|
|
727
|
+
? this._pointerHandlers.pointermove
|
|
728
|
+
: this._pointerHandlers[type];
|
|
647
729
|
|
|
648
730
|
if (handler) {
|
|
649
|
-
this._canvas.removeEventListener(
|
|
731
|
+
this._canvas.removeEventListener(
|
|
732
|
+
type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type,
|
|
733
|
+
handler
|
|
734
|
+
);
|
|
650
735
|
}
|
|
651
736
|
}
|
|
652
737
|
}
|
|
@@ -660,7 +745,9 @@ class AppElement extends AsyncElement {
|
|
|
660
745
|
*/
|
|
661
746
|
private _warnIfBooted(name: string) {
|
|
662
747
|
if (this._optionsLocked) {
|
|
663
|
-
console.warn(
|
|
748
|
+
console.warn(
|
|
749
|
+
`Attribute '${name}' on <pc-app> is only read when the application boots, so this change has no effect. Set it before the element is connected, or remove and re-insert the element to reboot with the new value.`
|
|
750
|
+
);
|
|
664
751
|
}
|
|
665
752
|
}
|
|
666
753
|
|
|
@@ -735,15 +822,6 @@ class AppElement extends AsyncElement {
|
|
|
735
822
|
return this._depthBuffer;
|
|
736
823
|
}
|
|
737
824
|
|
|
738
|
-
/**
|
|
739
|
-
* Gets the hierarchy ready flag.
|
|
740
|
-
* @returns The hierarchy ready flag.
|
|
741
|
-
* @ignore
|
|
742
|
-
*/
|
|
743
|
-
get hierarchyReady() {
|
|
744
|
-
return this._hierarchyReady;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
825
|
/**
|
|
748
826
|
* Sets whether the application shows its built-in loading bar while it boots and preloads its
|
|
749
827
|
* assets. Enabled by default; setting `false` removes the bar immediately, while setting
|
|
@@ -844,10 +922,4 @@ class AppElement extends AsyncElement {
|
|
|
844
922
|
|
|
845
923
|
customElements.define('pc-app', AppElement);
|
|
846
924
|
|
|
847
|
-
declare global {
|
|
848
|
-
interface HTMLElementTagNameMap {
|
|
849
|
-
'pc-app': AppElement;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
925
|
export { AppElement };
|
package/src/asset.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Asset, SPRITE_RENDERMODE_SIMPLE, SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED } from 'playcanvas';
|
|
2
2
|
|
|
3
|
+
import { MeshoptDecoder } from '../lib/meshopt_decoder.module.js';
|
|
4
|
+
|
|
3
5
|
import { AsyncElement } from './async-element';
|
|
4
6
|
import { parseBool, parseEnum, parseNumber } from './parse';
|
|
5
|
-
import { MeshoptDecoder } from '../lib/meshopt_decoder.module.js';
|
|
6
7
|
|
|
7
8
|
const renderModes = new Map<'simple' | 'sliced' | 'tiled', number>([
|
|
8
9
|
['simple', SPRITE_RENDERMODE_SIMPLE],
|
|
@@ -33,12 +34,11 @@ const extToType = new Map([
|
|
|
33
34
|
['webp', 'texture']
|
|
34
35
|
]);
|
|
35
36
|
|
|
36
|
-
|
|
37
37
|
// provide buffer view callback so we can handle models compressed with MeshOptimizer
|
|
38
38
|
// https://github.com/zeux/meshoptimizer
|
|
39
39
|
const processBufferView = (
|
|
40
40
|
gltfBuffer: any,
|
|
41
|
-
buffers:
|
|
41
|
+
buffers: any[],
|
|
42
42
|
continuation: (err: string | null, result: any) => void
|
|
43
43
|
) => {
|
|
44
44
|
if (gltfBuffer.extensions && gltfBuffer.extensions.EXT_meshopt_compression) {
|
|
@@ -56,14 +56,7 @@ const processBufferView = (
|
|
|
56
56
|
const result = new Uint8Array(count * stride);
|
|
57
57
|
const source = new Uint8Array(buffer.buffer, buffer.byteOffset + byteOffset, byteLength);
|
|
58
58
|
|
|
59
|
-
MeshoptDecoder.decodeGltfBuffer(
|
|
60
|
-
result,
|
|
61
|
-
count,
|
|
62
|
-
stride,
|
|
63
|
-
source,
|
|
64
|
-
extensionDef.mode,
|
|
65
|
-
extensionDef.filter
|
|
66
|
-
);
|
|
59
|
+
MeshoptDecoder.decodeGltfBuffer(result, count, stride, source, extensionDef.mode, extensionDef.filter);
|
|
67
60
|
|
|
68
61
|
continuation(null, result);
|
|
69
62
|
});
|
|
@@ -108,7 +101,7 @@ const processBufferView = (
|
|
|
108
101
|
* not that it succeeded.
|
|
109
102
|
*/
|
|
110
103
|
class AssetElement extends AsyncElement {
|
|
111
|
-
private _lazy
|
|
104
|
+
private _lazy = false;
|
|
112
105
|
|
|
113
106
|
/**
|
|
114
107
|
* The asset that is loaded. Available once the element is ready — await
|
|
@@ -122,7 +115,9 @@ class AssetElement extends AsyncElement {
|
|
|
122
115
|
|
|
123
116
|
// Assets must be direct children of pc-app (matches the boot query ':scope > pc-asset')
|
|
124
117
|
if (this.parentElement !== appElement) {
|
|
125
|
-
console.warn(
|
|
118
|
+
console.warn(
|
|
119
|
+
`pc-asset '${this.getAttribute('id') ?? this.getAttribute('src')}' must be a direct child of pc-app - asset not created`
|
|
120
|
+
);
|
|
126
121
|
return;
|
|
127
122
|
}
|
|
128
123
|
|
|
@@ -137,7 +132,7 @@ class AssetElement extends AsyncElement {
|
|
|
137
132
|
const app = appElement.app;
|
|
138
133
|
if (!app) return; // pc-app is re-connecting; its own boot will create this asset
|
|
139
134
|
|
|
140
|
-
this.
|
|
135
|
+
this._createAsset();
|
|
141
136
|
if (this.asset) {
|
|
142
137
|
app.assets.add(this.asset); // add() auto-loads when preload is true
|
|
143
138
|
if (!this.lazy) {
|
|
@@ -146,14 +141,16 @@ class AssetElement extends AsyncElement {
|
|
|
146
141
|
}
|
|
147
142
|
}
|
|
148
143
|
|
|
149
|
-
// Never ready if
|
|
144
|
+
// Never ready if _createAsset failed (unsupported asset type)
|
|
150
145
|
if (this.asset) {
|
|
151
146
|
this._onReady();
|
|
152
147
|
}
|
|
153
148
|
}
|
|
154
149
|
|
|
155
150
|
disconnectedCallback() {
|
|
156
|
-
this.
|
|
151
|
+
this._destroyAsset();
|
|
152
|
+
// Re-arm readiness so a re-inserted element announces the asset it creates then
|
|
153
|
+
this._resetReady();
|
|
157
154
|
}
|
|
158
155
|
|
|
159
156
|
private _onAssetLoad() {
|
|
@@ -161,12 +158,21 @@ class AssetElement extends AsyncElement {
|
|
|
161
158
|
}
|
|
162
159
|
|
|
163
160
|
private _onAssetError(err: string | Error) {
|
|
164
|
-
this.dispatchEvent(
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
this.dispatchEvent(
|
|
162
|
+
new ErrorEvent('error', {
|
|
163
|
+
message: err instanceof Error ? err.message : String(err)
|
|
164
|
+
})
|
|
165
|
+
);
|
|
167
166
|
}
|
|
168
167
|
|
|
169
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Creates the asset from the element's attributes. Called by the containing `<pc-app>`
|
|
170
|
+
* element during its boot sweep, and on connection for elements inserted while the
|
|
171
|
+
* application is already running.
|
|
172
|
+
*
|
|
173
|
+
* @internal
|
|
174
|
+
*/
|
|
175
|
+
_createAsset() {
|
|
170
176
|
const id = this.getAttribute('id') || '';
|
|
171
177
|
const src = this.getAttribute('src') || '';
|
|
172
178
|
let type = this.getAttribute('type');
|
|
@@ -268,8 +274,7 @@ class AssetElement extends AsyncElement {
|
|
|
268
274
|
return data;
|
|
269
275
|
}
|
|
270
276
|
|
|
271
|
-
|
|
272
|
-
destroyAsset() {
|
|
277
|
+
private _destroyAsset() {
|
|
273
278
|
if (this.asset) {
|
|
274
279
|
// A caller that keeps the Asset alive must not dispatch on a removed element
|
|
275
280
|
this.asset.off('load', this._onAssetLoad, this);
|
|
@@ -300,6 +305,13 @@ class AssetElement extends AsyncElement {
|
|
|
300
305
|
return this._lazy;
|
|
301
306
|
}
|
|
302
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Returns the {@link Asset} created by the `<pc-asset>` element with the given `id`, or
|
|
310
|
+
* `undefined` if there is no such element or its asset has not been created yet.
|
|
311
|
+
*
|
|
312
|
+
* @param id - The `id` of the `<pc-asset>` element.
|
|
313
|
+
* @returns The asset, or `undefined`.
|
|
314
|
+
*/
|
|
303
315
|
static get(id: string) {
|
|
304
316
|
const assetElement = document.querySelector<AssetElement>(`pc-asset[id="${id}"]`);
|
|
305
317
|
return assetElement?.asset;
|
|
@@ -318,10 +330,4 @@ class AssetElement extends AsyncElement {
|
|
|
318
330
|
|
|
319
331
|
customElements.define('pc-asset', AssetElement);
|
|
320
332
|
|
|
321
|
-
declare global {
|
|
322
|
-
interface HTMLElementTagNameMap {
|
|
323
|
-
'pc-asset': AssetElement;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
333
|
export { AssetElement };
|