@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.
Files changed (82) hide show
  1. package/README.md +1 -1
  2. package/dist/app.d.ts +31 -49
  3. package/dist/asset.d.ts +152 -12
  4. package/dist/async-element.d.ts +26 -9
  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 +19 -7
  8. package/dist/components/component.d.ts +41 -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 +5824 -10581
  26. package/dist/entity-base.d.ts +67 -0
  27. package/dist/entity.d.ts +7 -48
  28. package/dist/index.d.ts +41 -1
  29. package/dist/material.d.ts +14 -13
  30. package/dist/model.d.ts +43 -5
  31. package/dist/module.d.ts +0 -6
  32. package/dist/node.d.ts +253 -0
  33. package/dist/parse.d.ts +2 -1
  34. package/dist/pwc.cjs +1830 -274
  35. package/dist/pwc.cjs.map +1 -1
  36. package/dist/pwc.js +1830 -274
  37. package/dist/pwc.js.map +1 -1
  38. package/dist/pwc.min.js +1 -1
  39. package/dist/pwc.min.js.map +1 -1
  40. package/dist/pwc.min.mjs +1 -1
  41. package/dist/pwc.min.mjs.map +1 -1
  42. package/dist/pwc.mjs +1830 -276
  43. package/dist/pwc.mjs.map +1 -1
  44. package/dist/scene.d.ts +4 -7
  45. package/dist/sky.d.ts +13 -5
  46. package/dist/vscode.html-custom-data.json +148 -26
  47. package/dist/web-types.json +894 -581
  48. package/package.json +9 -8
  49. package/src/app.ts +163 -88
  50. package/src/asset.ts +472 -36
  51. package/src/async-element.ts +39 -12
  52. package/src/components/button-component.ts +5 -9
  53. package/src/components/camera-component.ts +24 -10
  54. package/src/components/collision-component.ts +61 -15
  55. package/src/components/component.ts +151 -11
  56. package/src/components/element-component.ts +26 -30
  57. package/src/components/gsplat-component.ts +4 -9
  58. package/src/components/layoutchild-component.ts +4 -9
  59. package/src/components/layoutgroup-component.ts +14 -9
  60. package/src/components/light-component.ts +42 -12
  61. package/src/components/listener-component.ts +1 -7
  62. package/src/components/particlesystem-component.ts +7 -15
  63. package/src/components/render-component.ts +5 -10
  64. package/src/components/rigidbody-component.ts +23 -16
  65. package/src/components/screen-component.ts +5 -9
  66. package/src/components/script-component.ts +108 -46
  67. package/src/components/script.ts +38 -33
  68. package/src/components/scrollbar-component.ts +6 -16
  69. package/src/components/scrollview-component.ts +16 -11
  70. package/src/components/sound-component.ts +10 -15
  71. package/src/components/sound-slot.ts +30 -20
  72. package/src/entity-base.ts +136 -0
  73. package/src/entity.ts +47 -118
  74. package/src/index.ts +50 -1
  75. package/src/loading-bar.ts +8 -8
  76. package/src/material.ts +65 -39
  77. package/src/model.ts +140 -17
  78. package/src/module.ts +8 -7
  79. package/src/node.ts +715 -0
  80. package/src/parse.ts +62 -17
  81. package/src/scene.ts +12 -9
  82. package/src/sky.ts +50 -10
@@ -1,9 +1,21 @@
1
- import { Color, Quat, ScriptComponent, Script, Vec2, Vec3, Vec4 } from 'playcanvas';
1
+ import type { ScriptComponent, Script } from 'playcanvas';
2
+ import { Color, Quat, Vec2, Vec3, Vec4 } from 'playcanvas';
2
3
 
3
4
  import { AssetElement } from '../asset';
5
+ import {
6
+ getEntity,
7
+ parseBool,
8
+ parseColor,
9
+ parseComponents,
10
+ parseNumber,
11
+ parseQuat,
12
+ parseVec2,
13
+ parseVec3,
14
+ parseVec4
15
+ } from '../parse';
16
+
4
17
  import { ComponentElement } from './component';
5
18
  import { ScriptElement } from './script';
6
- import { getEntity, parseBool, parseColor, parseComponents, parseNumber, parseQuat, parseVec2, parseVec3, parseVec4 } from '../parse';
7
19
 
8
20
  /**
9
21
  * Attributes on `pc-script` that never map to script attributes: the element's own API (derived
@@ -11,10 +23,34 @@ import { getEntity, parseBool, parseColor, parseComponents, parseNumber, parseQu
11
23
  */
12
24
  const RESERVED_ATTRIBUTES = new Set([
13
25
  ...ScriptElement.observedAttributes,
14
- 'accesskey', 'autocapitalize', 'autofocus', 'class', 'contenteditable', 'dir', 'draggable',
15
- 'exportparts', 'hidden', 'id', 'inert', 'is', 'itemid', 'itemprop', 'itemref', 'itemscope',
16
- 'itemtype', 'lang', 'nonce', 'part', 'popover', 'role', 'slot', 'spellcheck', 'style',
17
- 'tabindex', 'title', 'translate'
26
+ 'accesskey',
27
+ 'autocapitalize',
28
+ 'autofocus',
29
+ 'class',
30
+ 'contenteditable',
31
+ 'dir',
32
+ 'draggable',
33
+ 'exportparts',
34
+ 'hidden',
35
+ 'id',
36
+ 'inert',
37
+ 'is',
38
+ 'itemid',
39
+ 'itemprop',
40
+ 'itemref',
41
+ 'itemscope',
42
+ 'itemtype',
43
+ 'lang',
44
+ 'nonce',
45
+ 'part',
46
+ 'popover',
47
+ 'role',
48
+ 'slot',
49
+ 'spellcheck',
50
+ 'style',
51
+ 'tabindex',
52
+ 'title',
53
+ 'translate'
18
54
  ]);
19
55
 
20
56
  /**
@@ -27,11 +63,13 @@ const RESERVED_ATTRIBUTES = new Set([
27
63
  * @returns Whether the attribute name is reserved.
28
64
  */
29
65
  const isReservedAttribute = (name: string): boolean => {
30
- return RESERVED_ATTRIBUTES.has(name) ||
66
+ return (
67
+ RESERVED_ATTRIBUTES.has(name) ||
31
68
  name.startsWith('data-') ||
32
69
  name.startsWith('aria-') ||
33
70
  name.startsWith('_') ||
34
- (name.startsWith('on') && name in HTMLElement.prototype);
71
+ (name.startsWith('on') && name in HTMLElement.prototype)
72
+ );
35
73
  };
36
74
 
37
75
  /**
@@ -39,7 +77,14 @@ const isReservedAttribute = (name: string): boolean => {
39
77
  * the (optional, so possibly undefined) lifecycle methods.
40
78
  */
41
79
  const SCRIPT_API_MEMBERS = new Set([
42
- 'app', 'entity', 'destroy', 'initialize', 'postInitialize', 'postUpdate', 'swap', 'update'
80
+ 'app',
81
+ 'entity',
82
+ 'destroy',
83
+ 'initialize',
84
+ 'postInitialize',
85
+ 'postUpdate',
86
+ 'swap',
87
+ 'update'
43
88
  ]);
44
89
 
45
90
  /**
@@ -57,7 +102,7 @@ const kebabToCamel = (name: string): string => {
57
102
  * @returns The kebab-case name.
58
103
  */
59
104
  const camelToKebab = (name: string): string => {
60
- return name.replace(/[A-Z]/g, char => `-${char.toLowerCase()}`);
105
+ return name.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
61
106
  };
62
107
 
63
108
  /**
@@ -110,7 +155,9 @@ const vectorConversion = (length: 2 | 3 | 4, Ctor: new (components: number[]) =>
110
155
  if (components) {
111
156
  return new Ctor(components);
112
157
  }
113
- console.warn(`Invalid script attribute value '${raw}'. Expected ${length} space-separated numbers after 'vec${length}:'.`);
158
+ console.warn(
159
+ `Invalid script attribute value '${raw}'. Expected ${length} space-separated numbers after 'vec${length}:'.`
160
+ );
114
161
  return raw;
115
162
  };
116
163
  };
@@ -183,27 +230,17 @@ const findCaseMatch = (script: any, key: string): string | null => {
183
230
  return null;
184
231
  };
185
232
 
186
- // Add these interfaces at the top of the file, after the imports
187
- interface ScriptAttributesChangeEvent extends CustomEvent {
233
+ export type ScriptAttributesChangeEvent = {
188
234
  detail: { attributes: Record<string, any> };
189
- }
235
+ } & CustomEvent;
190
236
 
191
- interface ScriptEnableChangeEvent extends CustomEvent {
237
+ export type ScriptEnableChangeEvent = {
192
238
  detail: { enabled: boolean };
193
- }
194
-
195
- interface ScriptNameChangeEvent extends CustomEvent {
196
- detail: { oldName: string, newName: string };
197
- }
239
+ } & CustomEvent;
198
240
 
199
- // Add this interface before the ScriptComponentElement class
200
- declare global {
201
- interface HTMLElementEventMap {
202
- 'scriptattributeschange': ScriptAttributesChangeEvent;
203
- 'scriptenablechange': ScriptEnableChangeEvent;
204
- 'scriptnamechange': ScriptNameChangeEvent;
205
- }
206
- }
241
+ export type ScriptNameChangeEvent = {
242
+ detail: { oldName: string; newName: string };
243
+ } & CustomEvent;
207
244
 
208
245
  /**
209
246
  * The ScriptComponentElement interface provides properties and methods for manipulating
@@ -237,7 +274,7 @@ class ScriptComponentElement extends ComponentElement {
237
274
  return super.connectedCallback();
238
275
  }
239
276
 
240
- initComponent() {
277
+ protected initComponent() {
241
278
  // Handle initial script elements
242
279
  this.querySelectorAll<ScriptElement>(':scope > pc-script').forEach((scriptElement) => {
243
280
  this.createScript(scriptElement);
@@ -303,7 +340,12 @@ class ScriptComponentElement extends ComponentElement {
303
340
  // Only recurse into plain objects. Class instances (Vec3, Color, Asset, Entity...)
304
341
  // are leaf values assigned whole, so accessor-typed script attributes receive them
305
342
  // through their setters instead of having a getter's returned copy mutated.
306
- if (value && typeof value === 'object' && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype) {
343
+ if (
344
+ value &&
345
+ typeof value === 'object' &&
346
+ !Array.isArray(value) &&
347
+ Object.getPrototypeOf(value) === Object.prototype
348
+ ) {
307
349
  if (!current || typeof current !== 'object') {
308
350
  target[key] = {};
309
351
  }
@@ -321,7 +363,13 @@ class ScriptComponentElement extends ComponentElement {
321
363
  * @returns Whether the value is a math type.
322
364
  */
323
365
  private isMathType(value: any): value is Vec2 | Vec3 | Vec4 | Color | Quat {
324
- return value instanceof Vec2 || value instanceof Vec3 || value instanceof Vec4 || value instanceof Color || value instanceof Quat;
366
+ return (
367
+ value instanceof Vec2 ||
368
+ value instanceof Vec3 ||
369
+ value instanceof Vec4 ||
370
+ value instanceof Color ||
371
+ value instanceof Quat
372
+ );
325
373
  }
326
374
 
327
375
  /**
@@ -334,15 +382,22 @@ class ScriptComponentElement extends ComponentElement {
334
382
  * @param key - The property name, used in the warning message.
335
383
  * @returns The converted value, or `null`.
336
384
  */
337
- private arrayToMathType(current: Vec2 | Vec3 | Vec4 | Color | Quat, value: any[], key: string): Vec2 | Vec3 | Vec4 | Color | Quat | null {
338
- if (value.every(component => typeof component === 'number' && Number.isFinite(component))) {
385
+ private arrayToMathType(
386
+ current: Vec2 | Vec3 | Vec4 | Color | Quat,
387
+ value: any[],
388
+ key: string
389
+ ): Vec2 | Vec3 | Vec4 | Color | Quat | null {
390
+ if (value.every((component) => typeof component === 'number' && Number.isFinite(component))) {
339
391
  if (current instanceof Vec2 && value.length === 2) return new Vec2(value);
340
392
  if (current instanceof Vec3 && value.length === 3) return new Vec3(value);
341
393
  if (current instanceof Vec4 && value.length === 4) return new Vec4(value);
342
394
  if (current instanceof Color && (value.length === 3 || value.length === 4)) return new Color(value);
343
- if (current instanceof Quat && value.length === 3) return new Quat().setFromEulerAngles(value[0], value[1], value[2]);
395
+ if (current instanceof Quat && value.length === 3)
396
+ return new Quat().setFromEulerAngles(value[0], value[1], value[2]);
344
397
  }
345
- console.warn(`Cannot convert script attribute '${key}' array [${value}] to ${current.constructor.name}. Keeping the current value.`);
398
+ console.warn(
399
+ `Cannot convert script attribute '${key}' array [${value}] to ${current.constructor.name}. Keeping the current value.`
400
+ );
346
401
  return null;
347
402
  }
348
403
 
@@ -540,7 +595,9 @@ class ScriptComponentElement extends ComponentElement {
540
595
  const current = script[key];
541
596
 
542
597
  if (typeof current === 'function' || SCRIPT_API_MEMBERS.has(key)) {
543
- console.warn(`Ignoring attribute '${attributeName}' on pc-script '${scriptName}' - '${key}' is part of the Script API.`);
598
+ console.warn(
599
+ `Ignoring attribute '${attributeName}' on pc-script '${scriptName}' - '${key}' is part of the Script API.`
600
+ );
544
601
  return;
545
602
  }
546
603
 
@@ -570,14 +627,20 @@ class ScriptComponentElement extends ComponentElement {
570
627
  } else {
571
628
  const match = findCaseMatch(script, key);
572
629
  if (match) {
573
- console.warn(`Script '${scriptName}' has no attribute '${key}' - did you mean '${camelToKebab(match)}'? Attribute names are kebab-case.`);
630
+ console.warn(
631
+ `Script '${scriptName}' has no attribute '${key}' - did you mean '${camelToKebab(match)}'? Attribute names are kebab-case.`
632
+ );
574
633
  return;
575
634
  }
576
- console.warn(`Script '${scriptName}' has no typed attribute '${key}' - assigning the raw string from '${attributeName}'.`);
635
+ console.warn(
636
+ `Script '${scriptName}' has no typed attribute '${key}' - assigning the raw string from '${attributeName}'.`
637
+ );
577
638
  script[key] = value;
578
639
  }
579
640
  } catch (error) {
580
- console.warn(`Error applying attribute '${attributeName}' to script '${scriptName}': ${(error as Error).message}`);
641
+ console.warn(
642
+ `Error applying attribute '${attributeName}' to script '${scriptName}': ${(error as Error).message}`
643
+ );
581
644
  }
582
645
  }
583
646
 
@@ -616,7 +679,12 @@ class ScriptComponentElement extends ComponentElement {
616
679
  mutation.removedNodes.forEach((node) => {
617
680
  if (node instanceof ScriptElement) {
618
681
  const scriptName = node.getAttribute('name');
619
- if (scriptName && node._script && this.component && this.component.get(scriptName) === node._script) {
682
+ if (
683
+ scriptName &&
684
+ node._script &&
685
+ this.component &&
686
+ this.component.get(scriptName) === node._script
687
+ ) {
620
688
  this.destroyScript(scriptName);
621
689
  }
622
690
  node._script = null;
@@ -648,10 +716,4 @@ class ScriptComponentElement extends ComponentElement {
648
716
 
649
717
  customElements.define('pc-scripts', ScriptComponentElement);
650
718
 
651
- declare global {
652
- interface HTMLElementTagNameMap {
653
- 'pc-scripts': ScriptComponentElement;
654
- }
655
- }
656
-
657
719
  export { ScriptComponentElement };
@@ -1,4 +1,4 @@
1
- import { Script } from 'playcanvas';
1
+ import type { Script } from 'playcanvas';
2
2
 
3
3
  import { AsyncElement } from '../async-element';
4
4
  import { parseBool } from '../parse';
@@ -39,18 +39,11 @@ import { parseBool } from '../parse';
39
39
  class ScriptElement extends AsyncElement {
40
40
  private _attributes: Record<string, any> = {};
41
41
 
42
- private _enabled: boolean = true;
43
-
44
- /**
45
- * Whether readiness has been signalled. Creation can happen more than once over an
46
- * element's life (a runtime `name` change recreates the instance), but `ready` is a
47
- * one-shot signal, so only the first successful creation fires it.
48
- */
49
- private _readySignalled: boolean = false;
42
+ private _enabled = true;
50
43
 
51
44
  /**
52
45
  * The Script instance created for this element by its parent `<pc-scripts>` element.
53
- * @ignore
46
+ * @internal
54
47
  */
55
48
  _script: Script | null = null;
56
49
 
@@ -64,10 +57,12 @@ class ScriptElement extends AsyncElement {
64
57
  */
65
58
  set scriptAttributes(value: Record<string, any>) {
66
59
  this._attributes = value ?? {};
67
- this.dispatchEvent(new CustomEvent('scriptattributeschange', {
68
- detail: { attributes: this._attributes },
69
- bubbles: true
70
- }));
60
+ this.dispatchEvent(
61
+ new CustomEvent('scriptattributeschange', {
62
+ detail: { attributes: this._attributes },
63
+ bubbles: true
64
+ })
65
+ );
71
66
  }
72
67
 
73
68
  /**
@@ -84,10 +79,12 @@ class ScriptElement extends AsyncElement {
84
79
  */
85
80
  set enabled(value: boolean) {
86
81
  this._enabled = value;
87
- this.dispatchEvent(new CustomEvent('scriptenablechange', {
88
- detail: { enabled: value },
89
- bubbles: true
90
- }));
82
+ this.dispatchEvent(
83
+ new CustomEvent('scriptenablechange', {
84
+ detail: { enabled: value },
85
+ bubbles: true
86
+ })
87
+ );
91
88
  }
92
89
 
93
90
  /**
@@ -137,17 +134,27 @@ class ScriptElement extends AsyncElement {
137
134
  // Script instances are created by the parent pc-scripts element, so an element placed
138
135
  // anywhere else is inert and never becomes ready - warn rather than hang silently
139
136
  if (this.parentElement?.tagName !== 'PC-SCRIPTS') {
140
- console.warn(`pc-script '${this.getAttribute('name')}' must be a direct child of pc-scripts - script not created`);
137
+ console.warn(
138
+ `pc-script '${this.getAttribute('name')}' must be a direct child of pc-scripts - script not created`
139
+ );
141
140
  }
142
141
  }
143
142
 
143
+ disconnectedCallback() {
144
+ // Re-arm readiness so a re-inserted element announces the instance created for it then.
145
+ // `_script` is deliberately NOT cleared here: the parent's mutation observer processes
146
+ // this removal afterwards and reads it to establish which engine script this element
147
+ // owned - the parent is what clears it.
148
+ this._resetReady();
149
+ }
150
+
144
151
  /**
145
152
  * Called by the parent `<pc-scripts>` element when the script instance has been created.
146
- * @ignore
153
+ * Creation can happen more than once per connection (a runtime `name` change recreates the
154
+ * instance), but `_onReady` signals readiness at most once per cycle.
155
+ * @internal
147
156
  */
148
157
  _onScriptCreated() {
149
- if (this._readySignalled) return;
150
- this._readySignalled = true;
151
158
  this._onReady();
152
159
  }
153
160
 
@@ -165,7 +172,9 @@ class ScriptElement extends AsyncElement {
165
172
  try {
166
173
  this.scriptAttributes = JSON.parse(newValue);
167
174
  } catch (error) {
168
- console.warn(`Invalid 'attributes' JSON on pc-script '${this.getAttribute('name')}': ${(error as Error).message}`);
175
+ console.warn(
176
+ `Invalid 'attributes' JSON on pc-script '${this.getAttribute('name')}': ${(error as Error).message}`
177
+ );
169
178
  }
170
179
  break;
171
180
  case 'enabled':
@@ -176,10 +185,12 @@ class ScriptElement extends AsyncElement {
176
185
  // the added-node mutation), so only a genuine rename is signalled here. Note
177
186
  // that setAttribute fires this callback even when the value is unchanged.
178
187
  if (oldValue !== null && oldValue !== newValue) {
179
- this.dispatchEvent(new CustomEvent('scriptnamechange', {
180
- detail: { oldName: oldValue, newName: newValue },
181
- bubbles: true
182
- }));
188
+ this.dispatchEvent(
189
+ new CustomEvent('scriptnamechange', {
190
+ detail: { oldName: oldValue, newName: newValue },
191
+ bubbles: true
192
+ })
193
+ );
183
194
  }
184
195
  break;
185
196
  }
@@ -188,10 +199,4 @@ class ScriptElement extends AsyncElement {
188
199
 
189
200
  customElements.define('pc-script', ScriptElement);
190
201
 
191
- declare global {
192
- interface HTMLElementTagNameMap {
193
- 'pc-script': ScriptElement;
194
- }
195
- }
196
-
197
202
  export { ScriptElement };
@@ -1,8 +1,10 @@
1
- import { ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL, ScrollbarComponent } from 'playcanvas';
1
+ import type { ScrollbarComponent } from 'playcanvas';
2
+ import { ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL } from 'playcanvas';
2
3
 
3
- import { ComponentElement } from './component';
4
4
  import { getEntity, parseEnum, parseNumber } from '../parse';
5
5
 
6
+ import { ComponentElement } from './component';
7
+
6
8
  const orientations = new Map<'horizontal' | 'vertical', number>([
7
9
  ['horizontal', ORIENTATION_HORIZONTAL],
8
10
  ['vertical', ORIENTATION_VERTICAL]
@@ -30,7 +32,7 @@ class ScrollbarComponentElement extends ComponentElement {
30
32
  super('scrollbar');
31
33
  }
32
34
 
33
- getInitialComponentData() {
35
+ protected getInitialComponentData() {
34
36
  const data: Record<string, any> = {
35
37
  orientation: orientations.get(this._orientation),
36
38
  value: this._value,
@@ -133,13 +135,7 @@ class ScrollbarComponentElement extends ComponentElement {
133
135
  }
134
136
 
135
137
  static get observedAttributes() {
136
- return [
137
- ...super.observedAttributes,
138
- 'orientation',
139
- 'value',
140
- 'handle-size',
141
- 'handle'
142
- ];
138
+ return [...super.observedAttributes, 'orientation', 'value', 'handle-size', 'handle'];
143
139
  }
144
140
 
145
141
  attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
@@ -164,10 +160,4 @@ class ScrollbarComponentElement extends ComponentElement {
164
160
 
165
161
  customElements.define('pc-scrollbar', ScrollbarComponentElement);
166
162
 
167
- declare global {
168
- interface HTMLElementTagNameMap {
169
- 'pc-scrollbar': ScrollbarComponentElement;
170
- }
171
- }
172
-
173
163
  export { ScrollbarComponentElement };
@@ -1,8 +1,17 @@
1
- import { SCROLL_MODE_BOUNCE, SCROLL_MODE_CLAMP, SCROLL_MODE_INFINITE, SCROLLBAR_VISIBILITY_SHOW_ALWAYS, SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED, ScrollViewComponent, Vec2 } from 'playcanvas';
1
+ import type { ScrollViewComponent } from 'playcanvas';
2
+ import {
3
+ SCROLL_MODE_BOUNCE,
4
+ SCROLL_MODE_CLAMP,
5
+ SCROLL_MODE_INFINITE,
6
+ SCROLLBAR_VISIBILITY_SHOW_ALWAYS,
7
+ SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED,
8
+ Vec2
9
+ } from 'playcanvas';
2
10
 
3
- import { ComponentElement } from './component';
4
11
  import { getEntity, parseBool, parseEnum, parseNumber, parseVec2 } from '../parse';
5
12
 
13
+ import { ComponentElement } from './component';
14
+
6
15
  const scrollModes = new Map<'clamp' | 'bounce' | 'infinite', number>([
7
16
  ['clamp', SCROLL_MODE_CLAMP],
8
17
  ['bounce', SCROLL_MODE_BOUNCE],
@@ -54,7 +63,7 @@ class ScrollViewComponentElement extends ComponentElement {
54
63
  super('scrollview');
55
64
  }
56
65
 
57
- getInitialComponentData() {
66
+ protected getInitialComponentData() {
58
67
  const data: Record<string, any> = {
59
68
  horizontal: this._horizontal,
60
69
  vertical: this._vertical,
@@ -244,7 +253,8 @@ class ScrollViewComponentElement extends ComponentElement {
244
253
  set horizontalScrollbarVisibility(value: 'always' | 'when-required') {
245
254
  this._horizontalScrollbarVisibility = value;
246
255
  if (this.component) {
247
- this.component.horizontalScrollbarVisibility = visibilities.get(value) ?? SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
256
+ this.component.horizontalScrollbarVisibility =
257
+ visibilities.get(value) ?? SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
248
258
  }
249
259
  }
250
260
 
@@ -264,7 +274,8 @@ class ScrollViewComponentElement extends ComponentElement {
264
274
  set verticalScrollbarVisibility(value: 'always' | 'when-required') {
265
275
  this._verticalScrollbarVisibility = value;
266
276
  if (this.component) {
267
- this.component.verticalScrollbarVisibility = visibilities.get(value) ?? SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
277
+ this.component.verticalScrollbarVisibility =
278
+ visibilities.get(value) ?? SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
268
279
  }
269
280
  }
270
281
 
@@ -428,10 +439,4 @@ class ScrollViewComponentElement extends ComponentElement {
428
439
 
429
440
  customElements.define('pc-scrollview', ScrollViewComponentElement);
430
441
 
431
- declare global {
432
- interface HTMLElementTagNameMap {
433
- 'pc-scrollview': ScrollViewComponentElement;
434
- }
435
- }
436
-
437
442
  export { ScrollViewComponentElement };
@@ -1,8 +1,9 @@
1
- import { SoundComponent } from 'playcanvas';
1
+ import type { SoundComponent } from 'playcanvas';
2
2
 
3
- import { ComponentElement } from './component';
4
3
  import { parseBool, parseEnum, parseNumber } from '../parse';
5
4
 
5
+ import { ComponentElement } from './component';
6
+
6
7
  /**
7
8
  * The SoundComponentElement interface provides properties and methods for manipulating
8
9
  * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-sounds/ | `<pc-sounds>`} elements.
@@ -14,24 +15,24 @@ import { parseBool, parseEnum, parseNumber } from '../parse';
14
15
  class SoundComponentElement extends ComponentElement {
15
16
  private _distanceModel: 'exponential' | 'inverse' | 'linear' = 'linear';
16
17
 
17
- private _maxDistance: number = 10000;
18
+ private _maxDistance = 10000;
18
19
 
19
- private _pitch: number = 1;
20
+ private _pitch = 1;
20
21
 
21
- private _positional: boolean = false;
22
+ private _positional = false;
22
23
 
23
- private _refDistance: number = 1;
24
+ private _refDistance = 1;
24
25
 
25
- private _rollOffFactor: number = 1;
26
+ private _rollOffFactor = 1;
26
27
 
27
- private _volume: number = 1;
28
+ private _volume = 1;
28
29
 
29
30
  /** @ignore */
30
31
  constructor() {
31
32
  super('sound');
32
33
  }
33
34
 
34
- getInitialComponentData() {
35
+ protected getInitialComponentData() {
35
36
  return {
36
37
  distanceModel: this._distanceModel,
37
38
  maxDistance: this._maxDistance,
@@ -228,10 +229,4 @@ class SoundComponentElement extends ComponentElement {
228
229
 
229
230
  customElements.define('pc-sounds', SoundComponentElement);
230
231
 
231
- declare global {
232
- interface HTMLElementTagNameMap {
233
- 'pc-sounds': SoundComponentElement;
234
- }
235
- }
236
-
237
232
  export { SoundComponentElement };
@@ -1,33 +1,34 @@
1
- import { SoundSlot } from 'playcanvas';
1
+ import type { SoundSlot } from 'playcanvas';
2
2
 
3
3
  import { AssetElement } from '../asset';
4
4
  import { AsyncElement } from '../async-element';
5
- import { SoundComponentElement } from './sound-component';
6
5
  import { parseBool, parseNumber } from '../parse';
7
6
 
7
+ import { SoundComponentElement } from './sound-component';
8
+
8
9
  /**
9
10
  * The SoundSlotElement interface provides properties and methods for manipulating
10
11
  * `<pc-sound>` elements. The SoundSlotElement interface also inherits the properties and
11
12
  * methods of the {@link AsyncElement} interface.
12
13
  */
13
14
  class SoundSlotElement extends AsyncElement {
14
- private _asset: string = '';
15
+ private _asset = '';
15
16
 
16
- private _autoPlay: boolean = false;
17
+ private _autoPlay = false;
17
18
 
18
19
  private _duration: number | null = null;
19
20
 
20
- private _loop: boolean = false;
21
+ private _loop = false;
21
22
 
22
- private _name: string = '';
23
+ private _name = '';
23
24
 
24
- private _overlap: boolean = false;
25
+ private _overlap = false;
25
26
 
26
- private _pitch: number = 1;
27
+ private _pitch = 1;
27
28
 
28
- private _startTime: number = 0;
29
+ private _startTime = 0;
29
30
 
30
- private _volume: number = 1;
31
+ private _volume = 1;
31
32
 
32
33
  /**
33
34
  * The `<pc-sounds>` this slot was added to, captured at connect time.
@@ -38,20 +39,31 @@ class SoundSlotElement extends AsyncElement {
38
39
  */
39
40
  private _soundElement: SoundComponentElement | null = null;
40
41
 
42
+ /**
43
+ * Incremented on every connect and disconnect, and captured by connectedCallback on entry —
44
+ * a resume from an await abandons itself if the value has moved on, so a stale callback can
45
+ * neither act on a torn-down tree nor add its slot alongside a re-inserted element's own
46
+ * callback.
47
+ */
48
+ private _connectionGeneration = 0;
49
+
41
50
  /**
42
51
  * The sound slot.
43
52
  */
44
53
  soundSlot: SoundSlot | null = null;
45
54
 
46
55
  async connectedCallback() {
56
+ const generation = ++this._connectionGeneration;
57
+
47
58
  const soundElement = this.soundElement;
48
59
  await soundElement?.ready();
49
60
 
50
- // The element may have been removed, or its parent torn down, while we were waiting. A
51
- // <pc-app> disconnects before its children, so by the time we resume the component can
52
- // already be gone - see the matching guard in disconnectedCallback below.
61
+ // The element may have been removed (perhaps re-inserted, which runs a callback of its
62
+ // own), or its parent torn down, while we were waiting. A <pc-app> disconnects before
63
+ // its children, so by the time we resume the component can already be gone - see the
64
+ // matching guard in disconnectedCallback below.
53
65
  const component = soundElement?.component;
54
- if (!this.isConnected || !component) {
66
+ if (generation !== this._connectionGeneration || !component) {
55
67
  return;
56
68
  }
57
69
 
@@ -78,12 +90,16 @@ class SoundSlotElement extends AsyncElement {
78
90
  }
79
91
 
80
92
  disconnectedCallback() {
93
+ // Invalidate any connectedCallback still suspended on an await
94
+ this._connectionGeneration++;
95
+
81
96
  // Uses the cached parent rather than a fresh lookup, since parentElement is already null
82
97
  // by now. The component itself is null if the parent <pc-sound> (or the whole <pc-app>) is
83
98
  // being torn down — parents disconnect first and have already removed the component.
84
99
  this._soundElement?.component?.removeSlot(this._name);
85
100
  this._soundElement = null;
86
101
  this.soundSlot = null;
102
+ this._resetReady();
87
103
  }
88
104
 
89
105
  protected get soundElement(): SoundComponentElement | null {
@@ -310,10 +326,4 @@ class SoundSlotElement extends AsyncElement {
310
326
 
311
327
  customElements.define('pc-sound', SoundSlotElement);
312
328
 
313
- declare global {
314
- interface HTMLElementTagNameMap {
315
- 'pc-sound': SoundSlotElement;
316
- }
317
- }
318
-
319
329
  export { SoundSlotElement };