@react-three/fiber 10.0.0-canary.8f5b4d3 → 10.0.0-canary.979dc2d

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.cjs CHANGED
@@ -50,11 +50,9 @@ const Tb__default = /*#__PURE__*/_interopDefaultCompat(Tb);
50
50
 
51
51
  const R3F_BUILD_LEGACY = true;
52
52
  const R3F_BUILD_WEBGPU = false;
53
- const Inspector = class Inspector2 {
54
- constructor() {
55
- throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
56
- }
57
- };
53
+ async function loadInspector() {
54
+ throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
55
+ }
58
56
  const WebGPURenderer = class WebGPURenderer2 {
59
57
  constructor() {
60
58
  throw new Error("WebGPURenderer is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
@@ -67,12 +65,12 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
67
65
  __proto__: null,
68
66
  CubeRenderTarget: CubeRenderTarget,
69
67
  CubeRenderTargetCompat: three.WebGLCubeRenderTarget,
70
- Inspector: Inspector,
71
68
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
72
69
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
73
70
  RenderTarget: RenderTarget,
74
71
  RenderTargetCompat: three.WebGLRenderTarget,
75
- WebGPURenderer: WebGPURenderer
72
+ WebGPURenderer: WebGPURenderer,
73
+ loadInspector: loadInspector
76
74
  }, [three__namespace]);
77
75
 
78
76
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -931,7 +929,7 @@ const createStore = (invalidate, advance) => {
931
929
  size: newSize,
932
930
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
933
931
  }));
934
- scheduler.getScheduler().invalidate();
932
+ get().invalidate();
935
933
  }
936
934
  }
937
935
  return;
@@ -946,15 +944,16 @@ const createStore = (invalidate, advance) => {
946
944
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
947
945
  _sizeImperative: true
948
946
  }));
949
- scheduler.getScheduler().invalidate();
947
+ get().invalidate();
950
948
  },
951
949
  setDpr: (dpr) => set((state2) => {
952
950
  const resolved = calculateDpr(dpr);
953
951
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
954
952
  }),
955
953
  setFrameloop: (frameloop = "always") => {
954
+ const rootId = get().internal.rootId;
955
+ if (rootId) scheduler.getScheduler().setRootFrameloop(rootId, frameloop);
956
956
  set(() => ({ frameloop }));
957
- scheduler.getScheduler().frameloop = frameloop;
958
957
  },
959
958
  setError: (error) => set(() => ({ error })),
960
959
  error: null,
@@ -1070,17 +1069,11 @@ const createStore = (invalidate, advance) => {
1070
1069
  oldSize = size;
1071
1070
  oldDpr = viewport.dpr;
1072
1071
  updateCamera(camera, size);
1073
- if (internal.isSecondary && canvasTarget) {
1074
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1075
- canvasTarget.setSize(size.width, size.height, false);
1076
- } else {
1077
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1078
- actualRenderer.setSize(size.width, size.height, false);
1079
- if (canvasTarget) {
1080
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1081
- canvasTarget.setSize(size.width, size.height, false);
1082
- }
1083
- }
1072
+ const resizeTarget = canvasTarget ?? actualRenderer;
1073
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1074
+ resizeTarget.setSize(size.width, size.height, false);
1075
+ const activeTarget = actualRenderer.getCanvasTarget?.();
1076
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
1084
1077
  }
1085
1078
  if (camera !== oldCamera) {
1086
1079
  oldCamera = camera;
@@ -1142,7 +1135,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1142
1135
  const results = cacheKeys.map(
1143
1136
  (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1144
1137
  );
1145
- return Array.isArray(input) ? results : results[0];
1138
+ const stableRef = React.useRef(null);
1139
+ const previous = stableRef.current;
1140
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1141
+ stableRef.current = stable;
1142
+ return Array.isArray(input) ? stable : stable[0];
1146
1143
  }
1147
1144
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1148
1145
  const inputs = Array.isArray(input) ? input : [input];
@@ -1262,6 +1259,13 @@ function useFrame(callback, priorityOrOptions) {
1262
1259
  * Use for controlling the entire frame loop, adding phases, etc.
1263
1260
  */
1264
1261
  scheduler: scheduler2,
1262
+ /**
1263
+ * The root that currently owns this job. Resolve on access because ambient
1264
+ * jobs can be adopted by a Canvas after the controls object is created.
1265
+ */
1266
+ get rootId() {
1267
+ return scheduler.getScheduler().getJobRootId(id);
1268
+ },
1265
1269
  /**
1266
1270
  * Manually step this job only.
1267
1271
  * Bypasses FPS limiting - always runs.
@@ -1278,6 +1282,14 @@ function useFrame(callback, priorityOrOptions) {
1278
1282
  stepAll: (timestamp) => {
1279
1283
  scheduler.getScheduler().step(timestamp);
1280
1284
  },
1285
+ /**
1286
+ * Request frames for this job's owning root without waking sibling roots.
1287
+ */
1288
+ invalidate: (frames, stackFrames) => {
1289
+ const scheduler3 = scheduler.getScheduler();
1290
+ const rootId = scheduler3.getJobRootId(id);
1291
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1292
+ },
1281
1293
  /**
1282
1294
  * Pause this job (set enabled=false).
1283
1295
  * Job remains registered but won't run.
@@ -1307,6 +1319,11 @@ function getUrls(input) {
1307
1319
  if (Array.isArray(input)) return input;
1308
1320
  return Object.values(input);
1309
1321
  }
1322
+ function inputSignature(input) {
1323
+ if (typeof input === "string") return input;
1324
+ if (Array.isArray(input)) return input.join("\0");
1325
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1326
+ }
1310
1327
  function allUrlsCached(urls, textureCache) {
1311
1328
  return urls.every((url) => textureCache.has(url));
1312
1329
  }
@@ -1332,18 +1349,19 @@ function useTexture(input, optionsOrOnLoad) {
1332
1349
  const onLoadRef = React.useRef(onLoad);
1333
1350
  onLoadRef.current = onLoad;
1334
1351
  const onLoadCalledForRef = React.useRef(null);
1335
- const urls = React.useMemo(() => getUrls(input), [input]);
1352
+ const inputKey = React.useMemo(() => inputSignature(input), [input]);
1353
+ const stableInput = React.useMemo(() => input, [inputKey]);
1354
+ const urls = React.useMemo(() => getUrls(stableInput), [stableInput]);
1336
1355
  const cachedResult = React.useMemo(() => {
1337
1356
  if (!cache) return null;
1338
1357
  const textures = store.getState().textures;
1339
1358
  if (!allUrlsCached(urls, textures)) return null;
1340
- return buildFromCache(input, textures);
1341
- }, [cache, urls, input, store]);
1359
+ return buildFromCache(stableInput, textures);
1360
+ }, [cache, urls, stableInput, store]);
1342
1361
  const loadedTextures = useLoader(
1343
1362
  three.TextureLoader,
1344
- IsObject(input) ? Object.values(input) : input
1363
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1345
1364
  );
1346
- const inputKey = urls.join("\0");
1347
1365
  React.useLayoutEffect(() => {
1348
1366
  if (cachedResult) return;
1349
1367
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1370,28 +1388,28 @@ function useTexture(input, optionsOrOnLoad) {
1370
1388
  }, [renderer, loadedTextures, cachedResult]);
1371
1389
  const mappedTextures = React.useMemo(() => {
1372
1390
  if (cachedResult) return cachedResult;
1373
- if (IsObject(input)) {
1391
+ if (IsObject(stableInput)) {
1374
1392
  const keyed = {};
1375
1393
  const textureArray = loadedTextures;
1376
1394
  let i = 0;
1377
- for (const key in input) keyed[key] = textureArray[i++];
1395
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1378
1396
  return keyed;
1379
1397
  } else {
1380
1398
  return loadedTextures;
1381
1399
  }
1382
- }, [input, loadedTextures, cachedResult]);
1400
+ }, [stableInput, loadedTextures, cachedResult]);
1383
1401
  React.useEffect(() => {
1384
1402
  if (!cache) return;
1385
1403
  const urlTextureMap = [];
1386
- if (typeof input === "string") {
1387
- urlTextureMap.push([input, mappedTextures]);
1388
- } else if (Array.isArray(input)) {
1404
+ if (typeof stableInput === "string") {
1405
+ urlTextureMap.push([stableInput, mappedTextures]);
1406
+ } else if (Array.isArray(stableInput)) {
1389
1407
  const textureArray = mappedTextures;
1390
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1391
- } else if (IsObject(input)) {
1408
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1409
+ } else if (IsObject(stableInput)) {
1392
1410
  const textureRecord = mappedTextures;
1393
- for (const key in input) {
1394
- const url = input[key];
1411
+ for (const key in stableInput) {
1412
+ const url = stableInput[key];
1395
1413
  urlTextureMap.push([url, textureRecord[key]]);
1396
1414
  }
1397
1415
  }
@@ -1420,7 +1438,7 @@ function useTexture(input, optionsOrOnLoad) {
1420
1438
  }
1421
1439
  return { _textureRefs: refs };
1422
1440
  });
1423
- }, [cache, input, mappedTextures, store]);
1441
+ }, [cache, stableInput, mappedTextures, store]);
1424
1442
  return mappedTextures;
1425
1443
  }
1426
1444
  useTexture.preload = (url) => useLoader.preload(three.TextureLoader, url);
@@ -1689,13 +1707,19 @@ function addTail(callback) {
1689
1707
  return scheduler.getScheduler().onIdle(callback);
1690
1708
  }
1691
1709
  function invalidate(state, frames = 1, stackFrames = false) {
1692
- scheduler.getScheduler().invalidate(frames, stackFrames);
1710
+ const rootId = state?.internal?.rootId;
1711
+ const scheduler$1 = scheduler.getScheduler();
1712
+ if (rootId) scheduler$1.invalidateRoot(rootId, frames, stackFrames);
1713
+ else scheduler$1.invalidate(frames, stackFrames);
1693
1714
  }
1694
- function advance(timestamp) {
1695
- scheduler.getScheduler().step(timestamp);
1715
+ function advance(timestamp, runGlobalEffects, state) {
1716
+ const rootId = state?.internal?.rootId;
1717
+ const scheduler$1 = scheduler.getScheduler();
1718
+ if (rootId) scheduler$1.stepRoot(rootId, timestamp);
1719
+ else scheduler$1.step(timestamp);
1696
1720
  }
1697
1721
 
1698
- const version = "10.0.0-alpha.3";
1722
+ const version = "10.0.0-alpha.4";
1699
1723
  const packageData = {
1700
1724
  version: version};
1701
1725
 
@@ -12978,7 +13002,10 @@ function releaseInternalPointerCapture(internal, obj, pointerId) {
12978
13002
  const captureData = pointerState.captured.get(obj);
12979
13003
  if (captureData) {
12980
13004
  pointerState.captured.delete(obj);
12981
- captureData.target.releasePointerCapture(pointerId);
13005
+ try {
13006
+ captureData.target.releasePointerCapture?.(pointerId);
13007
+ } catch {
13008
+ }
12982
13009
  }
12983
13010
  }
12984
13011
  function swapInteractivity(store, object, newObject) {
@@ -13072,7 +13099,9 @@ function createEvents(store) {
13072
13099
  state2.events.compute?.(event, state2, state2.previousRoot?.getState());
13073
13100
  if (state2.raycaster.camera === void 0) state2.raycaster.camera = null;
13074
13101
  }
13075
- return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
13102
+ if (!state2.raycaster.camera) return [];
13103
+ obj.updateWorldMatrix(true, true);
13104
+ return state2.raycaster.intersectObject(obj, true);
13076
13105
  }
13077
13106
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
13078
13107
  const aInteractivePriority = a.object.userData?.interactivePriority;
@@ -14302,7 +14331,7 @@ function createRoot(canvas) {
14302
14331
  const handleXRFrame = (timestamp, _frame) => {
14303
14332
  const state2 = store.getState();
14304
14333
  if (state2.frameloop === "never") return;
14305
- advance(timestamp);
14334
+ advance(timestamp, true, state2);
14306
14335
  };
14307
14336
  const actualRenderer = state.internal.actualRenderer;
14308
14337
  const handleSessionChange = () => {
@@ -14310,7 +14339,7 @@ function createRoot(canvas) {
14310
14339
  const renderer2 = state2.internal.actualRenderer;
14311
14340
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14312
14341
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14313
- if (!renderer2.xr.isPresenting) invalidate();
14342
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14314
14343
  };
14315
14344
  const xr = {
14316
14345
  connect() {
@@ -14392,14 +14421,23 @@ function createRoot(canvas) {
14392
14421
  const newRootId = canvasId || scheduler$1.generateRootId();
14393
14422
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14394
14423
  getState: () => store.getState(),
14395
- onError: (err) => store.getState().setError(err)
14424
+ onError: (err) => store.getState().setError(err),
14425
+ frameloop: store.getState().frameloop,
14426
+ before: schedulerConfig?.before,
14427
+ after: schedulerConfig?.after,
14428
+ order: schedulerConfig?.order
14396
14429
  });
14397
14430
  const unregisterCanvasTarget = scheduler$1.register(
14398
14431
  () => {
14399
14432
  const state2 = store.getState();
14400
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14433
+ const canvasTarget = state2.internal.canvasTarget;
14434
+ if (canvasTarget) {
14401
14435
  const renderer2 = state2.internal.actualRenderer;
14402
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14436
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14437
+ if (state2.internal.canvasTargetSizeDirty) {
14438
+ state2.internal.canvasTargetSizeDirty = false;
14439
+ renderer2.backend?.updateSize?.();
14440
+ }
14403
14441
  }
14404
14442
  },
14405
14443
  {
@@ -14468,8 +14506,7 @@ function createRoot(canvas) {
14468
14506
  phase: "render",
14469
14507
  system: true,
14470
14508
  // Internal flag: this is a system job, not user-controlled
14471
- // Apply scheduler config for render ordering and rate limiting
14472
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14509
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14473
14510
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14474
14511
  }
14475
14512
  );
@@ -14488,8 +14525,21 @@ function createRoot(canvas) {
14488
14525
  scheduler: scheduler$1
14489
14526
  }
14490
14527
  }));
14528
+ } else {
14529
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14530
+ if (constraintsChanged) {
14531
+ scheduler$1.setRootConstraints(rootId, {
14532
+ before: schedulerConfig?.before,
14533
+ after: schedulerConfig?.after
14534
+ });
14535
+ }
14536
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14537
+ scheduler$1.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14538
+ }
14491
14539
  }
14492
- scheduler$1.frameloop = frameloop;
14540
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14541
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14542
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14493
14543
  onCreated = onCreatedCallback;
14494
14544
  configured = true;
14495
14545
  resolve();
@@ -14531,18 +14581,23 @@ function unmountComponentAtNode(canvas, callback) {
14531
14581
  const fiber = root?.fiber;
14532
14582
  if (fiber) {
14533
14583
  const state = root?.store.getState();
14534
- if (state) state.internal.active = false;
14584
+ if (state) {
14585
+ state.internal.active = false;
14586
+ const unregisterRoot = state.internal.unregisterRoot;
14587
+ if (unregisterRoot) {
14588
+ unregisterRoot();
14589
+ state.internal.unregisterRoot = void 0;
14590
+ }
14591
+ }
14535
14592
  reconciler.updateContainer(null, fiber, null, () => {
14536
14593
  if (state) {
14537
14594
  setTimeout(() => {
14538
14595
  try {
14539
14596
  const renderer = state.internal.actualRenderer;
14540
- const unregisterRoot = state.internal.unregisterRoot;
14541
- if (unregisterRoot) unregisterRoot();
14542
14597
  const unregisterPrimary = state.internal.unregisterPrimary;
14543
14598
  if (unregisterPrimary) unregisterPrimary();
14544
14599
  const canvasTarget = state.internal.canvasTarget;
14545
- if (canvasTarget?.dispose) canvasTarget.dispose();
14600
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14546
14601
  state.events.disconnect?.();
14547
14602
  cleanupHelperGroup(root.store);
14548
14603
  if (state.isLegacy && renderer) {
@@ -14986,9 +15041,9 @@ function CanvasImpl({
14986
15041
  React__namespace.useMemo(() => extend(THREE), []);
14987
15042
  const Bridge = useBridge();
14988
15043
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
14989
- const hasInitialSizeRef = React__namespace.useRef(false);
15044
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
14990
15045
  const measureConfig = React__namespace.useMemo(() => {
14991
- if (!hasInitialSizeRef.current) {
15046
+ if (!hasInitialSize) {
14992
15047
  return {
14993
15048
  ...resize,
14994
15049
  scroll: resize?.scroll ?? true,
@@ -15000,7 +15055,7 @@ function CanvasImpl({
15000
15055
  debounce: { scroll: 50, resize: 0 },
15001
15056
  ...resize
15002
15057
  };
15003
- }, [resize, hasInitialSizeRef.current]);
15058
+ }, [hasInitialSize, resize]);
15004
15059
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15005
15060
  const effectiveSize = React__namespace.useMemo(() => {
15006
15061
  let w = width ?? containerRect.width;
@@ -15016,9 +15071,7 @@ function CanvasImpl({
15016
15071
  left: containerRect.left
15017
15072
  };
15018
15073
  }, [width, height, containerRect, forceEven]);
15019
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15020
- hasInitialSizeRef.current = true;
15021
- }
15074
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15022
15075
  const canvasRef = React__namespace.useRef(null);
15023
15076
  const divRef = React__namespace.useRef(null);
15024
15077
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15132,6 +15185,7 @@ function CanvasImpl({
15132
15185
  const canvas = canvasRef.current;
15133
15186
  if (canvas) {
15134
15187
  return () => {
15188
+ if (canvas.isConnected) return;
15135
15189
  unmountComponentAtNode(canvas);
15136
15190
  root.current = null;
15137
15191
  };