@playcanvas/web-components 0.18.0 → 0.19.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 +24 -3
- package/dist/app.d.ts +24 -3
- package/dist/components/anim-clip.d.cts +0 -2
- package/dist/components/anim-clip.d.ts +0 -2
- package/dist/custom-elements.json +72 -2
- 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/pwc.cjs +322 -183
- package/dist/pwc.cjs.map +1 -1
- package/dist/pwc.js +322 -183
- 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 +322 -183
- 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 +24 -4
- package/dist/web-types.json +59 -5
- package/package.json +3 -3
- package/src/app.ts +146 -71
- package/src/components/anim-clip.ts +0 -2
- 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/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,28 @@ 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
|
+
|
|
204
255
|
private _app: AppBase | null = null;
|
|
205
256
|
|
|
206
257
|
private _loadProgress = 0;
|
|
@@ -241,12 +292,12 @@ class AppElement extends AsyncElement {
|
|
|
241
292
|
constructor() {
|
|
242
293
|
super();
|
|
243
294
|
|
|
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.
|
|
295
|
+
// Track listeners for the synthesized events being added to and removed from descendant
|
|
296
|
+
// entities. Registered once here rather than on every boot - the sync no-ops while there
|
|
297
|
+
// is no canvas, and a re-booted element must not stack a second set.
|
|
298
|
+
SYNTHESIZED_EVENTS.forEach((type) => {
|
|
299
|
+
this.addEventListener(`${type}:connect`, () => this._syncCanvasListeners());
|
|
300
|
+
this.addEventListener(`${type}:disconnect`, () => this._syncCanvasListeners());
|
|
250
301
|
});
|
|
251
302
|
}
|
|
252
303
|
|
|
@@ -609,18 +660,14 @@ class AppElement extends AsyncElement {
|
|
|
609
660
|
this._pointerHandlers.pointermove = listener(this._onPointerMove);
|
|
610
661
|
this._pointerHandlers.pointerdown = listener(this._onPointerDown);
|
|
611
662
|
this._pointerHandlers.pointerup = listener(this._onPointerUp);
|
|
663
|
+
this._pointerHandlers.pointercancel = (event: Event) => {
|
|
664
|
+
this._downPicks.delete((event as PointerEvent).pointerId);
|
|
665
|
+
};
|
|
612
666
|
|
|
613
|
-
// Attach canvas
|
|
614
|
-
// created from
|
|
667
|
+
// Attach canvas listeners for element listeners registered before this boot (e.g.
|
|
668
|
+
// handlers created from inline attributes when their elements were first upgraded, or
|
|
615
669
|
// 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
|
-
});
|
|
670
|
+
this._syncCanvasListeners();
|
|
624
671
|
}
|
|
625
672
|
|
|
626
673
|
private _pickerDestroy() {
|
|
@@ -637,15 +684,12 @@ class AppElement extends AsyncElement {
|
|
|
637
684
|
this._pointerHandlers = {
|
|
638
685
|
pointermove: null,
|
|
639
686
|
pointerdown: null,
|
|
640
|
-
pointerup: null
|
|
641
|
-
|
|
642
|
-
this._hasPointerListeners = {
|
|
643
|
-
pointerenter: false,
|
|
644
|
-
pointerleave: false,
|
|
645
|
-
pointerdown: false,
|
|
646
|
-
pointerup: false,
|
|
647
|
-
pointermove: false
|
|
687
|
+
pointerup: null,
|
|
688
|
+
pointercancel: null
|
|
648
689
|
};
|
|
690
|
+
this._downPicks.clear();
|
|
691
|
+
this._clickListened = false;
|
|
692
|
+
this._lastClick = null;
|
|
649
693
|
}
|
|
650
694
|
|
|
651
695
|
/**
|
|
@@ -871,7 +915,17 @@ class AppElement extends AsyncElement {
|
|
|
871
915
|
private async _onPointerDown(event: PointerEvent) {
|
|
872
916
|
if (!this._picker || !this.app) return;
|
|
873
917
|
|
|
874
|
-
const
|
|
918
|
+
const pick = this._pickNode(event);
|
|
919
|
+
|
|
920
|
+
// A click concludes on the matching pointerup, which needs to know what the press
|
|
921
|
+
// picked. Primary button only - the only button a click can conclude from - and only
|
|
922
|
+
// while click is listened for, since it is the click mapping that keeps the pointerup
|
|
923
|
+
// and pointercancel listeners attached to clean the entry up again.
|
|
924
|
+
if (this._clickListened && event.button === 0) {
|
|
925
|
+
this._downPicks.set(event.pointerId, pick);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const node = await pick;
|
|
875
929
|
if (!this._picker) return; // the element disconnected while the pick was in flight
|
|
876
930
|
|
|
877
931
|
const entityElement = this._elementWithListener(node, 'pointerdown');
|
|
@@ -883,6 +937,11 @@ class AppElement extends AsyncElement {
|
|
|
883
937
|
private async _onPointerUp(event: PointerEvent) {
|
|
884
938
|
if (!this._picker || !this.app) return;
|
|
885
939
|
|
|
940
|
+
// The press pick this release may conclude as a click. Claimed synchronously, so the
|
|
941
|
+
// entry is gone before any other event for this pointer can be handled.
|
|
942
|
+
const downPick = this._downPicks.get(event.pointerId);
|
|
943
|
+
this._downPicks.delete(event.pointerId);
|
|
944
|
+
|
|
886
945
|
const node = await this._pickNode(event);
|
|
887
946
|
if (!this._picker) return; // the element disconnected while the pick was in flight
|
|
888
947
|
|
|
@@ -890,47 +949,63 @@ class AppElement extends AsyncElement {
|
|
|
890
949
|
if (entityElement) {
|
|
891
950
|
entityElement.dispatchEvent(new PointerEvent('pointerup', event));
|
|
892
951
|
}
|
|
893
|
-
}
|
|
894
952
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
953
|
+
// A click fires where the DOM fires it: at the nearest common inclusive ancestor of
|
|
954
|
+
// what the press and the release picked, for the primary button only. The press pick
|
|
955
|
+
// may still be in flight - a quick tap resolves in pick order, not event order.
|
|
956
|
+
if (!downPick || event.button !== 0) return;
|
|
957
|
+
const downNode = await downPick;
|
|
958
|
+
if (!this._picker) return;
|
|
959
|
+
|
|
960
|
+
const clickElement = this._elementWithListener(commonAncestor(downNode, node), 'click');
|
|
961
|
+
if (clickElement) {
|
|
962
|
+
const click = new PointerEvent('click', event);
|
|
963
|
+
|
|
964
|
+
// The init above copied pointerup's `detail`, which the Pointer Events spec fixes
|
|
965
|
+
// at 0 - but click is exempt: its detail is the click count, chained here as the
|
|
966
|
+
// platform chains it (same target, within the double-click window). Overridden
|
|
967
|
+
// with defineProperty because an event instance used as an init dict cannot have
|
|
968
|
+
// single fields replaced.
|
|
969
|
+
const time = performance.now();
|
|
970
|
+
const last = this._lastClick;
|
|
971
|
+
const count =
|
|
972
|
+
last && last.element === clickElement && time - last.time <= CLICK_CHAIN_MS ? last.count + 1 : 1;
|
|
973
|
+
this._lastClick = { element: clickElement, time, count };
|
|
974
|
+
Object.defineProperty(click, 'detail', { value: count });
|
|
975
|
+
|
|
976
|
+
clickElement.dispatchEvent(click);
|
|
911
977
|
}
|
|
912
978
|
}
|
|
913
979
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
: this._pointerHandlers[type];
|
|
980
|
+
/**
|
|
981
|
+
* Attaches exactly the canvas listeners the tree's current element listeners need, and
|
|
982
|
+
* detaches the rest. Recomputed whenever a listener connects or disconnects anywhere under
|
|
983
|
+
* this element: several synthesized types can need the same canvas listener (enter, leave
|
|
984
|
+
* and move all ride the move pick; click rides the down/up pair), so one type's removal
|
|
985
|
+
* must not detach a listener another type still uses. Re-attaching an attached listener is
|
|
986
|
+
* a no-op by EventTarget semantics, so no attach state is kept.
|
|
987
|
+
*/
|
|
988
|
+
private _syncCanvasListeners() {
|
|
989
|
+
const canvas = this._canvas;
|
|
990
|
+
if (!canvas) return; // not booted yet: _pickerCreate syncs once the handlers exist
|
|
926
991
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
);
|
|
992
|
+
const elements = Array.from(this.querySelectorAll<EntityBaseElement>('pc-entity, pc-model, pc-node'));
|
|
993
|
+
const needed = new Set<string>();
|
|
994
|
+
for (const type of SYNTHESIZED_EVENTS) {
|
|
995
|
+
if (elements.some((element) => element._hasListeners(type))) {
|
|
996
|
+
canvasEventsFor[type].forEach((canvasType) => needed.add(canvasType));
|
|
932
997
|
}
|
|
933
998
|
}
|
|
999
|
+
this._clickListened = elements.some((element) => element._hasListeners('click'));
|
|
1000
|
+
|
|
1001
|
+
Object.entries(this._pointerHandlers).forEach(([canvasType, handler]) => {
|
|
1002
|
+
if (!handler) return;
|
|
1003
|
+
if (needed.has(canvasType)) {
|
|
1004
|
+
canvas.addEventListener(canvasType, handler);
|
|
1005
|
+
} else {
|
|
1006
|
+
canvas.removeEventListener(canvasType, handler);
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
934
1009
|
}
|
|
935
1010
|
|
|
936
1011
|
/**
|
|
@@ -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
|
/**
|
package/src/entity-base.ts
CHANGED
|
@@ -4,18 +4,29 @@ import type { AppElement } from './app';
|
|
|
4
4
|
import { AsyncElement } from './async-element';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* The
|
|
8
|
-
*
|
|
7
|
+
* The event types the containing `<pc-app>` synthesizes on entity-fronting elements via picking:
|
|
8
|
+
* the `pointer*` events, plus `click` — which concludes a primary-button press and release, and
|
|
9
|
+
* is delivered as a `PointerEvent` exactly as modern browsers deliver native clicks.
|
|
9
10
|
* @internal
|
|
10
11
|
*/
|
|
11
|
-
export const
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
12
|
+
export const SYNTHESIZED_EVENTS = [
|
|
13
|
+
'pointerenter',
|
|
14
|
+
'pointerleave',
|
|
15
|
+
'pointerdown',
|
|
16
|
+
'pointerup',
|
|
17
|
+
'pointermove',
|
|
18
|
+
'click'
|
|
17
19
|
] as const;
|
|
18
20
|
|
|
21
|
+
const SYNTHESIZED_EVENT_SET: ReadonlySet<string> = new Set(SYNTHESIZED_EVENTS);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The attribute names of the inline event handlers (`onpointerdown`, `onclick`, ...), shared by
|
|
25
|
+
* every element that fronts an engine entity. Spread into `observedAttributes` by subclasses.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export const EVENT_ATTRIBUTES = SYNTHESIZED_EVENTS.map((type) => `on${type}`);
|
|
29
|
+
|
|
19
30
|
/**
|
|
20
31
|
* The base class for elements that front an engine {@link Entity}: `<pc-entity>` and
|
|
21
32
|
* `<pc-model>`, which create one, and `<pc-node>`, which binds to one inside a model's
|
|
@@ -34,12 +45,13 @@ class EntityBaseElement extends AsyncElement {
|
|
|
34
45
|
protected _appElement: AppElement | null = null;
|
|
35
46
|
|
|
36
47
|
/**
|
|
37
|
-
* The
|
|
48
|
+
* The event listeners registered on the element, by type.
|
|
38
49
|
*/
|
|
39
50
|
private _listeners: Record<string, EventListener[]> = {};
|
|
40
51
|
|
|
41
52
|
/**
|
|
42
|
-
* The event types for which an inline
|
|
53
|
+
* The event types for which an inline handler attribute (`onpointerdown`, `onclick`, ...)
|
|
54
|
+
* is currently present.
|
|
43
55
|
*/
|
|
44
56
|
private _inlineHandlerTypes = new Set<string>();
|
|
45
57
|
|
|
@@ -75,7 +87,7 @@ class EntityBaseElement extends AsyncElement {
|
|
|
75
87
|
}
|
|
76
88
|
|
|
77
89
|
/**
|
|
78
|
-
* Tracks whether an inline
|
|
90
|
+
* Tracks whether an inline handler attribute is present. The browser itself compiles and
|
|
79
91
|
* runs these attributes — they are standard `GlobalEventHandlers`, so setting one replaces
|
|
80
92
|
* the previous handler and removing it removes the handler, exactly like `onclick` on any
|
|
81
93
|
* HTML element. But because they bypass {@link EventTarget.addEventListener}, the connect/disconnect
|
|
@@ -105,7 +117,7 @@ class EntityBaseElement extends AsyncElement {
|
|
|
105
117
|
}
|
|
106
118
|
this._listeners[type].push(listener);
|
|
107
119
|
super.addEventListener(type, listener, options);
|
|
108
|
-
if (
|
|
120
|
+
if (SYNTHESIZED_EVENT_SET.has(type)) {
|
|
109
121
|
this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true }));
|
|
110
122
|
}
|
|
111
123
|
}
|
|
@@ -115,15 +127,15 @@ class EntityBaseElement extends AsyncElement {
|
|
|
115
127
|
this._listeners[type] = this._listeners[type].filter((l) => l !== listener);
|
|
116
128
|
}
|
|
117
129
|
super.removeEventListener(type, listener, options);
|
|
118
|
-
if (
|
|
130
|
+
if (SYNTHESIZED_EVENT_SET.has(type)) {
|
|
119
131
|
this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
|
|
120
132
|
}
|
|
121
133
|
}
|
|
122
134
|
|
|
123
135
|
/**
|
|
124
136
|
* Whether the element has a listener for an event type, registered either with
|
|
125
|
-
* {@link EventTarget.addEventListener} or with the matching inline
|
|
126
|
-
* containing `<pc-app>` element to gate
|
|
137
|
+
* {@link EventTarget.addEventListener} or with the matching inline handler attribute. Read by the
|
|
138
|
+
* containing `<pc-app>` element to gate event synthesis.
|
|
127
139
|
*
|
|
128
140
|
* @param type - The event type.
|
|
129
141
|
* @returns Whether a listener is registered.
|
package/src/entity.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Vec3 } from 'playcanvas';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { EVENT_ATTRIBUTES } from './entity-base';
|
|
4
4
|
import { buildDescendantEntities, EntityOwnerElement } from './entity-owner';
|
|
5
5
|
import { parseBool, parseTags, parseVec3 } from './parse';
|
|
6
6
|
|
|
@@ -12,8 +12,8 @@ import { parseBool, parseTags, parseVec3 } from './parse';
|
|
|
12
12
|
*
|
|
13
13
|
* The pointer events below are dispatched by the containing `<pc-app>` element when the pointer
|
|
14
14
|
* intersects this entity's geometry. They are only generated while the entity has a listener for
|
|
15
|
-
* them, registered either with {@link EventTarget.addEventListener} or with the matching inline
|
|
16
|
-
* attribute.
|
|
15
|
+
* them, registered either with {@link EventTarget.addEventListener} or with the matching inline
|
|
16
|
+
* attribute (`onpointerdown`, `onclick`, ...).
|
|
17
17
|
*
|
|
18
18
|
* @elementSummary The `<pc-entity>` element creates an entity: a named, transformable node of the
|
|
19
19
|
* scene hierarchy, and the host for component elements such as `<pc-camera>`, `<pc-light>` and
|
|
@@ -33,11 +33,17 @@ import { parseBool, parseTags, parseVec3 } from './parse';
|
|
|
33
33
|
* entity.
|
|
34
34
|
* @attribute {string} onpointerup - Script to run when a pointer button is released over the
|
|
35
35
|
* entity.
|
|
36
|
+
* @attribute {string} onclick - Script to run when the entity is clicked: a primary pointer
|
|
37
|
+
* button pressed and then released over it.
|
|
36
38
|
* @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the entity.
|
|
37
39
|
* @fires {PointerEvent} pointerleave - Fired when the pointer moves off the entity.
|
|
38
40
|
* @fires {PointerEvent} pointermove - Fired when the pointer moves over the entity.
|
|
39
41
|
* @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the entity.
|
|
40
42
|
* @fires {PointerEvent} pointerup - Fired when a pointer button is released over the entity.
|
|
43
|
+
* @fires {PointerEvent} click - Fired when a primary pointer button is pressed and then released
|
|
44
|
+
* over the entity. A press and release that picked different entities fires on their nearest
|
|
45
|
+
* common ancestor instead, as in the DOM. `detail` carries the click count, so a double click
|
|
46
|
+
* arrives as a click whose `detail` is 2.
|
|
41
47
|
*/
|
|
42
48
|
class EntityElement extends EntityOwnerElement {
|
|
43
49
|
connectedCallback() {
|
|
@@ -77,7 +83,7 @@ class EntityElement extends EntityOwnerElement {
|
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
static get observedAttributes() {
|
|
80
|
-
return ['enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...
|
|
86
|
+
return ['enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...EVENT_ATTRIBUTES];
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
|
|
@@ -105,6 +111,7 @@ class EntityElement extends EntityOwnerElement {
|
|
|
105
111
|
case 'onpointerdown':
|
|
106
112
|
case 'onpointerup':
|
|
107
113
|
case 'onpointermove':
|
|
114
|
+
case 'onclick':
|
|
108
115
|
this._updateInlineHandler(name, newValue);
|
|
109
116
|
break;
|
|
110
117
|
}
|
package/src/model.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ContainerResource, Entity, EventHandle } from 'playcanvas';
|
|
|
2
2
|
import { Vec3 } from 'playcanvas';
|
|
3
3
|
|
|
4
4
|
import { useAsset } from './asset';
|
|
5
|
-
import {
|
|
5
|
+
import { EVENT_ATTRIBUTES } from './entity-base';
|
|
6
6
|
import { buildDescendantEntities, EntityOwnerElement } from './entity-owner';
|
|
7
7
|
import { parseBool, parseTags, parseVec3 } from './parse';
|
|
8
8
|
|
|
@@ -143,11 +143,17 @@ const formatHierarchy = (root: HierarchyNode, counts: ReadonlyMap<string, number
|
|
|
143
143
|
* model.
|
|
144
144
|
* @attribute {string} onpointerup - Script to run when a pointer button is released over the
|
|
145
145
|
* model.
|
|
146
|
+
* @attribute {string} onclick - Script to run when the model is clicked: a primary pointer
|
|
147
|
+
* button pressed and then released over it.
|
|
146
148
|
* @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the model.
|
|
147
149
|
* @fires {PointerEvent} pointerleave - Fired when the pointer moves off the model.
|
|
148
150
|
* @fires {PointerEvent} pointermove - Fired when the pointer moves over the model.
|
|
149
151
|
* @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the model.
|
|
150
152
|
* @fires {PointerEvent} pointerup - Fired when a pointer button is released over the model.
|
|
153
|
+
* @fires {PointerEvent} click - Fired when a primary pointer button is pressed and then released
|
|
154
|
+
* over the model. A press and release that picked different entities fires on their nearest
|
|
155
|
+
* common ancestor instead, as in the DOM. `detail` carries the click count, so a double click
|
|
156
|
+
* arrives as a click whose `detail` is 2.
|
|
151
157
|
* @fires {Event} load - Fired each time a container asset finishes instantiating, including
|
|
152
158
|
* re-instantiation after `asset` changes. Does not bubble — listen on this element, or use a
|
|
153
159
|
* capture-phase listener on an ancestor.
|
|
@@ -435,7 +441,7 @@ class ModelElement extends EntityOwnerElement {
|
|
|
435
441
|
}
|
|
436
442
|
|
|
437
443
|
static get observedAttributes() {
|
|
438
|
-
return ['asset', 'enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...
|
|
444
|
+
return ['asset', 'enabled', 'name', 'position', 'rotation', 'scale', 'tags', ...EVENT_ATTRIBUTES];
|
|
439
445
|
}
|
|
440
446
|
|
|
441
447
|
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
|
|
@@ -466,6 +472,7 @@ class ModelElement extends EntityOwnerElement {
|
|
|
466
472
|
case 'onpointerdown':
|
|
467
473
|
case 'onpointerup':
|
|
468
474
|
case 'onpointermove':
|
|
475
|
+
case 'onclick':
|
|
469
476
|
this._updateInlineHandler(name, newValue);
|
|
470
477
|
break;
|
|
471
478
|
}
|
package/src/node.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Entity, EventHandle, GraphNode, Material, MeshInstance, Quat, Rend
|
|
|
2
2
|
import { Vec3 } from 'playcanvas';
|
|
3
3
|
|
|
4
4
|
import { ComponentElement } from './components/component';
|
|
5
|
-
import { EntityBaseElement,
|
|
5
|
+
import { EntityBaseElement, EVENT_ATTRIBUTES } from './entity-base';
|
|
6
6
|
import { buildDescendantEntities } from './entity-owner';
|
|
7
7
|
import type { EntityOwnerElement } from './entity-owner';
|
|
8
8
|
import { MaterialElement } from './material';
|
|
@@ -193,11 +193,17 @@ const levenshtein = (a: string, b: string): number => {
|
|
|
193
193
|
* node.
|
|
194
194
|
* @attribute {string} onpointerup - Script to run when a pointer button is released over the
|
|
195
195
|
* node.
|
|
196
|
+
* @attribute {string} onclick - Script to run when the node is clicked: a primary pointer
|
|
197
|
+
* button pressed and then released over it.
|
|
196
198
|
* @fires {PointerEvent} pointerenter - Fired when the pointer moves onto the node.
|
|
197
199
|
* @fires {PointerEvent} pointerleave - Fired when the pointer moves off the node.
|
|
198
200
|
* @fires {PointerEvent} pointermove - Fired when the pointer moves over the node.
|
|
199
201
|
* @fires {PointerEvent} pointerdown - Fired when a pointer button is pressed over the node.
|
|
200
202
|
* @fires {PointerEvent} pointerup - Fired when a pointer button is released over the node.
|
|
203
|
+
* @fires {PointerEvent} click - Fired when a primary pointer button is pressed and then released
|
|
204
|
+
* over the node. A press and release that picked different entities fires on their nearest
|
|
205
|
+
* common ancestor instead, as in the DOM. `detail` carries the click count, so a double click
|
|
206
|
+
* arrives as a click whose `detail` is 2.
|
|
201
207
|
*/
|
|
202
208
|
class NodeElement extends EntityBaseElement {
|
|
203
209
|
private _name = '';
|
|
@@ -933,7 +939,7 @@ class NodeElement extends EntityBaseElement {
|
|
|
933
939
|
'rotation',
|
|
934
940
|
'scale',
|
|
935
941
|
'tags',
|
|
936
|
-
...
|
|
942
|
+
...EVENT_ATTRIBUTES
|
|
937
943
|
];
|
|
938
944
|
}
|
|
939
945
|
|
|
@@ -982,6 +988,7 @@ class NodeElement extends EntityBaseElement {
|
|
|
982
988
|
case 'onpointerdown':
|
|
983
989
|
case 'onpointerup':
|
|
984
990
|
case 'onpointermove':
|
|
991
|
+
case 'onclick':
|
|
985
992
|
this._updateInlineHandler(name, newValue);
|
|
986
993
|
break;
|
|
987
994
|
}
|
package/src/scene.ts
CHANGED
|
@@ -11,9 +11,15 @@ import { parseColor, parseEnum, parseNumber, parseVec3 } from './parse';
|
|
|
11
11
|
* {@link HTMLElement} interface.
|
|
12
12
|
*
|
|
13
13
|
* @elementSummary The `<pc-scene>` element holds the entity hierarchy the application renders,
|
|
14
|
-
* along with the scene-wide fog and gravity settings. Must be a direct child of
|
|
14
|
+
* along with the scene-wide fog, exposure and gravity settings. Must be a direct child of
|
|
15
|
+
* `<pc-app>`.
|
|
15
16
|
*/
|
|
16
17
|
class SceneElement extends AsyncElement {
|
|
18
|
+
/**
|
|
19
|
+
* The exposure of the scene.
|
|
20
|
+
*/
|
|
21
|
+
private _exposure = 1;
|
|
22
|
+
|
|
17
23
|
/**
|
|
18
24
|
* The fog type of the scene.
|
|
19
25
|
*/
|
|
@@ -96,6 +102,8 @@ class SceneElement extends AsyncElement {
|
|
|
96
102
|
|
|
97
103
|
private _updateSceneSettings() {
|
|
98
104
|
if (this._scene) {
|
|
105
|
+
this._scene.exposure = this._exposure;
|
|
106
|
+
|
|
99
107
|
this._scene.fog.type = this._fog;
|
|
100
108
|
this._scene.fog.color = this._fogColor;
|
|
101
109
|
this._scene.fog.density = this._fogDensity;
|
|
@@ -117,6 +125,26 @@ class SceneElement extends AsyncElement {
|
|
|
117
125
|
this.closestApp?.app?.systems.rigidbody?.gravity.copy(value);
|
|
118
126
|
}
|
|
119
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Sets the exposure of the scene, which tweaks the overall brightness of the rendered image.
|
|
130
|
+
* Ignored if the scene is using physical units. Defaults to 1.
|
|
131
|
+
* @param value - The exposure.
|
|
132
|
+
*/
|
|
133
|
+
set exposure(value: number) {
|
|
134
|
+
this._exposure = value;
|
|
135
|
+
if (this.scene) {
|
|
136
|
+
this.scene.exposure = value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Gets the exposure of the scene.
|
|
142
|
+
* @returns The exposure.
|
|
143
|
+
*/
|
|
144
|
+
get exposure() {
|
|
145
|
+
return this._exposure;
|
|
146
|
+
}
|
|
147
|
+
|
|
120
148
|
/**
|
|
121
149
|
* Sets the fog type of the scene. Can be `none`, `linear`, `exp` or `exp2`. Defaults to
|
|
122
150
|
* `none`.
|
|
@@ -233,11 +261,14 @@ class SceneElement extends AsyncElement {
|
|
|
233
261
|
}
|
|
234
262
|
|
|
235
263
|
static get observedAttributes() {
|
|
236
|
-
return ['fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end', 'gravity'];
|
|
264
|
+
return ['exposure', 'fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end', 'gravity'];
|
|
237
265
|
}
|
|
238
266
|
|
|
239
267
|
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
|
|
240
268
|
switch (name) {
|
|
269
|
+
case 'exposure':
|
|
270
|
+
this.exposure = parseNumber(newValue, 1, name);
|
|
271
|
+
break;
|
|
241
272
|
case 'fog':
|
|
242
273
|
this.fog = parseEnum(newValue, ['none', 'linear', 'exp', 'exp2'], 'none', name);
|
|
243
274
|
break;
|