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.defineProperty(instance.__ngt__, 'setPointerEvent', {
472
- value: (eventName, callback) => {
473
- const iS = getInstanceState(instance);
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 && delete iS.handlers[eventName];
492
- iS.eventCount -= 1;
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
- // Reset all raycaster cameras to undefined
892
- for (let i = 0; i < eventsObjects.length; i++) {
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 hits = eventsObjects
920
- // Intersect objects
921
- .flatMap(handleRaycast)
922
- // Sort by event priority and distance
923
- .sort((a, b) => {
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
- // Filter out duplicates
931
- .filter((item) => {
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
- return false;
947
+ continue;
935
948
  duplicates.add(id);
936
- return true;
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
- for (const hit of hits) {
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
- for (const captureData of state.internal.capturedMap.get(event.pointerId).values()) {
955
- if (!duplicates.has(makeId(captureData.intersection)))
956
- intersections.push(captureData.intersection);
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
- // Deal with cancelation
1092
- switch (name) {
1093
- case 'pointerleave':
1094
- case 'pointercancel':
1095
- return () => cancelPointer([]);
1096
- case 'lostpointercapture':
1097
- return (event) => {
1098
- const { internal } = store.snapshot;
1099
- if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
1100
- // If the object event interface had lostpointercapture, we'd call it here on every
1101
- // object that's getting removed. We call it on the next frame because lostpointercapture
1102
- // fires before pointerup. Otherwise pointerUp would never be called if the event didn't
1103
- // happen in the object it originated from, leaving components in a in-between state.
1104
- requestAnimationFrame(() => {
1105
- // Only release if pointer-up didn't do it already
1106
- if (internal.capturedMap.has(event.pointerId)) {
1107
- internal.capturedMap.delete(event.pointerId);
1108
- cancelPointer([]);
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
- // prepareRay(event)
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
- // If a click yields no results, pass it back to the user as a miss
1133
- // Missed events have to come first in order to establish user-land side-effect clean up
1134
- if (isClickEvent && !hits.length) {
1135
- if (delta <= 2) {
1136
- pointerMissed(event, internal.interaction);
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
- const handlers = instance?.handlers;
1147
- // Check presence of handlers
1163
+ // Early return if no instance or event count
1148
1164
  if (!instance?.eventCount)
1149
1165
  return;
1150
- /*
1151
- MAYBE TODO, DELETE IF NOT:
1152
- Check if the object is captured, captured events should not have intersects running in parallel
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
- // Move event ...
1166
- if (handlers?.pointerover ||
1167
- handlers?.pointerenter ||
1168
- handlers?.pointerout ||
1169
- handlers?.pointerleave) {
1170
- // When enter or out is present take care of hover-state
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?.(data);
1177
- handlers.pointerenter?.(data);
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?.pointermove?.(data);
1192
+ if (handlers.pointermove)
1193
+ handlers.pointermove(data);
1186
1194
  }
1187
1195
  else {
1188
1196
  // All other events ...
1189
- const handler = handlers?.[name];
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
- // Missed events have to come first
1195
- pointerMissed(event, internal.interaction.filter((object) => !internal.initialHits.includes(object)));
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
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
1203
- pointerMissed(event, internal.interaction.filter((object) => !internal.initialHits.includes(object)));
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
- handleIntersects(hits, event, delta, onIntersect);
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
- this.portalViewRef = anchor.createEmbeddedView(content, { injector: this.injector }, { injector: this.injector });
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
  });