@react-three/fiber 10.0.0-canary.164c7be → 10.0.0-canary.1be335a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/legacy.cjs CHANGED
@@ -62,9 +62,12 @@ const WebGPURenderer = class WebGPURenderer2 {
62
62
  }
63
63
  };
64
64
  const RenderTarget = null;
65
+ const CubeRenderTarget = null;
65
66
 
66
67
  const THREE = /*#__PURE__*/_mergeNamespaces({
67
68
  __proto__: null,
69
+ CubeRenderTarget: CubeRenderTarget,
70
+ CubeRenderTargetCompat: three.WebGLCubeRenderTarget,
68
71
  Inspector: Inspector,
69
72
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
70
73
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
@@ -342,15 +345,16 @@ function EnvironmentPortal({
342
345
  preset = void 0,
343
346
  extensions
344
347
  }) {
345
- const gl = fiber.useThree((state) => state.gl);
348
+ const renderer = fiber.useThree((state) => state.renderer);
349
+ const isLegacy = fiber.useThree((state) => state.isLegacy);
346
350
  const defaultScene = fiber.useThree((state) => state.scene);
347
351
  const camera = React__namespace.useRef(null);
348
352
  const [virtualScene] = React__namespace.useState(() => new three.Scene());
349
353
  const fbo = React__namespace.useMemo(() => {
350
- const fbo2 = new three.WebGLCubeRenderTarget(resolution);
354
+ const fbo2 = isLegacy ? new three.WebGLCubeRenderTarget(resolution) : new CubeRenderTarget(resolution);
351
355
  fbo2.texture.type = three.HalfFloatType;
352
356
  return fbo2;
353
- }, [resolution]);
357
+ }, [resolution, isLegacy]);
354
358
  React__namespace.useEffect(() => {
355
359
  return () => {
356
360
  fbo.dispose();
@@ -358,10 +362,10 @@ function EnvironmentPortal({
358
362
  }, [fbo]);
359
363
  React__namespace.useLayoutEffect(() => {
360
364
  if (frames === 1) {
361
- const autoClear = gl.autoClear;
362
- gl.autoClear = true;
363
- camera.current.update(gl, virtualScene);
364
- gl.autoClear = autoClear;
365
+ const autoClear = renderer.autoClear;
366
+ renderer.autoClear = true;
367
+ camera.current.update(renderer, virtualScene);
368
+ renderer.autoClear = autoClear;
365
369
  }
366
370
  return setEnvProps(background, scene, defaultScene, fbo.texture, {
367
371
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -378,7 +382,7 @@ function EnvironmentPortal({
378
382
  defaultScene,
379
383
  background,
380
384
  frames,
381
- gl,
385
+ renderer,
382
386
  blur,
383
387
  backgroundBlurriness,
384
388
  backgroundIntensity,
@@ -389,10 +393,10 @@ function EnvironmentPortal({
389
393
  let count = 1;
390
394
  fiber.useFrame(() => {
391
395
  if (frames === Infinity || count < frames) {
392
- const autoClear = gl.autoClear;
393
- gl.autoClear = true;
394
- camera.current.update(gl, virtualScene);
395
- gl.autoClear = autoClear;
396
+ const autoClear = renderer.autoClear;
397
+ renderer.autoClear = true;
398
+ camera.current.update(renderer, virtualScene);
399
+ renderer.autoClear = autoClear;
396
400
  count++;
397
401
  }
398
402
  });
@@ -635,8 +639,7 @@ function prepare(target, root, type, props) {
635
639
  object,
636
640
  eventCount: 0,
637
641
  handlers: {},
638
- isHidden: false,
639
- deferredRefs: []
642
+ isHidden: false
640
643
  };
641
644
  if (object) object.__r3f = instance;
642
645
  }
@@ -793,6 +796,17 @@ function unregisterVisibility(store, object) {
793
796
  internal.visibilityRegistry.delete(object.uuid);
794
797
  internal.occlusionCache.delete(object);
795
798
  }
799
+ function updateVisibilityHandlers(store, object, handlers) {
800
+ const { internal } = store.getState();
801
+ const entry = internal.visibilityRegistry.get(object.uuid);
802
+ if (!entry) return false;
803
+ entry.handlers = handlers;
804
+ if ((handlers.onOccluded || handlers.onVisible) && !internal.occlusionEnabled) {
805
+ object.occlusionTest = true;
806
+ enableOcclusion(store);
807
+ }
808
+ return true;
809
+ }
796
810
  function checkVisibility(state) {
797
811
  const { internal, camera } = state;
798
812
  const registry = internal.visibilityRegistry;
@@ -953,9 +967,9 @@ function diffProps(instance, newProps) {
953
967
  const { root, key } = resolve(instance.object, prop);
954
968
  if (root.constructor && root.constructor.length === 0) {
955
969
  const ctor = getMemoizedPrototype(root);
956
- if (!is.und(ctor)) changedProps[key] = ctor[key];
970
+ if (!is.und(ctor)) changedProps[prop] = ctor[key];
957
971
  } else {
958
- changedProps[key] = 0;
972
+ changedProps[prop] = 0;
959
973
  }
960
974
  }
961
975
  return changedProps;
@@ -980,10 +994,7 @@ function applyProps(object, props) {
980
994
  continue;
981
995
  }
982
996
  if (value === void 0) continue;
983
- if (isFromRef(value)) {
984
- instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
985
- continue;
986
- }
997
+ if (isFromRef(value)) continue;
987
998
  if (isOnce(value)) {
988
999
  if (instance?.appliedOnce?.has(prop)) continue;
989
1000
  if (instance) {
@@ -1019,6 +1030,19 @@ function applyProps(object, props) {
1019
1030
  } else if (isVectorLike(target) && is.num(value)) {
1020
1031
  if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
1021
1032
  else target.set(value);
1033
+ } else if (root instanceof three.ShaderMaterial && key === "uniforms" && is.obj(value)) {
1034
+ if (!is.obj(root.uniforms)) root.uniforms = {};
1035
+ const uniforms = root.uniforms;
1036
+ const nextUniforms = value;
1037
+ for (const name in nextUniforms) {
1038
+ const uniform = nextUniforms[name];
1039
+ const targetUniform = uniforms[name];
1040
+ if (targetUniform) Object.assign(targetUniform, uniform);
1041
+ else {
1042
+ const nextUniform = uniform instanceof three.Uniform ? uniform.clone() : new three.Uniform(uniform.value);
1043
+ uniforms[name] = nextUniform;
1044
+ }
1045
+ }
1022
1046
  } else {
1023
1047
  root[key] = value;
1024
1048
  if (key.endsWith("Node") && root.isMaterial) {
@@ -1037,6 +1061,9 @@ function applyProps(object, props) {
1037
1061
  if (instance.eventCount && object2.raycast !== null) {
1038
1062
  rootState.internal.interaction.push(object2);
1039
1063
  }
1064
+ }
1065
+ if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
1066
+ const object2 = instance.object;
1040
1067
  const root = findInitialRoot(instance);
1041
1068
  const visibilityHandlers = {
1042
1069
  onFramed: instance.handlers.onFramed,
@@ -1044,7 +1071,9 @@ function applyProps(object, props) {
1044
1071
  onVisible: instance.handlers.onVisible
1045
1072
  };
1046
1073
  if (hasVisibilityHandlers(visibilityHandlers)) {
1047
- registerVisibility(root, object2, visibilityHandlers);
1074
+ if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
1075
+ registerVisibility(root, object2, visibilityHandlers);
1076
+ }
1048
1077
  } else {
1049
1078
  unregisterVisibility(root, object2);
1050
1079
  }
@@ -1572,6 +1601,7 @@ function createPointerEvents(store) {
1572
1601
  set((state) => ({ events: { ...state.events, connected: void 0 } }));
1573
1602
  }
1574
1603
  },
1604
+ // PREVIEW (A8): the XR pointer registration API may change before stable.
1575
1605
  registerPointer: (config) => {
1576
1606
  const pointerId = nextXRPointerId++;
1577
1607
  xrPointers.set(pointerId, config);
@@ -1658,6 +1688,13 @@ function notifyAlpha({ message, link }) {
1658
1688
  }
1659
1689
  }
1660
1690
 
1691
+ const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
1692
+ function isInternalRendererAccess(stack, callerDepth = 2) {
1693
+ if (!stack) return false;
1694
+ const caller = stack.split("\n")[callerDepth] ?? "";
1695
+ return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
1696
+ }
1697
+
1661
1698
  const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
1662
1699
  const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
1663
1700
  const createStore = (invalidate, advance) => {
@@ -1790,6 +1827,7 @@ const createStore = (invalidate, advance) => {
1790
1827
  }),
1791
1828
  setFrameloop: (frameloop = "always") => {
1792
1829
  set(() => ({ frameloop }));
1830
+ scheduler.getScheduler().frameloop = frameloop;
1793
1831
  },
1794
1832
  setError: (error) => set(() => ({ error })),
1795
1833
  error: null,
@@ -1857,8 +1895,7 @@ const createStore = (invalidate, advance) => {
1857
1895
  const currentState = rootStore.getState();
1858
1896
  if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1859
1897
  const stack = new Error().stack || "";
1860
- const isInternalAccess = stack.includes("zustand") || stack.includes("setState") || stack.includes("Object.assign") || stack.includes("react-three-fiber/packages/fiber/src/core");
1861
- if (!isInternalAccess) {
1898
+ if (!isInternalRendererAccess(stack)) {
1862
1899
  const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1863
1900
  notifyDepreciated({
1864
1901
  heading: "Accessing state.gl in WebGPU mode",
@@ -1971,19 +2008,25 @@ function loadingFn(extensions, onProgress) {
1971
2008
  );
1972
2009
  };
1973
2010
  }
1974
- function useLoader(loader, input, extensions, onProgress) {
1975
- const keys = Array.isArray(input) ? input : [input];
2011
+ function useLoader(loader, input, extensions, onProgress, cacheKey) {
2012
+ const inputs = Array.isArray(input) ? input : [input];
2013
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1976
2014
  const fn = loadingFn(extensions, onProgress);
1977
- const results = keys.map((key) => suspendReact.suspend(fn, [loader, key], { equal: is.equ }));
2015
+ const results = cacheKeys.map(
2016
+ (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
2017
+ );
1978
2018
  return Array.isArray(input) ? results : results[0];
1979
2019
  }
1980
- useLoader.preload = function(loader, input, extensions, onProgress) {
1981
- const keys = Array.isArray(input) ? input : [input];
1982
- keys.forEach((key) => suspendReact.preload(loadingFn(extensions, onProgress), [loader, key]));
2020
+ useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
2021
+ const inputs = Array.isArray(input) ? input : [input];
2022
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
2023
+ cacheKeys.forEach(
2024
+ (key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
2025
+ );
1983
2026
  };
1984
- useLoader.clear = function(loader, input) {
1985
- const keys = Array.isArray(input) ? input : [input];
1986
- keys.forEach((key) => suspendReact.clear([loader, key]));
2027
+ useLoader.clear = function(loader, input, cacheKey) {
2028
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
2029
+ cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
1987
2030
  };
1988
2031
  useLoader.loader = getLoader;
1989
2032
 
@@ -2403,7 +2446,7 @@ function advance(timestamp) {
2403
2446
  scheduler.getScheduler().step(timestamp);
2404
2447
  }
2405
2448
 
2406
- const version = "10.0.0-alpha.2";
2449
+ const version = "10.0.0-alpha.3";
2407
2450
  const packageData = {
2408
2451
  version: version};
2409
2452
 
@@ -13659,7 +13702,7 @@ function n0() {
13659
13702
  var t0 = n0();
13660
13703
  const r0 = Xb(t0);
13661
13704
 
13662
- const t = 1, o = 8, r = 32, e = 2;
13705
+ const t = 1, o = 8, r = 32, e = 2, i$1 = 268435456;
13663
13706
 
13664
13707
  function createReconciler(config) {
13665
13708
  const reconciler2 = r0(config);
@@ -13824,7 +13867,15 @@ function setFiberRef(fiber, publicInstance) {
13824
13867
  }
13825
13868
  }
13826
13869
  const reconstructed = [];
13827
- function swapInstances() {
13870
+ function flushReconstructedInstances() {
13871
+ if (reconstructed.length === 0) return;
13872
+ try {
13873
+ swapReconstructedInstances();
13874
+ } finally {
13875
+ reconstructed.length = 0;
13876
+ }
13877
+ }
13878
+ function swapReconstructedInstances() {
13828
13879
  for (const [instance] of reconstructed) {
13829
13880
  const parent = instance.parent;
13830
13881
  if (parent) {
@@ -13842,6 +13893,7 @@ function swapInstances() {
13842
13893
  }
13843
13894
  }
13844
13895
  if (instance.isHidden) unhideInstance(instance);
13896
+ if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
13845
13897
  if (instance.object.__r3f) delete instance.object.__r3f;
13846
13898
  if (instance.type !== "primitive") disposeOnIdle(instance.object);
13847
13899
  }
@@ -13854,6 +13906,8 @@ function swapInstances() {
13854
13906
  instance.object.__r3f = instance;
13855
13907
  setFiberRef(fiber, instance.object);
13856
13908
  delete instance.appliedOnce;
13909
+ instance.eventCount = 0;
13910
+ instance.handlers = {};
13857
13911
  applyProps(instance.object, instance.props);
13858
13912
  if (instance.props.attach) {
13859
13913
  attach(parent, instance);
@@ -13870,14 +13924,114 @@ function swapInstances() {
13870
13924
  invalidateInstance(instance);
13871
13925
  }
13872
13926
  }
13873
- reconstructed.length = 0;
13874
13927
  }
13875
13928
  const handleTextInstance = () => {
13876
13929
  };
13877
13930
  const NO_CONTEXT = {};
13878
13931
  let currentUpdatePriority = NoEventPriority;
13879
- const NoFlags = 0;
13880
- const Update = 4;
13932
+ function getEventPriority(type) {
13933
+ switch (type) {
13934
+ case "beforetoggle":
13935
+ case "cancel":
13936
+ case "click":
13937
+ case "close":
13938
+ case "contextmenu":
13939
+ case "copy":
13940
+ case "cut":
13941
+ case "auxclick":
13942
+ case "dblclick":
13943
+ case "dragend":
13944
+ case "dragstart":
13945
+ case "drop":
13946
+ case "focusin":
13947
+ case "focusout":
13948
+ case "input":
13949
+ case "invalid":
13950
+ case "keydown":
13951
+ case "keypress":
13952
+ case "keyup":
13953
+ case "mousedown":
13954
+ case "mouseup":
13955
+ case "paste":
13956
+ case "pause":
13957
+ case "play":
13958
+ case "pointercancel":
13959
+ case "pointerdown":
13960
+ case "pointerup":
13961
+ case "ratechange":
13962
+ case "reset":
13963
+ case "resize":
13964
+ case "seeked":
13965
+ case "submit":
13966
+ case "touchcancel":
13967
+ case "touchend":
13968
+ case "touchstart":
13969
+ case "volumechange":
13970
+ case "change":
13971
+ case "selectionchange":
13972
+ case "compositionstart":
13973
+ case "compositionend":
13974
+ case "compositionupdate":
13975
+ case "beforeinput":
13976
+ case "blur":
13977
+ case "fullscreenchange":
13978
+ case "focus":
13979
+ case "hashchange":
13980
+ case "popstate":
13981
+ case "select":
13982
+ case "selectstart":
13983
+ return e;
13984
+ case "drag":
13985
+ case "dragenter":
13986
+ case "dragexit":
13987
+ case "dragleave":
13988
+ case "dragover":
13989
+ case "mousemove":
13990
+ case "mouseout":
13991
+ case "mouseover":
13992
+ case "pointermove":
13993
+ case "pointerout":
13994
+ case "pointerover":
13995
+ case "scroll":
13996
+ case "touchmove":
13997
+ case "wheel":
13998
+ case "mouseenter":
13999
+ case "mouseleave":
14000
+ case "pointerenter":
14001
+ case "pointerleave":
14002
+ return o;
14003
+ case "message": {
14004
+ switch (Tb.unstable_getCurrentPriorityLevel()) {
14005
+ case Tb.unstable_ImmediatePriority:
14006
+ return e;
14007
+ case Tb.unstable_UserBlockingPriority:
14008
+ return o;
14009
+ case Tb.unstable_NormalPriority:
14010
+ case Tb.unstable_LowPriority:
14011
+ return r;
14012
+ case Tb.unstable_IdlePriority:
14013
+ return i$1;
14014
+ default:
14015
+ return r;
14016
+ }
14017
+ }
14018
+ default:
14019
+ return r;
14020
+ }
14021
+ }
14022
+ function scheduleMicrotask(callback) {
14023
+ if (typeof queueMicrotask === "function") {
14024
+ queueMicrotask(callback);
14025
+ } else if (typeof Promise !== "undefined") {
14026
+ Promise.resolve().then(callback).catch((error) => {
14027
+ setTimeout(() => {
14028
+ throw error;
14029
+ });
14030
+ });
14031
+ } else {
14032
+ setTimeout(callback);
14033
+ }
14034
+ }
13881
14035
  const reconciler = /* @__PURE__ */ createReconciler({
13882
14036
  isPrimaryRenderer: false,
13883
14037
  warnsIfNotActing: false,
@@ -13916,16 +14070,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
13916
14070
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
13917
14071
  else if (newProps.args?.some((value, index) => value !== oldProps.args?.[index])) reconstruct = true;
13918
14072
  if (reconstruct) {
13919
- reconstructed.push([instance, { ...newProps }, fiber]);
14073
+ reconstructed.push([instance, getInstanceProps(newProps), fiber]);
13920
14074
  } else {
13921
14075
  const changedProps = diffProps(instance, newProps);
13922
- if (Object.keys(changedProps).length) {
13923
- Object.assign(instance.props, changedProps);
13924
- applyProps(instance.object, changedProps);
13925
- }
14076
+ const attach2 = instance.props.attach;
14077
+ instance.props = getInstanceProps(newProps);
14078
+ if (attach2 !== void 0) instance.props.attach = attach2;
14079
+ else delete instance.props.attach;
14080
+ if (Object.keys(changedProps).length) applyProps(instance.object, changedProps);
13926
14081
  }
13927
- const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
13928
- if (isTailSibling) swapInstances();
13929
14082
  },
13930
14083
  finalizeInitialChildren: (instance) => {
13931
14084
  for (const prop in instance.props) {
@@ -13950,8 +14103,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
13950
14103
  const target = container.getState().internal.container ?? container.getState().scene;
13951
14104
  return prepare(target, container, "", {});
13952
14105
  },
13953
- resetAfterCommit: () => {
13954
- },
14106
+ // Reconstructed instances are swapped once all mutations are committed,
14107
+ // before layout effects run so refs point to the new objects
14108
+ resetAfterCommit: flushReconstructedInstances,
13955
14109
  shouldSetTextContent: () => false,
13956
14110
  clearContainer: () => false,
13957
14111
  hideInstance,
@@ -13959,6 +14113,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
13959
14113
  createTextInstance: handleTextInstance,
13960
14114
  hideTextInstance: handleTextInstance,
13961
14115
  unhideTextInstance: handleTextInstance,
14116
+ // Mirrors react-dom, which flushes discrete work in microtasks
14117
+ supportsMicrotasks: true,
14118
+ scheduleMicrotask,
13962
14119
  scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
13963
14120
  cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
13964
14121
  noTimeout: -1,
@@ -13999,28 +14156,9 @@ const reconciler = /* @__PURE__ */ createReconciler({
13999
14156
  },
14000
14157
  resolveUpdatePriority() {
14001
14158
  if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
14002
- switch (typeof window !== "undefined" && window.event?.type) {
14003
- case "click":
14004
- case "contextmenu":
14005
- case "dblclick":
14006
- case "dragenter":
14007
- case "dragleave":
14008
- case "drop":
14009
- case "pointercancel":
14010
- case "pointerdown":
14011
- case "pointerup":
14012
- return e;
14013
- case "dragover":
14014
- case "pointermove":
14015
- case "pointerout":
14016
- case "pointerover":
14017
- case "pointerenter":
14018
- case "pointerleave":
14019
- case "wheel":
14020
- return o;
14021
- default:
14022
- return r;
14023
- }
14159
+ const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
14160
+ if (eventType === void 0) return r;
14161
+ return getEventPriority(eventType);
14024
14162
  },
14025
14163
  resetFormInstance() {
14026
14164
  },
@@ -14069,11 +14207,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
14069
14207
  cloneMutableTextInstance(textInstance) {
14070
14208
  return textInstance;
14071
14209
  },
14210
+ // React 19.2 added these for root-level view transitions. They only run for hosts that opt into
14211
+ // view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
14212
+ // into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
14213
+ // React never calls them. They stay as no-ops rather than throws: the host config is shared by
14214
+ // every root, and a throw here would turn a React-internal probe into a hard crash for users.
14215
+ // Guarded by a test asserting a full render + unmount never reaches them.
14072
14216
  cloneRootViewTransitionContainer(_rootContainer) {
14073
- throw new Error("Not implemented.");
14074
14217
  },
14075
14218
  removeRootViewTransitionClone(_rootContainer, _clone) {
14076
- throw new Error("Not implemented.");
14077
14219
  },
14078
14220
  // https://github.com/facebook/react/pull/32465
14079
14221
  createFragmentInstance: (_fiber) => null,
@@ -14158,6 +14300,7 @@ function createRoot(canvas) {
14158
14300
  const lastConfiguredProps = {};
14159
14301
  let configured = false;
14160
14302
  let pending = null;
14303
+ let rendererSetup = null;
14161
14304
  return {
14162
14305
  async configure(props = {}) {
14163
14306
  let resolve;
@@ -14188,7 +14331,7 @@ function createRoot(canvas) {
14188
14331
  forceEven
14189
14332
  } = props;
14190
14333
  const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || three.SRGBColorSpace;
14191
- const state = store.getState();
14334
+ let state = store.getState();
14192
14335
  const defaultGLProps = {
14193
14336
  canvas,
14194
14337
  powerPreference: "high-performance",
@@ -14216,9 +14359,21 @@ function createRoot(canvas) {
14216
14359
  );
14217
14360
  }
14218
14361
  if (!state.internal.actualRenderer) {
14219
- renderer = await resolveRenderer(glConfig, defaultGLProps, three.WebGLRenderer);
14220
- state.internal.actualRenderer = renderer;
14221
- state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
14362
+ if (!rendererSetup) {
14363
+ rendererSetup = (async () => {
14364
+ {
14365
+ renderer = await resolveRenderer(glConfig, defaultGLProps, three.WebGLRenderer);
14366
+ state.internal.actualRenderer = renderer;
14367
+ state.set({ isLegacy: true, gl: renderer, renderer, primaryStore: store });
14368
+ }
14369
+ })().catch((err) => {
14370
+ rendererSetup = null;
14371
+ throw err;
14372
+ });
14373
+ }
14374
+ await rendererSetup;
14375
+ state = store.getState();
14376
+ renderer = state.internal.actualRenderer;
14222
14377
  }
14223
14378
  let raycaster = state.raycaster;
14224
14379
  if (!raycaster) state.set({ raycaster: raycaster = new three.Raycaster() });
@@ -14568,7 +14723,8 @@ function unmountComponentAtNode(canvas, callback) {
14568
14723
  dispose(state.scene);
14569
14724
  _roots.delete(canvas);
14570
14725
  if (callback) callback(canvas);
14571
- } catch {
14726
+ } catch (error) {
14727
+ console.warn("[R3F] Error while unmounting root; teardown may be incomplete:", error);
14572
14728
  }
14573
14729
  }, 500);
14574
14730
  }
@@ -14631,6 +14787,7 @@ function PortalInner({ state = {}, children, container }) {
14631
14787
  viewport = rootState.viewport.getCurrentViewport(camera, new three.Vector3(), resolvedSize);
14632
14788
  if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14633
14789
  }
14790
+ const set = injectState.set;
14634
14791
  return {
14635
14792
  // The intersect consists of the previous root state
14636
14793
  ...rootState,
@@ -14649,7 +14806,7 @@ function PortalInner({ state = {}, children, container }) {
14649
14806
  size: resolvedSize,
14650
14807
  viewport: { ...rootState.viewport, ...viewport },
14651
14808
  // Layers are allowed to override events
14652
- setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
14809
+ setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
14653
14810
  // Container for child attachment - the portalScene (injected or container itself)
14654
14811
  internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
14655
14812
  };
@@ -14708,14 +14865,49 @@ function parseBackground(background) {
14708
14865
  return null;
14709
14866
  }
14710
14867
 
14868
+ function parseRendererConfig(rendererProp) {
14869
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14870
+ if (isRendererConfig) {
14871
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14872
+ return {
14873
+ primaryCanvas: pc,
14874
+ scheduler: sc,
14875
+ renderer: Object.keys(rest).length > 0 ? rest : rendererProp
14876
+ };
14877
+ }
14878
+ return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14879
+ }
14880
+
14881
+ const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14882
+ const registries = /* @__PURE__ */ new WeakMap();
14883
+ function getKindRegistry(store, kind) {
14884
+ let registry = registries.get(store);
14885
+ if (!registry) {
14886
+ registry = {
14887
+ nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14888
+ uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14889
+ buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14890
+ gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14891
+ };
14892
+ registries.set(store, registry);
14893
+ }
14894
+ return registry[kind];
14895
+ }
14896
+ function invalidateResource(store, kind, scopeKey) {
14897
+ const registry = getKindRegistry(store, kind);
14898
+ {
14899
+ registry.valid.clear();
14900
+ registry.staged.clear();
14901
+ }
14902
+ }
14903
+ function invalidateAllResources(store) {
14904
+ for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
14905
+ }
14906
+
14711
14907
  function clearHmrCaches(store) {
14712
- store.setState((state) => ({
14713
- nodes: {},
14714
- uniforms: {},
14715
- buffers: {},
14716
- gpuStorage: {},
14717
- _hmrVersion: state._hmrVersion + 1
14718
- }));
14908
+ const primary = store.getState().primaryStore ?? store;
14909
+ invalidateAllResources(primary);
14910
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
14719
14911
  }
14720
14912
 
14721
14913
  function CanvasImpl({
@@ -14751,18 +14943,7 @@ function CanvasImpl({
14751
14943
  forceEven,
14752
14944
  ...props
14753
14945
  }) {
14754
- const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14755
- let primaryCanvas;
14756
- let scheduler;
14757
- let renderer;
14758
- if (isRendererConfig) {
14759
- const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14760
- primaryCanvas = pc;
14761
- scheduler = sc;
14762
- renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
14763
- } else {
14764
- renderer = rendererProp;
14765
- }
14946
+ const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
14766
14947
  React__namespace.useMemo(() => extend(THREE), []);
14767
14948
  const Bridge = useBridge();
14768
14949
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
@@ -14807,6 +14988,7 @@ function CanvasImpl({
14807
14988
  const handleDropMissed = useMutableCallback(onDropMissed);
14808
14989
  const [block, setBlock] = React__namespace.useState(false);
14809
14990
  const [error, setError] = React__namespace.useState(false);
14991
+ const [fallbackVisible, setFallbackVisible] = React__namespace.useState(false);
14810
14992
  if (block) throw block;
14811
14993
  if (error) throw error;
14812
14994
  const root = React__namespace.useRef(null);
@@ -14814,6 +14996,7 @@ function CanvasImpl({
14814
14996
  const unsubscribeErrorRef = React__namespace.useRef(null);
14815
14997
  useIsomorphicLayoutEffect(() => {
14816
14998
  effectActiveRef.current = true;
14999
+ if (fallbackVisible) return;
14817
15000
  const canvas = canvasRef.current;
14818
15001
  if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
14819
15002
  if (!root.current) {
@@ -14834,7 +15017,7 @@ function CanvasImpl({
14834
15017
  }
14835
15018
  async function run() {
14836
15019
  if (!effectActiveRef.current || !root.current) return;
14837
- await root.current.configure({
15020
+ const configured = await root.current.configure({
14838
15021
  id,
14839
15022
  primaryCanvas,
14840
15023
  scheduler,
@@ -14875,8 +15058,20 @@ function CanvasImpl({
14875
15058
  }
14876
15059
  onCreated?.(state);
14877
15060
  }
14878
- });
14879
- if (!effectActiveRef.current || !root.current) return;
15061
+ }).then(
15062
+ () => true,
15063
+ // Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
15064
+ // inside <canvas>, which browsers don't display, so surface it as visible DOM
15065
+ // instead; with no fallback, rethrow to an external error boundary. (#3757)
15066
+ (setupError) => {
15067
+ if (effectActiveRef.current) {
15068
+ if (fallback != null) setFallbackVisible(true);
15069
+ else setError(setupError);
15070
+ }
15071
+ return false;
15072
+ }
15073
+ );
15074
+ if (!configured || !effectActiveRef.current || !root.current) return;
14880
15075
  root.current.render(
14881
15076
  /* @__PURE__ */ jsxRuntime.jsx(Bridge, { children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Block, { set: setBlock }), children: [
14882
15077
  backgroundProps && /* @__PURE__ */ jsxRuntime.jsx(Environment, { ...backgroundProps }),
@@ -14939,7 +15134,11 @@ function CanvasImpl({
14939
15134
  ...style
14940
15135
  },
14941
15136
  ...props,
14942
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
15137
+ children: fallbackVisible ? (
15138
+ // Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
15139
+ // it exists in the tree but browsers never paint it, which is the whole bug (#3757).
15140
+ fallback
15141
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
14943
15142
  "canvas",
14944
15143
  {
14945
15144
  ref: canvasRef,