angular-three 4.0.0-next.105 → 4.0.0-next.107
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.
|
@@ -468,34 +468,34 @@ function prepare(object, type, instanceState) {
|
|
|
468
468
|
...rest,
|
|
469
469
|
};
|
|
470
470
|
}
|
|
471
|
-
Object.
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (!iS.handlers)
|
|
475
|
-
iS.handlers = {};
|
|
476
|
-
// try to get the previous handler. compound might have one, the THREE object might also have one with the same name
|
|
477
|
-
const previousHandler = iS.handlers[eventName];
|
|
478
|
-
// readjust the callback
|
|
479
|
-
const updatedCallback = (event) => {
|
|
480
|
-
if (previousHandler)
|
|
481
|
-
previousHandler(event);
|
|
482
|
-
callback(event);
|
|
483
|
-
};
|
|
484
|
-
Object.assign(iS.handlers, { [eventName]: updatedCallback });
|
|
485
|
-
// increment the count everytime
|
|
486
|
-
iS.eventCount += 1;
|
|
487
|
-
// clean up the event listener by removing the target from the interaction array
|
|
488
|
-
return () => {
|
|
471
|
+
Object.defineProperties(instance.__ngt__, {
|
|
472
|
+
setPointerEvent: {
|
|
473
|
+
value: (eventName, callback) => {
|
|
489
474
|
const iS = getInstanceState(instance);
|
|
490
|
-
if (iS)
|
|
491
|
-
iS.handlers
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
475
|
+
if (!iS.handlers)
|
|
476
|
+
iS.handlers = {};
|
|
477
|
+
// try to get the previous handler. compound might have one, the THREE object might also have one with the same name
|
|
478
|
+
const previousHandler = iS.handlers[eventName];
|
|
479
|
+
// readjust the callback
|
|
480
|
+
const updatedCallback = (event) => {
|
|
481
|
+
if (previousHandler)
|
|
482
|
+
previousHandler(event);
|
|
483
|
+
callback(event);
|
|
484
|
+
};
|
|
485
|
+
Object.assign(iS.handlers, { [eventName]: updatedCallback });
|
|
486
|
+
// increment the count everytime
|
|
487
|
+
iS.eventCount += 1;
|
|
488
|
+
// clean up the event listener by removing the target from the interaction array
|
|
489
|
+
return () => {
|
|
490
|
+
const iS = getInstanceState(instance);
|
|
491
|
+
if (iS) {
|
|
492
|
+
iS.handlers && delete iS.handlers[eventName];
|
|
493
|
+
iS.eventCount -= 1;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
},
|
|
497
|
+
configurable: true,
|
|
495
498
|
},
|
|
496
|
-
configurable: true,
|
|
497
|
-
});
|
|
498
|
-
Object.defineProperties(instance.__ngt__, {
|
|
499
499
|
addInteraction: {
|
|
500
500
|
value: (store) => {
|
|
501
501
|
if (!store)
|
|
@@ -888,8 +888,12 @@ function createEvents(store) {
|
|
|
888
888
|
const intersections = [];
|
|
889
889
|
// Allow callers to eliminate event objects
|
|
890
890
|
const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
|
|
891
|
-
//
|
|
892
|
-
|
|
891
|
+
// Skip work if there are no event objects
|
|
892
|
+
if (eventsObjects.length === 0)
|
|
893
|
+
return intersections;
|
|
894
|
+
// Reset all raycaster cameras to undefined - use for loop for better performance
|
|
895
|
+
const eventsObjectsLen = eventsObjects.length;
|
|
896
|
+
for (let i = 0; i < eventsObjectsLen; i++) {
|
|
893
897
|
const objectRootState = getInstanceState(eventsObjects[i])?.store?.snapshot;
|
|
894
898
|
if (objectRootState) {
|
|
895
899
|
objectRootState.raycaster.camera = undefined;
|
|
@@ -899,6 +903,8 @@ function createEvents(store) {
|
|
|
899
903
|
// Make sure root-level pointer and ray are set up
|
|
900
904
|
state.events.compute?.(event, store, null);
|
|
901
905
|
}
|
|
906
|
+
// Pre-allocate array to avoid garbage collection
|
|
907
|
+
const raycastResults = [];
|
|
902
908
|
function handleRaycast(obj) {
|
|
903
909
|
const objStore = getInstanceState(obj)?.store;
|
|
904
910
|
const objState = objStore?.snapshot;
|
|
@@ -916,31 +922,40 @@ function createEvents(store) {
|
|
|
916
922
|
return objState.raycaster.camera ? objState.raycaster.intersectObject(obj, true) : [];
|
|
917
923
|
}
|
|
918
924
|
// Collect events
|
|
919
|
-
let
|
|
920
|
-
|
|
921
|
-
.
|
|
922
|
-
|
|
923
|
-
|
|
925
|
+
for (let i = 0; i < eventsObjectsLen; i++) {
|
|
926
|
+
const objResults = handleRaycast(eventsObjects[i]);
|
|
927
|
+
if (objResults.length <= 0)
|
|
928
|
+
continue;
|
|
929
|
+
for (let j = 0; j < objResults.length; j++) {
|
|
930
|
+
raycastResults.push(objResults[j]);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
// Sort by event priority and distance
|
|
934
|
+
raycastResults.sort((a, b) => {
|
|
924
935
|
const aState = getInstanceState(a.object)?.store?.snapshot;
|
|
925
936
|
const bState = getInstanceState(b.object)?.store?.snapshot;
|
|
926
937
|
if (!aState || !bState)
|
|
927
938
|
return a.distance - b.distance;
|
|
928
939
|
return bState.events.priority - aState.events.priority || a.distance - b.distance;
|
|
929
|
-
})
|
|
930
|
-
|
|
931
|
-
|
|
940
|
+
});
|
|
941
|
+
// Filter out duplicates - more efficient than chaining
|
|
942
|
+
let hits = [];
|
|
943
|
+
for (let i = 0; i < raycastResults.length; i++) {
|
|
944
|
+
const item = raycastResults[i];
|
|
932
945
|
const id = makeId(item);
|
|
933
946
|
if (duplicates.has(id))
|
|
934
|
-
|
|
947
|
+
continue;
|
|
935
948
|
duplicates.add(id);
|
|
936
|
-
|
|
937
|
-
}
|
|
949
|
+
hits.push(item);
|
|
950
|
+
}
|
|
938
951
|
// https://github.com/mrdoob/three.js/issues/16031
|
|
939
952
|
// Allow custom userland intersect sort order, this likely only makes sense on the root filter
|
|
940
953
|
if (state.events.filter)
|
|
941
954
|
hits = state.events.filter(hits, store);
|
|
942
955
|
// Bubble up the events, find the event source (eventObject)
|
|
943
|
-
|
|
956
|
+
const hitsLen = hits.length;
|
|
957
|
+
for (let i = 0; i < hitsLen; i++) {
|
|
958
|
+
const hit = hits[i];
|
|
944
959
|
let eventObject = hit.object;
|
|
945
960
|
// bubble event up
|
|
946
961
|
while (eventObject) {
|
|
@@ -951,9 +966,11 @@ function createEvents(store) {
|
|
|
951
966
|
}
|
|
952
967
|
// If the interaction is captured, make all capturing targets part of the intersect.
|
|
953
968
|
if ('pointerId' in event && state.internal.capturedMap.has(event.pointerId)) {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
969
|
+
const captures = state.internal.capturedMap.get(event.pointerId);
|
|
970
|
+
for (const captureData of captures.values()) {
|
|
971
|
+
if (duplicates.has(makeId(captureData.intersection)))
|
|
972
|
+
continue;
|
|
973
|
+
intersections.push(captureData.intersection);
|
|
957
974
|
}
|
|
958
975
|
}
|
|
959
976
|
return intersections;
|
|
@@ -1088,29 +1105,29 @@ function createEvents(store) {
|
|
|
1088
1105
|
}
|
|
1089
1106
|
}
|
|
1090
1107
|
function handlePointer(name) {
|
|
1091
|
-
//
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
};
|
|
1108
|
+
// Handle common cancelation events
|
|
1109
|
+
if (name === 'pointerleave' || name === 'pointercancel') {
|
|
1110
|
+
return () => cancelPointer([]);
|
|
1111
|
+
}
|
|
1112
|
+
if (name === 'lostpointercapture') {
|
|
1113
|
+
return (event) => {
|
|
1114
|
+
const { internal } = store.snapshot;
|
|
1115
|
+
if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
|
|
1116
|
+
// If the object event interface had lostpointercapture, we'd call it here on every
|
|
1117
|
+
// object that's getting removed. We call it on the next frame because lostpointercapture
|
|
1118
|
+
// fires before pointerup. Otherwise pointerUp would never be called if the event didn't
|
|
1119
|
+
// happen in the object it originated from, leaving components in a in-between state.
|
|
1120
|
+
requestAnimationFrame(() => {
|
|
1121
|
+
// Only release if pointer-up didn't do it already
|
|
1122
|
+
if (internal.capturedMap.has(event.pointerId)) {
|
|
1123
|
+
internal.capturedMap.delete(event.pointerId);
|
|
1124
|
+
cancelPointer([]);
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1113
1129
|
}
|
|
1130
|
+
// Cache these values since they're used in the closure
|
|
1114
1131
|
const isPointerMove = name === 'pointermove';
|
|
1115
1132
|
const isClickEvent = name === 'click' || name === 'contextmenu' || name === 'dblclick';
|
|
1116
1133
|
const filter = isPointerMove ? filterPointerEvents : undefined;
|
|
@@ -1119,62 +1136,52 @@ function createEvents(store) {
|
|
|
1119
1136
|
// NOTE: __pointerMissed$ on NgtStore is private subject since we only expose the Observable
|
|
1120
1137
|
const pointerMissed$ = store['__pointerMissed$'];
|
|
1121
1138
|
const internal = store.snapshot.internal;
|
|
1122
|
-
//
|
|
1139
|
+
// Cache the event
|
|
1123
1140
|
internal.lastEvent.nativeElement = event;
|
|
1124
1141
|
// Get fresh intersects
|
|
1125
1142
|
const hits = intersect(event, filter);
|
|
1143
|
+
// Only calculate distance for click events to avoid unnecessary math
|
|
1126
1144
|
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
1127
1145
|
// Save initial coordinates on pointer-down
|
|
1128
1146
|
if (name === 'pointerdown') {
|
|
1129
1147
|
internal.initialClick = [event.offsetX, event.offsetY];
|
|
1130
1148
|
internal.initialHits = hits.map((hit) => hit.eventObject);
|
|
1131
1149
|
}
|
|
1132
|
-
//
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
pointerMissed$.next(event);
|
|
1138
|
-
}
|
|
1150
|
+
// Handle click miss events - early return optimization for better performance
|
|
1151
|
+
if (isClickEvent && hits.length === 0 && delta <= 2) {
|
|
1152
|
+
pointerMissed(event, internal.interaction);
|
|
1153
|
+
pointerMissed$.next(event);
|
|
1154
|
+
return; // Early return if nothing was hit
|
|
1139
1155
|
}
|
|
1140
|
-
// Take care of unhover
|
|
1156
|
+
// Take care of unhover for pointer moves
|
|
1141
1157
|
if (isPointerMove)
|
|
1142
1158
|
cancelPointer(hits);
|
|
1159
|
+
// Define onIntersect handler - locally cache common properties for better performance
|
|
1143
1160
|
function onIntersect(data) {
|
|
1144
1161
|
const eventObject = data.eventObject;
|
|
1145
1162
|
const instance = getInstanceState(eventObject);
|
|
1146
|
-
|
|
1147
|
-
// Check presence of handlers
|
|
1163
|
+
// Early return if no instance or event count
|
|
1148
1164
|
if (!instance?.eventCount)
|
|
1149
1165
|
return;
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
But wouldn't it be better to just replace capturedMap with a single entry?
|
|
1154
|
-
Also, are we OK with straight up making picking up multiple objects impossible?
|
|
1155
|
-
|
|
1156
|
-
const pointerId = (data as ThreeEvent<PointerEvent>).pointerId
|
|
1157
|
-
if (pointerId !== undefined) {
|
|
1158
|
-
const capturedMeshSet = internal.capturedMap.get(pointerId)
|
|
1159
|
-
if (capturedMeshSet) {
|
|
1160
|
-
const captured = capturedMeshSet.get(eventObject)
|
|
1161
|
-
if (captured && captured.localState.stopped) return
|
|
1162
|
-
}
|
|
1163
|
-
}*/
|
|
1166
|
+
const handlers = instance.handlers;
|
|
1167
|
+
if (!handlers)
|
|
1168
|
+
return;
|
|
1164
1169
|
if (isPointerMove) {
|
|
1165
|
-
//
|
|
1166
|
-
|
|
1167
|
-
handlers
|
|
1168
|
-
handlers
|
|
1169
|
-
handlers
|
|
1170
|
-
|
|
1170
|
+
// Handle pointer move events
|
|
1171
|
+
const hasPointerOverHandlers = !!(handlers.pointerover ||
|
|
1172
|
+
handlers.pointerenter ||
|
|
1173
|
+
handlers.pointerout ||
|
|
1174
|
+
handlers.pointerleave);
|
|
1175
|
+
if (hasPointerOverHandlers) {
|
|
1171
1176
|
const id = makeId(data);
|
|
1172
1177
|
const hoveredItem = internal.hovered.get(id);
|
|
1173
1178
|
if (!hoveredItem) {
|
|
1174
1179
|
// If the object wasn't previously hovered, book it and call its handler
|
|
1175
1180
|
internal.hovered.set(id, data);
|
|
1176
|
-
handlers.pointerover
|
|
1177
|
-
|
|
1181
|
+
if (handlers.pointerover)
|
|
1182
|
+
handlers.pointerover(data);
|
|
1183
|
+
if (handlers.pointerenter)
|
|
1184
|
+
handlers.pointerenter(data);
|
|
1178
1185
|
}
|
|
1179
1186
|
else if (hoveredItem.stopped) {
|
|
1180
1187
|
// If the object was previously hovered and stopped, we shouldn't allow other items to proceed
|
|
@@ -1182,30 +1189,40 @@ function createEvents(store) {
|
|
|
1182
1189
|
}
|
|
1183
1190
|
}
|
|
1184
1191
|
// Call mouse move
|
|
1185
|
-
handlers
|
|
1192
|
+
if (handlers.pointermove)
|
|
1193
|
+
handlers.pointermove(data);
|
|
1186
1194
|
}
|
|
1187
1195
|
else {
|
|
1188
1196
|
// All other events ...
|
|
1189
|
-
const handler = handlers
|
|
1197
|
+
const handler = handlers[name];
|
|
1190
1198
|
if (handler) {
|
|
1191
1199
|
// Forward all events back to their respective handlers with the exception of click events,
|
|
1192
1200
|
// which must use the initial target
|
|
1193
1201
|
if (!isClickEvent || internal.initialHits.includes(eventObject)) {
|
|
1194
|
-
//
|
|
1195
|
-
|
|
1202
|
+
// Get objects not in initialHits for pointer missed - avoid creating new arrays if possible
|
|
1203
|
+
const missedObjects = internal.interaction.filter((object) => !internal.initialHits.includes(object));
|
|
1204
|
+
// Call pointerMissed only if we have objects to notify
|
|
1205
|
+
if (missedObjects.length > 0) {
|
|
1206
|
+
pointerMissed(event, missedObjects);
|
|
1207
|
+
}
|
|
1196
1208
|
// Now call the handler
|
|
1197
1209
|
handler(data);
|
|
1198
1210
|
}
|
|
1199
1211
|
}
|
|
1200
|
-
else {
|
|
1212
|
+
else if (isClickEvent && internal.initialHits.includes(eventObject)) {
|
|
1201
1213
|
// Trigger onPointerMissed on all elements that have pointer over/out handlers, but not click and weren't hit
|
|
1202
|
-
|
|
1203
|
-
|
|
1214
|
+
const missedObjects = internal.interaction.filter((object) => !internal.initialHits.includes(object));
|
|
1215
|
+
// Call pointerMissed only if we have objects to notify
|
|
1216
|
+
if (missedObjects.length > 0) {
|
|
1217
|
+
pointerMissed(event, missedObjects);
|
|
1204
1218
|
}
|
|
1205
1219
|
}
|
|
1206
1220
|
}
|
|
1207
1221
|
}
|
|
1208
|
-
|
|
1222
|
+
// Process all intersections
|
|
1223
|
+
if (hits.length > 0) {
|
|
1224
|
+
handleIntersects(hits, event, delta, onIntersect);
|
|
1225
|
+
}
|
|
1209
1226
|
};
|
|
1210
1227
|
}
|
|
1211
1228
|
return { handlePointer };
|
|
@@ -1977,9 +1994,8 @@ class NgtRenderer2 {
|
|
|
1977
1994
|
}
|
|
1978
1995
|
const cleanup = iS.setPointerEvent?.(eventName, callback) || (() => { });
|
|
1979
1996
|
// this means the object has already been attached to the parent and has its store propagated
|
|
1980
|
-
if (iS.store)
|
|
1997
|
+
if (iS.store)
|
|
1981
1998
|
iS.addInteraction?.(iS.store);
|
|
1982
|
-
}
|
|
1983
1999
|
return cleanup;
|
|
1984
2000
|
}
|
|
1985
2001
|
return this.delegateRenderer.listen(target, eventName, callback);
|
|
@@ -2782,7 +2798,6 @@ class NgtPortalContent {
|
|
|
2782
2798
|
constructor() {
|
|
2783
2799
|
const host = inject(ElementRef, { skipSelf: true });
|
|
2784
2800
|
const { element } = inject(ViewContainerRef);
|
|
2785
|
-
const injector = inject(Injector);
|
|
2786
2801
|
const commentNode = element.nativeElement;
|
|
2787
2802
|
const store = injectStore();
|
|
2788
2803
|
commentNode.data = NGT_PORTAL_CONTENT_FLAG;
|
|
@@ -2861,7 +2876,8 @@ class NgtPortalImpl {
|
|
|
2861
2876
|
this.portalViewRef.detectChanges();
|
|
2862
2877
|
return;
|
|
2863
2878
|
}
|
|
2864
|
-
|
|
2879
|
+
const portalViewContext = { injector: this.injector };
|
|
2880
|
+
this.portalViewRef = anchor.createEmbeddedView(content, portalViewContext, portalViewContext);
|
|
2865
2881
|
this.portalViewRef.detectChanges();
|
|
2866
2882
|
this.portalContentRendered.set(true);
|
|
2867
2883
|
});
|