@playcanvas/web-components 0.9.0 → 0.10.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 (74) hide show
  1. package/README.md +18 -0
  2. package/dist/app.d.ts +55 -9
  3. package/dist/asset.d.ts +25 -1
  4. package/dist/async-element.d.ts +15 -2
  5. package/dist/components/button-component.d.ts +1 -1
  6. package/dist/components/camera-component.d.ts +1 -1
  7. package/dist/components/collision-component.d.ts +1 -1
  8. package/dist/components/component.d.ts +6 -5
  9. package/dist/components/element-component.d.ts +1 -1
  10. package/dist/components/gsplat-component.d.ts +1 -1
  11. package/dist/components/layoutchild-component.d.ts +1 -1
  12. package/dist/components/layoutgroup-component.d.ts +1 -1
  13. package/dist/components/light-component.d.ts +1 -1
  14. package/dist/components/particlesystem-component.d.ts +1 -1
  15. package/dist/components/render-component.d.ts +1 -1
  16. package/dist/components/rigidbody-component.d.ts +1 -1
  17. package/dist/components/screen-component.d.ts +1 -1
  18. package/dist/components/script.d.ts +8 -1
  19. package/dist/components/scrollbar-component.d.ts +1 -1
  20. package/dist/components/scrollview-component.d.ts +1 -1
  21. package/dist/components/sound-component.d.ts +1 -1
  22. package/dist/components/sound-slot.d.ts +9 -1
  23. package/dist/custom-elements.json +16704 -0
  24. package/dist/entity.d.ts +24 -5
  25. package/dist/loading-bar.d.ts +35 -0
  26. package/dist/material.d.ts +972 -4
  27. package/dist/model.d.ts +1 -1
  28. package/dist/module.d.ts +10 -0
  29. package/dist/{utils.d.ts → parse.d.ts} +63 -33
  30. package/dist/pwc.cjs +3070 -699
  31. package/dist/pwc.cjs.map +1 -1
  32. package/dist/pwc.js +3070 -699
  33. package/dist/pwc.js.map +1 -1
  34. package/dist/pwc.min.js +1 -1
  35. package/dist/pwc.min.js.map +1 -1
  36. package/dist/pwc.min.mjs +2 -0
  37. package/dist/pwc.min.mjs.map +1 -0
  38. package/dist/pwc.mjs +3071 -700
  39. package/dist/pwc.mjs.map +1 -1
  40. package/dist/scene.d.ts +12 -4
  41. package/dist/sky.d.ts +1 -1
  42. package/dist/vscode.html-custom-data.json +1800 -0
  43. package/dist/web-types.json +3836 -0
  44. package/package.json +29 -11
  45. package/src/app.ts +178 -78
  46. package/src/asset.ts +44 -2
  47. package/src/async-element.ts +17 -4
  48. package/src/components/button-component.ts +6 -6
  49. package/src/components/camera-component.ts +2 -2
  50. package/src/components/collision-component.ts +2 -2
  51. package/src/components/component.ts +8 -7
  52. package/src/components/element-component.ts +6 -6
  53. package/src/components/gsplat-component.ts +3 -3
  54. package/src/components/layoutchild-component.ts +2 -2
  55. package/src/components/layoutgroup-component.ts +2 -2
  56. package/src/components/light-component.ts +2 -2
  57. package/src/components/particlesystem-component.ts +2 -2
  58. package/src/components/render-component.ts +10 -5
  59. package/src/components/rigidbody-component.ts +2 -2
  60. package/src/components/screen-component.ts +2 -2
  61. package/src/components/script-component.ts +4 -4
  62. package/src/components/script.ts +9 -2
  63. package/src/components/scrollbar-component.ts +3 -3
  64. package/src/components/scrollview-component.ts +6 -6
  65. package/src/components/sound-component.ts +2 -2
  66. package/src/components/sound-slot.ts +31 -9
  67. package/src/entity.ts +56 -22
  68. package/src/loading-bar.ts +122 -0
  69. package/src/material.ts +2402 -59
  70. package/src/model.ts +2 -2
  71. package/src/module.ts +10 -0
  72. package/src/{utils.ts → parse.ts} +104 -65
  73. package/src/scene.ts +51 -21
  74. package/src/sky.ts +3 -3
package/src/entity.ts CHANGED
@@ -1,13 +1,31 @@
1
1
  import { AppBase, Entity, Vec3 } from 'playcanvas';
2
2
 
3
3
  import { AsyncElement } from './async-element';
4
- import { parseBool, parseVec3 } from './utils';
4
+ import { parseBool, parseTags, parseVec3 } from './parse';
5
5
 
6
6
  /**
7
7
  * The EntityElement interface provides properties and methods for manipulating
8
8
  * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-entity/ | `<pc-entity>`} elements.
9
9
  * The EntityElement interface also inherits the properties and methods of the
10
10
  * {@link HTMLElement} interface.
11
+ *
12
+ * The pointer events below are dispatched by the containing `<pc-app>` element when the pointer
13
+ * intersects this entity's geometry. They are only generated while the entity has a listener for
14
+ * them, registered either with {@link addEventListener} or with the matching inline `onpointer*`
15
+ * attribute.
16
+ *
17
+ * @attribute {string} onpointerenter - Script to run when the pointer moves onto the entity.
18
+ * @attribute {string} onpointerleave - Script to run when the pointer moves off the entity.
19
+ * @attribute {string} onpointermove - Script to run when the pointer moves over the entity.
20
+ * @attribute {string} onpointerdown - Script to run when a pointer button is pressed over the
21
+ * entity.
22
+ * @attribute {string} onpointerup - Script to run when a pointer button is released over the
23
+ * entity.
24
+ * @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the entity.
25
+ * @fires {PointerEvent} pointerleave - Fired when the pointer moves off the entity.
26
+ * @fires {PointerEvent} pointermove - Fired when the pointer moves over the entity.
27
+ * @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the entity.
28
+ * @fires {PointerEvent} pointerup - Fired when a pointer button is released over the entity.
11
29
  */
12
30
  class EntityElement extends AsyncElement {
13
31
  /**
@@ -58,12 +76,13 @@ class EntityElement extends AsyncElement {
58
76
  private _entity: Entity | null = null;
59
77
 
60
78
  /**
61
- * The PlayCanvas entity instance. Available once the element is ready await
62
- * {@link whenReady} or the element's `ready()` promise before accessing it.
63
- * @returns The entity instance.
79
+ * The PlayCanvas entity instance. `null` until the element is ready, and again once it has
80
+ * been removed from the document — await {@link whenReady} or the element's `ready()`
81
+ * promise before accessing it.
82
+ * @returns The entity instance, or `null`.
64
83
  */
65
- get entity(): Entity {
66
- return this._entity!;
84
+ get entity(): Entity | null {
85
+ return this._entity;
67
86
  }
68
87
 
69
88
  createEntity(app: AppBase) {
@@ -74,18 +93,20 @@ class EntityElement extends AsyncElement {
74
93
  return;
75
94
  }
76
95
 
77
- // Create a new entity
78
- const entity = new Entity(this.getAttribute('name') || this._name, app);
96
+ // Seed from the cached fields rather than re-reading the attributes. Every observed
97
+ // attribute is routed through its property setter by attributeChangedCallback, so the field
98
+ // already holds the parsed attribute value - and it also holds anything assigned through the
99
+ // property API before the app booted, which reading the attribute back would discard.
100
+ const entity = new Entity(this._name, app);
79
101
  this._entity = entity;
80
102
 
81
- entity.enabled = parseBool(this.getAttribute('enabled'), true);
82
- entity.setLocalPosition(parseVec3(this.getAttribute('position'), Vec3.ZERO, 'position'));
83
- entity.setLocalEulerAngles(parseVec3(this.getAttribute('rotation'), Vec3.ZERO, 'rotation'));
84
- entity.setLocalScale(parseVec3(this.getAttribute('scale'), Vec3.ONE, 'scale'));
103
+ entity.enabled = this._enabled;
104
+ entity.setLocalPosition(this._position);
105
+ entity.setLocalEulerAngles(this._rotation);
106
+ entity.setLocalScale(this._scale);
85
107
 
86
- const tags = this.getAttribute('tags');
87
- if (tags) {
88
- entity.tags.add(tags.split(',').map(tag => tag.trim()));
108
+ if (this._tags.length > 0) {
109
+ entity.tags.add(this._tags);
89
110
  }
90
111
  }
91
112
 
@@ -106,7 +127,15 @@ class EntityElement extends AsyncElement {
106
127
  connectedCallback() {
107
128
  // Wait for app to be ready
108
129
  const closestApp = this.closestApp;
109
- if (!closestApp) return;
130
+ if (!closestApp) {
131
+ // An entity outside an application is inert and never becomes ready, so awaiting it
132
+ // hangs. Warn rather than fail silently, naming the parent it requires, as every other
133
+ // misplaced element does.
134
+ const name = this.getAttribute('name');
135
+ const label = name ? ` '${name}'` : '';
136
+ console.warn(`pc-entity${label} must be a descendant of pc-app - entity not created`);
137
+ return;
138
+ }
110
139
 
111
140
  // If app is already running, create entity immediately
112
141
  if (closestApp.hierarchyReady) {
@@ -128,10 +157,15 @@ class EntityElement extends AsyncElement {
128
157
 
129
158
  disconnectedCallback() {
130
159
  if (this.entity) {
131
- // Notify all children that their entities are about to become invalid
132
- const children = this.querySelectorAll('pc-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');
133
166
  children.forEach((child) => {
134
- (child as EntityElement)._entity = null;
167
+ child._entity = null;
168
+ child._built = false;
135
169
  });
136
170
 
137
171
  // Destroy the entity
@@ -297,13 +331,13 @@ class EntityElement extends AsyncElement {
297
331
  ];
298
332
  }
299
333
 
300
- attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
334
+ attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
301
335
  switch (name) {
302
336
  case 'enabled':
303
337
  this.enabled = parseBool(newValue, true);
304
338
  break;
305
339
  case 'name':
306
- this.name = newValue;
340
+ this.name = newValue ?? 'Untitled';
307
341
  break;
308
342
  case 'position':
309
343
  this.position = parseVec3(newValue, Vec3.ZERO, name);
@@ -315,7 +349,7 @@ class EntityElement extends AsyncElement {
315
349
  this.scale = parseVec3(newValue, Vec3.ONE, name);
316
350
  break;
317
351
  case 'tags':
318
- this.tags = newValue.split(',').map(tag => tag.trim());
352
+ this.tags = parseTags(newValue);
319
353
  break;
320
354
  case 'onpointerenter':
321
355
  case 'onpointerleave':
@@ -0,0 +1,122 @@
1
+ /** Covers the 0.2s opacity transition; jsdom never fires transitionend, so removal is timed. */
2
+ const REMOVAL_DELAY_MS = 250;
3
+
4
+ /**
5
+ * The slim progress bar `<pc-app>` shows while it boots and preloads. An implementation detail of
6
+ * AppElement rather than a custom element, so its shape can change without a breaking change.
7
+ *
8
+ * All styling is inline, so the library injects no stylesheet. The colors and height resolve CSS
9
+ * custom properties — `--pc-loading-bar-color`, `--pc-loading-bar-background` and
10
+ * `--pc-loading-bar-height` — so a page can theme the bar from `pc-app` or `:root`.
11
+ */
12
+ class LoadingBar {
13
+ private _track: HTMLDivElement;
14
+
15
+ private _fill: HTMLDivElement;
16
+
17
+ private _sweep: Animation | null = null;
18
+
19
+ private _removal: ReturnType<typeof setTimeout> | null = null;
20
+
21
+ /**
22
+ * Creates the bar and appends it to `parent`, starting in the indeterminate state.
23
+ * @param parent - The element to append the bar to.
24
+ */
25
+ constructor(parent: HTMLElement) {
26
+ this._track = document.createElement('div');
27
+ this._track.setAttribute('role', 'progressbar');
28
+ this._track.setAttribute('aria-label', 'Loading');
29
+ this._track.setAttribute('aria-valuemin', '0');
30
+ this._track.setAttribute('aria-valuemax', '100');
31
+ // Fixed positioning matches the canvas, which always fills the window (FILLMODE_FILL_WINDOW)
32
+ this._track.style.cssText = [
33
+ 'position: fixed',
34
+ 'top: 0',
35
+ 'left: 0',
36
+ 'width: 100%',
37
+ 'height: var(--pc-loading-bar-height, 3px)',
38
+ 'background: var(--pc-loading-bar-background, rgba(0, 0, 0, 0.1))',
39
+ 'z-index: 10000',
40
+ 'pointer-events: none',
41
+ 'opacity: 1',
42
+ 'transition: opacity 0.2s ease'
43
+ ].join('; ');
44
+
45
+ this._fill = document.createElement('div');
46
+ this._fill.style.cssText = [
47
+ 'width: 100%',
48
+ 'height: 100%',
49
+ 'transform-origin: left center',
50
+ 'transform: scaleX(0)',
51
+ 'background: var(--pc-loading-bar-color, #f60)',
52
+ 'transition: transform 0.2s ease'
53
+ ].join('; ');
54
+
55
+ this._track.appendChild(this._fill);
56
+ parent.appendChild(this._track);
57
+
58
+ // Indeterminate sweep until the first progress() call reports a real total. No
59
+ // aria-valuenow is set, which is what marks a progressbar indeterminate. jsdom has no Web
60
+ // Animations API, so the guard degrades to a static bar there rather than crashing boot.
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
+ });
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Reflects preload progress, switching the bar from indeterminate to determinate on the first
75
+ * call.
76
+ * @param loaded - The number of assets that have finished loading.
77
+ * @param total - The number of assets being preloaded.
78
+ */
79
+ progress(loaded: number, total: number) {
80
+ if (this._sweep) {
81
+ this._sweep.cancel();
82
+ this._sweep = null;
83
+ }
84
+ const fraction = total === 0 ? 1 : loaded / total;
85
+ this._track.setAttribute('aria-valuenow', String(Math.round(fraction * 100)));
86
+ this._fill.style.transform = `scaleX(${fraction})`;
87
+ }
88
+
89
+ /**
90
+ * Fills the bar, fades it out and removes it. Idempotent.
91
+ */
92
+ complete() {
93
+ if (this._removal !== null) {
94
+ return;
95
+ }
96
+ if (this._sweep) {
97
+ this._sweep.cancel();
98
+ this._sweep = null;
99
+ }
100
+ this._track.setAttribute('aria-valuenow', '100');
101
+ this._fill.style.transform = 'scaleX(1)';
102
+ this._track.style.opacity = '0';
103
+ this._removal = setTimeout(() => this._track.remove(), REMOVAL_DELAY_MS);
104
+ }
105
+
106
+ /**
107
+ * Removes the bar immediately, cancelling any pending fade. Idempotent.
108
+ */
109
+ destroy() {
110
+ if (this._sweep) {
111
+ this._sweep.cancel();
112
+ this._sweep = null;
113
+ }
114
+ if (this._removal !== null) {
115
+ clearTimeout(this._removal);
116
+ this._removal = null;
117
+ }
118
+ this._track.remove();
119
+ }
120
+ }
121
+
122
+ export { LoadingBar };