@react-three/fiber 9.8.0 → 9.8.1

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,29 @@
1
1
  # @react-three/fiber
2
2
 
3
+ ## 9.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 2bcc153b52213535e2149d70f6c01d7dcaee58a2: fix: keep a Canvas root alive while `<Activity>` hides it.
8
+
9
+ React destroys a hidden `<Activity>` tree's effects but keeps its DOM. The Canvas unmounted its root in that effect cleanup, which lost the WebGL context and disposed the scene. When the tree was shown again, the Canvas still held the unmounted root and stayed blank.
10
+
11
+ Canvas now keeps its renderer and scene state while hidden. A Canvas removed while hidden still releases its renderer.
12
+
13
+ - 138bfb0055a2534103686454c5e1f22bcdbf4537: Canvas and `root.configure()` now apply only the configuration props that changed. Runtime changes made through setters such as `setFrameloop`, or directly to `gl.shadowMap`, last through rerenders and resizes until their prop changes. The pixel ratio still follows `dpr` and the device pixel ratio on every render. Inline option objects compare by value. Changing `flat` or `linear` after creation now updates the renderer, unless `gl` sets `toneMapping` or `outputColorSpace` explicitly.
14
+
15
+ Configuration applies in call order, including calls made from a renderer factory or a store subscriber while another configuration is running.
16
+
17
+ - e0e9c2dbb5b9f1567f43ffe97205d29a4094028a: Update its-fine to 2.1.1 and bridge ancestor Activity visibility into the Canvas scene on React 19.2 and later. Hiding an Activity disconnects scene effects and useFrame subscriptions; revealing it reconnects them without resetting scene state. Keep the existing context and StrictMode bridges, and fall back to their previous behavior on React 19.0 and 19.1.
18
+ - 9ac75626b2191c65698d3bfb27c2e43d6e3c8891: fix: dispose the renderer R3F creates when its root unmounts.
19
+
20
+ Teardown only called `forceContextLoss()`, never `dispose()`. A `WebGPURenderer` built in a `gl` factory, which has no `forceContextLoss`, was never released at all: its GPU device and every allocation on it outlived the canvas. A `WebGLRenderer` lost its context while three's listener was still attached, which logged `THREE.WebGLRenderer: Context Lost.` on every unmount.
21
+
22
+ A renderer R3F builds, from its defaults, a props object or a factory, is now disposed, and then its context is lost. A renderer passed in as an instance keeps its previous teardown. Each teardown step now runs on its own, so one that throws no longer skips the rest.
23
+
24
+ - 8965ba4f05eab91c605fe0dfaaa58453c1ede7c9: Preserve an object's visibility across Activity and Suspense hiding. Visibility prop changes made while hidden take effect when the object is shown.
25
+ - 9ac75626b2191c65698d3bfb27c2e43d6e3c8891: Model root teardown with explicit open, closing, disposing, and disposed states. Allow pending unmounts to be cancelled, wait for React cleanups and queued configuration before disposing, and prevent disposed root handles from configuring or unmounting replacement roots. Complete teardown callbacks after asynchronous renderer disposal settles, and release the context even if renderer disposal fails.
26
+
3
27
  ## 9.8.0
4
28
 
5
29
  ### Minor Changes
@@ -1,14 +1,10 @@
1
- import * as React from 'react';
2
1
  import * as THREE from 'three';
3
2
  import type { ThreeElement } from "../three-types.js";
4
- import { ComputeFunction, EventManager } from "./events.js";
5
- import { Root } from "./reconciler.js";
6
- import { Dpr, Frameloop, Performance, Renderer, RootState, RootStore, Size } from "./store.js";
7
- import { type Properties, Camera } from "./utils.js";
8
- import { TrackedPromise } from "./promise.js";
3
+ import type { EventManager } from "./events.js";
4
+ import { type Dpr, type Frameloop, type Performance, type Renderer, type RootState, type RootStore, type Size } from "./store.js";
5
+ import { type Camera, type Properties } from "./utils.js";
9
6
  interface OffscreenCanvas extends EventTarget {
10
7
  }
11
- export declare const _roots: Map<HTMLCanvasElement | OffscreenCanvas, Root>;
12
8
  export type DefaultGLProps = Omit<THREE.WebGLRendererParameters, 'canvas'> & {
13
9
  canvas: HTMLCanvasElement | OffscreenCanvas;
14
10
  };
@@ -64,28 +60,25 @@ export interface RenderProps<TCanvas extends HTMLCanvasElement | OffscreenCanvas
64
60
  /** Response for pointer clicks that have missed any target */
65
61
  onPointerMissed?: (event: MouseEvent) => void;
66
62
  }
67
- export interface ReconcilerRoot<TCanvas extends HTMLCanvasElement | OffscreenCanvas> {
68
- ready: TrackedPromise<unknown>;
69
- configure: (config?: RenderProps<TCanvas>) => TrackedPromise<ReconcilerRoot<TCanvas>>;
70
- render: (element: React.ReactNode) => RootStore;
71
- unmount: () => void;
63
+ type Canvas = DefaultGLProps['canvas'];
64
+ export type Configuration = RenderProps<Canvas>;
65
+ /** What a root has applied, so its next configuration applies only what changed */
66
+ export interface AppliedConfiguration {
67
+ /** The last configuration applied in full */
68
+ previous?: Configuration;
69
+ /** The camera prop that set the current camera */
70
+ camera?: Configuration['camera'];
72
71
  }
73
- export declare function createRoot<TCanvas extends HTMLCanvasElement | OffscreenCanvas>(canvas: TCanvas): ReconcilerRoot<TCanvas>;
74
- export declare function unmountComponentAtNode<TCanvas extends HTMLCanvasElement | OffscreenCanvas>(canvas: TCanvas, callback?: (canvas: TCanvas) => void): void;
75
- export type InjectState = Partial<Omit<RootState, 'events'> & {
76
- events?: {
77
- enabled?: boolean;
78
- priority?: number;
79
- compute?: ComputeFunction;
80
- connected?: any;
81
- };
82
- }>;
83
- export declare function createPortal(children: React.ReactNode, container: THREE.Object3D, state?: InjectState): React.JSX.Element;
72
+ /** The renderer to configure, or a promise for one from an async factory */
73
+ export declare function createRenderer(canvas: Canvas, gl?: GLProps): Renderer | PromiseLike<Renderer>;
84
74
  /**
85
- * Force React to flush any updates inside the provided callback synchronously and immediately.
86
- * All the same caveats documented for react-dom's `flushSync` apply here (see https://react.dev/reference/react-dom/flushSync).
87
- * Nevertheless, sometimes one needs to render synchronously, for example to keep DOM and 3D changes in lock-step without
88
- * having to revert to a non-React solution. Note: this will only flush updates within the `Canvas` root.
75
+ * Applies the inputs that changed since the last configuration applied in full, to a root whose
76
+ * renderer is installed. A runtime change, such as setFrameloop or an edit to gl.shadowMap, lasts
77
+ * until its input changes. The pixel ratio instead follows the device on every call. Objects and
78
+ * arrays compare shallowly, so an equal inline value is unchanged.
89
79
  */
90
- export declare function flushSync<R>(fn: () => R): R;
80
+ export declare function applyRootConfiguration({ store, configuration }: {
81
+ store: RootStore;
82
+ configuration: AppliedConfiguration;
83
+ }, canvas: Canvas, props: Configuration): void;
91
84
  export {};
@@ -4,9 +4,12 @@ export * from "./hooks.js";
4
4
  export type { GlobalRenderCallback, GlobalEffectType } from "./loop.js";
5
5
  export { flushGlobalEffects, addEffect, addAfterEffect, addTail, invalidate, advance } from "./loop.js";
6
6
  export type { AttachFnType, AttachType, ConstructorRepresentation, Catalogue, Args, InstanceProps, Instance, } from "./reconciler.js";
7
- export { extend, reconciler } from "./reconciler.js";
8
- export type { ReconcilerRoot, GLProps, CameraProps, RenderProps, InjectState } from "./renderer.js";
9
- export { _roots, createRoot, unmountComponentAtNode, createPortal, flushSync } from "./renderer.js";
7
+ export { extend, reconciler, flushSync } from "./reconciler.js";
8
+ export type { GLProps, CameraProps, RenderProps } from "./configuration.js";
9
+ export type { ReconcilerRoot } from "./root.js";
10
+ export { _roots, createRoot, unmountComponentAtNode } from "./root.js";
11
+ export type { InjectState } from "./portal.js";
12
+ export { createPortal } from "./portal.js";
10
13
  export type { Subscription, Dpr, Size, Viewport, RenderCallback, Frameloop, Performance, Renderer, XRManager, RootState, RootStore, } from "./store.js";
11
14
  export { context } from "./store.js";
12
15
  export type { ObjectMap, Camera, Disposable, Act } from "./utils.js";
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ import * as THREE from 'three';
3
+ import type { ComputeFunction } from "./events.js";
4
+ import { type RootState } from "./store.js";
5
+ export type InjectState = Partial<Omit<RootState, 'events'> & {
6
+ events?: {
7
+ enabled?: boolean;
8
+ priority?: number;
9
+ compute?: ComputeFunction;
10
+ connected?: any;
11
+ };
12
+ }>;
13
+ export declare function createPortal(children: React.ReactNode, container: THREE.Object3D, state?: InjectState): React.JSX.Element;
@@ -1,4 +1,4 @@
1
- export declare const isPromiseLike: (value: unknown) => value is PromiseLike<unknown>;
1
+ export declare const isPromiseLike: <T>(value: T | PromiseLike<T>) => value is PromiseLike<T>;
2
2
  /** A promise tagged with its state, the protocol React's `use` reads */
3
3
  export type TrackedPromise<T> = Promise<T> & ({
4
4
  status: 'pending';
@@ -11,5 +11,9 @@ export type TrackedPromise<T> = Promise<T> & ({
11
11
  });
12
12
  export declare const fulfilled: <T>(value: T) => TrackedPromise<T>;
13
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>;
14
+ /** A pending promise, settled once from outside. Its `status` updates the moment it settles */
15
+ export declare const deferred: <T>() => {
16
+ promise: TrackedPromise<T>;
17
+ resolve: (_value: T) => void;
18
+ reject: (_reason: unknown) => void;
19
+ };
@@ -1,18 +1,10 @@
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";
5
4
  import { IsAllOptional } from "./utils.js";
6
5
  import type { RootStore } from "./store.js";
7
6
  import { type EventHandlers } from "./events.js";
8
7
  import type { ThreeElement } from "../three-types.js";
9
- export interface Root {
10
- fiber: Reconciler.FiberRoot;
11
- store: RootStore;
12
- unmountClaim: symbol | null;
13
- /** Pending only while an async renderer is being created */
14
- ready: TrackedPromise<unknown>;
15
- }
16
8
  export type AttachFnType<O = any> = (parent: any, self: O) => () => void;
17
9
  export type AttachType<O = any> = string | AttachFnType<O>;
18
10
  export type ConstructorRepresentation<T = any> = new (...args: any[]) => T;
@@ -46,9 +38,17 @@ export interface Instance<O = any> {
46
38
  handlers: Partial<EventHandlers>;
47
39
  attach?: AttachType<O>;
48
40
  previousAttach?: any;
41
+ previousVisible: boolean | undefined;
49
42
  isHidden: boolean;
50
43
  }
51
44
  export declare function extend<T extends ConstructorRepresentation>(objects: T): React.ExoticComponent<ThreeElement<T>>;
52
45
  export declare function extend<T extends Catalogue>(objects: T): void;
53
46
  export declare const reconciler: Reconciler.Reconciler<RootStore, Instance<any>, void, Instance<any>, never, any>;
47
+ /**
48
+ * Force React to flush any updates inside the provided callback synchronously and immediately.
49
+ * All the same caveats documented for react-dom's `flushSync` apply here (see https://react.dev/reference/react-dom/flushSync).
50
+ * Nevertheless, sometimes one needs to render synchronously, for example to keep DOM and 3D changes in lock-step without
51
+ * having to revert to a non-React solution. Note: this will only flush updates within the `Canvas` root.
52
+ */
53
+ export declare function flushSync<R>(fn: () => R): R;
54
54
  export {};
@@ -0,0 +1,35 @@
1
+ import * as React from 'react';
2
+ import type Reconciler from '../../react-reconciler/index.js';
3
+ import { type AppliedConfiguration, type RenderProps } from "./configuration.js";
4
+ import { type TrackedPromise } from "./promise.js";
5
+ import { type RootStore } from "./store.js";
6
+ interface OffscreenCanvas extends EventTarget {
7
+ }
8
+ export interface Root {
9
+ fiber: Reconciler.FiberRoot;
10
+ store: RootStore;
11
+ state: RootStateMachine;
12
+ /** Settles once all accepted configuration has been applied */
13
+ ready: TrackedPromise<unknown>;
14
+ configuration: AppliedConfiguration;
15
+ /** Whether R3F built `gl` (from defaults, props or a factory) and so disposes it on unmount */
16
+ ownsRenderer: boolean;
17
+ }
18
+ export declare const _roots: Map<HTMLCanvasElement | OffscreenCanvas, Root>;
19
+ export type RootStateMachine = {
20
+ status: 'open';
21
+ } | {
22
+ status: 'closing';
23
+ token: symbol;
24
+ } | {
25
+ status: 'disposed';
26
+ };
27
+ export interface ReconcilerRoot<TCanvas extends HTMLCanvasElement | OffscreenCanvas> {
28
+ ready: TrackedPromise<unknown>;
29
+ configure: (config?: RenderProps<TCanvas>) => TrackedPromise<ReconcilerRoot<TCanvas>>;
30
+ render: (element: React.ReactNode) => RootStore;
31
+ unmount: () => void;
32
+ }
33
+ export declare function createRoot<TCanvas extends HTMLCanvasElement | OffscreenCanvas>(canvas: TCanvas): ReconcilerRoot<TCanvas>;
34
+ export declare function unmountComponentAtNode<TCanvas extends HTMLCanvasElement | OffscreenCanvas>(canvas: TCanvas, callback?: (canvas: TCanvas) => void): void;
35
+ export {};
@@ -46,7 +46,7 @@ export type Bridge = React.FC<{
46
46
  children?: React.ReactNode;
47
47
  }>;
48
48
  /**
49
- * Bridges renderer Context and StrictMode from a primary renderer.
49
+ * Bridges Context, StrictMode, and Activity visibility from a primary renderer.
50
50
  */
51
51
  export declare function useBridge(): Bridge;
52
52
  export type SetBlock = false | Promise<null> | null;