@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.
Files changed (78) hide show
  1. package/README.md +1 -1
  2. package/dist/app.d.ts +18 -38
  3. package/dist/asset.d.ts +8 -7
  4. package/dist/async-element.d.ts +21 -5
  5. package/dist/components/button-component.d.ts +3 -7
  6. package/dist/components/camera-component.d.ts +3 -7
  7. package/dist/components/collision-component.d.ts +3 -7
  8. package/dist/components/component.d.ts +22 -4
  9. package/dist/components/element-component.d.ts +4 -8
  10. package/dist/components/gsplat-component.d.ts +2 -7
  11. package/dist/components/layoutchild-component.d.ts +2 -7
  12. package/dist/components/layoutgroup-component.d.ts +3 -7
  13. package/dist/components/light-component.d.ts +3 -7
  14. package/dist/components/listener-component.d.ts +1 -6
  15. package/dist/components/particlesystem-component.d.ts +2 -7
  16. package/dist/components/render-component.d.ts +2 -7
  17. package/dist/components/rigidbody-component.d.ts +3 -7
  18. package/dist/components/screen-component.d.ts +3 -7
  19. package/dist/components/script-component.d.ts +8 -20
  20. package/dist/components/script.d.ts +2 -22
  21. package/dist/components/scrollbar-component.d.ts +2 -7
  22. package/dist/components/scrollview-component.d.ts +3 -7
  23. package/dist/components/sound-component.d.ts +2 -7
  24. package/dist/components/sound-slot.d.ts +8 -6
  25. package/dist/custom-elements.json +5049 -10696
  26. package/dist/entity.d.ts +5 -11
  27. package/dist/index.d.ts +37 -0
  28. package/dist/material.d.ts +12 -12
  29. package/dist/model.d.ts +21 -5
  30. package/dist/module.d.ts +0 -6
  31. package/dist/parse.d.ts +2 -1
  32. package/dist/pwc.cjs +496 -136
  33. package/dist/pwc.cjs.map +1 -1
  34. package/dist/pwc.js +496 -136
  35. package/dist/pwc.js.map +1 -1
  36. package/dist/pwc.min.js +1 -1
  37. package/dist/pwc.min.js.map +1 -1
  38. package/dist/pwc.min.mjs +1 -1
  39. package/dist/pwc.min.mjs.map +1 -1
  40. package/dist/pwc.mjs +496 -136
  41. package/dist/pwc.mjs.map +1 -1
  42. package/dist/scene.d.ts +4 -7
  43. package/dist/sky.d.ts +13 -5
  44. package/dist/vscode.html-custom-data.json +26 -26
  45. package/dist/web-types.json +545 -536
  46. package/package.json +8 -7
  47. package/src/app.ts +142 -70
  48. package/src/asset.ts +34 -28
  49. package/src/async-element.ts +34 -8
  50. package/src/components/button-component.ts +5 -9
  51. package/src/components/camera-component.ts +24 -10
  52. package/src/components/collision-component.ts +26 -15
  53. package/src/components/component.ts +58 -8
  54. package/src/components/element-component.ts +26 -30
  55. package/src/components/gsplat-component.ts +4 -9
  56. package/src/components/layoutchild-component.ts +4 -9
  57. package/src/components/layoutgroup-component.ts +14 -9
  58. package/src/components/light-component.ts +42 -12
  59. package/src/components/listener-component.ts +1 -7
  60. package/src/components/particlesystem-component.ts +7 -15
  61. package/src/components/render-component.ts +5 -10
  62. package/src/components/rigidbody-component.ts +23 -16
  63. package/src/components/screen-component.ts +5 -9
  64. package/src/components/script-component.ts +108 -46
  65. package/src/components/script.ts +38 -33
  66. package/src/components/scrollbar-component.ts +6 -16
  67. package/src/components/scrollview-component.ts +16 -11
  68. package/src/components/sound-component.ts +10 -15
  69. package/src/components/sound-slot.ts +30 -20
  70. package/src/entity.ts +42 -19
  71. package/src/index.ts +45 -1
  72. package/src/loading-bar.ts +8 -8
  73. package/src/material.ts +63 -37
  74. package/src/model.ts +69 -14
  75. package/src/module.ts +8 -7
  76. package/src/parse.ts +62 -17
  77. package/src/scene.ts +12 -9
  78. package/src/sky.ts +50 -9
package/src/material.ts CHANGED
@@ -21,17 +21,26 @@ import {
21
21
  SPECOCC_GLOSSDEPENDENT,
22
22
  SPECOCC_NONE,
23
23
  StandardMaterial,
24
- Vec2,
25
- type EventHandle,
26
- type Texture
24
+ Vec2
27
25
  } from 'playcanvas';
26
+ import type { EventHandle, Texture } from 'playcanvas';
28
27
 
29
- import { AppElement } from './app';
28
+ import type { AppElement } from './app';
30
29
  import { AssetElement } from './asset';
31
30
  import { parseBool, parseColor, parseEnum, parseNumber, parseVec2 } from './parse';
32
31
 
33
- type BlendType = 'none' | 'normal' | 'additive' | 'additive-alpha' | 'premultiplied' |
34
- 'multiplicative' | 'multiplicative-2x' | 'screen' | 'min' | 'max' | 'subtractive';
32
+ type BlendType =
33
+ | 'none'
34
+ | 'normal'
35
+ | 'additive'
36
+ | 'additive-alpha'
37
+ | 'premultiplied'
38
+ | 'multiplicative'
39
+ | 'multiplicative-2x'
40
+ | 'screen'
41
+ | 'min'
42
+ | 'max'
43
+ | 'subtractive';
35
44
 
36
45
  const blendTypes = new Map<BlendType, number>([
37
46
  ['none', BLEND_NONE],
@@ -100,8 +109,8 @@ const roughnessAliases = ['roughness', 'roughness-map'];
100
109
  * The texture slots a `pc-material` can populate. Each is backed by a `pc-asset` id rather than a
101
110
  * `Texture`, so the element can be authored before the asset has loaded.
102
111
  */
103
- type TextureSlot = 'aoMap' | 'diffuseMap' | 'emissiveMap' | 'glossMap' | 'heightMap' |
104
- 'metalnessMap' | 'normalMap' | 'opacityMap';
112
+ type TextureSlot =
113
+ 'aoMap' | 'diffuseMap' | 'emissiveMap' | 'glossMap' | 'heightMap' | 'metalnessMap' | 'normalMap' | 'opacityMap';
105
114
 
106
115
  /**
107
116
  * The MaterialElement interface provides properties and methods for manipulating
@@ -286,7 +295,7 @@ class MaterialElement extends HTMLElement {
286
295
 
287
296
  private _useLighting = true;
288
297
 
289
- // Diverges from the engine default of false - see the class docblock and createMaterial()
298
+ // Diverges from the engine default of false - see the class docblock and _createMaterial()
290
299
  private _useMetalness = true;
291
300
 
292
301
  private _useMetalnessSpecularColor = false;
@@ -306,10 +315,14 @@ class MaterialElement extends HTMLElement {
306
315
 
307
316
  private _glossConflictWarned = false;
308
317
 
318
+ /**
319
+ * The material. `null` until the containing application has created it — an element present
320
+ * at startup has its material once the application is ready.
321
+ */
309
322
  material: StandardMaterial | null = null;
310
323
 
311
324
  async connectedCallback() {
312
- const appElement = this.parentElement?.closest('pc-app') as AppElement | null ?? null;
325
+ const appElement = (this.parentElement?.closest('pc-app') as AppElement | null) ?? null;
313
326
 
314
327
  // Materials must be direct children of pc-app (matches the boot query ':scope > pc-material')
315
328
  if (!appElement || this.parentElement !== appElement) {
@@ -326,11 +339,18 @@ class MaterialElement extends HTMLElement {
326
339
  // elements inserted (or re-inserted) after the app is already running
327
340
  if (!this.material) {
328
341
  if (!appElement.app) return; // pc-app is re-connecting; its own boot will create this
329
- this.createMaterial();
342
+ this._createMaterial();
330
343
  }
331
344
  }
332
345
 
333
- createMaterial() {
346
+ /**
347
+ * Creates the material from the element's cached properties. Called by the containing
348
+ * `<pc-app>` element during its boot sweep, and on connection for elements inserted while
349
+ * the application is already running.
350
+ *
351
+ * @internal
352
+ */
353
+ _createMaterial() {
334
354
  const material = new StandardMaterial();
335
355
  this.material = material;
336
356
 
@@ -464,10 +484,10 @@ class MaterialElement extends HTMLElement {
464
484
  * warning latches and reports once per episode, clearing when the clash is resolved.
465
485
  */
466
486
  private _warnGlossConflict() {
467
- const quote = (names: string[]) => `'${names.join('\', \'')}'`;
487
+ const quote = (names: string[]) => `'${names.join("', '")}'`;
468
488
 
469
- const roughness = roughnessAliases.filter(name => this.hasAttribute(name));
470
- const gloss = glossConflicts.filter(name => this.hasAttribute(name));
489
+ const roughness = roughnessAliases.filter((name) => this.hasAttribute(name));
490
+ const gloss = glossConflicts.filter((name) => this.hasAttribute(name));
471
491
 
472
492
  if (roughness.length === 0 || gloss.length === 0) {
473
493
  this._glossConflictWarned = false;
@@ -477,8 +497,10 @@ class MaterialElement extends HTMLElement {
477
497
  if (this._glossConflictWarned) return;
478
498
  this._glossConflictWarned = true;
479
499
 
480
- console.warn(`pc-material '${this.id}' sets both ${quote(roughness)} and ${quote(gloss)} - ` +
481
- 'the roughness-* attributes invert gloss, so the two families contradict each other. Use one or the other.');
500
+ console.warn(
501
+ `pc-material '${this.id}' sets both ${quote(roughness)} and ${quote(gloss)} - ` +
502
+ 'the roughness-* attributes invert gloss, so the two families contradict each other. Use one or the other.'
503
+ );
482
504
  }
483
505
 
484
506
  /**
@@ -488,7 +510,7 @@ class MaterialElement extends HTMLElement {
488
510
  * @param id - The id of the `pc-asset`, or an empty string to clear the slot.
489
511
  * @param slot - The material property to write.
490
512
  */
491
- setMap(id: string, slot: TextureSlot) {
513
+ private _setMap(id: string, slot: TextureSlot) {
492
514
  // Drop any load still pending for this slot - its texture is no longer the one we want
493
515
  this._mapHandles.get(slot)?.off();
494
516
  this._mapHandles.delete(slot);
@@ -509,10 +531,13 @@ class MaterialElement extends HTMLElement {
509
531
  return;
510
532
  }
511
533
 
512
- this._mapHandles.set(slot, asset.once('load', () => {
513
- this._mapHandles.delete(slot);
514
- this._applyMap(slot, asset.resource as Texture);
515
- }));
534
+ this._mapHandles.set(
535
+ slot,
536
+ asset.once('load', () => {
537
+ this._mapHandles.delete(slot);
538
+ this._applyMap(slot, asset.resource as Texture);
539
+ })
540
+ );
516
541
  }
517
542
 
518
543
  /**
@@ -592,7 +617,7 @@ class MaterialElement extends HTMLElement {
592
617
  */
593
618
  set aoMap(value: string) {
594
619
  this._aoMap = value;
595
- this.setMap(value, 'aoMap');
620
+ this._setMap(value, 'aoMap');
596
621
  }
597
622
 
598
623
  /**
@@ -850,7 +875,7 @@ class MaterialElement extends HTMLElement {
850
875
  */
851
876
  set diffuseMap(value: string) {
852
877
  this._diffuseMap = value;
853
- this.setMap(value, 'diffuseMap');
878
+ this._setMap(value, 'diffuseMap');
854
879
  }
855
880
 
856
881
  /**
@@ -1007,7 +1032,7 @@ class MaterialElement extends HTMLElement {
1007
1032
  */
1008
1033
  set emissiveMap(value: string) {
1009
1034
  this._emissiveMap = value;
1010
- this.setMap(value, 'emissiveMap');
1035
+ this._setMap(value, 'emissiveMap');
1011
1036
  }
1012
1037
 
1013
1038
  /**
@@ -1205,7 +1230,7 @@ class MaterialElement extends HTMLElement {
1205
1230
  */
1206
1231
  set glossMap(value: string) {
1207
1232
  this._glossMap = value;
1208
- this.setMap(value, 'glossMap');
1233
+ this._setMap(value, 'glossMap');
1209
1234
  }
1210
1235
 
1211
1236
  /**
@@ -1322,7 +1347,7 @@ class MaterialElement extends HTMLElement {
1322
1347
  */
1323
1348
  set heightMap(value: string) {
1324
1349
  this._heightMap = value;
1325
- this.setMap(value, 'heightMap');
1350
+ this._setMap(value, 'heightMap');
1326
1351
  }
1327
1352
 
1328
1353
  /**
@@ -1479,7 +1504,7 @@ class MaterialElement extends HTMLElement {
1479
1504
  */
1480
1505
  set metalnessMap(value: string) {
1481
1506
  this._metalnessMap = value;
1482
- this.setMap(value, 'metalnessMap');
1507
+ this._setMap(value, 'metalnessMap');
1483
1508
  }
1484
1509
 
1485
1510
  /**
@@ -1596,7 +1621,7 @@ class MaterialElement extends HTMLElement {
1596
1621
  */
1597
1622
  set normalMap(value: string) {
1598
1623
  this._normalMap = value;
1599
- this.setMap(value, 'normalMap');
1624
+ this._setMap(value, 'normalMap');
1600
1625
  }
1601
1626
 
1602
1627
  /**
@@ -1694,7 +1719,7 @@ class MaterialElement extends HTMLElement {
1694
1719
  set occludeDirect(value: boolean) {
1695
1720
  this._occludeDirect = value;
1696
1721
  if (this.material) {
1697
- // @ts-ignore see createMaterial() - the engine mistypes occludeDirect as a number
1722
+ // @ts-ignore see _createMaterial() - the engine mistypes occludeDirect as a number
1698
1723
  this.material.occludeDirect = value;
1699
1724
  this._scheduleUpdate();
1700
1725
  }
@@ -1796,7 +1821,7 @@ class MaterialElement extends HTMLElement {
1796
1821
  */
1797
1822
  set opacityMap(value: string) {
1798
1823
  this._opacityMap = value;
1799
- this.setMap(value, 'opacityMap');
1824
+ this._setMap(value, 'opacityMap');
1800
1825
  }
1801
1826
 
1802
1827
  /**
@@ -2150,6 +2175,13 @@ class MaterialElement extends HTMLElement {
2150
2175
  return this._useTonemap;
2151
2176
  }
2152
2177
 
2178
+ /**
2179
+ * Returns the {@link StandardMaterial} created by the `<pc-material>` element with the given
2180
+ * `id`, or `undefined` if there is no such element or its material has not been created yet.
2181
+ *
2182
+ * @param id - The `id` of the `<pc-material>` element.
2183
+ * @returns The material, or `undefined`.
2184
+ */
2153
2185
  static get(id: string) {
2154
2186
  const materialElement = document.querySelector<MaterialElement>(`pc-material[id="${id}"]`);
2155
2187
  return materialElement?.material;
@@ -2508,10 +2540,4 @@ class MaterialElement extends HTMLElement {
2508
2540
 
2509
2541
  customElements.define('pc-material', MaterialElement);
2510
2542
 
2511
- declare global {
2512
- interface HTMLElementTagNameMap {
2513
- 'pc-material': MaterialElement;
2514
- }
2515
- }
2516
-
2517
2543
  export { MaterialElement };
package/src/model.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ContainerResource, Entity } from 'playcanvas';
1
+ import type { ContainerResource, Entity, EventHandle } from 'playcanvas';
2
2
 
3
3
  import { AssetElement } from './asset';
4
4
  import { AsyncElement } from './async-element';
@@ -10,39 +10,83 @@ import { AsyncElement } from './async-element';
10
10
  * {@link HTMLElement} interface.
11
11
  */
12
12
  class ModelElement extends AsyncElement {
13
- private _asset: string = '';
13
+ private _asset = '';
14
14
 
15
15
  private _entity: Entity | null = null;
16
16
 
17
+ /**
18
+ * Incremented on every new load and on disconnect, and captured by a load when it starts. A
19
+ * load that resumes from an await or a load callback abandons itself if the value has moved
20
+ * on, so a superseded load can neither instantiate a second entity nor parent one that has
21
+ * since been destroyed.
22
+ */
23
+ private _loadGeneration = 0;
24
+
25
+ /**
26
+ * The pending asset-load subscription of the current load, if it is waiting for its asset.
27
+ * Held so that whatever supersedes the load can detach the handler from the asset, rather
28
+ * than leave it registered until the asset loads (or forever, if it never does).
29
+ */
30
+ private _loadHandle: EventHandle | null = null;
31
+
32
+ /**
33
+ * The root entity of the instantiated model. `null` until the container asset has loaded
34
+ * and been instantiated, and again once the element has been removed from the document.
35
+ * @returns The model's root entity, or `null`.
36
+ */
37
+ get entity(): Entity | null {
38
+ return this._entity;
39
+ }
40
+
17
41
  connectedCallback() {
18
42
  this._loadModel();
19
43
  this._onReady();
20
44
  }
21
45
 
22
46
  disconnectedCallback() {
47
+ this._loadGeneration++;
48
+ this._detachLoadHandler();
23
49
  this._unloadModel();
50
+ this._resetReady();
51
+ }
52
+
53
+ private _detachLoadHandler() {
54
+ this._loadHandle?.off();
55
+ this._loadHandle = null;
24
56
  }
25
57
 
26
58
  private _instantiate(container: ContainerResource) {
27
- this._entity = container.instantiateRenderEntity();
59
+ const generation = this._loadGeneration;
60
+
61
+ const entity = container.instantiateRenderEntity();
62
+ this._entity = entity;
28
63
 
29
64
  // @ts-ignore
30
65
  if (container.animations.length > 0) {
31
- this._entity.addComponent('anim');
66
+ entity.addComponent('anim');
32
67
  // @ts-ignore
33
- this._entity.anim.assignAnimation('animation', container.animations[0].resource);
68
+ entity.anim.assignAnimation('animation', container.animations[0].resource);
34
69
  }
35
70
 
71
+ // The parent's readiness re-arms when it is torn down, so these can resume in a later
72
+ // connection cycle. The entity is captured above and the generation re-checked, so a
73
+ // stale resume cannot parent an entity a newer cycle has already destroyed.
36
74
  const parentEntityElement = this.closestEntity;
37
75
  if (parentEntityElement) {
38
76
  parentEntityElement.ready().then(() => {
39
- parentEntityElement.entity!.addChild(this._entity!);
77
+ if (generation !== this._loadGeneration) {
78
+ return;
79
+ }
80
+ parentEntityElement.entity!.addChild(entity);
40
81
  });
41
82
  } else {
42
83
  const appElement = this.closestApp;
43
84
  if (appElement) {
44
85
  appElement.ready().then(() => {
45
- appElement.app!.root.addChild(this._entity!);
86
+ if (generation !== this._loadGeneration) {
87
+ return;
88
+ }
89
+ appElement.app!.root.addChild(entity);
46
90
  });
47
91
  }
48
92
  }
@@ -51,7 +95,17 @@ class ModelElement extends AsyncElement {
51
95
  private async _loadModel() {
52
96
  this._unloadModel();
53
97
 
98
+ // Supersede any load already in flight - only the newest load may instantiate
99
+ const generation = ++this._loadGeneration;
100
+ this._detachLoadHandler();
101
+
54
102
  const appElement = await this.closestApp?.ready();
103
+
104
+ // The element may have been removed, or another load started, while we waited
105
+ if (generation !== this._loadGeneration) {
106
+ return;
107
+ }
108
+
55
109
  const app = appElement?.app;
56
110
 
57
111
  const asset = AssetElement.get(this._asset);
@@ -62,7 +116,14 @@ class ModelElement extends AsyncElement {
62
116
  if (asset.loaded) {
63
117
  this._instantiate(asset.resource as ContainerResource);
64
118
  } else {
65
- asset.once('load', () => {
119
+ // The generation is re-checked even though a superseded handler is detached: the
120
+ // detach relies on how the engine's event emitter treats removal, while the check
121
+ // holds on its own.
122
+ this._loadHandle = asset.once('load', () => {
123
+ this._loadHandle = null;
124
+ if (generation !== this._loadGeneration) {
125
+ return;
126
+ }
66
127
  this._instantiate(asset.resource as ContainerResource);
67
128
  });
68
129
  app!.assets.load(asset);
@@ -108,10 +169,4 @@ class ModelElement extends AsyncElement {
108
169
 
109
170
  customElements.define('pc-model', ModelElement);
110
171
 
111
- declare global {
112
- interface HTMLElementTagNameMap {
113
- 'pc-model': ModelElement;
114
- }
115
- }
116
-
117
172
  export { ModelElement };
package/src/module.ts CHANGED
@@ -43,17 +43,18 @@ class ModuleElement extends HTMLElement {
43
43
  }
44
44
  }
45
45
 
46
- public getLoadPromise(): Promise<void> {
46
+ /**
47
+ * Returns the promise that settles when the module has loaded. Awaited by the containing
48
+ * `<pc-app>` element before it creates its graphics device.
49
+ *
50
+ * @returns The load promise.
51
+ * @internal
52
+ */
53
+ _getLoadPromise(): Promise<void> {
47
54
  return this.loadPromise;
48
55
  }
49
56
  }
50
57
 
51
58
  customElements.define('pc-module', ModuleElement);
52
59
 
53
- declare global {
54
- interface HTMLElementTagNameMap {
55
- 'pc-module': ModuleElement;
56
- }
57
- }
58
-
59
60
  export { ModuleElement };
package/src/parse.ts CHANGED
@@ -16,7 +16,8 @@
16
16
  * literal, and returns `null` instead of falling back to a default.
17
17
  */
18
18
 
19
- import { Color, Entity, Quat, Vec2, Vec3, Vec4 } from 'playcanvas';
19
+ import type { Entity } from 'playcanvas';
20
+ import { Color, Quat, Vec2, Vec3, Vec4 } from 'playcanvas';
20
21
 
21
22
  import { CSS_COLORS } from './colors';
22
23
 
@@ -31,7 +32,7 @@ import { CSS_COLORS } from './colors';
31
32
  */
32
33
  export const parseComponents = (value: string, count: number): number[] | null => {
33
34
  const components = value.trim().split(/\s+/).map(Number);
34
- if (components.length !== count || components.some(component => !Number.isFinite(component))) {
35
+ if (components.length !== count || components.some((component) => !Number.isFinite(component))) {
35
36
  return null;
36
37
  }
37
38
  return components;
@@ -77,7 +78,11 @@ export const parseBool = (value: string | null, defaultValue: boolean): boolean
77
78
  * @param attribute - The attribute name, used in the warning message.
78
79
  * @returns The parsed Color object.
79
80
  */
80
- export const parseColor = <T extends Color | null>(value: string | null, defaultValue: T, attribute: string): Color | T => {
81
+ export const parseColor = <T extends Color | null>(
82
+ value: string | null,
83
+ defaultValue: T,
84
+ attribute: string
85
+ ): Color | T => {
81
86
  if (value === null) {
82
87
  return cloneDefault(defaultValue);
83
88
  }
@@ -92,7 +97,10 @@ export const parseColor = <T extends Color | null>(value: string | null, default
92
97
  if (/^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)) {
93
98
  let hex = value.slice(1);
94
99
  if (hex.length === 3 || hex.length === 4) {
95
- hex = hex.split('').map(char => char + char).join('');
100
+ hex = hex
101
+ .split('')
102
+ .map((char) => char + char)
103
+ .join('');
96
104
  }
97
105
  return new Color().fromString(`#${hex}`);
98
106
  }
@@ -103,7 +111,9 @@ export const parseColor = <T extends Color | null>(value: string | null, default
103
111
  return new Color(components);
104
112
  }
105
113
 
106
- console.warn(`Invalid value '${value}' for attribute '${attribute}'. Expected a CSS color name, a hex color or 3 or 4 space-separated numbers. Using '${defaultValue}'.`);
114
+ console.warn(
115
+ `Invalid value '${value}' for attribute '${attribute}'. Expected a CSS color name, a hex color or 3 or 4 space-separated numbers. Using '${defaultValue}'.`
116
+ );
107
117
  return cloneDefault(defaultValue);
108
118
  };
109
119
 
@@ -133,7 +143,9 @@ export const parseEnum = <T extends string>(
133
143
  if (names.includes(value as T)) {
134
144
  return value as T;
135
145
  }
136
- console.warn(`Invalid value '${value}' for attribute '${attribute}'. Valid values: ${names.join(', ')}. Using '${defaultValue}'.`);
146
+ console.warn(
147
+ `Invalid value '${value}' for attribute '${attribute}'. Valid values: ${names.join(', ')}. Using '${defaultValue}'.`
148
+ );
137
149
  return defaultValue;
138
150
  };
139
151
 
@@ -147,13 +159,19 @@ export const parseEnum = <T extends string>(
147
159
  * @param attribute - The attribute name, used in the warning message.
148
160
  * @returns The parsed number.
149
161
  */
150
- export const parseNumber = <T extends number | null>(value: string | null, defaultValue: T, attribute: string): number | T => {
162
+ export const parseNumber = <T extends number | null>(
163
+ value: string | null,
164
+ defaultValue: T,
165
+ attribute: string
166
+ ): number | T => {
151
167
  if (value === null) {
152
168
  return defaultValue;
153
169
  }
154
170
  const number = value.trim() === '' ? NaN : Number(value);
155
171
  if (!Number.isFinite(number)) {
156
- console.warn(`Invalid value '${value}' for attribute '${attribute}'. Expected a finite number. Using '${defaultValue}'.`);
172
+ console.warn(
173
+ `Invalid value '${value}' for attribute '${attribute}'. Expected a finite number. Using '${defaultValue}'.`
174
+ );
157
175
  return defaultValue;
158
176
  }
159
177
  return number;
@@ -170,13 +188,19 @@ export const parseNumber = <T extends number | null>(value: string | null, defau
170
188
  * @param attribute - The attribute name, used in the warning message.
171
189
  * @returns The parsed Quat object.
172
190
  */
173
- export const parseQuat = <T extends Quat | null>(value: string | null, defaultValue: T, attribute: string): Quat | T => {
191
+ export const parseQuat = <T extends Quat | null>(
192
+ value: string | null,
193
+ defaultValue: T,
194
+ attribute: string
195
+ ): Quat | T => {
174
196
  if (value === null) {
175
197
  return cloneDefault(defaultValue);
176
198
  }
177
199
  const components = parseComponents(value, 3);
178
200
  if (!components) {
179
- console.warn(`Invalid value '${value}' for attribute '${attribute}'. Expected 3 space-separated numbers. Using '${defaultValue}'.`);
201
+ console.warn(
202
+ `Invalid value '${value}' for attribute '${attribute}'. Expected 3 space-separated numbers. Using '${defaultValue}'.`
203
+ );
180
204
  return cloneDefault(defaultValue);
181
205
  }
182
206
  return new Quat().setFromEulerAngles(components[0], components[1], components[2]);
@@ -200,7 +224,10 @@ export const parseTags = (value: string | null, defaultValue: string[] = []): st
200
224
  // caller's default, or a later mutation would write back through it.
201
225
  return [...defaultValue];
202
226
  }
203
- return value.split(',').map(tag => tag.trim()).filter(tag => tag !== '');
227
+ return value
228
+ .split(',')
229
+ .map((tag) => tag.trim())
230
+ .filter((tag) => tag !== '');
204
231
  };
205
232
 
206
233
  /**
@@ -213,13 +240,19 @@ export const parseTags = (value: string | null, defaultValue: string[] = []): st
213
240
  * @param attribute - The attribute name, used in the warning message.
214
241
  * @returns The parsed Vec2 object.
215
242
  */
216
- export const parseVec2 = <T extends Vec2 | null>(value: string | null, defaultValue: T, attribute: string): Vec2 | T => {
243
+ export const parseVec2 = <T extends Vec2 | null>(
244
+ value: string | null,
245
+ defaultValue: T,
246
+ attribute: string
247
+ ): Vec2 | T => {
217
248
  if (value === null) {
218
249
  return cloneDefault(defaultValue);
219
250
  }
220
251
  const components = parseComponents(value, 2);
221
252
  if (!components) {
222
- console.warn(`Invalid value '${value}' for attribute '${attribute}'. Expected 2 space-separated numbers. Using '${defaultValue}'.`);
253
+ console.warn(
254
+ `Invalid value '${value}' for attribute '${attribute}'. Expected 2 space-separated numbers. Using '${defaultValue}'.`
255
+ );
223
256
  return cloneDefault(defaultValue);
224
257
  }
225
258
  return new Vec2(components);
@@ -235,13 +268,19 @@ export const parseVec2 = <T extends Vec2 | null>(value: string | null, defaultVa
235
268
  * @param attribute - The attribute name, used in the warning message.
236
269
  * @returns The parsed Vec3 object.
237
270
  */
238
- export const parseVec3 = <T extends Vec3 | null>(value: string | null, defaultValue: T, attribute: string): Vec3 | T => {
271
+ export const parseVec3 = <T extends Vec3 | null>(
272
+ value: string | null,
273
+ defaultValue: T,
274
+ attribute: string
275
+ ): Vec3 | T => {
239
276
  if (value === null) {
240
277
  return cloneDefault(defaultValue);
241
278
  }
242
279
  const components = parseComponents(value, 3);
243
280
  if (!components) {
244
- console.warn(`Invalid value '${value}' for attribute '${attribute}'. Expected 3 space-separated numbers. Using '${defaultValue}'.`);
281
+ console.warn(
282
+ `Invalid value '${value}' for attribute '${attribute}'. Expected 3 space-separated numbers. Using '${defaultValue}'.`
283
+ );
245
284
  return cloneDefault(defaultValue);
246
285
  }
247
286
  return new Vec3(components);
@@ -257,13 +296,19 @@ export const parseVec3 = <T extends Vec3 | null>(value: string | null, defaultVa
257
296
  * @param attribute - The attribute name, used in the warning message.
258
297
  * @returns The parsed Vec4 object.
259
298
  */
260
- export const parseVec4 = <T extends Vec4 | null>(value: string | null, defaultValue: T, attribute: string): Vec4 | T => {
299
+ export const parseVec4 = <T extends Vec4 | null>(
300
+ value: string | null,
301
+ defaultValue: T,
302
+ attribute: string
303
+ ): Vec4 | T => {
261
304
  if (value === null) {
262
305
  return cloneDefault(defaultValue);
263
306
  }
264
307
  const components = parseComponents(value, 4);
265
308
  if (!components) {
266
- console.warn(`Invalid value '${value}' for attribute '${attribute}'. Expected 4 space-separated numbers. Using '${defaultValue}'.`);
309
+ console.warn(
310
+ `Invalid value '${value}' for attribute '${attribute}'. Expected 4 space-separated numbers. Using '${defaultValue}'.`
311
+ );
267
312
  return cloneDefault(defaultValue);
268
313
  }
269
314
  return new Vec4(components);
package/src/scene.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { Color, Scene, Vec3 } from 'playcanvas';
1
+ import type { Scene } from 'playcanvas';
2
+ import { Color, Vec3 } from 'playcanvas';
2
3
 
3
4
  import { AsyncElement } from './async-element';
4
5
  import { parseColor, parseEnum, parseNumber, parseVec3 } from './parse';
@@ -77,12 +78,20 @@ class SceneElement extends AsyncElement {
77
78
  }
78
79
 
79
80
  this._scene = app.scene;
80
- this.updateSceneSettings();
81
+ this._updateSceneSettings();
81
82
 
82
83
  this._onReady();
83
84
  }
84
85
 
85
- updateSceneSettings() {
86
+ disconnectedCallback() {
87
+ // The scene belongs to the application, and removing this element - or the <pc-app>
88
+ // above it, which disconnects first - parts the two. Re-arm readiness so a re-inserted
89
+ // element announces the scene it acquires then, not the one it lost here.
90
+ this._scene = null;
91
+ this._resetReady();
92
+ }
93
+
94
+ private _updateSceneSettings() {
86
95
  if (this._scene) {
87
96
  this._scene.fog.type = this._fog;
88
97
  this._scene.fog.color = this._fogColor;
@@ -251,10 +260,4 @@ class SceneElement extends AsyncElement {
251
260
 
252
261
  customElements.define('pc-scene', SceneElement);
253
262
 
254
- declare global {
255
- interface HTMLElementTagNameMap {
256
- 'pc-scene': SceneElement;
257
- }
258
- }
259
-
260
263
  export { SceneElement };