@react-three/fiber 10.0.0-canary.604355a → 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/index.mjs CHANGED
@@ -975,6 +975,9 @@ function applyProps(object, props) {
975
975
  else target.set(value);
976
976
  } else {
977
977
  root[key] = value;
978
+ if (key.endsWith("Node") && root.isMaterial) {
979
+ root.needsUpdate = true;
980
+ }
978
981
  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
979
982
  root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
980
983
  root[key].colorSpace = rootState.textureColorSpace;
@@ -1008,38 +1011,60 @@ function applyProps(object, props) {
1008
1011
  return object;
1009
1012
  }
1010
1013
 
1014
+ const DEFAULT_POINTER_ID = 0;
1015
+ const XR_POINTER_ID_START = 1e3;
1016
+ function getPointerState(internal, pointerId) {
1017
+ let state = internal.pointerMap.get(pointerId);
1018
+ if (!state) {
1019
+ state = {
1020
+ hovered: /* @__PURE__ */ new Map(),
1021
+ captured: /* @__PURE__ */ new Map(),
1022
+ initialClick: [0, 0],
1023
+ initialHits: []
1024
+ };
1025
+ internal.pointerMap.set(pointerId, state);
1026
+ }
1027
+ return state;
1028
+ }
1029
+ function getPointerId(event) {
1030
+ return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
1031
+ }
1011
1032
  function makeId(event) {
1012
1033
  return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
1013
1034
  }
1014
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
1015
- const captureData = captures.get(obj);
1035
+ function releaseInternalPointerCapture(internal, obj, pointerId) {
1036
+ const pointerState = internal.pointerMap.get(pointerId);
1037
+ if (!pointerState) return;
1038
+ const captureData = pointerState.captured.get(obj);
1016
1039
  if (captureData) {
1017
- captures.delete(obj);
1018
- if (captures.size === 0) {
1019
- capturedMap.delete(pointerId);
1020
- captureData.target.releasePointerCapture(pointerId);
1021
- }
1040
+ pointerState.captured.delete(obj);
1041
+ captureData.target.releasePointerCapture(pointerId);
1022
1042
  }
1023
1043
  }
1024
1044
  function removeInteractivity(store, object) {
1025
1045
  const { internal } = store.getState();
1026
1046
  internal.interaction = internal.interaction.filter((o) => o !== object);
1027
- internal.initialHits = internal.initialHits.filter((o) => o !== object);
1028
- internal.hovered.forEach((value, key) => {
1029
- if (value.eventObject === object || value.object === object) {
1030
- internal.hovered.delete(key);
1047
+ for (const [pointerId, pointerState] of internal.pointerMap) {
1048
+ pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
1049
+ pointerState.hovered.forEach((value, key) => {
1050
+ if (value.eventObject === object || value.object === object) {
1051
+ pointerState.hovered.delete(key);
1052
+ }
1053
+ });
1054
+ if (pointerState.captured.has(object)) {
1055
+ releaseInternalPointerCapture(internal, object, pointerId);
1031
1056
  }
1032
- });
1033
- internal.capturedMap.forEach((captures, pointerId) => {
1034
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
1035
- });
1057
+ }
1036
1058
  unregisterVisibility(store, object);
1037
1059
  }
1038
1060
  function createEvents(store) {
1039
- function calculateDistance(event) {
1061
+ function calculateDistance(event, pointerId) {
1040
1062
  const { internal } = store.getState();
1041
- const dx = event.offsetX - internal.initialClick[0];
1042
- const dy = event.offsetY - internal.initialClick[1];
1063
+ const pointerState = internal.pointerMap.get(pointerId);
1064
+ if (!pointerState) return 0;
1065
+ const [initialX, initialY] = pointerState.initialClick;
1066
+ const dx = event.offsetX - initialX;
1067
+ const dy = event.offsetY - initialY;
1043
1068
  return Math.round(Math.sqrt(dx * dx + dy * dy));
1044
1069
  }
1045
1070
  function filterPointerEvents(objects) {
@@ -1075,6 +1100,15 @@ function createEvents(store) {
1075
1100
  return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
1076
1101
  }
1077
1102
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
1103
+ const aInteractivePriority = a.object.userData?.interactivePriority;
1104
+ const bInteractivePriority = b.object.userData?.interactivePriority;
1105
+ if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
1106
+ if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
1107
+ if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
1108
+ if (aInteractivePriority !== bInteractivePriority) {
1109
+ return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
1110
+ }
1111
+ }
1078
1112
  const aState = getRootState(a.object);
1079
1113
  const bState = getRootState(b.object);
1080
1114
  const aPriority = aState?.events?.priority ?? 1;
@@ -1096,9 +1130,13 @@ function createEvents(store) {
1096
1130
  eventObject = eventObject.parent;
1097
1131
  }
1098
1132
  }
1099
- if ("pointerId" in event && state.internal.capturedMap.has(event.pointerId)) {
1100
- for (const captureData of state.internal.capturedMap.get(event.pointerId).values()) {
1101
- if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1133
+ if ("pointerId" in event) {
1134
+ const pointerId = event.pointerId;
1135
+ const pointerState = state.internal.pointerMap.get(pointerId);
1136
+ if (pointerState?.captured.size) {
1137
+ for (const captureData of pointerState.captured.values()) {
1138
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1139
+ }
1102
1140
  }
1103
1141
  }
1104
1142
  return intersections;
@@ -1111,27 +1149,25 @@ function createEvents(store) {
1111
1149
  if (state) {
1112
1150
  const { raycaster, pointer, camera, internal } = state;
1113
1151
  const unprojectedPoint = new Vector3(pointer.x, pointer.y, 0).unproject(camera);
1114
- const hasPointerCapture = (id) => internal.capturedMap.get(id)?.has(hit.eventObject) ?? false;
1152
+ const hasPointerCapture = (id) => {
1153
+ const pointerState = internal.pointerMap.get(id);
1154
+ return pointerState?.captured.has(hit.eventObject) ?? false;
1155
+ };
1115
1156
  const setPointerCapture = (id) => {
1116
1157
  const captureData = { intersection: hit, target: event.target };
1117
- if (internal.capturedMap.has(id)) {
1118
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
1119
- } else {
1120
- internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
1121
- }
1158
+ const pointerState = getPointerState(internal, id);
1159
+ pointerState.captured.set(hit.eventObject, captureData);
1122
1160
  event.target.setPointerCapture(id);
1123
1161
  };
1124
1162
  const releasePointerCapture = (id) => {
1125
- const captures = internal.capturedMap.get(id);
1126
- if (captures) {
1127
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
1128
- }
1163
+ releaseInternalPointerCapture(internal, hit.eventObject, id);
1129
1164
  };
1130
1165
  const extractEventProps = {};
1131
1166
  for (const prop in event) {
1132
1167
  const property = event[prop];
1133
1168
  if (typeof property !== "function") extractEventProps[prop] = property;
1134
1169
  }
1170
+ const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
1135
1171
  const raycastEvent = {
1136
1172
  ...hit,
1137
1173
  ...extractEventProps,
@@ -1142,18 +1178,19 @@ function createEvents(store) {
1142
1178
  unprojectedPoint,
1143
1179
  ray: raycaster.ray,
1144
1180
  camera,
1181
+ pointerId: eventPointerId,
1145
1182
  // Hijack stopPropagation, which just sets a flag
1146
1183
  stopPropagation() {
1147
- const capturesForPointer = "pointerId" in event && internal.capturedMap.get(event.pointerId);
1184
+ const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
1148
1185
  if (
1149
1186
  // ...if this pointer hasn't been captured
1150
- !capturesForPointer || // ... or if the hit object is capturing the pointer
1151
- capturesForPointer.has(hit.eventObject)
1187
+ !pointerState?.captured.size || // ... or if the hit object is capturing the pointer
1188
+ pointerState.captured.has(hit.eventObject)
1152
1189
  ) {
1153
1190
  raycastEvent.stopped = localState.stopped = true;
1154
- if (internal.hovered.size && Array.from(internal.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1191
+ if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1155
1192
  const higher = intersections.slice(0, intersections.indexOf(hit));
1156
- cancelPointer([...higher, hit]);
1193
+ cancelPointer([...higher, hit], eventPointerId);
1157
1194
  }
1158
1195
  }
1159
1196
  },
@@ -1169,15 +1206,18 @@ function createEvents(store) {
1169
1206
  }
1170
1207
  return intersections;
1171
1208
  }
1172
- function cancelPointer(intersections) {
1209
+ function cancelPointer(intersections, pointerId) {
1173
1210
  const { internal } = store.getState();
1174
- for (const hoveredObj of internal.hovered.values()) {
1211
+ const pid = pointerId ?? DEFAULT_POINTER_ID;
1212
+ const pointerState = internal.pointerMap.get(pid);
1213
+ if (!pointerState) return;
1214
+ for (const [hoveredId, hoveredObj] of pointerState.hovered) {
1175
1215
  if (!intersections.length || !intersections.find(
1176
1216
  (hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
1177
1217
  )) {
1178
1218
  const eventObject = hoveredObj.eventObject;
1179
1219
  const instance = eventObject.__r3f;
1180
- internal.hovered.delete(makeId(hoveredObj));
1220
+ pointerState.hovered.delete(hoveredId);
1181
1221
  if (instance?.eventCount) {
1182
1222
  const handlers = instance.handlers;
1183
1223
  const data = { ...hoveredObj, intersections };
@@ -1206,41 +1246,118 @@ function createEvents(store) {
1206
1246
  instance?.handlers.onDropMissed?.(event);
1207
1247
  }
1208
1248
  }
1249
+ function cleanupPointer(pointerId) {
1250
+ const { internal } = store.getState();
1251
+ const pointerState = internal.pointerMap.get(pointerId);
1252
+ if (pointerState) {
1253
+ for (const [, hoveredObj] of pointerState.hovered) {
1254
+ const eventObject = hoveredObj.eventObject;
1255
+ const instance = eventObject.__r3f;
1256
+ if (instance?.eventCount) {
1257
+ const handlers = instance.handlers;
1258
+ const data = { ...hoveredObj, intersections: [] };
1259
+ handlers.onPointerOut?.(data);
1260
+ handlers.onPointerLeave?.(data);
1261
+ }
1262
+ }
1263
+ internal.pointerMap.delete(pointerId);
1264
+ }
1265
+ internal.pointerDirty.delete(pointerId);
1266
+ }
1267
+ function processDeferredPointer(event, pointerId) {
1268
+ const state = store.getState();
1269
+ const { onPointerMissed, onDragOverMissed, internal } = state;
1270
+ if (!state.events.enabled) return;
1271
+ const filter = filterPointerEvents;
1272
+ const hits = intersect(event, filter);
1273
+ cancelPointer(hits, pointerId);
1274
+ function onIntersect(data) {
1275
+ const eventObject = data.eventObject;
1276
+ const instance = eventObject.__r3f;
1277
+ if (!instance?.eventCount) return;
1278
+ const handlers = instance.handlers;
1279
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1280
+ const id = makeId(data);
1281
+ const pointerState = getPointerState(internal, pointerId);
1282
+ const hoveredItem = pointerState.hovered.get(id);
1283
+ if (!hoveredItem) {
1284
+ pointerState.hovered.set(id, data);
1285
+ handlers.onPointerOver?.(data);
1286
+ handlers.onPointerEnter?.(data);
1287
+ } else if (hoveredItem.stopped) {
1288
+ data.stopPropagation();
1289
+ }
1290
+ }
1291
+ handlers.onPointerMove?.(data);
1292
+ }
1293
+ handleIntersects(hits, event, 0, onIntersect);
1294
+ }
1209
1295
  function handlePointer(name) {
1210
1296
  switch (name) {
1211
1297
  case "onPointerLeave":
1212
- case "onPointerCancel":
1213
1298
  case "onDragLeave":
1214
1299
  return () => cancelPointer([]);
1300
+ // Global cancel of these events
1301
+ case "onPointerCancel":
1302
+ return (event) => {
1303
+ const pointerId = getPointerId(event);
1304
+ cleanupPointer(pointerId);
1305
+ };
1215
1306
  case "onLostPointerCapture":
1216
1307
  return (event) => {
1217
1308
  const { internal } = store.getState();
1218
- if ("pointerId" in event && internal.capturedMap.has(event.pointerId)) {
1309
+ const pointerId = getPointerId(event);
1310
+ const pointerState = internal.pointerMap.get(pointerId);
1311
+ if (pointerState?.captured.size) {
1219
1312
  requestAnimationFrame(() => {
1220
- if (internal.capturedMap.has(event.pointerId)) {
1221
- internal.capturedMap.delete(event.pointerId);
1222
- cancelPointer([]);
1313
+ const pointerState2 = internal.pointerMap.get(pointerId);
1314
+ if (pointerState2?.captured.size) {
1315
+ pointerState2.captured.clear();
1223
1316
  }
1317
+ cancelPointer([], pointerId);
1224
1318
  });
1225
1319
  }
1226
1320
  };
1227
1321
  }
1228
1322
  return function handleEvent(event) {
1229
1323
  const state = store.getState();
1230
- const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
1324
+ const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
1325
+ const pointerId = getPointerId(event);
1231
1326
  internal.lastEvent.current = event;
1232
- if (!state.events.enabled) return;
1327
+ if (!events.enabled) return;
1233
1328
  const isPointerMove = name === "onPointerMove";
1234
1329
  const isDragOver = name === "onDragOver";
1235
1330
  const isDrop = name === "onDrop";
1236
1331
  const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
1332
+ const isPointerDown = name === "onPointerDown";
1333
+ const isPointerUp = name === "onPointerUp";
1334
+ const isWheel = name === "onWheel";
1335
+ const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
1336
+ if (isPointerMove && canDeferRaycasts) {
1337
+ events.compute?.(event, state);
1338
+ internal.pointerDirty.set(pointerId, event);
1339
+ return;
1340
+ }
1341
+ if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
1342
+ events.compute?.(event, state);
1343
+ internal.pointerDirty.set(pointerId, event);
1344
+ return;
1345
+ }
1346
+ if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
1347
+ const deferredEvent = internal.pointerDirty.get(pointerId);
1348
+ internal.pointerDirty.delete(pointerId);
1349
+ processDeferredPointer(deferredEvent, pointerId);
1350
+ }
1237
1351
  const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
1238
1352
  const hits = intersect(event, filter);
1239
- const delta = isClickEvent ? calculateDistance(event) : 0;
1240
- if (name === "onPointerDown") {
1241
- internal.initialClick = [event.offsetX, event.offsetY];
1242
- internal.initialHits = hits.map((hit) => hit.eventObject);
1243
- }
1353
+ const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
1354
+ if (isPointerDown) {
1355
+ const pointerState2 = getPointerState(internal, pointerId);
1356
+ pointerState2.initialClick = [event.offsetX, event.offsetY];
1357
+ pointerState2.initialHits = hits.map((hit) => hit.eventObject);
1358
+ }
1359
+ const pointerState = internal.pointerMap.get(pointerId);
1360
+ const initialHits = pointerState?.initialHits ?? [];
1244
1361
  if (isClickEvent && !hits.length) {
1245
1362
  if (delta <= 2) {
1246
1363
  pointerMissed(event, internal.interaction);
@@ -1255,7 +1372,9 @@ function createEvents(store) {
1255
1372
  dropMissed(event, internal.interaction);
1256
1373
  if (onDropMissed) onDropMissed(event);
1257
1374
  }
1258
- if (isPointerMove || isDragOver) cancelPointer(hits);
1375
+ if (isPointerMove || isDragOver) {
1376
+ cancelPointer(hits, pointerId);
1377
+ }
1259
1378
  function onIntersect(data) {
1260
1379
  const eventObject = data.eventObject;
1261
1380
  const instance = eventObject.__r3f;
@@ -1264,9 +1383,10 @@ function createEvents(store) {
1264
1383
  if (isPointerMove) {
1265
1384
  if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1266
1385
  const id = makeId(data);
1267
- const hoveredItem = internal.hovered.get(id);
1386
+ const pointerState2 = getPointerState(internal, pointerId);
1387
+ const hoveredItem = pointerState2.hovered.get(id);
1268
1388
  if (!hoveredItem) {
1269
- internal.hovered.set(id, data);
1389
+ pointerState2.hovered.set(id, data);
1270
1390
  handlers.onPointerOver?.(data);
1271
1391
  handlers.onPointerEnter?.(data);
1272
1392
  } else if (hoveredItem.stopped) {
@@ -1276,9 +1396,10 @@ function createEvents(store) {
1276
1396
  handlers.onPointerMove?.(data);
1277
1397
  } else if (isDragOver) {
1278
1398
  const id = makeId(data);
1279
- const hoveredItem = internal.hovered.get(id);
1399
+ const pointerState2 = getPointerState(internal, pointerId);
1400
+ const hoveredItem = pointerState2.hovered.get(id);
1280
1401
  if (!hoveredItem) {
1281
- internal.hovered.set(id, data);
1402
+ pointerState2.hovered.set(id, data);
1282
1403
  handlers.onDragOverEnter?.(data);
1283
1404
  } else if (hoveredItem.stopped) {
1284
1405
  data.stopPropagation();
@@ -1289,18 +1410,18 @@ function createEvents(store) {
1289
1410
  } else {
1290
1411
  const handler = handlers[name];
1291
1412
  if (handler) {
1292
- if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1413
+ if (!isClickEvent || initialHits.includes(eventObject)) {
1293
1414
  pointerMissed(
1294
1415
  event,
1295
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1416
+ internal.interaction.filter((object) => !initialHits.includes(object))
1296
1417
  );
1297
1418
  handler(data);
1298
1419
  }
1299
1420
  } else {
1300
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
1421
+ if (isClickEvent && initialHits.includes(eventObject)) {
1301
1422
  pointerMissed(
1302
1423
  event,
1303
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1424
+ internal.interaction.filter((object) => !initialHits.includes(object))
1304
1425
  );
1305
1426
  }
1306
1427
  }
@@ -1309,7 +1430,15 @@ function createEvents(store) {
1309
1430
  handleIntersects(hits, event, delta, onIntersect);
1310
1431
  };
1311
1432
  }
1312
- return { handlePointer };
1433
+ function flushDeferredPointers() {
1434
+ const { internal, events } = store.getState();
1435
+ if (!events.frameTimedRaycasts) return;
1436
+ for (const [pointerId, event] of internal.pointerDirty) {
1437
+ processDeferredPointer(event, pointerId);
1438
+ }
1439
+ internal.pointerDirty.clear();
1440
+ }
1441
+ return { handlePointer, flushDeferredPointers, processDeferredPointer };
1313
1442
  }
1314
1443
  const DOM_EVENTS = {
1315
1444
  onClick: ["click", false],
@@ -1328,10 +1457,15 @@ const DOM_EVENTS = {
1328
1457
  onLostPointerCapture: ["lostpointercapture", true]
1329
1458
  };
1330
1459
  function createPointerEvents(store) {
1331
- const { handlePointer } = createEvents(store);
1460
+ const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
1461
+ let nextXRPointerId = XR_POINTER_ID_START;
1462
+ const xrPointers = /* @__PURE__ */ new Map();
1332
1463
  return {
1333
1464
  priority: 1,
1334
1465
  enabled: true,
1466
+ frameTimedRaycasts: true,
1467
+ alwaysFireOnScroll: true,
1468
+ updateOnFrame: false,
1335
1469
  compute(event, state) {
1336
1470
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
1337
1471
  state.raycaster.setFromCamera(state.pointer, state.camera);
@@ -1341,9 +1475,30 @@ function createPointerEvents(store) {
1341
1475
  (acc, key) => ({ ...acc, [key]: handlePointer(key) }),
1342
1476
  {}
1343
1477
  ),
1344
- update: () => {
1478
+ update: (pointerId) => {
1479
+ const { events, internal } = store.getState();
1480
+ if (!events.handlers) return;
1481
+ if (pointerId !== void 0) {
1482
+ const event = internal.pointerDirty.get(pointerId);
1483
+ if (event) {
1484
+ internal.pointerDirty.delete(pointerId);
1485
+ processDeferredPointer(event, pointerId);
1486
+ } else if (internal.lastEvent?.current) {
1487
+ processDeferredPointer(internal.lastEvent.current, pointerId);
1488
+ }
1489
+ } else {
1490
+ flushDeferredPointers();
1491
+ if (internal.lastEvent?.current) {
1492
+ events.handlers.onPointerMove(internal.lastEvent.current);
1493
+ }
1494
+ }
1495
+ },
1496
+ flush: () => {
1345
1497
  const { events, internal } = store.getState();
1346
- if (internal.lastEvent?.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
1498
+ flushDeferredPointers();
1499
+ if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
1500
+ events.handlers.onPointerMove(internal.lastEvent.current);
1501
+ }
1347
1502
  },
1348
1503
  connect: (target) => {
1349
1504
  const { set, events } = store.getState();
@@ -1369,6 +1524,32 @@ function createPointerEvents(store) {
1369
1524
  }
1370
1525
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
1371
1526
  }
1527
+ },
1528
+ registerPointer: (config) => {
1529
+ const pointerId = nextXRPointerId++;
1530
+ xrPointers.set(pointerId, config);
1531
+ const { internal } = store.getState();
1532
+ getPointerState(internal, pointerId);
1533
+ return pointerId;
1534
+ },
1535
+ unregisterPointer: (pointerId) => {
1536
+ xrPointers.delete(pointerId);
1537
+ const { internal } = store.getState();
1538
+ const pointerState = internal.pointerMap.get(pointerId);
1539
+ if (pointerState) {
1540
+ for (const [, hoveredObj] of pointerState.hovered) {
1541
+ const eventObject = hoveredObj.eventObject;
1542
+ const instance = eventObject.__r3f;
1543
+ if (instance?.eventCount) {
1544
+ const handlers = instance.handlers;
1545
+ const data = { ...hoveredObj, intersections: [] };
1546
+ handlers.onPointerOut?.(data);
1547
+ handlers.onPointerLeave?.(data);
1548
+ }
1549
+ }
1550
+ internal.pointerMap.delete(pointerId);
1551
+ }
1552
+ internal.pointerDirty.delete(pointerId);
1372
1553
  }
1373
1554
  };
1374
1555
  }
@@ -2499,7 +2680,14 @@ const createStore = (invalidate, advance) => {
2499
2680
  frustum: new Frustum(),
2500
2681
  autoUpdateFrustum: true,
2501
2682
  raycaster: null,
2502
- events: { priority: 1, enabled: true, connected: false },
2683
+ events: {
2684
+ priority: 1,
2685
+ enabled: true,
2686
+ connected: false,
2687
+ frameTimedRaycasts: true,
2688
+ alwaysFireOnScroll: true,
2689
+ updateOnFrame: false
2690
+ },
2503
2691
  scene: null,
2504
2692
  rootScene: null,
2505
2693
  xr: null,
@@ -2590,11 +2778,13 @@ const createStore = (invalidate, advance) => {
2590
2778
  },
2591
2779
  setError: (error) => set(() => ({ error })),
2592
2780
  error: null,
2593
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
2781
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
2594
2782
  uniforms: {},
2595
2783
  nodes: {},
2784
+ buffers: {},
2785
+ gpuStorage: {},
2596
2786
  textures: /* @__PURE__ */ new Map(),
2597
- postProcessing: null,
2787
+ renderPipeline: null,
2598
2788
  passes: {},
2599
2789
  _hmrVersion: 0,
2600
2790
  _sizeImperative: false,
@@ -2603,12 +2793,16 @@ const createStore = (invalidate, advance) => {
2603
2793
  internal: {
2604
2794
  // Events
2605
2795
  interaction: [],
2606
- hovered: /* @__PURE__ */ new Map(),
2607
2796
  subscribers: [],
2797
+ // Per-pointer state (new unified structure)
2798
+ pointerMap: /* @__PURE__ */ new Map(),
2799
+ pointerDirty: /* @__PURE__ */ new Map(),
2800
+ lastEvent: React.createRef(),
2801
+ // Deprecated but kept for backwards compatibility
2802
+ hovered: /* @__PURE__ */ new Map(),
2608
2803
  initialClick: [0, 0],
2609
2804
  initialHits: [],
2610
2805
  capturedMap: /* @__PURE__ */ new Map(),
2611
- lastEvent: React.createRef(),
2612
2806
  // Visibility tracking (onFramed, onOccluded, onVisible)
2613
2807
  visibilityRegistry: /* @__PURE__ */ new Map(),
2614
2808
  // Occlusion system (WebGPU only)
@@ -15060,6 +15254,12 @@ function createRoot(canvas) {
15060
15254
  } else if (!wantsGL && !state.internal.actualRenderer) {
15061
15255
  renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
15062
15256
  if (!renderer.hasInitialized?.()) {
15257
+ const size2 = computeInitialSize(canvas, propsSize);
15258
+ if (size2.width > 0 && size2.height > 0) {
15259
+ const pixelRatio = calculateDpr(dpr);
15260
+ canvas.width = size2.width * pixelRatio;
15261
+ canvas.height = size2.height * pixelRatio;
15262
+ }
15063
15263
  await renderer.init();
15064
15264
  }
15065
15265
  const backend = renderer.backend;
@@ -15261,6 +15461,18 @@ function createRoot(canvas) {
15261
15461
  system: true
15262
15462
  }
15263
15463
  );
15464
+ const unregisterEventsFlush = scheduler.register(
15465
+ () => {
15466
+ const state2 = store.getState();
15467
+ state2.events.flush?.();
15468
+ },
15469
+ {
15470
+ id: `${newRootId}_events`,
15471
+ rootId: newRootId,
15472
+ phase: "input",
15473
+ system: true
15474
+ }
15475
+ );
15264
15476
  const unregisterFrustum = scheduler.register(
15265
15477
  () => {
15266
15478
  const state2 = store.getState();
@@ -15295,7 +15507,7 @@ function createRoot(canvas) {
15295
15507
  const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
15296
15508
  if (userHandlesRender || state2.internal.priority) return;
15297
15509
  try {
15298
- if (state2.postProcessing?.render) state2.postProcessing.render();
15510
+ if (state2.renderPipeline?.render) state2.renderPipeline.render();
15299
15511
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
15300
15512
  } catch (error) {
15301
15513
  state2.setError(error instanceof Error ? error : new Error(String(error)));
@@ -15320,6 +15532,7 @@ function createRoot(canvas) {
15320
15532
  unregisterRoot: () => {
15321
15533
  unregisterRoot();
15322
15534
  unregisterCanvasTarget();
15535
+ unregisterEventsFlush();
15323
15536
  unregisterFrustum();
15324
15537
  unregisterVisibility();
15325
15538
  unregisterRender();
@@ -15485,9 +15698,13 @@ function PortalInner({ state = {}, children, container }) {
15485
15698
  const store = createWithEqualityFn((set, get) => ({ ...rest, set, get }));
15486
15699
  const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
15487
15700
  onMutate(previousRoot.getState());
15488
- previousRoot.subscribe(onMutate);
15489
15701
  return store;
15490
15702
  }, [previousRoot, container]);
15703
+ useIsomorphicLayoutEffect(() => {
15704
+ const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
15705
+ const unsubscribe = previousRoot.subscribe(onMutate);
15706
+ return unsubscribe;
15707
+ }, [previousRoot, usePortalStore]);
15491
15708
  return (
15492
15709
  // @ts-ignore, reconciler types are not maintained
15493
15710
  /* @__PURE__ */ jsx(Fragment, { children: reconciler.createPortal(
@@ -15708,6 +15925,7 @@ function CanvasImpl({
15708
15925
  queueMicrotask(() => {
15709
15926
  const rootEntry = _roots.get(canvas);
15710
15927
  if (rootEntry?.store) {
15928
+ console.log("[R3F] HMR detected \u2014 rebuilding nodes/uniforms");
15711
15929
  rootEntry.store.setState((state) => ({
15712
15930
  nodes: {},
15713
15931
  uniforms: {},
@@ -15719,8 +15937,7 @@ function CanvasImpl({
15719
15937
  if (typeof import.meta !== "undefined" && import.meta.hot) {
15720
15938
  const hot = import.meta.hot;
15721
15939
  hot.on("vite:afterUpdate", handleHMR);
15722
- return () => hot.dispose?.(() => {
15723
- });
15940
+ return () => hot.off?.("vite:afterUpdate", handleHMR);
15724
15941
  }
15725
15942
  if (typeof module !== "undefined" && module.hot) {
15726
15943
  const hot = module.hot;