@react-three/fiber 9.7.0 → 9.8.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @react-three/fiber
2
2
 
3
+ ## 9.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4ae2f816543f21e72b38d3e1328a21fc312428c1: Support React 19.3. Transitions that touch a `<ViewTransition>` subtree inside the canvas now commit synchronously, mirroring react-dom in browsers without view transitions, instead of leaving the reconciler stuck mid-commit.
8
+ - bc240cc56dbb0162737b2df0e99077c889d7cf2e: feat: configure and render roots synchronously; suspend on async renderers.
9
+
10
+ `root.configure()` now runs synchronously unless `gl` is an async factory (WebGPU), and the first `root.render()` mounts synchronously once the root is configured; later renders are batched as before. The returned promises carry a `status` field that React's `use` reads, and every root exposes `ready`, which is pending only while an async renderer is being created.
11
+
12
+ `<Canvas>` uses this to commit its scene inside its own layout effect, so `onCreated` runs while the wrapper is mounted and the events are connected before the browser paints. With an async renderer the canvas suspends on `root.ready` internally, without showing a parent fallback, and mounts once it resolves. Unmounting meanwhile never renders the scene. A renderer factory that fails now reaches the nearest error boundary instead of an unhandled rejection.
13
+
14
+ Configure calls made while another is in flight are applied after it, in order. The `flushSync` caveats apply to that first `root.render()` when it is called from inside another root's commit.
15
+
16
+ ### Patch Changes
17
+
18
+ - f0f1cdf4336519a988faadae7f8607bc0cd8d7e2: fix: unmount roots deterministically instead of after a 500ms timer.
19
+
20
+ `unmountComponentAtNode` deferred disposal by 500ms. A `<StrictMode>` remount lands inside that window and reuses the same root, store, scene and GL context, so the deferred callback destroyed a live root. `forceContextLoss()` is permanent, which left the canvas blank for the rest of the session.
21
+
22
+ Unmounting now claims the root and runs the teardown through React once the unmounted tree's effect cleanups have flushed. Using the root again through `configure` or `render` before then cancels it. A root that is unmounted, remounted and unmounted again is torn down exactly once.
23
+
24
+ - bc240cc56dbb0162737b2df0e99077c889d7cf2e: fix: don't resurrect a `<Canvas>` root that unmounts before its render settles.
25
+
26
+ `<Canvas>` configured its root asynchronously. When the canvas was unmounted between that effect and the microtasks it queued, for example by a spring reparenting it through a synchronous state update from a layout effect, the stale continuation still rendered into the torn-down root. Its Provider then mounted again and called `events.connect` with the unmounted wrapper `div`, throwing `Cannot read properties of null (reading 'addEventListener')`, and the resurrected root leaked a live WebGL context.
27
+
28
+ The scene now commits inside the canvas' own commit, a deferred render bails once the root is claimed for teardown, and `unmountComponentAtNode` waits for a configure still in flight so the renderer it creates is disposed rather than orphaned.
29
+
3
30
  ## 9.7.0
4
31
 
5
32
  ### Minor Changes
@@ -610,7 +610,8 @@ declare namespace ReactReconciler {
610
610
  type LanePriority = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17;
611
611
 
612
612
  type Lanes = number;
613
- type Lane = number;
613
+
614
+ type Lane = number & { __LaneBrand: any };
614
615
 
615
616
  type Flags = number;
616
617
 
@@ -941,7 +942,6 @@ declare namespace ReactReconciler {
941
942
  onCaughtError: (error: Error, info: BaseErrorInfo) => void,
942
943
  onRecoverableError: (error: Error, info: BaseErrorInfo) => void,
943
944
  onDefaultTransitionIndicator: () => void,
944
- transitionCallbacks: null | TransitionTracingCallbacks,
945
945
  ): OpaqueRoot;
946
946
 
947
947
  createPortal(
@@ -951,8 +951,6 @@ declare namespace ReactReconciler {
951
951
  key?: string | null,
952
952
  ): ReactPortal;
953
953
 
954
- registerMutableSourceForHydration(root: FiberRoot, mutableSource: MutableSource): void;
955
-
956
954
  createComponentSelector(component: React$AbstractComponent<never, unknown>): ComponentSelector;
957
955
 
958
956
  createHasPseudoClassSelector(selectors: Selector[]): HasPseudoClassSelector;
@@ -987,8 +985,12 @@ declare namespace ReactReconciler {
987
985
  isStrictMode: boolean,
988
986
  concurrentUpdatesByDefaultOverride: null | boolean,
989
987
  identifierPrefix: string,
990
- onRecoverableError: (error: Error) => void,
988
+ onUncaughtError: (error: Error, info: BaseErrorInfo & { errorBoundary?: Component }) => void,
989
+ onCaughtError: (error: Error, info: BaseErrorInfo) => void,
990
+ onRecoverableError: (error: Error, info: BaseErrorInfo) => void,
991
+ onDefaultTransitionIndicator: () => void,
991
992
  transitionCallbacks: null | TransitionTracingCallbacks,
993
+ formState: unknown,
992
994
  ): OpaqueRoot;
993
995
 
994
996
  updateContainer(
@@ -998,17 +1000,27 @@ declare namespace ReactReconciler {
998
1000
  callback?: (() => void) | null,
999
1001
  ): Lane;
1000
1002
 
1003
+ updateContainerSync(
1004
+ element: ReactNode,
1005
+ container: OpaqueRoot,
1006
+ parentComponent?: Component<any, any> | null,
1007
+ callback?: (() => void) | null,
1008
+ ): Lane;
1009
+
1001
1010
  batchedUpdates<A, R>(fn: (a: A) => R, a: A): R;
1002
1011
 
1003
1012
  deferredUpdates<A>(fn: () => A): A;
1004
1013
 
1005
1014
  discreteUpdates<A, B, C, D, R>(fn: (arg0: A, arg1: B, arg2: C, arg3: D) => R, a: A, b: B, c: C, d: D): R;
1006
1015
 
1007
- flushControlled(fn: () => any): void;
1008
-
1009
1016
  flushSync(): void;
1010
1017
  flushSync<R>(fn: () => R): R;
1011
1018
 
1019
+ flushSyncFromReconciler(): void;
1020
+ flushSyncFromReconciler<R>(fn: () => R): R;
1021
+
1022
+ flushSyncWork(): boolean;
1023
+
1012
1024
  isAlreadyRendering(): boolean;
1013
1025
 
1014
1026
  flushPassiveEffects(): boolean;
@@ -1017,16 +1029,10 @@ declare namespace ReactReconciler {
1017
1029
 
1018
1030
  attemptSynchronousHydration(fiber: Fiber): void;
1019
1031
 
1020
- attemptDiscreteHydration(fiber: Fiber): void;
1021
-
1022
1032
  attemptContinuousHydration(fiber: Fiber): void;
1023
1033
 
1024
1034
  attemptHydrationAtCurrentPriority(fiber: Fiber): void;
1025
1035
 
1026
- getCurrentUpdatePriority(): LanePriority;
1027
-
1028
- runWithPriority<T>(priority: LanePriority, fn: () => T): T;
1029
-
1030
1036
  findHostInstance(component: any): PublicInstance | null;
1031
1037
 
1032
1038
  findHostInstanceWithWarning(component: any, methodName: string): PublicInstance | null;
@@ -1039,6 +1045,17 @@ declare namespace ReactReconciler {
1039
1045
 
1040
1046
  injectIntoDevTools(devToolsConfig: DevToolsConfig<Instance, TextInstance, any>): boolean;
1041
1047
  }
1048
+
1049
+ function defaultOnUncaughtError(error: Error): void;
1050
+ function defaultOnCaughtError(error: Error): void;
1051
+ function defaultOnRecoverableError(error: Error): void;
1052
+
1053
+ function startHostTransition(
1054
+ formFiber: Fiber,
1055
+ pendingState: unknown,
1056
+ action: ((formData: FormData) => void) | null,
1057
+ formData: FormData,
1058
+ ): void;
1042
1059
  }
1043
1060
 
1044
1061
  export = ReactReconciler;
@@ -11,3 +11,4 @@ export type { Subscription, Dpr, Size, Viewport, RenderCallback, Frameloop, Perf
11
11
  export { context } from "./store.js";
12
12
  export type { ObjectMap, Camera, Disposable, Act } from "./utils.js";
13
13
  export { applyProps, getRootState, dispose, act, buildGraph } from "./utils.js";
14
+ export type { TrackedPromise } from "./promise.js";
@@ -0,0 +1,15 @@
1
+ export declare const isPromiseLike: (value: unknown) => value is PromiseLike<unknown>;
2
+ /** A promise tagged with its state, the protocol React's `use` reads */
3
+ export type TrackedPromise<T> = Promise<T> & ({
4
+ status: 'pending';
5
+ } | {
6
+ status: 'fulfilled';
7
+ value: T;
8
+ } | {
9
+ status: 'rejected';
10
+ reason: unknown;
11
+ });
12
+ export declare const fulfilled: <T>(value: T) => TrackedPromise<T>;
13
+ export declare const rejected: <T>(reason: unknown) => TrackedPromise<T>;
14
+ /** Tags `promise` in place once it settles. */
15
+ export declare const tracked: <T>(promise: PromiseLike<T>) => TrackedPromise<T>;
@@ -1,6 +1,7 @@
1
1
  import * as THREE from 'three';
2
2
  import * as React from 'react';
3
3
  import Reconciler from '../../react-reconciler/index.js';
4
+ import type { TrackedPromise } from "./promise.js";
4
5
  import { IsAllOptional } from "./utils.js";
5
6
  import type { RootStore } from "./store.js";
6
7
  import { type EventHandlers } from "./events.js";
@@ -8,6 +9,9 @@ import type { ThreeElement } from "../three-types.js";
8
9
  export interface Root {
9
10
  fiber: Reconciler.FiberRoot;
10
11
  store: RootStore;
12
+ unmountClaim: symbol | null;
13
+ /** Pending only while an async renderer is being created */
14
+ ready: TrackedPromise<unknown>;
11
15
  }
12
16
  export type AttachFnType<O = any> = (parent: any, self: O) => () => void;
13
17
  export type AttachType<O = any> = string | AttachFnType<O>;
@@ -5,6 +5,7 @@ import { ComputeFunction, EventManager } from "./events.js";
5
5
  import { Root } from "./reconciler.js";
6
6
  import { Dpr, Frameloop, Performance, Renderer, RootState, RootStore, Size } from "./store.js";
7
7
  import { type Properties, Camera } from "./utils.js";
8
+ import { TrackedPromise } from "./promise.js";
8
9
  interface OffscreenCanvas extends EventTarget {
9
10
  }
10
11
  export declare const _roots: Map<HTMLCanvasElement | OffscreenCanvas, Root>;
@@ -29,7 +30,7 @@ export interface RenderProps<TCanvas extends HTMLCanvasElement | OffscreenCanvas
29
30
  shadows?: boolean | 'basic' | 'percentage' | 'soft' | 'variance' | Partial<THREE.WebGLShadowMap>;
30
31
  /**
31
32
  * Disables three r139 color management.
32
- * @see https://threejs.org/docs/#manual/en/introduction/Color-management
33
+ * @see https://threejs.org/manual/#en/color-management
33
34
  */
34
35
  legacy?: boolean;
35
36
  /** Switch off automatic sRGB encoding and gamma correction */
@@ -64,7 +65,8 @@ export interface RenderProps<TCanvas extends HTMLCanvasElement | OffscreenCanvas
64
65
  onPointerMissed?: (event: MouseEvent) => void;
65
66
  }
66
67
  export interface ReconcilerRoot<TCanvas extends HTMLCanvasElement | OffscreenCanvas> {
67
- configure: (config?: RenderProps<TCanvas>) => Promise<ReconcilerRoot<TCanvas>>;
68
+ ready: TrackedPromise<unknown>;
69
+ configure: (config?: RenderProps<TCanvas>) => TrackedPromise<ReconcilerRoot<TCanvas>>;
68
70
  render: (element: React.ReactNode) => RootStore;
69
71
  unmount: () => void;
70
72
  }
@@ -39,6 +39,9 @@ export declare const isColorRepresentation: (value: unknown) => value is THREE.C
39
39
  */
40
40
  export declare const useIsomorphicLayoutEffect: typeof React.useLayoutEffect;
41
41
  export declare function useMutableCallback<T>(fn: T): React.RefObject<T>;
42
+ export declare const noop: () => void;
43
+ /** Waits for a promise in a null subtree, re-rendering the caller once it settles either way. */
44
+ export declare function useGate(): [gate: React.ReactNode, waitFor: (promise: PromiseLike<unknown>) => void];
42
45
  export type Bridge = React.FC<{
43
46
  children?: React.ReactNode;
44
47
  }>;
@@ -10,4 +10,4 @@ export interface CanvasProps extends Omit<RenderProps<HTMLCanvasElement>, 'size'
10
10
  * A native canvas which accepts threejs elements as children.
11
11
  * @see https://docs.pmnd.rs/react-three-fiber/api/canvas
12
12
  */
13
- export declare function Canvas(props: CanvasProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function Canvas(props: CanvasProps): React.JSX.Element;
@@ -12,7 +12,7 @@ export interface CanvasProps extends Omit<RenderProps<HTMLCanvasElement>, 'size'
12
12
  */
13
13
  resize?: ResizeOptions;
14
14
  /** The target where events are being subscribed to, default: the div that wraps canvas */
15
- eventSource?: HTMLElement | React.RefObject<HTMLElement>;
15
+ eventSource?: HTMLElement | React.RefObject<HTMLElement | null>;
16
16
  /** The event prefix that is cast into canvas pointer x/y events, default: "offset" */
17
17
  eventPrefix?: 'offset' | 'client' | 'page' | 'layer' | 'screen';
18
18
  }
@@ -20,4 +20,4 @@ export interface CanvasProps extends Omit<RenderProps<HTMLCanvasElement>, 'size'
20
20
  * A DOM canvas which accepts threejs elements as children.
21
21
  * @see https://docs.pmnd.rs/react-three-fiber/api/canvas
22
22
  */
23
- export declare function Canvas(props: CanvasProps): import("react/jsx-runtime").JSX.Element;
23
+ export declare function Canvas(props: CanvasProps): React.JSX.Element;