@react-three/fiber 10.0.0-canary.2b511a5 → 10.0.0-canary.aecbafb

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/legacy.mjs CHANGED
@@ -984,6 +984,9 @@ function applyProps(object, props) {
984
984
  else target.set(value);
985
985
  } else {
986
986
  root[key] = value;
987
+ if (key.endsWith("Node") && root.isMaterial) {
988
+ root.needsUpdate = true;
989
+ }
987
990
  if (rootState && rootState.renderer?.outputColorSpace === SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
988
991
  root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
989
992
  root[key].colorSpace = rootState.textureColorSpace;
@@ -1017,38 +1020,60 @@ function applyProps(object, props) {
1017
1020
  return object;
1018
1021
  }
1019
1022
 
1023
+ const DEFAULT_POINTER_ID = 0;
1024
+ const XR_POINTER_ID_START = 1e3;
1025
+ function getPointerState(internal, pointerId) {
1026
+ let state = internal.pointerMap.get(pointerId);
1027
+ if (!state) {
1028
+ state = {
1029
+ hovered: /* @__PURE__ */ new Map(),
1030
+ captured: /* @__PURE__ */ new Map(),
1031
+ initialClick: [0, 0],
1032
+ initialHits: []
1033
+ };
1034
+ internal.pointerMap.set(pointerId, state);
1035
+ }
1036
+ return state;
1037
+ }
1038
+ function getPointerId(event) {
1039
+ return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
1040
+ }
1020
1041
  function makeId(event) {
1021
1042
  return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
1022
1043
  }
1023
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
1024
- const captureData = captures.get(obj);
1044
+ function releaseInternalPointerCapture(internal, obj, pointerId) {
1045
+ const pointerState = internal.pointerMap.get(pointerId);
1046
+ if (!pointerState) return;
1047
+ const captureData = pointerState.captured.get(obj);
1025
1048
  if (captureData) {
1026
- captures.delete(obj);
1027
- if (captures.size === 0) {
1028
- capturedMap.delete(pointerId);
1029
- captureData.target.releasePointerCapture(pointerId);
1030
- }
1049
+ pointerState.captured.delete(obj);
1050
+ captureData.target.releasePointerCapture(pointerId);
1031
1051
  }
1032
1052
  }
1033
1053
  function removeInteractivity(store, object) {
1034
1054
  const { internal } = store.getState();
1035
1055
  internal.interaction = internal.interaction.filter((o) => o !== object);
1036
- internal.initialHits = internal.initialHits.filter((o) => o !== object);
1037
- internal.hovered.forEach((value, key) => {
1038
- if (value.eventObject === object || value.object === object) {
1039
- internal.hovered.delete(key);
1056
+ for (const [pointerId, pointerState] of internal.pointerMap) {
1057
+ pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
1058
+ pointerState.hovered.forEach((value, key) => {
1059
+ if (value.eventObject === object || value.object === object) {
1060
+ pointerState.hovered.delete(key);
1061
+ }
1062
+ });
1063
+ if (pointerState.captured.has(object)) {
1064
+ releaseInternalPointerCapture(internal, object, pointerId);
1040
1065
  }
1041
- });
1042
- internal.capturedMap.forEach((captures, pointerId) => {
1043
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
1044
- });
1066
+ }
1045
1067
  unregisterVisibility(store, object);
1046
1068
  }
1047
1069
  function createEvents(store) {
1048
- function calculateDistance(event) {
1070
+ function calculateDistance(event, pointerId) {
1049
1071
  const { internal } = store.getState();
1050
- const dx = event.offsetX - internal.initialClick[0];
1051
- const dy = event.offsetY - internal.initialClick[1];
1072
+ const pointerState = internal.pointerMap.get(pointerId);
1073
+ if (!pointerState) return 0;
1074
+ const [initialX, initialY] = pointerState.initialClick;
1075
+ const dx = event.offsetX - initialX;
1076
+ const dy = event.offsetY - initialY;
1052
1077
  return Math.round(Math.sqrt(dx * dx + dy * dy));
1053
1078
  }
1054
1079
  function filterPointerEvents(objects) {
@@ -1084,6 +1109,15 @@ function createEvents(store) {
1084
1109
  return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
1085
1110
  }
1086
1111
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
1112
+ const aInteractivePriority = a.object.userData?.interactivePriority;
1113
+ const bInteractivePriority = b.object.userData?.interactivePriority;
1114
+ if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
1115
+ if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
1116
+ if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
1117
+ if (aInteractivePriority !== bInteractivePriority) {
1118
+ return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
1119
+ }
1120
+ }
1087
1121
  const aState = getRootState(a.object);
1088
1122
  const bState = getRootState(b.object);
1089
1123
  const aPriority = aState?.events?.priority ?? 1;
@@ -1105,9 +1139,13 @@ function createEvents(store) {
1105
1139
  eventObject = eventObject.parent;
1106
1140
  }
1107
1141
  }
1108
- if ("pointerId" in event && state.internal.capturedMap.has(event.pointerId)) {
1109
- for (const captureData of state.internal.capturedMap.get(event.pointerId).values()) {
1110
- if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1142
+ if ("pointerId" in event) {
1143
+ const pointerId = event.pointerId;
1144
+ const pointerState = state.internal.pointerMap.get(pointerId);
1145
+ if (pointerState?.captured.size) {
1146
+ for (const captureData of pointerState.captured.values()) {
1147
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1148
+ }
1111
1149
  }
1112
1150
  }
1113
1151
  return intersections;
@@ -1120,27 +1158,25 @@ function createEvents(store) {
1120
1158
  if (state) {
1121
1159
  const { raycaster, pointer, camera, internal } = state;
1122
1160
  const unprojectedPoint = new Vector3(pointer.x, pointer.y, 0).unproject(camera);
1123
- const hasPointerCapture = (id) => internal.capturedMap.get(id)?.has(hit.eventObject) ?? false;
1161
+ const hasPointerCapture = (id) => {
1162
+ const pointerState = internal.pointerMap.get(id);
1163
+ return pointerState?.captured.has(hit.eventObject) ?? false;
1164
+ };
1124
1165
  const setPointerCapture = (id) => {
1125
1166
  const captureData = { intersection: hit, target: event.target };
1126
- if (internal.capturedMap.has(id)) {
1127
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
1128
- } else {
1129
- internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
1130
- }
1167
+ const pointerState = getPointerState(internal, id);
1168
+ pointerState.captured.set(hit.eventObject, captureData);
1131
1169
  event.target.setPointerCapture(id);
1132
1170
  };
1133
1171
  const releasePointerCapture = (id) => {
1134
- const captures = internal.capturedMap.get(id);
1135
- if (captures) {
1136
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
1137
- }
1172
+ releaseInternalPointerCapture(internal, hit.eventObject, id);
1138
1173
  };
1139
1174
  const extractEventProps = {};
1140
1175
  for (const prop in event) {
1141
1176
  const property = event[prop];
1142
1177
  if (typeof property !== "function") extractEventProps[prop] = property;
1143
1178
  }
1179
+ const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
1144
1180
  const raycastEvent = {
1145
1181
  ...hit,
1146
1182
  ...extractEventProps,
@@ -1151,18 +1187,19 @@ function createEvents(store) {
1151
1187
  unprojectedPoint,
1152
1188
  ray: raycaster.ray,
1153
1189
  camera,
1190
+ pointerId: eventPointerId,
1154
1191
  // Hijack stopPropagation, which just sets a flag
1155
1192
  stopPropagation() {
1156
- const capturesForPointer = "pointerId" in event && internal.capturedMap.get(event.pointerId);
1193
+ const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
1157
1194
  if (
1158
1195
  // ...if this pointer hasn't been captured
1159
- !capturesForPointer || // ... or if the hit object is capturing the pointer
1160
- capturesForPointer.has(hit.eventObject)
1196
+ !pointerState?.captured.size || // ... or if the hit object is capturing the pointer
1197
+ pointerState.captured.has(hit.eventObject)
1161
1198
  ) {
1162
1199
  raycastEvent.stopped = localState.stopped = true;
1163
- if (internal.hovered.size && Array.from(internal.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1200
+ if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1164
1201
  const higher = intersections.slice(0, intersections.indexOf(hit));
1165
- cancelPointer([...higher, hit]);
1202
+ cancelPointer([...higher, hit], eventPointerId);
1166
1203
  }
1167
1204
  }
1168
1205
  },
@@ -1178,15 +1215,18 @@ function createEvents(store) {
1178
1215
  }
1179
1216
  return intersections;
1180
1217
  }
1181
- function cancelPointer(intersections) {
1218
+ function cancelPointer(intersections, pointerId) {
1182
1219
  const { internal } = store.getState();
1183
- for (const hoveredObj of internal.hovered.values()) {
1220
+ const pid = pointerId ?? DEFAULT_POINTER_ID;
1221
+ const pointerState = internal.pointerMap.get(pid);
1222
+ if (!pointerState) return;
1223
+ for (const [hoveredId, hoveredObj] of pointerState.hovered) {
1184
1224
  if (!intersections.length || !intersections.find(
1185
1225
  (hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
1186
1226
  )) {
1187
1227
  const eventObject = hoveredObj.eventObject;
1188
1228
  const instance = eventObject.__r3f;
1189
- internal.hovered.delete(makeId(hoveredObj));
1229
+ pointerState.hovered.delete(hoveredId);
1190
1230
  if (instance?.eventCount) {
1191
1231
  const handlers = instance.handlers;
1192
1232
  const data = { ...hoveredObj, intersections };
@@ -1215,41 +1255,118 @@ function createEvents(store) {
1215
1255
  instance?.handlers.onDropMissed?.(event);
1216
1256
  }
1217
1257
  }
1258
+ function cleanupPointer(pointerId) {
1259
+ const { internal } = store.getState();
1260
+ const pointerState = internal.pointerMap.get(pointerId);
1261
+ if (pointerState) {
1262
+ for (const [, hoveredObj] of pointerState.hovered) {
1263
+ const eventObject = hoveredObj.eventObject;
1264
+ const instance = eventObject.__r3f;
1265
+ if (instance?.eventCount) {
1266
+ const handlers = instance.handlers;
1267
+ const data = { ...hoveredObj, intersections: [] };
1268
+ handlers.onPointerOut?.(data);
1269
+ handlers.onPointerLeave?.(data);
1270
+ }
1271
+ }
1272
+ internal.pointerMap.delete(pointerId);
1273
+ }
1274
+ internal.pointerDirty.delete(pointerId);
1275
+ }
1276
+ function processDeferredPointer(event, pointerId) {
1277
+ const state = store.getState();
1278
+ const { onPointerMissed, onDragOverMissed, internal } = state;
1279
+ if (!state.events.enabled) return;
1280
+ const filter = filterPointerEvents;
1281
+ const hits = intersect(event, filter);
1282
+ cancelPointer(hits, pointerId);
1283
+ function onIntersect(data) {
1284
+ const eventObject = data.eventObject;
1285
+ const instance = eventObject.__r3f;
1286
+ if (!instance?.eventCount) return;
1287
+ const handlers = instance.handlers;
1288
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1289
+ const id = makeId(data);
1290
+ const pointerState = getPointerState(internal, pointerId);
1291
+ const hoveredItem = pointerState.hovered.get(id);
1292
+ if (!hoveredItem) {
1293
+ pointerState.hovered.set(id, data);
1294
+ handlers.onPointerOver?.(data);
1295
+ handlers.onPointerEnter?.(data);
1296
+ } else if (hoveredItem.stopped) {
1297
+ data.stopPropagation();
1298
+ }
1299
+ }
1300
+ handlers.onPointerMove?.(data);
1301
+ }
1302
+ handleIntersects(hits, event, 0, onIntersect);
1303
+ }
1218
1304
  function handlePointer(name) {
1219
1305
  switch (name) {
1220
1306
  case "onPointerLeave":
1221
- case "onPointerCancel":
1222
1307
  case "onDragLeave":
1223
1308
  return () => cancelPointer([]);
1309
+ // Global cancel of these events
1310
+ case "onPointerCancel":
1311
+ return (event) => {
1312
+ const pointerId = getPointerId(event);
1313
+ cleanupPointer(pointerId);
1314
+ };
1224
1315
  case "onLostPointerCapture":
1225
1316
  return (event) => {
1226
1317
  const { internal } = store.getState();
1227
- if ("pointerId" in event && internal.capturedMap.has(event.pointerId)) {
1318
+ const pointerId = getPointerId(event);
1319
+ const pointerState = internal.pointerMap.get(pointerId);
1320
+ if (pointerState?.captured.size) {
1228
1321
  requestAnimationFrame(() => {
1229
- if (internal.capturedMap.has(event.pointerId)) {
1230
- internal.capturedMap.delete(event.pointerId);
1231
- cancelPointer([]);
1322
+ const pointerState2 = internal.pointerMap.get(pointerId);
1323
+ if (pointerState2?.captured.size) {
1324
+ pointerState2.captured.clear();
1232
1325
  }
1326
+ cancelPointer([], pointerId);
1233
1327
  });
1234
1328
  }
1235
1329
  };
1236
1330
  }
1237
1331
  return function handleEvent(event) {
1238
1332
  const state = store.getState();
1239
- const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
1333
+ const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
1334
+ const pointerId = getPointerId(event);
1240
1335
  internal.lastEvent.current = event;
1241
- if (!state.events.enabled) return;
1336
+ if (!events.enabled) return;
1242
1337
  const isPointerMove = name === "onPointerMove";
1243
1338
  const isDragOver = name === "onDragOver";
1244
1339
  const isDrop = name === "onDrop";
1245
1340
  const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
1341
+ const isPointerDown = name === "onPointerDown";
1342
+ const isPointerUp = name === "onPointerUp";
1343
+ const isWheel = name === "onWheel";
1344
+ const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
1345
+ if (isPointerMove && canDeferRaycasts) {
1346
+ events.compute?.(event, state);
1347
+ internal.pointerDirty.set(pointerId, event);
1348
+ return;
1349
+ }
1350
+ if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
1351
+ events.compute?.(event, state);
1352
+ internal.pointerDirty.set(pointerId, event);
1353
+ return;
1354
+ }
1355
+ if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
1356
+ const deferredEvent = internal.pointerDirty.get(pointerId);
1357
+ internal.pointerDirty.delete(pointerId);
1358
+ processDeferredPointer(deferredEvent, pointerId);
1359
+ }
1246
1360
  const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
1247
1361
  const hits = intersect(event, filter);
1248
- const delta = isClickEvent ? calculateDistance(event) : 0;
1249
- if (name === "onPointerDown") {
1250
- internal.initialClick = [event.offsetX, event.offsetY];
1251
- internal.initialHits = hits.map((hit) => hit.eventObject);
1252
- }
1362
+ const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
1363
+ if (isPointerDown) {
1364
+ const pointerState2 = getPointerState(internal, pointerId);
1365
+ pointerState2.initialClick = [event.offsetX, event.offsetY];
1366
+ pointerState2.initialHits = hits.map((hit) => hit.eventObject);
1367
+ }
1368
+ const pointerState = internal.pointerMap.get(pointerId);
1369
+ const initialHits = pointerState?.initialHits ?? [];
1253
1370
  if (isClickEvent && !hits.length) {
1254
1371
  if (delta <= 2) {
1255
1372
  pointerMissed(event, internal.interaction);
@@ -1264,7 +1381,9 @@ function createEvents(store) {
1264
1381
  dropMissed(event, internal.interaction);
1265
1382
  if (onDropMissed) onDropMissed(event);
1266
1383
  }
1267
- if (isPointerMove || isDragOver) cancelPointer(hits);
1384
+ if (isPointerMove || isDragOver) {
1385
+ cancelPointer(hits, pointerId);
1386
+ }
1268
1387
  function onIntersect(data) {
1269
1388
  const eventObject = data.eventObject;
1270
1389
  const instance = eventObject.__r3f;
@@ -1273,9 +1392,10 @@ function createEvents(store) {
1273
1392
  if (isPointerMove) {
1274
1393
  if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1275
1394
  const id = makeId(data);
1276
- const hoveredItem = internal.hovered.get(id);
1395
+ const pointerState2 = getPointerState(internal, pointerId);
1396
+ const hoveredItem = pointerState2.hovered.get(id);
1277
1397
  if (!hoveredItem) {
1278
- internal.hovered.set(id, data);
1398
+ pointerState2.hovered.set(id, data);
1279
1399
  handlers.onPointerOver?.(data);
1280
1400
  handlers.onPointerEnter?.(data);
1281
1401
  } else if (hoveredItem.stopped) {
@@ -1285,9 +1405,10 @@ function createEvents(store) {
1285
1405
  handlers.onPointerMove?.(data);
1286
1406
  } else if (isDragOver) {
1287
1407
  const id = makeId(data);
1288
- const hoveredItem = internal.hovered.get(id);
1408
+ const pointerState2 = getPointerState(internal, pointerId);
1409
+ const hoveredItem = pointerState2.hovered.get(id);
1289
1410
  if (!hoveredItem) {
1290
- internal.hovered.set(id, data);
1411
+ pointerState2.hovered.set(id, data);
1291
1412
  handlers.onDragOverEnter?.(data);
1292
1413
  } else if (hoveredItem.stopped) {
1293
1414
  data.stopPropagation();
@@ -1298,18 +1419,18 @@ function createEvents(store) {
1298
1419
  } else {
1299
1420
  const handler = handlers[name];
1300
1421
  if (handler) {
1301
- if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1422
+ if (!isClickEvent || initialHits.includes(eventObject)) {
1302
1423
  pointerMissed(
1303
1424
  event,
1304
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1425
+ internal.interaction.filter((object) => !initialHits.includes(object))
1305
1426
  );
1306
1427
  handler(data);
1307
1428
  }
1308
1429
  } else {
1309
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
1430
+ if (isClickEvent && initialHits.includes(eventObject)) {
1310
1431
  pointerMissed(
1311
1432
  event,
1312
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1433
+ internal.interaction.filter((object) => !initialHits.includes(object))
1313
1434
  );
1314
1435
  }
1315
1436
  }
@@ -1318,7 +1439,15 @@ function createEvents(store) {
1318
1439
  handleIntersects(hits, event, delta, onIntersect);
1319
1440
  };
1320
1441
  }
1321
- return { handlePointer };
1442
+ function flushDeferredPointers() {
1443
+ const { internal, events } = store.getState();
1444
+ if (!events.frameTimedRaycasts) return;
1445
+ for (const [pointerId, event] of internal.pointerDirty) {
1446
+ processDeferredPointer(event, pointerId);
1447
+ }
1448
+ internal.pointerDirty.clear();
1449
+ }
1450
+ return { handlePointer, flushDeferredPointers, processDeferredPointer };
1322
1451
  }
1323
1452
  const DOM_EVENTS = {
1324
1453
  onClick: ["click", false],
@@ -1337,10 +1466,15 @@ const DOM_EVENTS = {
1337
1466
  onLostPointerCapture: ["lostpointercapture", true]
1338
1467
  };
1339
1468
  function createPointerEvents(store) {
1340
- const { handlePointer } = createEvents(store);
1469
+ const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
1470
+ let nextXRPointerId = XR_POINTER_ID_START;
1471
+ const xrPointers = /* @__PURE__ */ new Map();
1341
1472
  return {
1342
1473
  priority: 1,
1343
1474
  enabled: true,
1475
+ frameTimedRaycasts: true,
1476
+ alwaysFireOnScroll: true,
1477
+ updateOnFrame: false,
1344
1478
  compute(event, state) {
1345
1479
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
1346
1480
  state.raycaster.setFromCamera(state.pointer, state.camera);
@@ -1350,9 +1484,30 @@ function createPointerEvents(store) {
1350
1484
  (acc, key) => ({ ...acc, [key]: handlePointer(key) }),
1351
1485
  {}
1352
1486
  ),
1353
- update: () => {
1487
+ update: (pointerId) => {
1354
1488
  const { events, internal } = store.getState();
1355
- if (internal.lastEvent?.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
1489
+ if (!events.handlers) return;
1490
+ if (pointerId !== void 0) {
1491
+ const event = internal.pointerDirty.get(pointerId);
1492
+ if (event) {
1493
+ internal.pointerDirty.delete(pointerId);
1494
+ processDeferredPointer(event, pointerId);
1495
+ } else if (internal.lastEvent?.current) {
1496
+ processDeferredPointer(internal.lastEvent.current, pointerId);
1497
+ }
1498
+ } else {
1499
+ flushDeferredPointers();
1500
+ if (internal.lastEvent?.current) {
1501
+ events.handlers.onPointerMove(internal.lastEvent.current);
1502
+ }
1503
+ }
1504
+ },
1505
+ flush: () => {
1506
+ const { events, internal } = store.getState();
1507
+ flushDeferredPointers();
1508
+ if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
1509
+ events.handlers.onPointerMove(internal.lastEvent.current);
1510
+ }
1356
1511
  },
1357
1512
  connect: (target) => {
1358
1513
  const { set, events } = store.getState();
@@ -1378,6 +1533,32 @@ function createPointerEvents(store) {
1378
1533
  }
1379
1534
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
1380
1535
  }
1536
+ },
1537
+ registerPointer: (config) => {
1538
+ const pointerId = nextXRPointerId++;
1539
+ xrPointers.set(pointerId, config);
1540
+ const { internal } = store.getState();
1541
+ getPointerState(internal, pointerId);
1542
+ return pointerId;
1543
+ },
1544
+ unregisterPointer: (pointerId) => {
1545
+ xrPointers.delete(pointerId);
1546
+ const { internal } = store.getState();
1547
+ const pointerState = internal.pointerMap.get(pointerId);
1548
+ if (pointerState) {
1549
+ for (const [, hoveredObj] of pointerState.hovered) {
1550
+ const eventObject = hoveredObj.eventObject;
1551
+ const instance = eventObject.__r3f;
1552
+ if (instance?.eventCount) {
1553
+ const handlers = instance.handlers;
1554
+ const data = { ...hoveredObj, intersections: [] };
1555
+ handlers.onPointerOut?.(data);
1556
+ handlers.onPointerLeave?.(data);
1557
+ }
1558
+ }
1559
+ internal.pointerMap.delete(pointerId);
1560
+ }
1561
+ internal.pointerDirty.delete(pointerId);
1381
1562
  }
1382
1563
  };
1383
1564
  }
@@ -2508,7 +2689,14 @@ const createStore = (invalidate, advance) => {
2508
2689
  frustum: new Frustum(),
2509
2690
  autoUpdateFrustum: true,
2510
2691
  raycaster: null,
2511
- events: { priority: 1, enabled: true, connected: false },
2692
+ events: {
2693
+ priority: 1,
2694
+ enabled: true,
2695
+ connected: false,
2696
+ frameTimedRaycasts: true,
2697
+ alwaysFireOnScroll: true,
2698
+ updateOnFrame: false
2699
+ },
2512
2700
  scene: null,
2513
2701
  rootScene: null,
2514
2702
  xr: null,
@@ -2599,11 +2787,13 @@ const createStore = (invalidate, advance) => {
2599
2787
  },
2600
2788
  setError: (error) => set(() => ({ error })),
2601
2789
  error: null,
2602
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
2790
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
2603
2791
  uniforms: {},
2604
2792
  nodes: {},
2793
+ buffers: {},
2794
+ gpuStorage: {},
2605
2795
  textures: /* @__PURE__ */ new Map(),
2606
- postProcessing: null,
2796
+ renderPipeline: null,
2607
2797
  passes: {},
2608
2798
  _hmrVersion: 0,
2609
2799
  _sizeImperative: false,
@@ -2612,12 +2802,16 @@ const createStore = (invalidate, advance) => {
2612
2802
  internal: {
2613
2803
  // Events
2614
2804
  interaction: [],
2615
- hovered: /* @__PURE__ */ new Map(),
2616
2805
  subscribers: [],
2806
+ // Per-pointer state (new unified structure)
2807
+ pointerMap: /* @__PURE__ */ new Map(),
2808
+ pointerDirty: /* @__PURE__ */ new Map(),
2809
+ lastEvent: React.createRef(),
2810
+ // Deprecated but kept for backwards compatibility
2811
+ hovered: /* @__PURE__ */ new Map(),
2617
2812
  initialClick: [0, 0],
2618
2813
  initialHits: [],
2619
2814
  capturedMap: /* @__PURE__ */ new Map(),
2620
- lastEvent: React.createRef(),
2621
2815
  // Visibility tracking (onFramed, onOccluded, onVisible)
2622
2816
  visibilityRegistry: /* @__PURE__ */ new Map(),
2623
2817
  // Occlusion system (WebGPU only)
@@ -15227,6 +15421,18 @@ function createRoot(canvas) {
15227
15421
  system: true
15228
15422
  }
15229
15423
  );
15424
+ const unregisterEventsFlush = scheduler.register(
15425
+ () => {
15426
+ const state2 = store.getState();
15427
+ state2.events.flush?.();
15428
+ },
15429
+ {
15430
+ id: `${newRootId}_events`,
15431
+ rootId: newRootId,
15432
+ phase: "input",
15433
+ system: true
15434
+ }
15435
+ );
15230
15436
  const unregisterFrustum = scheduler.register(
15231
15437
  () => {
15232
15438
  const state2 = store.getState();
@@ -15261,7 +15467,7 @@ function createRoot(canvas) {
15261
15467
  const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
15262
15468
  if (userHandlesRender || state2.internal.priority) return;
15263
15469
  try {
15264
- if (state2.postProcessing?.render) state2.postProcessing.render();
15470
+ if (state2.renderPipeline?.render) state2.renderPipeline.render();
15265
15471
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
15266
15472
  } catch (error) {
15267
15473
  state2.setError(error instanceof Error ? error : new Error(String(error)));
@@ -15286,6 +15492,7 @@ function createRoot(canvas) {
15286
15492
  unregisterRoot: () => {
15287
15493
  unregisterRoot();
15288
15494
  unregisterCanvasTarget();
15495
+ unregisterEventsFlush();
15289
15496
  unregisterFrustum();
15290
15497
  unregisterVisibility();
15291
15498
  unregisterRender();
@@ -15451,9 +15658,13 @@ function PortalInner({ state = {}, children, container }) {
15451
15658
  const store = createWithEqualityFn((set, get) => ({ ...rest, set, get }));
15452
15659
  const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
15453
15660
  onMutate(previousRoot.getState());
15454
- previousRoot.subscribe(onMutate);
15455
15661
  return store;
15456
15662
  }, [previousRoot, container]);
15663
+ useIsomorphicLayoutEffect(() => {
15664
+ const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
15665
+ const unsubscribe = previousRoot.subscribe(onMutate);
15666
+ return unsubscribe;
15667
+ }, [previousRoot, usePortalStore]);
15457
15668
  return (
15458
15669
  // @ts-ignore, reconciler types are not maintained
15459
15670
  /* @__PURE__ */ jsx(Fragment, { children: reconciler.createPortal(
@@ -15674,6 +15885,7 @@ function CanvasImpl({
15674
15885
  queueMicrotask(() => {
15675
15886
  const rootEntry = _roots.get(canvas);
15676
15887
  if (rootEntry?.store) {
15888
+ console.log("[R3F] HMR detected \u2014 rebuilding nodes/uniforms");
15677
15889
  rootEntry.store.setState((state) => ({
15678
15890
  nodes: {},
15679
15891
  uniforms: {},
@@ -15685,8 +15897,7 @@ function CanvasImpl({
15685
15897
  if (typeof import.meta !== "undefined" && import.meta.hot) {
15686
15898
  const hot = import.meta.hot;
15687
15899
  hot.on("vite:afterUpdate", handleHMR);
15688
- return () => hot.dispose?.(() => {
15689
- });
15900
+ return () => hot.off?.("vite:afterUpdate", handleHMR);
15690
15901
  }
15691
15902
  if (typeof module !== "undefined" && module.hot) {
15692
15903
  const hot = module.hot;