@playcanvas/web-components 0.18.0 → 0.20.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/app.d.cts +38 -3
- package/dist/app.d.ts +38 -3
- package/dist/components/anim-clip.d.cts +0 -2
- package/dist/components/anim-clip.d.ts +0 -2
- package/dist/components/button-component.d.cts +9 -5
- package/dist/components/button-component.d.ts +9 -5
- package/dist/components/joint-component.d.cts +24 -10
- package/dist/components/joint-component.d.ts +24 -10
- package/dist/components/script-component.d.cts +4 -2
- package/dist/components/script-component.d.ts +4 -2
- package/dist/components/script-instance.d.cts +14 -6
- package/dist/components/script-instance.d.ts +14 -6
- package/dist/components/scroll-view-component.d.cts +24 -12
- package/dist/components/scroll-view-component.d.ts +24 -12
- package/dist/components/scrollbar-component.d.cts +6 -3
- package/dist/components/scrollbar-component.d.ts +6 -3
- package/dist/custom-elements.json +93 -23
- package/dist/entity-base.d.cts +4 -3
- package/dist/entity-base.d.ts +4 -3
- package/dist/entity.d.cts +8 -2
- package/dist/entity.d.ts +8 -2
- package/dist/model.d.cts +6 -0
- package/dist/model.d.ts +6 -0
- package/dist/node.d.cts +6 -0
- package/dist/node.d.ts +6 -0
- package/dist/parse.d.cts +7 -2
- package/dist/parse.d.ts +7 -2
- package/dist/pwc.cjs +706 -289
- package/dist/pwc.cjs.map +1 -1
- package/dist/pwc.js +706 -289
- 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 +706 -289
- package/dist/pwc.mjs.map +1 -1
- package/dist/scene.d.cts +17 -1
- package/dist/scene.d.ts +17 -1
- package/dist/vscode.html-custom-data.json +34 -14
- package/dist/web-types.json +78 -24
- package/package.json +3 -3
- package/src/app.ts +197 -87
- package/src/components/anim-clip.ts +0 -2
- package/src/components/button-component.ts +18 -10
- package/src/components/joint-component.ts +29 -15
- package/src/components/script-component.ts +25 -12
- package/src/components/script-instance.ts +14 -6
- package/src/components/scroll-view-component.ts +49 -29
- package/src/components/scrollbar-component.ts +13 -8
- package/src/entity-base.ts +27 -15
- package/src/entity.ts +11 -4
- package/src/model.ts +9 -2
- package/src/node.ts +9 -2
- package/src/parse.ts +213 -16
- package/src/scene.ts +33 -2
package/src/app.ts
CHANGED
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
|
|
67
67
|
import type { AssetElement } from './asset';
|
|
68
68
|
import { AsyncElement } from './async-element';
|
|
69
|
+
import { SYNTHESIZED_EVENTS } from './entity-base';
|
|
69
70
|
import type { EntityBaseElement } from './entity-base';
|
|
70
71
|
import type { EntityOwnerElement } from './entity-owner';
|
|
71
72
|
import { LoadingBar } from './loading-bar';
|
|
@@ -73,9 +74,6 @@ import type { MaterialElement } from './material';
|
|
|
73
74
|
import { parseBool, parseEnum, parseNumber } from './parse';
|
|
74
75
|
import type { WasmElement } from './wasm';
|
|
75
76
|
|
|
76
|
-
/** The pointer event types the application synthesizes on `<pc-entity>` elements via picking. */
|
|
77
|
-
const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'] as const;
|
|
78
|
-
|
|
79
77
|
/**
|
|
80
78
|
* The event types whose listeners make an element a hover target. Hover resolution walks past
|
|
81
79
|
* elements listening for none of them, so a silent element never swallows an ancestor's
|
|
@@ -83,6 +81,48 @@ const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointeren
|
|
|
83
81
|
*/
|
|
84
82
|
const hoverEventTypes = ['pointerenter', 'pointerleave', 'pointermove'] as const;
|
|
85
83
|
|
|
84
|
+
/**
|
|
85
|
+
* The canvas listeners each synthesized event type is driven by. Enter and leave are derived
|
|
86
|
+
* from move picks. A click is concluded from the down/up pair, with pointercancel discarding a
|
|
87
|
+
* press the browser takes back (for example a touch that becomes a scroll).
|
|
88
|
+
*/
|
|
89
|
+
const canvasEventsFor: Record<(typeof SYNTHESIZED_EVENTS)[number], readonly string[]> = {
|
|
90
|
+
pointermove: ['pointermove'],
|
|
91
|
+
pointerenter: ['pointermove'],
|
|
92
|
+
pointerleave: ['pointermove'],
|
|
93
|
+
pointerdown: ['pointerdown'],
|
|
94
|
+
pointerup: ['pointerup'],
|
|
95
|
+
click: ['pointerdown', 'pointerup', 'pointercancel']
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* How long after a click a further click on the same target still raises the click count that
|
|
100
|
+
* `detail` carries, approximating the platform's double-click time.
|
|
101
|
+
*/
|
|
102
|
+
const CLICK_CHAIN_MS = 500;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Finds the nearest common inclusive ancestor of two picked nodes - the node a click belongs to
|
|
106
|
+
* when the press and the release picked different geometry, exactly as the DOM assigns a click
|
|
107
|
+
* whose down and up have different targets.
|
|
108
|
+
*
|
|
109
|
+
* @param a - The node the press picked, or `null`.
|
|
110
|
+
* @param b - The node the release picked, or `null`.
|
|
111
|
+
* @returns The nearest common inclusive ancestor, or `null` when there is none.
|
|
112
|
+
*/
|
|
113
|
+
const commonAncestor = (a: GraphNode | null, b: GraphNode | null): GraphNode | null => {
|
|
114
|
+
const ancestors = new Set<GraphNode>();
|
|
115
|
+
for (let node = a; node !== null; node = node.parent) {
|
|
116
|
+
ancestors.add(node);
|
|
117
|
+
}
|
|
118
|
+
for (let node = b; node !== null; node = node.parent) {
|
|
119
|
+
if (ancestors.has(node)) {
|
|
120
|
+
return node;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
};
|
|
125
|
+
|
|
86
126
|
/**
|
|
87
127
|
* Gives `pc-app` the sizing contract of a replaced element (`<video>`, `<img>`): a block-level
|
|
88
128
|
* box that the page's CSS sizes, defaulting to the canvas's own 300x150 intrinsic size, with the
|
|
@@ -182,14 +222,6 @@ class AppElement extends AsyncElement {
|
|
|
182
222
|
|
|
183
223
|
private _picker: Picker | null = null;
|
|
184
224
|
|
|
185
|
-
private _hasPointerListeners: Record<string, boolean> = {
|
|
186
|
-
pointerenter: false,
|
|
187
|
-
pointerleave: false,
|
|
188
|
-
pointerdown: false,
|
|
189
|
-
pointerup: false,
|
|
190
|
-
pointermove: false
|
|
191
|
-
};
|
|
192
|
-
|
|
193
225
|
private _hoveredEntity: EntityBaseElement | null = null;
|
|
194
226
|
|
|
195
227
|
// Identifies the newest in-flight hover pick, so out-of-order results can be discarded
|
|
@@ -198,9 +230,35 @@ class AppElement extends AsyncElement {
|
|
|
198
230
|
private _pointerHandlers: Record<string, EventListener | null> = {
|
|
199
231
|
pointermove: null,
|
|
200
232
|
pointerdown: null,
|
|
201
|
-
pointerup: null
|
|
233
|
+
pointerup: null,
|
|
234
|
+
pointercancel: null
|
|
202
235
|
};
|
|
203
236
|
|
|
237
|
+
/**
|
|
238
|
+
* The pick of each pointer's primary-button press, keyed by pointerId and kept while a click
|
|
239
|
+
* may still conclude it. The promise is stored rather than its result, so a release can
|
|
240
|
+
* await a press pick that has not resolved yet. Entries are removed by the matching
|
|
241
|
+
* pointerup or pointercancel, and only ever stored while some element listens for click -
|
|
242
|
+
* which is also what keeps those two canvas listeners attached.
|
|
243
|
+
*/
|
|
244
|
+
private _downPicks = new Map<number, Promise<GraphNode | null>>();
|
|
245
|
+
|
|
246
|
+
/** Whether any element in the tree listens for click. Maintained by _syncCanvasListeners. */
|
|
247
|
+
private _clickListened = false;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The previous click's target, time and count, for chaining successive clicks into the
|
|
251
|
+
* click count that `detail` carries. `null` until a click has fired.
|
|
252
|
+
*/
|
|
253
|
+
private _lastClick: { element: EntityBaseElement; time: number; count: number } | null = null;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Serializes dispatch of the discrete synthesized events (pointerdown, pointerup, click),
|
|
257
|
+
* whose picks resolve in GPU order, not canvas-event order. Replaced on teardown, so a pick
|
|
258
|
+
* that never resolves cannot stall the dispatches of a later boot.
|
|
259
|
+
*/
|
|
260
|
+
private _dispatchChain: Promise<void> = Promise.resolve();
|
|
261
|
+
|
|
204
262
|
private _app: AppBase | null = null;
|
|
205
263
|
|
|
206
264
|
private _loadProgress = 0;
|
|
@@ -241,12 +299,12 @@ class AppElement extends AsyncElement {
|
|
|
241
299
|
constructor() {
|
|
242
300
|
super();
|
|
243
301
|
|
|
244
|
-
// Track
|
|
245
|
-
// Registered once here rather than on every boot - the
|
|
246
|
-
// canvas, and a re-booted element must not stack a second set.
|
|
247
|
-
|
|
248
|
-
this.addEventListener(`${type}:connect`, () => this.
|
|
249
|
-
this.addEventListener(`${type}:disconnect`, () => this.
|
|
302
|
+
// Track listeners for the synthesized events being added to and removed from descendant
|
|
303
|
+
// entities. Registered once here rather than on every boot - the sync no-ops while there
|
|
304
|
+
// is no canvas, and a re-booted element must not stack a second set.
|
|
305
|
+
SYNTHESIZED_EVENTS.forEach((type) => {
|
|
306
|
+
this.addEventListener(`${type}:connect`, () => this._syncCanvasListeners());
|
|
307
|
+
this.addEventListener(`${type}:disconnect`, () => this._syncCanvasListeners());
|
|
250
308
|
});
|
|
251
309
|
}
|
|
252
310
|
|
|
@@ -597,10 +655,9 @@ class AppElement extends AsyncElement {
|
|
|
597
655
|
const { width, height } = this.app!.graphicsDevice;
|
|
598
656
|
this._picker = new Picker(this.app!, width, height);
|
|
599
657
|
|
|
600
|
-
// Create bound handlers but don't attach them yet. The
|
|
601
|
-
//
|
|
602
|
-
|
|
603
|
-
const listener = (handler: (event: PointerEvent) => Promise<void>): EventListener => {
|
|
658
|
+
// Create bound handlers but don't attach them yet. The move handler is async, so it is
|
|
659
|
+
// wrapped to discard the promise - a listener must not return one.
|
|
660
|
+
const listener = (handler: (event: PointerEvent) => void | Promise<void>): EventListener => {
|
|
604
661
|
return (event: Event) => {
|
|
605
662
|
handler.call(this, event as PointerEvent);
|
|
606
663
|
};
|
|
@@ -609,18 +666,14 @@ class AppElement extends AsyncElement {
|
|
|
609
666
|
this._pointerHandlers.pointermove = listener(this._onPointerMove);
|
|
610
667
|
this._pointerHandlers.pointerdown = listener(this._onPointerDown);
|
|
611
668
|
this._pointerHandlers.pointerup = listener(this._onPointerUp);
|
|
669
|
+
this._pointerHandlers.pointercancel = (event: Event) => {
|
|
670
|
+
this._downPicks.delete((event as PointerEvent).pointerId);
|
|
671
|
+
};
|
|
612
672
|
|
|
613
|
-
// Attach canvas
|
|
614
|
-
// created from
|
|
673
|
+
// Attach canvas listeners for element listeners registered before this boot (e.g.
|
|
674
|
+
// handlers created from inline attributes when their elements were first upgraded, or
|
|
615
675
|
// listeners carried over from before a re-boot)
|
|
616
|
-
|
|
617
|
-
const anyListeners = Array.from(
|
|
618
|
-
this.querySelectorAll<EntityBaseElement>('pc-entity, pc-model, pc-node')
|
|
619
|
-
).some((entity) => entity._hasListeners(type));
|
|
620
|
-
if (anyListeners) {
|
|
621
|
-
this._onPointerListenerAdded(type);
|
|
622
|
-
}
|
|
623
|
-
});
|
|
676
|
+
this._syncCanvasListeners();
|
|
624
677
|
}
|
|
625
678
|
|
|
626
679
|
private _pickerDestroy() {
|
|
@@ -637,15 +690,15 @@ class AppElement extends AsyncElement {
|
|
|
637
690
|
this._pointerHandlers = {
|
|
638
691
|
pointermove: null,
|
|
639
692
|
pointerdown: null,
|
|
640
|
-
pointerup: null
|
|
641
|
-
|
|
642
|
-
this._hasPointerListeners = {
|
|
643
|
-
pointerenter: false,
|
|
644
|
-
pointerleave: false,
|
|
645
|
-
pointerdown: false,
|
|
646
|
-
pointerup: false,
|
|
647
|
-
pointermove: false
|
|
693
|
+
pointerup: null,
|
|
694
|
+
pointercancel: null
|
|
648
695
|
};
|
|
696
|
+
this._downPicks.clear();
|
|
697
|
+
this._clickListened = false;
|
|
698
|
+
this._lastClick = null;
|
|
699
|
+
|
|
700
|
+
// Replace the chain: a pick that never resolves must not stall a later boot's dispatches
|
|
701
|
+
this._dispatchChain = Promise.resolve();
|
|
649
702
|
}
|
|
650
703
|
|
|
651
704
|
/**
|
|
@@ -868,69 +921,126 @@ class AppElement extends AsyncElement {
|
|
|
868
921
|
}
|
|
869
922
|
}
|
|
870
923
|
|
|
871
|
-
|
|
924
|
+
/**
|
|
925
|
+
* Appends a dispatch step to {@link _dispatchChain}. Must be called synchronously from the
|
|
926
|
+
* canvas event handler - the order of appends is what carries canvas-event order. A step
|
|
927
|
+
* that rejects is reported and released, so the steps queued behind it still dispatch.
|
|
928
|
+
*
|
|
929
|
+
* @param step - The dispatch work to run once every earlier step has finished.
|
|
930
|
+
*/
|
|
931
|
+
private _chainDispatch(step: () => Promise<void>) {
|
|
932
|
+
this._dispatchChain = this._dispatchChain.then(step).catch((error) => {
|
|
933
|
+
console.error(error);
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
private _onPointerDown(event: PointerEvent) {
|
|
872
938
|
if (!this._picker || !this.app) return;
|
|
873
939
|
|
|
874
|
-
|
|
875
|
-
|
|
940
|
+
// Picks stay concurrent - only the dispatch of the results is serialized
|
|
941
|
+
const pick = this._pickNode(event);
|
|
876
942
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
943
|
+
// A click concludes on the matching pointerup, which needs to know what the press
|
|
944
|
+
// picked. Primary button only - the only button a click can conclude from - and only
|
|
945
|
+
// while click is listened for, since it is the click mapping that keeps the pointerup
|
|
946
|
+
// and pointercancel listeners attached to clean the entry up again.
|
|
947
|
+
if (this._clickListened && event.button === 0) {
|
|
948
|
+
this._downPicks.set(event.pointerId, pick);
|
|
880
949
|
}
|
|
950
|
+
|
|
951
|
+
this._chainDispatch(async () => {
|
|
952
|
+
const node = await pick;
|
|
953
|
+
if (!this._picker) return; // the element disconnected while the pick was in flight
|
|
954
|
+
|
|
955
|
+
const entityElement = this._elementWithListener(node, 'pointerdown');
|
|
956
|
+
if (entityElement) {
|
|
957
|
+
entityElement.dispatchEvent(new PointerEvent('pointerdown', event));
|
|
958
|
+
}
|
|
959
|
+
});
|
|
881
960
|
}
|
|
882
961
|
|
|
883
|
-
private
|
|
962
|
+
private _onPointerUp(event: PointerEvent) {
|
|
884
963
|
if (!this._picker || !this.app) return;
|
|
885
964
|
|
|
886
|
-
|
|
887
|
-
|
|
965
|
+
// The press pick this release may conclude as a click. Claimed synchronously, so the
|
|
966
|
+
// entry is gone before any other event for this pointer can be handled.
|
|
967
|
+
const downPick = this._downPicks.get(event.pointerId);
|
|
968
|
+
this._downPicks.delete(event.pointerId);
|
|
888
969
|
|
|
889
|
-
const
|
|
890
|
-
if (entityElement) {
|
|
891
|
-
entityElement.dispatchEvent(new PointerEvent('pointerup', event));
|
|
892
|
-
}
|
|
893
|
-
}
|
|
970
|
+
const pick = this._pickNode(event);
|
|
894
971
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
this.
|
|
898
|
-
|
|
899
|
-
// For enter/leave events, we need the move handler
|
|
900
|
-
const handler =
|
|
901
|
-
type === 'pointerenter' || type === 'pointerleave'
|
|
902
|
-
? this._pointerHandlers.pointermove
|
|
903
|
-
: this._pointerHandlers[type];
|
|
904
|
-
|
|
905
|
-
if (handler) {
|
|
906
|
-
this._canvas.addEventListener(
|
|
907
|
-
type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type,
|
|
908
|
-
handler
|
|
909
|
-
);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
}
|
|
972
|
+
this._chainDispatch(async () => {
|
|
973
|
+
const node = await pick;
|
|
974
|
+
if (!this._picker) return; // the element disconnected while the pick was in flight
|
|
913
975
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
976
|
+
const entityElement = this._elementWithListener(node, 'pointerup');
|
|
977
|
+
if (entityElement) {
|
|
978
|
+
entityElement.dispatchEvent(new PointerEvent('pointerup', event));
|
|
979
|
+
}
|
|
980
|
+
});
|
|
918
981
|
|
|
919
|
-
|
|
920
|
-
|
|
982
|
+
// A click fires where the DOM fires it: at the nearest common inclusive ancestor of
|
|
983
|
+
// what the press and the release picked, for the primary button only. Appended after
|
|
984
|
+
// the release's own step, so it dispatches after the pointerup that concludes it.
|
|
985
|
+
if (!downPick || event.button !== 0) return;
|
|
986
|
+
|
|
987
|
+
this._chainDispatch(async () => {
|
|
988
|
+
// A rejected pick was already reported by the press or release step that awaited it;
|
|
989
|
+
// here it just means no click can conclude.
|
|
990
|
+
const picked = await Promise.all([downPick, pick]).catch(() => null);
|
|
991
|
+
if (!picked || !this._picker) return;
|
|
992
|
+
|
|
993
|
+
const [downNode, upNode] = picked;
|
|
994
|
+
const clickElement = this._elementWithListener(commonAncestor(downNode, upNode), 'click');
|
|
995
|
+
if (clickElement) {
|
|
996
|
+
const click = new PointerEvent('click', event);
|
|
997
|
+
|
|
998
|
+
// The init above copied pointerup's `detail`, which the Pointer Events spec fixes
|
|
999
|
+
// at 0 - but click is exempt: its detail is the click count, chained here as the
|
|
1000
|
+
// platform chains it (same target, within the double-click window). Overridden
|
|
1001
|
+
// with defineProperty because an event instance used as an init dict cannot have
|
|
1002
|
+
// single fields replaced.
|
|
1003
|
+
const time = performance.now();
|
|
1004
|
+
const last = this._lastClick;
|
|
1005
|
+
const count =
|
|
1006
|
+
last && last.element === clickElement && time - last.time <= CLICK_CHAIN_MS ? last.count + 1 : 1;
|
|
1007
|
+
this._lastClick = { element: clickElement, time, count };
|
|
1008
|
+
Object.defineProperty(click, 'detail', { value: count });
|
|
1009
|
+
|
|
1010
|
+
clickElement.dispatchEvent(click);
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
921
1014
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1015
|
+
/**
|
|
1016
|
+
* Attaches exactly the canvas listeners the tree's current element listeners need, and
|
|
1017
|
+
* detaches the rest. Recomputed whenever a listener connects or disconnects anywhere under
|
|
1018
|
+
* this element: several synthesized types can need the same canvas listener (enter, leave
|
|
1019
|
+
* and move all ride the move pick; click rides the down/up pair), so one type's removal
|
|
1020
|
+
* must not detach a listener another type still uses. Re-attaching an attached listener is
|
|
1021
|
+
* a no-op by EventTarget semantics, so no attach state is kept.
|
|
1022
|
+
*/
|
|
1023
|
+
private _syncCanvasListeners() {
|
|
1024
|
+
const canvas = this._canvas;
|
|
1025
|
+
if (!canvas) return; // not booted yet: _pickerCreate syncs once the handlers exist
|
|
926
1026
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
);
|
|
1027
|
+
const elements = Array.from(this.querySelectorAll<EntityBaseElement>('pc-entity, pc-model, pc-node'));
|
|
1028
|
+
const needed = new Set<string>();
|
|
1029
|
+
for (const type of SYNTHESIZED_EVENTS) {
|
|
1030
|
+
if (elements.some((element) => element._hasListeners(type))) {
|
|
1031
|
+
canvasEventsFor[type].forEach((canvasType) => needed.add(canvasType));
|
|
932
1032
|
}
|
|
933
1033
|
}
|
|
1034
|
+
this._clickListened = elements.some((element) => element._hasListeners('click'));
|
|
1035
|
+
|
|
1036
|
+
Object.entries(this._pointerHandlers).forEach(([canvasType, handler]) => {
|
|
1037
|
+
if (!handler) return;
|
|
1038
|
+
if (needed.has(canvasType)) {
|
|
1039
|
+
canvas.addEventListener(canvasType, handler);
|
|
1040
|
+
} else {
|
|
1041
|
+
canvas.removeEventListener(canvasType, handler);
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
934
1044
|
}
|
|
935
1045
|
|
|
936
1046
|
/**
|
|
@@ -25,8 +25,6 @@ import { AnimComponentElement } from './anim-component';
|
|
|
25
25
|
* @elementSummary The `<pc-anim-clip>` element declares one named animation clip on its parent
|
|
26
26
|
* `<pc-anim>`, taken from the `asset` it names or, without one, from the enclosing `<pc-model>`'s
|
|
27
27
|
* own animations. Must be a direct child of `<pc-anim>`.
|
|
28
|
-
*
|
|
29
|
-
* @category Components
|
|
30
28
|
*/
|
|
31
29
|
class AnimClipElement extends AsyncElement {
|
|
32
30
|
/**
|
|
@@ -2,7 +2,7 @@ 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 {
|
|
5
|
+
import { parseBool, parseColor, parseEnum, parseNumber, parseVec4, resolveEntity } from '../parse';
|
|
6
6
|
|
|
7
7
|
import { ComponentElement } from './component';
|
|
8
8
|
|
|
@@ -76,7 +76,9 @@ class ButtonComponentElement extends ComponentElement {
|
|
|
76
76
|
|
|
77
77
|
// The image entity defaults to the button's own entity (which carries the image element)
|
|
78
78
|
// when no explicit reference is provided.
|
|
79
|
-
const imageEntity = this._image
|
|
79
|
+
const imageEntity = this._image
|
|
80
|
+
? resolveEntity(this._image, this, 'image', 'reference ignored')
|
|
81
|
+
: this.closestEntity?.entity;
|
|
80
82
|
if (imageEntity) {
|
|
81
83
|
data.imageEntity = imageEntity;
|
|
82
84
|
}
|
|
@@ -127,22 +129,28 @@ class ButtonComponentElement extends ComponentElement {
|
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
/**
|
|
130
|
-
* Sets the reference (
|
|
131
|
-
* element is used for visual transitions.
|
|
132
|
-
*
|
|
133
|
-
*
|
|
132
|
+
* Sets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
|
|
133
|
+
* selector) to the entity whose image element is used for visual transitions. An exact name
|
|
134
|
+
* resolves against the nearest enclosing entity first, then outward, then the document.
|
|
135
|
+
* Defaults to the button's own entity — inside a `<pc-model>`, that is the model's host
|
|
136
|
+
* entity, so supply an explicit reference to target a UI entity instead. A non-empty
|
|
137
|
+
* reference that does not resolve warns and is ignored.
|
|
134
138
|
* @param value - The image entity reference.
|
|
135
139
|
*/
|
|
136
140
|
set image(value: string) {
|
|
137
141
|
this._image = value;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
142
|
+
if (this.component) {
|
|
143
|
+
const entity = resolveEntity(value, this, 'image', 'reference ignored');
|
|
144
|
+
if (entity) {
|
|
145
|
+
this.component.imageEntity = entity;
|
|
146
|
+
}
|
|
141
147
|
}
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
/**
|
|
145
|
-
* Gets the reference
|
|
151
|
+
* Gets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
|
|
152
|
+
* selector) to the entity whose image element is used for visual transitions, or empty for
|
|
153
|
+
* the button's own entity.
|
|
146
154
|
* @returns The image entity reference.
|
|
147
155
|
*/
|
|
148
156
|
get image() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { JointComponent } from 'playcanvas';
|
|
2
2
|
import { Vec2, Vec3 } from 'playcanvas';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { parseBool, parseEnum, parseNumber, parseVec2, parseVec3, resolveEntity } from '../parse';
|
|
5
5
|
|
|
6
6
|
import { ComponentElement } from './component';
|
|
7
7
|
|
|
@@ -25,7 +25,12 @@ export type MotionMode = 'locked' | 'limited' | 'free';
|
|
|
25
25
|
* primary axis: a hinge rotates about it, a slider translates along it and a ball joint twists
|
|
26
26
|
* about it. The constrained bodies are referenced by `entity-a` and `entity-b`, both of which need
|
|
27
27
|
* a rigid body component; leaving `entity-b` empty constrains `entity-a` to a fixed point in world
|
|
28
|
-
* space.
|
|
28
|
+
* space. A reference can name any entity-fronting element — `<pc-entity>`, `<pc-model>` or
|
|
29
|
+
* `<pc-node>`, so a ragdoll can join a model's own skeleton nodes by name — and a name resolves
|
|
30
|
+
* against the nearest enclosing entity first, then outward through the entity hierarchy, then the
|
|
31
|
+
* document, while a `#` selector resolves document-wide. A `<template>` prefab with one
|
|
32
|
+
* entity-fronting root can therefore wire its joints by name and stay self-contained when cloned.
|
|
33
|
+
* The underlying engine component is in alpha, so its API may change.
|
|
29
34
|
*
|
|
30
35
|
* @elementSummary The `<pc-joint>` element constrains two rigid bodies to each other — a hinged
|
|
31
36
|
* door, a swinging chain, a sliding drawer. Its entity's transform is the joint frame, and
|
|
@@ -210,8 +215,8 @@ class JointComponentElement extends ComponentElement {
|
|
|
210
215
|
breakImpulse: this._breakImpulse,
|
|
211
216
|
enableCollision: this._enableCollision,
|
|
212
217
|
enableLimits: this._enableLimits,
|
|
213
|
-
entityA:
|
|
214
|
-
entityB:
|
|
218
|
+
entityA: resolveEntity(this._entityA, this, 'entity-a', 'constraint not created'),
|
|
219
|
+
entityB: resolveEntity(this._entityB, this, 'entity-b', 'constraint not created'),
|
|
215
220
|
limits: this._limits,
|
|
216
221
|
linearDamping: this._linearDamping,
|
|
217
222
|
linearEquilibrium: this._linearEquilibrium,
|
|
@@ -495,20 +500,24 @@ class JointComponentElement extends ComponentElement {
|
|
|
495
500
|
}
|
|
496
501
|
|
|
497
502
|
/**
|
|
498
|
-
* Sets the reference (
|
|
499
|
-
* the first constrained body.
|
|
500
|
-
*
|
|
503
|
+
* Sets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
|
|
504
|
+
* selector) to the element providing the first constrained body. An exact name resolves
|
|
505
|
+
* against the nearest enclosing entity first, then outward, then the document. The reference
|
|
506
|
+
* resolves when it is set, so an entity created later is picked up by setting the attribute
|
|
507
|
+
* again. A non-empty reference that does not resolve warns, naming which of the two causes it
|
|
508
|
+
* hit.
|
|
501
509
|
* @param value - The first body's entity reference.
|
|
502
510
|
*/
|
|
503
511
|
set entityA(value: string) {
|
|
504
512
|
this._entityA = value;
|
|
505
513
|
if (this.component) {
|
|
506
|
-
this.component.entityA =
|
|
514
|
+
this.component.entityA = resolveEntity(value, this, 'entity-a', 'constraint not created');
|
|
507
515
|
}
|
|
508
516
|
}
|
|
509
517
|
|
|
510
518
|
/**
|
|
511
|
-
* Gets the reference
|
|
519
|
+
* Gets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
|
|
520
|
+
* selector) to the element providing the first constrained body.
|
|
512
521
|
* @returns The first body's entity reference.
|
|
513
522
|
*/
|
|
514
523
|
get entityA() {
|
|
@@ -516,21 +525,26 @@ class JointComponentElement extends ComponentElement {
|
|
|
516
525
|
}
|
|
517
526
|
|
|
518
527
|
/**
|
|
519
|
-
* Sets the reference (
|
|
520
|
-
* the second constrained body, or empty to constrain the
|
|
521
|
-
*
|
|
522
|
-
*
|
|
528
|
+
* Sets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
|
|
529
|
+
* selector) to the element providing the second constrained body, or empty to constrain the
|
|
530
|
+
* first body to a fixed point in world space. An exact name resolves against the nearest
|
|
531
|
+
* enclosing entity first, then outward, then the document. The reference resolves when it is
|
|
532
|
+
* set, so an entity created later is picked up by setting the attribute again. A non-empty
|
|
533
|
+
* reference that does not resolve warns; an empty one is the documented world-space case and
|
|
534
|
+
* stays silent.
|
|
523
535
|
* @param value - The second body's entity reference.
|
|
524
536
|
*/
|
|
525
537
|
set entityB(value: string) {
|
|
526
538
|
this._entityB = value;
|
|
527
539
|
if (this.component) {
|
|
528
|
-
this.component.entityB =
|
|
540
|
+
this.component.entityB = resolveEntity(value, this, 'entity-b', 'constraint not created');
|
|
529
541
|
}
|
|
530
542
|
}
|
|
531
543
|
|
|
532
544
|
/**
|
|
533
|
-
* Gets the reference
|
|
545
|
+
* Gets the reference (a `pc-entity`, `pc-model` or `pc-node` name, or a document-wide `#`
|
|
546
|
+
* selector) to the element providing the second constrained body, or empty for the
|
|
547
|
+
* world-space case.
|
|
534
548
|
* @returns The second body's entity reference.
|
|
535
549
|
*/
|
|
536
550
|
get entityB() {
|
|
@@ -3,7 +3,9 @@ import { Color, Quat, Vec2, Vec3, Vec4 } from 'playcanvas';
|
|
|
3
3
|
|
|
4
4
|
import { useAsset } from '../asset';
|
|
5
5
|
import {
|
|
6
|
+
findEntityElement,
|
|
6
7
|
getEntity,
|
|
8
|
+
idHint,
|
|
7
9
|
parseBool,
|
|
8
10
|
parseColor,
|
|
9
11
|
parseComponents,
|
|
@@ -11,7 +13,8 @@ import {
|
|
|
11
13
|
parseQuat,
|
|
12
14
|
parseVec2,
|
|
13
15
|
parseVec3,
|
|
14
|
-
parseVec4
|
|
16
|
+
parseVec4,
|
|
17
|
+
unresolvedCause
|
|
15
18
|
} from '../parse';
|
|
16
19
|
|
|
17
20
|
import { ComponentElement } from './component';
|
|
@@ -107,10 +110,11 @@ const camelToKebab = (name: string): string => {
|
|
|
107
110
|
|
|
108
111
|
/**
|
|
109
112
|
* A conversion applied to a script attribute value carrying an explicit type prefix. Receives the
|
|
110
|
-
* text after the prefix
|
|
111
|
-
*
|
|
113
|
+
* text after the prefix, the raw value, and the element the value is declared under — which
|
|
114
|
+
* scopes entity references — and returns the raw value (having warned) when it cannot resolve or
|
|
115
|
+
* parse it — callers rely on that identity to tell failure from success.
|
|
112
116
|
*/
|
|
113
|
-
type Conversion = (rest: string, raw: string) => any;
|
|
117
|
+
type Conversion = (rest: string, raw: string, from: Element) => any;
|
|
114
118
|
|
|
115
119
|
/**
|
|
116
120
|
* Resolves an `asset:` prefix to the Asset created by the `pc-asset` element with that id.
|
|
@@ -128,18 +132,25 @@ const assetConversion: Conversion = (rest, raw) => {
|
|
|
128
132
|
};
|
|
129
133
|
|
|
130
134
|
/**
|
|
131
|
-
* Resolves an `entity:` prefix to the Entity backing a `pc-entity`
|
|
132
|
-
*
|
|
135
|
+
* Resolves an `entity:` prefix to the Entity backing a `pc-entity`, `pc-model` or `pc-node`
|
|
136
|
+
* element. The reference is a name — resolved against the nearest enclosing entity first, then
|
|
137
|
+
* outward, then the document — or a document-wide `#` selector. The failure warning names which
|
|
138
|
+
* of the three causes ({@link unresolvedCause}) it hit.
|
|
133
139
|
* @param rest - The entity reference.
|
|
134
140
|
* @param raw - The raw value, returned unchanged when the reference does not resolve.
|
|
141
|
+
* @param from - The element the value is declared under, which scopes the reference.
|
|
135
142
|
* @returns The entity, or `raw`.
|
|
136
143
|
*/
|
|
137
|
-
const entityConversion: Conversion = (rest, raw) => {
|
|
138
|
-
const entity = getEntity(rest);
|
|
144
|
+
const entityConversion: Conversion = (rest, raw, from) => {
|
|
145
|
+
const entity = getEntity(rest, from);
|
|
139
146
|
if (entity) {
|
|
140
147
|
return entity;
|
|
141
148
|
}
|
|
142
|
-
|
|
149
|
+
const element = findEntityElement(rest, from);
|
|
150
|
+
const hint = element ? '' : idHint(rest, 'entity:');
|
|
151
|
+
console.warn(
|
|
152
|
+
`Unable to resolve '${raw}' in script attributes - ${unresolvedCause(element)}.${hint ? ` ${hint}` : ''}`
|
|
153
|
+
);
|
|
143
154
|
return raw;
|
|
144
155
|
};
|
|
145
156
|
|
|
@@ -298,8 +309,10 @@ class ScriptComponentElement extends ComponentElement {
|
|
|
298
309
|
/**
|
|
299
310
|
* Recursively converts raw attribute data into proper PlayCanvas types. Supported conversions:
|
|
300
311
|
* - "asset:id" → the Asset created by the `pc-asset` element with that id
|
|
301
|
-
* - "entity:ref" → the Entity backing a `pc-entity` element. The
|
|
302
|
-
*
|
|
312
|
+
* - "entity:ref" → the Entity backing a `pc-entity`, `pc-model` or `pc-node` element. The
|
|
313
|
+
* reference is a name, resolved against this element's nearest enclosing entity first,
|
|
314
|
+
* then outward, then the document — or a document-wide `#` selector (`entity:#id`). A bare
|
|
315
|
+
* value is always a name, never an id.
|
|
303
316
|
* - "vec2:1 2" → new Vec2(1, 2)
|
|
304
317
|
* - "vec3:1 2 3" → new Vec3(1, 2, 3)
|
|
305
318
|
* - "vec4:1 2 3 4" → new Vec4(1, 2, 3, 4)
|
|
@@ -313,7 +326,7 @@ class ScriptComponentElement extends ComponentElement {
|
|
|
313
326
|
private convertAttributes(item: any): any {
|
|
314
327
|
if (typeof item === 'string') {
|
|
315
328
|
const match = matchConversion(item);
|
|
316
|
-
return match ? match.convert(match.rest, item) : item;
|
|
329
|
+
return match ? match.convert(match.rest, item, this) : item;
|
|
317
330
|
}
|
|
318
331
|
|
|
319
332
|
if (Array.isArray(item)) {
|