@playcanvas/web-components 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pwc.js CHANGED
@@ -4,6 +4,41 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.pd = {}, global.pc));
5
5
  })(this, (function (exports, playcanvas) { 'use strict';
6
6
 
7
+ /**
8
+ * Base class for all PlayCanvas web components that initialize asynchronously.
9
+ */
10
+ class AsyncElement extends HTMLElement {
11
+ constructor() {
12
+ super();
13
+ this._readyPromise = new Promise((resolve) => {
14
+ this._readyResolve = resolve;
15
+ });
16
+ }
17
+ get closestApp() {
18
+ var _a;
19
+ return (_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.closest('pc-app');
20
+ }
21
+ get closestEntity() {
22
+ var _a;
23
+ return (_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.closest('pc-entity');
24
+ }
25
+ /**
26
+ * Called when the element is fully initialized and ready.
27
+ * Subclasses should call this when they're ready.
28
+ */
29
+ _onReady() {
30
+ this._readyResolve();
31
+ this.dispatchEvent(new CustomEvent('ready'));
32
+ }
33
+ /**
34
+ * Returns a promise that resolves with this element when it's ready.
35
+ * @returns A promise that resolves with this element when it's ready.
36
+ */
37
+ ready() {
38
+ return this._readyPromise.then(() => this);
39
+ }
40
+ }
41
+
7
42
  class ModuleElement extends HTMLElement {
8
43
  constructor() {
9
44
  super();
@@ -32,7 +67,7 @@
32
67
  /**
33
68
  * The main application element.
34
69
  */
35
- class AppElement extends HTMLElement {
70
+ class AppElement extends AsyncElement {
36
71
  /**
37
72
  * Creates a new AppElement.
38
73
  */
@@ -49,9 +84,6 @@
49
84
  this.app = null;
50
85
  // Bind methods to maintain 'this' context
51
86
  this._onWindowResize = this._onWindowResize.bind(this);
52
- this.appReadyPromise = new Promise((resolve) => {
53
- this.appReadyResolve = resolve;
54
- });
55
87
  }
56
88
  async connectedCallback() {
57
89
  // Get all pc-module elements that are direct children of the pc-app element
@@ -91,7 +123,7 @@
91
123
  this.app.start();
92
124
  // Handle window resize to keep the canvas responsive
93
125
  window.addEventListener('resize', this._onWindowResize);
94
- this.appReadyResolve(this.app);
126
+ this._onReady();
95
127
  });
96
128
  }
97
129
  disconnectedCallback() {
@@ -108,21 +140,26 @@
108
140
  this._canvas = null;
109
141
  }
110
142
  }
111
- async getApplication() {
112
- await this.appReadyPromise;
113
- return this.app;
114
- }
115
143
  _onWindowResize() {
116
144
  if (this.app) {
117
145
  this.app.resizeCanvas();
118
146
  }
119
147
  }
148
+ /**
149
+ * Sets the high resolution flag. When true, the application will render at the device's
150
+ * physical resolution. When false, the application will render at CSS resolution.
151
+ * @param value - The high resolution flag.
152
+ */
120
153
  set highResolution(value) {
121
154
  this._highResolution = value;
122
155
  if (this.app) {
123
156
  this.app.graphicsDevice.maxPixelRatio = value ? window.devicePixelRatio : 1;
124
157
  }
125
158
  }
159
+ /**
160
+ * Gets the high resolution flag.
161
+ * @returns The high resolution flag.
162
+ */
126
163
  get highResolution() {
127
164
  return this._highResolution;
128
165
  }
@@ -199,7 +236,7 @@
199
236
  /**
200
237
  * Represents an entity in the PlayCanvas engine.
201
238
  */
202
- class EntityElement extends HTMLElement {
239
+ class EntityElement extends AsyncElement {
203
240
  constructor() {
204
241
  super(...arguments);
205
242
  /**
@@ -226,33 +263,20 @@
226
263
  * The tags of the entity.
227
264
  */
228
265
  this._tags = [];
229
- this._entityReady = new Promise((resolve) => {
230
- this._resolveEntity = resolve;
231
- });
232
266
  /**
233
267
  * The PlayCanvas entity instance.
234
268
  */
235
269
  this.entity = null;
236
270
  }
237
271
  async connectedCallback() {
238
- // Get the application
239
- const appElement = this.closest('pc-app');
240
- if (!appElement) {
241
- console.warn(`${this.tagName} must be a child of pc-app`);
272
+ const closestApp = this.closestApp;
273
+ if (!closestApp)
242
274
  return;
243
- }
244
- const app = await appElement.getApplication();
275
+ // Wait for the app to complete initialization
276
+ await closestApp.ready();
277
+ const app = closestApp.app;
245
278
  // Create a new entity
246
279
  this.entity = new playcanvas.Entity(this._name, app);
247
- this._resolveEntity(this.entity);
248
- if (this.parentElement &&
249
- this.parentElement.tagName.toLowerCase() === 'pc-entity') {
250
- const parentEntity = await this.parentElement._entityReady;
251
- parentEntity.addChild(this.entity);
252
- }
253
- else {
254
- app.root.addChild(this.entity);
255
- }
256
280
  // Initialize from attributes
257
281
  const nameAttr = this.getAttribute('name');
258
282
  const positionAttr = this.getAttribute('position');
@@ -269,6 +293,17 @@
269
293
  this.scale = parseVec3(scaleAttr);
270
294
  if (tagsAttr)
271
295
  this.tags = tagsAttr.split(',').map(tag => tag.trim());
296
+ const closestEntity = this.closestEntity;
297
+ if (closestEntity) {
298
+ closestEntity.ready().then(() => {
299
+ closestEntity.entity.addChild(this.entity);
300
+ this._onReady();
301
+ });
302
+ }
303
+ else {
304
+ app.root.addChild(this.entity);
305
+ this._onReady();
306
+ }
272
307
  }
273
308
  disconnectedCallback() {
274
309
  if (this.entity) {
@@ -280,10 +315,6 @@
280
315
  this.entity.destroy();
281
316
  this.entity = null;
282
317
  }
283
- // Reset the promise for potential reconnection
284
- this._entityReady = new Promise((resolve) => {
285
- this._resolveEntity = resolve;
286
- });
287
318
  }
288
319
  /**
289
320
  * Sets the enabled state of the entity.
@@ -422,6 +453,7 @@
422
453
  ['frag', 'shader'],
423
454
  ['glb', 'container'],
424
455
  ['glsl', 'shader'],
456
+ ['hdr', 'texture'],
425
457
  ['html', 'html'],
426
458
  ['jpg', 'texture'],
427
459
  ['js', 'script'],
@@ -446,8 +478,6 @@
446
478
  */
447
479
  this.asset = null;
448
480
  }
449
- async connectedCallback() {
450
- }
451
481
  disconnectedCallback() {
452
482
  this.destroyAsset();
453
483
  }
@@ -511,7 +541,7 @@
511
541
  *
512
542
  * @category Components
513
543
  */
514
- class ComponentElement extends HTMLElement {
544
+ class ComponentElement extends AsyncElement {
515
545
  /**
516
546
  * Constructor for the ComponentElement.
517
547
  * @param componentName - The name of the component.
@@ -526,27 +556,23 @@
526
556
  getInitialComponentData() {
527
557
  return {};
528
558
  }
529
- async connectedCallback() {
530
- const appElement = this.closest('pc-app');
531
- if (!appElement) {
532
- console.error(`${this.tagName.toLowerCase()} should be a descendant of pc-app`);
533
- return;
534
- }
535
- await appElement.getApplication();
536
- this.addComponent();
537
- }
538
- addComponent() {
539
- // Access the parent pc-entity's 'entity' property
540
- const entityElement = this.closest('pc-entity');
541
- if (!entityElement) {
542
- console.error(`${this.tagName.toLowerCase()} should be a child of pc-entity`);
543
- return;
544
- }
545
- if (entityElement && entityElement.entity) {
559
+ async addComponent() {
560
+ const entityElement = this.closestEntity;
561
+ if (entityElement) {
562
+ await entityElement.ready();
546
563
  // Add the component to the entity
547
- this._component = entityElement.entity.addComponent(this._componentName, this.getInitialComponentData());
564
+ const data = this.getInitialComponentData();
565
+ this._component = entityElement.entity.addComponent(this._componentName, data);
548
566
  }
549
567
  }
568
+ initComponent() { }
569
+ async connectedCallback() {
570
+ var _a;
571
+ await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());
572
+ await this.addComponent();
573
+ this.initComponent();
574
+ this._onReady();
575
+ }
550
576
  disconnectedCallback() {
551
577
  // Remove the component when the element is disconnected
552
578
  if (this.component && this.component.entity) {
@@ -651,6 +677,26 @@
651
677
  scissorRect: this._scissorRect
652
678
  };
653
679
  }
680
+ get xrAvailable() {
681
+ var _a;
682
+ const xrManager = (_a = this.component) === null || _a === void 0 ? void 0 : _a.system.app.xr;
683
+ return xrManager && xrManager.supported && xrManager.isAvailable(playcanvas.XRTYPE_VR);
684
+ }
685
+ startXr(type, space) {
686
+ if (this.component && this.xrAvailable) {
687
+ this.component.startXr(type, space, {
688
+ callback: (err) => {
689
+ if (err)
690
+ console.error(`WebXR Immersive VR failed to start: ${err.message}`);
691
+ }
692
+ });
693
+ }
694
+ }
695
+ endXr() {
696
+ if (this.component) {
697
+ this.component.endXr();
698
+ }
699
+ }
654
700
  /**
655
701
  * Gets the camera component.
656
702
  * @returns The camera component.
@@ -1152,8 +1198,7 @@
1152
1198
  this._width = 0;
1153
1199
  this._wrapLines = false;
1154
1200
  }
1155
- async connectedCallback() {
1156
- await super.connectedCallback();
1201
+ initComponent() {
1157
1202
  this.component._text._material.useFog = true;
1158
1203
  }
1159
1204
  getInitialComponentData() {
@@ -1287,7 +1332,20 @@
1287
1332
  return this._wrapLines;
1288
1333
  }
1289
1334
  static get observedAttributes() {
1290
- return [...super.observedAttributes, 'anchor', 'asset', 'auto-width', 'color', 'font-size', 'line-height', 'pivot', 'text', 'type', 'width', 'wrap-lines'];
1335
+ return [
1336
+ ...super.observedAttributes,
1337
+ 'anchor',
1338
+ 'asset',
1339
+ 'auto-width',
1340
+ 'color',
1341
+ 'font-size',
1342
+ 'line-height',
1343
+ 'pivot',
1344
+ 'text',
1345
+ 'type',
1346
+ 'width',
1347
+ 'wrap-lines'
1348
+ ];
1291
1349
  }
1292
1350
  attributeChangedCallback(name, _oldValue, newValue) {
1293
1351
  super.attributeChangedCallback(name, _oldValue, newValue);
@@ -1773,14 +1831,11 @@
1773
1831
  this._receiveShadows = true;
1774
1832
  this._type = 'asset';
1775
1833
  }
1776
- async connectedCallback() {
1777
- await super.connectedCallback();
1778
- this.material = this._material;
1779
- }
1780
1834
  getInitialComponentData() {
1781
1835
  return {
1782
1836
  type: this._type,
1783
1837
  castShadows: this._castShadows,
1838
+ material: MaterialElement.get(this._material),
1784
1839
  receiveShadows: this._receiveShadows
1785
1840
  };
1786
1841
  }
@@ -2209,8 +2264,7 @@
2209
2264
  this.addEventListener('scriptattributeschange', this.handleScriptAttributesChange.bind(this));
2210
2265
  this.addEventListener('scriptenablechange', this.handleScriptEnableChange.bind(this));
2211
2266
  }
2212
- async connectedCallback() {
2213
- await super.connectedCallback();
2267
+ initComponent() {
2214
2268
  // Handle initial script elements
2215
2269
  this.querySelectorAll(':scope > pc-script').forEach((scriptElement) => {
2216
2270
  const scriptName = scriptElement.getAttribute('name');
@@ -2480,7 +2534,7 @@
2480
2534
  /**
2481
2535
  * Represents a sound slot in the PlayCanvas engine.
2482
2536
  */
2483
- class SoundSlotElement extends HTMLElement {
2537
+ class SoundSlotElement extends AsyncElement {
2484
2538
  constructor() {
2485
2539
  super(...arguments);
2486
2540
  this._asset = '';
@@ -2498,12 +2552,8 @@
2498
2552
  this.soundSlot = null;
2499
2553
  }
2500
2554
  async connectedCallback() {
2501
- const appElement = this.closest('pc-app');
2502
- if (!appElement) {
2503
- console.error(`${this.tagName.toLowerCase()} should be a descendant of pc-app`);
2504
- return;
2505
- }
2506
- await appElement.getApplication();
2555
+ var _a;
2556
+ await ((_a = this.soundElement) === null || _a === void 0 ? void 0 : _a.ready());
2507
2557
  const options = {
2508
2558
  autoPlay: this._autoPlay,
2509
2559
  loop: this._loop,
@@ -2518,6 +2568,7 @@
2518
2568
  this.soundSlot = this.soundElement.component.addSlot(this._name, options);
2519
2569
  this.asset = this._asset;
2520
2570
  this.soundSlot.play();
2571
+ this._onReady();
2521
2572
  }
2522
2573
  disconnectedCallback() {
2523
2574
  this.soundElement.component.removeSlot(this._name);
@@ -2724,23 +2775,19 @@
2724
2775
  /**
2725
2776
  * Represents a model in the PlayCanvas engine.
2726
2777
  */
2727
- class ModelElement extends HTMLElement {
2778
+ class ModelElement extends AsyncElement {
2728
2779
  constructor() {
2729
2780
  super(...arguments);
2730
2781
  this._asset = '';
2731
2782
  }
2732
2783
  async connectedCallback() {
2733
- // Get the application
2734
- const appElement = this.closest('pc-app');
2735
- if (!appElement) {
2736
- console.warn(`${this.tagName} must be a child of pc-app`);
2737
- return;
2738
- }
2739
- await appElement.getApplication();
2784
+ var _a;
2785
+ await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());
2740
2786
  const asset = this.getAttribute('asset');
2741
2787
  if (asset) {
2742
2788
  this.asset = asset;
2743
2789
  }
2790
+ this._onReady();
2744
2791
  }
2745
2792
  _loadModel() {
2746
2793
  const asset = AssetElement.get(this._asset);
@@ -2748,13 +2795,23 @@
2748
2795
  return;
2749
2796
  }
2750
2797
  const entity = asset.resource.instantiateRenderEntity();
2751
- const parentEntityElement = this.closest('pc-entity');
2798
+ if (asset.resource.animations.length > 0) {
2799
+ entity.addComponent('anim');
2800
+ entity.anim.assignAnimation('animation', asset.resource.animations[0].resource);
2801
+ }
2802
+ const parentEntityElement = this.closestEntity;
2752
2803
  if (parentEntityElement) {
2753
- parentEntityElement.entity.addChild(entity);
2804
+ parentEntityElement.ready().then(() => {
2805
+ parentEntityElement.entity.addChild(entity);
2806
+ });
2754
2807
  }
2755
2808
  else {
2756
- const appElement = this.closest('pc-app');
2757
- appElement.app.root.addChild(entity);
2809
+ const appElement = this.closestApp;
2810
+ if (appElement) {
2811
+ appElement.ready().then(() => {
2812
+ appElement.app.root.addChild(entity);
2813
+ });
2814
+ }
2758
2815
  }
2759
2816
  }
2760
2817
  /**
@@ -2788,7 +2845,7 @@
2788
2845
  /**
2789
2846
  * Represents a scene in the PlayCanvas engine.
2790
2847
  */
2791
- class SceneElement extends HTMLElement {
2848
+ class SceneElement extends AsyncElement {
2792
2849
  constructor() {
2793
2850
  super(...arguments);
2794
2851
  /**
@@ -2817,15 +2874,11 @@
2817
2874
  this.scene = null;
2818
2875
  }
2819
2876
  async connectedCallback() {
2820
- // Get the application
2821
- const appElement = this.closest('pc-app');
2822
- if (!appElement) {
2823
- console.warn(`${this.tagName} must be a child of pc-app`);
2824
- return;
2825
- }
2826
- const app = await appElement.getApplication();
2827
- this.scene = app.scene;
2877
+ var _a;
2878
+ await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());
2879
+ this.scene = this.closestApp.app.scene;
2828
2880
  this.updateSceneSettings();
2881
+ this._onReady();
2829
2882
  }
2830
2883
  updateSceneSettings() {
2831
2884
  if (this.scene) {
@@ -2951,57 +3004,71 @@
2951
3004
  /**
2952
3005
  * Represents a sky in the PlayCanvas engine.
2953
3006
  */
2954
- class SkyElement extends HTMLElement {
3007
+ class SkyElement extends AsyncElement {
2955
3008
  constructor() {
2956
3009
  super(...arguments);
2957
3010
  this._asset = '';
3011
+ this._center = new playcanvas.Vec3(0, 0.01, 0);
2958
3012
  this._intensity = 1;
2959
- this._rotation = [0, 0, 0];
3013
+ this._rotation = new playcanvas.Vec3();
2960
3014
  this._level = 0;
2961
- this._solidColor = false;
3015
+ this._scale = new playcanvas.Vec3(100, 100, 100);
3016
+ this._type = 'infinite';
2962
3017
  }
2963
3018
  async connectedCallback() {
2964
- // Get the application
2965
- const appElement = this.closest('pc-app');
2966
- if (!appElement) {
2967
- console.warn(`${this.tagName} must be a child of pc-app`);
2968
- return;
2969
- }
2970
- await appElement.getApplication();
3019
+ var _a;
3020
+ await ((_a = this.closestApp) === null || _a === void 0 ? void 0 : _a.ready());
2971
3021
  this.asset = this.getAttribute('asset') || '';
2972
- this.solidColor = this.hasAttribute('solid-color');
3022
+ this._onReady();
2973
3023
  }
2974
3024
  getScene() {
2975
- const appElement = this.closest('pc-app');
2976
- if (!appElement) {
2977
- return;
2978
- }
2979
- const app = appElement.app;
3025
+ const app = this.closestApp.app;
2980
3026
  if (!app) {
2981
3027
  return;
2982
3028
  }
2983
3029
  return app.scene;
2984
3030
  }
3031
+ initSkybox(source) {
3032
+ source.anisotropy = 4;
3033
+ const skybox = playcanvas.EnvLighting.generateSkyboxCubemap(source);
3034
+ const lighting = playcanvas.EnvLighting.generateLightingSource(source);
3035
+ const envAtlas = playcanvas.EnvLighting.generateAtlas(lighting);
3036
+ const app = this.closestApp.app;
3037
+ if (app) {
3038
+ app.scene.envAtlas = envAtlas;
3039
+ app.scene.skybox = skybox;
3040
+ const layer = app.scene.layers.getLayerById(playcanvas.LAYERID_SKYBOX);
3041
+ if (layer) {
3042
+ layer.enabled = this._type !== 'none';
3043
+ }
3044
+ app.scene.sky.type = this._type;
3045
+ app.scene.sky.node.setLocalScale(this._scale);
3046
+ app.scene.sky.center = this._center;
3047
+ }
3048
+ }
2985
3049
  set asset(value) {
2986
3050
  this._asset = value;
2987
3051
  const scene = this.getScene();
2988
3052
  if (scene) {
2989
3053
  const asset = AssetElement.get(value);
2990
3054
  if (asset) {
2991
- if (asset.resource) {
2992
- scene.envAtlas = asset.resource;
2993
- }
2994
- else {
2995
- asset.once('load', () => {
2996
- scene.envAtlas = asset.resource;
2997
- });
2998
- }
3055
+ this.initSkybox(asset.resource);
2999
3056
  }
3000
3057
  }
3001
3058
  }
3002
3059
  get asset() {
3003
3060
  return this._asset;
3004
3061
  }
3062
+ set center(value) {
3063
+ this._center = value;
3064
+ const scene = this.getScene();
3065
+ if (scene) {
3066
+ scene.sky.center = this._center;
3067
+ }
3068
+ }
3069
+ get center() {
3070
+ return this._center;
3071
+ }
3005
3072
  set intensity(value) {
3006
3073
  this._intensity = value;
3007
3074
  const scene = this.getScene();
@@ -3016,24 +3083,21 @@
3016
3083
  this._rotation = value;
3017
3084
  const scene = this.getScene();
3018
3085
  if (scene) {
3019
- scene.skyboxRotation = new playcanvas.Quat().setFromEulerAngles(this._rotation[0], this._rotation[1], this._rotation[2]);
3086
+ scene.skyboxRotation = new playcanvas.Quat().setFromEulerAngles(value);
3020
3087
  }
3021
3088
  }
3022
3089
  get rotation() {
3023
3090
  return this._rotation;
3024
3091
  }
3025
- set solidColor(value) {
3026
- this._solidColor = value;
3092
+ set scale(value) {
3093
+ this._scale = value;
3027
3094
  const scene = this.getScene();
3028
3095
  if (scene) {
3029
- const layer = scene.layers.getLayerById(playcanvas.LAYERID_SKYBOX);
3030
- if (layer) {
3031
- layer.enabled = !this._solidColor;
3032
- }
3096
+ scene.sky.node.setLocalScale(this._scale);
3033
3097
  }
3034
3098
  }
3035
- get solidColor() {
3036
- return this._solidColor;
3099
+ get scale() {
3100
+ return this._scale;
3037
3101
  }
3038
3102
  set level(value) {
3039
3103
  this._level = value;
@@ -3045,25 +3109,45 @@
3045
3109
  get level() {
3046
3110
  return this._level;
3047
3111
  }
3112
+ set type(value) {
3113
+ this._type = value;
3114
+ const scene = this.getScene();
3115
+ if (scene) {
3116
+ scene.sky.type = this._type;
3117
+ const layer = scene.layers.getLayerById(playcanvas.LAYERID_SKYBOX);
3118
+ if (layer) {
3119
+ layer.enabled = this._type !== 'none';
3120
+ }
3121
+ }
3122
+ }
3123
+ get type() {
3124
+ return this._type;
3125
+ }
3048
3126
  static get observedAttributes() {
3049
- return ['asset', 'intensity', 'level', 'rotation', 'solid-color'];
3127
+ return ['asset', 'center', 'intensity', 'level', 'rotation', 'scale', 'type'];
3050
3128
  }
3051
3129
  attributeChangedCallback(name, _oldValue, newValue) {
3052
3130
  switch (name) {
3053
3131
  case 'asset':
3054
3132
  this.asset = newValue;
3055
3133
  break;
3134
+ case 'center':
3135
+ this.center = parseVec3(newValue);
3136
+ break;
3056
3137
  case 'intensity':
3057
3138
  this.intensity = parseFloat(newValue);
3058
3139
  break;
3059
3140
  case 'rotation':
3060
- this.rotation = newValue.split(',').map(Number);
3141
+ this.rotation = parseVec3(newValue);
3061
3142
  break;
3062
3143
  case 'level':
3063
3144
  this.level = parseInt(newValue, 10);
3064
3145
  break;
3065
- case 'solid-color':
3066
- this.solidColor = this.hasAttribute('solid-color');
3146
+ case 'scale':
3147
+ this.scale = parseVec3(newValue);
3148
+ break;
3149
+ case 'type':
3150
+ this.type = newValue;
3067
3151
  break;
3068
3152
  }
3069
3153
  }
@@ -3072,6 +3156,7 @@
3072
3156
 
3073
3157
  exports.AppElement = AppElement;
3074
3158
  exports.AssetElement = AssetElement;
3159
+ exports.AsyncElement = AsyncElement;
3075
3160
  exports.CameraComponentElement = CameraComponentElement;
3076
3161
  exports.CollisionComponentElement = CollisionComponentElement;
3077
3162
  exports.ComponentElement = ComponentElement;