@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,6 +1,7 @@
1
- import type { ParticleSystemComponent } from 'playcanvas';
1
+ import type { Asset, ParticleSystemComponent } from 'playcanvas';
2
2
 
3
3
  import { useAsset } from '../asset';
4
+ import { AssetBinding } from '../asset-binding';
4
5
 
5
6
  import { ComponentElement } from './component';
6
7
 
@@ -18,9 +19,17 @@ import { ComponentElement } from './component';
18
19
  *
19
20
  * @category Components
20
21
  */
21
- class ParticleSystemComponentElement extends ComponentElement {
22
+ class ParticleSystemComponentElement extends ComponentElement<ParticleSystemComponent> {
22
23
  private _asset = '';
23
24
 
25
+ /**
26
+ * The subscription to the current config asset while its load is in flight. Rebinding
27
+ * supersedes it and disconnect cancels it, so a superseded config — an earlier asset that
28
+ * finishes loading after its replacement, or a callback left behind by a previous
29
+ * connection — can never configure the component.
30
+ */
31
+ private _binding = new AssetBinding();
32
+
24
33
  /** @ignore */
25
34
  constructor() {
26
35
  super('particlesystem');
@@ -28,28 +37,54 @@ class ParticleSystemComponentElement extends ComponentElement {
28
37
 
29
38
  protected getInitialComponentData() {
30
39
  const asset = useAsset(this._asset);
31
- // A lazy config has no resource yet - _loadAsset applies it once the load completes
40
+ // A lazy config has no resource yet - the config binding applies it once the load
41
+ // completes
32
42
  if (!asset || !asset.resource) {
33
43
  return {};
34
44
  }
35
45
 
36
- if ((asset.resource as any).colorMapAsset) {
37
- const id = (asset.resource as any).colorMapAsset;
38
- const colorMapAsset = useAsset(id)?.id;
39
- if (colorMapAsset) {
40
- (asset.resource as any).colorMapAsset = colorMapAsset;
41
- }
46
+ this._resolveColorMap(asset.resource);
47
+ return asset.resource;
48
+ }
49
+
50
+ protected initComponent() {
51
+ // A loaded config already arrived through getInitialComponentData - the binding is only
52
+ // needed for a load still in flight. Resolution here also starts a lazy config's load.
53
+ const asset = useAsset(this._asset);
54
+ if (asset && !asset.loaded) {
55
+ this._bindConfig();
42
56
  }
57
+ }
43
58
 
44
- return asset.resource;
59
+ disconnectedCallback() {
60
+ // The binding dies with the connection, so a config that finishes loading later cannot
61
+ // configure the component a reconnection creates - that connection binds afresh.
62
+ this._binding.cancel();
63
+ super.disconnectedCallback();
45
64
  }
46
65
 
47
66
  /**
48
- * Gets the underlying PlayCanvas particle system component.
49
- * @returns The particle system component.
67
+ * Gets the underlying PlayCanvas particle system component. `null` until the element is
68
+ * ready see {@link ComponentElement.component}.
69
+ * @returns The particle system component, or `null`.
50
70
  */
51
- get component(): ParticleSystemComponent {
52
- return super.component as ParticleSystemComponent;
71
+ get component(): ParticleSystemComponent | null {
72
+ return super.component;
73
+ }
74
+
75
+ /**
76
+ * Rewrites the config's `colorMapAsset` from the `pc-asset` id it is authored with to the
77
+ * engine asset id the component resolves, starting the texture's load if it is lazy. The
78
+ * rewrite is in place, so a config applied again — a host cycle, a reconnection — is already
79
+ * resolved and passes through unchanged.
80
+ */
81
+ private _resolveColorMap(resource: any) {
82
+ if (resource.colorMapAsset) {
83
+ const colorMapAsset = useAsset(resource.colorMapAsset)?.id;
84
+ if (colorMapAsset) {
85
+ resource.colorMapAsset = colorMapAsset;
86
+ }
87
+ }
53
88
  }
54
89
 
55
90
  private applyConfig(resource: any) {
@@ -57,6 +92,8 @@ class ParticleSystemComponentElement extends ComponentElement {
57
92
  return;
58
93
  }
59
94
 
95
+ this._resolveColorMap(resource);
96
+
60
97
  // Set all the config properties on the component
61
98
  for (const key in resource) {
62
99
  if (Object.hasOwn(resource, key)) {
@@ -65,21 +102,10 @@ class ParticleSystemComponentElement extends ComponentElement {
65
102
  }
66
103
  }
67
104
 
68
- private async _loadAsset() {
69
- await this.closestApp?.ready();
70
-
71
- const asset = useAsset(this._asset);
72
- if (!asset) {
73
- return;
74
- }
75
-
76
- if (asset.loaded) {
77
- this.applyConfig(asset.resource);
78
- } else {
79
- asset.once('load', () => {
80
- this.applyConfig(asset.resource);
81
- });
82
- }
105
+ private _bindConfig() {
106
+ this._binding.bind(this._asset, {
107
+ load: (asset: Asset) => this.applyConfig(asset.resource)
108
+ });
83
109
  }
84
110
 
85
111
  /**
@@ -89,7 +115,7 @@ class ParticleSystemComponentElement extends ComponentElement {
89
115
  set asset(value: string) {
90
116
  this._asset = value;
91
117
  if (this.isConnected) {
92
- this._loadAsset();
118
+ this._bindConfig();
93
119
  }
94
120
  }
95
121
 
@@ -24,7 +24,7 @@ import { ComponentElement } from './component';
24
24
  *
25
25
  * @category Components
26
26
  */
27
- class RenderComponentElement extends ComponentElement {
27
+ class RenderComponentElement extends ComponentElement<RenderComponent> {
28
28
  private _castShadows = true;
29
29
 
30
30
  private _material = '';
@@ -48,11 +48,12 @@ class RenderComponentElement extends ComponentElement {
48
48
  }
49
49
 
50
50
  /**
51
- * Gets the underlying PlayCanvas render component.
52
- * @returns The render component.
51
+ * Gets the underlying PlayCanvas render component. `null` until the element is
52
+ * ready see {@link ComponentElement.component}.
53
+ * @returns The render component, or `null`.
53
54
  */
54
- get component(): RenderComponent {
55
- return super.component as RenderComponent;
55
+ get component(): RenderComponent | null {
56
+ return super.component;
56
57
  }
57
58
 
58
59
  /**
@@ -20,7 +20,7 @@ import { ComponentElement } from './component';
20
20
  *
21
21
  * @category Components
22
22
  */
23
- class RigidBodyComponentElement extends ComponentElement {
23
+ class RigidBodyComponentElement extends ComponentElement<RigidBodyComponent> {
24
24
  /**
25
25
  * The angular damping of the rigidbody.
26
26
  */
@@ -86,11 +86,12 @@ class RigidBodyComponentElement extends ComponentElement {
86
86
  }
87
87
 
88
88
  /**
89
- * Gets the underlying PlayCanvas rigidbody component.
90
- * @returns The rigidbody component.
89
+ * Gets the underlying PlayCanvas rigidbody component. `null` until the element is
90
+ * ready see {@link ComponentElement.component}.
91
+ * @returns The rigidbody component, or `null`.
91
92
  */
92
- get component(): RigidBodyComponent {
93
- return super.component as RigidBodyComponent;
93
+ get component(): RigidBodyComponent | null {
94
+ return super.component;
94
95
  }
95
96
 
96
97
  set angularDamping(value: number) {
@@ -28,7 +28,7 @@ const scaleModes = new Map<'none' | 'blend', string>([
28
28
  *
29
29
  * @category Components
30
30
  */
31
- class ScreenComponentElement extends ComponentElement {
31
+ class ScreenComponentElement extends ComponentElement<ScreenComponent> {
32
32
  private _screenSpace = false;
33
33
 
34
34
  private _resolution: Vec2 = new Vec2(640, 320);
@@ -58,11 +58,12 @@ class ScreenComponentElement extends ComponentElement {
58
58
  }
59
59
 
60
60
  /**
61
- * Gets the underlying PlayCanvas screen component.
62
- * @returns The screen component.
61
+ * Gets the underlying PlayCanvas screen component. `null` until the element is
62
+ * ready see {@link ComponentElement.component}.
63
+ * @returns The screen component, or `null`.
63
64
  */
64
- get component(): ScreenComponent {
65
- return super.component as ScreenComponent;
65
+ get component(): ScreenComponent | null {
66
+ return super.component;
66
67
  }
67
68
 
68
69
  set priority(value: number) {
@@ -2,8 +2,8 @@ import type { ScriptComponent, Script } from 'playcanvas';
2
2
  import { Color, Quat, Vec2, Vec3, Vec4 } from 'playcanvas';
3
3
 
4
4
  import { useAsset } from '../asset';
5
+ import { findEntityElement, getEntity, idHint, unresolvedCause } from '../entity-reference';
5
6
  import {
6
- getEntity,
7
7
  parseBool,
8
8
  parseColor,
9
9
  parseComponents,
@@ -107,10 +107,11 @@ const camelToKebab = (name: string): string => {
107
107
 
108
108
  /**
109
109
  * A conversion applied to a script attribute value carrying an explicit type prefix. Receives the
110
- * text after the prefix plus the raw value, and returns the raw value (having warned) when it
111
- * cannot resolve or parse it callers rely on that identity to tell failure from success.
110
+ * text after the prefix, the raw value, and the element the value is declared under — which
111
+ * scopes entity references and returns the raw value (having warned) when it cannot resolve or
112
+ * parse it — callers rely on that identity to tell failure from success.
112
113
  */
113
- type Conversion = (rest: string, raw: string) => any;
114
+ type Conversion = (rest: string, raw: string, from: Element) => any;
114
115
 
115
116
  /**
116
117
  * Resolves an `asset:` prefix to the Asset created by the `pc-asset` element with that id.
@@ -128,18 +129,25 @@ const assetConversion: Conversion = (rest, raw) => {
128
129
  };
129
130
 
130
131
  /**
131
- * Resolves an `entity:` prefix to the Entity backing a `pc-entity` element. The reference can be a
132
- * CSS selector, an element id or an entity name.
132
+ * Resolves an `entity:` prefix to the Entity backing a `pc-entity`, `pc-model` or `pc-node`
133
+ * element. The reference is a name resolved against the nearest enclosing entity first, then
134
+ * outward, then the document — or a document-wide `#` selector. The failure warning names which
135
+ * of the three causes ({@link unresolvedCause}) it hit.
133
136
  * @param rest - The entity reference.
134
137
  * @param raw - The raw value, returned unchanged when the reference does not resolve.
138
+ * @param from - The element the value is declared under, which scopes the reference.
135
139
  * @returns The entity, or `raw`.
136
140
  */
137
- const entityConversion: Conversion = (rest, raw) => {
138
- const entity = getEntity(rest);
141
+ const entityConversion: Conversion = (rest, raw, from) => {
142
+ const entity = getEntity(rest, from);
139
143
  if (entity) {
140
144
  return entity;
141
145
  }
142
- console.warn(`Unable to resolve '${raw}' in script attributes - no pc-entity found matching '${rest}'.`);
146
+ const element = findEntityElement(rest, from);
147
+ const hint = element ? '' : idHint(rest, 'entity:');
148
+ console.warn(
149
+ `Unable to resolve '${raw}' in script attributes - ${unresolvedCause(element)}.${hint ? ` ${hint}` : ''}`
150
+ );
143
151
  return raw;
144
152
  };
145
153
 
@@ -255,7 +263,7 @@ export type ScriptNameChangeEvent = {
255
263
  *
256
264
  * @category Components
257
265
  */
258
- class ScriptComponentElement extends ComponentElement {
266
+ class ScriptComponentElement extends ComponentElement<ScriptComponent> {
259
267
  private observer: MutationObserver;
260
268
 
261
269
  /** @ignore */
@@ -298,8 +306,10 @@ class ScriptComponentElement extends ComponentElement {
298
306
  /**
299
307
  * Recursively converts raw attribute data into proper PlayCanvas types. Supported conversions:
300
308
  * - "asset:id" → the Asset created by the `pc-asset` element with that id
301
- * - "entity:ref" → the Entity backing a `pc-entity` element. The reference can be a CSS
302
- * selector, an element id or an entity name.
309
+ * - "entity:ref" → the Entity backing a `pc-entity`, `pc-model` or `pc-node` element. The
310
+ * reference is a name, resolved against this element's nearest enclosing entity first,
311
+ * then outward, then the document — or a document-wide `#` selector (`entity:#id`). A bare
312
+ * value is always a name, never an id.
303
313
  * - "vec2:1 2" → new Vec2(1, 2)
304
314
  * - "vec3:1 2 3" → new Vec3(1, 2, 3)
305
315
  * - "vec4:1 2 3 4" → new Vec4(1, 2, 3, 4)
@@ -313,7 +323,7 @@ class ScriptComponentElement extends ComponentElement {
313
323
  private convertAttributes(item: any): any {
314
324
  if (typeof item === 'string') {
315
325
  const match = matchConversion(item);
316
- return match ? match.convert(match.rest, item) : item;
326
+ return match ? match.convert(match.rest, item, this) : item;
317
327
  }
318
328
 
319
329
  if (Array.isArray(item)) {
@@ -730,11 +740,12 @@ class ScriptComponentElement extends ComponentElement {
730
740
  }
731
741
 
732
742
  /**
733
- * Gets the underlying PlayCanvas script component.
734
- * @returns The script component.
743
+ * Gets the underlying PlayCanvas script component. `null` until the element is
744
+ * ready see {@link ComponentElement.component}.
745
+ * @returns The script component, or `null`.
735
746
  */
736
- get component(): ScriptComponent {
737
- return super.component as ScriptComponent;
747
+ get component(): ScriptComponent | null {
748
+ return super.component;
738
749
  }
739
750
  }
740
751
 
@@ -15,7 +15,9 @@ import { parseBool } from '../parse';
15
15
  * Values are parsed according to the type of the attribute's current value — initially the
16
16
  * script's declared default (numbers, booleans, strings, Vec2/3/4, Color, Quat as Euler
17
17
  * angles) — and the `asset:`/`entity:`/`vec2:`/`vec3:`/`vec4:`/`color:` prefixes may be used
18
- * to be explicit.
18
+ * to be explicit. An `entity:` reference is an entity name — resolved against the nearest
19
+ * enclosing entity first, then outward, then the document — or a document-wide `#` selector
20
+ * (`entity:#id`); a bare value is always a name, never an element id.
19
21
  * - **The `attributes` JSON attribute**: an object supporting nested structures and attribute
20
22
  * names that collide with reserved HTML attribute names (e.g. `title`).
21
23
  *
@@ -31,7 +33,8 @@ import { parseBool } from '../parse';
31
33
  *
32
34
  * @elementSummary The `<pc-script-instance>` element attaches one script class, named by `name`, to
33
35
  * the entity of its parent `<pc-script>`. Its other attributes set script attributes of the same
34
- * name, and `attributes` takes a JSON object instead. Must be a direct child of `<pc-script>`.
36
+ * name, and `attributes` takes a JSON object instead. An `entity:` value is an entity name —
37
+ * write `entity:#id` for an element id. Must be a direct child of `<pc-script>`.
35
38
  *
36
39
  * @fires {CustomEvent} scriptattributeschange - Fired when the script's attributes change. The
37
40
  * `detail` carries the new `attributes` object. Bubbles.
@@ -54,9 +57,11 @@ class ScriptInstanceElement extends AsyncElement {
54
57
  /**
55
58
  * Sets the attributes of the script as an object. Values are converted with the same rules
56
59
  * as the `attributes` attribute: `asset:`/`entity:` references and `vec2:`/`vec3:`/`vec4:`/
57
- * `color:` prefixed strings are resolved, and a plain numeric array is converted to the
58
- * type of the attribute it targets when that attribute currently holds a Vec2, Vec3, Vec4
59
- * or Color.
60
+ * `color:` prefixed strings are resolved (an entity name against the nearest enclosing
61
+ * entity first, then outward, then the document or a document-wide `#` selector; a bare
62
+ * value is always a name, never an element id), and a plain numeric array is converted to
63
+ * the type of the attribute it targets when that attribute currently holds a Vec2, Vec3,
64
+ * Vec4 or Color.
60
65
  * @param value - The attributes of the script.
61
66
  */
62
67
  set scriptAttributes(value: Record<string, any>) {
@@ -70,7 +75,10 @@ class ScriptInstanceElement extends AsyncElement {
70
75
  }
71
76
 
72
77
  /**
73
- * Gets the attributes of the script.
78
+ * Gets the attributes of the script as an object whose `asset:`, `entity:`, `vec2:`, `vec3:`,
79
+ * `vec4:` and `color:` prefixed values are resolved when applied — an `entity:` value being
80
+ * an entity name (nearest enclosing entity first, then outward, then the document) or a
81
+ * document-wide `#` selector (`entity:#id`), never a bare element id.
74
82
  * @returns The attributes of the script.
75
83
  */
76
84
  get scriptAttributes(): Record<string, any> {
@@ -8,7 +8,8 @@ import {
8
8
  Vec2
9
9
  } from 'playcanvas';
10
10
 
11
- import { getEntity, parseBool, parseEnum, parseNumber, parseVec2 } from '../parse';
11
+ import { resolveEntity } from '../entity-reference';
12
+ import { parseBool, parseEnum, parseNumber, parseVec2 } from '../parse';
12
13
 
13
14
  import { ComponentElement } from './component';
14
15
 
@@ -37,7 +38,7 @@ const visibilities = new Map<'always' | 'when-required', number>([
37
38
  *
38
39
  * @category Components
39
40
  */
40
- class ScrollViewComponentElement extends ComponentElement {
41
+ class ScrollViewComponentElement extends ComponentElement<ScrollViewComponent> {
41
42
  private _horizontal = true;
42
43
 
43
44
  private _vertical = true;
@@ -82,22 +83,22 @@ class ScrollViewComponentElement extends ComponentElement {
82
83
  verticalScrollbarVisibility: visibilities.get(this._verticalScrollbarVisibility)
83
84
  };
84
85
 
85
- const viewport = getEntity(this._viewport);
86
+ const viewport = resolveEntity(this._viewport, this, 'viewport', 'reference ignored');
86
87
  if (viewport) {
87
88
  data.viewportEntity = viewport;
88
89
  }
89
90
 
90
- const content = getEntity(this._content);
91
+ const content = resolveEntity(this._content, this, 'content', 'reference ignored');
91
92
  if (content) {
92
93
  data.contentEntity = content;
93
94
  }
94
95
 
95
- const horizontalScrollbar = getEntity(this._horizontalScrollbar);
96
+ const horizontalScrollbar = resolveEntity(this._horizontalScrollbar, this, 'horizontal-scrollbar', 'reference ignored');
96
97
  if (horizontalScrollbar) {
97
98
  data.horizontalScrollbarEntity = horizontalScrollbar;
98
99
  }
99
100
 
100
- const verticalScrollbar = getEntity(this._verticalScrollbar);
101
+ const verticalScrollbar = resolveEntity(this._verticalScrollbar, this, 'vertical-scrollbar', 'reference ignored');
101
102
  if (verticalScrollbar) {
102
103
  data.verticalScrollbarEntity = verticalScrollbar;
103
104
  }
@@ -106,11 +107,12 @@ class ScrollViewComponentElement extends ComponentElement {
106
107
  }
107
108
 
108
109
  /**
109
- * Gets the underlying PlayCanvas scroll view component.
110
- * @returns The scroll view component.
110
+ * Gets the underlying PlayCanvas scroll view component. `null` until the element is
111
+ * ready see {@link ComponentElement.component}.
112
+ * @returns The scroll view component, or `null`.
111
113
  */
112
- get component(): ScrollViewComponent {
113
- return super.component as ScrollViewComponent;
114
+ get component(): ScrollViewComponent | null {
115
+ return super.component;
114
116
  }
115
117
 
116
118
  /**
@@ -294,20 +296,25 @@ class ScrollViewComponentElement extends ComponentElement {
294
296
  }
295
297
 
296
298
  /**
297
- * Sets the reference (CSS selector, element id or entity name) to the `<pc-entity>` used as the
298
- * viewport, which clips the content to the scroll view's bounds.
299
+ * Sets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
300
+ * selector) to the entity used as the viewport, which clips the content to the scroll view's
301
+ * bounds. An exact name resolves against the nearest enclosing entity first, then outward,
302
+ * then the document. A non-empty reference that does not resolve warns and is ignored.
299
303
  * @param value - The viewport entity reference.
300
304
  */
301
305
  set viewport(value: string) {
302
306
  this._viewport = value;
303
- const entity = getEntity(value);
304
- if (this.component && entity) {
305
- this.component.viewportEntity = entity;
307
+ if (this.component) {
308
+ const entity = resolveEntity(value, this, 'viewport', 'reference ignored');
309
+ if (entity) {
310
+ this.component.viewportEntity = entity;
311
+ }
306
312
  }
307
313
  }
308
314
 
309
315
  /**
310
- * Gets the reference to the `<pc-entity>` used as the viewport.
316
+ * Gets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
317
+ * selector) to the entity used as the viewport.
311
318
  * @returns The viewport entity reference.
312
319
  */
313
320
  get viewport() {
@@ -315,20 +322,25 @@ class ScrollViewComponentElement extends ComponentElement {
315
322
  }
316
323
 
317
324
  /**
318
- * Sets the reference (CSS selector, element id or entity name) to the `<pc-entity>` used as the
319
- * content, which is moved as the scroll view is scrolled.
325
+ * Sets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
326
+ * selector) to the entity used as the content, which is moved as the scroll view is
327
+ * scrolled. An exact name resolves against the nearest enclosing entity first, then outward,
328
+ * then the document. A non-empty reference that does not resolve warns and is ignored.
320
329
  * @param value - The content entity reference.
321
330
  */
322
331
  set content(value: string) {
323
332
  this._content = value;
324
- const entity = getEntity(value);
325
- if (this.component && entity) {
326
- this.component.contentEntity = entity;
333
+ if (this.component) {
334
+ const entity = resolveEntity(value, this, 'content', 'reference ignored');
335
+ if (entity) {
336
+ this.component.contentEntity = entity;
337
+ }
327
338
  }
328
339
  }
329
340
 
330
341
  /**
331
- * Gets the reference to the `<pc-entity>` used as the content.
342
+ * Gets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
343
+ * selector) to the entity used as the content.
332
344
  * @returns The content entity reference.
333
345
  */
334
346
  get content() {
@@ -336,20 +348,25 @@ class ScrollViewComponentElement extends ComponentElement {
336
348
  }
337
349
 
338
350
  /**
339
- * Sets the reference (CSS selector, element id or entity name) to the `<pc-entity>` containing
340
- * the horizontal `<pc-scrollbar>`.
351
+ * Sets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
352
+ * selector) to the entity containing the horizontal `<pc-scrollbar>`. An exact name resolves
353
+ * against the nearest enclosing entity first, then outward, then the document. A non-empty
354
+ * reference that does not resolve warns and is ignored.
341
355
  * @param value - The horizontal scrollbar entity reference.
342
356
  */
343
357
  set horizontalScrollbar(value: string) {
344
358
  this._horizontalScrollbar = value;
345
- const entity = getEntity(value);
346
- if (this.component && entity) {
347
- this.component.horizontalScrollbarEntity = entity;
359
+ if (this.component) {
360
+ const entity = resolveEntity(value, this, 'horizontal-scrollbar', 'reference ignored');
361
+ if (entity) {
362
+ this.component.horizontalScrollbarEntity = entity;
363
+ }
348
364
  }
349
365
  }
350
366
 
351
367
  /**
352
- * Gets the reference to the `<pc-entity>` containing the horizontal scrollbar.
368
+ * Gets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
369
+ * selector) to the entity containing the horizontal scrollbar.
353
370
  * @returns The horizontal scrollbar entity reference.
354
371
  */
355
372
  get horizontalScrollbar() {
@@ -357,20 +374,25 @@ class ScrollViewComponentElement extends ComponentElement {
357
374
  }
358
375
 
359
376
  /**
360
- * Sets the reference (CSS selector, element id or entity name) to the `<pc-entity>` containing
361
- * the vertical `<pc-scrollbar>`.
377
+ * Sets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
378
+ * selector) to the entity containing the vertical `<pc-scrollbar>`. An exact name resolves
379
+ * against the nearest enclosing entity first, then outward, then the document. A non-empty
380
+ * reference that does not resolve warns and is ignored.
362
381
  * @param value - The vertical scrollbar entity reference.
363
382
  */
364
383
  set verticalScrollbar(value: string) {
365
384
  this._verticalScrollbar = value;
366
- const entity = getEntity(value);
367
- if (this.component && entity) {
368
- this.component.verticalScrollbarEntity = entity;
385
+ if (this.component) {
386
+ const entity = resolveEntity(value, this, 'vertical-scrollbar', 'reference ignored');
387
+ if (entity) {
388
+ this.component.verticalScrollbarEntity = entity;
389
+ }
369
390
  }
370
391
  }
371
392
 
372
393
  /**
373
- * Gets the reference to the `<pc-entity>` containing the vertical scrollbar.
394
+ * Gets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
395
+ * selector) to the entity containing the vertical scrollbar.
374
396
  * @returns The vertical scrollbar entity reference.
375
397
  */
376
398
  get verticalScrollbar() {
@@ -1,7 +1,8 @@
1
1
  import type { ScrollbarComponent } from 'playcanvas';
2
2
  import { ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL } from 'playcanvas';
3
3
 
4
- import { getEntity, parseEnum, parseNumber } from '../parse';
4
+ import { resolveEntity } from '../entity-reference';
5
+ import { parseEnum, parseNumber } from '../parse';
5
6
 
6
7
  import { ComponentElement } from './component';
7
8
 
@@ -24,7 +25,7 @@ const orientations = new Map<'horizontal' | 'vertical', number>([
24
25
  *
25
26
  * @category Components
26
27
  */
27
- class ScrollbarComponentElement extends ComponentElement {
28
+ class ScrollbarComponentElement extends ComponentElement<ScrollbarComponent> {
28
29
  private _orientation: 'horizontal' | 'vertical' = 'horizontal';
29
30
 
30
31
  private _value = 0;
@@ -45,7 +46,7 @@ class ScrollbarComponentElement extends ComponentElement {
45
46
  handleSize: this._handleSize
46
47
  };
47
48
 
48
- const handle = getEntity(this._handle);
49
+ const handle = resolveEntity(this._handle, this, 'handle', 'reference ignored');
49
50
  if (handle) {
50
51
  data.handleEntity = handle;
51
52
  }
@@ -54,11 +55,12 @@ class ScrollbarComponentElement extends ComponentElement {
54
55
  }
55
56
 
56
57
  /**
57
- * Gets the underlying PlayCanvas scrollbar component.
58
- * @returns The scrollbar component.
58
+ * Gets the underlying PlayCanvas scrollbar component. `null` until the element is
59
+ * ready see {@link ComponentElement.component}.
60
+ * @returns The scrollbar component, or `null`.
59
61
  */
60
- get component(): ScrollbarComponent {
61
- return super.component as ScrollbarComponent;
62
+ get component(): ScrollbarComponent | null {
63
+ return super.component;
62
64
  }
63
65
 
64
66
  /**
@@ -120,20 +122,25 @@ class ScrollbarComponentElement extends ComponentElement {
120
122
  }
121
123
 
122
124
  /**
123
- * Sets the reference (CSS selector, element id or entity name) to the `<pc-entity>` used as the
124
- * scrollbar handle.
125
+ * Sets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
126
+ * selector) to the entity used as the scrollbar handle. An exact name resolves against the
127
+ * nearest enclosing entity first, then outward, then the document. A non-empty reference that
128
+ * does not resolve warns and is ignored.
125
129
  * @param value - The handle entity reference.
126
130
  */
127
131
  set handle(value: string) {
128
132
  this._handle = value;
129
- const entity = getEntity(value);
130
- if (this.component && entity) {
131
- this.component.handleEntity = entity;
133
+ if (this.component) {
134
+ const entity = resolveEntity(value, this, 'handle', 'reference ignored');
135
+ if (entity) {
136
+ this.component.handleEntity = entity;
137
+ }
132
138
  }
133
139
  }
134
140
 
135
141
  /**
136
- * Gets the reference to the `<pc-entity>` used as the scrollbar handle.
142
+ * Gets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
143
+ * selector) to the entity used as the scrollbar handle.
137
144
  * @returns The handle entity reference.
138
145
  */
139
146
  get handle() {