@react-three/fiber 10.0.0-alpha.1 → 10.0.0-canary.b0fafc8

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.mjs CHANGED
@@ -1,10 +1,16 @@
1
1
  import * as three from 'three';
2
- import { WebGLRenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, SRGBColorSpace, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFSoftShadowMap, VSMShadowMap, PCFShadowMap, BasicShadowMap, ColorManagement, LinearSRGBColorSpace, NoToneMapping, ACESFilmicToneMapping, WebGLRenderer } from 'three';
3
- import { jsx, Fragment } from 'react/jsx-runtime';
2
+ import { WebGLRenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene, WebGLCubeRenderTarget, HalfFloatType, Color, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, Raycaster, OrthographicCamera, PerspectiveCamera, PCFSoftShadowMap, VSMShadowMap, PCFShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGLRenderer } from 'three';
3
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
4
  import * as React from 'react';
5
- import React__default, { useMemo, useLayoutEffect, useEffect, useContext, useRef, useImperativeHandle, useCallback, useState } from 'react';
5
+ import React__default, { useLayoutEffect, useRef, useMemo, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
6
6
  import useMeasure from 'react-use-measure';
7
7
  import { useFiber, useContextBridge, traverseFiber, FiberProvider } from 'its-fine';
8
+ import { useThree as useThree$1, useLoader as useLoader$1, useFrame as useFrame$1, createPortal as createPortal$1, applyProps as applyProps$1, extend as extend$1 } from '@react-three/fiber';
9
+ import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
10
+ import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
11
+ import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
12
+ import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
13
+ import { GainMapLoader } from '@monogrid/gainmap-js';
8
14
  import Tb, { unstable_scheduleCallback, unstable_IdlePriority } from 'scheduler';
9
15
  import { createWithEqualityFn } from 'zustand/traditional';
10
16
  import { suspend, preload, clear } from 'suspend-react';
@@ -45,6 +51,374 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
45
51
  WebGPURenderer: WebGPURenderer
46
52
  }, [three]);
47
53
 
54
+ const primaryRegistry = /* @__PURE__ */ new Map();
55
+ const pendingSubscribers = /* @__PURE__ */ new Map();
56
+ function registerPrimary(id, renderer, store) {
57
+ if (primaryRegistry.has(id)) {
58
+ console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
59
+ }
60
+ const entry = { renderer, store };
61
+ primaryRegistry.set(id, entry);
62
+ const subscribers = pendingSubscribers.get(id);
63
+ if (subscribers) {
64
+ subscribers.forEach((callback) => callback(entry));
65
+ pendingSubscribers.delete(id);
66
+ }
67
+ return () => {
68
+ const currentEntry = primaryRegistry.get(id);
69
+ if (currentEntry?.renderer === renderer) {
70
+ primaryRegistry.delete(id);
71
+ }
72
+ };
73
+ }
74
+ function getPrimary(id) {
75
+ return primaryRegistry.get(id);
76
+ }
77
+ function waitForPrimary(id, timeout = 5e3) {
78
+ const existing = primaryRegistry.get(id);
79
+ if (existing) {
80
+ return Promise.resolve(existing);
81
+ }
82
+ return new Promise((resolve, reject) => {
83
+ const timeoutId = setTimeout(() => {
84
+ const subscribers = pendingSubscribers.get(id);
85
+ if (subscribers) {
86
+ const index = subscribers.indexOf(callback);
87
+ if (index !== -1) subscribers.splice(index, 1);
88
+ if (subscribers.length === 0) pendingSubscribers.delete(id);
89
+ }
90
+ reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
91
+ }, timeout);
92
+ const callback = (entry) => {
93
+ clearTimeout(timeoutId);
94
+ resolve(entry);
95
+ };
96
+ if (!pendingSubscribers.has(id)) {
97
+ pendingSubscribers.set(id, []);
98
+ }
99
+ pendingSubscribers.get(id).push(callback);
100
+ });
101
+ }
102
+ function hasPrimary(id) {
103
+ return primaryRegistry.has(id);
104
+ }
105
+ function unregisterPrimary(id) {
106
+ primaryRegistry.delete(id);
107
+ }
108
+ function getPrimaryIds() {
109
+ return Array.from(primaryRegistry.keys());
110
+ }
111
+
112
+ const presetsObj = {
113
+ apartment: "lebombo_1k.hdr",
114
+ city: "potsdamer_platz_1k.hdr",
115
+ dawn: "kiara_1_dawn_1k.hdr",
116
+ forest: "forest_slope_1k.hdr",
117
+ lobby: "st_fagans_interior_1k.hdr",
118
+ night: "dikhololo_night_1k.hdr",
119
+ park: "rooitou_park_1k.hdr",
120
+ studio: "studio_small_03_1k.hdr",
121
+ sunset: "venice_sunset_1k.hdr",
122
+ warehouse: "empty_warehouse_01_1k.hdr"
123
+ };
124
+
125
+ const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
126
+ const isArray = (arr) => Array.isArray(arr);
127
+ const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
128
+ function useEnvironment({
129
+ files = defaultFiles,
130
+ path = "",
131
+ preset = void 0,
132
+ colorSpace = void 0,
133
+ extensions
134
+ } = {}) {
135
+ if (preset) {
136
+ validatePreset(preset);
137
+ files = presetsObj[preset];
138
+ path = CUBEMAP_ROOT;
139
+ }
140
+ const multiFile = isArray(files);
141
+ const { extension, isCubemap } = getExtension(files);
142
+ const loader = getLoader$1(extension);
143
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
144
+ const renderer = useThree$1((state) => state.renderer);
145
+ useLayoutEffect(() => {
146
+ if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
147
+ function clearGainmapTexture() {
148
+ useLoader$1.clear(loader, multiFile ? [files] : files);
149
+ }
150
+ renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
151
+ }, [files, renderer.domElement]);
152
+ const loaderResult = useLoader$1(
153
+ loader,
154
+ multiFile ? [files] : files,
155
+ (loader2) => {
156
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
157
+ loader2.setRenderer?.(renderer);
158
+ }
159
+ loader2.setPath?.(path);
160
+ if (extensions) extensions(loader2);
161
+ }
162
+ );
163
+ let texture = multiFile ? (
164
+ // @ts-ignore
165
+ loaderResult[0]
166
+ ) : loaderResult;
167
+ if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
168
+ texture = texture.renderTarget?.texture;
169
+ }
170
+ texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
171
+ texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
172
+ return texture;
173
+ }
174
+ const preloadDefaultOptions = {
175
+ files: defaultFiles,
176
+ path: "",
177
+ preset: void 0,
178
+ extensions: void 0
179
+ };
180
+ useEnvironment.preload = (preloadOptions) => {
181
+ const options = { ...preloadDefaultOptions, ...preloadOptions };
182
+ let { files, path = "" } = options;
183
+ const { preset, extensions } = options;
184
+ if (preset) {
185
+ validatePreset(preset);
186
+ files = presetsObj[preset];
187
+ path = CUBEMAP_ROOT;
188
+ }
189
+ const { extension } = getExtension(files);
190
+ if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
191
+ throw new Error("useEnvironment: Preloading gainmaps is not supported");
192
+ }
193
+ const loader = getLoader$1(extension);
194
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
195
+ useLoader$1.preload(loader, isArray(files) ? [files] : files, (loader2) => {
196
+ loader2.setPath?.(path);
197
+ if (extensions) extensions(loader2);
198
+ });
199
+ };
200
+ const clearDefaultOptins = {
201
+ files: defaultFiles,
202
+ preset: void 0
203
+ };
204
+ useEnvironment.clear = (clearOptions) => {
205
+ const options = { ...clearDefaultOptins, ...clearOptions };
206
+ let { files } = options;
207
+ const { preset } = options;
208
+ if (preset) {
209
+ validatePreset(preset);
210
+ files = presetsObj[preset];
211
+ }
212
+ const { extension } = getExtension(files);
213
+ const loader = getLoader$1(extension);
214
+ if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
215
+ useLoader$1.clear(loader, isArray(files) ? [files] : files);
216
+ };
217
+ function validatePreset(preset) {
218
+ if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
219
+ }
220
+ function getExtension(files) {
221
+ const isCubemap = isArray(files) && files.length === 6;
222
+ const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
223
+ const firstEntry = isArray(files) ? files[0] : files;
224
+ const extension = isCubemap ? "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
225
+ return { extension, isCubemap, isGainmap };
226
+ }
227
+ function getLoader$1(extension) {
228
+ const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
229
+ return loader;
230
+ }
231
+
232
+ const isRef$1 = (obj) => obj.current && obj.current.isScene;
233
+ const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
234
+ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
235
+ sceneProps = {
236
+ backgroundBlurriness: 0,
237
+ backgroundIntensity: 1,
238
+ backgroundRotation: [0, 0, 0],
239
+ environmentIntensity: 1,
240
+ environmentRotation: [0, 0, 0],
241
+ ...sceneProps
242
+ };
243
+ const target = resolveScene(scene || defaultScene);
244
+ const oldbg = target.background;
245
+ const oldenv = target.environment;
246
+ const oldSceneProps = {
247
+ // @ts-ignore
248
+ backgroundBlurriness: target.backgroundBlurriness,
249
+ // @ts-ignore
250
+ backgroundIntensity: target.backgroundIntensity,
251
+ // @ts-ignore
252
+ backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
253
+ // @ts-ignore
254
+ environmentIntensity: target.environmentIntensity,
255
+ // @ts-ignore
256
+ environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
257
+ };
258
+ if (background !== "only") target.environment = texture;
259
+ if (background) target.background = texture;
260
+ applyProps$1(target, sceneProps);
261
+ return () => {
262
+ if (background !== "only") target.environment = oldenv;
263
+ if (background) target.background = oldbg;
264
+ applyProps$1(target, oldSceneProps);
265
+ };
266
+ }
267
+ function EnvironmentMap({ scene, background = false, map, ...config }) {
268
+ const defaultScene = useThree$1((state) => state.scene);
269
+ React.useLayoutEffect(() => {
270
+ if (map) return setEnvProps(background, scene, defaultScene, map, config);
271
+ });
272
+ return null;
273
+ }
274
+ function EnvironmentCube({
275
+ background = false,
276
+ scene,
277
+ blur,
278
+ backgroundBlurriness,
279
+ backgroundIntensity,
280
+ backgroundRotation,
281
+ environmentIntensity,
282
+ environmentRotation,
283
+ ...rest
284
+ }) {
285
+ const texture = useEnvironment(rest);
286
+ const defaultScene = useThree$1((state) => state.scene);
287
+ React.useLayoutEffect(() => {
288
+ return setEnvProps(background, scene, defaultScene, texture, {
289
+ backgroundBlurriness: blur ?? backgroundBlurriness,
290
+ backgroundIntensity,
291
+ backgroundRotation,
292
+ environmentIntensity,
293
+ environmentRotation
294
+ });
295
+ });
296
+ React.useEffect(() => {
297
+ return () => {
298
+ texture.dispose();
299
+ };
300
+ }, [texture]);
301
+ return null;
302
+ }
303
+ function EnvironmentPortal({
304
+ children,
305
+ near = 0.1,
306
+ far = 1e3,
307
+ resolution = 256,
308
+ frames = 1,
309
+ map,
310
+ background = false,
311
+ blur,
312
+ backgroundBlurriness,
313
+ backgroundIntensity,
314
+ backgroundRotation,
315
+ environmentIntensity,
316
+ environmentRotation,
317
+ scene,
318
+ files,
319
+ path,
320
+ preset = void 0,
321
+ extensions
322
+ }) {
323
+ const gl = useThree$1((state) => state.gl);
324
+ const defaultScene = useThree$1((state) => state.scene);
325
+ const camera = React.useRef(null);
326
+ const [virtualScene] = React.useState(() => new Scene());
327
+ const fbo = React.useMemo(() => {
328
+ const fbo2 = new WebGLCubeRenderTarget(resolution);
329
+ fbo2.texture.type = HalfFloatType;
330
+ return fbo2;
331
+ }, [resolution]);
332
+ React.useEffect(() => {
333
+ return () => {
334
+ fbo.dispose();
335
+ };
336
+ }, [fbo]);
337
+ React.useLayoutEffect(() => {
338
+ if (frames === 1) {
339
+ const autoClear = gl.autoClear;
340
+ gl.autoClear = true;
341
+ camera.current.update(gl, virtualScene);
342
+ gl.autoClear = autoClear;
343
+ }
344
+ return setEnvProps(background, scene, defaultScene, fbo.texture, {
345
+ backgroundBlurriness: blur ?? backgroundBlurriness,
346
+ backgroundIntensity,
347
+ backgroundRotation,
348
+ environmentIntensity,
349
+ environmentRotation
350
+ });
351
+ }, [children, virtualScene, fbo.texture, scene, defaultScene, background, frames, gl]);
352
+ let count = 1;
353
+ useFrame$1(() => {
354
+ if (frames === Infinity || count < frames) {
355
+ const autoClear = gl.autoClear;
356
+ gl.autoClear = true;
357
+ camera.current.update(gl, virtualScene);
358
+ gl.autoClear = autoClear;
359
+ count++;
360
+ }
361
+ });
362
+ return /* @__PURE__ */ jsx(Fragment, { children: createPortal$1(
363
+ /* @__PURE__ */ jsxs(Fragment, { children: [
364
+ children,
365
+ /* @__PURE__ */ jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
366
+ files || preset ? /* @__PURE__ */ jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsx(EnvironmentMap, { background: true, map, extensions }) : null
367
+ ] }),
368
+ virtualScene
369
+ ) });
370
+ }
371
+ function EnvironmentGround(props) {
372
+ const textureDefault = useEnvironment(props);
373
+ const texture = props.map || textureDefault;
374
+ React.useMemo(() => extend$1({ GroundProjectedEnvImpl: GroundedSkybox }), []);
375
+ React.useEffect(() => {
376
+ return () => {
377
+ textureDefault.dispose();
378
+ };
379
+ }, [textureDefault]);
380
+ const height = props.ground?.height ?? 15;
381
+ const radius = props.ground?.radius ?? 60;
382
+ const scale = props.ground?.scale ?? 1e3;
383
+ const args = React.useMemo(
384
+ () => [texture, height, radius],
385
+ [texture, height, radius]
386
+ );
387
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
388
+ /* @__PURE__ */ jsx(EnvironmentMap, { ...props, map: texture }),
389
+ /* @__PURE__ */ jsx("groundProjectedEnvImpl", { args, scale })
390
+ ] });
391
+ }
392
+ function EnvironmentColor({ color, scene }) {
393
+ const defaultScene = useThree$1((state) => state.scene);
394
+ React.useLayoutEffect(() => {
395
+ if (color === void 0) return;
396
+ const target = resolveScene(scene || defaultScene);
397
+ const oldBg = target.background;
398
+ target.background = new Color(color);
399
+ return () => {
400
+ target.background = oldBg;
401
+ };
402
+ });
403
+ return null;
404
+ }
405
+ function EnvironmentDualSource(props) {
406
+ const { backgroundFiles, ...envProps } = props;
407
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
408
+ /* @__PURE__ */ jsx(EnvironmentCube, { ...envProps, background: false }),
409
+ /* @__PURE__ */ jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
410
+ ] });
411
+ }
412
+ function Environment(props) {
413
+ if (props.color && !props.files && !props.preset && !props.map) {
414
+ return /* @__PURE__ */ jsx(EnvironmentColor, { ...props });
415
+ }
416
+ if (props.backgroundFiles && props.backgroundFiles !== props.files) {
417
+ return /* @__PURE__ */ jsx(EnvironmentDualSource, { ...props });
418
+ }
419
+ return props.ground ? /* @__PURE__ */ jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsx(EnvironmentCube, { ...props });
420
+ }
421
+
48
422
  var __defProp$2 = Object.defineProperty;
49
423
  var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
50
424
  var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -224,7 +598,8 @@ function prepare(target, root, type, props) {
224
598
  object,
225
599
  eventCount: 0,
226
600
  handlers: {},
227
- isHidden: false
601
+ isHidden: false,
602
+ deferredRefs: []
228
603
  };
229
604
  if (object) object.__r3f = instance;
230
605
  }
@@ -273,7 +648,7 @@ function createOcclusionObserverNode(store, uniform) {
273
648
  let occlusionSetupPromise = null;
274
649
  function enableOcclusion(store) {
275
650
  const state = store.getState();
276
- const { internal, renderer, rootScene } = state;
651
+ const { internal, renderer } = state;
277
652
  if (internal.occlusionEnabled || occlusionSetupPromise) return;
278
653
  const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
279
654
  if (!hasOcclusionSupport) {
@@ -436,6 +811,22 @@ function hasVisibilityHandlers(handlers) {
436
811
  return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
437
812
  }
438
813
 
814
+ const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
815
+ function fromRef(ref) {
816
+ return { [FROM_REF]: ref };
817
+ }
818
+ function isFromRef(value) {
819
+ return value !== null && typeof value === "object" && FROM_REF in value;
820
+ }
821
+
822
+ const ONCE = Symbol.for("@react-three/fiber.once");
823
+ function once(...args) {
824
+ return { [ONCE]: args.length ? args : true };
825
+ }
826
+ function isOnce(value) {
827
+ return value !== null && typeof value === "object" && ONCE in value;
828
+ }
829
+
439
830
  const RESERVED_PROPS = [
440
831
  "children",
441
832
  "key",
@@ -506,7 +897,7 @@ function getMemoizedPrototype(root) {
506
897
  ctor = new root.constructor();
507
898
  MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
508
899
  }
509
- } catch (e) {
900
+ } catch {
510
901
  }
511
902
  return ctor;
512
903
  }
@@ -537,7 +928,7 @@ function applyProps(object, props) {
537
928
  const rootState = instance && findInitialRoot(instance).getState();
538
929
  const prevHandlers = instance?.eventCount;
539
930
  for (const prop in props) {
540
- let value = props[prop];
931
+ const value = props[prop];
541
932
  if (RESERVED_PROPS.includes(prop)) continue;
542
933
  if (instance && EVENT_REGEX.test(prop)) {
543
934
  if (typeof value === "function") instance.handlers[prop] = value;
@@ -552,6 +943,25 @@ function applyProps(object, props) {
552
943
  continue;
553
944
  }
554
945
  if (value === void 0) continue;
946
+ if (isFromRef(value)) {
947
+ instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
948
+ continue;
949
+ }
950
+ if (isOnce(value)) {
951
+ if (instance?.appliedOnce?.has(prop)) continue;
952
+ if (instance) {
953
+ instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
954
+ instance.appliedOnce.add(prop);
955
+ }
956
+ const { root: targetRoot, key: targetKey } = resolve(object, prop);
957
+ const args = value[ONCE];
958
+ if (typeof targetRoot[targetKey] === "function") {
959
+ targetRoot[targetKey](...args === true ? [] : args);
960
+ } else if (args !== true && args.length > 0) {
961
+ targetRoot[targetKey] = args[0];
962
+ }
963
+ continue;
964
+ }
555
965
  let { root, key, target } = resolve(object, prop);
556
966
  if (target === void 0 && (typeof root !== "object" || root === null)) {
557
967
  throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
@@ -574,7 +984,7 @@ function applyProps(object, props) {
574
984
  else target.set(value);
575
985
  } else {
576
986
  root[key] = value;
577
- if (rootState && !rootState.linear && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
987
+ if (rootState && rootState.renderer?.outputColorSpace === SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
578
988
  root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
579
989
  root[key].colorSpace = rootState.textureColorSpace;
580
990
  }
@@ -689,13 +1099,14 @@ function createEvents(store) {
689
1099
  for (const hit of hits) {
690
1100
  let eventObject = hit.object;
691
1101
  while (eventObject) {
692
- if (eventObject.__r3f?.eventCount)
1102
+ if (eventObject.__r3f?.eventCount) {
693
1103
  intersections.push({ ...hit, eventObject });
1104
+ }
694
1105
  eventObject = eventObject.parent;
695
1106
  }
696
1107
  }
697
1108
  if ("pointerId" in event && state.internal.capturedMap.has(event.pointerId)) {
698
- for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
1109
+ for (const captureData of state.internal.capturedMap.get(event.pointerId).values()) {
699
1110
  if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
700
1111
  }
701
1112
  }
@@ -725,12 +1136,12 @@ function createEvents(store) {
725
1136
  releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
726
1137
  }
727
1138
  };
728
- let extractEventProps = {};
729
- for (let prop in event) {
730
- let property = event[prop];
1139
+ const extractEventProps = {};
1140
+ for (const prop in event) {
1141
+ const property = event[prop];
731
1142
  if (typeof property !== "function") extractEventProps[prop] = property;
732
1143
  }
733
- let raycastEvent = {
1144
+ const raycastEvent = {
734
1145
  ...hit,
735
1146
  ...extractEventProps,
736
1147
  pointer,
@@ -930,7 +1341,7 @@ function createPointerEvents(store) {
930
1341
  return {
931
1342
  priority: 1,
932
1343
  enabled: true,
933
- compute(event, state, previous) {
1344
+ compute(event, state) {
934
1345
  state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
935
1346
  state.raycaster.setFromCamera(state.pointer, state.camera);
936
1347
  },
@@ -1028,300 +1439,26 @@ function notifyAlpha({ message, link }) {
1028
1439
  }
1029
1440
  }
1030
1441
 
1031
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1032
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
1033
- const createStore = (invalidate, advance) => {
1034
- const rootStore = createWithEqualityFn((set, get) => {
1035
- const position = new Vector3();
1036
- const defaultTarget = new Vector3();
1037
- const tempTarget = new Vector3();
1038
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
1039
- const { width, height, top, left } = size;
1040
- const aspect = width / height;
1041
- if (target.isVector3) tempTarget.copy(target);
1042
- else tempTarget.set(...target);
1043
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
1044
- if (isOrthographicCamera(camera)) {
1045
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
1046
- } else {
1047
- const fov = camera.fov * Math.PI / 180;
1048
- const h = 2 * Math.tan(fov / 2) * distance;
1049
- const w = h * (width / height);
1050
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
1051
- }
1052
- }
1053
- let performanceTimeout = void 0;
1054
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
1055
- const pointer = new Vector2();
1056
- const rootState = {
1057
- set,
1058
- get,
1059
- // Mock objects that have to be configured
1060
- gl: null,
1061
- renderer: null,
1062
- camera: null,
1063
- frustum: new Frustum(),
1064
- autoUpdateFrustum: true,
1065
- raycaster: null,
1066
- events: { priority: 1, enabled: true, connected: false },
1067
- scene: null,
1068
- rootScene: null,
1069
- xr: null,
1070
- inspector: null,
1071
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
1072
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1073
- legacy: false,
1074
- linear: false,
1075
- flat: false,
1076
- textureColorSpace: "srgb",
1077
- isLegacy: false,
1078
- webGPUSupported: false,
1079
- isNative: false,
1080
- controls: null,
1081
- pointer,
1082
- mouse: pointer,
1083
- frameloop: "always",
1084
- onPointerMissed: void 0,
1085
- onDragOverMissed: void 0,
1086
- onDropMissed: void 0,
1087
- performance: {
1088
- current: 1,
1089
- min: 0.5,
1090
- max: 1,
1091
- debounce: 200,
1092
- regress: () => {
1093
- const state2 = get();
1094
- if (performanceTimeout) clearTimeout(performanceTimeout);
1095
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
1096
- performanceTimeout = setTimeout(
1097
- () => setPerformanceCurrent(get().performance.max),
1098
- state2.performance.debounce
1099
- );
1100
- }
1101
- },
1102
- size: { width: 0, height: 0, top: 0, left: 0 },
1103
- viewport: {
1104
- initialDpr: 0,
1105
- dpr: 0,
1106
- width: 0,
1107
- height: 0,
1108
- top: 0,
1109
- left: 0,
1110
- aspect: 0,
1111
- distance: 0,
1112
- factor: 0,
1113
- getCurrentViewport
1114
- },
1115
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
1116
- setSize: (width, height, top = 0, left = 0) => {
1117
- const camera = get().camera;
1118
- const size = { width, height, top, left };
1119
- set((state2) => ({ size, viewport: { ...state2.viewport, ...getCurrentViewport(camera, defaultTarget, size) } }));
1120
- },
1121
- setDpr: (dpr) => set((state2) => {
1122
- const resolved = calculateDpr(dpr);
1123
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
1124
- }),
1125
- setFrameloop: (frameloop = "always") => {
1126
- set(() => ({ frameloop }));
1127
- },
1128
- setError: (error) => set(() => ({ error })),
1129
- error: null,
1130
- //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
1131
- uniforms: {},
1132
- nodes: {},
1133
- textures: /* @__PURE__ */ new Map(),
1134
- postProcessing: null,
1135
- passes: {},
1136
- previousRoot: void 0,
1137
- internal: {
1138
- // Events
1139
- interaction: [],
1140
- hovered: /* @__PURE__ */ new Map(),
1141
- subscribers: [],
1142
- initialClick: [0, 0],
1143
- initialHits: [],
1144
- capturedMap: /* @__PURE__ */ new Map(),
1145
- lastEvent: React.createRef(),
1146
- // Visibility tracking (onFramed, onOccluded, onVisible)
1147
- visibilityRegistry: /* @__PURE__ */ new Map(),
1148
- // Occlusion system (WebGPU only)
1149
- occlusionEnabled: false,
1150
- occlusionObserver: null,
1151
- occlusionCache: /* @__PURE__ */ new Map(),
1152
- helperGroup: null,
1153
- // Updates
1154
- active: false,
1155
- frames: 0,
1156
- priority: 0,
1157
- subscribe: (ref, priority, store) => {
1158
- const internal = get().internal;
1159
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1160
- internal.subscribers.push({ ref, priority, store });
1161
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1162
- return () => {
1163
- const internal2 = get().internal;
1164
- if (internal2?.subscribers) {
1165
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
1166
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
1167
- }
1168
- };
1169
- },
1170
- // Renderer Storage (single source of truth)
1171
- actualRenderer: null,
1172
- // Scheduler for useFrameNext (initialized in renderer.tsx)
1173
- scheduler: null
1174
- }
1175
- };
1176
- return rootState;
1177
- });
1178
- const state = rootStore.getState();
1179
- Object.defineProperty(state, "gl", {
1180
- get() {
1181
- const currentState = rootStore.getState();
1182
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1183
- const stack = new Error().stack || "";
1184
- const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
1185
- if (!isInternalAccess) {
1186
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1187
- notifyDepreciated({
1188
- heading: "Accessing state.gl in WebGPU mode",
1189
- body: "Please use state.renderer instead. state.gl is deprecated and will be removed in future versions.\n\nFor backwards compatibility, state.gl currently maps to state.renderer, but this may cause issues with libraries expecting WebGLRenderer.\n\nAccessed from:\n" + cleanedStack
1190
- });
1191
- }
1192
- }
1193
- return currentState.internal.actualRenderer;
1194
- },
1195
- set(value) {
1196
- rootStore.getState().internal.actualRenderer = value;
1197
- },
1198
- enumerable: true,
1199
- configurable: true
1200
- });
1201
- Object.defineProperty(state, "renderer", {
1202
- get() {
1203
- return rootStore.getState().internal.actualRenderer;
1204
- },
1205
- set(value) {
1206
- rootStore.getState().internal.actualRenderer = value;
1207
- },
1208
- enumerable: true,
1209
- configurable: true
1210
- });
1211
- let oldScene = state.scene;
1212
- rootStore.subscribe(() => {
1213
- const currentState = rootStore.getState();
1214
- const { scene, rootScene, set } = currentState;
1215
- if (scene !== oldScene) {
1216
- oldScene = scene;
1217
- if (scene?.isScene && scene !== rootScene) {
1218
- set({ rootScene: scene });
1219
- }
1220
- }
1221
- });
1222
- let oldSize = state.size;
1223
- let oldDpr = state.viewport.dpr;
1224
- let oldCamera = state.camera;
1225
- rootStore.subscribe(() => {
1226
- const { camera, size, viewport, set, internal } = rootStore.getState();
1227
- const actualRenderer = internal.actualRenderer;
1228
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1229
- oldSize = size;
1230
- oldDpr = viewport.dpr;
1231
- updateCamera(camera, size);
1232
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1233
- const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
1234
- actualRenderer.setSize(size.width, size.height, updateStyle);
1235
- }
1236
- if (camera !== oldCamera) {
1237
- oldCamera = camera;
1238
- const { rootScene } = rootStore.getState();
1239
- if (camera && rootScene && !camera.parent) {
1240
- rootScene.add(camera);
1241
- }
1242
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1243
- const currentState = rootStore.getState();
1244
- if (currentState.autoUpdateFrustum && camera) {
1245
- updateFrustum(camera, currentState.frustum);
1246
- }
1247
- }
1248
- });
1249
- rootStore.subscribe((state2) => invalidate(state2));
1250
- return rootStore;
1251
- };
1252
-
1253
- const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1254
- const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
1255
- function getLoader(Proto) {
1256
- if (isConstructor$1(Proto)) {
1257
- let loader = memoizedLoaders.get(Proto);
1258
- if (!loader) {
1259
- loader = new Proto();
1260
- memoizedLoaders.set(Proto, loader);
1261
- }
1262
- return loader;
1263
- }
1264
- return Proto;
1265
- }
1266
- function loadingFn(extensions, onProgress) {
1267
- return function(Proto, input) {
1268
- const loader = getLoader(Proto);
1269
- if (extensions) extensions(loader);
1270
- if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
1271
- return loader.loadAsync(input, onProgress).then((data) => {
1272
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1273
- return data;
1274
- });
1275
- }
1276
- return new Promise(
1277
- (res, reject) => loader.load(
1278
- input,
1279
- (data) => {
1280
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1281
- res(data);
1282
- },
1283
- onProgress,
1284
- (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
1285
- )
1286
- );
1287
- };
1288
- }
1289
- function useLoader(loader, input, extensions, onProgress) {
1290
- const keys = Array.isArray(input) ? input : [input];
1291
- const fn = loadingFn(extensions, onProgress);
1292
- const results = keys.map((key) => suspend(fn, [loader, key], { equal: is.equ }));
1293
- return Array.isArray(input) ? results : results[0];
1294
- }
1295
- useLoader.preload = function(loader, input, extensions, onProgress) {
1296
- const keys = Array.isArray(input) ? input : [input];
1297
- keys.forEach((key) => preload(loadingFn(extensions, onProgress), [loader, key]));
1298
- };
1299
- useLoader.clear = function(loader, input) {
1300
- const keys = Array.isArray(input) ? input : [input];
1301
- keys.forEach((key) => clear([loader, key]));
1302
- };
1303
- useLoader.loader = getLoader;
1304
-
1305
- var __defProp$1 = Object.defineProperty;
1306
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1307
- var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1308
- const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1309
- class PhaseGraph {
1310
- constructor() {
1311
- /** Ordered list of phase nodes */
1312
- __publicField$1(this, "phases", []);
1313
- /** Quick lookup by name */
1314
- __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1315
- /** Cached ordered names (invalidated on changes) */
1316
- __publicField$1(this, "orderedNamesCache", null);
1317
- this.initializeDefaultPhases();
1318
- }
1319
- //* Initialization --------------------------------
1320
- initializeDefaultPhases() {
1321
- for (const name of DEFAULT_PHASES) {
1322
- const node = { name, isAutoGenerated: false };
1323
- this.phases.push(node);
1324
- this.phaseMap.set(name, node);
1442
+ var __defProp$1 = Object.defineProperty;
1443
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1444
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1445
+ const DEFAULT_PHASES = ["start", "input", "physics", "update", "render", "finish"];
1446
+ class PhaseGraph {
1447
+ constructor() {
1448
+ /** Ordered list of phase nodes */
1449
+ __publicField$1(this, "phases", []);
1450
+ /** Quick lookup by name */
1451
+ __publicField$1(this, "phaseMap", /* @__PURE__ */ new Map());
1452
+ /** Cached ordered names (invalidated on changes) */
1453
+ __publicField$1(this, "orderedNamesCache", null);
1454
+ this.initializeDefaultPhases();
1455
+ }
1456
+ //* Initialization --------------------------------
1457
+ initializeDefaultPhases() {
1458
+ for (const name of DEFAULT_PHASES) {
1459
+ const node = { name, isAutoGenerated: false };
1460
+ this.phases.push(node);
1461
+ this.phaseMap.set(name, node);
1325
1462
  }
1326
1463
  this.invalidateCache();
1327
1464
  }
@@ -1340,8 +1477,9 @@ class PhaseGraph {
1340
1477
  const node = { name, isAutoGenerated: false };
1341
1478
  let insertIndex = this.phases.length;
1342
1479
  const targetIndex = this.getPhaseIndex(before ?? after);
1343
- if (targetIndex !== -1) insertIndex = before ? targetIndex : targetIndex + 1;
1344
- else {
1480
+ if (targetIndex !== -1) {
1481
+ insertIndex = before ? targetIndex : targetIndex + 1;
1482
+ } else {
1345
1483
  const constraintType = before ? "before" : "after";
1346
1484
  console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
1347
1485
  }
@@ -2206,132 +2344,445 @@ const _Scheduler = class _Scheduler {
2206
2344
  console.error(`[Scheduler] Error in global job "${job.id}":`, error);
2207
2345
  }
2208
2346
  }
2209
- }
2210
- /**
2211
- * Execute all jobs for a single root in sorted order.
2212
- * Rebuilds sorted job list if needed, then dispatches each job.
2213
- * Errors are caught and propagated via triggerError.
2214
- * @param {RootEntry} root - The root entry to tick
2215
- * @param {number} timestamp - RAF timestamp in milliseconds
2216
- * @param {number} delta - Time since last frame in seconds
2217
- * @returns {void}
2218
- * @private
2219
- */
2220
- tickRoot(root, timestamp, delta) {
2221
- if (root.needsRebuild) {
2222
- root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
2223
- root.needsRebuild = false;
2224
- }
2225
- const providedState = root.getState?.() ?? {};
2226
- const frameState = {
2227
- ...providedState,
2228
- time: timestamp,
2229
- delta,
2230
- elapsed: this.loopState.elapsedTime / 1e3,
2231
- // Convert ms to seconds
2232
- frame: this.loopState.frameCount
2233
- };
2234
- for (const job of root.sortedJobs) {
2235
- if (!shouldRun(job, timestamp)) continue;
2236
- try {
2237
- job.callback(frameState, delta);
2238
- } catch (error) {
2239
- console.error(`[Scheduler] Error in job "${job.id}":`, error);
2240
- this.triggerError(error instanceof Error ? error : new Error(String(error)));
2347
+ }
2348
+ /**
2349
+ * Execute all jobs for a single root in sorted order.
2350
+ * Rebuilds sorted job list if needed, then dispatches each job.
2351
+ * Errors are caught and propagated via triggerError.
2352
+ * @param {RootEntry} root - The root entry to tick
2353
+ * @param {number} timestamp - RAF timestamp in milliseconds
2354
+ * @param {number} delta - Time since last frame in seconds
2355
+ * @returns {void}
2356
+ * @private
2357
+ */
2358
+ tickRoot(root, timestamp, delta) {
2359
+ if (root.needsRebuild) {
2360
+ root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
2361
+ root.needsRebuild = false;
2362
+ }
2363
+ const providedState = root.getState?.() ?? {};
2364
+ const frameState = {
2365
+ ...providedState,
2366
+ time: timestamp,
2367
+ delta,
2368
+ elapsed: this.loopState.elapsedTime / 1e3,
2369
+ // Convert ms to seconds
2370
+ frame: this.loopState.frameCount
2371
+ };
2372
+ for (const job of root.sortedJobs) {
2373
+ if (!shouldRun(job, timestamp)) continue;
2374
+ try {
2375
+ job.callback(frameState, delta);
2376
+ } catch (error) {
2377
+ console.error(`[Scheduler] Error in job "${job.id}":`, error);
2378
+ this.triggerError(error instanceof Error ? error : new Error(String(error)));
2379
+ }
2380
+ }
2381
+ }
2382
+ //* Debug & Inspection Methods ================================
2383
+ /**
2384
+ * Get the total number of registered jobs across all roots.
2385
+ * Includes both per-root jobs and global before/after jobs.
2386
+ * @returns {number} Total job count
2387
+ */
2388
+ getJobCount() {
2389
+ let count = 0;
2390
+ for (const root of this.roots.values()) {
2391
+ count += root.jobs.size;
2392
+ }
2393
+ return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2394
+ }
2395
+ /**
2396
+ * Get all registered job IDs across all roots.
2397
+ * Includes both per-root jobs and global before/after jobs.
2398
+ * @returns {string[]} Array of all job IDs
2399
+ */
2400
+ getJobIds() {
2401
+ const ids = [];
2402
+ for (const root of this.roots.values()) {
2403
+ ids.push(...root.jobs.keys());
2404
+ }
2405
+ ids.push(...this.globalBeforeJobs.keys());
2406
+ ids.push(...this.globalAfterJobs.keys());
2407
+ return ids;
2408
+ }
2409
+ /**
2410
+ * Get the number of registered roots (Canvas instances).
2411
+ * @returns {number} Number of registered roots
2412
+ */
2413
+ getRootCount() {
2414
+ return this.roots.size;
2415
+ }
2416
+ /**
2417
+ * Check if any user (non-system) jobs are registered in a specific phase.
2418
+ * Used by the default render job to know if a user has taken over rendering.
2419
+ *
2420
+ * @param phase The phase to check
2421
+ * @param rootId Optional root ID to check (checks all roots if not provided)
2422
+ * @returns true if any user jobs exist in the phase
2423
+ */
2424
+ hasUserJobsInPhase(phase, rootId) {
2425
+ const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2426
+ return rootsToCheck.some((root) => {
2427
+ if (!root) return false;
2428
+ for (const job of root.jobs.values()) {
2429
+ if (job.phase === phase && !job.system && job.enabled) return true;
2430
+ }
2431
+ return false;
2432
+ });
2433
+ }
2434
+ //* Utility Methods ================================
2435
+ /**
2436
+ * Generate a unique root ID for automatic root registration.
2437
+ * @returns {string} A unique root ID in the format 'root_N'
2438
+ */
2439
+ generateRootId() {
2440
+ return `root_${this.nextRootIndex++}`;
2441
+ }
2442
+ /**
2443
+ * Generate a unique job ID.
2444
+ * @returns {string} A unique job ID in the format 'job_N'
2445
+ * @private
2446
+ */
2447
+ generateJobId() {
2448
+ return `job_${this.nextJobIndex}`;
2449
+ }
2450
+ /**
2451
+ * Normalize before/after constraints to a Set.
2452
+ * Handles undefined, single string, or array inputs.
2453
+ * @param {string | string[] | undefined} value - The constraint value(s)
2454
+ * @returns {Set<string>} Normalized Set of constraint strings
2455
+ * @private
2456
+ */
2457
+ normalizeConstraints(value) {
2458
+ if (!value) return /* @__PURE__ */ new Set();
2459
+ if (Array.isArray(value)) return new Set(value);
2460
+ return /* @__PURE__ */ new Set([value]);
2461
+ }
2462
+ };
2463
+ //* Static State & Methods (Singleton Usage) ================================
2464
+ //* Cross-Bundle Singleton Key ==============================
2465
+ // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2466
+ // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2467
+ __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2468
+ let Scheduler = _Scheduler;
2469
+ const getScheduler = () => Scheduler.get();
2470
+ if (hmrData) {
2471
+ hmrData.accept?.();
2472
+ }
2473
+
2474
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
2475
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
2476
+ const createStore = (invalidate, advance) => {
2477
+ const rootStore = createWithEqualityFn((set, get) => {
2478
+ const position = new Vector3();
2479
+ const defaultTarget = new Vector3();
2480
+ const tempTarget = new Vector3();
2481
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
2482
+ const { width, height, top, left } = size;
2483
+ const aspect = width / height;
2484
+ if (target.isVector3) tempTarget.copy(target);
2485
+ else tempTarget.set(...target);
2486
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
2487
+ if (isOrthographicCamera(camera)) {
2488
+ return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
2489
+ } else {
2490
+ const fov = camera.fov * Math.PI / 180;
2491
+ const h = 2 * Math.tan(fov / 2) * distance;
2492
+ const w = h * (width / height);
2493
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
2494
+ }
2495
+ }
2496
+ let performanceTimeout = void 0;
2497
+ const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
2498
+ const pointer = new Vector2();
2499
+ const rootState = {
2500
+ set,
2501
+ get,
2502
+ // Mock objects that have to be configured
2503
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
2504
+ primaryStore: null,
2505
+ gl: null,
2506
+ renderer: null,
2507
+ camera: null,
2508
+ frustum: new Frustum(),
2509
+ autoUpdateFrustum: true,
2510
+ raycaster: null,
2511
+ events: { priority: 1, enabled: true, connected: false },
2512
+ scene: null,
2513
+ rootScene: null,
2514
+ xr: null,
2515
+ inspector: null,
2516
+ invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
2517
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
2518
+ textureColorSpace: SRGBColorSpace,
2519
+ isLegacy: false,
2520
+ webGPUSupported: false,
2521
+ isNative: false,
2522
+ controls: null,
2523
+ pointer,
2524
+ mouse: pointer,
2525
+ frameloop: "always",
2526
+ onPointerMissed: void 0,
2527
+ onDragOverMissed: void 0,
2528
+ onDropMissed: void 0,
2529
+ performance: {
2530
+ current: 1,
2531
+ min: 0.5,
2532
+ max: 1,
2533
+ debounce: 200,
2534
+ regress: () => {
2535
+ const state2 = get();
2536
+ if (performanceTimeout) clearTimeout(performanceTimeout);
2537
+ if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
2538
+ performanceTimeout = setTimeout(
2539
+ () => setPerformanceCurrent(get().performance.max),
2540
+ state2.performance.debounce
2541
+ );
2542
+ }
2543
+ },
2544
+ size: { width: 0, height: 0, top: 0, left: 0 },
2545
+ viewport: {
2546
+ initialDpr: 0,
2547
+ dpr: 0,
2548
+ width: 0,
2549
+ height: 0,
2550
+ top: 0,
2551
+ left: 0,
2552
+ aspect: 0,
2553
+ distance: 0,
2554
+ factor: 0,
2555
+ getCurrentViewport
2556
+ },
2557
+ setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
2558
+ setSize: (width, height, top, left) => {
2559
+ const state2 = get();
2560
+ if (width === void 0) {
2561
+ set({ _sizeImperative: false });
2562
+ if (state2._sizeProps) {
2563
+ const { width: propW, height: propH } = state2._sizeProps;
2564
+ if (propW !== void 0 || propH !== void 0) {
2565
+ const currentSize = state2.size;
2566
+ const newSize = {
2567
+ width: propW ?? currentSize.width,
2568
+ height: propH ?? currentSize.height,
2569
+ top: currentSize.top,
2570
+ left: currentSize.left
2571
+ };
2572
+ set((s) => ({
2573
+ size: newSize,
2574
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
2575
+ }));
2576
+ getScheduler().invalidate();
2577
+ }
2578
+ }
2579
+ return;
2580
+ }
2581
+ const w = width;
2582
+ const h = height ?? width;
2583
+ const t = top ?? state2.size.top;
2584
+ const l = left ?? state2.size.left;
2585
+ const size = { width: w, height: h, top: t, left: l };
2586
+ set((s) => ({
2587
+ size,
2588
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
2589
+ _sizeImperative: true
2590
+ }));
2591
+ getScheduler().invalidate();
2592
+ },
2593
+ setDpr: (dpr) => set((state2) => {
2594
+ const resolved = calculateDpr(dpr);
2595
+ return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
2596
+ }),
2597
+ setFrameloop: (frameloop = "always") => {
2598
+ set(() => ({ frameloop }));
2599
+ },
2600
+ setError: (error) => set(() => ({ error })),
2601
+ error: null,
2602
+ //* TSL State (managed via hooks: useUniforms, useNodes, useTextures, usePostProcessing) ==============================
2603
+ uniforms: {},
2604
+ nodes: {},
2605
+ textures: /* @__PURE__ */ new Map(),
2606
+ postProcessing: null,
2607
+ passes: {},
2608
+ _hmrVersion: 0,
2609
+ _sizeImperative: false,
2610
+ _sizeProps: null,
2611
+ previousRoot: void 0,
2612
+ internal: {
2613
+ // Events
2614
+ interaction: [],
2615
+ hovered: /* @__PURE__ */ new Map(),
2616
+ subscribers: [],
2617
+ initialClick: [0, 0],
2618
+ initialHits: [],
2619
+ capturedMap: /* @__PURE__ */ new Map(),
2620
+ lastEvent: React.createRef(),
2621
+ // Visibility tracking (onFramed, onOccluded, onVisible)
2622
+ visibilityRegistry: /* @__PURE__ */ new Map(),
2623
+ // Occlusion system (WebGPU only)
2624
+ occlusionEnabled: false,
2625
+ occlusionObserver: null,
2626
+ occlusionCache: /* @__PURE__ */ new Map(),
2627
+ helperGroup: null,
2628
+ // Updates
2629
+ active: false,
2630
+ frames: 0,
2631
+ priority: 0,
2632
+ subscribe: (ref, priority, store) => {
2633
+ const internal = get().internal;
2634
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
2635
+ internal.subscribers.push({ ref, priority, store });
2636
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
2637
+ return () => {
2638
+ const internal2 = get().internal;
2639
+ if (internal2?.subscribers) {
2640
+ internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
2641
+ internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
2642
+ }
2643
+ };
2644
+ },
2645
+ // Renderer Storage (single source of truth)
2646
+ actualRenderer: null,
2647
+ // Scheduler for useFrameNext (initialized in renderer.tsx)
2648
+ scheduler: null
2649
+ }
2650
+ };
2651
+ return rootState;
2652
+ });
2653
+ const state = rootStore.getState();
2654
+ Object.defineProperty(state, "gl", {
2655
+ get() {
2656
+ const currentState = rootStore.getState();
2657
+ if (!currentState.isLegacy && currentState.internal.actualRenderer) {
2658
+ const stack = new Error().stack || "";
2659
+ const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
2660
+ if (!isInternalAccess) {
2661
+ const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
2662
+ notifyDepreciated({
2663
+ heading: "Accessing state.gl in WebGPU mode",
2664
+ body: "Please use state.renderer instead. state.gl is deprecated and will be removed in future versions.\n\nFor backwards compatibility, state.gl currently maps to state.renderer, but this may cause issues with libraries expecting WebGLRenderer.\n\nAccessed from:\n" + cleanedStack
2665
+ });
2666
+ }
2667
+ }
2668
+ return currentState.internal.actualRenderer;
2669
+ },
2670
+ set(value) {
2671
+ rootStore.getState().internal.actualRenderer = value;
2672
+ },
2673
+ enumerable: true,
2674
+ configurable: true
2675
+ });
2676
+ Object.defineProperty(state, "renderer", {
2677
+ get() {
2678
+ return rootStore.getState().internal.actualRenderer;
2679
+ },
2680
+ set(value) {
2681
+ rootStore.getState().internal.actualRenderer = value;
2682
+ },
2683
+ enumerable: true,
2684
+ configurable: true
2685
+ });
2686
+ let oldScene = state.scene;
2687
+ rootStore.subscribe(() => {
2688
+ const currentState = rootStore.getState();
2689
+ const { scene, rootScene, set } = currentState;
2690
+ if (scene !== oldScene) {
2691
+ oldScene = scene;
2692
+ if (scene?.isScene && scene !== rootScene) {
2693
+ set({ rootScene: scene });
2694
+ }
2695
+ }
2696
+ });
2697
+ let oldSize = state.size;
2698
+ let oldDpr = state.viewport.dpr;
2699
+ let oldCamera = state.camera;
2700
+ rootStore.subscribe(() => {
2701
+ const { camera, size, viewport, set, internal } = rootStore.getState();
2702
+ const actualRenderer = internal.actualRenderer;
2703
+ const canvasTarget = internal.canvasTarget;
2704
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
2705
+ oldSize = size;
2706
+ oldDpr = viewport.dpr;
2707
+ updateCamera(camera, size);
2708
+ if (canvasTarget) {
2709
+ if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
2710
+ const updateStyle = typeof HTMLCanvasElement !== "undefined" && canvasTarget.domElement instanceof HTMLCanvasElement;
2711
+ canvasTarget.setSize(size.width, size.height, updateStyle);
2712
+ } else {
2713
+ if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
2714
+ const updateStyle = typeof HTMLCanvasElement !== "undefined" && actualRenderer.domElement instanceof HTMLCanvasElement;
2715
+ actualRenderer.setSize(size.width, size.height, updateStyle);
2716
+ }
2717
+ }
2718
+ if (camera !== oldCamera) {
2719
+ oldCamera = camera;
2720
+ const { rootScene } = rootStore.getState();
2721
+ if (camera && rootScene && !camera.parent) {
2722
+ rootScene.add(camera);
2723
+ }
2724
+ set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
2725
+ const currentState = rootStore.getState();
2726
+ if (currentState.autoUpdateFrustum && camera) {
2727
+ updateFrustum(camera, currentState.frustum);
2241
2728
  }
2242
2729
  }
2243
- }
2244
- //* Debug & Inspection Methods ================================
2245
- /**
2246
- * Get the total number of registered jobs across all roots.
2247
- * Includes both per-root jobs and global before/after jobs.
2248
- * @returns {number} Total job count
2249
- */
2250
- getJobCount() {
2251
- let count = 0;
2252
- for (const root of this.roots.values()) {
2253
- count += root.jobs.size;
2730
+ });
2731
+ rootStore.subscribe((state2) => invalidate(state2));
2732
+ return rootStore;
2733
+ };
2734
+
2735
+ const memoizedLoaders = /* @__PURE__ */ new WeakMap();
2736
+ const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
2737
+ function getLoader(Proto) {
2738
+ if (isConstructor$1(Proto)) {
2739
+ let loader = memoizedLoaders.get(Proto);
2740
+ if (!loader) {
2741
+ loader = new Proto();
2742
+ memoizedLoaders.set(Proto, loader);
2254
2743
  }
2255
- return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
2744
+ return loader;
2256
2745
  }
2257
- /**
2258
- * Get all registered job IDs across all roots.
2259
- * Includes both per-root jobs and global before/after jobs.
2260
- * @returns {string[]} Array of all job IDs
2261
- */
2262
- getJobIds() {
2263
- const ids = [];
2264
- for (const root of this.roots.values()) {
2265
- ids.push(...root.jobs.keys());
2746
+ return Proto;
2747
+ }
2748
+ function loadingFn(extensions, onProgress) {
2749
+ return function(Proto, input) {
2750
+ const loader = getLoader(Proto);
2751
+ if (extensions) extensions(loader);
2752
+ if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
2753
+ return loader.loadAsync(input, onProgress).then((data) => {
2754
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2755
+ return data;
2756
+ });
2266
2757
  }
2267
- ids.push(...this.globalBeforeJobs.keys());
2268
- ids.push(...this.globalAfterJobs.keys());
2269
- return ids;
2270
- }
2271
- /**
2272
- * Get the number of registered roots (Canvas instances).
2273
- * @returns {number} Number of registered roots
2274
- */
2275
- getRootCount() {
2276
- return this.roots.size;
2277
- }
2278
- /**
2279
- * Check if any user (non-system) jobs are registered in a specific phase.
2280
- * Used by the default render job to know if a user has taken over rendering.
2281
- *
2282
- * @param phase The phase to check
2283
- * @param rootId Optional root ID to check (checks all roots if not provided)
2284
- * @returns true if any user jobs exist in the phase
2285
- */
2286
- hasUserJobsInPhase(phase, rootId) {
2287
- const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
2288
- return rootsToCheck.some((root) => {
2289
- if (!root) return false;
2290
- for (const job of root.jobs.values()) {
2291
- if (job.phase === phase && !job.system && job.enabled) return true;
2292
- }
2293
- return false;
2294
- });
2295
- }
2296
- //* Utility Methods ================================
2297
- /**
2298
- * Generate a unique root ID for automatic root registration.
2299
- * @returns {string} A unique root ID in the format 'root_N'
2300
- */
2301
- generateRootId() {
2302
- return `root_${this.nextRootIndex++}`;
2303
- }
2304
- /**
2305
- * Generate a unique job ID.
2306
- * @returns {string} A unique job ID in the format 'job_N'
2307
- * @private
2308
- */
2309
- generateJobId() {
2310
- return `job_${this.nextJobIndex}`;
2311
- }
2312
- /**
2313
- * Normalize before/after constraints to a Set.
2314
- * Handles undefined, single string, or array inputs.
2315
- * @param {string | string[] | undefined} value - The constraint value(s)
2316
- * @returns {Set<string>} Normalized Set of constraint strings
2317
- * @private
2318
- */
2319
- normalizeConstraints(value) {
2320
- if (!value) return /* @__PURE__ */ new Set();
2321
- if (Array.isArray(value)) return new Set(value);
2322
- return /* @__PURE__ */ new Set([value]);
2323
- }
2324
- };
2325
- //* Static State & Methods (Singleton Usage) ================================
2326
- //* Cross-Bundle Singleton Key ==============================
2327
- // Use Symbol.for() to ensure scheduler is shared across bundle boundaries
2328
- // This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
2329
- __publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
2330
- let Scheduler = _Scheduler;
2331
- const getScheduler = () => Scheduler.get();
2332
- if (hmrData) {
2333
- hmrData.accept?.();
2758
+ return new Promise(
2759
+ (res, reject) => loader.load(
2760
+ input,
2761
+ (data) => {
2762
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
2763
+ res(data);
2764
+ },
2765
+ onProgress,
2766
+ (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
2767
+ )
2768
+ );
2769
+ };
2770
+ }
2771
+ function useLoader(loader, input, extensions, onProgress) {
2772
+ const keys = Array.isArray(input) ? input : [input];
2773
+ const fn = loadingFn(extensions, onProgress);
2774
+ const results = keys.map((key) => suspend(fn, [loader, key], { equal: is.equ }));
2775
+ return Array.isArray(input) ? results : results[0];
2334
2776
  }
2777
+ useLoader.preload = function(loader, input, extensions, onProgress) {
2778
+ const keys = Array.isArray(input) ? input : [input];
2779
+ keys.forEach((key) => preload(loadingFn(extensions, onProgress), [loader, key]));
2780
+ };
2781
+ useLoader.clear = function(loader, input) {
2782
+ const keys = Array.isArray(input) ? input : [input];
2783
+ keys.forEach((key) => clear([loader, key]));
2784
+ };
2785
+ useLoader.loader = getLoader;
2335
2786
 
2336
2787
  function useFrame(callback, priorityOrOptions) {
2337
2788
  const store = React.useContext(context);
@@ -2512,6 +2963,9 @@ function useTexture(input, optionsOrOnLoad) {
2512
2963
  const textureCache = useThree((state) => state.textures);
2513
2964
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
2514
2965
  const { onLoad, cache = false } = options;
2966
+ const onLoadRef = useRef(onLoad);
2967
+ onLoadRef.current = onLoad;
2968
+ const onLoadCalledForRef = useRef(null);
2515
2969
  const urls = useMemo(() => getUrls(input), [input]);
2516
2970
  const cachedResult = useMemo(() => {
2517
2971
  if (!cache) return null;
@@ -2522,9 +2976,13 @@ function useTexture(input, optionsOrOnLoad) {
2522
2976
  TextureLoader,
2523
2977
  IsObject(input) ? Object.values(input) : input
2524
2978
  );
2979
+ const inputKey = urls.join("\0");
2525
2980
  useLayoutEffect(() => {
2526
- if (!cachedResult) onLoad?.(loadedTextures);
2527
- }, [onLoad, cachedResult, loadedTextures]);
2981
+ if (cachedResult) return;
2982
+ if (onLoadCalledForRef.current === inputKey) return;
2983
+ onLoadCalledForRef.current = inputKey;
2984
+ onLoadRef.current?.(loadedTextures);
2985
+ }, [cachedResult, loadedTextures, inputKey]);
2528
2986
  useEffect(() => {
2529
2987
  if (cachedResult) return;
2530
2988
  if ("initTexture" in renderer) {
@@ -2691,14 +3149,31 @@ function useTextures() {
2691
3149
  }, [store]);
2692
3150
  }
2693
3151
 
2694
- function useRenderTarget(width, height, options) {
3152
+ function useRenderTarget(widthOrOptions, heightOrOptions, options) {
2695
3153
  const isLegacy = useThree((s) => s.isLegacy);
2696
3154
  const size = useThree((s) => s.size);
3155
+ let width;
3156
+ let height;
3157
+ let opts;
3158
+ if (typeof widthOrOptions === "object") {
3159
+ opts = widthOrOptions;
3160
+ } else if (typeof widthOrOptions === "number") {
3161
+ width = widthOrOptions;
3162
+ if (typeof heightOrOptions === "object") {
3163
+ height = widthOrOptions;
3164
+ opts = heightOrOptions;
3165
+ } else if (typeof heightOrOptions === "number") {
3166
+ height = heightOrOptions;
3167
+ opts = options;
3168
+ } else {
3169
+ height = widthOrOptions;
3170
+ }
3171
+ }
2697
3172
  return useMemo(() => {
2698
3173
  const w = width ?? size.width;
2699
3174
  const h = height ?? size.height;
2700
- return new WebGLRenderTarget(w, h, options);
2701
- }, [width, height, size.width, size.height, options, isLegacy]);
3175
+ return new WebGLRenderTarget(w, h, opts);
3176
+ }, [width, height, size.width, size.height, opts, isLegacy]);
2702
3177
  }
2703
3178
 
2704
3179
  function useStore() {
@@ -2748,28 +3223,18 @@ function addTail(callback) {
2748
3223
  function invalidate(state, frames = 1, stackFrames = false) {
2749
3224
  getScheduler().invalidate(frames, stackFrames);
2750
3225
  }
2751
- function advance(timestamp, runGlobalEffects = true, state, frame) {
3226
+ function advance(timestamp) {
2752
3227
  getScheduler().step(timestamp);
2753
3228
  }
2754
3229
 
2755
- const version = "10.0.0-alpha.1";
3230
+ const version = "10.0.0-alpha.2";
2756
3231
  const packageData = {
2757
3232
  version: version};
2758
3233
 
2759
3234
  function Xb(Tt) {
2760
3235
  return Tt && Tt.__esModule && Object.prototype.hasOwnProperty.call(Tt, "default") ? Tt.default : Tt;
2761
3236
  }
2762
- var Rm = { exports: {} }, Og = { exports: {} };
2763
- /**
2764
- * @license React
2765
- * react-reconciler.production.js
2766
- *
2767
- * Copyright (c) Meta Platforms, Inc. and affiliates.
2768
- *
2769
- * This source code is licensed under the MIT license found in the
2770
- * LICENSE file in the root directory of this source tree.
2771
- */
2772
- var _b;
3237
+ var Rm = { exports: {} }, Og = { exports: {} }, _b;
2773
3238
  function Kb() {
2774
3239
  return _b || (_b = 1, (function(Tt) {
2775
3240
  Tt.exports = function(m) {
@@ -3841,7 +4306,6 @@ Error generating stack: ` + l.message + `
3841
4306
  if (J === cl || J === jc) throw J;
3842
4307
  var Ge = Yn(29, J, null, P.mode);
3843
4308
  return Ge.lanes = H, Ge.return = P, Ge;
3844
- } finally {
3845
4309
  }
3846
4310
  };
3847
4311
  }
@@ -4495,7 +4959,6 @@ Error generating stack: ` + l.message + `
4495
4959
  var h = r.lastRenderedState, y = d(h, a);
4496
4960
  if (c.hasEagerState = true, c.eagerState = y, jn(y, h)) return go(t, r, c, 0), Ne === null && Bn(), false;
4497
4961
  } catch {
4498
- } finally {
4499
4962
  }
4500
4963
  if (a = yo(t, r, c, l), a !== null) return nt(a, t, l), ns(a, r, l), true;
4501
4964
  }
@@ -6916,10 +7379,7 @@ Error generating stack: ` + l.message + `
6916
7379
  function vr(t, r) {
6917
7380
  Sf(t, r), (t = t.alternate) && Sf(t, r);
6918
7381
  }
6919
- var ie = {}, Fm = React__default, tt = Tb, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy");
6920
- var gc = Symbol.for("react.activity");
6921
- var $r = Symbol.for("react.memo_cache_sentinel");
6922
- var Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
7382
+ var ie = {}, Fm = React__default, tt = Tb, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy"), gc = Symbol.for("react.activity"), $r = Symbol.for("react.memo_cache_sentinel"), Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
6923
7383
  m.cloneMutableInstance;
6924
7384
  var yc = m.appendInitialChild, Kp = m.finalizeInitialChildren, Rs = m.shouldSetTextContent, bc = m.createTextInstance;
6925
7385
  m.cloneMutableTextInstance;
@@ -7288,17 +7748,7 @@ No matching component was found for:
7288
7748
  }, Tt.exports.default = Tt.exports, Object.defineProperty(Tt.exports, "__esModule", { value: true });
7289
7749
  })(Og)), Og.exports;
7290
7750
  }
7291
- var Mg = { exports: {} };
7292
- /**
7293
- * @license React
7294
- * react-reconciler.development.js
7295
- *
7296
- * Copyright (c) Meta Platforms, Inc. and affiliates.
7297
- *
7298
- * This source code is licensed under the MIT license found in the
7299
- * LICENSE file in the root directory of this source tree.
7300
- */
7301
- var Rb;
7751
+ var Mg = { exports: {} }, Rb;
7302
7752
  function e0() {
7303
7753
  return Rb || (Rb = 1, (function(Tt) {
7304
7754
  process.env.NODE_ENV !== "production" && (Tt.exports = function(m) {
@@ -13065,10 +13515,7 @@ Check the render method of %s.`, G(di) || "Unknown")), i = zo(n), i.payload = {
13065
13515
  function Ic() {
13066
13516
  return di;
13067
13517
  }
13068
- var le = {}, qm = React__default, St = Tb, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy");
13069
- var Ds = Symbol.for("react.activity");
13070
- var Bh = Symbol.for("react.memo_cache_sentinel");
13071
- var ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
13518
+ var le = {}, qm = React__default, St = Tb, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy"), Ds = Symbol.for("react.activity"), Bh = Symbol.for("react.memo_cache_sentinel"), ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
13072
13519
  m.cloneMutableInstance;
13073
13520
  var bn = m.appendInitialChild, Ue = m.finalizeInitialChildren, ue = m.shouldSetTextContent, Do = m.createTextInstance;
13074
13521
  m.cloneMutableTextInstance;
@@ -14036,15 +14483,6 @@ function n0() {
14036
14483
  var t0 = n0();
14037
14484
  const r0 = Xb(t0);
14038
14485
 
14039
- /**
14040
- * @license React
14041
- * react-reconciler-constants.production.js
14042
- *
14043
- * Copyright (c) Meta Platforms, Inc. and affiliates.
14044
- *
14045
- * This source code is licensed under the MIT license found in the
14046
- * LICENSE file in the root directory of this source tree.
14047
- */
14048
14486
  const t = 1, o = 8, r = 32, e = 2;
14049
14487
 
14050
14488
  function createReconciler(config) {
@@ -14071,10 +14509,11 @@ function extend(objects) {
14071
14509
  function validateInstance(type, props) {
14072
14510
  const name = toPascalCase(type);
14073
14511
  const target = catalogue[name];
14074
- if (type !== "primitive" && !target)
14512
+ if (type !== "primitive" && !target) {
14075
14513
  throw new Error(
14076
14514
  `R3F: ${name} is not part of the THREE namespace! Did you forget to extend? See: https://docs.pmnd.rs/react-three-fiber/api/objects#using-3rd-party-objects-declaratively`
14077
14515
  );
14516
+ }
14078
14517
  if (type === "primitive" && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
14079
14518
  if (props.args !== void 0 && !Array.isArray(props.args)) throw new Error("R3F: The args prop must be an array!");
14080
14519
  }
@@ -14238,6 +14677,7 @@ function swapInstances() {
14238
14677
  instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
14239
14678
  instance.object.__r3f = instance;
14240
14679
  setFiberRef(fiber, instance.object);
14680
+ delete instance.appliedOnce;
14241
14681
  applyProps(instance.object, instance.props);
14242
14682
  if (instance.props.attach) {
14243
14683
  attach(parent, instance);
@@ -14311,8 +14751,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
14311
14751
  const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
14312
14752
  if (isTailSibling) swapInstances();
14313
14753
  },
14314
- finalizeInitialChildren: () => false,
14315
- commitMount() {
14754
+ finalizeInitialChildren: (instance) => {
14755
+ for (const prop in instance.props) {
14756
+ if (isFromRef(instance.props[prop])) return true;
14757
+ }
14758
+ return false;
14759
+ },
14760
+ commitMount(instance) {
14761
+ const resolved = {};
14762
+ for (const prop in instance.props) {
14763
+ const value = instance.props[prop];
14764
+ if (isFromRef(value)) {
14765
+ const ref = value[FROM_REF];
14766
+ if (ref.current != null) resolved[prop] = ref.current;
14767
+ }
14768
+ }
14769
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
14316
14770
  },
14317
14771
  getPublicInstance: (instance) => instance?.object,
14318
14772
  prepareForCommit: () => null,
@@ -14525,14 +14979,17 @@ function createRoot(canvas) {
14525
14979
  if (!prevRoot) _roots.set(canvas, { fiber, store });
14526
14980
  let onCreated;
14527
14981
  let lastCamera;
14528
- let lastConfiguredProps = {};
14982
+ const lastConfiguredProps = {};
14529
14983
  let configured = false;
14530
14984
  let pending = null;
14531
14985
  return {
14532
14986
  async configure(props = {}) {
14533
14987
  let resolve;
14534
14988
  pending = new Promise((_resolve) => resolve = _resolve);
14535
- let {
14989
+ const {
14990
+ id: canvasId,
14991
+ primaryCanvas,
14992
+ scheduler: schedulerConfig,
14536
14993
  gl: glConfig,
14537
14994
  renderer: rendererConfig,
14538
14995
  size: propsSize,
@@ -14540,10 +14997,7 @@ function createRoot(canvas) {
14540
14997
  events,
14541
14998
  onCreated: onCreatedCallback,
14542
14999
  shadows = false,
14543
- linear = false,
14544
- flat = false,
14545
15000
  textureColorSpace = SRGBColorSpace,
14546
- legacy = false,
14547
15001
  orthographic = false,
14548
15002
  frameloop = "always",
14549
15003
  dpr = [1, 2],
@@ -14554,9 +15008,11 @@ function createRoot(canvas) {
14554
15008
  onDragOverMissed,
14555
15009
  onDropMissed,
14556
15010
  autoUpdateFrustum = true,
14557
- occlusion = false
15011
+ occlusion = false,
15012
+ _sizeProps,
15013
+ forceEven
14558
15014
  } = props;
14559
- let state = store.getState();
15015
+ const state = store.getState();
14560
15016
  const defaultGLProps = {
14561
15017
  canvas,
14562
15018
  powerPreference: "high-performance",
@@ -14568,22 +15024,34 @@ function createRoot(canvas) {
14568
15024
  "WebGPURenderer (renderer prop) is not available in this build. Use @react-three/fiber or @react-three/fiber/webgpu instead."
14569
15025
  );
14570
15026
  }
14571
- (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU);
15027
+ const wantsGL = (state.isLegacy || glConfig || !R3F_BUILD_WEBGPU);
14572
15028
  if (glConfig && rendererConfig) {
14573
15029
  throw new Error("Cannot use both gl and renderer props at the same time");
14574
15030
  }
14575
15031
  let renderer = state.internal.actualRenderer;
15032
+ if (primaryCanvas && !R3F_BUILD_WEBGPU) {
15033
+ throw new Error(
15034
+ "The `primaryCanvas` prop for multi-canvas rendering is only available with WebGPU. Use @react-three/fiber/webgpu instead."
15035
+ );
15036
+ }
15037
+ if (primaryCanvas && wantsGL) {
15038
+ throw new Error(
15039
+ "The `primaryCanvas` prop for multi-canvas rendering cannot be used with WebGL. Remove the `gl` prop or use WebGPU."
15040
+ );
15041
+ }
14576
15042
  if (!state.internal.actualRenderer) {
14577
15043
  renderer = await resolveRenderer(glConfig, defaultGLProps, WebGLRenderer);
14578
15044
  state.internal.actualRenderer = renderer;
14579
- state.set({ isLegacy: true, gl: renderer, renderer });
15045
+ state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
14580
15046
  }
14581
15047
  let raycaster = state.raycaster;
14582
15048
  if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
14583
15049
  const { params, ...options } = raycastOptions || {};
14584
15050
  if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, { ...options });
14585
- if (!is.equ(params, raycaster.params, shallowLoose))
15051
+ if (!is.equ(params, raycaster.params, shallowLoose)) {
14586
15052
  applyProps(raycaster, { params: { ...raycaster.params, ...params } });
15053
+ }
15054
+ let tempCamera = state.camera;
14587
15055
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
14588
15056
  lastCamera = cameraOptions;
14589
15057
  const isCamera = cameraOptions?.isCamera;
@@ -14603,6 +15071,7 @@ function createRoot(canvas) {
14603
15071
  if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
14604
15072
  }
14605
15073
  state.set({ camera });
15074
+ tempCamera = camera;
14606
15075
  raycaster.camera = camera;
14607
15076
  }
14608
15077
  if (!state.scene) {
@@ -14620,7 +15089,7 @@ function createRoot(canvas) {
14620
15089
  rootScene: scene,
14621
15090
  internal: { ...prev.internal, container: scene }
14622
15091
  }));
14623
- const camera = state.camera;
15092
+ const camera = tempCamera;
14624
15093
  if (camera && !camera.parent) scene.add(camera);
14625
15094
  }
14626
15095
  if (events && !state.events.handlers) {
@@ -14634,9 +15103,17 @@ function createRoot(canvas) {
14634
15103
  wasEnabled = enabled;
14635
15104
  });
14636
15105
  }
15106
+ if (_sizeProps !== void 0) {
15107
+ state.set({ _sizeProps });
15108
+ }
15109
+ if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
15110
+ state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
15111
+ }
14637
15112
  const size = computeInitialSize(canvas, propsSize);
14638
- if (!is.equ(size, state.size, shallowLoose)) {
15113
+ if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
15114
+ const wasImperative = state._sizeImperative;
14639
15115
  state.setSize(size.width, size.height, size.top, size.left);
15116
+ if (!wasImperative) state.set({ _sizeImperative: false });
14640
15117
  }
14641
15118
  if (dpr !== void 0 && !is.equ(dpr, lastConfiguredProps.dpr, shallowLoose)) {
14642
15119
  state.setDpr(dpr);
@@ -14661,7 +15138,7 @@ function createRoot(canvas) {
14661
15138
  const handleXRFrame = (timestamp, frame) => {
14662
15139
  const state2 = store.getState();
14663
15140
  if (state2.frameloop === "never") return;
14664
- advance(timestamp, true);
15141
+ advance(timestamp);
14665
15142
  };
14666
15143
  const actualRenderer = state.internal.actualRenderer;
14667
15144
  const handleSessionChange = () => {
@@ -14673,16 +15150,16 @@ function createRoot(canvas) {
14673
15150
  };
14674
15151
  const xr = {
14675
15152
  connect() {
14676
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14677
- const actualRenderer2 = renderer2 || gl;
14678
- actualRenderer2.xr.addEventListener("sessionstart", handleSessionChange);
14679
- actualRenderer2.xr.addEventListener("sessionend", handleSessionChange);
15153
+ const { gl, renderer: renderer2 } = store.getState();
15154
+ const xrManager = (renderer2 || gl).xr;
15155
+ xrManager.addEventListener("sessionstart", handleSessionChange);
15156
+ xrManager.addEventListener("sessionend", handleSessionChange);
14680
15157
  },
14681
15158
  disconnect() {
14682
- const { gl, renderer: renderer2, isLegacy } = store.getState();
14683
- const actualRenderer2 = renderer2 || gl;
14684
- actualRenderer2.xr.removeEventListener("sessionstart", handleSessionChange);
14685
- actualRenderer2.xr.removeEventListener("sessionend", handleSessionChange);
15159
+ const { gl, renderer: renderer2 } = store.getState();
15160
+ const xrManager = (renderer2 || gl).xr;
15161
+ xrManager.removeEventListener("sessionstart", handleSessionChange);
15162
+ xrManager.removeEventListener("sessionend", handleSessionChange);
14686
15163
  }
14687
15164
  };
14688
15165
  if (typeof renderer.xr?.addEventListener === "function") xr.connect();
@@ -14706,35 +15183,21 @@ function createRoot(canvas) {
14706
15183
  } else if (is.obj(shadows)) {
14707
15184
  Object.assign(renderer.shadowMap, shadows);
14708
15185
  }
14709
- if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type)
15186
+ if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type) {
14710
15187
  renderer.shadowMap.needsUpdate = true;
14711
- }
14712
- {
14713
- const legacyChanged = legacy !== lastConfiguredProps.legacy;
14714
- const linearChanged = linear !== lastConfiguredProps.linear;
14715
- const flatChanged = flat !== lastConfiguredProps.flat;
14716
- if (legacyChanged) {
14717
- ColorManagement.enabled = !legacy;
14718
- lastConfiguredProps.legacy = legacy;
14719
15188
  }
14720
- if (!configured || linearChanged) {
14721
- renderer.outputColorSpace = linear ? LinearSRGBColorSpace : SRGBColorSpace;
14722
- lastConfiguredProps.linear = linear;
14723
- }
14724
- if (!configured || flatChanged) {
14725
- renderer.toneMapping = flat ? NoToneMapping : ACESFilmicToneMapping;
14726
- lastConfiguredProps.flat = flat;
14727
- }
14728
- if (legacyChanged && state.legacy !== legacy) state.set(() => ({ legacy }));
14729
- if (linearChanged && state.linear !== linear) state.set(() => ({ linear }));
14730
- if (flatChanged && state.flat !== flat) state.set(() => ({ flat }));
15189
+ }
15190
+ if (!configured) {
15191
+ renderer.outputColorSpace = SRGBColorSpace;
15192
+ renderer.toneMapping = ACESFilmicToneMapping;
14731
15193
  }
14732
15194
  if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
14733
15195
  if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
14734
15196
  lastConfiguredProps.textureColorSpace = textureColorSpace;
14735
15197
  }
14736
- if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose))
15198
+ if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
14737
15199
  applyProps(renderer, glConfig);
15200
+ }
14738
15201
  if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
14739
15202
  const currentRenderer = state.renderer;
14740
15203
  if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
@@ -14744,11 +15207,26 @@ function createRoot(canvas) {
14744
15207
  const scheduler = getScheduler();
14745
15208
  const rootId = state.internal.rootId;
14746
15209
  if (!rootId) {
14747
- const newRootId = scheduler.generateRootId();
15210
+ const newRootId = canvasId || scheduler.generateRootId();
14748
15211
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14749
15212
  getState: () => store.getState(),
14750
15213
  onError: (err) => store.getState().setError(err)
14751
15214
  });
15215
+ const unregisterCanvasTarget = scheduler.register(
15216
+ () => {
15217
+ const state2 = store.getState();
15218
+ if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
15219
+ const renderer2 = state2.internal.actualRenderer;
15220
+ renderer2.setCanvasTarget(state2.internal.canvasTarget);
15221
+ }
15222
+ },
15223
+ {
15224
+ id: `${newRootId}_canvasTarget`,
15225
+ rootId: newRootId,
15226
+ phase: "start",
15227
+ system: true
15228
+ }
15229
+ );
14752
15230
  const unregisterFrustum = scheduler.register(
14753
15231
  () => {
14754
15232
  const state2 = store.getState();
@@ -14790,11 +15268,15 @@ function createRoot(canvas) {
14790
15268
  }
14791
15269
  },
14792
15270
  {
14793
- id: `${newRootId}_render`,
15271
+ // Use canvas ID directly as job ID if available, otherwise use generated rootId
15272
+ id: canvasId || `${newRootId}_render`,
14794
15273
  rootId: newRootId,
14795
15274
  phase: "render",
14796
- system: true
15275
+ system: true,
14797
15276
  // Internal flag: this is a system job, not user-controlled
15277
+ // Apply scheduler config for render ordering and rate limiting
15278
+ ...schedulerConfig?.after && { after: schedulerConfig.after },
15279
+ ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14798
15280
  }
14799
15281
  );
14800
15282
  state.set((state2) => ({
@@ -14803,6 +15285,7 @@ function createRoot(canvas) {
14803
15285
  rootId: newRootId,
14804
15286
  unregisterRoot: () => {
14805
15287
  unregisterRoot();
15288
+ unregisterCanvasTarget();
14806
15289
  unregisterFrustum();
14807
15290
  unregisterVisibility();
14808
15291
  unregisterRender();
@@ -14861,15 +15344,24 @@ function unmountComponentAtNode(canvas, callback) {
14861
15344
  const renderer = state.internal.actualRenderer;
14862
15345
  const unregisterRoot = state.internal.unregisterRoot;
14863
15346
  if (unregisterRoot) unregisterRoot();
15347
+ const unregisterPrimary = state.internal.unregisterPrimary;
15348
+ if (unregisterPrimary) unregisterPrimary();
15349
+ const canvasTarget = state.internal.canvasTarget;
15350
+ if (canvasTarget?.dispose) canvasTarget.dispose();
14864
15351
  state.events.disconnect?.();
14865
15352
  cleanupHelperGroup(root.store);
14866
- renderer?.renderLists?.dispose?.();
14867
- renderer?.forceContextLoss?.();
14868
- if (renderer?.xr) state.xr.disconnect();
15353
+ if (state.isLegacy && renderer) {
15354
+ ;
15355
+ renderer.renderLists?.dispose?.();
15356
+ renderer.forceContextLoss?.();
15357
+ }
15358
+ if (!state.internal.isSecondary) {
15359
+ if (renderer?.xr) state.xr.disconnect();
15360
+ }
14869
15361
  dispose(state.scene);
14870
15362
  _roots.delete(canvas);
14871
15363
  if (callback) callback(canvas);
14872
- } catch (e) {
15364
+ } catch {
14873
15365
  }
14874
15366
  }, 500);
14875
15367
  }
@@ -14877,36 +15369,34 @@ function unmountComponentAtNode(canvas, callback) {
14877
15369
  }
14878
15370
  }
14879
15371
  function createPortal(children, container, state) {
14880
- return /* @__PURE__ */ jsx(PortalWrapper, { children, container, state });
15372
+ return /* @__PURE__ */ jsx(Portal, { children, container, state });
14881
15373
  }
14882
- function PortalWrapper({ children, container, state }) {
15374
+ function Portal({ children, container, state }) {
14883
15375
  const isRef = useCallback((obj) => obj && "current" in obj, []);
14884
- const [resolvedContainer, setResolvedContainer] = useState(() => {
15376
+ const [resolvedContainer, _setResolvedContainer] = useState(() => {
14885
15377
  if (isRef(container)) return container.current ?? null;
14886
15378
  return container;
14887
15379
  });
15380
+ const setResolvedContainer = useCallback(
15381
+ (newContainer) => {
15382
+ if (!newContainer || newContainer === resolvedContainer) return;
15383
+ _setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
15384
+ },
15385
+ [resolvedContainer, _setResolvedContainer, isRef]
15386
+ );
14888
15387
  useMemo(() => {
14889
- if (isRef(container)) {
14890
- const current = container.current;
14891
- if (!current) {
14892
- queueMicrotask(() => {
14893
- const updated = container.current;
14894
- if (updated && updated !== resolvedContainer) {
14895
- setResolvedContainer(updated);
14896
- }
14897
- });
14898
- } else if (current !== resolvedContainer) {
14899
- setResolvedContainer(current);
14900
- }
14901
- } else if (container !== resolvedContainer) {
14902
- setResolvedContainer(container);
15388
+ if (isRef(container) && !container.current) {
15389
+ return queueMicrotask(() => {
15390
+ setResolvedContainer(container.current);
15391
+ });
14903
15392
  }
14904
- }, [container, resolvedContainer, isRef]);
15393
+ setResolvedContainer(container);
15394
+ }, [container, isRef, setResolvedContainer]);
14905
15395
  if (!resolvedContainer) return /* @__PURE__ */ jsx(Fragment, {});
14906
15396
  const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
14907
- return /* @__PURE__ */ jsx(Portal, { children, container: resolvedContainer, state }, portalKey);
15397
+ return /* @__PURE__ */ jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
14908
15398
  }
14909
- function Portal({ state = {}, children, container }) {
15399
+ function PortalInner({ state = {}, children, container }) {
14910
15400
  const { events, size, injectScene = true, ...rest } = state;
14911
15401
  const previousRoot = useStore();
14912
15402
  const [raycaster] = useState(() => new Raycaster());
@@ -14927,11 +15417,12 @@ function Portal({ state = {}, children, container }) {
14927
15417
  };
14928
15418
  }, [portalScene, container, injectScene]);
14929
15419
  const inject = useMutableCallback((rootState, injectState) => {
15420
+ const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14930
15421
  let viewport = void 0;
14931
- if (injectState.camera && size) {
15422
+ if (injectState.camera && (size || injectState.size)) {
14932
15423
  const camera = injectState.camera;
14933
- viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), size);
14934
- if (camera !== rootState.camera) updateCamera(camera, size);
15424
+ viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
15425
+ if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14935
15426
  }
14936
15427
  return {
14937
15428
  // The intersect consists of the previous root state
@@ -14948,7 +15439,7 @@ function Portal({ state = {}, children, container }) {
14948
15439
  previousRoot,
14949
15440
  // Events, size and viewport can be overridden by the inject layer
14950
15441
  events: { ...rootState.events, ...injectState.events, ...events },
14951
- size: { ...rootState.size, ...size },
15442
+ size: resolvedSize,
14952
15443
  viewport: { ...rootState.viewport, ...viewport },
14953
15444
  // Layers are allowed to override events
14954
15445
  setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
@@ -14982,15 +15473,13 @@ function CanvasImpl({
14982
15473
  fallback,
14983
15474
  resize,
14984
15475
  style,
15476
+ id,
14985
15477
  gl,
14986
- renderer,
15478
+ renderer: rendererProp,
14987
15479
  events = createPointerEvents,
14988
15480
  eventSource,
14989
15481
  eventPrefix,
14990
15482
  shadows,
14991
- linear,
14992
- flat,
14993
- legacy,
14994
15483
  orthographic,
14995
15484
  frameloop,
14996
15485
  dpr,
@@ -15002,10 +15491,46 @@ function CanvasImpl({
15002
15491
  onDragOverMissed,
15003
15492
  onDropMissed,
15004
15493
  onCreated,
15494
+ hmr,
15495
+ width,
15496
+ height,
15497
+ background,
15498
+ forceEven,
15005
15499
  ...props
15006
15500
  }) {
15501
+ const { primaryCanvas, scheduler, ...rendererConfig } = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp) ? rendererProp : { primaryCanvas: void 0, scheduler: void 0 };
15502
+ const renderer = Object.keys(rendererConfig).length > 0 ? rendererConfig : rendererProp;
15007
15503
  React.useMemo(() => extend(THREE), []);
15008
15504
  const Bridge = useBridge();
15505
+ const backgroundProps = React.useMemo(() => {
15506
+ if (!background) return null;
15507
+ if (typeof background === "object" && !background.isColor) {
15508
+ const { backgroundMap, envMap, files, preset, ...rest } = background;
15509
+ return {
15510
+ ...rest,
15511
+ preset,
15512
+ files: envMap || files,
15513
+ backgroundFiles: backgroundMap,
15514
+ background: true
15515
+ };
15516
+ }
15517
+ if (typeof background === "number") {
15518
+ return { color: background, background: true };
15519
+ }
15520
+ if (typeof background === "string") {
15521
+ if (background in presetsObj) {
15522
+ return { preset: background, background: true };
15523
+ }
15524
+ if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
15525
+ return { files: background, background: true };
15526
+ }
15527
+ return { color: background, background: true };
15528
+ }
15529
+ if (background.isColor) {
15530
+ return { color: background, background: true };
15531
+ }
15532
+ return null;
15533
+ }, [background]);
15009
15534
  const hasInitialSizeRef = React.useRef(false);
15010
15535
  const measureConfig = React.useMemo(() => {
15011
15536
  if (!hasInitialSizeRef.current) {
@@ -15022,7 +15547,21 @@ function CanvasImpl({
15022
15547
  };
15023
15548
  }, [resize, hasInitialSizeRef.current]);
15024
15549
  const [containerRef, containerRect] = useMeasure(measureConfig);
15025
- if (!hasInitialSizeRef.current && containerRect.width > 0 && containerRect.height > 0) {
15550
+ const effectiveSize = React.useMemo(() => {
15551
+ let w = width ?? containerRect.width;
15552
+ let h = height ?? containerRect.height;
15553
+ if (forceEven) {
15554
+ w = Math.ceil(w / 2) * 2;
15555
+ h = Math.ceil(h / 2) * 2;
15556
+ }
15557
+ return {
15558
+ width: w,
15559
+ height: h,
15560
+ top: containerRect.top,
15561
+ left: containerRect.left
15562
+ };
15563
+ }, [width, height, containerRect, forceEven]);
15564
+ if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15026
15565
  hasInitialSizeRef.current = true;
15027
15566
  }
15028
15567
  const canvasRef = React.useRef(null);
@@ -15041,7 +15580,7 @@ function CanvasImpl({
15041
15580
  useIsomorphicLayoutEffect(() => {
15042
15581
  effectActiveRef.current = true;
15043
15582
  const canvas = canvasRef.current;
15044
- if (containerRect.width > 0 && containerRect.height > 0 && canvas) {
15583
+ if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
15045
15584
  if (!root.current) {
15046
15585
  root.current = createRoot(canvas);
15047
15586
  notifyAlpha({
@@ -15061,21 +15600,24 @@ function CanvasImpl({
15061
15600
  async function run() {
15062
15601
  if (!effectActiveRef.current || !root.current) return;
15063
15602
  await root.current.configure({
15603
+ id,
15604
+ primaryCanvas,
15605
+ scheduler,
15064
15606
  gl,
15065
15607
  renderer,
15066
15608
  scene,
15067
15609
  events,
15068
15610
  shadows,
15069
- linear,
15070
- flat,
15071
- legacy,
15072
15611
  orthographic,
15073
15612
  frameloop,
15074
15613
  dpr,
15075
15614
  performance,
15076
15615
  raycaster,
15077
15616
  camera,
15078
- size: containerRect,
15617
+ size: effectiveSize,
15618
+ // Store size props for reset functionality
15619
+ _sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
15620
+ forceEven,
15079
15621
  // Pass mutable reference to onPointerMissed so it's free to update
15080
15622
  onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
15081
15623
  onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
@@ -15099,7 +15641,10 @@ function CanvasImpl({
15099
15641
  });
15100
15642
  if (!effectActiveRef.current || !root.current) return;
15101
15643
  root.current.render(
15102
- /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsx(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: children ?? null }) }) })
15644
+ /* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
15645
+ backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
15646
+ children ?? null
15647
+ ] }) }) })
15103
15648
  );
15104
15649
  }
15105
15650
  run();
@@ -15121,6 +15666,35 @@ function CanvasImpl({
15121
15666
  };
15122
15667
  }
15123
15668
  }, []);
15669
+ React.useEffect(() => {
15670
+ if (hmr === false) return;
15671
+ const canvas = canvasRef.current;
15672
+ if (!canvas) return;
15673
+ const handleHMR = () => {
15674
+ queueMicrotask(() => {
15675
+ const rootEntry = _roots.get(canvas);
15676
+ if (rootEntry?.store) {
15677
+ rootEntry.store.setState((state) => ({
15678
+ nodes: {},
15679
+ uniforms: {},
15680
+ _hmrVersion: state._hmrVersion + 1
15681
+ }));
15682
+ }
15683
+ });
15684
+ };
15685
+ if (typeof import.meta !== "undefined" && import.meta.hot) {
15686
+ const hot = import.meta.hot;
15687
+ hot.on("vite:afterUpdate", handleHMR);
15688
+ return () => hot.dispose?.(() => {
15689
+ });
15690
+ }
15691
+ if (typeof module !== "undefined" && module.hot) {
15692
+ const hot = module.hot;
15693
+ hot.addStatusHandler((status) => {
15694
+ if (status === "idle") handleHMR();
15695
+ });
15696
+ }
15697
+ }, [hmr]);
15124
15698
  const pointerEvents = eventSource ? "none" : "auto";
15125
15699
  return /* @__PURE__ */ jsx(
15126
15700
  "div",
@@ -15135,7 +15709,7 @@ function CanvasImpl({
15135
15709
  ...style
15136
15710
  },
15137
15711
  ...props,
15138
- children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className: "r3f-canvas", style: { display: "block" }, children: fallback }) })
15712
+ children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, id, className: "r3f-canvas", style: { display: "block" }, children: fallback }) })
15139
15713
  }
15140
15714
  );
15141
15715
  }
@@ -15145,4 +15719,4 @@ function Canvas(props) {
15145
15719
 
15146
15720
  extend(THREE);
15147
15721
 
15148
- export { Block, Canvas, ErrorBoundary, IsObject, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Scheduler, Texture, _roots, act, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, getInstanceProps, getRootState, getScheduler, getUuidPrefix, hasConstructor, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isObject3D, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, prepare, reconciler, removeInteractivity, resolve, unmountComponentAtNode, updateCamera, updateFrustum, useBridge, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useMutableCallback, useRenderTarget, useStore, useTexture, useTextures, useThree };
15722
+ export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Scheduler, Texture, _roots, act, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, fromRef, getInstanceProps, getPrimary, getPrimaryIds, getRootState, getScheduler, getUuidPrefix, hasConstructor, hasPrimary, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isFromRef, isObject3D, isOnce, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, reconciler, registerPrimary, removeInteractivity, resolve, unmountComponentAtNode, unregisterPrimary, updateCamera, updateFrustum, useBridge, useEnvironment, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useMutableCallback, useRenderTarget, useStore, useTexture, useTextures, useThree, waitForPrimary };