@playcanvas/web-components 0.9.0 → 0.10.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 (74) hide show
  1. package/README.md +18 -0
  2. package/dist/app.d.ts +55 -9
  3. package/dist/asset.d.ts +25 -1
  4. package/dist/async-element.d.ts +15 -2
  5. package/dist/components/button-component.d.ts +1 -1
  6. package/dist/components/camera-component.d.ts +1 -1
  7. package/dist/components/collision-component.d.ts +1 -1
  8. package/dist/components/component.d.ts +6 -5
  9. package/dist/components/element-component.d.ts +1 -1
  10. package/dist/components/gsplat-component.d.ts +1 -1
  11. package/dist/components/layoutchild-component.d.ts +1 -1
  12. package/dist/components/layoutgroup-component.d.ts +1 -1
  13. package/dist/components/light-component.d.ts +1 -1
  14. package/dist/components/particlesystem-component.d.ts +1 -1
  15. package/dist/components/render-component.d.ts +1 -1
  16. package/dist/components/rigidbody-component.d.ts +1 -1
  17. package/dist/components/screen-component.d.ts +1 -1
  18. package/dist/components/script.d.ts +8 -1
  19. package/dist/components/scrollbar-component.d.ts +1 -1
  20. package/dist/components/scrollview-component.d.ts +1 -1
  21. package/dist/components/sound-component.d.ts +1 -1
  22. package/dist/components/sound-slot.d.ts +9 -1
  23. package/dist/custom-elements.json +16704 -0
  24. package/dist/entity.d.ts +24 -5
  25. package/dist/loading-bar.d.ts +35 -0
  26. package/dist/material.d.ts +972 -4
  27. package/dist/model.d.ts +1 -1
  28. package/dist/module.d.ts +10 -0
  29. package/dist/{utils.d.ts → parse.d.ts} +63 -33
  30. package/dist/pwc.cjs +3070 -699
  31. package/dist/pwc.cjs.map +1 -1
  32. package/dist/pwc.js +3070 -699
  33. package/dist/pwc.js.map +1 -1
  34. package/dist/pwc.min.js +1 -1
  35. package/dist/pwc.min.js.map +1 -1
  36. package/dist/pwc.min.mjs +2 -0
  37. package/dist/pwc.min.mjs.map +1 -0
  38. package/dist/pwc.mjs +3071 -700
  39. package/dist/pwc.mjs.map +1 -1
  40. package/dist/scene.d.ts +12 -4
  41. package/dist/sky.d.ts +1 -1
  42. package/dist/vscode.html-custom-data.json +1800 -0
  43. package/dist/web-types.json +3836 -0
  44. package/package.json +29 -11
  45. package/src/app.ts +178 -78
  46. package/src/asset.ts +44 -2
  47. package/src/async-element.ts +17 -4
  48. package/src/components/button-component.ts +6 -6
  49. package/src/components/camera-component.ts +2 -2
  50. package/src/components/collision-component.ts +2 -2
  51. package/src/components/component.ts +8 -7
  52. package/src/components/element-component.ts +6 -6
  53. package/src/components/gsplat-component.ts +3 -3
  54. package/src/components/layoutchild-component.ts +2 -2
  55. package/src/components/layoutgroup-component.ts +2 -2
  56. package/src/components/light-component.ts +2 -2
  57. package/src/components/particlesystem-component.ts +2 -2
  58. package/src/components/render-component.ts +10 -5
  59. package/src/components/rigidbody-component.ts +2 -2
  60. package/src/components/screen-component.ts +2 -2
  61. package/src/components/script-component.ts +4 -4
  62. package/src/components/script.ts +9 -2
  63. package/src/components/scrollbar-component.ts +3 -3
  64. package/src/components/scrollview-component.ts +6 -6
  65. package/src/components/sound-component.ts +2 -2
  66. package/src/components/sound-slot.ts +31 -9
  67. package/src/entity.ts +56 -22
  68. package/src/loading-bar.ts +122 -0
  69. package/src/material.ts +2402 -59
  70. package/src/model.ts +2 -2
  71. package/src/module.ts +10 -0
  72. package/src/{utils.ts → parse.ts} +104 -65
  73. package/src/scene.ts +51 -21
  74. package/src/sky.ts +3 -3
package/src/model.ts CHANGED
@@ -97,10 +97,10 @@ class ModelElement extends AsyncElement {
97
97
  return ['asset'];
98
98
  }
99
99
 
100
- attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
100
+ attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
101
101
  switch (name) {
102
102
  case 'asset':
103
- this.asset = newValue;
103
+ this.asset = newValue ?? '';
104
104
  break;
105
105
  }
106
106
  }
package/src/module.ts CHANGED
@@ -5,6 +5,16 @@ import { basisInitialize, WasmModule } from 'playcanvas';
5
5
  * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-module/ | `<pc-module>`} elements.
6
6
  * The ModuleElement interface also inherits the properties and methods of the
7
7
  * {@link HTMLElement} interface.
8
+ *
9
+ * Note that these attributes are read once when the element is created, so changing them later
10
+ * has no effect.
11
+ *
12
+ * @attribute {string} name - The name of the WebAssembly module to configure, e.g. `Basis` or
13
+ * `Ammo`.
14
+ * @attribute {string} glue - The URL of the module's glue script.
15
+ * @attribute {string} wasm - The URL of the module's WebAssembly binary.
16
+ * @attribute {string} fallback - The URL of the module's asm.js fallback script, used when
17
+ * WebAssembly is unavailable.
8
18
  */
9
19
  class ModuleElement extends HTMLElement {
10
20
  private loadPromise: Promise<void>;
@@ -1,22 +1,24 @@
1
- import { Color, Entity, Quat, Vec2, Vec3, Vec4 } from 'playcanvas';
2
-
3
- import { CSS_COLORS } from './colors';
4
-
5
1
  /**
6
- * Parse a boolean attribute value. The same rules apply to every boolean attribute:
2
+ * Converts HTML attribute values into the values the engine expects. Every element's
3
+ * `attributeChangedCallback` funnels through this module.
7
4
  *
8
- * - Attribute absent (or removed): the supplied default is used.
9
- * - Attribute set to the string 'false': `false`.
10
- * - Attribute present with any other value, including the empty string of a bare boolean
11
- * attribute (e.g. `<pc-light cast-shadows>`): `true`.
5
+ * The parsers share one contract:
12
6
  *
13
- * @param value - The attribute value to parse (`null` when the attribute is absent).
14
- * @param defaultValue - The value to use when the attribute is absent or removed.
15
- * @returns The parsed boolean.
7
+ * - A `null` value means the attribute is absent or was removed, and yields the supplied default.
8
+ * - A malformed value yields the same default and logs exactly one `console.warn` naming the
9
+ * attribute, so misuse is reported rather than thrown — nothing here throws or rejects.
10
+ * - A math-type default is cloned on the way out, which is what makes it safe to pass the engine's
11
+ * shared frozen constants (`Vec3.ZERO`, `Color.WHITE`) as defaults.
12
+ * - `parseBool` and `parseTags` take no attribute name, because every value is valid for them and
13
+ * so they never warn.
14
+ *
15
+ * `getEntity` is the exception: it resolves a reference to a live entity rather than parsing a
16
+ * literal, and returns `null` instead of falling back to a default.
16
17
  */
17
- export const parseBool = (value: string | null, defaultValue: boolean): boolean => {
18
- return value === null ? defaultValue : value !== 'false';
19
- };
18
+
19
+ import { Color, Entity, Quat, Vec2, Vec3, Vec4 } from 'playcanvas';
20
+
21
+ import { CSS_COLORS } from './colors';
20
22
 
21
23
  /**
22
24
  * Splits an attribute value into exactly `count` numeric components. Returns `null` when the
@@ -47,6 +49,22 @@ const cloneDefault = <T extends Color | Quat | Vec2 | Vec3 | Vec4 | null>(value:
47
49
  return (value === null ? null : value.clone()) as T;
48
50
  };
49
51
 
52
+ /**
53
+ * Parse a boolean attribute value. The same rules apply to every boolean attribute:
54
+ *
55
+ * - Attribute absent (or removed): the supplied default is used.
56
+ * - Attribute set to the string 'false': `false`.
57
+ * - Attribute present with any other value, including the empty string of a bare boolean
58
+ * attribute (e.g. `<pc-light cast-shadows>`): `true`.
59
+ *
60
+ * @param value - The attribute value to parse (`null` when the attribute is absent).
61
+ * @param defaultValue - The value to use when the attribute is absent or removed.
62
+ * @returns The parsed boolean.
63
+ */
64
+ export const parseBool = (value: string | null, defaultValue: boolean): boolean => {
65
+ return value === null ? defaultValue : value !== 'false';
66
+ };
67
+
50
68
  /**
51
69
  * Parse a color attribute value. The expected format is a CSS color name (e.g. 'rebeccapurple'),
52
70
  * a hex color (e.g. '#ff0000' or '#f00'), or 3 or 4 space-separated numbers in the range 0 to 1
@@ -89,6 +107,56 @@ export const parseColor = <T extends Color | null>(value: string | null, default
89
107
  return cloneDefault(defaultValue);
90
108
  };
91
109
 
110
+ /**
111
+ * Resolves an enum attribute value against its set of valid names. Returns the value when it is
112
+ * one of the valid names. Returns `defaultValue` when the attribute is absent (`null`), or when
113
+ * the value is invalid — the latter also logs a warning listing the valid names.
114
+ *
115
+ * @param value - The attribute value to parse (`null` when the attribute is absent).
116
+ * @param valid - The valid names: an array, or a map whose keys are the valid names.
117
+ * @param defaultValue - The value to use when the attribute is absent or invalid.
118
+ * @param attribute - The attribute name, used in the warning message.
119
+ * @returns The resolved enum name.
120
+ */
121
+ export const parseEnum = <T extends string>(
122
+ value: string | null,
123
+ valid: readonly T[] | ReadonlyMap<T, number>,
124
+ defaultValue: T,
125
+ attribute: string
126
+ ): T => {
127
+ if (value === null) {
128
+ return defaultValue;
129
+ }
130
+ const names = Array.isArray(valid) ? valid : [...(valid as ReadonlyMap<T, number>).keys()];
131
+ if (names.includes(value as T)) {
132
+ return value as T;
133
+ }
134
+ console.warn(`Invalid value '${value}' for attribute '${attribute}'. Valid values: ${names.join(', ')}. Using '${defaultValue}'.`);
135
+ return defaultValue;
136
+ };
137
+
138
+ /**
139
+ * Parses a number attribute value. Returns the parsed number when the value is a finite number.
140
+ * Returns `defaultValue` when the attribute is absent (`null`), or when the value is not a
141
+ * finite number — the latter also logs a warning.
142
+ *
143
+ * @param value - The attribute value to parse (`null` when the attribute is absent).
144
+ * @param defaultValue - The value to use when the attribute is absent or invalid.
145
+ * @param attribute - The attribute name, used in the warning message.
146
+ * @returns The parsed number.
147
+ */
148
+ export const parseNumber = <T extends number | null>(value: string | null, defaultValue: T, attribute: string): number | T => {
149
+ if (value === null) {
150
+ return defaultValue;
151
+ }
152
+ const number = value.trim() === '' ? NaN : Number(value);
153
+ if (!Number.isFinite(number)) {
154
+ console.warn(`Invalid value '${value}' for attribute '${attribute}'. Expected a finite number. Using '${defaultValue}'.`);
155
+ return defaultValue;
156
+ }
157
+ return number;
158
+ };
159
+
92
160
  /**
93
161
  * Parse an Euler-angles attribute value into a quaternion. The expected format is 3
94
162
  * space-separated angles in degrees (e.g. '0 90 0'). Returns `defaultValue` (cloned, when it is
@@ -112,6 +180,27 @@ export const parseQuat = <T extends Quat | null>(value: string | null, defaultVa
112
180
  return new Quat().setFromEulerAngles(components[0], components[1], components[2]);
113
181
  };
114
182
 
183
+ /**
184
+ * Parse a tags attribute value. The expected format is a comma-separated list of tag names
185
+ * (e.g. 'enemy, flying'). Surrounding whitespace is trimmed from each name and empty names are
186
+ * discarded, so a trailing comma or a doubled separator does not produce a blank tag. Returns a
187
+ * copy of `defaultValue` when the attribute is absent or removed (`null`).
188
+ *
189
+ * Every value is valid, so this never warns.
190
+ *
191
+ * @param value - The attribute value to parse (`null` when the attribute is absent).
192
+ * @param defaultValue - The value to use when the attribute is absent or removed.
193
+ * @returns The parsed tag names.
194
+ */
195
+ export const parseTags = (value: string | null, defaultValue: string[] = []): string[] => {
196
+ if (value === null) {
197
+ // Copied for the same reason cloneDefault exists: a parsed result must never alias the
198
+ // caller's default, or a later mutation would write back through it.
199
+ return [...defaultValue];
200
+ }
201
+ return value.split(',').map(tag => tag.trim()).filter(tag => tag !== '');
202
+ };
203
+
115
204
  /**
116
205
  * Parse a Vec2 attribute value. The expected format is 2 space-separated numbers (e.g. '1 2').
117
206
  * Returns `defaultValue` (cloned, when it is a vector) when the attribute is absent (`null`),
@@ -178,56 +267,6 @@ export const parseVec4 = <T extends Vec4 | null>(value: string | null, defaultVa
178
267
  return new Vec4(components);
179
268
  };
180
269
 
181
- /**
182
- * Resolves an enum attribute value against its set of valid names. Returns the value when it is
183
- * one of the valid names. Returns `defaultValue` when the attribute is absent (`null`), or when
184
- * the value is invalid — the latter also logs a warning listing the valid names.
185
- *
186
- * @param value - The attribute value to parse (`null` when the attribute is absent).
187
- * @param valid - The valid names: an array, or a map whose keys are the valid names.
188
- * @param defaultValue - The value to use when the attribute is absent or invalid.
189
- * @param attribute - The attribute name, used in the warning message.
190
- * @returns The resolved enum name.
191
- */
192
- export const parseEnum = <T extends string>(
193
- value: string | null,
194
- valid: readonly T[] | ReadonlyMap<T, number>,
195
- defaultValue: T,
196
- attribute: string
197
- ): T => {
198
- if (value === null) {
199
- return defaultValue;
200
- }
201
- const names = Array.isArray(valid) ? valid : [...(valid as ReadonlyMap<T, number>).keys()];
202
- if (names.includes(value as T)) {
203
- return value as T;
204
- }
205
- console.warn(`Invalid value '${value}' for attribute '${attribute}'. Valid values: ${names.join(', ')}. Using '${defaultValue}'.`);
206
- return defaultValue;
207
- };
208
-
209
- /**
210
- * Parses a number attribute value. Returns the parsed number when the value is a finite number.
211
- * Returns `defaultValue` when the attribute is absent (`null`), or when the value is not a
212
- * finite number — the latter also logs a warning.
213
- *
214
- * @param value - The attribute value to parse (`null` when the attribute is absent).
215
- * @param defaultValue - The value to use when the attribute is absent or invalid.
216
- * @param attribute - The attribute name, used in the warning message.
217
- * @returns The parsed number.
218
- */
219
- export const parseNumber = <T extends number | null>(value: string | null, defaultValue: T, attribute: string): number | T => {
220
- if (value === null) {
221
- return defaultValue;
222
- }
223
- const number = value.trim() === '' ? NaN : Number(value);
224
- if (!Number.isFinite(number)) {
225
- console.warn(`Invalid value '${value}' for attribute '${attribute}'. Expected a finite number. Using '${defaultValue}'.`);
226
- return defaultValue;
227
- }
228
- return number;
229
- };
230
-
231
270
  /**
232
271
  * Resolves a reference string to the {@link Entity} backing a `<pc-entity>` element. The reference
233
272
  * can be a CSS selector (e.g. `#my-id`, `pc-entity[name="Foo"]`), a bare element id, or a bare
package/src/scene.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { Color, Scene, Vec3 } from 'playcanvas';
2
2
 
3
- import { AppElement } from './app';
4
3
  import { AsyncElement } from './async-element';
5
- import { parseColor, parseEnum, parseNumber, parseVec3 } from './utils';
4
+ import { parseColor, parseEnum, parseNumber, parseVec3 } from './parse';
6
5
 
7
6
  /**
8
7
  * The SceneElement interface provides properties and methods for manipulating
@@ -44,36 +43,68 @@ class SceneElement extends AsyncElement {
44
43
  private _scene: Scene | null = null;
45
44
 
46
45
  /**
47
- * The PlayCanvas scene instance. Available once the element is ready — await
46
+ * The PlayCanvas scene instance. `null` until the element is ready — await
48
47
  * {@link whenReady} or the element's `ready()` promise before accessing it.
49
- * @returns The scene instance.
48
+ * @returns The scene instance, or `null`.
50
49
  */
51
- get scene(): Scene {
52
- return this._scene!;
50
+ get scene(): Scene | null {
51
+ return this._scene;
53
52
  }
54
53
 
55
54
  async connectedCallback() {
56
- await this.closestApp?.ready();
55
+ const appElement = this.closestApp;
56
+ if (!appElement) {
57
+ console.warn('pc-scene must be a descendant of pc-app - scene settings not applied');
58
+ return;
59
+ }
60
+
61
+ await appElement.ready();
62
+
63
+ // The element may have been removed or re-parented while waiting for the app. Matches the
64
+ // guard in AssetElement and MaterialElement, but compares closestApp rather than
65
+ // parentElement because pc-scene resolves its app by ancestor rather than direct child.
66
+ // Without this, a scene re-parented mid-await would take its Scene from the app it started
67
+ // under while _applyGravity resolved the app it ended up under, splitting the two.
68
+ if (!this.isConnected || this.closestApp !== appElement) {
69
+ return;
70
+ }
57
71
 
58
- this._scene = this.closestApp!.app!.scene;
72
+ // The application is gone if the tree was torn down while we awaited readiness. There is
73
+ // nothing to configure and nothing the author can act on, so this stays silent.
74
+ const app = appElement.app;
75
+ if (!app) {
76
+ return;
77
+ }
78
+
79
+ this._scene = app.scene;
59
80
  this.updateSceneSettings();
60
81
 
61
82
  this._onReady();
62
83
  }
63
84
 
64
85
  updateSceneSettings() {
65
- if (this.scene) {
66
- this.scene.fog.type = this._fog;
67
- this.scene.fog.color = this._fogColor;
68
- this.scene.fog.density = this._fogDensity;
69
- this.scene.fog.start = this._fogStart;
70
- this.scene.fog.end = this._fogEnd;
71
-
72
- const appElement = this.parentElement as AppElement;
73
- appElement.app!.systems.rigidbody!.gravity.copy(this._gravity);
86
+ if (this._scene) {
87
+ this._scene.fog.type = this._fog;
88
+ this._scene.fog.color = this._fogColor;
89
+ this._scene.fog.density = this._fogDensity;
90
+ this._scene.fog.start = this._fogStart;
91
+ this._scene.fog.end = this._fogEnd;
92
+
93
+ this._applyGravity(this._gravity);
74
94
  }
75
95
  }
76
96
 
97
+ /**
98
+ * Applies gravity to the rigid body system. Resolved through `closestApp` rather than
99
+ * `parentElement` so that a `<pc-scene>` nested inside a wrapper element behaves the same as
100
+ * a direct child, matching how `connectedCallback` resolves the application.
101
+ *
102
+ * @param value - The gravity to apply.
103
+ */
104
+ private _applyGravity(value: Vec3) {
105
+ this.closestApp?.app?.systems.rigidbody?.gravity.copy(value);
106
+ }
107
+
77
108
  /**
78
109
  * Sets the fog type of the scene. Can be `none`, `linear`, `exp` or `exp2`. Defaults to
79
110
  * `none`.
@@ -176,9 +207,8 @@ class SceneElement extends AsyncElement {
176
207
  */
177
208
  set gravity(value: Vec3) {
178
209
  this._gravity = value;
179
- if (this.scene) {
180
- const appElement = this.parentElement as AppElement;
181
- appElement.app!.systems.rigidbody!.gravity.copy(value);
210
+ if (this._scene) {
211
+ this._applyGravity(value);
182
212
  }
183
213
  }
184
214
 
@@ -194,7 +224,7 @@ class SceneElement extends AsyncElement {
194
224
  return ['fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end', 'gravity'];
195
225
  }
196
226
 
197
- attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
227
+ attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
198
228
  switch (name) {
199
229
  case 'fog':
200
230
  this.fog = parseEnum(newValue, ['none', 'linear', 'exp', 'exp2'], 'none', name);
package/src/sky.ts CHANGED
@@ -3,7 +3,7 @@ import { Asset, EnvLighting, LAYERID_SKYBOX, Quat, Scene, Texture, Vec3 } from '
3
3
  import { AppElement } from './app';
4
4
  import { AssetElement } from './asset';
5
5
  import { AsyncElement } from './async-element';
6
- import { parseBool, parseEnum, parseNumber, parseVec3 } from './utils';
6
+ import { parseBool, parseEnum, parseNumber, parseVec3 } from './parse';
7
7
 
8
8
  /**
9
9
  * The SkyElement interface provides properties and methods for manipulating
@@ -270,10 +270,10 @@ class SkyElement extends AsyncElement {
270
270
  return ['asset', 'center', 'intensity', 'level', 'lighting', 'rotation', 'scale', 'type'];
271
271
  }
272
272
 
273
- attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
273
+ attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
274
274
  switch (name) {
275
275
  case 'asset':
276
- this.asset = newValue;
276
+ this.asset = newValue ?? '';
277
277
  break;
278
278
  case 'center':
279
279
  this.center = parseVec3(newValue, new Vec3(0, 0.01, 0), name);