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

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
@@ -2,7 +2,6 @@
2
2
 
3
3
  const webgpu = require('three/webgpu');
4
4
  const three = require('three');
5
- const Inspector_js = require('three/addons/inspector/Inspector.js');
6
5
  const jsxRuntime = require('react/jsx-runtime');
7
6
  const React = require('react');
8
7
  const useMeasure = require('react-use-measure');
@@ -52,18 +51,22 @@ const Tb__default = /*#__PURE__*/_interopDefaultCompat(Tb);
52
51
 
53
52
  const R3F_BUILD_LEGACY = true;
54
53
  const R3F_BUILD_WEBGPU = true;
54
+ async function loadInspector() {
55
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
56
+ return Inspector;
57
+ }
55
58
 
56
59
  const THREE = /*#__PURE__*/_mergeNamespaces({
57
60
  __proto__: null,
58
61
  CubeRenderTarget: webgpu.CubeRenderTarget,
59
62
  CubeRenderTargetCompat: webgpu.CubeRenderTarget,
60
- Inspector: Inspector_js.Inspector,
61
63
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
62
64
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
63
65
  RenderTargetCompat: webgpu.RenderTarget,
64
66
  WebGLCubeRenderTarget: three.WebGLCubeRenderTarget,
65
67
  WebGLRenderTarget: three.WebGLRenderTarget,
66
- WebGLRenderer: three.WebGLRenderer
68
+ WebGLRenderer: three.WebGLRenderer,
69
+ loadInspector: loadInspector
67
70
  }, [webgpu__namespace]);
68
71
 
69
72
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -1061,17 +1064,10 @@ const createStore = (invalidate, advance) => {
1061
1064
  oldSize = size;
1062
1065
  oldDpr = viewport.dpr;
1063
1066
  updateCamera(camera, size);
1064
- if (internal.isSecondary && canvasTarget) {
1065
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1066
- canvasTarget.setSize(size.width, size.height, false);
1067
- } else {
1068
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1069
- actualRenderer.setSize(size.width, size.height, false);
1070
- if (canvasTarget) {
1071
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1072
- canvasTarget.setSize(size.width, size.height, false);
1073
- }
1074
- }
1067
+ const resizeTarget = canvasTarget ?? actualRenderer;
1068
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1069
+ resizeTarget.setSize(size.width, size.height, false);
1070
+ internal.canvasTargetSizeDirty = true;
1075
1071
  }
1076
1072
  if (camera !== oldCamera) {
1077
1073
  oldCamera = camera;
@@ -1133,7 +1129,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1133
1129
  const results = cacheKeys.map(
1134
1130
  (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1135
1131
  );
1136
- return Array.isArray(input) ? results : results[0];
1132
+ const stableRef = React.useRef(null);
1133
+ const previous = stableRef.current;
1134
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1135
+ stableRef.current = stable;
1136
+ return Array.isArray(input) ? stable : stable[0];
1137
1137
  }
1138
1138
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1139
1139
  const inputs = Array.isArray(input) ? input : [input];
@@ -1298,6 +1298,11 @@ function getUrls(input) {
1298
1298
  if (Array.isArray(input)) return input;
1299
1299
  return Object.values(input);
1300
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
+ }
1301
1306
  function allUrlsCached(urls, textureCache) {
1302
1307
  return urls.every((url) => textureCache.has(url));
1303
1308
  }
@@ -1323,18 +1328,19 @@ function useTexture(input, optionsOrOnLoad) {
1323
1328
  const onLoadRef = React.useRef(onLoad);
1324
1329
  onLoadRef.current = onLoad;
1325
1330
  const onLoadCalledForRef = React.useRef(null);
1326
- const urls = React.useMemo(() => getUrls(input), [input]);
1331
+ const inputKey = React.useMemo(() => inputSignature(input), [input]);
1332
+ const stableInput = React.useMemo(() => input, [inputKey]);
1333
+ const urls = React.useMemo(() => getUrls(stableInput), [stableInput]);
1327
1334
  const cachedResult = React.useMemo(() => {
1328
1335
  if (!cache) return null;
1329
1336
  const textures = store.getState().textures;
1330
1337
  if (!allUrlsCached(urls, textures)) return null;
1331
- return buildFromCache(input, textures);
1332
- }, [cache, urls, input, store]);
1338
+ return buildFromCache(stableInput, textures);
1339
+ }, [cache, urls, stableInput, store]);
1333
1340
  const loadedTextures = useLoader(
1334
1341
  webgpu.TextureLoader,
1335
- IsObject(input) ? Object.values(input) : input
1342
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1336
1343
  );
1337
- const inputKey = urls.join("\0");
1338
1344
  React.useLayoutEffect(() => {
1339
1345
  if (cachedResult) return;
1340
1346
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1361,28 +1367,28 @@ function useTexture(input, optionsOrOnLoad) {
1361
1367
  }, [renderer, loadedTextures, cachedResult]);
1362
1368
  const mappedTextures = React.useMemo(() => {
1363
1369
  if (cachedResult) return cachedResult;
1364
- if (IsObject(input)) {
1370
+ if (IsObject(stableInput)) {
1365
1371
  const keyed = {};
1366
1372
  const textureArray = loadedTextures;
1367
1373
  let i = 0;
1368
- for (const key in input) keyed[key] = textureArray[i++];
1374
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1369
1375
  return keyed;
1370
1376
  } else {
1371
1377
  return loadedTextures;
1372
1378
  }
1373
- }, [input, loadedTextures, cachedResult]);
1379
+ }, [stableInput, loadedTextures, cachedResult]);
1374
1380
  React.useEffect(() => {
1375
1381
  if (!cache) return;
1376
1382
  const urlTextureMap = [];
1377
- if (typeof input === "string") {
1378
- urlTextureMap.push([input, mappedTextures]);
1379
- } else if (Array.isArray(input)) {
1383
+ if (typeof stableInput === "string") {
1384
+ urlTextureMap.push([stableInput, mappedTextures]);
1385
+ } else if (Array.isArray(stableInput)) {
1380
1386
  const textureArray = mappedTextures;
1381
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1382
- } else if (IsObject(input)) {
1387
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1388
+ } else if (IsObject(stableInput)) {
1383
1389
  const textureRecord = mappedTextures;
1384
- for (const key in input) {
1385
- const url = input[key];
1390
+ for (const key in stableInput) {
1391
+ const url = stableInput[key];
1386
1392
  urlTextureMap.push([url, textureRecord[key]]);
1387
1393
  }
1388
1394
  }
@@ -1411,7 +1417,7 @@ function useTexture(input, optionsOrOnLoad) {
1411
1417
  }
1412
1418
  return { _textureRefs: refs };
1413
1419
  });
1414
- }, [cache, input, mappedTextures, store]);
1420
+ }, [cache, stableInput, mappedTextures, store]);
1415
1421
  return mappedTextures;
1416
1422
  }
1417
1423
  useTexture.preload = (url) => useLoader.preload(webgpu.TextureLoader, url);
@@ -12971,7 +12977,10 @@ function releaseInternalPointerCapture(internal, obj, pointerId) {
12971
12977
  const captureData = pointerState.captured.get(obj);
12972
12978
  if (captureData) {
12973
12979
  pointerState.captured.delete(obj);
12974
- captureData.target.releasePointerCapture(pointerId);
12980
+ try {
12981
+ captureData.target.releasePointerCapture?.(pointerId);
12982
+ } catch {
12983
+ }
12975
12984
  }
12976
12985
  }
12977
12986
  function swapInteractivity(store, object, newObject) {
@@ -13065,7 +13074,9 @@ function createEvents(store) {
13065
13074
  state2.events.compute?.(event, state2, state2.previousRoot?.getState());
13066
13075
  if (state2.raycaster.camera === void 0) state2.raycaster.camera = null;
13067
13076
  }
13068
- return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
13077
+ if (!state2.raycaster.camera) return [];
13078
+ obj.updateWorldMatrix(true, true);
13079
+ return state2.raycaster.intersectObject(obj, true);
13069
13080
  }
13070
13081
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
13071
13082
  const aInteractivePriority = a.object.userData?.interactivePriority;
@@ -14440,6 +14451,10 @@ function createRoot(canvas) {
14440
14451
  if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14441
14452
  const renderer2 = state2.internal.actualRenderer;
14442
14453
  renderer2.setCanvasTarget(state2.internal.canvasTarget);
14454
+ if (state2.internal.canvasTargetSizeDirty) {
14455
+ state2.internal.canvasTargetSizeDirty = false;
14456
+ renderer2.backend?.updateSize?.();
14457
+ }
14443
14458
  }
14444
14459
  },
14445
14460
  {
@@ -15172,6 +15187,7 @@ function CanvasImpl({
15172
15187
  const canvas = canvasRef.current;
15173
15188
  if (canvas) {
15174
15189
  return () => {
15190
+ if (canvas.isConnected) return;
15175
15191
  unmountComponentAtNode(canvas);
15176
15192
  root.current = null;
15177
15193
  };
package/dist/index.d.cts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as three_examples_jsm_inspector_Inspector_js from 'three/examples/jsm/inspector/Inspector.js';
1
2
  import * as three_webgpu from 'three/webgpu';
2
3
  import { CubeRenderTarget, RenderTarget, WebGPURenderer, Node, StorageTexture, Data3DTexture, CanvasTarget, ShaderNodeObject, Euler as Euler$2, Color as Color$2, ColorRepresentation as ColorRepresentation$1, Layers as Layers$1, Raycaster, Intersection as Intersection$1, BufferGeometry, Matrix4 as Matrix4$1, Quaternion as Quaternion$1, Vector2 as Vector2$1, Vector3 as Vector3$1, Vector4 as Vector4$1, Matrix3 as Matrix3$1, Loader as Loader$1, ColorSpace, Texture as Texture$1, CubeTexture, Scene, Object3D, Frustum, OrthographicCamera } from 'three/webgpu';
3
4
  import * as THREE$1 from 'three';
@@ -47,6 +48,9 @@ function _mergeNamespaces(n, m) {
47
48
  declare const R3F_BUILD_LEGACY = true;
48
49
  declare const R3F_BUILD_WEBGPU = true;
49
50
 
51
+ /** Lazily load the three Inspector. Keeps it out of the eager module graph — see note above. */
52
+ declare function loadInspector(): Promise<typeof three_examples_jsm_inspector_Inspector_js.Inspector>;
53
+
50
54
  var THREE = /*#__PURE__*/_mergeNamespaces({
51
55
  __proto__: null,
52
56
  CubeRenderTarget: CubeRenderTarget,
@@ -58,7 +62,8 @@ var THREE = /*#__PURE__*/_mergeNamespaces({
58
62
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
59
63
  WebGLRenderTarget: WebGLRenderTarget,
60
64
  WebGLRenderer: WebGLRenderer,
61
- WebGLRendererParameters: WebGLRendererParameters
65
+ WebGLRendererParameters: WebGLRendererParameters,
66
+ loadInspector: loadInspector
62
67
  }, [three_webgpu]);
63
68
 
64
69
  //* Utility Types ==============================
@@ -496,6 +501,18 @@ interface InternalState {
496
501
  * @see https://threejs.org/docs/#api/en/renderers/common/CanvasTarget
497
502
  */
498
503
  canvasTarget?: CanvasTarget
504
+ /**
505
+ * Set when this root's canvas target has been resized and the backend's cached render pass
506
+ * descriptor (which holds a depth-stencil view built once per canvas) is therefore stale.
507
+ *
508
+ * Flushed by the canvas-target job in the `start` phase, which is the only place this root's
509
+ * target is guaranteed to be the renderer's active one — `backend.updateSize()` operates on
510
+ * whatever `getCanvasTarget()` returns, so calling it from the resize subscription would
511
+ * invalidate some other canvas's descriptor instead.
512
+ *
513
+ * @see https://github.com/pmndrs/react-three-fiber/issues/3847
514
+ */
515
+ canvasTargetSizeDirty?: boolean
499
516
  /**
500
517
  * Whether multi-canvas rendering is active.
501
518
  * True when any canvas uses `renderer={{ primaryCanvas: 'id' }}` to share a renderer.
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as three_examples_jsm_inspector_Inspector_js from 'three/examples/jsm/inspector/Inspector.js';
1
2
  import * as three_webgpu from 'three/webgpu';
2
3
  import { CubeRenderTarget, RenderTarget, WebGPURenderer, Node, StorageTexture, Data3DTexture, CanvasTarget, ShaderNodeObject, Euler as Euler$2, Color as Color$2, ColorRepresentation as ColorRepresentation$1, Layers as Layers$1, Raycaster, Intersection as Intersection$1, BufferGeometry, Matrix4 as Matrix4$1, Quaternion as Quaternion$1, Vector2 as Vector2$1, Vector3 as Vector3$1, Vector4 as Vector4$1, Matrix3 as Matrix3$1, Loader as Loader$1, ColorSpace, Texture as Texture$1, CubeTexture, Scene, Object3D, Frustum, OrthographicCamera } from 'three/webgpu';
3
4
  import * as THREE$1 from 'three';
@@ -47,6 +48,9 @@ function _mergeNamespaces(n, m) {
47
48
  declare const R3F_BUILD_LEGACY = true;
48
49
  declare const R3F_BUILD_WEBGPU = true;
49
50
 
51
+ /** Lazily load the three Inspector. Keeps it out of the eager module graph — see note above. */
52
+ declare function loadInspector(): Promise<typeof three_examples_jsm_inspector_Inspector_js.Inspector>;
53
+
50
54
  var THREE = /*#__PURE__*/_mergeNamespaces({
51
55
  __proto__: null,
52
56
  CubeRenderTarget: CubeRenderTarget,
@@ -58,7 +62,8 @@ var THREE = /*#__PURE__*/_mergeNamespaces({
58
62
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
59
63
  WebGLRenderTarget: WebGLRenderTarget,
60
64
  WebGLRenderer: WebGLRenderer,
61
- WebGLRendererParameters: WebGLRendererParameters
65
+ WebGLRendererParameters: WebGLRendererParameters,
66
+ loadInspector: loadInspector
62
67
  }, [three_webgpu]);
63
68
 
64
69
  //* Utility Types ==============================
@@ -496,6 +501,18 @@ interface InternalState {
496
501
  * @see https://threejs.org/docs/#api/en/renderers/common/CanvasTarget
497
502
  */
498
503
  canvasTarget?: CanvasTarget
504
+ /**
505
+ * Set when this root's canvas target has been resized and the backend's cached render pass
506
+ * descriptor (which holds a depth-stencil view built once per canvas) is therefore stale.
507
+ *
508
+ * Flushed by the canvas-target job in the `start` phase, which is the only place this root's
509
+ * target is guaranteed to be the renderer's active one — `backend.updateSize()` operates on
510
+ * whatever `getCanvasTarget()` returns, so calling it from the resize subscription would
511
+ * invalidate some other canvas's descriptor instead.
512
+ *
513
+ * @see https://github.com/pmndrs/react-three-fiber/issues/3847
514
+ */
515
+ canvasTargetSizeDirty?: boolean
499
516
  /**
500
517
  * Whether multi-canvas rendering is active.
501
518
  * True when any canvas uses `renderer={{ primaryCanvas: 'id' }}` to share a renderer.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as three_examples_jsm_inspector_Inspector_js from 'three/examples/jsm/inspector/Inspector.js';
1
2
  import * as three_webgpu from 'three/webgpu';
2
3
  import { CubeRenderTarget, RenderTarget, WebGPURenderer, Node, StorageTexture, Data3DTexture, CanvasTarget, ShaderNodeObject, Euler as Euler$2, Color as Color$2, ColorRepresentation as ColorRepresentation$1, Layers as Layers$1, Raycaster, Intersection as Intersection$1, BufferGeometry, Matrix4 as Matrix4$1, Quaternion as Quaternion$1, Vector2 as Vector2$1, Vector3 as Vector3$1, Vector4 as Vector4$1, Matrix3 as Matrix3$1, Loader as Loader$1, ColorSpace, Texture as Texture$1, CubeTexture, Scene, Object3D, Frustum, OrthographicCamera } from 'three/webgpu';
3
4
  import * as THREE$1 from 'three';
@@ -47,6 +48,9 @@ function _mergeNamespaces(n, m) {
47
48
  declare const R3F_BUILD_LEGACY = true;
48
49
  declare const R3F_BUILD_WEBGPU = true;
49
50
 
51
+ /** Lazily load the three Inspector. Keeps it out of the eager module graph — see note above. */
52
+ declare function loadInspector(): Promise<typeof three_examples_jsm_inspector_Inspector_js.Inspector>;
53
+
50
54
  var THREE = /*#__PURE__*/_mergeNamespaces({
51
55
  __proto__: null,
52
56
  CubeRenderTarget: CubeRenderTarget,
@@ -58,7 +62,8 @@ var THREE = /*#__PURE__*/_mergeNamespaces({
58
62
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
59
63
  WebGLRenderTarget: WebGLRenderTarget,
60
64
  WebGLRenderer: WebGLRenderer,
61
- WebGLRendererParameters: WebGLRendererParameters
65
+ WebGLRendererParameters: WebGLRendererParameters,
66
+ loadInspector: loadInspector
62
67
  }, [three_webgpu]);
63
68
 
64
69
  //* Utility Types ==============================
@@ -496,6 +501,18 @@ interface InternalState {
496
501
  * @see https://threejs.org/docs/#api/en/renderers/common/CanvasTarget
497
502
  */
498
503
  canvasTarget?: CanvasTarget
504
+ /**
505
+ * Set when this root's canvas target has been resized and the backend's cached render pass
506
+ * descriptor (which holds a depth-stencil view built once per canvas) is therefore stale.
507
+ *
508
+ * Flushed by the canvas-target job in the `start` phase, which is the only place this root's
509
+ * target is guaranteed to be the renderer's active one — `backend.updateSize()` operates on
510
+ * whatever `getCanvasTarget()` returns, so calling it from the resize subscription would
511
+ * invalidate some other canvas's descriptor instead.
512
+ *
513
+ * @see https://github.com/pmndrs/react-three-fiber/issues/3847
514
+ */
515
+ canvasTargetSizeDirty?: boolean
499
516
  /**
500
517
  * Whether multi-canvas rendering is active.
501
518
  * True when any canvas uses `renderer={{ primaryCanvas: 'id' }}` to share a renderer.
package/dist/index.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  import * as webgpu from 'three/webgpu';
2
2
  import { CubeRenderTarget, RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, ShaderMaterial, Uniform, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, HalfFloatType, Color } from 'three/webgpu';
3
3
  import { WebGLCubeRenderTarget, WebGLRenderTarget, WebGLRenderer } from 'three';
4
- import { Inspector } from 'three/addons/inspector/Inspector.js';
5
4
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
6
5
  import * as React from 'react';
7
6
  import React__default, { useRef, useMemo, useLayoutEffect, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
@@ -32,18 +31,22 @@ function _mergeNamespaces(n, m) {
32
31
 
33
32
  const R3F_BUILD_LEGACY = true;
34
33
  const R3F_BUILD_WEBGPU = true;
34
+ async function loadInspector() {
35
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
36
+ return Inspector;
37
+ }
35
38
 
36
39
  const THREE = /*#__PURE__*/_mergeNamespaces({
37
40
  __proto__: null,
38
41
  CubeRenderTarget: CubeRenderTarget,
39
42
  CubeRenderTargetCompat: CubeRenderTarget,
40
- Inspector: Inspector,
41
43
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
42
44
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
43
45
  RenderTargetCompat: RenderTarget,
44
46
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
45
47
  WebGLRenderTarget: WebGLRenderTarget,
46
- WebGLRenderer: WebGLRenderer
48
+ WebGLRenderer: WebGLRenderer,
49
+ loadInspector: loadInspector
47
50
  }, [webgpu]);
48
51
 
49
52
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -1041,17 +1044,10 @@ const createStore = (invalidate, advance) => {
1041
1044
  oldSize = size;
1042
1045
  oldDpr = viewport.dpr;
1043
1046
  updateCamera(camera, size);
1044
- if (internal.isSecondary && canvasTarget) {
1045
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1046
- canvasTarget.setSize(size.width, size.height, false);
1047
- } else {
1048
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1049
- actualRenderer.setSize(size.width, size.height, false);
1050
- if (canvasTarget) {
1051
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1052
- canvasTarget.setSize(size.width, size.height, false);
1053
- }
1054
- }
1047
+ const resizeTarget = canvasTarget ?? actualRenderer;
1048
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
1049
+ resizeTarget.setSize(size.width, size.height, false);
1050
+ internal.canvasTargetSizeDirty = true;
1055
1051
  }
1056
1052
  if (camera !== oldCamera) {
1057
1053
  oldCamera = camera;
@@ -1113,7 +1109,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1113
1109
  const results = cacheKeys.map(
1114
1110
  (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1115
1111
  );
1116
- return Array.isArray(input) ? results : results[0];
1112
+ const stableRef = useRef(null);
1113
+ const previous = stableRef.current;
1114
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
1115
+ stableRef.current = stable;
1116
+ return Array.isArray(input) ? stable : stable[0];
1117
1117
  }
1118
1118
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1119
1119
  const inputs = Array.isArray(input) ? input : [input];
@@ -1278,6 +1278,11 @@ function getUrls(input) {
1278
1278
  if (Array.isArray(input)) return input;
1279
1279
  return Object.values(input);
1280
1280
  }
1281
+ function inputSignature(input) {
1282
+ if (typeof input === "string") return input;
1283
+ if (Array.isArray(input)) return input.join("\0");
1284
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1285
+ }
1281
1286
  function allUrlsCached(urls, textureCache) {
1282
1287
  return urls.every((url) => textureCache.has(url));
1283
1288
  }
@@ -1303,18 +1308,19 @@ function useTexture(input, optionsOrOnLoad) {
1303
1308
  const onLoadRef = useRef(onLoad);
1304
1309
  onLoadRef.current = onLoad;
1305
1310
  const onLoadCalledForRef = useRef(null);
1306
- const urls = useMemo(() => getUrls(input), [input]);
1311
+ const inputKey = useMemo(() => inputSignature(input), [input]);
1312
+ const stableInput = useMemo(() => input, [inputKey]);
1313
+ const urls = useMemo(() => getUrls(stableInput), [stableInput]);
1307
1314
  const cachedResult = useMemo(() => {
1308
1315
  if (!cache) return null;
1309
1316
  const textures = store.getState().textures;
1310
1317
  if (!allUrlsCached(urls, textures)) return null;
1311
- return buildFromCache(input, textures);
1312
- }, [cache, urls, input, store]);
1318
+ return buildFromCache(stableInput, textures);
1319
+ }, [cache, urls, stableInput, store]);
1313
1320
  const loadedTextures = useLoader(
1314
1321
  TextureLoader,
1315
- IsObject(input) ? Object.values(input) : input
1322
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1316
1323
  );
1317
- const inputKey = urls.join("\0");
1318
1324
  useLayoutEffect(() => {
1319
1325
  if (cachedResult) return;
1320
1326
  if (onLoadCalledForRef.current === inputKey) return;
@@ -1341,28 +1347,28 @@ function useTexture(input, optionsOrOnLoad) {
1341
1347
  }, [renderer, loadedTextures, cachedResult]);
1342
1348
  const mappedTextures = useMemo(() => {
1343
1349
  if (cachedResult) return cachedResult;
1344
- if (IsObject(input)) {
1350
+ if (IsObject(stableInput)) {
1345
1351
  const keyed = {};
1346
1352
  const textureArray = loadedTextures;
1347
1353
  let i = 0;
1348
- for (const key in input) keyed[key] = textureArray[i++];
1354
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1349
1355
  return keyed;
1350
1356
  } else {
1351
1357
  return loadedTextures;
1352
1358
  }
1353
- }, [input, loadedTextures, cachedResult]);
1359
+ }, [stableInput, loadedTextures, cachedResult]);
1354
1360
  useEffect(() => {
1355
1361
  if (!cache) return;
1356
1362
  const urlTextureMap = [];
1357
- if (typeof input === "string") {
1358
- urlTextureMap.push([input, mappedTextures]);
1359
- } else if (Array.isArray(input)) {
1363
+ if (typeof stableInput === "string") {
1364
+ urlTextureMap.push([stableInput, mappedTextures]);
1365
+ } else if (Array.isArray(stableInput)) {
1360
1366
  const textureArray = mappedTextures;
1361
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1362
- } else if (IsObject(input)) {
1367
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1368
+ } else if (IsObject(stableInput)) {
1363
1369
  const textureRecord = mappedTextures;
1364
- for (const key in input) {
1365
- const url = input[key];
1370
+ for (const key in stableInput) {
1371
+ const url = stableInput[key];
1366
1372
  urlTextureMap.push([url, textureRecord[key]]);
1367
1373
  }
1368
1374
  }
@@ -1391,7 +1397,7 @@ function useTexture(input, optionsOrOnLoad) {
1391
1397
  }
1392
1398
  return { _textureRefs: refs };
1393
1399
  });
1394
- }, [cache, input, mappedTextures, store]);
1400
+ }, [cache, stableInput, mappedTextures, store]);
1395
1401
  return mappedTextures;
1396
1402
  }
1397
1403
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -12951,7 +12957,10 @@ function releaseInternalPointerCapture(internal, obj, pointerId) {
12951
12957
  const captureData = pointerState.captured.get(obj);
12952
12958
  if (captureData) {
12953
12959
  pointerState.captured.delete(obj);
12954
- captureData.target.releasePointerCapture(pointerId);
12960
+ try {
12961
+ captureData.target.releasePointerCapture?.(pointerId);
12962
+ } catch {
12963
+ }
12955
12964
  }
12956
12965
  }
12957
12966
  function swapInteractivity(store, object, newObject) {
@@ -13045,7 +13054,9 @@ function createEvents(store) {
13045
13054
  state2.events.compute?.(event, state2, state2.previousRoot?.getState());
13046
13055
  if (state2.raycaster.camera === void 0) state2.raycaster.camera = null;
13047
13056
  }
13048
- return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
13057
+ if (!state2.raycaster.camera) return [];
13058
+ obj.updateWorldMatrix(true, true);
13059
+ return state2.raycaster.intersectObject(obj, true);
13049
13060
  }
13050
13061
  let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
13051
13062
  const aInteractivePriority = a.object.userData?.interactivePriority;
@@ -14420,6 +14431,10 @@ function createRoot(canvas) {
14420
14431
  if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14421
14432
  const renderer2 = state2.internal.actualRenderer;
14422
14433
  renderer2.setCanvasTarget(state2.internal.canvasTarget);
14434
+ if (state2.internal.canvasTargetSizeDirty) {
14435
+ state2.internal.canvasTargetSizeDirty = false;
14436
+ renderer2.backend?.updateSize?.();
14437
+ }
14423
14438
  }
14424
14439
  },
14425
14440
  {
@@ -15152,6 +15167,7 @@ function CanvasImpl({
15152
15167
  const canvas = canvasRef.current;
15153
15168
  if (canvas) {
15154
15169
  return () => {
15170
+ if (canvas.isConnected) return;
15155
15171
  unmountComponentAtNode(canvas);
15156
15172
  root.current = null;
15157
15173
  };