@playcanvas/web-components 0.14.0 → 0.16.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.
- package/dist/components/anim-clip.d.cts +127 -0
- package/dist/components/anim-clip.d.ts +127 -0
- package/dist/components/anim-component.d.cts +207 -0
- package/dist/components/anim-component.d.ts +207 -0
- package/dist/components/joint-component.d.cts +521 -0
- package/dist/components/joint-component.d.ts +521 -0
- package/dist/custom-elements.json +2841 -1393
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/pwc.cjs +3536 -1744
- package/dist/pwc.cjs.map +1 -1
- package/dist/pwc.js +3536 -1744
- package/dist/pwc.js.map +1 -1
- package/dist/pwc.min.js +1 -1
- package/dist/pwc.min.js.map +1 -1
- package/dist/pwc.min.mjs +1 -1
- package/dist/pwc.min.mjs.map +1 -1
- package/dist/pwc.mjs +3535 -1746
- package/dist/pwc.mjs.map +1 -1
- package/dist/vscode.html-custom-data.json +251 -0
- package/dist/web-types.json +503 -1
- package/package.json +3 -2
- package/src/app.ts +18 -0
- package/src/components/anim-clip.ts +395 -0
- package/src/components/anim-component.ts +649 -0
- package/src/components/joint-component.ts +984 -0
- package/src/entity.ts +28 -7
- package/src/index.ts +10 -0
- package/src/model.ts +0 -7
- package/src/node.ts +2 -7
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import type { Asset, EventHandle } from 'playcanvas';
|
|
2
|
+
import { AnimTrack } from 'playcanvas';
|
|
3
|
+
|
|
4
|
+
import { AssetElement, useAsset } from '../asset';
|
|
5
|
+
import { AsyncElement } from '../async-element';
|
|
6
|
+
import { ModelElement } from '../model';
|
|
7
|
+
import { parseBool, parseNumber } from '../parse';
|
|
8
|
+
|
|
9
|
+
import type { ContainerWithAnimations } from './anim-component';
|
|
10
|
+
import { AnimComponentElement } from './anim-component';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The AnimClipElement interface provides properties and methods for manipulating
|
|
14
|
+
* {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-anim-clip/ | `<pc-anim-clip>`}
|
|
15
|
+
* elements. The AnimClipElement interface also inherits the properties and methods of the
|
|
16
|
+
* {@link HTMLElement} interface.
|
|
17
|
+
*
|
|
18
|
+
* A clip declares one named animation on its parent `<pc-anim>`. `name` is both the clip's name
|
|
19
|
+
* and the track looked up in the clip's source: an explicit `asset` (a `container`, an
|
|
20
|
+
* `animation` `.glb`, or an `animclip` JSON), or, without one, the container of the `<pc-model>`
|
|
21
|
+
* enclosing the parent `<pc-anim>`. A source holding a single track supplies it whatever it is
|
|
22
|
+
* named; in a multi-track source the track named `name` is chosen, falling back to the first
|
|
23
|
+
* with a warning. The element becomes ready once its resolved track is assigned.
|
|
24
|
+
*
|
|
25
|
+
* @category Components
|
|
26
|
+
*/
|
|
27
|
+
class AnimClipElement extends AsyncElement {
|
|
28
|
+
/**
|
|
29
|
+
* The `<pc-anim>` this clip was adopted by, captured when the parent adopts the clip and on
|
|
30
|
+
* connection.
|
|
31
|
+
*
|
|
32
|
+
* `disconnectedCallback` cannot rediscover it: by the time the element is disconnected its
|
|
33
|
+
* `parentElement` is already `null`, so a lookup would both fail to find the component and
|
|
34
|
+
* emit a misleading "must be a direct child" warning for what is an ordinary removal.
|
|
35
|
+
*/
|
|
36
|
+
private _animElement: AnimComponentElement | null = null;
|
|
37
|
+
|
|
38
|
+
private _asset = '';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Incremented on every connect and disconnect, and captured by connectedCallback on entry —
|
|
42
|
+
* a resume from an await abandons itself if the value has moved on, so a stale callback can
|
|
43
|
+
* neither act on a torn-down tree nor register its clip alongside a re-inserted element's
|
|
44
|
+
* own callback.
|
|
45
|
+
*/
|
|
46
|
+
private _connectionGeneration = 0;
|
|
47
|
+
|
|
48
|
+
private _errorHandle: EventHandle | null = null;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Incremented on every track resolution and on disconnect, and captured by a resolution when
|
|
52
|
+
* it starts. A resolution that resumes from an await or an asset callback abandons itself if
|
|
53
|
+
* the value has moved on, so a superseded resolution cannot hand a stale track to the parent.
|
|
54
|
+
*/
|
|
55
|
+
private _loadGeneration = 0;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The pending asset subscriptions of the current resolution, if it is waiting for its asset.
|
|
59
|
+
* Held so that whatever supersedes the resolution can detach the handlers from the asset,
|
|
60
|
+
* rather than leave them registered until the asset settles (or forever, if it never does).
|
|
61
|
+
*/
|
|
62
|
+
private _loadHandle: EventHandle | null = null;
|
|
63
|
+
|
|
64
|
+
private _loop = true;
|
|
65
|
+
|
|
66
|
+
private _name = '';
|
|
67
|
+
|
|
68
|
+
private _speed = 1;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The source complaint already made — the asset id it was made for, or `''` for the
|
|
72
|
+
* no-asset-no-model case — so re-resolutions (host cycles, model reloads) do not repeat it.
|
|
73
|
+
*/
|
|
74
|
+
private _warnedSource: string | null = null;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether the owning `<pc-anim>` already rejected this clip's name — its sweeps re-run on
|
|
78
|
+
* host cycles and must not repeat the complaint.
|
|
79
|
+
*/
|
|
80
|
+
private _warnedInvalid = false;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The clip's resolved track. `null` until resolution completes, during which the owning
|
|
84
|
+
* `<pc-anim>` assigns the engine's placeholder track in its stead.
|
|
85
|
+
*
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
_track: AnimTrack | null = null;
|
|
89
|
+
|
|
90
|
+
async connectedCallback() {
|
|
91
|
+
const generation = ++this._connectionGeneration;
|
|
92
|
+
|
|
93
|
+
const animElement = this.animElement;
|
|
94
|
+
await animElement?.ready();
|
|
95
|
+
|
|
96
|
+
// The element may have been removed (perhaps re-inserted, which runs a callback of its
|
|
97
|
+
// own), or its parent torn down, while we were waiting. A <pc-app> disconnects before
|
|
98
|
+
// its children, so by the time we resume the component can already be gone - see the
|
|
99
|
+
// matching guard in disconnectedCallback below.
|
|
100
|
+
const component = animElement ? animElement.component : null;
|
|
101
|
+
if (generation !== this._connectionGeneration || !animElement || !component) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this._animElement = animElement;
|
|
106
|
+
animElement._registerClip(this);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
disconnectedCallback() {
|
|
110
|
+
// Invalidate any connectedCallback or track resolution still suspended on an await
|
|
111
|
+
this._connectionGeneration++;
|
|
112
|
+
this._loadGeneration++;
|
|
113
|
+
this._detachLoadHandlers();
|
|
114
|
+
|
|
115
|
+
// Uses the cached parent rather than a fresh lookup, since parentElement is already null
|
|
116
|
+
// by now. The component itself is null if the whole <pc-app> is being torn down —
|
|
117
|
+
// parents disconnect first and have already removed the component.
|
|
118
|
+
this._animElement?._unregisterClip(this);
|
|
119
|
+
this._animElement = null;
|
|
120
|
+
this._track = null;
|
|
121
|
+
this._resetReady();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected get animElement(): AnimComponentElement | null {
|
|
125
|
+
const animElement = this.parentElement as AnimComponentElement;
|
|
126
|
+
|
|
127
|
+
if (!(animElement instanceof AnimComponentElement)) {
|
|
128
|
+
const label = this._name ? ` '${this._name}'` : '';
|
|
129
|
+
console.warn(`pc-anim-clip${label} must be a direct child of a pc-anim element`);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return animElement;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private _detachLoadHandlers() {
|
|
137
|
+
this._loadHandle?.off();
|
|
138
|
+
this._loadHandle = null;
|
|
139
|
+
this._errorHandle?.off();
|
|
140
|
+
this._errorHandle = null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Reports a name-validation failure from the owning `<pc-anim>`, once per name value.
|
|
145
|
+
*
|
|
146
|
+
* @param message - The complaint.
|
|
147
|
+
* @internal
|
|
148
|
+
*/
|
|
149
|
+
_markInvalid(message: string) {
|
|
150
|
+
if (this._warnedInvalid) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this._warnedInvalid = true;
|
|
154
|
+
console.warn(message);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Resolves the clip's track from its source and hands it to the owning `<pc-anim>`. Called
|
|
159
|
+
* by the parent whenever the clip is (re)adopted, and again when the source changes; a newer
|
|
160
|
+
* resolution supersedes one still in flight. The element becomes ready once the resolved
|
|
161
|
+
* track is assigned.
|
|
162
|
+
*
|
|
163
|
+
* @param animElement - The owning `<pc-anim>`.
|
|
164
|
+
* @internal
|
|
165
|
+
*/
|
|
166
|
+
async _resolveTrack(animElement: AnimComponentElement) {
|
|
167
|
+
this._animElement = animElement;
|
|
168
|
+
|
|
169
|
+
const generation = ++this._loadGeneration;
|
|
170
|
+
this._detachLoadHandlers();
|
|
171
|
+
|
|
172
|
+
if (this._asset) {
|
|
173
|
+
const asset = useAsset(this._asset);
|
|
174
|
+
if (!asset) {
|
|
175
|
+
this._warnSource(`pc-anim-clip '${this._name}' could not find asset '${this._asset}' - clip not assigned`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (asset.loaded) {
|
|
179
|
+
this._extractTrack(asset, `asset '${this._asset}'`);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Whichever of load/error fires first detaches the other. The generation is
|
|
183
|
+
// re-checked even though a superseded handler is detached: the detach relies on how
|
|
184
|
+
// the engine's event emitter treats removal, while the check holds on its own.
|
|
185
|
+
this._loadHandle = asset.once('load', () => {
|
|
186
|
+
this._detachLoadHandlers();
|
|
187
|
+
if (generation !== this._loadGeneration) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
this._extractTrack(asset, `asset '${this._asset}'`);
|
|
191
|
+
});
|
|
192
|
+
this._errorHandle = asset.once('error', () => {
|
|
193
|
+
this._detachLoadHandlers();
|
|
194
|
+
if (generation !== this._loadGeneration) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
this._warnSource(`pc-anim-clip '${this._name}' - asset '${this._asset}' failed to load - clip not assigned`);
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const model = animElement.parentElement;
|
|
203
|
+
if (!(model instanceof ModelElement)) {
|
|
204
|
+
this._warnSource(`pc-anim-clip '${this._name}' has no asset and no enclosing pc-model - clip not assigned`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await model.ready();
|
|
209
|
+
if (generation !== this._loadGeneration) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const asset = AssetElement.get(model.asset);
|
|
214
|
+
if (!asset?.resource) {
|
|
215
|
+
// The model's load failed; it already reported the error
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this._extractTrack(asset, `model '${model.asset}'`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Complains about the clip's source, once per source value — resolutions re-run on host
|
|
223
|
+
* cycles and model reloads, and must not repeat the complaint.
|
|
224
|
+
*/
|
|
225
|
+
private _warnSource(message: string) {
|
|
226
|
+
if (this._warnedSource === this._asset) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
this._warnedSource = this._asset;
|
|
230
|
+
console.warn(message);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Picks the clip's track out of a loaded source asset: the track named `name`, or a lone
|
|
235
|
+
* track whatever it is named, or the first of several with a warning.
|
|
236
|
+
*
|
|
237
|
+
* @param asset - The loaded source asset.
|
|
238
|
+
* @param source - How warnings name the source.
|
|
239
|
+
*/
|
|
240
|
+
private _extractTrack(asset: Asset, source: string) {
|
|
241
|
+
const label = `pc-anim-clip '${this._name}'`;
|
|
242
|
+
|
|
243
|
+
// Widened: the engine registers an 'animclip' handler but omits the type from the
|
|
244
|
+
// Asset.type union
|
|
245
|
+
const type: string = asset.type;
|
|
246
|
+
|
|
247
|
+
let candidates: unknown[];
|
|
248
|
+
switch (type) {
|
|
249
|
+
case 'container':
|
|
250
|
+
candidates = (asset.resource as ContainerWithAnimations).animations.map(
|
|
251
|
+
(animationAsset) => animationAsset.resource
|
|
252
|
+
);
|
|
253
|
+
break;
|
|
254
|
+
case 'animation':
|
|
255
|
+
candidates = asset.resources;
|
|
256
|
+
break;
|
|
257
|
+
case 'animclip':
|
|
258
|
+
candidates = [asset.resource];
|
|
259
|
+
break;
|
|
260
|
+
default:
|
|
261
|
+
this._warnSource(`${label} - ${source} has type '${asset.type}', expected 'container', 'animation' or 'animclip' - clip not assigned`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// A JSON 'animation' asset parses to the engine's legacy Animation class, which the anim
|
|
266
|
+
// system rejects - only real AnimTracks qualify
|
|
267
|
+
const tracks = candidates.filter((candidate): candidate is AnimTrack => candidate instanceof AnimTrack);
|
|
268
|
+
if (tracks.length === 0) {
|
|
269
|
+
this._warnSource(`${label} - ${source} contains no usable animation track - clip not assigned`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let track = tracks.find((candidate) => candidate.name === this._name);
|
|
274
|
+
if (!track) {
|
|
275
|
+
track = tracks[0];
|
|
276
|
+
if (tracks.length > 1) {
|
|
277
|
+
console.warn(
|
|
278
|
+
`${label} - no track named '${this._name}' in ${source} - using '${track.name}' (available: ${tracks.map((candidate) => candidate.name).join(', ')})`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
this._track = track;
|
|
284
|
+
if (this._animElement?._onClipResolved(this)) {
|
|
285
|
+
this._onReady();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Sets the id of the `pc-asset` supplying the clip's track: a `container`, an `animation`
|
|
291
|
+
* `.glb`, or an `animclip` JSON. When empty, the track comes from the container of the
|
|
292
|
+
* `<pc-model>` enclosing the parent `<pc-anim>`.
|
|
293
|
+
* @param value - The asset id.
|
|
294
|
+
*/
|
|
295
|
+
set asset(value: string) {
|
|
296
|
+
this._asset = value;
|
|
297
|
+
this._warnedSource = null;
|
|
298
|
+
if (this._animElement) {
|
|
299
|
+
this._resetReady();
|
|
300
|
+
this._track = null;
|
|
301
|
+
this._resolveTrack(this._animElement);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Gets the id of the `pc-asset` supplying the clip's track.
|
|
307
|
+
* @returns The asset id.
|
|
308
|
+
*/
|
|
309
|
+
get asset() {
|
|
310
|
+
return this._asset;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Sets whether the clip loops. A non-looping clip holds its last pose when it ends — the
|
|
315
|
+
* engine reports no completion. Defaults to `true`.
|
|
316
|
+
* @param value - Whether the clip loops.
|
|
317
|
+
*/
|
|
318
|
+
set loop(value: boolean) {
|
|
319
|
+
this._loop = value;
|
|
320
|
+
this._animElement?._onClipParamsChanged(this);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Gets whether the clip loops.
|
|
325
|
+
* @returns Whether the clip loops.
|
|
326
|
+
*/
|
|
327
|
+
get loop() {
|
|
328
|
+
return this._loop;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Sets the name of the clip: the name it is played by, and the track looked up in the
|
|
333
|
+
* clip's source. Names must be unique within a `<pc-anim>` and must not contain `.`.
|
|
334
|
+
* @param value - The clip name.
|
|
335
|
+
*/
|
|
336
|
+
set name(value: string) {
|
|
337
|
+
this._name = value;
|
|
338
|
+
this._warnedInvalid = false;
|
|
339
|
+
if (this._animElement) {
|
|
340
|
+
this._resetReady();
|
|
341
|
+
this._animElement._refreshClips();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Gets the name of the clip.
|
|
347
|
+
* @returns The clip name.
|
|
348
|
+
*/
|
|
349
|
+
get name() {
|
|
350
|
+
return this._name;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Sets the playback speed of the clip, where negative values play it backwards. Applies
|
|
355
|
+
* immediately, preserving the playhead. Defaults to 1.
|
|
356
|
+
* @param value - The playback speed.
|
|
357
|
+
*/
|
|
358
|
+
set speed(value: number) {
|
|
359
|
+
this._speed = value;
|
|
360
|
+
this._animElement?._onClipParamsChanged(this);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Gets the playback speed of the clip.
|
|
365
|
+
* @returns The playback speed.
|
|
366
|
+
*/
|
|
367
|
+
get speed() {
|
|
368
|
+
return this._speed;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
static get observedAttributes() {
|
|
372
|
+
return ['asset', 'loop', 'name', 'speed'];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
|
|
376
|
+
switch (name) {
|
|
377
|
+
case 'asset':
|
|
378
|
+
this.asset = newValue ?? '';
|
|
379
|
+
break;
|
|
380
|
+
case 'loop':
|
|
381
|
+
this.loop = parseBool(newValue, true);
|
|
382
|
+
break;
|
|
383
|
+
case 'name':
|
|
384
|
+
this.name = newValue ?? '';
|
|
385
|
+
break;
|
|
386
|
+
case 'speed':
|
|
387
|
+
this.speed = parseNumber(newValue, 1, name);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
customElements.define('pc-anim-clip', AnimClipElement);
|
|
394
|
+
|
|
395
|
+
export { AnimClipElement };
|