@playcanvas/web-components 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/parse.ts CHANGED
@@ -113,21 +113,23 @@ export const parseColor = <T extends Color | null>(value: string | null, default
113
113
  * the value is invalid — the latter also logs a warning listing the valid names.
114
114
  *
115
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.
116
+ * @param valid - The valid names: an array, or a map whose keys are the valid names. Only the keys
117
+ * are read, so the map's value type is unconstrained - engine enums are mostly numeric constants,
118
+ * but some (e.g. `SCALEMODE_BLEND`) are strings.
117
119
  * @param defaultValue - The value to use when the attribute is absent or invalid.
118
120
  * @param attribute - The attribute name, used in the warning message.
119
121
  * @returns The resolved enum name.
120
122
  */
121
123
  export const parseEnum = <T extends string>(
122
124
  value: string | null,
123
- valid: readonly T[] | ReadonlyMap<T, number>,
125
+ valid: readonly T[] | ReadonlyMap<T, unknown>,
124
126
  defaultValue: T,
125
127
  attribute: string
126
128
  ): T => {
127
129
  if (value === null) {
128
130
  return defaultValue;
129
131
  }
130
- const names = Array.isArray(valid) ? valid : [...(valid as ReadonlyMap<T, number>).keys()];
132
+ const names: readonly T[] = Array.isArray(valid) ? valid : [...valid.keys()];
131
133
  if (names.includes(value as T)) {
132
134
  return value as T;
133
135
  }
package/src/scene.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Color, Scene, Vec3 } from 'playcanvas';
2
2
 
3
- import { AppElement } from './app';
4
3
  import { AsyncElement } from './async-element';
5
4
  import { parseColor, parseEnum, parseNumber, parseVec3 } from './parse';
6
5
 
@@ -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
 
package/src/sky.ts CHANGED
@@ -19,7 +19,7 @@ class SkyElement extends AsyncElement {
19
19
 
20
20
  private _rotation = new Vec3();
21
21
 
22
- private _level = 0;
22
+ private _mipLevel = 0;
23
23
 
24
24
  private _lighting = false;
25
25
 
@@ -65,7 +65,7 @@ class SkyElement extends AsyncElement {
65
65
  this._scene.sky.node.setLocalScale(this._scale);
66
66
  this._scene.sky.center = this._center;
67
67
  this._scene.skyboxIntensity = this._intensity;
68
- this._scene.skyboxMip = this._level;
68
+ this._scene.skyboxMip = this._mipLevel;
69
69
  }
70
70
 
71
71
  private async _loadSkybox() {
@@ -170,25 +170,6 @@ class SkyElement extends AsyncElement {
170
170
  return this._intensity;
171
171
  }
172
172
 
173
- /**
174
- * Sets the mip level of the skybox.
175
- * @param value - The mip level.
176
- */
177
- set level(value: number) {
178
- this._level = value;
179
- if (this._scene) {
180
- this._scene.skyboxMip = this._level;
181
- }
182
- }
183
-
184
- /**
185
- * Gets the mip level of the skybox.
186
- * @returns The mip level.
187
- */
188
- get level() {
189
- return this._level;
190
- }
191
-
192
173
  /**
193
174
  * Sets whether the skybox is used as a light source.
194
175
  * @param value - Whether to use lighting.
@@ -205,6 +186,26 @@ class SkyElement extends AsyncElement {
205
186
  return this._lighting;
206
187
  }
207
188
 
189
+ /**
190
+ * Sets the mip level of the skybox, where 0 is the sharpest. Raising it selects a blurrier mip,
191
+ * which is how a skybox is softened without blurring the texture itself.
192
+ * @param value - The mip level.
193
+ */
194
+ set mipLevel(value: number) {
195
+ this._mipLevel = value;
196
+ if (this._scene) {
197
+ this._scene.skyboxMip = this._mipLevel;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Gets the mip level of the skybox.
203
+ * @returns The mip level.
204
+ */
205
+ get mipLevel() {
206
+ return this._mipLevel;
207
+ }
208
+
208
209
  /**
209
210
  * Sets the Euler rotation of the skybox.
210
211
  * @param value - The rotation.
@@ -267,7 +268,7 @@ class SkyElement extends AsyncElement {
267
268
  }
268
269
 
269
270
  static get observedAttributes() {
270
- return ['asset', 'center', 'intensity', 'level', 'lighting', 'rotation', 'scale', 'type'];
271
+ return ['asset', 'center', 'intensity', 'lighting', 'mip-level', 'rotation', 'scale', 'type'];
271
272
  }
272
273
 
273
274
  attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
@@ -281,12 +282,12 @@ class SkyElement extends AsyncElement {
281
282
  case 'intensity':
282
283
  this.intensity = parseNumber(newValue, 1, name);
283
284
  break;
284
- case 'level':
285
- this.level = parseNumber(newValue, 0, name);
286
- break;
287
285
  case 'lighting':
288
286
  this.lighting = parseBool(newValue, false);
289
287
  break;
288
+ case 'mip-level':
289
+ this.mipLevel = parseNumber(newValue, 0, name);
290
+ break;
290
291
  case 'rotation':
291
292
  this.rotation = parseVec3(newValue, Vec3.ZERO, name);
292
293
  break;