@playcanvas/web-components 0.10.1 → 0.11.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 (78) hide show
  1. package/README.md +1 -1
  2. package/dist/app.d.ts +102 -51
  3. package/dist/asset.d.ts +8 -7
  4. package/dist/async-element.d.ts +21 -5
  5. package/dist/components/button-component.d.ts +3 -7
  6. package/dist/components/camera-component.d.ts +16 -20
  7. package/dist/components/collision-component.d.ts +3 -7
  8. package/dist/components/component.d.ts +22 -4
  9. package/dist/components/element-component.d.ts +4 -8
  10. package/dist/components/gsplat-component.d.ts +2 -7
  11. package/dist/components/layoutchild-component.d.ts +2 -7
  12. package/dist/components/layoutgroup-component.d.ts +3 -7
  13. package/dist/components/light-component.d.ts +3 -7
  14. package/dist/components/listener-component.d.ts +1 -6
  15. package/dist/components/particlesystem-component.d.ts +2 -7
  16. package/dist/components/render-component.d.ts +2 -7
  17. package/dist/components/rigidbody-component.d.ts +3 -7
  18. package/dist/components/screen-component.d.ts +28 -11
  19. package/dist/components/script-component.d.ts +8 -20
  20. package/dist/components/script.d.ts +2 -22
  21. package/dist/components/scrollbar-component.d.ts +2 -7
  22. package/dist/components/scrollview-component.d.ts +9 -11
  23. package/dist/components/sound-component.d.ts +2 -7
  24. package/dist/components/sound-slot.d.ts +8 -6
  25. package/dist/custom-elements.json +5191 -10695
  26. package/dist/entity.d.ts +16 -9
  27. package/dist/index.d.ts +37 -0
  28. package/dist/material.d.ts +12 -12
  29. package/dist/model.d.ts +21 -5
  30. package/dist/module.d.ts +0 -6
  31. package/dist/parse.d.ts +6 -3
  32. package/dist/pwc.cjs +799 -287
  33. package/dist/pwc.cjs.map +1 -1
  34. package/dist/pwc.js +799 -287
  35. package/dist/pwc.js.map +1 -1
  36. package/dist/pwc.min.js +1 -1
  37. package/dist/pwc.min.js.map +1 -1
  38. package/dist/pwc.min.mjs +1 -1
  39. package/dist/pwc.min.mjs.map +1 -1
  40. package/dist/pwc.mjs +800 -288
  41. package/dist/pwc.mjs.map +1 -1
  42. package/dist/scene.d.ts +4 -7
  43. package/dist/sky.d.ts +25 -16
  44. package/dist/vscode.html-custom-data.json +65 -45
  45. package/dist/web-types.json +585 -557
  46. package/package.json +8 -7
  47. package/src/app.ts +326 -144
  48. package/src/asset.ts +34 -28
  49. package/src/async-element.ts +34 -8
  50. package/src/components/button-component.ts +5 -9
  51. package/src/components/camera-component.ts +55 -36
  52. package/src/components/collision-component.ts +26 -15
  53. package/src/components/component.ts +58 -8
  54. package/src/components/element-component.ts +26 -30
  55. package/src/components/gsplat-component.ts +4 -9
  56. package/src/components/layoutchild-component.ts +4 -9
  57. package/src/components/layoutgroup-component.ts +14 -9
  58. package/src/components/light-component.ts +42 -12
  59. package/src/components/listener-component.ts +1 -7
  60. package/src/components/particlesystem-component.ts +7 -15
  61. package/src/components/render-component.ts +5 -10
  62. package/src/components/rigidbody-component.ts +23 -16
  63. package/src/components/screen-component.ts +46 -20
  64. package/src/components/script-component.ts +108 -46
  65. package/src/components/script.ts +38 -33
  66. package/src/components/scrollbar-component.ts +6 -16
  67. package/src/components/scrollview-component.ts +22 -15
  68. package/src/components/sound-component.ts +10 -15
  69. package/src/components/sound-slot.ts +30 -20
  70. package/src/entity.ts +75 -34
  71. package/src/index.ts +45 -1
  72. package/src/loading-bar.ts +8 -8
  73. package/src/material.ts +63 -37
  74. package/src/model.ts +69 -14
  75. package/src/module.ts +8 -7
  76. package/src/parse.ts +67 -20
  77. package/src/scene.ts +12 -9
  78. package/src/sky.ts +76 -34
@@ -1,33 +1,34 @@
1
- import { SoundSlot } from 'playcanvas';
1
+ import type { SoundSlot } from 'playcanvas';
2
2
 
3
3
  import { AssetElement } from '../asset';
4
4
  import { AsyncElement } from '../async-element';
5
- import { SoundComponentElement } from './sound-component';
6
5
  import { parseBool, parseNumber } from '../parse';
7
6
 
7
+ import { SoundComponentElement } from './sound-component';
8
+
8
9
  /**
9
10
  * The SoundSlotElement interface provides properties and methods for manipulating
10
11
  * `<pc-sound>` elements. The SoundSlotElement interface also inherits the properties and
11
12
  * methods of the {@link AsyncElement} interface.
12
13
  */
13
14
  class SoundSlotElement extends AsyncElement {
14
- private _asset: string = '';
15
+ private _asset = '';
15
16
 
16
- private _autoPlay: boolean = false;
17
+ private _autoPlay = false;
17
18
 
18
19
  private _duration: number | null = null;
19
20
 
20
- private _loop: boolean = false;
21
+ private _loop = false;
21
22
 
22
- private _name: string = '';
23
+ private _name = '';
23
24
 
24
- private _overlap: boolean = false;
25
+ private _overlap = false;
25
26
 
26
- private _pitch: number = 1;
27
+ private _pitch = 1;
27
28
 
28
- private _startTime: number = 0;
29
+ private _startTime = 0;
29
30
 
30
- private _volume: number = 1;
31
+ private _volume = 1;
31
32
 
32
33
  /**
33
34
  * The `<pc-sounds>` this slot was added to, captured at connect time.
@@ -38,20 +39,31 @@ class SoundSlotElement extends AsyncElement {
38
39
  */
39
40
  private _soundElement: SoundComponentElement | null = null;
40
41
 
42
+ /**
43
+ * Incremented on every connect and disconnect, and captured by connectedCallback on entry —
44
+ * a resume from an await abandons itself if the value has moved on, so a stale callback can
45
+ * neither act on a torn-down tree nor add its slot alongside a re-inserted element's own
46
+ * callback.
47
+ */
48
+ private _connectionGeneration = 0;
49
+
41
50
  /**
42
51
  * The sound slot.
43
52
  */
44
53
  soundSlot: SoundSlot | null = null;
45
54
 
46
55
  async connectedCallback() {
56
+ const generation = ++this._connectionGeneration;
57
+
47
58
  const soundElement = this.soundElement;
48
59
  await soundElement?.ready();
49
60
 
50
- // The element may have been removed, or its parent torn down, while we were waiting. A
51
- // <pc-app> disconnects before its children, so by the time we resume the component can
52
- // already be gone - see the matching guard in disconnectedCallback below.
61
+ // The element may have been removed (perhaps re-inserted, which runs a callback of its
62
+ // own), or its parent torn down, while we were waiting. A <pc-app> disconnects before
63
+ // its children, so by the time we resume the component can already be gone - see the
64
+ // matching guard in disconnectedCallback below.
53
65
  const component = soundElement?.component;
54
- if (!this.isConnected || !component) {
66
+ if (generation !== this._connectionGeneration || !component) {
55
67
  return;
56
68
  }
57
69
 
@@ -78,12 +90,16 @@ class SoundSlotElement extends AsyncElement {
78
90
  }
79
91
 
80
92
  disconnectedCallback() {
93
+ // Invalidate any connectedCallback still suspended on an await
94
+ this._connectionGeneration++;
95
+
81
96
  // Uses the cached parent rather than a fresh lookup, since parentElement is already null
82
97
  // by now. The component itself is null if the parent <pc-sound> (or the whole <pc-app>) is
83
98
  // being torn down — parents disconnect first and have already removed the component.
84
99
  this._soundElement?.component?.removeSlot(this._name);
85
100
  this._soundElement = null;
86
101
  this.soundSlot = null;
102
+ this._resetReady();
87
103
  }
88
104
 
89
105
  protected get soundElement(): SoundComponentElement | null {
@@ -310,10 +326,4 @@ class SoundSlotElement extends AsyncElement {
310
326
 
311
327
  customElements.define('pc-sound', SoundSlotElement);
312
328
 
313
- declare global {
314
- interface HTMLElementTagNameMap {
315
- 'pc-sound': SoundSlotElement;
316
- }
317
- }
318
-
319
329
  export { SoundSlotElement };
package/src/entity.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { AppBase, Entity, Vec3 } from 'playcanvas';
1
+ import type { AppBase } from 'playcanvas';
2
+ import { Entity, Vec3 } from 'playcanvas';
2
3
 
4
+ import type { AppElement } from './app';
3
5
  import { AsyncElement } from './async-element';
4
6
  import { parseBool, parseTags, parseVec3 } from './parse';
5
7
 
@@ -61,7 +63,7 @@ class EntityElement extends AsyncElement {
61
63
  /**
62
64
  * The pointer event listeners for the entity.
63
65
  */
64
- private _listeners: { [key: string]: EventListener[] } = {};
66
+ private _listeners: Record<string, EventListener[]> = {};
65
67
 
66
68
  /**
67
69
  * The event types for which an inline `onpointer*` attribute is currently present.
@@ -75,6 +77,12 @@ class EntityElement extends AsyncElement {
75
77
 
76
78
  private _entity: Entity | null = null;
77
79
 
80
+ /**
81
+ * The application element this entity is registered with, cached at creation time so the
82
+ * entity can be unregistered even once this element has left the DOM.
83
+ */
84
+ private _appElement: AppElement | null = null;
85
+
78
86
  /**
79
87
  * The PlayCanvas entity instance. `null` until the element is ready, and again once it has
80
88
  * been removed from the document — await {@link whenReady} or the element's `ready()`
@@ -85,7 +93,14 @@ class EntityElement extends AsyncElement {
85
93
  return this._entity;
86
94
  }
87
95
 
88
- createEntity(app: AppBase) {
96
+ /**
97
+ * Creates the backing entity. Called by the containing `<pc-app>` element during its boot
98
+ * sweep, and on connection for elements inserted while the application is already running.
99
+ *
100
+ * @param app - The application to create the entity in.
101
+ * @internal
102
+ */
103
+ _createEntity(app: AppBase) {
89
104
  // Guard against double creation. When a subtree is inserted at runtime (e.g. cloning a
90
105
  // `<template>`), an ancestor's connectedCallback eagerly creates descendant entities; the
91
106
  // descendants' own connectedCallbacks would otherwise create them a second time.
@@ -108,9 +123,44 @@ class EntityElement extends AsyncElement {
108
123
  if (this._tags.length > 0) {
109
124
  entity.tags.add(this._tags);
110
125
  }
126
+
127
+ // Register with the owning application, which joins engine nodes back to elements by
128
+ // identity (never by name), and hook the entity's destruction. The engine fires 'destroy'
129
+ // for every entity in a destroyed subtree, so the element learns of its entity's death no
130
+ // matter who causes it: this element, an ancestor, the whole application, or a user
131
+ // script calling entity.destroy().
132
+ this._appElement = this.closestApp;
133
+ this._appElement?._registerEntityElement(entity, this);
134
+ entity.once('destroy', this._onEntityDestroy, this);
111
135
  }
112
136
 
113
- buildHierarchy(app: AppBase) {
137
+ /**
138
+ * Handles the destruction of the backing entity. Resets the element so a later re-insertion
139
+ * starts clean: `_built` must be cleared alongside `_entity`, or _buildHierarchy would bail
140
+ * and a re-created entity would never be parented. Readiness is re-armed for the same
141
+ * reason — with the entity gone, a resolved ready promise would resume its awaiters against
142
+ * a null `entity`.
143
+ *
144
+ * @param entity - The entity that was destroyed.
145
+ */
146
+ private _onEntityDestroy(entity: Entity) {
147
+ this._appElement?._unregisterEntityElement(entity);
148
+ this._appElement = null;
149
+ this._entity = null;
150
+ this._built = false;
151
+ this._resetReady();
152
+ }
153
+
154
+ /**
155
+ * Parents the backing entity: under the entity of the nearest ancestor `<pc-entity>` when
156
+ * there is one, and under the application root otherwise. Called by the containing `<pc-app>`
157
+ * element once a sweep has created every entity, so a parent's existence never depends on
158
+ * document order.
159
+ *
160
+ * @param app - The application whose root adopts parentless entities.
161
+ * @internal
162
+ */
163
+ _buildHierarchy(app: AppBase) {
114
164
  if (!this.entity || this._built) return;
115
165
  this._built = true;
116
166
 
@@ -138,41 +188,29 @@ class EntityElement extends AsyncElement {
138
188
  }
139
189
 
140
190
  // If app is already running, create entity immediately
141
- if (closestApp.hierarchyReady) {
191
+ if (closestApp._hierarchyReady) {
142
192
  const app = closestApp.app!;
143
193
 
144
- this.createEntity(app);
145
- this.buildHierarchy(app);
194
+ this._createEntity(app);
195
+ this._buildHierarchy(app);
146
196
 
147
197
  // Handle any child entities that might exist
148
198
  const childEntities = this.querySelectorAll<EntityElement>('pc-entity');
149
199
  childEntities.forEach((child) => {
150
- child.createEntity(app);
200
+ child._createEntity(app);
151
201
  });
152
202
  childEntities.forEach((child) => {
153
- child.buildHierarchy(app);
203
+ child._buildHierarchy(app);
154
204
  });
155
205
  }
156
206
  }
157
207
 
158
208
  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
- }
209
+ // Destroying the entity destroys its whole subtree, and the engine fires 'destroy' for
210
+ // every entity in it - so _onEntityDestroy resets this element AND every descendant
211
+ // element before the descendants' own disconnectedCallbacks run. Their entities are null
212
+ // by then, making this call a no-op for them.
213
+ this._entity?.destroy();
176
214
  }
177
215
 
178
216
  /**
@@ -374,7 +412,7 @@ class EntityElement extends AsyncElement {
374
412
 
375
413
  removeEventListener(type: string, listener: EventListener, options?: boolean | EventListenerOptions) {
376
414
  if (this._listeners[type]) {
377
- this._listeners[type] = this._listeners[type].filter(l => l !== listener);
415
+ this._listeners[type] = this._listeners[type].filter((l) => l !== listener);
378
416
  }
379
417
  super.removeEventListener(type, listener, options);
380
418
  if (type.startsWith('pointer')) {
@@ -382,17 +420,20 @@ class EntityElement extends AsyncElement {
382
420
  }
383
421
  }
384
422
 
385
- hasListeners(type: string): boolean {
423
+ /**
424
+ * Whether the element has a listener for an event type, registered either with
425
+ * {@link addEventListener} or with the matching inline `onpointer*` attribute. Read by the
426
+ * containing `<pc-app>` element to gate pointer event synthesis.
427
+ *
428
+ * @param type - The event type.
429
+ * @returns Whether a listener is registered.
430
+ * @internal
431
+ */
432
+ _hasListeners(type: string): boolean {
386
433
  return Boolean(this._listeners[type]?.length) || this._inlineHandlerTypes.has(type);
387
434
  }
388
435
  }
389
436
 
390
437
  customElements.define('pc-entity', EntityElement);
391
438
 
392
- declare global {
393
- interface HTMLElementTagNameMap {
394
- 'pc-entity': EntityElement;
395
- }
396
- }
397
-
398
439
  export { EntityElement };
package/src/index.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * @module EngineWebComponents
8
8
  */
9
9
 
10
- /* eslint-disable import/order */
10
+ /* eslint-disable import-x/order */
11
11
 
12
12
  // Note that order matters here (e.g. pc-entity must be defined before components)
13
13
  import { AsyncElement, whenReady } from './async-element';
@@ -40,6 +40,50 @@ import { ModelElement } from './model';
40
40
  import { SceneElement } from './scene';
41
41
  import { SkyElement } from './sky';
42
42
 
43
+ import type {
44
+ ScriptAttributesChangeEvent,
45
+ ScriptEnableChangeEvent,
46
+ ScriptNameChangeEvent
47
+ } from './components/script-component';
48
+
49
+ declare global {
50
+ interface HTMLElementEventMap {
51
+ scriptattributeschange: ScriptAttributesChangeEvent;
52
+ scriptenablechange: ScriptEnableChangeEvent;
53
+ scriptnamechange: ScriptNameChangeEvent;
54
+ }
55
+
56
+ interface HTMLElementTagNameMap {
57
+ 'pc-app': AppElement;
58
+ 'pc-asset': AssetElement;
59
+ 'pc-button': ButtonComponentElement;
60
+ 'pc-camera': CameraComponentElement;
61
+ 'pc-collision': CollisionComponentElement;
62
+ 'pc-element': ElementComponentElement;
63
+ 'pc-entity': EntityElement;
64
+ 'pc-gsplat': GSplatComponentElement;
65
+ 'pc-layoutchild': LayoutChildComponentElement;
66
+ 'pc-layoutgroup': LayoutGroupComponentElement;
67
+ 'pc-light': LightComponentElement;
68
+ 'pc-listener': ListenerComponentElement;
69
+ 'pc-material': MaterialElement;
70
+ 'pc-model': ModelElement;
71
+ 'pc-module': ModuleElement;
72
+ 'pc-particles': ParticleSystemComponentElement;
73
+ 'pc-render': RenderComponentElement;
74
+ 'pc-rigidbody': RigidBodyComponentElement;
75
+ 'pc-scene': SceneElement;
76
+ 'pc-screen': ScreenComponentElement;
77
+ 'pc-script': ScriptElement;
78
+ 'pc-scripts': ScriptComponentElement;
79
+ 'pc-scrollbar': ScrollbarComponentElement;
80
+ 'pc-scrollview': ScrollViewComponentElement;
81
+ 'pc-sky': SkyElement;
82
+ 'pc-sound': SoundSlotElement;
83
+ 'pc-sounds': SoundComponentElement;
84
+ }
85
+ }
86
+
43
87
  export {
44
88
  AsyncElement,
45
89
  ModuleElement,
@@ -59,14 +59,14 @@ class LoadingBar {
59
59
  // aria-valuenow is set, which is what marks a progressbar indeterminate. jsdom has no Web
60
60
  // Animations API, so the guard degrades to a static bar there rather than crashing boot.
61
61
  if (typeof this._fill.animate === 'function') {
62
- this._sweep = this._fill.animate([
63
- { transform: 'scaleX(0.25) translateX(-100%)' },
64
- { transform: 'scaleX(0.25) translateX(500%)' }
65
- ], {
66
- duration: 1000,
67
- iterations: Infinity,
68
- easing: 'ease-in-out'
69
- });
62
+ this._sweep = this._fill.animate(
63
+ [{ transform: 'scaleX(0.25) translateX(-100%)' }, { transform: 'scaleX(0.25) translateX(500%)' }],
64
+ {
65
+ duration: 1000,
66
+ iterations: Infinity,
67
+ easing: 'ease-in-out'
68
+ }
69
+ );
70
70
  }
71
71
  }
72
72
 
package/src/material.ts CHANGED
@@ -21,17 +21,26 @@ import {
21
21
  SPECOCC_GLOSSDEPENDENT,
22
22
  SPECOCC_NONE,
23
23
  StandardMaterial,
24
- Vec2,
25
- type EventHandle,
26
- type Texture
24
+ Vec2
27
25
  } from 'playcanvas';
26
+ import type { EventHandle, Texture } from 'playcanvas';
28
27
 
29
- import { AppElement } from './app';
28
+ import type { AppElement } from './app';
30
29
  import { AssetElement } from './asset';
31
30
  import { parseBool, parseColor, parseEnum, parseNumber, parseVec2 } from './parse';
32
31
 
33
- type BlendType = 'none' | 'normal' | 'additive' | 'additive-alpha' | 'premultiplied' |
34
- 'multiplicative' | 'multiplicative-2x' | 'screen' | 'min' | 'max' | 'subtractive';
32
+ type BlendType =
33
+ | 'none'
34
+ | 'normal'
35
+ | 'additive'
36
+ | 'additive-alpha'
37
+ | 'premultiplied'
38
+ | 'multiplicative'
39
+ | 'multiplicative-2x'
40
+ | 'screen'
41
+ | 'min'
42
+ | 'max'
43
+ | 'subtractive';
35
44
 
36
45
  const blendTypes = new Map<BlendType, number>([
37
46
  ['none', BLEND_NONE],
@@ -100,8 +109,8 @@ const roughnessAliases = ['roughness', 'roughness-map'];
100
109
  * The texture slots a `pc-material` can populate. Each is backed by a `pc-asset` id rather than a
101
110
  * `Texture`, so the element can be authored before the asset has loaded.
102
111
  */
103
- type TextureSlot = 'aoMap' | 'diffuseMap' | 'emissiveMap' | 'glossMap' | 'heightMap' |
104
- 'metalnessMap' | 'normalMap' | 'opacityMap';
112
+ type TextureSlot =
113
+ 'aoMap' | 'diffuseMap' | 'emissiveMap' | 'glossMap' | 'heightMap' | 'metalnessMap' | 'normalMap' | 'opacityMap';
105
114
 
106
115
  /**
107
116
  * The MaterialElement interface provides properties and methods for manipulating
@@ -286,7 +295,7 @@ class MaterialElement extends HTMLElement {
286
295
 
287
296
  private _useLighting = true;
288
297
 
289
- // Diverges from the engine default of false - see the class docblock and createMaterial()
298
+ // Diverges from the engine default of false - see the class docblock and _createMaterial()
290
299
  private _useMetalness = true;
291
300
 
292
301
  private _useMetalnessSpecularColor = false;
@@ -306,10 +315,14 @@ class MaterialElement extends HTMLElement {
306
315
 
307
316
  private _glossConflictWarned = false;
308
317
 
318
+ /**
319
+ * The material. `null` until the containing application has created it — an element present
320
+ * at startup has its material once the application is ready.
321
+ */
309
322
  material: StandardMaterial | null = null;
310
323
 
311
324
  async connectedCallback() {
312
- const appElement = this.parentElement?.closest('pc-app') as AppElement | null ?? null;
325
+ const appElement = (this.parentElement?.closest('pc-app') as AppElement | null) ?? null;
313
326
 
314
327
  // Materials must be direct children of pc-app (matches the boot query ':scope > pc-material')
315
328
  if (!appElement || this.parentElement !== appElement) {
@@ -326,11 +339,18 @@ class MaterialElement extends HTMLElement {
326
339
  // elements inserted (or re-inserted) after the app is already running
327
340
  if (!this.material) {
328
341
  if (!appElement.app) return; // pc-app is re-connecting; its own boot will create this
329
- this.createMaterial();
342
+ this._createMaterial();
330
343
  }
331
344
  }
332
345
 
333
- createMaterial() {
346
+ /**
347
+ * Creates the material from the element's cached properties. Called by the containing
348
+ * `<pc-app>` element during its boot sweep, and on connection for elements inserted while
349
+ * the application is already running.
350
+ *
351
+ * @internal
352
+ */
353
+ _createMaterial() {
334
354
  const material = new StandardMaterial();
335
355
  this.material = material;
336
356
 
@@ -464,10 +484,10 @@ class MaterialElement extends HTMLElement {
464
484
  * warning latches and reports once per episode, clearing when the clash is resolved.
465
485
  */
466
486
  private _warnGlossConflict() {
467
- const quote = (names: string[]) => `'${names.join('\', \'')}'`;
487
+ const quote = (names: string[]) => `'${names.join("', '")}'`;
468
488
 
469
- const roughness = roughnessAliases.filter(name => this.hasAttribute(name));
470
- const gloss = glossConflicts.filter(name => this.hasAttribute(name));
489
+ const roughness = roughnessAliases.filter((name) => this.hasAttribute(name));
490
+ const gloss = glossConflicts.filter((name) => this.hasAttribute(name));
471
491
 
472
492
  if (roughness.length === 0 || gloss.length === 0) {
473
493
  this._glossConflictWarned = false;
@@ -477,8 +497,10 @@ class MaterialElement extends HTMLElement {
477
497
  if (this._glossConflictWarned) return;
478
498
  this._glossConflictWarned = true;
479
499
 
480
- console.warn(`pc-material '${this.id}' sets both ${quote(roughness)} and ${quote(gloss)} - ` +
481
- 'the roughness-* attributes invert gloss, so the two families contradict each other. Use one or the other.');
500
+ console.warn(
501
+ `pc-material '${this.id}' sets both ${quote(roughness)} and ${quote(gloss)} - ` +
502
+ 'the roughness-* attributes invert gloss, so the two families contradict each other. Use one or the other.'
503
+ );
482
504
  }
483
505
 
484
506
  /**
@@ -488,7 +510,7 @@ class MaterialElement extends HTMLElement {
488
510
  * @param id - The id of the `pc-asset`, or an empty string to clear the slot.
489
511
  * @param slot - The material property to write.
490
512
  */
491
- setMap(id: string, slot: TextureSlot) {
513
+ private _setMap(id: string, slot: TextureSlot) {
492
514
  // Drop any load still pending for this slot - its texture is no longer the one we want
493
515
  this._mapHandles.get(slot)?.off();
494
516
  this._mapHandles.delete(slot);
@@ -509,10 +531,13 @@ class MaterialElement extends HTMLElement {
509
531
  return;
510
532
  }
511
533
 
512
- this._mapHandles.set(slot, asset.once('load', () => {
513
- this._mapHandles.delete(slot);
514
- this._applyMap(slot, asset.resource as Texture);
515
- }));
534
+ this._mapHandles.set(
535
+ slot,
536
+ asset.once('load', () => {
537
+ this._mapHandles.delete(slot);
538
+ this._applyMap(slot, asset.resource as Texture);
539
+ })
540
+ );
516
541
  }
517
542
 
518
543
  /**
@@ -592,7 +617,7 @@ class MaterialElement extends HTMLElement {
592
617
  */
593
618
  set aoMap(value: string) {
594
619
  this._aoMap = value;
595
- this.setMap(value, 'aoMap');
620
+ this._setMap(value, 'aoMap');
596
621
  }
597
622
 
598
623
  /**
@@ -850,7 +875,7 @@ class MaterialElement extends HTMLElement {
850
875
  */
851
876
  set diffuseMap(value: string) {
852
877
  this._diffuseMap = value;
853
- this.setMap(value, 'diffuseMap');
878
+ this._setMap(value, 'diffuseMap');
854
879
  }
855
880
 
856
881
  /**
@@ -1007,7 +1032,7 @@ class MaterialElement extends HTMLElement {
1007
1032
  */
1008
1033
  set emissiveMap(value: string) {
1009
1034
  this._emissiveMap = value;
1010
- this.setMap(value, 'emissiveMap');
1035
+ this._setMap(value, 'emissiveMap');
1011
1036
  }
1012
1037
 
1013
1038
  /**
@@ -1205,7 +1230,7 @@ class MaterialElement extends HTMLElement {
1205
1230
  */
1206
1231
  set glossMap(value: string) {
1207
1232
  this._glossMap = value;
1208
- this.setMap(value, 'glossMap');
1233
+ this._setMap(value, 'glossMap');
1209
1234
  }
1210
1235
 
1211
1236
  /**
@@ -1322,7 +1347,7 @@ class MaterialElement extends HTMLElement {
1322
1347
  */
1323
1348
  set heightMap(value: string) {
1324
1349
  this._heightMap = value;
1325
- this.setMap(value, 'heightMap');
1350
+ this._setMap(value, 'heightMap');
1326
1351
  }
1327
1352
 
1328
1353
  /**
@@ -1479,7 +1504,7 @@ class MaterialElement extends HTMLElement {
1479
1504
  */
1480
1505
  set metalnessMap(value: string) {
1481
1506
  this._metalnessMap = value;
1482
- this.setMap(value, 'metalnessMap');
1507
+ this._setMap(value, 'metalnessMap');
1483
1508
  }
1484
1509
 
1485
1510
  /**
@@ -1596,7 +1621,7 @@ class MaterialElement extends HTMLElement {
1596
1621
  */
1597
1622
  set normalMap(value: string) {
1598
1623
  this._normalMap = value;
1599
- this.setMap(value, 'normalMap');
1624
+ this._setMap(value, 'normalMap');
1600
1625
  }
1601
1626
 
1602
1627
  /**
@@ -1694,7 +1719,7 @@ class MaterialElement extends HTMLElement {
1694
1719
  set occludeDirect(value: boolean) {
1695
1720
  this._occludeDirect = value;
1696
1721
  if (this.material) {
1697
- // @ts-ignore see createMaterial() - the engine mistypes occludeDirect as a number
1722
+ // @ts-ignore see _createMaterial() - the engine mistypes occludeDirect as a number
1698
1723
  this.material.occludeDirect = value;
1699
1724
  this._scheduleUpdate();
1700
1725
  }
@@ -1796,7 +1821,7 @@ class MaterialElement extends HTMLElement {
1796
1821
  */
1797
1822
  set opacityMap(value: string) {
1798
1823
  this._opacityMap = value;
1799
- this.setMap(value, 'opacityMap');
1824
+ this._setMap(value, 'opacityMap');
1800
1825
  }
1801
1826
 
1802
1827
  /**
@@ -2150,6 +2175,13 @@ class MaterialElement extends HTMLElement {
2150
2175
  return this._useTonemap;
2151
2176
  }
2152
2177
 
2178
+ /**
2179
+ * Returns the {@link StandardMaterial} created by the `<pc-material>` element with the given
2180
+ * `id`, or `undefined` if there is no such element or its material has not been created yet.
2181
+ *
2182
+ * @param id - The `id` of the `<pc-material>` element.
2183
+ * @returns The material, or `undefined`.
2184
+ */
2153
2185
  static get(id: string) {
2154
2186
  const materialElement = document.querySelector<MaterialElement>(`pc-material[id="${id}"]`);
2155
2187
  return materialElement?.material;
@@ -2508,10 +2540,4 @@ class MaterialElement extends HTMLElement {
2508
2540
 
2509
2541
  customElements.define('pc-material', MaterialElement);
2510
2542
 
2511
- declare global {
2512
- interface HTMLElementTagNameMap {
2513
- 'pc-material': MaterialElement;
2514
- }
2515
- }
2516
-
2517
2543
  export { MaterialElement };