@react-three/fiber 10.0.0-canary.c6846c3 → 10.0.0-canary.ce4bd5f

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.d.ts CHANGED
@@ -38,9 +38,9 @@ function _mergeNamespaces(n, m) {
38
38
  declare const R3F_BUILD_LEGACY = true;
39
39
  declare const R3F_BUILD_WEBGPU = false;
40
40
 
41
- declare const Inspector: {
42
- new (): {};
43
- };
41
+ type Inspector = never;
42
+ /** Not available on the legacy (WebGL-only) entry. */
43
+ declare function loadInspector(): Promise<never>;
44
44
  declare const WebGPURenderer: {
45
45
  new (): {};
46
46
  };
@@ -58,7 +58,8 @@ var THREE = /*#__PURE__*/_mergeNamespaces({
58
58
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
59
59
  RenderTarget: RenderTarget,
60
60
  RenderTargetCompat: WebGLRenderTarget,
61
- WebGPURenderer: WebGPURenderer
61
+ WebGPURenderer: WebGPURenderer,
62
+ loadInspector: loadInspector
62
63
  }, [THREE$1]);
63
64
 
64
65
  //* Utility Types ==============================
@@ -496,6 +497,18 @@ interface InternalState {
496
497
  * @see https://threejs.org/docs/#api/en/renderers/common/CanvasTarget
497
498
  */
498
499
  canvasTarget?: CanvasTarget
500
+ /**
501
+ * Set when this root's canvas target has been resized and the backend's cached render pass
502
+ * descriptor (which holds a depth-stencil view built once per canvas) is therefore stale.
503
+ *
504
+ * Flushed by the canvas-target job in the `start` phase, which is the only place this root's
505
+ * target is guaranteed to be the renderer's active one — `backend.updateSize()` operates on
506
+ * whatever `getCanvasTarget()` returns, so calling it from the resize subscription would
507
+ * invalidate some other canvas's descriptor instead.
508
+ *
509
+ * @see https://github.com/pmndrs/react-three-fiber/issues/3847
510
+ */
511
+ canvasTargetSizeDirty?: boolean
499
512
  /**
500
513
  * Whether multi-canvas rendering is active.
501
514
  * True when any canvas uses `renderer={{ primaryCanvas: 'id' }}` to share a renderer.
@@ -705,12 +718,19 @@ type DefaultRendererProps = {
705
718
  */
706
719
  interface CanvasSchedulerConfig {
707
720
  /**
708
- * Render this canvas after another canvas completes.
709
- * Pass the `id` of another canvas.
721
+ * Run this Canvas root before the referenced Canvas root id(s).
722
+ */
723
+ before?: string | string[]
724
+ /**
725
+ * Run this Canvas root after the referenced Canvas root id(s).
726
+ */
727
+ after?: string | string[]
728
+ /**
729
+ * Numeric root order. Lower values run first.
710
730
  */
711
- after?: string
731
+ order?: number
712
732
  /**
713
- * Limit this canvas's render rate (frames per second).
733
+ * Limit this Canvas's default render job (frames per second).
714
734
  */
715
735
  fps?: number
716
736
  }
@@ -1314,11 +1334,16 @@ declare global {
1314
1334
  /** Pass record - stores TSL pass nodes for render pipeline */
1315
1335
  type PassRecord = Record<string, any>
1316
1336
 
1337
+ /** State passed to pipeline callbacks after the active pipeline has been created */
1338
+ type RenderPipelineCallbackState = RootState & {
1339
+ renderPipeline: ThreeRenderPipeline
1340
+ }
1341
+
1317
1342
  /** Setup callback - runs first to configure MRT, create additional passes */
1318
- type RenderPipelineSetupCallback = (state: RootState) => PassRecord | void
1343
+ type RenderPipelineSetupCallback = (state: RenderPipelineCallbackState) => PassRecord | void
1319
1344
 
1320
1345
  /** Main callback - runs second to configure outputNode, create effect passes */
1321
- type RenderPipelineMainCallback = (state: RootState) => PassRecord | void
1346
+ type RenderPipelineMainCallback = (state: RenderPipelineCallbackState) => PassRecord | void
1322
1347
 
1323
1348
  /** Return type for useRenderPipeline hook */
1324
1349
  interface UseRenderPipelineReturn {
@@ -1878,7 +1903,7 @@ declare namespace useLoader {
1878
1903
  *
1879
1904
  * @param callback - Function called each frame with (state, delta). Optional if you only need scheduler access.
1880
1905
  * @param priorityOrOptions - Either a priority number (backwards compat) or options object
1881
- * @returns Controls object with step(), stepAll(), pause(), resume(), isPaused, id, scheduler
1906
+ * @returns Controls object with step(), stepAll(), invalidate(), pause(), resume(), isPaused, id, rootId, scheduler
1882
1907
  *
1883
1908
  * @example
1884
1909
  * // Simple priority (backwards compat)
@@ -2174,7 +2199,10 @@ declare function addTail(callback: GlobalRenderCallback): () => void;
2174
2199
  * Invalidates the view, requesting a frame to be rendered.
2175
2200
  * In demand mode, this triggers the scheduler to run frames.
2176
2201
  *
2177
- * @param state - Optional root state (ignored in new scheduler, kept for backwards compat)
2202
+ * With a state argument, only that root's jobs run on the requested frames; other
2203
+ * roots on 'demand' stay idle. Without one, every registered root is invalidated.
2204
+ *
2205
+ * @param state - Optional root state; targets the invalidation at that root
2178
2206
  * @param frames - Number of frames to request (default: 1)
2179
2207
  * @param stackFrames - If false, sets pendingFrames to frames. If true, adds to existing pendingFrames (default: false)
2180
2208
  *
@@ -2185,14 +2213,16 @@ declare function invalidate(state?: RootState, frames?: number, stackFrames?: bo
2185
2213
  * Advances the frameloop and runs render effects.
2186
2214
  * Useful for when manually rendering via `frameloop="never"`.
2187
2215
  *
2216
+ * With a state argument, only that root is stepped. Without one, every
2217
+ * registered root is stepped for backwards compatibility.
2218
+ *
2188
2219
  * @param timestamp - The timestamp to use for this frame
2189
2220
  * @param runGlobalEffects - Ignored (kept for backwards compat, global effects always run)
2190
- * @param state - Ignored (kept for backwards compat)
2191
- * @param frame - Ignored (kept for backwards compat)
2221
+ * @param state - Optional root state; targets the manual step at that root
2192
2222
  *
2193
2223
  * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#advance
2194
2224
  */
2195
- declare function advance(timestamp: number): void;
2225
+ declare function advance(timestamp: number, runGlobalEffects?: boolean, state?: RootState): void;
2196
2226
 
2197
2227
  /* eslint-disable @definitelytyped/no-unnecessary-generics */
2198
2228
  declare function ReactReconciler<
package/dist/legacy.mjs CHANGED
@@ -30,11 +30,9 @@ function _mergeNamespaces(n, m) {
30
30
 
31
31
  const R3F_BUILD_LEGACY = true;
32
32
  const R3F_BUILD_WEBGPU = false;
33
- const Inspector = class Inspector2 {
34
- constructor() {
35
- throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
36
- }
37
- };
33
+ async function loadInspector() {
34
+ throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
35
+ }
38
36
  const WebGPURenderer = class WebGPURenderer2 {
39
37
  constructor() {
40
38
  throw new Error("WebGPURenderer is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
@@ -47,12 +45,12 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
47
45
  __proto__: null,
48
46
  CubeRenderTarget: CubeRenderTarget,
49
47
  CubeRenderTargetCompat: WebGLCubeRenderTarget,
50
- Inspector: Inspector,
51
48
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
52
49
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
53
50
  RenderTarget: RenderTarget,
54
51
  RenderTargetCompat: WebGLRenderTarget,
55
- WebGPURenderer: WebGPURenderer
52
+ WebGPURenderer: WebGPURenderer,
53
+ loadInspector: loadInspector
56
54
  }, [three]);
57
55
 
58
56
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -911,7 +909,7 @@ const createStore = (invalidate, advance) => {
911
909
  size: newSize,
912
910
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
913
911
  }));
914
- getScheduler().invalidate();
912
+ get().invalidate();
915
913
  }
916
914
  }
917
915
  return;
@@ -926,15 +924,16 @@ const createStore = (invalidate, advance) => {
926
924
  viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
927
925
  _sizeImperative: true
928
926
  }));
929
- getScheduler().invalidate();
927
+ get().invalidate();
930
928
  },
931
929
  setDpr: (dpr) => set((state2) => {
932
930
  const resolved = calculateDpr(dpr);
933
931
  return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
934
932
  }),
935
933
  setFrameloop: (frameloop = "always") => {
934
+ const rootId = get().internal.rootId;
935
+ if (rootId) getScheduler().setRootFrameloop(rootId, frameloop);
936
936
  set(() => ({ frameloop }));
937
- getScheduler().frameloop = frameloop;
938
937
  },
939
938
  setError: (error) => set(() => ({ error })),
940
939
  error: null,
@@ -1050,17 +1049,10 @@ const createStore = (invalidate, advance) => {
1050
1049
  oldSize = size;
1051
1050
  oldDpr = viewport.dpr;
1052
1051
  updateCamera(camera, size);
1053
- if (internal.isSecondary && canvasTarget) {
1054
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1055
- canvasTarget.setSize(size.width, size.height, false);
1056
- } else {
1057
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1058
- actualRenderer.setSize(size.width, size.height, false);
1059
- if (canvasTarget) {
1060
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1061
- canvasTarget.setSize(size.width, size.height, false);
1062
- }
1063
- }
1052
+ const resizeTarget = canvasTarget ?? actualRenderer;
1053
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1054
+ resizeTarget.setSize(size.width, size.height, false);
1055
+ internal.canvasTargetSizeDirty = true;
1064
1056
  }
1065
1057
  if (camera !== oldCamera) {
1066
1058
  oldCamera = camera;
@@ -1122,7 +1114,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1122
1114
  const results = cacheKeys.map(
1123
1115
  (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1124
1116
  );
1125
- return Array.isArray(input) ? results : results[0];
1117
+ const stableRef = useRef(null);
1118
+ const previous = stableRef.current;
1119
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1120
+ stableRef.current = stable;
1121
+ return Array.isArray(input) ? stable : stable[0];
1126
1122
  }
1127
1123
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1128
1124
  const inputs = Array.isArray(input) ? input : [input];
@@ -1242,6 +1238,13 @@ function useFrame(callback, priorityOrOptions) {
1242
1238
  * Use for controlling the entire frame loop, adding phases, etc.
1243
1239
  */
1244
1240
  scheduler: scheduler2,
1241
+ /**
1242
+ * The root that currently owns this job. Resolve on access because ambient
1243
+ * jobs can be adopted by a Canvas after the controls object is created.
1244
+ */
1245
+ get rootId() {
1246
+ return getScheduler().getJobRootId(id);
1247
+ },
1245
1248
  /**
1246
1249
  * Manually step this job only.
1247
1250
  * Bypasses FPS limiting - always runs.
@@ -1258,6 +1261,14 @@ function useFrame(callback, priorityOrOptions) {
1258
1261
  stepAll: (timestamp) => {
1259
1262
  getScheduler().step(timestamp);
1260
1263
  },
1264
+ /**
1265
+ * Request frames for this job's owning root without waking sibling roots.
1266
+ */
1267
+ invalidate: (frames, stackFrames) => {
1268
+ const scheduler3 = getScheduler();
1269
+ const rootId = scheduler3.getJobRootId(id);
1270
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1271
+ },
1261
1272
  /**
1262
1273
  * Pause this job (set enabled=false).
1263
1274
  * Job remains registered but won't run.
@@ -1287,6 +1298,11 @@ function getUrls(input) {
1287
1298
  if (Array.isArray(input)) return input;
1288
1299
  return Object.values(input);
1289
1300
  }
1301
+ function inputSignature(input) {
1302
+ if (typeof input === "string") return input;
1303
+ if (Array.isArray(input)) return input.join("\0");
1304
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1305
+ }
1290
1306
  function allUrlsCached(urls, textureCache) {
1291
1307
  return urls.every((url) => textureCache.has(url));
1292
1308
  }
@@ -1312,18 +1328,19 @@ function useTexture(input, optionsOrOnLoad) {
1312
1328
  const onLoadRef = useRef(onLoad);
1313
1329
  onLoadRef.current = onLoad;
1314
1330
  const onLoadCalledForRef = useRef(null);
1315
- const urls = useMemo(() => getUrls(input), [input]);
1331
+ const inputKey = useMemo(() => inputSignature(input), [input]);
1332
+ const stableInput = useMemo(() => input, [inputKey]);
1333
+ const urls = useMemo(() => getUrls(stableInput), [stableInput]);
1316
1334
  const cachedResult = useMemo(() => {
1317
1335
  if (!cache) return null;
1318
1336
  const textures = store.getState().textures;
1319
1337
  if (!allUrlsCached(urls, textures)) return null;
1320
- return buildFromCache(input, textures);
1321
- }, [cache, urls, input, store]);
1338
+ return buildFromCache(stableInput, textures);
1339
+ }, [cache, urls, stableInput, store]);
1322
1340
  const loadedTextures = useLoader(
1323
1341
  TextureLoader,
1324
- IsObject(input) ? Object.values(input) : input
1342
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1325
1343
  );
1326
- const inputKey = urls.join("\0");
1327
1344
  useLayoutEffect(() => {
1328
1345
  if (cachedResult) return;
1329
1346
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1350,28 +1367,28 @@ function useTexture(input, optionsOrOnLoad) {
1350
1367
  }, [renderer, loadedTextures, cachedResult]);
1351
1368
  const mappedTextures = useMemo(() => {
1352
1369
  if (cachedResult) return cachedResult;
1353
- if (IsObject(input)) {
1370
+ if (IsObject(stableInput)) {
1354
1371
  const keyed = {};
1355
1372
  const textureArray = loadedTextures;
1356
1373
  let i = 0;
1357
- for (const key in input) keyed[key] = textureArray[i++];
1374
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1358
1375
  return keyed;
1359
1376
  } else {
1360
1377
  return loadedTextures;
1361
1378
  }
1362
- }, [input, loadedTextures, cachedResult]);
1379
+ }, [stableInput, loadedTextures, cachedResult]);
1363
1380
  useEffect(() => {
1364
1381
  if (!cache) return;
1365
1382
  const urlTextureMap = [];
1366
- if (typeof input === "string") {
1367
- urlTextureMap.push([input, mappedTextures]);
1368
- } else if (Array.isArray(input)) {
1383
+ if (typeof stableInput === "string") {
1384
+ urlTextureMap.push([stableInput, mappedTextures]);
1385
+ } else if (Array.isArray(stableInput)) {
1369
1386
  const textureArray = mappedTextures;
1370
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1371
- } else if (IsObject(input)) {
1387
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1388
+ } else if (IsObject(stableInput)) {
1372
1389
  const textureRecord = mappedTextures;
1373
- for (const key in input) {
1374
- const url = input[key];
1390
+ for (const key in stableInput) {
1391
+ const url = stableInput[key];
1375
1392
  urlTextureMap.push([url, textureRecord[key]]);
1376
1393
  }
1377
1394
  }
@@ -1400,7 +1417,7 @@ function useTexture(input, optionsOrOnLoad) {
1400
1417
  }
1401
1418
  return { _textureRefs: refs };
1402
1419
  });
1403
- }, [cache, input, mappedTextures, store]);
1420
+ }, [cache, stableInput, mappedTextures, store]);
1404
1421
  return mappedTextures;
1405
1422
  }
1406
1423
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -1669,13 +1686,19 @@ function addTail(callback) {
1669
1686
  return getScheduler().onIdle(callback);
1670
1687
  }
1671
1688
  function invalidate(state, frames = 1, stackFrames = false) {
1672
- getScheduler().invalidate(frames, stackFrames);
1689
+ const rootId = state?.internal?.rootId;
1690
+ const scheduler = getScheduler();
1691
+ if (rootId) scheduler.invalidateRoot(rootId, frames, stackFrames);
1692
+ else scheduler.invalidate(frames, stackFrames);
1673
1693
  }
1674
- function advance(timestamp) {
1675
- getScheduler().step(timestamp);
1694
+ function advance(timestamp, runGlobalEffects, state) {
1695
+ const rootId = state?.internal?.rootId;
1696
+ const scheduler = getScheduler();
1697
+ if (rootId) scheduler.stepRoot(rootId, timestamp);
1698
+ else scheduler.step(timestamp);
1676
1699
  }
1677
1700
 
1678
- const version = "10.0.0-alpha.3";
1701
+ const version = "10.0.0-alpha.4";
1679
1702
  const packageData = {
1680
1703
  version: version};
1681
1704
 
@@ -14287,7 +14310,7 @@ function createRoot(canvas) {
14287
14310
  const handleXRFrame = (timestamp, _frame) => {
14288
14311
  const state2 = store.getState();
14289
14312
  if (state2.frameloop === "never") return;
14290
- advance(timestamp);
14313
+ advance(timestamp, true, state2);
14291
14314
  };
14292
14315
  const actualRenderer = state.internal.actualRenderer;
14293
14316
  const handleSessionChange = () => {
@@ -14295,7 +14318,7 @@ function createRoot(canvas) {
14295
14318
  const renderer2 = state2.internal.actualRenderer;
14296
14319
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14297
14320
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14298
- if (!renderer2.xr.isPresenting) invalidate();
14321
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14299
14322
  };
14300
14323
  const xr = {
14301
14324
  connect() {
@@ -14377,7 +14400,11 @@ function createRoot(canvas) {
14377
14400
  const newRootId = canvasId || scheduler.generateRootId();
14378
14401
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14379
14402
  getState: () => store.getState(),
14380
- onError: (err) => store.getState().setError(err)
14403
+ onError: (err) => store.getState().setError(err),
14404
+ frameloop: store.getState().frameloop,
14405
+ before: schedulerConfig?.before,
14406
+ after: schedulerConfig?.after,
14407
+ order: schedulerConfig?.order
14381
14408
  });
14382
14409
  const unregisterCanvasTarget = scheduler.register(
14383
14410
  () => {
@@ -14385,6 +14412,10 @@ function createRoot(canvas) {
14385
14412
  if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14386
14413
  const renderer2 = state2.internal.actualRenderer;
14387
14414
  renderer2.setCanvasTarget(state2.internal.canvasTarget);
14415
+ if (state2.internal.canvasTargetSizeDirty) {
14416
+ state2.internal.canvasTargetSizeDirty = false;
14417
+ renderer2.backend?.updateSize?.();
14418
+ }
14388
14419
  }
14389
14420
  },
14390
14421
  {
@@ -14453,8 +14484,7 @@ function createRoot(canvas) {
14453
14484
  phase: "render",
14454
14485
  system: true,
14455
14486
  // Internal flag: this is a system job, not user-controlled
14456
- // Apply scheduler config for render ordering and rate limiting
14457
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14487
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14458
14488
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14459
14489
  }
14460
14490
  );
@@ -14473,8 +14503,21 @@ function createRoot(canvas) {
14473
14503
  scheduler
14474
14504
  }
14475
14505
  }));
14506
+ } else {
14507
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14508
+ if (constraintsChanged) {
14509
+ scheduler.setRootConstraints(rootId, {
14510
+ before: schedulerConfig?.before,
14511
+ after: schedulerConfig?.after
14512
+ });
14513
+ }
14514
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14515
+ scheduler.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14516
+ }
14476
14517
  }
14477
- scheduler.frameloop = frameloop;
14518
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14519
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14520
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14478
14521
  onCreated = onCreatedCallback;
14479
14522
  configured = true;
14480
14523
  resolve();
@@ -14516,14 +14559,19 @@ function unmountComponentAtNode(canvas, callback) {
14516
14559
  const fiber = root?.fiber;
14517
14560
  if (fiber) {
14518
14561
  const state = root?.store.getState();
14519
- if (state) state.internal.active = false;
14562
+ if (state) {
14563
+ state.internal.active = false;
14564
+ const unregisterRoot = state.internal.unregisterRoot;
14565
+ if (unregisterRoot) {
14566
+ unregisterRoot();
14567
+ state.internal.unregisterRoot = void 0;
14568
+ }
14569
+ }
14520
14570
  reconciler.updateContainer(null, fiber, null, () => {
14521
14571
  if (state) {
14522
14572
  setTimeout(() => {
14523
14573
  try {
14524
14574
  const renderer = state.internal.actualRenderer;
14525
- const unregisterRoot = state.internal.unregisterRoot;
14526
- if (unregisterRoot) unregisterRoot();
14527
14575
  const unregisterPrimary = state.internal.unregisterPrimary;
14528
14576
  if (unregisterPrimary) unregisterPrimary();
14529
14577
  const canvasTarget = state.internal.canvasTarget;
@@ -14971,9 +15019,9 @@ function CanvasImpl({
14971
15019
  React.useMemo(() => extend(THREE), []);
14972
15020
  const Bridge = useBridge();
14973
15021
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
14974
- const hasInitialSizeRef = React.useRef(false);
15022
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
14975
15023
  const measureConfig = React.useMemo(() => {
14976
- if (!hasInitialSizeRef.current) {
15024
+ if (!hasInitialSize) {
14977
15025
  return {
14978
15026
  ...resize,
14979
15027
  scroll: resize?.scroll ?? true,
@@ -14985,7 +15033,7 @@ function CanvasImpl({
14985
15033
  debounce: { scroll: 50, resize: 0 },
14986
15034
  ...resize
14987
15035
  };
14988
- }, [resize, hasInitialSizeRef.current]);
15036
+ }, [hasInitialSize, resize]);
14989
15037
  const [containerRef, containerRect] = useMeasure(measureConfig);
14990
15038
  const effectiveSize = React.useMemo(() => {
14991
15039
  let w = width ?? containerRect.width;
@@ -15001,9 +15049,7 @@ function CanvasImpl({
15001
15049
  left: containerRect.left
15002
15050
  };
15003
15051
  }, [width, height, containerRect, forceEven]);
15004
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15005
- hasInitialSizeRef.current = true;
15006
- }
15052
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15007
15053
  const canvasRef = React.useRef(null);
15008
15054
  const divRef = React.useRef(null);
15009
15055
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15117,6 +15163,7 @@ function CanvasImpl({
15117
15163
  const canvas = canvasRef.current;
15118
15164
  if (canvas) {
15119
15165
  return () => {
15166
+ if (canvas.isConnected) return;
15120
15167
  unmountComponentAtNode(canvas);
15121
15168
  root.current = null;
15122
15169
  };