@playcanvas/web-components 0.11.0 → 0.12.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/README.md +1 -1
- package/dist/app.d.ts +31 -49
- package/dist/asset.d.ts +152 -12
- package/dist/async-element.d.ts +26 -9
- 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 +19 -7
- package/dist/components/component.d.ts +41 -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 +5824 -10581
- package/dist/entity-base.d.ts +67 -0
- package/dist/entity.d.ts +7 -48
- package/dist/index.d.ts +41 -1
- package/dist/material.d.ts +14 -13
- package/dist/model.d.ts +43 -5
- package/dist/module.d.ts +0 -6
- package/dist/node.d.ts +253 -0
- package/dist/parse.d.ts +2 -1
- package/dist/pwc.cjs +1830 -274
- package/dist/pwc.cjs.map +1 -1
- package/dist/pwc.js +1830 -274
- 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 +1830 -276
- 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 +148 -26
- package/dist/web-types.json +894 -581
- package/package.json +9 -8
- package/src/app.ts +163 -88
- package/src/asset.ts +472 -36
- package/src/async-element.ts +39 -12
- package/src/components/button-component.ts +5 -9
- package/src/components/camera-component.ts +24 -10
- package/src/components/collision-component.ts +61 -15
- package/src/components/component.ts +151 -11
- 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-base.ts +136 -0
- package/src/entity.ts +47 -118
- package/src/index.ts +50 -1
- package/src/loading-bar.ts +8 -8
- package/src/material.ts +65 -39
- package/src/model.ts +140 -17
- package/src/module.ts +8 -7
- package/src/node.ts +715 -0
- package/src/parse.ts +62 -17
- package/src/scene.ts +12 -9
- package/src/sky.ts +50 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcanvas/web-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
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
|
-
"playcanvas": "2.
|
|
95
|
-
"
|
|
94
|
+
"playcanvas": "2.22.0-beta.10",
|
|
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,21 @@ 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';
|
|
70
|
+
import type { EntityBaseElement } from './entity-base';
|
|
73
71
|
import { LoadingBar } from './loading-bar';
|
|
74
|
-
import { MaterialElement } from './material';
|
|
75
|
-
import { ModuleElement } from './module';
|
|
72
|
+
import type { MaterialElement } from './material';
|
|
73
|
+
import type { ModuleElement } from './module';
|
|
76
74
|
import { parseBool, parseEnum, parseNumber } from './parse';
|
|
77
75
|
|
|
76
|
+
/** The pointer event types the application synthesizes on `<pc-entity>` elements via picking. */
|
|
77
|
+
const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'] as const;
|
|
78
|
+
|
|
78
79
|
/**
|
|
79
80
|
* The AppElement interface provides properties and methods for manipulating
|
|
80
81
|
* {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-app/ | `<pc-app>`} elements.
|
|
@@ -115,18 +116,33 @@ class AppElement extends AsyncElement {
|
|
|
115
116
|
|
|
116
117
|
private _bar: LoadingBar | null = null;
|
|
117
118
|
|
|
118
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Whether the application has created its initial entity hierarchy. Read by EntityElement to
|
|
121
|
+
* decide whether a newly connected element must create its entity itself or leave it to the
|
|
122
|
+
* boot sweep.
|
|
123
|
+
* @internal
|
|
124
|
+
*/
|
|
125
|
+
_hierarchyReady = false;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Incremented on every connect and disconnect. Boot captures the value on entry and abandons
|
|
129
|
+
* itself wherever it resumes from an await if the value has moved on — so a boot whose
|
|
130
|
+
* element was removed cannot complete against a torn-down element, and a boot whose element
|
|
131
|
+
* was removed and re-inserted (which starts a boot of its own) cannot race the newer one.
|
|
132
|
+
*/
|
|
133
|
+
private _bootGeneration = 0;
|
|
119
134
|
|
|
120
135
|
/**
|
|
121
136
|
* The elements backing this application's entities, keyed by the entity itself. Registered
|
|
122
|
-
* by EntityElement at creation and removed when an entity is
|
|
123
|
-
* scene nodes back to their owning elements by
|
|
137
|
+
* by EntityElement at creation (and NodeElement at binding) and removed when an entity is
|
|
138
|
+
* destroyed or unbound, this joins engine scene nodes back to their owning elements by
|
|
139
|
+
* identity - never by name.
|
|
124
140
|
*/
|
|
125
|
-
private _entityElements = new Map<GraphNode,
|
|
141
|
+
private _entityElements = new Map<GraphNode, EntityBaseElement>();
|
|
126
142
|
|
|
127
143
|
private _picker: Picker | null = null;
|
|
128
144
|
|
|
129
|
-
private _hasPointerListeners:
|
|
145
|
+
private _hasPointerListeners: Record<string, boolean> = {
|
|
130
146
|
pointerenter: false,
|
|
131
147
|
pointerleave: false,
|
|
132
148
|
pointerdown: false,
|
|
@@ -134,12 +150,12 @@ class AppElement extends AsyncElement {
|
|
|
134
150
|
pointermove: false
|
|
135
151
|
};
|
|
136
152
|
|
|
137
|
-
private _hoveredEntity:
|
|
153
|
+
private _hoveredEntity: EntityBaseElement | null = null;
|
|
138
154
|
|
|
139
155
|
// Identifies the newest in-flight hover pick, so out-of-order results can be discarded
|
|
140
156
|
private _pickToken = 0;
|
|
141
157
|
|
|
142
|
-
private _pointerHandlers:
|
|
158
|
+
private _pointerHandlers: Record<string, EventListener | null> = {
|
|
143
159
|
pointermove: null,
|
|
144
160
|
pointerdown: null,
|
|
145
161
|
pointerup: null
|
|
@@ -180,9 +196,19 @@ class AppElement extends AsyncElement {
|
|
|
180
196
|
|
|
181
197
|
// Bind methods to maintain 'this' context
|
|
182
198
|
this._onWindowResize = this._onWindowResize.bind(this);
|
|
199
|
+
|
|
200
|
+
// Track pointer listeners being added to and removed from descendant entities.
|
|
201
|
+
// Registered once here rather than on every boot - the handlers no-op while there is no
|
|
202
|
+
// canvas, and a re-booted element must not stack a second set.
|
|
203
|
+
pointerEventTypes.forEach((type) => {
|
|
204
|
+
this.addEventListener(`${type}:connect`, () => this._onPointerListenerAdded(type));
|
|
205
|
+
this.addEventListener(`${type}:disconnect`, () => this._onPointerListenerRemoved(type));
|
|
206
|
+
});
|
|
183
207
|
}
|
|
184
208
|
|
|
185
209
|
async connectedCallback() {
|
|
210
|
+
const generation = ++this._bootGeneration;
|
|
211
|
+
|
|
186
212
|
// Created before the first await, so the bar is visible while modules and the graphics
|
|
187
213
|
// device are created, and exists before any disconnect could need to clean it up
|
|
188
214
|
if (this._loadingBar && !this._bar) {
|
|
@@ -193,14 +219,20 @@ class AppElement extends AsyncElement {
|
|
|
193
219
|
const moduleElements = this.querySelectorAll<ModuleElement>(':scope > pc-module');
|
|
194
220
|
|
|
195
221
|
// Wait for all modules to load
|
|
196
|
-
await Promise.all(Array.from(moduleElements).map(module => module.
|
|
222
|
+
await Promise.all(Array.from(moduleElements).map((module) => module._getLoadPromise()));
|
|
223
|
+
|
|
224
|
+
// The element may have been removed while the modules loaded. Nothing beyond the loading
|
|
225
|
+
// bar exists yet, and disconnectedCallback has already destroyed that.
|
|
226
|
+
if (generation !== this._bootGeneration) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
197
229
|
|
|
198
230
|
// Create and append the canvas to the element
|
|
199
231
|
this._canvas = document.createElement('canvas');
|
|
200
232
|
this.appendChild(this._canvas);
|
|
201
233
|
|
|
202
234
|
// Configure device types based on backend selection
|
|
203
|
-
const backendToDeviceTypes:
|
|
235
|
+
const backendToDeviceTypes: Record<string, string[]> = {
|
|
204
236
|
webgpu: ['webgpu', 'webgl2'], // fallback to webgl2 if webgpu not available
|
|
205
237
|
webgl2: ['webgl2'],
|
|
206
238
|
null: ['null']
|
|
@@ -218,6 +250,14 @@ class AppElement extends AsyncElement {
|
|
|
218
250
|
stencil: this._stencilBuffer
|
|
219
251
|
});
|
|
220
252
|
|
|
253
|
+
// The element may have been removed while the device was created. disconnectedCallback
|
|
254
|
+
// has already cleaned up the canvas; the device was created inside the await, so it is
|
|
255
|
+
// this boot's to release.
|
|
256
|
+
if (generation !== this._bootGeneration) {
|
|
257
|
+
device.destroy();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
221
261
|
// Assigned rather than resolved to a number here: the engine caps against the live
|
|
222
262
|
// window.devicePixelRatio on every resize, so an uncapped Infinity keeps following the
|
|
223
263
|
// display when a window moves between monitors of differing density.
|
|
@@ -299,31 +339,47 @@ class AppElement extends AsyncElement {
|
|
|
299
339
|
|
|
300
340
|
// Get all pc-asset elements that are direct children of the pc-app element
|
|
301
341
|
const assetElements = this.querySelectorAll<AssetElement>(':scope > pc-asset');
|
|
302
|
-
Array.from(assetElements)
|
|
303
|
-
assetElement.
|
|
342
|
+
for (const assetElement of Array.from(assetElements)) {
|
|
343
|
+
assetElement._createAsset();
|
|
304
344
|
const asset = assetElement.asset;
|
|
305
345
|
if (asset) {
|
|
306
346
|
app.assets.add(asset);
|
|
347
|
+
|
|
348
|
+
// Adding a fileless asset (one built purely from data, such as a sprite)
|
|
349
|
+
// completes it synchronously, dispatching the element's load event - whose
|
|
350
|
+
// listeners may have removed this element. Stop before the next addition
|
|
351
|
+
// reaches the destroyed registry, and before orphan entities are created.
|
|
352
|
+
if (generation !== this._bootGeneration) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
307
355
|
}
|
|
308
|
-
}
|
|
356
|
+
}
|
|
309
357
|
|
|
310
358
|
// Get all pc-material elements that are direct children of the pc-app element
|
|
311
359
|
const materialElements = this.querySelectorAll<MaterialElement>(':scope > pc-material');
|
|
312
360
|
Array.from(materialElements).forEach((materialElement) => {
|
|
313
|
-
materialElement.
|
|
361
|
+
materialElement._createMaterial();
|
|
314
362
|
});
|
|
315
363
|
|
|
316
364
|
// Create all entities
|
|
317
365
|
const entityElements = this.querySelectorAll<EntityElement>('pc-entity');
|
|
318
366
|
Array.from(entityElements).forEach((entityElement) => {
|
|
319
|
-
entityElement.
|
|
367
|
+
entityElement._createEntity(app);
|
|
320
368
|
});
|
|
321
369
|
|
|
322
370
|
// Build hierarchy
|
|
323
371
|
entityElements.forEach((entityElement) => {
|
|
324
|
-
entityElement.
|
|
372
|
+
entityElement._buildHierarchy(app);
|
|
325
373
|
});
|
|
326
374
|
|
|
375
|
+
// Building the hierarchy dispatched each entity's ready event synchronously, and a
|
|
376
|
+
// listener may have removed the element. The sweep itself degrades safely - destroying
|
|
377
|
+
// the application nulls every element's entity, so the remaining builds no-op - but the
|
|
378
|
+
// teardown's reset must not be overwritten here.
|
|
379
|
+
if (generation !== this._bootGeneration) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
327
383
|
this._hierarchyReady = true;
|
|
328
384
|
|
|
329
385
|
// Forward the engine's preload lifecycle as DOM ProgressEvents on this element. The
|
|
@@ -343,8 +399,21 @@ class AppElement extends AsyncElement {
|
|
|
343
399
|
this._bar?.progress(0, total);
|
|
344
400
|
this.dispatchEvent(new ProgressEvent('progress', { lengthComputable: true, loaded: 0, total }));
|
|
345
401
|
|
|
402
|
+
// The progress dispatch above ran listeners synchronously, and one may have removed the
|
|
403
|
+
// element. The application is already destroyed - it must not be asked to preload.
|
|
404
|
+
if (generation !== this._bootGeneration) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
346
408
|
// Load assets before starting the application
|
|
347
409
|
app.preload(() => {
|
|
410
|
+
// The element may have been removed while assets loaded. The application is already
|
|
411
|
+
// destroyed, so it must not be started — and readiness must not be signaled for a
|
|
412
|
+
// boot that no longer owns the element.
|
|
413
|
+
if (generation !== this._bootGeneration) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
348
417
|
// Scope the counter to this preload pass, so a later app.preload() call by user code
|
|
349
418
|
// cannot push `loaded` past `total`
|
|
350
419
|
app.off('preload:progress', onPreloadProgress);
|
|
@@ -365,6 +434,10 @@ class AppElement extends AsyncElement {
|
|
|
365
434
|
}
|
|
366
435
|
|
|
367
436
|
disconnectedCallback() {
|
|
437
|
+
// Invalidate any boot still in flight, so it abandons itself when it next resumes
|
|
438
|
+
// instead of completing against a torn-down element.
|
|
439
|
+
this._bootGeneration++;
|
|
440
|
+
|
|
368
441
|
this._optionsLocked = false;
|
|
369
442
|
this._pickerDestroy();
|
|
370
443
|
|
|
@@ -379,6 +452,12 @@ class AppElement extends AsyncElement {
|
|
|
379
452
|
this._bar?.destroy();
|
|
380
453
|
this._bar = null;
|
|
381
454
|
|
|
455
|
+
// Return the element to its pre-boot state, so re-inserting it boots afresh: descendants
|
|
456
|
+
// must neither see a hierarchy that no longer exists nor resume against a readiness that
|
|
457
|
+
// no longer holds.
|
|
458
|
+
this._hierarchyReady = false;
|
|
459
|
+
this._resetReady();
|
|
460
|
+
|
|
382
461
|
// Remove event listeners
|
|
383
462
|
window.removeEventListener('resize', this._onWindowResize);
|
|
384
463
|
|
|
@@ -389,13 +468,13 @@ class AppElement extends AsyncElement {
|
|
|
389
468
|
}
|
|
390
469
|
}
|
|
391
470
|
|
|
392
|
-
_onWindowResize() {
|
|
471
|
+
private _onWindowResize() {
|
|
393
472
|
if (this.app) {
|
|
394
473
|
this.app.resizeCanvas();
|
|
395
474
|
}
|
|
396
475
|
}
|
|
397
476
|
|
|
398
|
-
_pickerCreate() {
|
|
477
|
+
private _pickerCreate() {
|
|
399
478
|
const { width, height } = this.app!.graphicsDevice;
|
|
400
479
|
this._picker = new Picker(this.app!, width, height);
|
|
401
480
|
|
|
@@ -412,22 +491,20 @@ class AppElement extends AsyncElement {
|
|
|
412
491
|
this._pointerHandlers.pointerdown = listener(this._onPointerDown);
|
|
413
492
|
this._pointerHandlers.pointerup = listener(this._onPointerUp);
|
|
414
493
|
|
|
415
|
-
//
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const anyListeners = Array.from(this.querySelectorAll<EntityElement>('pc-entity'))
|
|
423
|
-
.some(entity => entity.hasListeners(type));
|
|
494
|
+
// Attach canvas handlers for listeners registered before this boot (e.g. handlers
|
|
495
|
+
// created from onpointer* attributes when their elements were first upgraded, or
|
|
496
|
+
// listeners carried over from before a re-boot)
|
|
497
|
+
pointerEventTypes.forEach((type) => {
|
|
498
|
+
const anyListeners = Array.from(
|
|
499
|
+
this.querySelectorAll<EntityBaseElement>('pc-entity, pc-node')
|
|
500
|
+
).some((entity) => entity._hasListeners(type));
|
|
424
501
|
if (anyListeners) {
|
|
425
502
|
this._onPointerListenerAdded(type);
|
|
426
503
|
}
|
|
427
504
|
});
|
|
428
505
|
}
|
|
429
506
|
|
|
430
|
-
_pickerDestroy() {
|
|
507
|
+
private _pickerDestroy() {
|
|
431
508
|
if (this._canvas) {
|
|
432
509
|
Object.entries(this._pointerHandlers).forEach(([type, handler]) => {
|
|
433
510
|
if (handler) {
|
|
@@ -437,6 +514,7 @@ class AppElement extends AsyncElement {
|
|
|
437
514
|
}
|
|
438
515
|
|
|
439
516
|
this._picker = null;
|
|
517
|
+
this._hoveredEntity = null;
|
|
440
518
|
this._pointerHandlers = {
|
|
441
519
|
pointermove: null,
|
|
442
520
|
pointerdown: null,
|
|
@@ -452,14 +530,14 @@ class AppElement extends AsyncElement {
|
|
|
452
530
|
}
|
|
453
531
|
|
|
454
532
|
/**
|
|
455
|
-
* Registers the element that
|
|
456
|
-
* entity.
|
|
533
|
+
* Registers the element that fronts an entity. Called by EntityElement when it creates its
|
|
534
|
+
* entity, and by NodeElement when it binds one.
|
|
457
535
|
*
|
|
458
536
|
* @param entity - The entity.
|
|
459
|
-
* @param element - The element that
|
|
460
|
-
* @
|
|
537
|
+
* @param element - The element that fronts it.
|
|
538
|
+
* @internal
|
|
461
539
|
*/
|
|
462
|
-
_registerEntityElement(entity: Entity, element:
|
|
540
|
+
_registerEntityElement(entity: Entity, element: EntityBaseElement) {
|
|
463
541
|
this._entityElements.set(entity, element);
|
|
464
542
|
}
|
|
465
543
|
|
|
@@ -467,34 +545,35 @@ class AppElement extends AsyncElement {
|
|
|
467
545
|
* Removes the registration for a destroyed entity. Called by EntityElement.
|
|
468
546
|
*
|
|
469
547
|
* @param entity - The entity.
|
|
470
|
-
* @
|
|
548
|
+
* @internal
|
|
471
549
|
*/
|
|
472
550
|
_unregisterEntityElement(entity: Entity) {
|
|
473
551
|
this._entityElements.delete(entity);
|
|
474
552
|
}
|
|
475
553
|
|
|
476
554
|
/**
|
|
477
|
-
* Returns the `<pc-entity>` element whose backing entity is `entity`, or
|
|
478
|
-
* entity
|
|
479
|
-
* model's instantiated hierarchy, or an entity created through the
|
|
555
|
+
* Returns the `<pc-entity>` or `<pc-node>` element whose backing entity is `entity`, or
|
|
556
|
+
* `null` if the entity is not fronted by an element of this application - for example, an
|
|
557
|
+
* unbound node inside a model's instantiated hierarchy, or an entity created through the
|
|
558
|
+
* engine API.
|
|
480
559
|
*
|
|
481
560
|
* @param entity - The entity to look up.
|
|
482
|
-
* @returns The element
|
|
561
|
+
* @returns The element fronting the entity, or `null`.
|
|
483
562
|
*/
|
|
484
|
-
elementFromEntity(entity: Entity):
|
|
563
|
+
elementFromEntity(entity: Entity): EntityBaseElement | null {
|
|
485
564
|
return this._entityElements.get(entity) ?? null;
|
|
486
565
|
}
|
|
487
566
|
|
|
488
567
|
/**
|
|
489
568
|
* Resolves the element that owns a picked node: the nearest node up the parent chain -
|
|
490
|
-
* starting with the node itself - that
|
|
491
|
-
* A hit inside a model's instantiated hierarchy therefore resolves to the
|
|
492
|
-
* the model.
|
|
569
|
+
* starting with the node itself - that is fronted by a `<pc-entity>` or `<pc-node>` of this
|
|
570
|
+
* application. A hit inside a model's instantiated hierarchy therefore resolves to the
|
|
571
|
+
* nearest bound `<pc-node>`, or failing that the element hosting the model.
|
|
493
572
|
*
|
|
494
573
|
* @param node - The picked node, or `null`.
|
|
495
574
|
* @returns The owning element, or `null`.
|
|
496
575
|
*/
|
|
497
|
-
private _elementFromNode(node: GraphNode | null):
|
|
576
|
+
private _elementFromNode(node: GraphNode | null): EntityBaseElement | null {
|
|
498
577
|
while (node !== null) {
|
|
499
578
|
const element = this._entityElements.get(node);
|
|
500
579
|
if (element) {
|
|
@@ -513,10 +592,10 @@ class AppElement extends AsyncElement {
|
|
|
513
592
|
* @param type - The pointer event type a listener is required for.
|
|
514
593
|
* @returns The nearest listening element, or `null`.
|
|
515
594
|
*/
|
|
516
|
-
private _elementWithListener(node: GraphNode | null, type: string):
|
|
595
|
+
private _elementWithListener(node: GraphNode | null, type: string): EntityBaseElement | null {
|
|
517
596
|
while (node !== null) {
|
|
518
597
|
const element = this._entityElements.get(node);
|
|
519
|
-
if (element?.
|
|
598
|
+
if (element?._hasListeners(type)) {
|
|
520
599
|
return element;
|
|
521
600
|
}
|
|
522
601
|
node = node.parent;
|
|
@@ -525,7 +604,7 @@ class AppElement extends AsyncElement {
|
|
|
525
604
|
}
|
|
526
605
|
|
|
527
606
|
// New helper to convert CSS coordinates to canvas (picker) coordinates
|
|
528
|
-
private _getPickerCoordinates(event: PointerEvent): { x: number
|
|
607
|
+
private _getPickerCoordinates(event: PointerEvent): { x: number; y: number } {
|
|
529
608
|
// Get the canvas' bounding rectangle in CSS pixels.
|
|
530
609
|
const canvasRect = this._canvas!.getBoundingClientRect();
|
|
531
610
|
// Compute scale factors based on canvas actual resolution vs. its CSS display size.
|
|
@@ -562,7 +641,7 @@ class AppElement extends AsyncElement {
|
|
|
562
641
|
return item instanceof MeshInstance ? item.node : (item as GSplatComponent).entity;
|
|
563
642
|
}
|
|
564
643
|
|
|
565
|
-
async _onPointerMove(event: PointerEvent) {
|
|
644
|
+
private async _onPointerMove(event: PointerEvent) {
|
|
566
645
|
if (!this._picker || !this.app) return;
|
|
567
646
|
|
|
568
647
|
// Moves arrive faster than a pick resolves, so results can land out of order. Only the
|
|
@@ -578,10 +657,10 @@ class AppElement extends AsyncElement {
|
|
|
578
657
|
|
|
579
658
|
// Handle enter/leave events
|
|
580
659
|
if (this._hoveredEntity !== newHoverEntity) {
|
|
581
|
-
if (this._hoveredEntity && this._hoveredEntity.
|
|
660
|
+
if (this._hoveredEntity && this._hoveredEntity._hasListeners('pointerleave')) {
|
|
582
661
|
this._hoveredEntity.dispatchEvent(new PointerEvent('pointerleave', event));
|
|
583
662
|
}
|
|
584
|
-
if (newHoverEntity && newHoverEntity.
|
|
663
|
+
if (newHoverEntity && newHoverEntity._hasListeners('pointerenter')) {
|
|
585
664
|
newHoverEntity.dispatchEvent(new PointerEvent('pointerenter', event));
|
|
586
665
|
}
|
|
587
666
|
}
|
|
@@ -590,12 +669,12 @@ class AppElement extends AsyncElement {
|
|
|
590
669
|
this._hoveredEntity = newHoverEntity;
|
|
591
670
|
|
|
592
671
|
// Handle pointermove event
|
|
593
|
-
if (newHoverEntity && newHoverEntity.
|
|
672
|
+
if (newHoverEntity && newHoverEntity._hasListeners('pointermove')) {
|
|
594
673
|
newHoverEntity.dispatchEvent(new PointerEvent('pointermove', event));
|
|
595
674
|
}
|
|
596
675
|
}
|
|
597
676
|
|
|
598
|
-
async _onPointerDown(event: PointerEvent) {
|
|
677
|
+
private async _onPointerDown(event: PointerEvent) {
|
|
599
678
|
if (!this._picker || !this.app) return;
|
|
600
679
|
|
|
601
680
|
const node = await this._pickNode(event);
|
|
@@ -607,7 +686,7 @@ class AppElement extends AsyncElement {
|
|
|
607
686
|
}
|
|
608
687
|
}
|
|
609
688
|
|
|
610
|
-
async _onPointerUp(event: PointerEvent) {
|
|
689
|
+
private async _onPointerUp(event: PointerEvent) {
|
|
611
690
|
if (!this._picker || !this.app) return;
|
|
612
691
|
|
|
613
692
|
const node = await this._pickNode(event);
|
|
@@ -619,34 +698,43 @@ class AppElement extends AsyncElement {
|
|
|
619
698
|
}
|
|
620
699
|
}
|
|
621
700
|
|
|
622
|
-
_onPointerListenerAdded(type: string) {
|
|
701
|
+
private _onPointerListenerAdded(type: string) {
|
|
623
702
|
if (!this._hasPointerListeners[type] && this._canvas) {
|
|
624
703
|
this._hasPointerListeners[type] = true;
|
|
625
704
|
|
|
626
705
|
// For enter/leave events, we need the move handler
|
|
627
|
-
const handler =
|
|
628
|
-
|
|
629
|
-
|
|
706
|
+
const handler =
|
|
707
|
+
type === 'pointerenter' || type === 'pointerleave'
|
|
708
|
+
? this._pointerHandlers.pointermove
|
|
709
|
+
: this._pointerHandlers[type];
|
|
630
710
|
|
|
631
711
|
if (handler) {
|
|
632
|
-
this._canvas.addEventListener(
|
|
712
|
+
this._canvas.addEventListener(
|
|
713
|
+
type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type,
|
|
714
|
+
handler
|
|
715
|
+
);
|
|
633
716
|
}
|
|
634
717
|
}
|
|
635
718
|
}
|
|
636
719
|
|
|
637
|
-
_onPointerListenerRemoved(type: string) {
|
|
638
|
-
const hasListeners = Array.from(
|
|
639
|
-
|
|
720
|
+
private _onPointerListenerRemoved(type: string) {
|
|
721
|
+
const hasListeners = Array.from(
|
|
722
|
+
this.querySelectorAll<EntityBaseElement>('pc-entity, pc-node')
|
|
723
|
+
).some((entity) => entity._hasListeners(type));
|
|
640
724
|
|
|
641
725
|
if (!hasListeners && this._canvas) {
|
|
642
726
|
this._hasPointerListeners[type] = false;
|
|
643
727
|
|
|
644
|
-
const handler =
|
|
645
|
-
|
|
646
|
-
|
|
728
|
+
const handler =
|
|
729
|
+
type === 'pointerenter' || type === 'pointerleave'
|
|
730
|
+
? this._pointerHandlers.pointermove
|
|
731
|
+
: this._pointerHandlers[type];
|
|
647
732
|
|
|
648
733
|
if (handler) {
|
|
649
|
-
this._canvas.removeEventListener(
|
|
734
|
+
this._canvas.removeEventListener(
|
|
735
|
+
type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type,
|
|
736
|
+
handler
|
|
737
|
+
);
|
|
650
738
|
}
|
|
651
739
|
}
|
|
652
740
|
}
|
|
@@ -660,7 +748,9 @@ class AppElement extends AsyncElement {
|
|
|
660
748
|
*/
|
|
661
749
|
private _warnIfBooted(name: string) {
|
|
662
750
|
if (this._optionsLocked) {
|
|
663
|
-
console.warn(
|
|
751
|
+
console.warn(
|
|
752
|
+
`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.`
|
|
753
|
+
);
|
|
664
754
|
}
|
|
665
755
|
}
|
|
666
756
|
|
|
@@ -735,15 +825,6 @@ class AppElement extends AsyncElement {
|
|
|
735
825
|
return this._depthBuffer;
|
|
736
826
|
}
|
|
737
827
|
|
|
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
828
|
/**
|
|
748
829
|
* Sets whether the application shows its built-in loading bar while it boots and preloads its
|
|
749
830
|
* assets. Enabled by default; setting `false` removes the bar immediately, while setting
|
|
@@ -844,10 +925,4 @@ class AppElement extends AsyncElement {
|
|
|
844
925
|
|
|
845
926
|
customElements.define('pc-app', AppElement);
|
|
846
927
|
|
|
847
|
-
declare global {
|
|
848
|
-
interface HTMLElementTagNameMap {
|
|
849
|
-
'pc-app': AppElement;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
928
|
export { AppElement };
|