@playcanvas/web-components 0.19.0 → 0.21.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.
Files changed (104) hide show
  1. package/dist/app.d.cts +5 -99
  2. package/dist/app.d.ts +5 -99
  3. package/dist/asset-binding.d.cts +1 -0
  4. package/dist/asset-binding.d.ts +1 -0
  5. package/dist/components/anim-clip.d.cts +8 -10
  6. package/dist/components/anim-clip.d.ts +8 -10
  7. package/dist/components/anim-component.d.cts +5 -4
  8. package/dist/components/anim-component.d.ts +5 -4
  9. package/dist/components/audio-listener-component.d.cts +5 -4
  10. package/dist/components/audio-listener-component.d.ts +5 -4
  11. package/dist/components/button-component.d.cts +14 -9
  12. package/dist/components/button-component.d.ts +14 -9
  13. package/dist/components/camera-component.d.cts +5 -4
  14. package/dist/components/camera-component.d.ts +5 -4
  15. package/dist/components/collision-component.d.cts +5 -4
  16. package/dist/components/collision-component.d.ts +5 -4
  17. package/dist/components/component.d.cts +2 -2
  18. package/dist/components/component.d.ts +2 -2
  19. package/dist/components/element-component.d.cts +5 -4
  20. package/dist/components/element-component.d.ts +5 -4
  21. package/dist/components/gsplat-component.d.cts +5 -4
  22. package/dist/components/gsplat-component.d.ts +5 -4
  23. package/dist/components/joint-component.d.cts +29 -14
  24. package/dist/components/joint-component.d.ts +29 -14
  25. package/dist/components/layout-child-component.d.cts +5 -4
  26. package/dist/components/layout-child-component.d.ts +5 -4
  27. package/dist/components/layout-group-component.d.cts +5 -4
  28. package/dist/components/layout-group-component.d.ts +5 -4
  29. package/dist/components/light-component.d.cts +5 -4
  30. package/dist/components/light-component.d.ts +5 -4
  31. package/dist/components/particle-system-component.d.cts +22 -5
  32. package/dist/components/particle-system-component.d.ts +22 -5
  33. package/dist/components/render-component.d.cts +5 -4
  34. package/dist/components/render-component.d.ts +5 -4
  35. package/dist/components/rigid-body-component.d.cts +5 -4
  36. package/dist/components/rigid-body-component.d.ts +5 -4
  37. package/dist/components/screen-component.d.cts +5 -4
  38. package/dist/components/screen-component.d.ts +5 -4
  39. package/dist/components/script-component.d.cts +9 -6
  40. package/dist/components/script-component.d.ts +9 -6
  41. package/dist/components/script-instance.d.cts +14 -6
  42. package/dist/components/script-instance.d.ts +14 -6
  43. package/dist/components/scroll-view-component.d.cts +29 -16
  44. package/dist/components/scroll-view-component.d.ts +29 -16
  45. package/dist/components/scrollbar-component.d.cts +11 -7
  46. package/dist/components/scrollbar-component.d.ts +11 -7
  47. package/dist/components/sound-component.d.cts +5 -4
  48. package/dist/components/sound-component.d.ts +5 -4
  49. package/dist/custom-elements.json +59 -59
  50. package/dist/entity-reference.d.cts +13 -0
  51. package/dist/entity-reference.d.ts +13 -0
  52. package/dist/material.d.cts +8 -6
  53. package/dist/material.d.ts +8 -6
  54. package/dist/model.d.cts +8 -9
  55. package/dist/model.d.ts +8 -9
  56. package/dist/parse.d.cts +0 -3
  57. package/dist/parse.d.ts +0 -3
  58. package/dist/pointer-controller.d.cts +1 -0
  59. package/dist/pointer-controller.d.ts +1 -0
  60. package/dist/pwc.cjs +1085 -633
  61. package/dist/pwc.cjs.map +1 -1
  62. package/dist/pwc.js +1085 -633
  63. package/dist/pwc.js.map +1 -1
  64. package/dist/pwc.min.js +1 -1
  65. package/dist/pwc.min.js.map +1 -1
  66. package/dist/pwc.min.mjs +1 -1
  67. package/dist/pwc.min.mjs.map +1 -1
  68. package/dist/pwc.mjs +1086 -634
  69. package/dist/pwc.mjs.map +1 -1
  70. package/dist/sky.d.cts +6 -7
  71. package/dist/sky.d.ts +6 -7
  72. package/dist/vscode.html-custom-data.json +10 -10
  73. package/dist/web-types.json +58 -58
  74. package/package.json +3 -3
  75. package/src/app.ts +16 -416
  76. package/src/asset-binding.ts +141 -0
  77. package/src/components/anim-clip.ts +22 -45
  78. package/src/components/anim-component.ts +10 -6
  79. package/src/components/audio-listener-component.ts +6 -5
  80. package/src/components/button-component.ts +25 -15
  81. package/src/components/camera-component.ts +6 -5
  82. package/src/components/collision-component.ts +6 -5
  83. package/src/components/component.ts +6 -4
  84. package/src/components/element-component.ts +6 -5
  85. package/src/components/gsplat-component.ts +6 -5
  86. package/src/components/joint-component.ts +36 -20
  87. package/src/components/layout-child-component.ts +6 -5
  88. package/src/components/layout-group-component.ts +6 -5
  89. package/src/components/light-component.ts +6 -5
  90. package/src/components/particle-system-component.ts +56 -30
  91. package/src/components/render-component.ts +6 -5
  92. package/src/components/rigid-body-component.ts +6 -5
  93. package/src/components/screen-component.ts +6 -5
  94. package/src/components/script-component.ts +28 -17
  95. package/src/components/script-instance.ts +14 -6
  96. package/src/components/scroll-view-component.ts +56 -34
  97. package/src/components/scrollbar-component.ts +20 -13
  98. package/src/components/sound-component.ts +6 -5
  99. package/src/entity-reference.ts +237 -0
  100. package/src/material.ts +25 -29
  101. package/src/model.ts +24 -48
  102. package/src/parse.ts +0 -35
  103. package/src/pointer-controller.ts +550 -0
  104. package/src/sky.ts +19 -30
@@ -1,7 +1,8 @@
1
- import type { Asset, EventHandle } from 'playcanvas';
1
+ import type { Asset } from 'playcanvas';
2
2
  import { AnimTrack } from 'playcanvas';
3
3
 
4
- import { AssetElement, useAsset } from '../asset';
4
+ import { AssetElement } from '../asset';
5
+ import { AssetBinding } from '../asset-binding';
5
6
  import { AsyncElement } from '../async-element';
6
7
  import { ModelElement } from '../model';
7
8
  import { parseBool, parseNumber } from '../parse';
@@ -39,6 +40,12 @@ class AnimClipElement extends AsyncElement {
39
40
 
40
41
  private _asset = '';
41
42
 
43
+ /**
44
+ * Watches the current source asset while it loads. Starting a new resolution or
45
+ * disconnecting cancels it, so a superseded source can never hand its track to the parent.
46
+ */
47
+ private _binding = new AssetBinding();
48
+
42
49
  /**
43
50
  * Incremented on every connect and disconnect, and captured by connectedCallback on entry —
44
51
  * a resume from an await abandons itself if the value has moved on, so a stale callback can
@@ -47,22 +54,14 @@ class AnimClipElement extends AsyncElement {
47
54
  */
48
55
  private _connectionGeneration = 0;
49
56
 
50
- private _errorHandle: EventHandle | null = null;
51
-
52
57
  /**
53
58
  * Incremented on every track resolution and on disconnect, and captured by a resolution when
54
- * it starts. A resolution that resumes from an await or an asset callback abandons itself if
55
- * the value has moved on, so a superseded resolution cannot hand a stale track to the parent.
59
+ * it starts. A resolution that resumes from an await abandons itself if the value has moved
60
+ * on, so a superseded resolution cannot hand a stale track to the parent. The asset
61
+ * subscription itself is guarded by the binding above.
56
62
  */
57
63
  private _loadGeneration = 0;
58
64
 
59
- /**
60
- * The pending asset subscriptions of the current resolution, if it is waiting for its asset.
61
- * Held so that whatever supersedes the resolution can detach the handlers from the asset,
62
- * rather than leave them registered until the asset settles (or forever, if it never does).
63
- */
64
- private _loadHandle: EventHandle | null = null;
65
-
66
65
  private _loop = true;
67
66
 
68
67
  private _name = '';
@@ -112,7 +111,7 @@ class AnimClipElement extends AsyncElement {
112
111
  // Invalidate any connectedCallback or track resolution still suspended on an await
113
112
  this._connectionGeneration++;
114
113
  this._loadGeneration++;
115
- this._detachLoadHandlers();
114
+ this._binding.cancel();
116
115
 
117
116
  // Uses the cached parent rather than a fresh lookup, since parentElement is already null
118
117
  // by now. The component itself is null if the whole <pc-app> is being torn down —
@@ -135,13 +134,6 @@ class AnimClipElement extends AsyncElement {
135
134
  return animElement;
136
135
  }
137
136
 
138
- private _detachLoadHandlers() {
139
- this._loadHandle?.off();
140
- this._loadHandle = null;
141
- this._errorHandle?.off();
142
- this._errorHandle = null;
143
- }
144
-
145
137
  /**
146
138
  * Reports a name-validation failure from the owning `<pc-anim>`, once per name value.
147
139
  *
@@ -169,35 +161,20 @@ class AnimClipElement extends AsyncElement {
169
161
  this._animElement = animElement;
170
162
 
171
163
  const generation = ++this._loadGeneration;
172
- this._detachLoadHandlers();
164
+ this._binding.cancel();
173
165
 
174
166
  if (this._asset) {
175
- const asset = useAsset(this._asset);
167
+ // Every path that moves _loadGeneration also rebinds or cancels the binding, so a
168
+ // delivery below is always current.
169
+ const asset = this._binding.bind(this._asset, {
170
+ load: (loaded) => this._extractTrack(loaded, `asset '${this._asset}'`),
171
+ error: () => {
172
+ this._warnSource(`pc-anim-clip '${this._name}' - asset '${this._asset}' failed to load - clip not assigned`);
173
+ }
174
+ });
176
175
  if (!asset) {
177
176
  this._warnSource(`pc-anim-clip '${this._name}' could not find asset '${this._asset}' - clip not assigned`);
178
- return;
179
- }
180
- if (asset.loaded) {
181
- this._extractTrack(asset, `asset '${this._asset}'`);
182
- return;
183
177
  }
184
- // Whichever of load/error fires first detaches the other. The generation is
185
- // re-checked even though a superseded handler is detached: the detach relies on how
186
- // the engine's event emitter treats removal, while the check holds on its own.
187
- this._loadHandle = asset.once('load', () => {
188
- this._detachLoadHandlers();
189
- if (generation !== this._loadGeneration) {
190
- return;
191
- }
192
- this._extractTrack(asset, `asset '${this._asset}'`);
193
- });
194
- this._errorHandle = asset.once('error', () => {
195
- this._detachLoadHandlers();
196
- if (generation !== this._loadGeneration) {
197
- return;
198
- }
199
- this._warnSource(`pc-anim-clip '${this._name}' - asset '${this._asset}' failed to load - clip not assigned`);
200
- });
201
178
  return;
202
179
  }
203
180
 
@@ -55,7 +55,7 @@ type PlaybackState = {
55
55
  *
56
56
  * @category Components
57
57
  */
58
- class AnimComponentElement extends ComponentElement {
58
+ class AnimComponentElement extends ComponentElement<AnimComponent> {
59
59
  /**
60
60
  * Whether playback starts automatically once a clip is assigned.
61
61
  */
@@ -168,6 +168,9 @@ class AnimComponentElement extends ComponentElement {
168
168
  */
169
169
  private _applyRootBone() {
170
170
  const component = this.component;
171
+ if (!component) {
172
+ return;
173
+ }
171
174
 
172
175
  // A non-null root this element did not assign came through the engine API. A fresh
173
176
  // component starts at null, which is always reclaimable.
@@ -252,7 +255,7 @@ class AnimComponentElement extends ComponentElement {
252
255
  * declared `clip` selection can apply before any asset has loaded.
253
256
  */
254
257
  private _assignClip(clip: AnimClipElement) {
255
- this.component.assignAnimation(clip.name, clip._track ?? AnimTrack.EMPTY, undefined, clip.speed, clip.loop);
258
+ this.component?.assignAnimation(clip.name, clip._track ?? AnimTrack.EMPTY, undefined, clip.speed, clip.loop);
256
259
  }
257
260
 
258
261
  /**
@@ -586,11 +589,12 @@ class AnimComponentElement extends ComponentElement {
586
589
  }
587
590
 
588
591
  /**
589
- * Gets the underlying PlayCanvas anim component.
590
- * @returns The anim component.
592
+ * Gets the underlying PlayCanvas anim component. `null` until the element is
593
+ * ready see {@link ComponentElement.component}.
594
+ * @returns The anim component, or `null`.
591
595
  */
592
- get component(): AnimComponent {
593
- return super.component as AnimComponent;
596
+ get component(): AnimComponent | null {
597
+ return super.component;
594
598
  }
595
599
 
596
600
  /**
@@ -16,18 +16,19 @@ import { ComponentElement } from './component';
16
16
  *
17
17
  * @category Components
18
18
  */
19
- class AudioListenerComponentElement extends ComponentElement {
19
+ class AudioListenerComponentElement extends ComponentElement<AudioListenerComponent> {
20
20
  /** @ignore */
21
21
  constructor() {
22
22
  super('audiolistener');
23
23
  }
24
24
 
25
25
  /**
26
- * Gets the underlying PlayCanvas audio listener component.
27
- * @returns The audio listener component.
26
+ * Gets the underlying PlayCanvas audio listener component. `null` until the element is
27
+ * ready see {@link ComponentElement.component}.
28
+ * @returns The audio listener component, or `null`.
28
29
  */
29
- get component(): AudioListenerComponent {
30
- return super.component as AudioListenerComponent;
30
+ get component(): AudioListenerComponent | null {
31
+ return super.component;
31
32
  }
32
33
  }
33
34
 
@@ -2,7 +2,8 @@ import type { ButtonComponent } from 'playcanvas';
2
2
  import { BUTTON_TRANSITION_MODE_SPRITE_CHANGE, BUTTON_TRANSITION_MODE_TINT, Color, Vec4 } from 'playcanvas';
3
3
 
4
4
  import { useAsset } from '../asset';
5
- import { getEntity, parseBool, parseColor, parseEnum, parseNumber, parseVec4 } from '../parse';
5
+ import { resolveEntity } from '../entity-reference';
6
+ import { parseBool, parseColor, parseEnum, parseNumber, parseVec4 } from '../parse';
6
7
 
7
8
  import { ComponentElement } from './component';
8
9
 
@@ -26,7 +27,7 @@ const transitionModes = new Map<'tint' | 'sprite', number>([
26
27
  *
27
28
  * @category Components
28
29
  */
29
- class ButtonComponentElement extends ComponentElement {
30
+ class ButtonComponentElement extends ComponentElement<ButtonComponent> {
30
31
  private _active = true;
31
32
 
32
33
  private _image = '';
@@ -76,7 +77,9 @@ class ButtonComponentElement extends ComponentElement {
76
77
 
77
78
  // The image entity defaults to the button's own entity (which carries the image element)
78
79
  // when no explicit reference is provided.
79
- const imageEntity = this._image ? getEntity(this._image) : this.closestEntity?.entity;
80
+ const imageEntity = this._image
81
+ ? resolveEntity(this._image, this, 'image', 'reference ignored')
82
+ : this.closestEntity?.entity;
80
83
  if (imageEntity) {
81
84
  data.imageEntity = imageEntity;
82
85
  }
@@ -100,11 +103,12 @@ class ButtonComponentElement extends ComponentElement {
100
103
  }
101
104
 
102
105
  /**
103
- * Gets the underlying PlayCanvas button component.
104
- * @returns The button component.
106
+ * Gets the underlying PlayCanvas button component. `null` until the element is
107
+ * ready see {@link ComponentElement.component}.
108
+ * @returns The button component, or `null`.
105
109
  */
106
- get component(): ButtonComponent {
107
- return super.component as ButtonComponent;
110
+ get component(): ButtonComponent | null {
111
+ return super.component;
108
112
  }
109
113
 
110
114
  /**
@@ -127,22 +131,28 @@ class ButtonComponentElement extends ComponentElement {
127
131
  }
128
132
 
129
133
  /**
130
- * Sets the reference (CSS selector, element id or entity name) to the `<pc-entity>` whose image
131
- * element is used for visual transitions. Defaults to the button's own entity — inside a
132
- * `<pc-model>`, that is the model's host entity, so supply an explicit reference to target a
133
- * UI entity instead.
134
+ * Sets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
135
+ * selector) to the entity whose image element is used for visual transitions. An exact name
136
+ * resolves against the nearest enclosing entity first, then outward, then the document.
137
+ * Defaults to the button's own entity — inside a `<pc-model>`, that is the model's host
138
+ * entity, so supply an explicit reference to target a UI entity instead. A non-empty
139
+ * reference that does not resolve warns and is ignored.
134
140
  * @param value - The image entity reference.
135
141
  */
136
142
  set image(value: string) {
137
143
  this._image = value;
138
- const entity = getEntity(value);
139
- if (this.component && entity) {
140
- this.component.imageEntity = entity;
144
+ if (this.component) {
145
+ const entity = resolveEntity(value, this, 'image', 'reference ignored');
146
+ if (entity) {
147
+ this.component.imageEntity = entity;
148
+ }
141
149
  }
142
150
  }
143
151
 
144
152
  /**
145
- * Gets the reference to the `<pc-entity>` whose image element is used for visual transitions.
153
+ * Gets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
154
+ * selector) to the entity whose image element is used for visual transitions, or empty for
155
+ * the button's own entity.
146
156
  * @returns The image entity reference.
147
157
  */
148
158
  get image() {
@@ -50,7 +50,7 @@ const tonemaps = new Map<'none' | 'linear' | 'filmic' | 'hejl' | 'aces' | 'aces2
50
50
  *
51
51
  * @category Components
52
52
  */
53
- class CameraComponentElement extends ComponentElement {
53
+ class CameraComponentElement extends ComponentElement<CameraComponent> {
54
54
  private _clearColor = new Color(0.75, 0.75, 0.75, 1);
55
55
 
56
56
  private _clearColorBuffer = true;
@@ -177,11 +177,12 @@ class CameraComponentElement extends ComponentElement {
177
177
  }
178
178
 
179
179
  /**
180
- * Gets the underlying PlayCanvas camera component.
181
- * @returns The camera component.
180
+ * Gets the underlying PlayCanvas camera component. `null` until the element is
181
+ * ready see {@link ComponentElement.component}.
182
+ * @returns The camera component, or `null`.
182
183
  */
183
- get component(): CameraComponent {
184
- return super.component as CameraComponent;
184
+ get component(): CameraComponent | null {
185
+ return super.component;
185
186
  }
186
187
 
187
188
  /**
@@ -25,7 +25,7 @@ import { ComponentElement } from './component';
25
25
  *
26
26
  * @category Components
27
27
  */
28
- class CollisionComponentElement extends ComponentElement {
28
+ class CollisionComponentElement extends ComponentElement<CollisionComponent> {
29
29
  private _angularOffset: Quat = new Quat();
30
30
 
31
31
  private _axis = 1;
@@ -91,11 +91,12 @@ class CollisionComponentElement extends ComponentElement {
91
91
  }
92
92
 
93
93
  /**
94
- * Gets the underlying PlayCanvas collision component.
95
- * @returns The collision component.
94
+ * Gets the underlying PlayCanvas collision component. `null` until the element is
95
+ * ready see {@link ComponentElement.component}.
96
+ * @returns The collision component, or `null`.
96
97
  */
97
- get component(): CollisionComponent {
98
- return super.component as CollisionComponent;
98
+ get component(): CollisionComponent | null {
99
+ return super.component;
99
100
  }
100
101
 
101
102
  set angularOffset(value: Quat) {
@@ -10,12 +10,12 @@ import { parseBool } from '../parse';
10
10
  *
11
11
  * @category Components
12
12
  */
13
- class ComponentElement extends AsyncElement {
13
+ class ComponentElement<T extends Component = Component> extends AsyncElement {
14
14
  private _componentName: string;
15
15
 
16
16
  private _enabled = true;
17
17
 
18
- private _component: Component | null = null;
18
+ private _component: T | null = null;
19
19
 
20
20
  private _appElement: AppElement | null = null;
21
21
 
@@ -97,7 +97,9 @@ class ComponentElement extends AsyncElement {
97
97
  return;
98
98
  }
99
99
 
100
- this._component = entity.addComponent(this._componentName, this.getInitialComponentData());
100
+ // The name passed by the subclass selects the engine system that creates its T - a
101
+ // pairing the type system cannot express, so it is asserted this once
102
+ this._component = entity.addComponent(this._componentName, this.getInitialComponentData()) as T | null;
101
103
  }
102
104
 
103
105
  private async _addComponent() {
@@ -219,7 +221,7 @@ class ComponentElement extends AsyncElement {
219
221
  * before accessing it.
220
222
  * @returns The component instance, or `null`.
221
223
  */
222
- get component(): Component | null {
224
+ get component(): T | null {
223
225
  return this._component;
224
226
  }
225
227
 
@@ -25,7 +25,7 @@ import { ComponentElement } from './component';
25
25
  *
26
26
  * @category Components
27
27
  */
28
- class ElementComponentElement extends ComponentElement {
28
+ class ElementComponentElement extends ComponentElement<ElementComponent> {
29
29
  private _anchor: Vec4 = new Vec4(0.5, 0.5, 0.5, 0.5);
30
30
 
31
31
  private _autoWidth = true;
@@ -158,11 +158,12 @@ class ElementComponentElement extends ComponentElement {
158
158
  }
159
159
 
160
160
  /**
161
- * Gets the underlying PlayCanvas element component.
162
- * @returns The element component.
161
+ * Gets the underlying PlayCanvas element component. `null` until the element is
162
+ * ready see {@link ComponentElement.component}.
163
+ * @returns The element component, or `null`.
163
164
  */
164
- get component(): ElementComponent {
165
- return super.component as ElementComponent;
165
+ get component(): ElementComponent | null {
166
+ return super.component;
166
167
  }
167
168
 
168
169
  /**
@@ -19,7 +19,7 @@ import { ComponentElement } from './component';
19
19
  *
20
20
  * @category Components
21
21
  */
22
- class GSplatComponentElement extends ComponentElement {
22
+ class GSplatComponentElement extends ComponentElement<GSplatComponent> {
23
23
  private _asset = '';
24
24
 
25
25
  private _castShadows = false;
@@ -49,11 +49,12 @@ class GSplatComponentElement extends ComponentElement {
49
49
  }
50
50
 
51
51
  /**
52
- * Gets the underlying PlayCanvas gsplat component.
53
- * @returns The gsplat component.
52
+ * Gets the underlying PlayCanvas gsplat component. `null` until the element is
53
+ * ready see {@link ComponentElement.component}.
54
+ * @returns The gsplat component, or `null`.
54
55
  */
55
- get component(): GSplatComponent {
56
- return super.component as GSplatComponent;
56
+ get component(): GSplatComponent | null {
57
+ return super.component;
57
58
  }
58
59
 
59
60
  /**
@@ -1,7 +1,8 @@
1
1
  import type { JointComponent } from 'playcanvas';
2
2
  import { Vec2, Vec3 } from 'playcanvas';
3
3
 
4
- import { getEntity, parseBool, parseEnum, parseNumber, parseVec2, parseVec3 } from '../parse';
4
+ import { resolveEntity } from '../entity-reference';
5
+ import { parseBool, parseEnum, parseNumber, parseVec2, parseVec3 } from '../parse';
5
6
 
6
7
  import { ComponentElement } from './component';
7
8
 
@@ -25,7 +26,12 @@ export type MotionMode = 'locked' | 'limited' | 'free';
25
26
  * primary axis: a hinge rotates about it, a slider translates along it and a ball joint twists
26
27
  * about it. The constrained bodies are referenced by `entity-a` and `entity-b`, both of which need
27
28
  * a rigid body component; leaving `entity-b` empty constrains `entity-a` to a fixed point in world
28
- * space. The underlying engine component is in alpha, so its API may change.
29
+ * space. A reference can name any entity-fronting element `<pc-entity>`, `<pc-model>` or
30
+ * `<pc-node>`, so a ragdoll can join a model's own skeleton nodes by name — and a name resolves
31
+ * against the nearest enclosing entity first, then outward through the entity hierarchy, then the
32
+ * document, while a `#` selector resolves document-wide. A `<template>` prefab with one
33
+ * entity-fronting root can therefore wire its joints by name and stay self-contained when cloned.
34
+ * The underlying engine component is in alpha, so its API may change.
29
35
  *
30
36
  * @elementSummary The `<pc-joint>` element constrains two rigid bodies to each other — a hinged
31
37
  * door, a swinging chain, a sliding drawer. Its entity's transform is the joint frame, and
@@ -40,7 +46,7 @@ export type MotionMode = 'locked' | 'limited' | 'free';
40
46
  *
41
47
  * @category Components
42
48
  */
43
- class JointComponentElement extends ComponentElement {
49
+ class JointComponentElement extends ComponentElement<JointComponent> {
44
50
  /**
45
51
  * The spring damping of the joint per angular axis.
46
52
  */
@@ -210,8 +216,8 @@ class JointComponentElement extends ComponentElement {
210
216
  breakImpulse: this._breakImpulse,
211
217
  enableCollision: this._enableCollision,
212
218
  enableLimits: this._enableLimits,
213
- entityA: getEntity(this._entityA),
214
- entityB: getEntity(this._entityB),
219
+ entityA: resolveEntity(this._entityA, this, 'entity-a', 'constraint not created'),
220
+ entityB: resolveEntity(this._entityB, this, 'entity-b', 'constraint not created'),
215
221
  limits: this._limits,
216
222
  linearDamping: this._linearDamping,
217
223
  linearEquilibrium: this._linearEquilibrium,
@@ -249,11 +255,12 @@ class JointComponentElement extends ComponentElement {
249
255
  }
250
256
 
251
257
  /**
252
- * Gets the underlying PlayCanvas joint component.
253
- * @returns The joint component.
258
+ * Gets the underlying PlayCanvas joint component. `null` until the element is
259
+ * ready see {@link ComponentElement.component}.
260
+ * @returns The joint component, or `null`.
254
261
  */
255
- get component(): JointComponent {
256
- return super.component as JointComponent;
262
+ get component(): JointComponent | null {
263
+ return super.component;
257
264
  }
258
265
 
259
266
  /**
@@ -495,20 +502,24 @@ class JointComponentElement extends ComponentElement {
495
502
  }
496
503
 
497
504
  /**
498
- * Sets the reference (CSS selector, element id or entity name) to the `<pc-entity>` providing
499
- * the first constrained body. The reference resolves when it is set, so an entity created
500
- * later is picked up by setting the attribute again.
505
+ * Sets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
506
+ * selector) to the element providing the first constrained body. An exact name resolves
507
+ * against the nearest enclosing entity first, then outward, then the document. The reference
508
+ * resolves when it is set, so an entity created later is picked up by setting the attribute
509
+ * again. A non-empty reference that does not resolve warns, naming which of the two causes it
510
+ * hit.
501
511
  * @param value - The first body's entity reference.
502
512
  */
503
513
  set entityA(value: string) {
504
514
  this._entityA = value;
505
515
  if (this.component) {
506
- this.component.entityA = getEntity(value);
516
+ this.component.entityA = resolveEntity(value, this, 'entity-a', 'constraint not created');
507
517
  }
508
518
  }
509
519
 
510
520
  /**
511
- * Gets the reference to the `<pc-entity>` providing the first constrained body.
521
+ * Gets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
522
+ * selector) to the element providing the first constrained body.
512
523
  * @returns The first body's entity reference.
513
524
  */
514
525
  get entityA() {
@@ -516,21 +527,26 @@ class JointComponentElement extends ComponentElement {
516
527
  }
517
528
 
518
529
  /**
519
- * Sets the reference (CSS selector, element id or entity name) to the `<pc-entity>` providing
520
- * the second constrained body, or empty to constrain the first body to a fixed point in world
521
- * space. The reference resolves when it is set, so an entity created later is picked up by
522
- * setting the attribute again.
530
+ * Sets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
531
+ * selector) to the element providing the second constrained body, or empty to constrain the
532
+ * first body to a fixed point in world space. An exact name resolves against the nearest
533
+ * enclosing entity first, then outward, then the document. The reference resolves when it is
534
+ * set, so an entity created later is picked up by setting the attribute again. A non-empty
535
+ * reference that does not resolve warns; an empty one is the documented world-space case and
536
+ * stays silent.
523
537
  * @param value - The second body's entity reference.
524
538
  */
525
539
  set entityB(value: string) {
526
540
  this._entityB = value;
527
541
  if (this.component) {
528
- this.component.entityB = getEntity(value);
542
+ this.component.entityB = resolveEntity(value, this, 'entity-b', 'constraint not created');
529
543
  }
530
544
  }
531
545
 
532
546
  /**
533
- * Gets the reference to the `<pc-entity>` providing the second constrained body.
547
+ * Gets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
548
+ * selector) to the element providing the second constrained body, or empty for the
549
+ * world-space case.
534
550
  * @returns The second body's entity reference.
535
551
  */
536
552
  get entityB() {
@@ -18,7 +18,7 @@ import { ComponentElement } from './component';
18
18
  *
19
19
  * @category Components
20
20
  */
21
- class LayoutChildComponentElement extends ComponentElement {
21
+ class LayoutChildComponentElement extends ComponentElement<LayoutChildComponent> {
22
22
  private _minWidth = 0;
23
23
 
24
24
  private _minHeight = 0;
@@ -51,11 +51,12 @@ class LayoutChildComponentElement extends ComponentElement {
51
51
  }
52
52
 
53
53
  /**
54
- * Gets the underlying PlayCanvas layout child component.
55
- * @returns The layout child component.
54
+ * Gets the underlying PlayCanvas layout child component. `null` until the element is
55
+ * ready see {@link ComponentElement.component}.
56
+ * @returns The layout child component, or `null`.
56
57
  */
57
- get component(): LayoutChildComponent {
58
- return super.component as LayoutChildComponent;
58
+ get component(): LayoutChildComponent | null {
59
+ return super.component;
59
60
  }
60
61
 
61
62
  /**
@@ -40,7 +40,7 @@ const fittings = new Map<'none' | 'stretch' | 'shrink' | 'both', number>([
40
40
  *
41
41
  * @category Components
42
42
  */
43
- class LayoutGroupComponentElement extends ComponentElement {
43
+ class LayoutGroupComponentElement extends ComponentElement<LayoutGroupComponent> {
44
44
  private _orientation: 'horizontal' | 'vertical' = 'horizontal';
45
45
 
46
46
  private _reverseX = false;
@@ -79,11 +79,12 @@ class LayoutGroupComponentElement extends ComponentElement {
79
79
  }
80
80
 
81
81
  /**
82
- * Gets the underlying PlayCanvas layout group component.
83
- * @returns The layout group component.
82
+ * Gets the underlying PlayCanvas layout group component. `null` until the element is
83
+ * ready see {@link ComponentElement.component}.
84
+ * @returns The layout group component, or `null`.
84
85
  */
85
- get component(): LayoutGroupComponent {
86
- return super.component as LayoutGroupComponent;
86
+ get component(): LayoutGroupComponent | null {
87
+ return super.component;
87
88
  }
88
89
 
89
90
  /**
@@ -45,7 +45,7 @@ const shadowTypes = new Map<
45
45
  *
46
46
  * @category Components
47
47
  */
48
- class LightComponentElement extends ComponentElement {
48
+ class LightComponentElement extends ComponentElement<LightComponent> {
49
49
  private _cascadeBlend = 0;
50
50
 
51
51
  private _cascadeDistribution = 0.5;
@@ -132,11 +132,12 @@ class LightComponentElement extends ComponentElement {
132
132
  }
133
133
 
134
134
  /**
135
- * Gets the underlying PlayCanvas light component.
136
- * @returns The light component.
135
+ * Gets the underlying PlayCanvas light component. `null` until the element is
136
+ * ready see {@link ComponentElement.component}.
137
+ * @returns The light component, or `null`.
137
138
  */
138
- get component(): LightComponent {
139
- return super.component as LightComponent;
139
+ get component(): LightComponent | null {
140
+ return super.component;
140
141
  }
141
142
 
142
143
  /**