@playcanvas/web-components 0.13.0 → 0.13.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.
package/dist/app.d.cts CHANGED
@@ -131,10 +131,45 @@ declare class AppElement extends AsyncElement {
131
131
  * @returns The nearest listening element, or `null`.
132
132
  */
133
133
  private _elementWithListener;
134
+ /**
135
+ * Converts a pointer event's client coordinates into drawing-buffer coordinates - the space
136
+ * the pick buffer and the camera viewports are laid out in. When the canvas has no CSS box
137
+ * to map through (jsdom; a hidden canvas receives no pointer events in a browser), the
138
+ * client coordinates are passed through unmapped and `mapped` is false, so callers know the
139
+ * coordinates correspond to no real geometry.
140
+ *
141
+ * @param event - The pointer event to convert.
142
+ * @param canvas - The canvas the event was dispatched on.
143
+ * @returns The buffer-space coordinates, and whether they were actually mapped.
144
+ */
134
145
  private _getPickerCoordinates;
146
+ /**
147
+ * Whether a camera's viewport contains the point. A camera renders into its normalized
148
+ * `rect`, whose origin is the bottom-left of the canvas while buffer coordinates run from
149
+ * the top-left - so the vertical test flips, as the engine's ElementInput flips it for UI
150
+ * input. The right and bottom edges are exclusive: a viewport rasterizes the half-open
151
+ * pixel range [left, right) x [top, bottom), so a coordinate on a shared edge belongs to
152
+ * the viewport whose first pixel it is - never to the one it just left, whose pick buffer
153
+ * holds nothing there.
154
+ *
155
+ * @param camera - The camera to test.
156
+ * @param x - The x coordinate, in buffer space.
157
+ * @param y - The y coordinate, in buffer space.
158
+ * @param canvas - The canvas the coordinates are relative to.
159
+ * @returns Whether the camera's viewport contains the point.
160
+ */
161
+ private _cameraContains;
135
162
  /**
136
163
  * Picks the scene under the pointer and returns the graph node that was hit, or `null`.
137
164
  *
165
+ * The camera is resolved the way the engine's ElementInput resolves it for UI input:
166
+ * enabled cameras are tried topmost-first (they render in ascending `priority` order),
167
+ * skipping cameras that render to a texture and cameras whose viewport `rect` does not
168
+ * contain the pointer. A camera that picks nothing ends the search if it clears the color
169
+ * buffer - its background visually owns the pixel - and otherwise cedes to the cameras
170
+ * beneath it, so an overlay camera only intercepts picks where it actually drew something.
171
+ * The pick buffer is prepared per camera, so each camera picks from its own layers.
172
+ *
138
173
  * The read back is asynchronous because the synchronous {@link Picker.getSelection} is not
139
174
  * supported on WebGPU, where it returns an empty selection rather than failing - which
140
175
  * silently disabled every `onpointer*` handler once WebGPU became the resolved backend. The
package/dist/app.d.ts CHANGED
@@ -131,10 +131,45 @@ declare class AppElement extends AsyncElement {
131
131
  * @returns The nearest listening element, or `null`.
132
132
  */
133
133
  private _elementWithListener;
134
+ /**
135
+ * Converts a pointer event's client coordinates into drawing-buffer coordinates - the space
136
+ * the pick buffer and the camera viewports are laid out in. When the canvas has no CSS box
137
+ * to map through (jsdom; a hidden canvas receives no pointer events in a browser), the
138
+ * client coordinates are passed through unmapped and `mapped` is false, so callers know the
139
+ * coordinates correspond to no real geometry.
140
+ *
141
+ * @param event - The pointer event to convert.
142
+ * @param canvas - The canvas the event was dispatched on.
143
+ * @returns The buffer-space coordinates, and whether they were actually mapped.
144
+ */
134
145
  private _getPickerCoordinates;
146
+ /**
147
+ * Whether a camera's viewport contains the point. A camera renders into its normalized
148
+ * `rect`, whose origin is the bottom-left of the canvas while buffer coordinates run from
149
+ * the top-left - so the vertical test flips, as the engine's ElementInput flips it for UI
150
+ * input. The right and bottom edges are exclusive: a viewport rasterizes the half-open
151
+ * pixel range [left, right) x [top, bottom), so a coordinate on a shared edge belongs to
152
+ * the viewport whose first pixel it is - never to the one it just left, whose pick buffer
153
+ * holds nothing there.
154
+ *
155
+ * @param camera - The camera to test.
156
+ * @param x - The x coordinate, in buffer space.
157
+ * @param y - The y coordinate, in buffer space.
158
+ * @param canvas - The canvas the coordinates are relative to.
159
+ * @returns Whether the camera's viewport contains the point.
160
+ */
161
+ private _cameraContains;
135
162
  /**
136
163
  * Picks the scene under the pointer and returns the graph node that was hit, or `null`.
137
164
  *
165
+ * The camera is resolved the way the engine's ElementInput resolves it for UI input:
166
+ * enabled cameras are tried topmost-first (they render in ascending `priority` order),
167
+ * skipping cameras that render to a texture and cameras whose viewport `rect` does not
168
+ * contain the pointer. A camera that picks nothing ends the search if it clears the color
169
+ * buffer - its background visually owns the pixel - and otherwise cedes to the cameras
170
+ * beneath it, so an overlay camera only intercepts picks where it actually drew something.
171
+ * The pick buffer is prepared per camera, so each camera picks from its own layers.
172
+ *
138
173
  * The read back is asynchronous because the synchronous {@link Picker.getSelection} is not
139
174
  * supported on WebGPU, where it returns an empty selection rather than failing - which
140
175
  * silently disabled every `onpointer*` handler once WebGPU became the resolved backend. The
package/dist/asset.d.cts CHANGED
@@ -16,6 +16,10 @@ type MagFilterMode = 'nearest' | 'linear';
16
16
  * immediately unless `lazy`. A `pc-asset` must be a direct child of `pc-app` — elements placed
17
17
  * elsewhere, or with an unsupported asset type, never become ready.
18
18
  *
19
+ * A `lazy` asset loads on first use: the first time any element resolves it by `id` — a model,
20
+ * a material map, a sky, a script `asset:` reference — or when the `lazy` attribute is removed,
21
+ * whichever comes first. Until then it stays registered and unloaded.
22
+ *
19
23
  * For `texture` and `textureatlas` assets, the texture options (`address-u`, `address-v`,
20
24
  * `min-filter`, `mag-filter`, `anisotropy`, `mipmaps`, `srgb`, `flip-y`) apply when the texture is
21
25
  * created and — like `lazy` — are observed: changing one updates a texture that has already
@@ -146,7 +150,9 @@ declare class AssetElement extends AsyncElement {
146
150
  */
147
151
  get flipY(): boolean | null;
148
152
  /**
149
- * Sets whether the asset should be loaded lazily.
153
+ * Sets whether the asset should be loaded lazily. A lazy asset is registered without being
154
+ * loaded; it loads on first use - the first time any element resolves it by `id` - or when
155
+ * this flag is cleared on a registered asset, whichever comes first.
150
156
  * @param value - The lazy loading flag.
151
157
  */
152
158
  set lazy(value: boolean);
package/dist/asset.d.ts CHANGED
@@ -16,6 +16,10 @@ type MagFilterMode = 'nearest' | 'linear';
16
16
  * immediately unless `lazy`. A `pc-asset` must be a direct child of `pc-app` — elements placed
17
17
  * elsewhere, or with an unsupported asset type, never become ready.
18
18
  *
19
+ * A `lazy` asset loads on first use: the first time any element resolves it by `id` — a model,
20
+ * a material map, a sky, a script `asset:` reference — or when the `lazy` attribute is removed,
21
+ * whichever comes first. Until then it stays registered and unloaded.
22
+ *
19
23
  * For `texture` and `textureatlas` assets, the texture options (`address-u`, `address-v`,
20
24
  * `min-filter`, `mag-filter`, `anisotropy`, `mipmaps`, `srgb`, `flip-y`) apply when the texture is
21
25
  * created and — like `lazy` — are observed: changing one updates a texture that has already
@@ -146,7 +150,9 @@ declare class AssetElement extends AsyncElement {
146
150
  */
147
151
  get flipY(): boolean | null;
148
152
  /**
149
- * Sets whether the asset should be loaded lazily.
153
+ * Sets whether the asset should be loaded lazily. A lazy asset is registered without being
154
+ * loaded; it loads on first use - the first time any element resolves it by `id` - or when
155
+ * this flag is cleared on a registered asset, whichever comes first.
150
156
  * @param value - The lazy loading flag.
151
157
  */
152
158
  set lazy(value: boolean);
@@ -346,7 +346,7 @@
346
346
  "declarations": [
347
347
  {
348
348
  "kind": "class",
349
- "description": "The AssetElement interface provides properties and methods for manipulating\n[`<pc-asset>`](https://developer.playcanvas.com/user-manual/web-components/tags/pc-asset/) elements.\nThe AssetElement interface also inherits the properties and methods of the\nHTMLElement interface.\n\nThe element becomes ready once the containing application has started and the asset is in the\nstate declared by the markup: loaded for preloaded assets (even if loading failed — check the\nasset's `resource`), or registered and awaiting a load for `lazy` assets. Elements inserted\nwhile the application is running are created and registered on insertion, and begin loading\nimmediately unless `lazy`. A `pc-asset` must be a direct child of `pc-app` — elements placed\nelsewhere, or with an unsupported asset type, never become ready.\n\nFor `texture` and `textureatlas` assets, the texture options (`address-u`, `address-v`,\n`min-filter`, `mag-filter`, `anisotropy`, `mipmaps`, `srgb`, `flip-y`) apply when the texture is\ncreated and — like `lazy` — are observed: changing one updates a texture that has already\nloaded, and removing one restores the engine default. Changing `srgb` or `mipmaps` on a loaded\ntexture recreates the underlying GPU resource, so prefer declaring those up front. Each option\noverrides the matching key in the `data` JSON; options left unset write nothing, leaving the\nengine's per-format defaults in force.\n\nApart from `lazy` and the texture options, these attributes are read once when the asset is\ncreated, so changing them later has no effect.\n",
349
+ "description": "The AssetElement interface provides properties and methods for manipulating\n[`<pc-asset>`](https://developer.playcanvas.com/user-manual/web-components/tags/pc-asset/) elements.\nThe AssetElement interface also inherits the properties and methods of the\nHTMLElement interface.\n\nThe element becomes ready once the containing application has started and the asset is in the\nstate declared by the markup: loaded for preloaded assets (even if loading failed — check the\nasset's `resource`), or registered and awaiting a load for `lazy` assets. Elements inserted\nwhile the application is running are created and registered on insertion, and begin loading\nimmediately unless `lazy`. A `pc-asset` must be a direct child of `pc-app` — elements placed\nelsewhere, or with an unsupported asset type, never become ready.\n\nA `lazy` asset loads on first use: the first time any element resolves it by `id` — a model,\na material map, a sky, a script `asset:` reference — or when the `lazy` attribute is removed,\nwhichever comes first. Until then it stays registered and unloaded.\n\nFor `texture` and `textureatlas` assets, the texture options (`address-u`, `address-v`,\n`min-filter`, `mag-filter`, `anisotropy`, `mipmaps`, `srgb`, `flip-y`) apply when the texture is\ncreated and — like `lazy` — are observed: changing one updates a texture that has already\nloaded, and removing one restores the engine default. Changing `srgb` or `mipmaps` on a loaded\ntexture recreates the underlying GPU resource, so prefer declaring those up front. Each option\noverrides the matching key in the `data` JSON; options left unset write nothing, leaving the\nengine's per-format defaults in force.\n\nApart from `lazy` and the texture options, these attributes are read once when the asset is\ncreated, so changing them later has no effect.\n",
350
350
  "name": "AssetElement",
351
351
  "members": [
352
352
  {
package/dist/pwc.cjs CHANGED
@@ -1250,21 +1250,63 @@ class AppElement extends AsyncElement {
1250
1250
  }
1251
1251
  return null;
1252
1252
  }
1253
- // New helper to convert CSS coordinates to canvas (picker) coordinates
1254
- _getPickerCoordinates(event) {
1255
- // Get the canvas' bounding rectangle in CSS pixels.
1256
- const canvasRect = this._canvas.getBoundingClientRect();
1257
- // Compute scale factors based on canvas actual resolution vs. its CSS display size.
1258
- const scaleX = this._canvas.width / canvasRect.width;
1259
- const scaleY = this._canvas.height / canvasRect.height;
1260
- // Convert the client coordinates accordingly.
1261
- const x = (event.clientX - canvasRect.left) * scaleX;
1262
- const y = (event.clientY - canvasRect.top) * scaleY;
1263
- return { x, y };
1253
+ /**
1254
+ * Converts a pointer event's client coordinates into drawing-buffer coordinates - the space
1255
+ * the pick buffer and the camera viewports are laid out in. When the canvas has no CSS box
1256
+ * to map through (jsdom; a hidden canvas receives no pointer events in a browser), the
1257
+ * client coordinates are passed through unmapped and `mapped` is false, so callers know the
1258
+ * coordinates correspond to no real geometry.
1259
+ *
1260
+ * @param event - The pointer event to convert.
1261
+ * @param canvas - The canvas the event was dispatched on.
1262
+ * @returns The buffer-space coordinates, and whether they were actually mapped.
1263
+ */
1264
+ _getPickerCoordinates(event, canvas) {
1265
+ const canvasRect = canvas.getBoundingClientRect();
1266
+ if (canvasRect.width === 0 || canvasRect.height === 0) {
1267
+ return { x: event.clientX, y: event.clientY, mapped: false };
1268
+ }
1269
+ const scaleX = canvas.width / canvasRect.width;
1270
+ const scaleY = canvas.height / canvasRect.height;
1271
+ return {
1272
+ x: (event.clientX - canvasRect.left) * scaleX,
1273
+ y: (event.clientY - canvasRect.top) * scaleY,
1274
+ mapped: true
1275
+ };
1276
+ }
1277
+ /**
1278
+ * Whether a camera's viewport contains the point. A camera renders into its normalized
1279
+ * `rect`, whose origin is the bottom-left of the canvas while buffer coordinates run from
1280
+ * the top-left - so the vertical test flips, as the engine's ElementInput flips it for UI
1281
+ * input. The right and bottom edges are exclusive: a viewport rasterizes the half-open
1282
+ * pixel range [left, right) x [top, bottom), so a coordinate on a shared edge belongs to
1283
+ * the viewport whose first pixel it is - never to the one it just left, whose pick buffer
1284
+ * holds nothing there.
1285
+ *
1286
+ * @param camera - The camera to test.
1287
+ * @param x - The x coordinate, in buffer space.
1288
+ * @param y - The y coordinate, in buffer space.
1289
+ * @param canvas - The canvas the coordinates are relative to.
1290
+ * @returns Whether the camera's viewport contains the point.
1291
+ */
1292
+ _cameraContains(camera, x, y, canvas) {
1293
+ const rect = camera.rect;
1294
+ const left = rect.x * canvas.width;
1295
+ const bottom = (1 - rect.y) * canvas.height;
1296
+ const top = bottom - rect.w * canvas.height;
1297
+ return x >= left && x < left + rect.z * canvas.width && y >= top && y < bottom;
1264
1298
  }
1265
1299
  /**
1266
1300
  * Picks the scene under the pointer and returns the graph node that was hit, or `null`.
1267
1301
  *
1302
+ * The camera is resolved the way the engine's ElementInput resolves it for UI input:
1303
+ * enabled cameras are tried topmost-first (they render in ascending `priority` order),
1304
+ * skipping cameras that render to a texture and cameras whose viewport `rect` does not
1305
+ * contain the pointer. A camera that picks nothing ends the search if it clears the color
1306
+ * buffer - its background visually owns the pixel - and otherwise cedes to the cameras
1307
+ * beneath it, so an overlay camera only intercepts picks where it actually drew something.
1308
+ * The pick buffer is prepared per camera, so each camera picks from its own layers.
1309
+ *
1268
1310
  * The read back is asynchronous because the synchronous {@link Picker.getSelection} is not
1269
1311
  * supported on WebGPU, where it returns an empty selection rather than failing - which
1270
1312
  * silently disabled every `onpointer*` handler once WebGPU became the resolved backend. The
@@ -1274,16 +1316,40 @@ class AppElement extends AsyncElement {
1274
1316
  * @returns The graph node under the pointer, or `null` if nothing was hit.
1275
1317
  */
1276
1318
  async _pickNode(event) {
1277
- const camera = this.app.root.findComponent('camera');
1278
- if (!camera)
1279
- return null;
1280
- const { x, y } = this._getPickerCoordinates(event);
1281
- this._picker.prepare(camera, this.app.scene);
1282
- const selection = await this._picker.getSelectionAsync(x, y);
1283
- if (selection.length === 0)
1319
+ const app = this.app;
1320
+ const picker = this._picker;
1321
+ const canvas = this._canvas;
1322
+ if (!app || !picker || !canvas)
1284
1323
  return null;
1285
- const item = selection[0];
1286
- return item instanceof playcanvas.MeshInstance ? item.node : item.entity;
1324
+ const { x, y, mapped } = this._getPickerCoordinates(event, canvas);
1325
+ // Walked from the end: the array is sorted by ascending priority, so the last camera
1326
+ // renders last and sits on top. Read through .at() because a pick handler may remove
1327
+ // cameras while an earlier iteration's read back is in flight.
1328
+ const cameras = app.systems.camera?.cameras ?? [];
1329
+ for (let i = cameras.length - 1; i >= 0; i--) {
1330
+ const camera = cameras.at(i);
1331
+ // A camera rendering to a texture is not on the canvas.
1332
+ if (!camera || camera.renderTarget)
1333
+ continue;
1334
+ // Coordinates that could not be mapped cannot be tested for containment.
1335
+ if (mapped && !this._cameraContains(camera, x, y, canvas))
1336
+ continue;
1337
+ picker.prepare(camera, app.scene);
1338
+ const selection = await picker.getSelectionAsync(x, y);
1339
+ // The element may have disconnected while the read back was in flight.
1340
+ if (!this._picker || !this.app)
1341
+ return null;
1342
+ if (selection.length > 0) {
1343
+ const item = selection[0];
1344
+ return item instanceof playcanvas.MeshInstance ? item.node : item.entity;
1345
+ }
1346
+ // Nothing hit. A camera that clears the color buffer paints its background over
1347
+ // everything beneath it, so the miss is final; one that does not is an overlay
1348
+ // that the cameras beneath show through, so they get their turn.
1349
+ if (camera.clearColorBuffer)
1350
+ return null;
1351
+ }
1352
+ return null;
1287
1353
  }
1288
1354
  async _onPointerMove(event) {
1289
1355
  if (!this._picker || !this.app)
@@ -2227,6 +2293,10 @@ const processBufferView = (gltfBuffer, buffers, continuation) => {
2227
2293
  * immediately unless `lazy`. A `pc-asset` must be a direct child of `pc-app` — elements placed
2228
2294
  * elsewhere, or with an unsupported asset type, never become ready.
2229
2295
  *
2296
+ * A `lazy` asset loads on first use: the first time any element resolves it by `id` — a model,
2297
+ * a material map, a sky, a script `asset:` reference — or when the `lazy` attribute is removed,
2298
+ * whichever comes first. Until then it stays registered and unloaded.
2299
+ *
2230
2300
  * For `texture` and `textureatlas` assets, the texture options (`address-u`, `address-v`,
2231
2301
  * `min-filter`, `mag-filter`, `anisotropy`, `mipmaps`, `srgb`, `flip-y`) apply when the texture is
2232
2302
  * created and — like `lazy` — are observed: changing one updates a texture that has already
@@ -2426,7 +2496,9 @@ class AssetElement extends AsyncElement {
2426
2496
  if (type === 'sprite') {
2427
2497
  data = data ?? {};
2428
2498
  // Resolve the referenced texture atlas to its (numeric) asset id. The atlas must be
2429
- // declared before the sprite so its asset already exists in the registry.
2499
+ // declared before the sprite so its asset already exists in the registry. Resolved
2500
+ // with get, not useAsset: creation-time wiring is not a use, and the engine's
2501
+ // sprite handler loads the atlas when the sprite itself loads.
2430
2502
  const atlas = this.getAttribute('atlas') ?? data.textureAtlasAsset;
2431
2503
  if (typeof atlas === 'string') {
2432
2504
  const atlasAsset = AssetElement.get(atlas);
@@ -2587,13 +2659,18 @@ class AssetElement extends AsyncElement {
2587
2659
  return this._flipY;
2588
2660
  }
2589
2661
  /**
2590
- * Sets whether the asset should be loaded lazily.
2662
+ * Sets whether the asset should be loaded lazily. A lazy asset is registered without being
2663
+ * loaded; it loads on first use - the first time any element resolves it by `id` - or when
2664
+ * this flag is cleared on a registered asset, whichever comes first.
2591
2665
  * @param value - The lazy loading flag.
2592
2666
  */
2593
2667
  set lazy(value) {
2594
2668
  this._lazy = value;
2595
2669
  if (this.asset) {
2596
2670
  this.asset.preload = !value;
2671
+ if (!value) {
2672
+ this.asset.registry?.load(this.asset);
2673
+ }
2597
2674
  }
2598
2675
  }
2599
2676
  /**
@@ -2777,6 +2854,26 @@ class AssetElement extends AsyncElement {
2777
2854
  }
2778
2855
  }
2779
2856
  customElements.define('pc-asset', AssetElement);
2857
+ /**
2858
+ * Resolves an asset reference for use: {@link AssetElement.get}, plus starting the load of a
2859
+ * registered asset that has not begun one - a `lazy` asset. Every element that consumes assets
2860
+ * resolves its references here, which is what makes `lazy` mean load on first use without any
2861
+ * consumer having to remember the load. The load is asynchronous - callers observe the asset's
2862
+ * `load` event for the resource.
2863
+ *
2864
+ * @param id - The `id` of the `<pc-asset>` element.
2865
+ * @returns The asset, or `undefined`.
2866
+ * @internal
2867
+ */
2868
+ const useAsset = (id) => {
2869
+ const asset = AssetElement.get(id);
2870
+ // load() ignores an asset that is already loaded or loading, so repeated resolution
2871
+ // costs nothing.
2872
+ if (asset) {
2873
+ asset.registry?.load(asset);
2874
+ }
2875
+ return asset;
2876
+ };
2780
2877
 
2781
2878
  /**
2782
2879
  * Represents a component in the PlayCanvas engine.
@@ -3060,15 +3157,15 @@ class ButtonComponentElement extends ComponentElement {
3060
3157
  if (imageEntity) {
3061
3158
  data.imageEntity = imageEntity;
3062
3159
  }
3063
- const hoverSpriteAsset = AssetElement.get(this._hoverSpriteAsset);
3160
+ const hoverSpriteAsset = useAsset(this._hoverSpriteAsset);
3064
3161
  if (hoverSpriteAsset) {
3065
3162
  data.hoverSpriteAsset = hoverSpriteAsset.id;
3066
3163
  }
3067
- const pressedSpriteAsset = AssetElement.get(this._pressedSpriteAsset);
3164
+ const pressedSpriteAsset = useAsset(this._pressedSpriteAsset);
3068
3165
  if (pressedSpriteAsset) {
3069
3166
  data.pressedSpriteAsset = pressedSpriteAsset.id;
3070
3167
  }
3071
- const inactiveSpriteAsset = AssetElement.get(this._inactiveSpriteAsset);
3168
+ const inactiveSpriteAsset = useAsset(this._inactiveSpriteAsset);
3072
3169
  if (inactiveSpriteAsset) {
3073
3170
  data.inactiveSpriteAsset = inactiveSpriteAsset.id;
3074
3171
  }
@@ -3230,7 +3327,7 @@ class ButtonComponentElement extends ComponentElement {
3230
3327
  */
3231
3328
  set hoverSpriteAsset(value) {
3232
3329
  this._hoverSpriteAsset = value;
3233
- const asset = AssetElement.get(value);
3330
+ const asset = useAsset(value);
3234
3331
  if (this.component && asset) {
3235
3332
  this.component.hoverSpriteAsset = asset.id;
3236
3333
  }
@@ -3266,7 +3363,7 @@ class ButtonComponentElement extends ComponentElement {
3266
3363
  */
3267
3364
  set pressedSpriteAsset(value) {
3268
3365
  this._pressedSpriteAsset = value;
3269
- const asset = AssetElement.get(value);
3366
+ const asset = useAsset(value);
3270
3367
  if (this.component && asset) {
3271
3368
  this.component.pressedSpriteAsset = asset.id;
3272
3369
  }
@@ -3302,7 +3399,7 @@ class ButtonComponentElement extends ComponentElement {
3302
3399
  */
3303
3400
  set inactiveSpriteAsset(value) {
3304
3401
  this._inactiveSpriteAsset = value;
3305
- const asset = AssetElement.get(value);
3402
+ const asset = useAsset(value);
3306
3403
  if (this.component && asset) {
3307
3404
  this.component.inactiveSpriteAsset = asset.id;
3308
3405
  }
@@ -4160,15 +4257,15 @@ class ElementComponentElement extends ComponentElement {
4160
4257
  };
4161
4258
  // Asset references are resolved from `<pc-asset>` element ids to engine asset ids. They are
4162
4259
  // only included when they resolve, so image/group elements (with no font) don't error.
4163
- const fontAsset = AssetElement.get(this._fontAsset);
4260
+ const fontAsset = useAsset(this._fontAsset);
4164
4261
  if (fontAsset) {
4165
4262
  data.fontAsset = fontAsset.id;
4166
4263
  }
4167
- const spriteAsset = AssetElement.get(this._spriteAsset);
4264
+ const spriteAsset = useAsset(this._spriteAsset);
4168
4265
  if (spriteAsset) {
4169
4266
  data.spriteAsset = spriteAsset.id;
4170
4267
  }
4171
- const textureAsset = AssetElement.get(this._textureAsset);
4268
+ const textureAsset = useAsset(this._textureAsset);
4172
4269
  if (textureAsset) {
4173
4270
  data.textureAsset = textureAsset.id;
4174
4271
  }
@@ -4282,7 +4379,7 @@ class ElementComponentElement extends ComponentElement {
4282
4379
  */
4283
4380
  set fontAsset(value) {
4284
4381
  this._fontAsset = value;
4285
- const asset = AssetElement.get(value);
4382
+ const asset = useAsset(value);
4286
4383
  if (this.component && asset) {
4287
4384
  this.component.fontAsset = asset.id;
4288
4385
  }
@@ -4437,7 +4534,7 @@ class ElementComponentElement extends ComponentElement {
4437
4534
  */
4438
4535
  set spriteAsset(value) {
4439
4536
  this._spriteAsset = value;
4440
- const asset = AssetElement.get(value);
4537
+ const asset = useAsset(value);
4441
4538
  if (this.component && asset) {
4442
4539
  this.component.spriteAsset = asset.id;
4443
4540
  }
@@ -4489,7 +4586,7 @@ class ElementComponentElement extends ComponentElement {
4489
4586
  */
4490
4587
  set textureAsset(value) {
4491
4588
  this._textureAsset = value;
4492
- const asset = AssetElement.get(value);
4589
+ const asset = useAsset(value);
4493
4590
  if (this.component && asset) {
4494
4591
  this.component.textureAsset = asset.id;
4495
4592
  }
@@ -5724,13 +5821,14 @@ class ParticleSystemComponentElement extends ComponentElement {
5724
5821
  super('particlesystem');
5725
5822
  }
5726
5823
  getInitialComponentData() {
5727
- const asset = AssetElement.get(this._asset);
5728
- if (!asset) {
5824
+ const asset = useAsset(this._asset);
5825
+ // A lazy config has no resource yet - _loadAsset applies it once the load completes
5826
+ if (!asset || !asset.resource) {
5729
5827
  return {};
5730
5828
  }
5731
5829
  if (asset.resource.colorMapAsset) {
5732
5830
  const id = asset.resource.colorMapAsset;
5733
- const colorMapAsset = AssetElement.get(id)?.id;
5831
+ const colorMapAsset = useAsset(id)?.id;
5734
5832
  if (colorMapAsset) {
5735
5833
  asset.resource.colorMapAsset = colorMapAsset;
5736
5834
  }
@@ -5756,9 +5854,8 @@ class ParticleSystemComponentElement extends ComponentElement {
5756
5854
  }
5757
5855
  }
5758
5856
  async _loadAsset() {
5759
- const appElement = await this.closestApp?.ready();
5760
- const app = appElement?.app;
5761
- const asset = AssetElement.get(this._asset);
5857
+ await this.closestApp?.ready();
5858
+ const asset = useAsset(this._asset);
5762
5859
  if (!asset) {
5763
5860
  return;
5764
5861
  }
@@ -5769,7 +5866,6 @@ class ParticleSystemComponentElement extends ComponentElement {
5769
5866
  asset.once('load', () => {
5770
5867
  this.applyConfig(asset.resource);
5771
5868
  });
5772
- app.assets.load(asset);
5773
5869
  }
5774
5870
  }
5775
5871
  /**
@@ -6185,7 +6281,7 @@ class MaterialElement extends HTMLElement {
6185
6281
  this._scheduleUpdate();
6186
6282
  return;
6187
6283
  }
6188
- const asset = AssetElement.get(id);
6284
+ const asset = useAsset(id);
6189
6285
  if (!asset)
6190
6286
  return;
6191
6287
  if (asset.loaded) {
@@ -9286,7 +9382,7 @@ const camelToKebab = (name) => {
9286
9382
  * @returns The asset, or `raw`.
9287
9383
  */
9288
9384
  const assetConversion = (rest, raw) => {
9289
- const asset = AssetElement.get(rest);
9385
+ const asset = useAsset(rest);
9290
9386
  if (asset) {
9291
9387
  return asset;
9292
9388
  }
@@ -10106,7 +10202,7 @@ class SoundSlotElement extends AsyncElement {
10106
10202
  set asset(value) {
10107
10203
  this._asset = value;
10108
10204
  if (this.soundSlot) {
10109
- const id = AssetElement.get(value)?.id;
10205
+ const id = useAsset(value)?.id;
10110
10206
  if (id) {
10111
10207
  this.soundSlot.asset = id;
10112
10208
  }
@@ -10313,7 +10409,7 @@ class GSplatComponentElement extends ComponentElement {
10313
10409
  }
10314
10410
  getInitialComponentData() {
10315
10411
  return {
10316
- asset: AssetElement.get(this._asset),
10412
+ asset: useAsset(this._asset),
10317
10413
  castShadows: this._castShadows,
10318
10414
  lodBaseDistance: this._lodBaseDistance,
10319
10415
  lodMultiplier: this._lodMultiplier,
@@ -10334,7 +10430,7 @@ class GSplatComponentElement extends ComponentElement {
10334
10430
  */
10335
10431
  set asset(value) {
10336
10432
  this._asset = value;
10337
- const asset = AssetElement.get(value);
10433
+ const asset = useAsset(value);
10338
10434
  if (this.component && asset) {
10339
10435
  this.component.asset = asset;
10340
10436
  }
@@ -10612,8 +10708,7 @@ class ModelElement extends AsyncElement {
10612
10708
  if (generation !== this._loadGeneration) {
10613
10709
  return;
10614
10710
  }
10615
- const app = appElement.app;
10616
- const asset = AssetElement.get(this._asset);
10711
+ const asset = useAsset(this._asset);
10617
10712
  if (!asset) {
10618
10713
  // An empty id is a legitimate transient (the asset may be assigned later); a
10619
10714
  // non-empty one that resolves to nothing is a dead end - say so rather than staying
@@ -10649,7 +10744,6 @@ class ModelElement extends AsyncElement {
10649
10744
  }));
10650
10745
  this._onReady();
10651
10746
  });
10652
- app.assets.load(asset);
10653
10747
  }
10654
10748
  }
10655
10749
  _unloadModel() {
@@ -11614,7 +11708,7 @@ class SkyElement extends AsyncElement {
11614
11708
  return;
11615
11709
  }
11616
11710
  this._appElement = appElement;
11617
- const asset = AssetElement.get(this._asset);
11711
+ const asset = useAsset(this._asset);
11618
11712
  if (!asset) {
11619
11713
  return;
11620
11714
  }
@@ -11633,7 +11727,6 @@ class SkyElement extends AsyncElement {
11633
11727
  }
11634
11728
  this._generateSkybox(asset);
11635
11729
  });
11636
- app.assets.load(asset);
11637
11730
  }
11638
11731
  }
11639
11732
  _unloadSkybox() {