@playcanvas/web-components 0.8.1 → 0.9.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 (71) hide show
  1. package/dist/app.d.ts +10 -2
  2. package/dist/asset.d.ts +18 -3
  3. package/dist/async-element.d.ts +51 -4
  4. package/dist/components/button-component.d.ts +10 -4
  5. package/dist/components/camera-component.d.ts +9 -4
  6. package/dist/components/collision-component.d.ts +9 -4
  7. package/dist/components/component.d.ts +7 -1
  8. package/dist/components/element-component.d.ts +17 -12
  9. package/dist/components/gsplat-component.d.ts +6 -1
  10. package/dist/components/layoutchild-component.d.ts +6 -1
  11. package/dist/components/layoutgroup-component.d.ts +21 -15
  12. package/dist/components/light-component.d.ts +12 -6
  13. package/dist/components/listener-component.d.ts +6 -1
  14. package/dist/components/particlesystem-component.d.ts +6 -1
  15. package/dist/components/render-component.d.ts +13 -4
  16. package/dist/components/rigidbody-component.d.ts +9 -4
  17. package/dist/components/screen-component.d.ts +6 -1
  18. package/dist/components/script-component.d.ts +116 -16
  19. package/dist/components/script.d.ts +72 -8
  20. package/dist/components/scrollbar-component.d.ts +10 -4
  21. package/dist/components/scrollview-component.d.ts +17 -10
  22. package/dist/components/sound-component.d.ts +6 -1
  23. package/dist/components/sound-slot.d.ts +8 -3
  24. package/dist/entity.d.ts +26 -2
  25. package/dist/index.d.ts +3 -2
  26. package/dist/material.d.ts +10 -0
  27. package/dist/model.d.ts +5 -0
  28. package/dist/module.d.ts +5 -0
  29. package/dist/pwc.cjs +1574 -853
  30. package/dist/pwc.cjs.map +1 -1
  31. package/dist/pwc.js +1574 -853
  32. package/dist/pwc.js.map +1 -1
  33. package/dist/pwc.min.js +1 -1
  34. package/dist/pwc.min.js.map +1 -1
  35. package/dist/pwc.mjs +1575 -855
  36. package/dist/pwc.mjs.map +1 -1
  37. package/dist/scene.d.ts +14 -5
  38. package/dist/sky.d.ts +6 -0
  39. package/dist/utils.d.ts +81 -23
  40. package/package.json +23 -13
  41. package/src/app.ts +38 -12
  42. package/src/asset.ts +61 -8
  43. package/src/async-element.ts +86 -5
  44. package/src/components/button-component.ts +26 -19
  45. package/src/components/camera-component.ts +29 -23
  46. package/src/components/collision-component.ts +19 -13
  47. package/src/components/component.ts +32 -13
  48. package/src/components/element-component.ts +58 -52
  49. package/src/components/gsplat-component.ts +14 -7
  50. package/src/components/layoutchild-component.ts +16 -9
  51. package/src/components/layoutgroup-component.ts +38 -31
  52. package/src/components/light-component.ts +33 -31
  53. package/src/components/listener-component.ts +8 -2
  54. package/src/components/particlesystem-component.ts +8 -2
  55. package/src/components/render-component.ts +19 -8
  56. package/src/components/rigidbody-component.ts +21 -15
  57. package/src/components/screen-component.ts +15 -9
  58. package/src/components/script-component.ts +512 -125
  59. package/src/components/script.ts +116 -16
  60. package/src/components/scrollbar-component.ts +19 -12
  61. package/src/components/scrollview-component.ts +37 -29
  62. package/src/components/sound-component.ts +16 -9
  63. package/src/components/sound-slot.ts +23 -14
  64. package/src/entity.ts +61 -71
  65. package/src/index.ts +5 -2
  66. package/src/material.ts +34 -1
  67. package/src/model.ts +6 -0
  68. package/src/module.ts +6 -0
  69. package/src/scene.ts +25 -12
  70. package/src/sky.ts +34 -16
  71. package/src/utils.ts +180 -40
@@ -1,23 +1,64 @@
1
+ import { Script } from 'playcanvas';
2
+
3
+ import { AsyncElement } from '../async-element';
4
+ import { parseBool } from '../utils';
5
+
1
6
  /**
2
7
  * The ScriptElement interface provides properties and methods for manipulating
3
8
  * `<pc-script>` elements. The ScriptElement interface also inherits the properties and
4
- * methods of the {@link HTMLElement} interface.
9
+ * methods of the {@link AsyncElement} interface.
10
+ *
11
+ * Script attributes can be supplied through two channels:
12
+ *
13
+ * - **Per-property attributes**: any non-reserved attribute on the element maps to the script
14
+ * attribute of the same name (kebab-case to camelCase, e.g. `focus-point` → `focusPoint`).
15
+ * Values are parsed according to the type of the attribute's current value — initially the
16
+ * script's declared default (numbers, booleans, strings, Vec2/3/4, Color, Quat as Euler
17
+ * angles) — and the `asset:`/`entity:`/`vec2:`/`vec3:`/`vec4:`/`color:` prefixes may be used
18
+ * to be explicit.
19
+ * - **The `attributes` JSON attribute**: an object supporting nested structures and attribute
20
+ * names that collide with reserved HTML attribute names (e.g. `title`).
21
+ *
22
+ * When both specify the same attribute, the per-property attribute wins — at creation and
23
+ * whenever either channel changes at runtime. The element's own `name` and `enabled`
24
+ * attributes configure the element itself and are not script attributes.
25
+ *
26
+ * Changing `name` on a live element destroys the old-name script instance and creates the
27
+ * new-name one, re-applying both attribute channels to it.
28
+ *
29
+ * The element becomes ready once its script instance has been created by the parent
30
+ * `<pc-scripts>` element.
5
31
  */
6
- class ScriptElement extends HTMLElement {
7
- private _attributes: string = '{}';
32
+ class ScriptElement extends AsyncElement {
33
+ private _attributes: Record<string, any> = {};
8
34
 
9
35
  private _enabled: boolean = true;
10
36
 
11
- private _name: string = '';
37
+ /**
38
+ * Whether readiness has been signalled. Creation can happen more than once over an
39
+ * element's life (a runtime `name` change recreates the instance), but `ready` is a
40
+ * one-shot signal, so only the first successful creation fires it.
41
+ */
42
+ private _readySignalled: boolean = false;
12
43
 
13
44
  /**
14
- * Sets the attributes of the script.
45
+ * The Script instance created for this element by its parent `<pc-scripts>` element.
46
+ * @ignore
47
+ */
48
+ _script: Script | null = null;
49
+
50
+ /**
51
+ * Sets the attributes of the script as an object. Values are converted with the same rules
52
+ * as the `attributes` attribute: `asset:`/`entity:` references and `vec2:`/`vec3:`/`vec4:`/
53
+ * `color:` prefixed strings are resolved, and a plain numeric array is converted to the
54
+ * type of the attribute it targets when that attribute currently holds a Vec2, Vec3, Vec4
55
+ * or Color.
15
56
  * @param value - The attributes of the script.
16
57
  */
17
- set scriptAttributes(value: string) {
18
- this._attributes = value;
58
+ set scriptAttributes(value: Record<string, any>) {
59
+ this._attributes = value ?? {};
19
60
  this.dispatchEvent(new CustomEvent('scriptattributeschange', {
20
- detail: { attributes: value },
61
+ detail: { attributes: this._attributes },
21
62
  bubbles: true
22
63
  }));
23
64
  }
@@ -26,7 +67,7 @@ class ScriptElement extends HTMLElement {
26
67
  * Gets the attributes of the script.
27
68
  * @returns The attributes of the script.
28
69
  */
29
- get scriptAttributes() {
70
+ get scriptAttributes(): Record<string, any> {
30
71
  return this._attributes;
31
72
  }
32
73
 
@@ -51,11 +92,20 @@ class ScriptElement extends HTMLElement {
51
92
  }
52
93
 
53
94
  /**
54
- * Sets the name of the script to create.
95
+ * Sets the name of the script to create. The `name` attribute is the single source of truth
96
+ * (it is what the parent `<pc-scripts>` element reads when creating the instance), so the
97
+ * property writes through to it — assigning before insertion works as expected:
98
+ *
99
+ * ```js
100
+ * const script = document.createElement('pc-script');
101
+ * script.name = 'rotate';
102
+ * scriptsElement.appendChild(script);
103
+ * await script.ready();
104
+ * ```
55
105
  * @param value - The name.
56
106
  */
57
107
  set name(value: string) {
58
- this._name = value;
108
+ this.setAttribute('name', value);
59
109
  }
60
110
 
61
111
  /**
@@ -63,23 +113,67 @@ class ScriptElement extends HTMLElement {
63
113
  * @returns The name.
64
114
  */
65
115
  get name() {
66
- return this._name;
116
+ return this.getAttribute('name') ?? '';
117
+ }
118
+
119
+ /**
120
+ * Gets the {@link Script} instance created for this element. Returns `null` until the
121
+ * instance exists — await {@link whenReady} or the element's `ready()` promise before
122
+ * accessing it.
123
+ * @returns The script instance, or `null`.
124
+ */
125
+ get script(): Script | null {
126
+ return this._script;
127
+ }
128
+
129
+ connectedCallback() {
130
+ // Script instances are created by the parent pc-scripts element, so an element placed
131
+ // anywhere else is inert and never becomes ready - warn rather than hang silently
132
+ if (this.parentElement?.tagName !== 'PC-SCRIPTS') {
133
+ console.warn(`pc-script '${this.getAttribute('name')}' must be a direct child of pc-scripts - script not created`);
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Called by the parent `<pc-scripts>` element when the script instance has been created.
139
+ * @ignore
140
+ */
141
+ _onScriptCreated() {
142
+ if (this._readySignalled) return;
143
+ this._readySignalled = true;
144
+ this._onReady();
67
145
  }
68
146
 
69
147
  static get observedAttributes() {
70
148
  return ['attributes', 'enabled', 'name'];
71
149
  }
72
150
 
73
- attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
151
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
74
152
  switch (name) {
75
153
  case 'attributes':
76
- this.scriptAttributes = newValue;
154
+ if (newValue === null) {
155
+ this.scriptAttributes = {};
156
+ break;
157
+ }
158
+ try {
159
+ this.scriptAttributes = JSON.parse(newValue);
160
+ } catch (error) {
161
+ console.warn(`Invalid 'attributes' JSON on pc-script '${this.getAttribute('name')}': ${(error as Error).message}`);
162
+ }
77
163
  break;
78
164
  case 'enabled':
79
- this.enabled = newValue !== 'false';
165
+ this.enabled = parseBool(newValue, true);
80
166
  break;
81
167
  case 'name':
82
- this.name = newValue;
168
+ // The first set is handled by the parent's creation paths (the boot query and
169
+ // the added-node mutation), so only a genuine rename is signalled here. Note
170
+ // that setAttribute fires this callback even when the value is unchanged.
171
+ if (oldValue !== null && oldValue !== newValue) {
172
+ this.dispatchEvent(new CustomEvent('scriptnamechange', {
173
+ detail: { oldName: oldValue, newName: newValue },
174
+ bubbles: true
175
+ }));
176
+ }
83
177
  break;
84
178
  }
85
179
  }
@@ -87,4 +181,10 @@ class ScriptElement extends HTMLElement {
87
181
 
88
182
  customElements.define('pc-script', ScriptElement);
89
183
 
184
+ declare global {
185
+ interface HTMLElementTagNameMap {
186
+ 'pc-script': ScriptElement;
187
+ }
188
+ }
189
+
90
190
  export { ScriptElement };
@@ -1,9 +1,9 @@
1
1
  import { ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL, ScrollbarComponent } from 'playcanvas';
2
2
 
3
3
  import { ComponentElement } from './component';
4
- import { getEntity, parseEnum } from '../utils';
4
+ import { getEntity, parseEnum, parseNumber } from '../utils';
5
5
 
6
- const orientations = new Map<string, number>([
6
+ const orientations = new Map<'horizontal' | 'vertical', number>([
7
7
  ['horizontal', ORIENTATION_HORIZONTAL],
8
8
  ['vertical', ORIENTATION_VERTICAL]
9
9
  ]);
@@ -17,7 +17,7 @@ const orientations = new Map<string, number>([
17
17
  * @category Components
18
18
  */
19
19
  class ScrollbarComponentElement extends ComponentElement {
20
- private _orientation: number = ORIENTATION_HORIZONTAL;
20
+ private _orientation: 'horizontal' | 'vertical' = 'horizontal';
21
21
 
22
22
  private _value = 0;
23
23
 
@@ -32,7 +32,7 @@ class ScrollbarComponentElement extends ComponentElement {
32
32
 
33
33
  getInitialComponentData() {
34
34
  const data: Record<string, any> = {
35
- orientation: this._orientation,
35
+ orientation: orientations.get(this._orientation),
36
36
  value: this._value,
37
37
  handleSize: this._handleSize
38
38
  };
@@ -49,18 +49,19 @@ class ScrollbarComponentElement extends ComponentElement {
49
49
  * Gets the underlying PlayCanvas scrollbar component.
50
50
  * @returns The scrollbar component.
51
51
  */
52
- get component(): ScrollbarComponent | null {
53
- return super.component as ScrollbarComponent | null;
52
+ get component(): ScrollbarComponent {
53
+ return super.component as ScrollbarComponent;
54
54
  }
55
55
 
56
56
  /**
57
- * Sets the orientation of the scrollbar. Can be `horizontal` (0) or `vertical` (1).
57
+ * Sets the orientation of the scrollbar. Can be `horizontal` or `vertical`. Defaults to
58
+ * `horizontal`.
58
59
  * @param value - The orientation.
59
60
  */
60
- set orientation(value: number) {
61
+ set orientation(value: 'horizontal' | 'vertical') {
61
62
  this._orientation = value;
62
63
  if (this.component) {
63
- this.component.orientation = value;
64
+ this.component.orientation = orientations.get(value) ?? ORIENTATION_HORIZONTAL;
64
65
  }
65
66
  }
66
67
 
@@ -146,13 +147,13 @@ class ScrollbarComponentElement extends ComponentElement {
146
147
 
147
148
  switch (name) {
148
149
  case 'orientation':
149
- this.orientation = parseEnum(newValue, orientations, ORIENTATION_HORIZONTAL);
150
+ this.orientation = parseEnum(newValue, orientations, 'horizontal', name);
150
151
  break;
151
152
  case 'value':
152
- this.value = Number(newValue);
153
+ this.value = parseNumber(newValue, 0, name);
153
154
  break;
154
155
  case 'handle-size':
155
- this.handleSize = Number(newValue);
156
+ this.handleSize = parseNumber(newValue, 0.5, name);
156
157
  break;
157
158
  case 'handle':
158
159
  this.handle = newValue;
@@ -163,4 +164,10 @@ class ScrollbarComponentElement extends ComponentElement {
163
164
 
164
165
  customElements.define('pc-scrollbar', ScrollbarComponentElement);
165
166
 
167
+ declare global {
168
+ interface HTMLElementTagNameMap {
169
+ 'pc-scrollbar': ScrollbarComponentElement;
170
+ }
171
+ }
172
+
166
173
  export { ScrollbarComponentElement };
@@ -1,15 +1,15 @@
1
1
  import { SCROLL_MODE_BOUNCE, SCROLL_MODE_CLAMP, SCROLL_MODE_INFINITE, SCROLLBAR_VISIBILITY_SHOW_ALWAYS, SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED, ScrollViewComponent, Vec2 } from 'playcanvas';
2
2
 
3
3
  import { ComponentElement } from './component';
4
- import { getEntity, parseEnum, parseVec2 } from '../utils';
4
+ import { getEntity, parseBool, parseEnum, parseNumber, parseVec2 } from '../utils';
5
5
 
6
- const scrollModes = new Map<string, number>([
6
+ const scrollModes = new Map<'clamp' | 'bounce' | 'infinite', number>([
7
7
  ['clamp', SCROLL_MODE_CLAMP],
8
8
  ['bounce', SCROLL_MODE_BOUNCE],
9
9
  ['infinite', SCROLL_MODE_INFINITE]
10
10
  ]);
11
11
 
12
- const visibilities = new Map<string, number>([
12
+ const visibilities = new Map<'always' | 'when-required', number>([
13
13
  ['always', SCROLLBAR_VISIBILITY_SHOW_ALWAYS],
14
14
  ['when-required', SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED]
15
15
  ]);
@@ -27,7 +27,7 @@ class ScrollViewComponentElement extends ComponentElement {
27
27
 
28
28
  private _vertical = true;
29
29
 
30
- private _scrollMode: number = SCROLL_MODE_BOUNCE;
30
+ private _scrollMode: 'clamp' | 'bounce' | 'infinite' = 'bounce';
31
31
 
32
32
  private _bounceAmount = 0.1;
33
33
 
@@ -37,9 +37,9 @@ class ScrollViewComponentElement extends ComponentElement {
37
37
 
38
38
  private _mouseWheelSensitivity = new Vec2(1, 1);
39
39
 
40
- private _horizontalScrollbarVisibility: number = SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
40
+ private _horizontalScrollbarVisibility: 'always' | 'when-required' = 'when-required';
41
41
 
42
- private _verticalScrollbarVisibility: number = SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
42
+ private _verticalScrollbarVisibility: 'always' | 'when-required' = 'when-required';
43
43
 
44
44
  private _viewport = '';
45
45
 
@@ -58,13 +58,13 @@ class ScrollViewComponentElement extends ComponentElement {
58
58
  const data: Record<string, any> = {
59
59
  horizontal: this._horizontal,
60
60
  vertical: this._vertical,
61
- scrollMode: this._scrollMode,
61
+ scrollMode: scrollModes.get(this._scrollMode),
62
62
  bounceAmount: this._bounceAmount,
63
63
  friction: this._friction,
64
64
  useMouseWheel: this._useMouseWheel,
65
65
  mouseWheelSensitivity: this._mouseWheelSensitivity,
66
- horizontalScrollbarVisibility: this._horizontalScrollbarVisibility,
67
- verticalScrollbarVisibility: this._verticalScrollbarVisibility
66
+ horizontalScrollbarVisibility: visibilities.get(this._horizontalScrollbarVisibility),
67
+ verticalScrollbarVisibility: visibilities.get(this._verticalScrollbarVisibility)
68
68
  };
69
69
 
70
70
  const viewport = getEntity(this._viewport);
@@ -94,8 +94,8 @@ class ScrollViewComponentElement extends ComponentElement {
94
94
  * Gets the underlying PlayCanvas scroll view component.
95
95
  * @returns The scroll view component.
96
96
  */
97
- get component(): ScrollViewComponent | null {
98
- return super.component as ScrollViewComponent | null;
97
+ get component(): ScrollViewComponent {
98
+ return super.component as ScrollViewComponent;
99
99
  }
100
100
 
101
101
  /**
@@ -138,13 +138,13 @@ class ScrollViewComponentElement extends ComponentElement {
138
138
 
139
139
  /**
140
140
  * Sets how the scroll view should behave when the content is scrolled beyond its bounds. Can be
141
- * `clamp` (0), `bounce` (1) or `infinite` (2).
141
+ * `clamp`, `bounce` or `infinite`. Defaults to `bounce`.
142
142
  * @param value - The scroll mode.
143
143
  */
144
- set scrollMode(value: number) {
144
+ set scrollMode(value: 'clamp' | 'bounce' | 'infinite') {
145
145
  this._scrollMode = value;
146
146
  if (this.component) {
147
- this.component.scrollMode = value;
147
+ this.component.scrollMode = scrollModes.get(value) ?? SCROLL_MODE_BOUNCE;
148
148
  }
149
149
  }
150
150
 
@@ -235,13 +235,14 @@ class ScrollViewComponentElement extends ComponentElement {
235
235
  }
236
236
 
237
237
  /**
238
- * Sets the visibility of the horizontal scrollbar. Can be `always` (0) or `when-required` (1).
238
+ * Sets the visibility of the horizontal scrollbar. Can be `always` or `when-required`.
239
+ * Defaults to `when-required`.
239
240
  * @param value - The horizontal scrollbar visibility.
240
241
  */
241
- set horizontalScrollbarVisibility(value: number) {
242
+ set horizontalScrollbarVisibility(value: 'always' | 'when-required') {
242
243
  this._horizontalScrollbarVisibility = value;
243
244
  if (this.component) {
244
- this.component.horizontalScrollbarVisibility = value;
245
+ this.component.horizontalScrollbarVisibility = visibilities.get(value) ?? SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
245
246
  }
246
247
  }
247
248
 
@@ -254,13 +255,14 @@ class ScrollViewComponentElement extends ComponentElement {
254
255
  }
255
256
 
256
257
  /**
257
- * Sets the visibility of the vertical scrollbar. Can be `always` (0) or `when-required` (1).
258
+ * Sets the visibility of the vertical scrollbar. Can be `always` or `when-required`.
259
+ * Defaults to `when-required`.
258
260
  * @param value - The vertical scrollbar visibility.
259
261
  */
260
- set verticalScrollbarVisibility(value: number) {
262
+ set verticalScrollbarVisibility(value: 'always' | 'when-required') {
261
263
  this._verticalScrollbarVisibility = value;
262
264
  if (this.component) {
263
- this.component.verticalScrollbarVisibility = value;
265
+ this.component.verticalScrollbarVisibility = visibilities.get(value) ?? SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
264
266
  }
265
267
  }
266
268
 
@@ -380,31 +382,31 @@ class ScrollViewComponentElement extends ComponentElement {
380
382
 
381
383
  switch (name) {
382
384
  case 'horizontal':
383
- this.horizontal = newValue !== 'false';
385
+ this.horizontal = parseBool(newValue, true);
384
386
  break;
385
387
  case 'vertical':
386
- this.vertical = newValue !== 'false';
388
+ this.vertical = parseBool(newValue, true);
387
389
  break;
388
390
  case 'scroll-mode':
389
- this.scrollMode = parseEnum(newValue, scrollModes, SCROLL_MODE_BOUNCE);
391
+ this.scrollMode = parseEnum(newValue, scrollModes, 'bounce', name);
390
392
  break;
391
393
  case 'bounce-amount':
392
- this.bounceAmount = Number(newValue);
394
+ this.bounceAmount = parseNumber(newValue, 0.1, name);
393
395
  break;
394
396
  case 'friction':
395
- this.friction = Number(newValue);
397
+ this.friction = parseNumber(newValue, 0.05, name);
396
398
  break;
397
399
  case 'use-mouse-wheel':
398
- this.useMouseWheel = newValue !== 'false';
400
+ this.useMouseWheel = parseBool(newValue, true);
399
401
  break;
400
402
  case 'mouse-wheel-sensitivity':
401
- this.mouseWheelSensitivity = parseVec2(newValue);
403
+ this.mouseWheelSensitivity = parseVec2(newValue, Vec2.ONE, name);
402
404
  break;
403
405
  case 'horizontal-scrollbar-visibility':
404
- this.horizontalScrollbarVisibility = parseEnum(newValue, visibilities, SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED);
406
+ this.horizontalScrollbarVisibility = parseEnum(newValue, visibilities, 'when-required', name);
405
407
  break;
406
408
  case 'vertical-scrollbar-visibility':
407
- this.verticalScrollbarVisibility = parseEnum(newValue, visibilities, SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED);
409
+ this.verticalScrollbarVisibility = parseEnum(newValue, visibilities, 'when-required', name);
408
410
  break;
409
411
  case 'viewport':
410
412
  this.viewport = newValue;
@@ -424,4 +426,10 @@ class ScrollViewComponentElement extends ComponentElement {
424
426
 
425
427
  customElements.define('pc-scrollview', ScrollViewComponentElement);
426
428
 
429
+ declare global {
430
+ interface HTMLElementTagNameMap {
431
+ 'pc-scrollview': ScrollViewComponentElement;
432
+ }
433
+ }
434
+
427
435
  export { ScrollViewComponentElement };
@@ -1,6 +1,7 @@
1
1
  import { SoundComponent } from 'playcanvas';
2
2
 
3
3
  import { ComponentElement } from './component';
4
+ import { parseBool, parseEnum, parseNumber } from '../utils';
4
5
 
5
6
  /**
6
7
  * The SoundComponentElement interface provides properties and methods for manipulating
@@ -46,8 +47,8 @@ class SoundComponentElement extends ComponentElement {
46
47
  * Gets the underlying PlayCanvas sound component.
47
48
  * @returns The sound component.
48
49
  */
49
- get component(): SoundComponent | null {
50
- return super.component as SoundComponent | null;
50
+ get component(): SoundComponent {
51
+ return super.component as SoundComponent;
51
52
  }
52
53
 
53
54
  /**
@@ -201,25 +202,25 @@ class SoundComponentElement extends ComponentElement {
201
202
 
202
203
  switch (name) {
203
204
  case 'distance-model':
204
- this.distanceModel = newValue as 'exponential' | 'inverse' | 'linear';
205
+ this.distanceModel = parseEnum(newValue, ['exponential', 'inverse', 'linear'], 'linear', name);
205
206
  break;
206
207
  case 'max-distance':
207
- this.maxDistance = parseFloat(newValue);
208
+ this.maxDistance = parseNumber(newValue, 10000, name);
208
209
  break;
209
210
  case 'pitch':
210
- this.pitch = parseFloat(newValue);
211
+ this.pitch = parseNumber(newValue, 1, name);
211
212
  break;
212
213
  case 'positional':
213
- this.positional = this.hasAttribute('positional');
214
+ this.positional = parseBool(newValue, false);
214
215
  break;
215
216
  case 'ref-distance':
216
- this.refDistance = parseFloat(newValue);
217
+ this.refDistance = parseNumber(newValue, 1, name);
217
218
  break;
218
219
  case 'roll-off-factor':
219
- this.rollOffFactor = parseFloat(newValue);
220
+ this.rollOffFactor = parseNumber(newValue, 1, name);
220
221
  break;
221
222
  case 'volume':
222
- this.volume = parseFloat(newValue);
223
+ this.volume = parseNumber(newValue, 1, name);
223
224
  break;
224
225
  }
225
226
  }
@@ -227,4 +228,10 @@ class SoundComponentElement extends ComponentElement {
227
228
 
228
229
  customElements.define('pc-sounds', SoundComponentElement);
229
230
 
231
+ declare global {
232
+ interface HTMLElementTagNameMap {
233
+ 'pc-sounds': SoundComponentElement;
234
+ }
235
+ }
236
+
230
237
  export { SoundComponentElement };
@@ -3,6 +3,7 @@ import { SoundSlot } from 'playcanvas';
3
3
  import { AssetElement } from '../asset';
4
4
  import { AsyncElement } from '../async-element';
5
5
  import { SoundComponentElement } from './sound-component';
6
+ import { parseBool, parseNumber } from '../utils';
6
7
 
7
8
  /**
8
9
  * The SoundSlotElement interface provides properties and methods for manipulating
@@ -58,14 +59,16 @@ class SoundSlotElement extends AsyncElement {
58
59
  }
59
60
 
60
61
  disconnectedCallback() {
61
- this.soundElement!.component!.removeSlot(this._name);
62
+ // The component is null if the parent <pc-sound> (or the whole <pc-app>) is being
63
+ // torn down — parents disconnect first and have already removed the component.
64
+ this.soundElement?.component?.removeSlot(this._name);
62
65
  }
63
66
 
64
67
  protected get soundElement(): SoundComponentElement | null {
65
68
  const soundElement = this.parentElement as SoundComponentElement;
66
69
 
67
70
  if (!(soundElement instanceof SoundComponentElement)) {
68
- console.warn('pc-sound-slot must be a direct child of a pc-sound element');
71
+ console.warn('pc-sound must be a direct child of a pc-sounds element');
69
72
  return null;
70
73
  }
71
74
 
@@ -114,12 +117,12 @@ class SoundSlotElement extends AsyncElement {
114
117
  }
115
118
 
116
119
  /**
117
- * Sets the duration of the sound slot.
120
+ * Sets the duration of the sound slot, in seconds (or `null` to play the whole clip).
118
121
  * @param value - The duration.
119
122
  */
120
- set duration(value: number) {
123
+ set duration(value: number | null) {
121
124
  this._duration = value;
122
- if (this.soundSlot) {
125
+ if (this.soundSlot && value !== null) {
123
126
  this.soundSlot.duration = value;
124
127
  }
125
128
  }
@@ -128,8 +131,8 @@ class SoundSlotElement extends AsyncElement {
128
131
  * Gets the duration of the sound slot.
129
132
  * @returns The duration.
130
133
  */
131
- get duration() {
132
- return this._duration as number;
134
+ get duration(): number | null {
135
+ return this._duration;
133
136
  }
134
137
 
135
138
  /**
@@ -256,28 +259,28 @@ class SoundSlotElement extends AsyncElement {
256
259
  this.asset = newValue;
257
260
  break;
258
261
  case 'auto-play':
259
- this.autoPlay = this.hasAttribute('auto-play');
262
+ this.autoPlay = parseBool(newValue, false);
260
263
  break;
261
264
  case 'duration':
262
- this.duration = parseFloat(newValue);
265
+ this.duration = parseNumber(newValue, null, name);
263
266
  break;
264
267
  case 'loop':
265
- this.loop = this.hasAttribute('loop');
268
+ this.loop = parseBool(newValue, false);
266
269
  break;
267
270
  case 'name':
268
271
  this.name = newValue;
269
272
  break;
270
273
  case 'overlap':
271
- this.overlap = this.hasAttribute('overlap');
274
+ this.overlap = parseBool(newValue, false);
272
275
  break;
273
276
  case 'pitch':
274
- this.pitch = parseFloat(newValue);
277
+ this.pitch = parseNumber(newValue, 1, name);
275
278
  break;
276
279
  case 'start-time':
277
- this.startTime = parseFloat(newValue);
280
+ this.startTime = parseNumber(newValue, 0, name);
278
281
  break;
279
282
  case 'volume':
280
- this.volume = parseFloat(newValue);
283
+ this.volume = parseNumber(newValue, 1, name);
281
284
  break;
282
285
  }
283
286
  }
@@ -285,4 +288,10 @@ class SoundSlotElement extends AsyncElement {
285
288
 
286
289
  customElements.define('pc-sound', SoundSlotElement);
287
290
 
291
+ declare global {
292
+ interface HTMLElementTagNameMap {
293
+ 'pc-sound': SoundSlotElement;
294
+ }
295
+ }
296
+
288
297
  export { SoundSlotElement };