@playcanvas/web-components 0.10.1 → 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.
@@ -99,7 +99,8 @@ class ScrollViewComponentElement extends ComponentElement {
99
99
  }
100
100
 
101
101
  /**
102
- * Sets whether horizontal scrolling is enabled.
102
+ * Sets whether scrolling along the horizontal axis is enabled. This is a toggle, unlike the
103
+ * `orientation` of a `<pc-scrollbar>`, for which `horizontal` is one of the accepted values.
103
104
  * @param value - Whether horizontal scrolling is enabled.
104
105
  */
105
106
  set horizontal(value: boolean) {
@@ -110,7 +111,7 @@ class ScrollViewComponentElement extends ComponentElement {
110
111
  }
111
112
 
112
113
  /**
113
- * Gets whether horizontal scrolling is enabled.
114
+ * Gets whether scrolling along the horizontal axis is enabled.
114
115
  * @returns Whether horizontal scrolling is enabled.
115
116
  */
116
117
  get horizontal() {
@@ -118,7 +119,8 @@ class ScrollViewComponentElement extends ComponentElement {
118
119
  }
119
120
 
120
121
  /**
121
- * Sets whether vertical scrolling is enabled.
122
+ * Sets whether scrolling along the vertical axis is enabled. This is a toggle, unlike the
123
+ * `orientation` of a `<pc-scrollbar>`, for which `vertical` is one of the accepted values.
122
124
  * @param value - Whether vertical scrolling is enabled.
123
125
  */
124
126
  set vertical(value: boolean) {
@@ -129,7 +131,7 @@ class ScrollViewComponentElement extends ComponentElement {
129
131
  }
130
132
 
131
133
  /**
132
- * Gets whether vertical scrolling is enabled.
134
+ * Gets whether scrolling along the vertical axis is enabled.
133
135
  * @returns Whether vertical scrolling is enabled.
134
136
  */
135
137
  get vertical() {
package/src/entity.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { AppBase, Entity, Vec3 } from 'playcanvas';
2
2
 
3
+ import type { AppElement } from './app';
3
4
  import { AsyncElement } from './async-element';
4
5
  import { parseBool, parseTags, parseVec3 } from './parse';
5
6
 
@@ -75,6 +76,12 @@ class EntityElement extends AsyncElement {
75
76
 
76
77
  private _entity: Entity | null = null;
77
78
 
79
+ /**
80
+ * The application element this entity is registered with, cached at creation time so the
81
+ * entity can be unregistered even once this element has left the DOM.
82
+ */
83
+ private _appElement: AppElement | null = null;
84
+
78
85
  /**
79
86
  * The PlayCanvas entity instance. `null` until the element is ready, and again once it has
80
87
  * been removed from the document — await {@link whenReady} or the element's `ready()`
@@ -108,6 +115,29 @@ class EntityElement extends AsyncElement {
108
115
  if (this._tags.length > 0) {
109
116
  entity.tags.add(this._tags);
110
117
  }
118
+
119
+ // Register with the owning application, which joins engine nodes back to elements by
120
+ // identity (never by name), and hook the entity's destruction. The engine fires 'destroy'
121
+ // for every entity in a destroyed subtree, so the element learns of its entity's death no
122
+ // matter who causes it: this element, an ancestor, the whole application, or a user
123
+ // script calling entity.destroy().
124
+ this._appElement = this.closestApp;
125
+ this._appElement?._registerEntityElement(entity, this);
126
+ entity.once('destroy', this._onEntityDestroy, this);
127
+ }
128
+
129
+ /**
130
+ * Handles the destruction of the backing entity. Resets the element so a later re-insertion
131
+ * starts clean: `_built` must be cleared alongside `_entity`, or buildHierarchy would bail
132
+ * and a re-created entity would never be parented.
133
+ *
134
+ * @param entity - The entity that was destroyed.
135
+ */
136
+ private _onEntityDestroy(entity: Entity) {
137
+ this._appElement?._unregisterEntityElement(entity);
138
+ this._appElement = null;
139
+ this._entity = null;
140
+ this._built = false;
111
141
  }
112
142
 
113
143
  buildHierarchy(app: AppBase) {
@@ -156,23 +186,11 @@ class EntityElement extends AsyncElement {
156
186
  }
157
187
 
158
188
  disconnectedCallback() {
159
- if (this.entity) {
160
- // Notify all children that their entities are about to become invalid. Both fields have
161
- // to be reset here, not just _entity: a descendant's own disconnectedCallback runs after
162
- // this one and skips its reset behind the `if (this.entity)` guard, because we have
163
- // already nulled the entity it tests. Leaving _built set would make buildHierarchy bail
164
- // on re-insertion, so the descendant would get a fresh entity that is never parented.
165
- const children = this.querySelectorAll<EntityElement>('pc-entity');
166
- children.forEach((child) => {
167
- child._entity = null;
168
- child._built = false;
169
- });
170
-
171
- // Destroy the entity
172
- this.entity.destroy();
173
- this._entity = null;
174
- this._built = false;
175
- }
189
+ // Destroying the entity destroys its whole subtree, and the engine fires 'destroy' for
190
+ // every entity in it - so _onEntityDestroy resets this element AND every descendant
191
+ // element before the descendants' own disconnectedCallbacks run. Their entities are null
192
+ // by then, making this call a no-op for them.
193
+ this._entity?.destroy();
176
194
  }
177
195
 
178
196
  /**
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/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;