@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/index.cjs CHANGED
@@ -996,6 +996,9 @@ function applyProps(object, props) {
996
996
  else target.set(value);
997
997
  } else {
998
998
  root[key] = value;
999
+ if (key.endsWith("Node") && root.isMaterial) {
1000
+ root.needsUpdate = true;
1001
+ }
999
1002
  if (rootState && rootState.renderer?.outputColorSpace === webgpu.SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
1000
1003
  root[key].format === webgpu.RGBAFormat && root[key].type === webgpu.UnsignedByteType) {
1001
1004
  root[key].colorSpace = rootState.textureColorSpace;
@@ -1029,38 +1032,60 @@ function applyProps(object, props) {
1029
1032
  return object;
1030
1033
  }
1031
1034
 
1035
+ const DEFAULT_POINTER_ID = 0;
1036
+ const XR_POINTER_ID_START = 1e3;
1037
+ function getPointerState(internal, pointerId) {
1038
+ let state = internal.pointerMap.get(pointerId);
1039
+ if (!state) {
1040
+ state = {
1041
+ hovered: /* @__PURE__ */ new Map(),
1042
+ captured: /* @__PURE__ */ new Map(),
1043
+ initialClick: [0, 0],
1044
+ initialHits: []
1045
+ };
1046
+ internal.pointerMap.set(pointerId, state);
1047
+ }
1048
+ return state;
1049
+ }
1050
+ function getPointerId(event) {
1051
+ return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
1052
+ }
1032
1053
  function makeId(event) {
1033
1054
  return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
1034
1055
  }
1035
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
1036
- const captureData = captures.get(obj);
1056
+ function releaseInternalPointerCapture(internal, obj, pointerId) {
1057
+ const pointerState = internal.pointerMap.get(pointerId);
1058
+ if (!pointerState) return;
1059
+ const captureData = pointerState.captured.get(obj);
1037
1060
  if (captureData) {
1038
- captures.delete(obj);
1039
- if (captures.size === 0) {
1040
- capturedMap.delete(pointerId);
1041
- captureData.target.releasePointerCapture(pointerId);
1042
- }
1061
+ pointerState.captured.delete(obj);
1062
+ captureData.target.releasePointerCapture(pointerId);
1043
1063
  }
1044
1064
  }
1045
1065
  function removeInteractivity(store, object) {
1046
1066
  const { internal } = store.getState();
1047
1067
  internal.interaction = internal.interaction.filter((o) => o !== object);
1048
- internal.initialHits = internal.initialHits.filter((o) => o !== object);
1049
- internal.hovered.forEach((value, key) => {
1050
- if (value.eventObject === object || value.object === object) {
1051
- internal.hovered.delete(key);
1068
+ for (const [pointerId, pointerState] of internal.pointerMap) {
1069
+ pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
1070
+ pointerState.hovered.forEach((value, key) => {
1071
+ if (value.eventObject === object || value.object === object) {
1072
+ pointerState.hovered.delete(key);
1073
+ }
1074
+ });
1075
+ if (pointerState.captured.has(object)) {
1076
+ releaseInternalPointerCapture(internal, object, pointerId);
1052
1077
  }
1053
- });
1054
- internal.capturedMap.forEach((captures, pointerId) => {
1055
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
1056
- });
1078
+ }
1057
1079
  unregisterVisibility(store, object);
1058
1080
  }
1059
1081
  function createEvents(store) {
1060
- function calculateDistance(event) {
1082
+ function calculateDistance(event, pointerId) {
1061
1083
  const { internal } = store.getState();
1062
- const dx = event.offsetX - internal.initialClick[0];
1063
- const dy = event.offsetY - internal.initialClick[1];
1084
+ const pointerState = internal.pointerMap.get(pointerId);
1085
+ if (!pointerState) return 0;
1086
+ const [initialX, initialY] = pointerState.initialClick;
1087
+ const dx = event.offsetX - initialX;
1088
+ const dy = event.offsetY - initialY;
1064
1089
  return Math.round(Math.sqrt(dx * dx + dy * dy));
1065
1090
  }
1066
1091
  function filterPointerEvents(objects) {
@@ -1096,6 +1121,15 @@ function createEvents(store) {
1096
1121
  return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
1097
1122
  }
1098
1123
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
1124
+ const aInteractivePriority = a.object.userData?.interactivePriority;
1125
+ const bInteractivePriority = b.object.userData?.interactivePriority;
1126
+ if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
1127
+ if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
1128
+ if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
1129
+ if (aInteractivePriority !== bInteractivePriority) {
1130
+ return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
1131
+ }
1132
+ }
1099
1133
  const aState = getRootState(a.object);
1100
1134
  const bState = getRootState(b.object);
1101
1135
  const aPriority = aState?.events?.priority ?? 1;
@@ -1117,9 +1151,13 @@ function createEvents(store) {
1117
1151
  eventObject = eventObject.parent;
1118
1152
  }
1119
1153
  }
1120
- if ("pointerId" in event && state.internal.capturedMap.has(event.pointerId)) {
1121
- for (const captureData of state.internal.capturedMap.get(event.pointerId).values()) {
1122
- if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1154
+ if ("pointerId" in event) {
1155
+ const pointerId = event.pointerId;
1156
+ const pointerState = state.internal.pointerMap.get(pointerId);
1157
+ if (pointerState?.captured.size) {
1158
+ for (const captureData of pointerState.captured.values()) {
1159
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1160
+ }
1123
1161
  }
1124
1162
  }
1125
1163
  return intersections;
@@ -1132,27 +1170,25 @@ function createEvents(store) {
1132
1170
  if (state) {
1133
1171
  const { raycaster, pointer, camera, internal } = state;
1134
1172
  const unprojectedPoint = new webgpu.Vector3(pointer.x, pointer.y, 0).unproject(camera);
1135
- const hasPointerCapture = (id) => internal.capturedMap.get(id)?.has(hit.eventObject) ?? false;
1173
+ const hasPointerCapture = (id) => {
1174
+ const pointerState = internal.pointerMap.get(id);
1175
+ return pointerState?.captured.has(hit.eventObject) ?? false;
1176
+ };
1136
1177
  const setPointerCapture = (id) => {
1137
1178
  const captureData = { intersection: hit, target: event.target };
1138
- if (internal.capturedMap.has(id)) {
1139
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
1140
- } else {
1141
- internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
1142
- }
1179
+ const pointerState = getPointerState(internal, id);
1180
+ pointerState.captured.set(hit.eventObject, captureData);
1143
1181
  event.target.setPointerCapture(id);
1144
1182
  };
1145
1183
  const releasePointerCapture = (id) => {
1146
- const captures = internal.capturedMap.get(id);
1147
- if (captures) {
1148
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
1149
- }
1184
+ releaseInternalPointerCapture(internal, hit.eventObject, id);
1150
1185
  };
1151
1186
  const extractEventProps = {};
1152
1187
  for (const prop in event) {
1153
1188
  const property = event[prop];
1154
1189
  if (typeof property !== "function") extractEventProps[prop] = property;
1155
1190
  }
1191
+ const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
1156
1192
  const raycastEvent = {
1157
1193
  ...hit,
1158
1194
  ...extractEventProps,
@@ -1163,18 +1199,19 @@ function createEvents(store) {
1163
1199
  unprojectedPoint,
1164
1200
  ray: raycaster.ray,
1165
1201
  camera,
1202
+ pointerId: eventPointerId,
1166
1203
  // Hijack stopPropagation, which just sets a flag
1167
1204
  stopPropagation() {
1168
- const capturesForPointer = "pointerId" in event && internal.capturedMap.get(event.pointerId);
1205
+ const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
1169
1206
  if (
1170
1207
  // ...if this pointer hasn't been captured
1171
- !capturesForPointer || // ... or if the hit object is capturing the pointer
1172
- capturesForPointer.has(hit.eventObject)
1208
+ !pointerState?.captured.size || // ... or if the hit object is capturing the pointer
1209
+ pointerState.captured.has(hit.eventObject)
1173
1210
  ) {
1174
1211
  raycastEvent.stopped = localState.stopped = true;
1175
- if (internal.hovered.size && Array.from(internal.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1212
+ if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
1176
1213
  const higher = intersections.slice(0, intersections.indexOf(hit));
1177
- cancelPointer([...higher, hit]);
1214
+ cancelPointer([...higher, hit], eventPointerId);
1178
1215
  }
1179
1216
  }
1180
1217
  },
@@ -1190,15 +1227,18 @@ function createEvents(store) {
1190
1227
  }
1191
1228
  return intersections;
1192
1229
  }
1193
- function cancelPointer(intersections) {
1230
+ function cancelPointer(intersections, pointerId) {
1194
1231
  const { internal } = store.getState();
1195
- for (const hoveredObj of internal.hovered.values()) {
1232
+ const pid = pointerId ?? DEFAULT_POINTER_ID;
1233
+ const pointerState = internal.pointerMap.get(pid);
1234
+ if (!pointerState) return;
1235
+ for (const [hoveredId, hoveredObj] of pointerState.hovered) {
1196
1236
  if (!intersections.length || !intersections.find(
1197
1237
  (hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
1198
1238
  )) {
1199
1239
  const eventObject = hoveredObj.eventObject;
1200
1240
  const instance = eventObject.__r3f;
1201
- internal.hovered.delete(makeId(hoveredObj));
1241
+ pointerState.hovered.delete(hoveredId);
1202
1242
  if (instance?.eventCount) {
1203
1243
  const handlers = instance.handlers;
1204
1244
  const data = { ...hoveredObj, intersections };
@@ -1227,41 +1267,118 @@ function createEvents(store) {
1227
1267
  instance?.handlers.onDropMissed?.(event);
1228
1268
  }
1229
1269
  }
1270
+ function cleanupPointer(pointerId) {
1271
+ const { internal } = store.getState();
1272
+ const pointerState = internal.pointerMap.get(pointerId);
1273
+ if (pointerState) {
1274
+ for (const [, hoveredObj] of pointerState.hovered) {
1275
+ const eventObject = hoveredObj.eventObject;
1276
+ const instance = eventObject.__r3f;
1277
+ if (instance?.eventCount) {
1278
+ const handlers = instance.handlers;
1279
+ const data = { ...hoveredObj, intersections: [] };
1280
+ handlers.onPointerOut?.(data);
1281
+ handlers.onPointerLeave?.(data);
1282
+ }
1283
+ }
1284
+ internal.pointerMap.delete(pointerId);
1285
+ }
1286
+ internal.pointerDirty.delete(pointerId);
1287
+ }
1288
+ function processDeferredPointer(event, pointerId) {
1289
+ const state = store.getState();
1290
+ const { onPointerMissed, onDragOverMissed, internal } = state;
1291
+ if (!state.events.enabled) return;
1292
+ const filter = filterPointerEvents;
1293
+ const hits = intersect(event, filter);
1294
+ cancelPointer(hits, pointerId);
1295
+ function onIntersect(data) {
1296
+ const eventObject = data.eventObject;
1297
+ const instance = eventObject.__r3f;
1298
+ if (!instance?.eventCount) return;
1299
+ const handlers = instance.handlers;
1300
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1301
+ const id = makeId(data);
1302
+ const pointerState = getPointerState(internal, pointerId);
1303
+ const hoveredItem = pointerState.hovered.get(id);
1304
+ if (!hoveredItem) {
1305
+ pointerState.hovered.set(id, data);
1306
+ handlers.onPointerOver?.(data);
1307
+ handlers.onPointerEnter?.(data);
1308
+ } else if (hoveredItem.stopped) {
1309
+ data.stopPropagation();
1310
+ }
1311
+ }
1312
+ handlers.onPointerMove?.(data);
1313
+ }
1314
+ handleIntersects(hits, event, 0, onIntersect);
1315
+ }
1230
1316
  function handlePointer(name) {
1231
1317
  switch (name) {
1232
1318
  case "onPointerLeave":
1233
- case "onPointerCancel":
1234
1319
  case "onDragLeave":
1235
1320
  return () => cancelPointer([]);
1321
+ // Global cancel of these events
1322
+ case "onPointerCancel":
1323
+ return (event) => {
1324
+ const pointerId = getPointerId(event);
1325
+ cleanupPointer(pointerId);
1326
+ };
1236
1327
  case "onLostPointerCapture":
1237
1328
  return (event) => {
1238
1329
  const { internal } = store.getState();
1239
- if ("pointerId" in event && internal.capturedMap.has(event.pointerId)) {
1330
+ const pointerId = getPointerId(event);
1331
+ const pointerState = internal.pointerMap.get(pointerId);
1332
+ if (pointerState?.captured.size) {
1240
1333
  requestAnimationFrame(() => {
1241
- if (internal.capturedMap.has(event.pointerId)) {
1242
- internal.capturedMap.delete(event.pointerId);
1243
- cancelPointer([]);
1334
+ const pointerState2 = internal.pointerMap.get(pointerId);
1335
+ if (pointerState2?.captured.size) {
1336
+ pointerState2.captured.clear();
1244
1337
  }
1338
+ cancelPointer([], pointerId);
1245
1339
  });
1246
1340
  }
1247
1341
  };
1248
1342
  }
1249
1343
  return function handleEvent(event) {
1250
1344
  const state = store.getState();
1251
- const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
1345
+ const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
1346
+ const pointerId = getPointerId(event);
1252
1347
  internal.lastEvent.current = event;
1253
- if (!state.events.enabled) return;
1348
+ if (!events.enabled) return;
1254
1349
  const isPointerMove = name === "onPointerMove";
1255
1350
  const isDragOver = name === "onDragOver";
1256
1351
  const isDrop = name === "onDrop";
1257
1352
  const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
1353
+ const isPointerDown = name === "onPointerDown";
1354
+ const isPointerUp = name === "onPointerUp";
1355
+ const isWheel = name === "onWheel";
1356
+ const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
1357
+ if (isPointerMove && canDeferRaycasts) {
1358
+ events.compute?.(event, state);
1359
+ internal.pointerDirty.set(pointerId, event);
1360
+ return;
1361
+ }
1362
+ if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
1363
+ events.compute?.(event, state);
1364
+ internal.pointerDirty.set(pointerId, event);
1365
+ return;
1366
+ }
1367
+ if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
1368
+ const deferredEvent = internal.pointerDirty.get(pointerId);
1369
+ internal.pointerDirty.delete(pointerId);
1370
+ processDeferredPointer(deferredEvent, pointerId);
1371
+ }
1258
1372
  const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
1259
1373
  const hits = intersect(event, filter);
1260
- const delta = isClickEvent ? calculateDistance(event) : 0;
1261
- if (name === "onPointerDown") {
1262
- internal.initialClick = [event.offsetX, event.offsetY];
1263
- internal.initialHits = hits.map((hit) => hit.eventObject);
1264
- }
1374
+ const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
1375
+ if (isPointerDown) {
1376
+ const pointerState2 = getPointerState(internal, pointerId);
1377
+ pointerState2.initialClick = [event.offsetX, event.offsetY];
1378
+ pointerState2.initialHits = hits.map((hit) => hit.eventObject);
1379
+ }
1380
+ const pointerState = internal.pointerMap.get(pointerId);
1381
+ const initialHits = pointerState?.initialHits ?? [];
1265
1382
  if (isClickEvent && !hits.length) {
1266
1383
  if (delta <= 2) {
1267
1384
  pointerMissed(event, internal.interaction);
@@ -1276,7 +1393,9 @@ function createEvents(store) {
1276
1393
  dropMissed(event, internal.interaction);
1277
1394
  if (onDropMissed) onDropMissed(event);
1278
1395
  }
1279
- if (isPointerMove || isDragOver) cancelPointer(hits);
1396
+ if (isPointerMove || isDragOver) {
1397
+ cancelPointer(hits, pointerId);
1398
+ }
1280
1399
  function onIntersect(data) {
1281
1400
  const eventObject = data.eventObject;
1282
1401
  const instance = eventObject.__r3f;
@@ -1285,9 +1404,10 @@ function createEvents(store) {
1285
1404
  if (isPointerMove) {
1286
1405
  if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1287
1406
  const id = makeId(data);
1288
- const hoveredItem = internal.hovered.get(id);
1407
+ const pointerState2 = getPointerState(internal, pointerId);
1408
+ const hoveredItem = pointerState2.hovered.get(id);
1289
1409
  if (!hoveredItem) {
1290
- internal.hovered.set(id, data);
1410
+ pointerState2.hovered.set(id, data);
1291
1411
  handlers.onPointerOver?.(data);
1292
1412
  handlers.onPointerEnter?.(data);
1293
1413
  } else if (hoveredItem.stopped) {
@@ -1297,9 +1417,10 @@ function createEvents(store) {
1297
1417
  handlers.onPointerMove?.(data);
1298
1418
  } else if (isDragOver) {
1299
1419
  const id = makeId(data);
1300
- const hoveredItem = internal.hovered.get(id);
1420
+ const pointerState2 = getPointerState(internal, pointerId);
1421
+ const hoveredItem = pointerState2.hovered.get(id);
1301
1422
  if (!hoveredItem) {
1302
- internal.hovered.set(id, data);
1423
+ pointerState2.hovered.set(id, data);
1303
1424
  handlers.onDragOverEnter?.(data);
1304
1425
  } else if (hoveredItem.stopped) {
1305
1426
  data.stopPropagation();
@@ -1310,18 +1431,18 @@ function createEvents(store) {
1310
1431
  } else {
1311
1432
  const handler = handlers[name];
1312
1433
  if (handler) {
1313
- if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1434
+ if (!isClickEvent || initialHits.includes(eventObject)) {
1314
1435
  pointerMissed(
1315
1436
  event,
1316
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1437
+ internal.interaction.filter((object) => !initialHits.includes(object))
1317
1438
  );
1318
1439
  handler(data);
1319
1440
  }
1320
1441
  } else {
1321
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
1442
+ if (isClickEvent && initialHits.includes(eventObject)) {
1322
1443
  pointerMissed(
1323
1444
  event,
1324
- internal.interaction.filter((object) => !internal.initialHits.includes(object))
1445
+ internal.interaction.filter((object) => !initialHits.includes(object))
1325
1446
  );
1326
1447
  }
1327
1448
  }
@@ -1330,7 +1451,15 @@ function createEvents(store) {
1330
1451
  handleIntersects(hits, event, delta, onIntersect);
1331
1452
  };
1332
1453
  }
1333
- return { handlePointer };
1454
+ function flushDeferredPointers() {
1455
+ const { internal, events } = store.getState();
1456
+ if (!events.frameTimedRaycasts) return;
1457
+ for (const [pointerId, event] of internal.pointerDirty) {
1458
+ processDeferredPointer(event, pointerId);
1459
+ }
1460
+ internal.pointerDirty.clear();
1461
+ }
1462
+ return { handlePointer, flushDeferredPointers, processDeferredPointer };
1334
1463
  }
1335
1464
  const DOM_EVENTS = {
1336
1465
  onClick: ["click", false],
@@ -1349,10 +1478,15 @@ const DOM_EVENTS = {
1349
1478
  onLostPointerCapture: ["lostpointercapture", true]
1350
1479
  };
1351
1480
  function createPointerEvents(store) {
1352
- const { handlePointer } = createEvents(store);
1481
+ const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
1482
+ let nextXRPointerId = XR_POINTER_ID_START;
1483
+ const xrPointers = /* @__PURE__ */ new Map();
1353
1484
  return {
1354
1485
  priority: 1,
1355
1486
  enabled: true,
1487
+ frameTimedRaycasts: true,
1488
+ alwaysFireOnScroll: true,
1489
+ updateOnFrame: false,
1356
1490
  compute(event, state) {
1357
1491
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
1358
1492
  state.raycaster.setFromCamera(state.pointer, state.camera);
@@ -1362,9 +1496,30 @@ function createPointerEvents(store) {
1362
1496
  (acc, key) => ({ ...acc, [key]: handlePointer(key) }),
1363
1497
  {}
1364
1498
  ),
1365
- update: () => {
1499
+ update: (pointerId) => {
1500
+ const { events, internal } = store.getState();
1501
+ if (!events.handlers) return;
1502
+ if (pointerId !== void 0) {
1503
+ const event = internal.pointerDirty.get(pointerId);
1504
+ if (event) {
1505
+ internal.pointerDirty.delete(pointerId);
1506
+ processDeferredPointer(event, pointerId);
1507
+ } else if (internal.lastEvent?.current) {
1508
+ processDeferredPointer(internal.lastEvent.current, pointerId);
1509
+ }
1510
+ } else {
1511
+ flushDeferredPointers();
1512
+ if (internal.lastEvent?.current) {
1513
+ events.handlers.onPointerMove(internal.lastEvent.current);
1514
+ }
1515
+ }
1516
+ },
1517
+ flush: () => {
1366
1518
  const { events, internal } = store.getState();
1367
- if (internal.lastEvent?.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
1519
+ flushDeferredPointers();
1520
+ if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
1521
+ events.handlers.onPointerMove(internal.lastEvent.current);
1522
+ }
1368
1523
  },
1369
1524
  connect: (target) => {
1370
1525
  const { set, events } = store.getState();
@@ -1390,6 +1545,32 @@ function createPointerEvents(store) {
1390
1545
  }
1391
1546
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
1392
1547
  }
1548
+ },
1549
+ registerPointer: (config) => {
1550
+ const pointerId = nextXRPointerId++;
1551
+ xrPointers.set(pointerId, config);
1552
+ const { internal } = store.getState();
1553
+ getPointerState(internal, pointerId);
1554
+ return pointerId;
1555
+ },
1556
+ unregisterPointer: (pointerId) => {
1557
+ xrPointers.delete(pointerId);
1558
+ const { internal } = store.getState();
1559
+ const pointerState = internal.pointerMap.get(pointerId);
1560
+ if (pointerState) {
1561
+ for (const [, hoveredObj] of pointerState.hovered) {
1562
+ const eventObject = hoveredObj.eventObject;
1563
+ const instance = eventObject.__r3f;
1564
+ if (instance?.eventCount) {
1565
+ const handlers = instance.handlers;
1566
+ const data = { ...hoveredObj, intersections: [] };
1567
+ handlers.onPointerOut?.(data);
1568
+ handlers.onPointerLeave?.(data);
1569
+ }
1570
+ }
1571
+ internal.pointerMap.delete(pointerId);
1572
+ }
1573
+ internal.pointerDirty.delete(pointerId);
1393
1574
  }
1394
1575
  };
1395
1576
  }
@@ -2520,7 +2701,14 @@ const createStore = (invalidate, advance) => {
2520
2701
  frustum: new webgpu.Frustum(),
2521
2702
  autoUpdateFrustum: true,
2522
2703
  raycaster: null,
2523
- events: { priority: 1, enabled: true, connected: false },
2704
+ events: {
2705
+ priority: 1,
2706
+ enabled: true,
2707
+ connected: false,
2708
+ frameTimedRaycasts: true,
2709
+ alwaysFireOnScroll: true,
2710
+ updateOnFrame: false
2711
+ },
2524
2712
  scene: null,
2525
2713
  rootScene: null,
2526
2714
  xr: null,
@@ -2611,11 +2799,13 @@ const createStore = (invalidate, advance) => {
2611
2799
  },
2612
2800
  setError: (error) => set(() => ({ error })),
2613
2801
  error: null,
2614
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
2802
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
2615
2803
  uniforms: {},
2616
2804
  nodes: {},
2805
+ buffers: {},
2806
+ gpuStorage: {},
2617
2807
  textures: /* @__PURE__ */ new Map(),
2618
- postProcessing: null,
2808
+ renderPipeline: null,
2619
2809
  passes: {},
2620
2810
  _hmrVersion: 0,
2621
2811
  _sizeImperative: false,
@@ -2624,12 +2814,16 @@ const createStore = (invalidate, advance) => {
2624
2814
  internal: {
2625
2815
  // Events
2626
2816
  interaction: [],
2627
- hovered: /* @__PURE__ */ new Map(),
2628
2817
  subscribers: [],
2818
+ // Per-pointer state (new unified structure)
2819
+ pointerMap: /* @__PURE__ */ new Map(),
2820
+ pointerDirty: /* @__PURE__ */ new Map(),
2821
+ lastEvent: React__namespace.createRef(),
2822
+ // Deprecated but kept for backwards compatibility
2823
+ hovered: /* @__PURE__ */ new Map(),
2629
2824
  initialClick: [0, 0],
2630
2825
  initialHits: [],
2631
2826
  capturedMap: /* @__PURE__ */ new Map(),
2632
- lastEvent: React__namespace.createRef(),
2633
2827
  // Visibility tracking (onFramed, onOccluded, onVisible)
2634
2828
  visibilityRegistry: /* @__PURE__ */ new Map(),
2635
2829
  // Occlusion system (WebGPU only)
@@ -15081,6 +15275,12 @@ function createRoot(canvas) {
15081
15275
  } else if (!wantsGL && !state.internal.actualRenderer) {
15082
15276
  renderer = await resolveRenderer(rendererConfig, defaultGPUProps, webgpu.WebGPURenderer);
15083
15277
  if (!renderer.hasInitialized?.()) {
15278
+ const size2 = computeInitialSize(canvas, propsSize);
15279
+ if (size2.width > 0 && size2.height > 0) {
15280
+ const pixelRatio = calculateDpr(dpr);
15281
+ canvas.width = size2.width * pixelRatio;
15282
+ canvas.height = size2.height * pixelRatio;
15283
+ }
15084
15284
  await renderer.init();
15085
15285
  }
15086
15286
  const backend = renderer.backend;
@@ -15282,6 +15482,18 @@ function createRoot(canvas) {
15282
15482
  system: true
15283
15483
  }
15284
15484
  );
15485
+ const unregisterEventsFlush = scheduler.register(
15486
+ () => {
15487
+ const state2 = store.getState();
15488
+ state2.events.flush?.();
15489
+ },
15490
+ {
15491
+ id: `${newRootId}_events`,
15492
+ rootId: newRootId,
15493
+ phase: "input",
15494
+ system: true
15495
+ }
15496
+ );
15285
15497
  const unregisterFrustum = scheduler.register(
15286
15498
  () => {
15287
15499
  const state2 = store.getState();
@@ -15316,7 +15528,7 @@ function createRoot(canvas) {
15316
15528
  const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
15317
15529
  if (userHandlesRender || state2.internal.priority) return;
15318
15530
  try {
15319
- if (state2.postProcessing?.render) state2.postProcessing.render();
15531
+ if (state2.renderPipeline?.render) state2.renderPipeline.render();
15320
15532
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
15321
15533
  } catch (error) {
15322
15534
  state2.setError(error instanceof Error ? error : new Error(String(error)));
@@ -15341,6 +15553,7 @@ function createRoot(canvas) {
15341
15553
  unregisterRoot: () => {
15342
15554
  unregisterRoot();
15343
15555
  unregisterCanvasTarget();
15556
+ unregisterEventsFlush();
15344
15557
  unregisterFrustum();
15345
15558
  unregisterVisibility();
15346
15559
  unregisterRender();
@@ -15506,9 +15719,13 @@ function PortalInner({ state = {}, children, container }) {
15506
15719
  const store = traditional.createWithEqualityFn((set, get) => ({ ...rest, set, get }));
15507
15720
  const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
15508
15721
  onMutate(previousRoot.getState());
15509
- previousRoot.subscribe(onMutate);
15510
15722
  return store;
15511
15723
  }, [previousRoot, container]);
15724
+ useIsomorphicLayoutEffect(() => {
15725
+ const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
15726
+ const unsubscribe = previousRoot.subscribe(onMutate);
15727
+ return unsubscribe;
15728
+ }, [previousRoot, usePortalStore]);
15512
15729
  return (
15513
15730
  // @ts-ignore, reconciler types are not maintained
15514
15731
  /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reconciler.createPortal(
@@ -15729,6 +15946,7 @@ function CanvasImpl({
15729
15946
  queueMicrotask(() => {
15730
15947
  const rootEntry = _roots.get(canvas);
15731
15948
  if (rootEntry?.store) {
15949
+ console.log("[R3F] HMR detected \u2014 rebuilding nodes/uniforms");
15732
15950
  rootEntry.store.setState((state) => ({
15733
15951
  nodes: {},
15734
15952
  uniforms: {},
@@ -15740,8 +15958,7 @@ function CanvasImpl({
15740
15958
  if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
15741
15959
  const hot = undefined;
15742
15960
  hot.on("vite:afterUpdate", handleHMR);
15743
- return () => hot.dispose?.(() => {
15744
- });
15961
+ return () => hot.off?.("vite:afterUpdate", handleHMR);
15745
15962
  }
15746
15963
  if (typeof module !== "undefined" && module.hot) {
15747
15964
  const hot = module.hot;