@react-three/fiber 9.0.0-alpha.3 → 9.0.0-alpha.5

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,11 @@
1
1
  # @react-three/fiber
2
2
 
3
+ ## 8.16.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 9c83502c: fix(Canvas): don't override camera frustum props
8
+
3
9
  ## 8.16.2
4
10
 
5
11
  ### Patch Changes
@@ -5,11 +5,15 @@ import { ObjectMap } from "./utils.js";
5
5
  import type { Instance } from "./reconciler.js";
6
6
  /**
7
7
  * Exposes an object's {@link Instance}.
8
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useInstanceHandle
8
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useinstancehandle
9
9
  *
10
10
  * **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.
11
11
  */
12
- export declare function useInstanceHandle<O>(ref: React.MutableRefObject<O>): React.MutableRefObject<Instance>;
12
+ export declare function useInstanceHandle<T>(ref: React.RefObject<T>): React.RefObject<Instance<T>>;
13
+ /**
14
+ * Returns the R3F Canvas' Zustand store. Useful for [transient updates](https://github.com/pmndrs/zustand#transient-updates-for-often-occurring-state-changes).
15
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usestore
16
+ */
13
17
  export declare function useStore(): RootStore;
14
18
  /**
15
19
  * Accesses R3F's internal state, containing renderer, canvas, scene, etc.
@@ -24,7 +28,7 @@ export declare function useThree<T = RootState>(selector?: (state: RootState) =>
24
28
  export declare function useFrame(callback: RenderCallback, renderPriority?: number): null;
25
29
  /**
26
30
  * Executes a callback in a given update stage.
27
- * Uses the stage instance to indetify which stage to target in the lifecycle.
31
+ * Uses the stage instance to identify which stage to target in the lifecycle.
28
32
  */
29
33
  export declare function useUpdate(callback: UpdateCallback, stage?: StageTypes): void;
30
34
  /**
@@ -6,16 +6,15 @@ import { Root } from "./reconciler.js";
6
6
  import { EventManager, ComputeFunction } from "./events.js";
7
7
  import { Camera } from "./utils.js";
8
8
  import { Stage } from "./stages.js";
9
- declare var OffscreenCanvas: any;
10
- type OffscreenCanvas = any;
11
- type Canvas = HTMLCanvasElement | OffscreenCanvas;
12
- export declare const _roots: Map<any, Root>;
13
- export type GLProps = Renderer | ((canvas: Canvas) => Renderer) | Partial<Properties<THREE.WebGLRenderer> | THREE.WebGLRendererParameters>;
9
+ interface OffscreenCanvas extends EventTarget {
10
+ }
11
+ export declare const _roots: Map<HTMLCanvasElement | OffscreenCanvas, Root>;
12
+ export type GLProps = Renderer | ((canvas: HTMLCanvasElement | OffscreenCanvas) => Renderer) | Partial<Properties<THREE.WebGLRenderer> | THREE.WebGLRendererParameters>;
14
13
  export type CameraProps = (Camera | Partial<ThreeElement<typeof THREE.Camera> & ThreeElement<typeof THREE.PerspectiveCamera> & ThreeElement<typeof THREE.OrthographicCamera>>) & {
15
14
  /** Flags the camera as manual, putting projection into your own hands */
16
15
  manual?: boolean;
17
16
  };
18
- export interface RenderProps<TCanvas extends Canvas> {
17
+ export interface RenderProps<TCanvas extends HTMLCanvasElement | OffscreenCanvas> {
19
18
  /** A threejs renderer instance or props that go into the default renderer */
20
19
  gl?: GLProps;
21
20
  /** Dimensions to fit the renderer to. Will measure canvas dimensions if omitted */
@@ -65,14 +64,14 @@ export interface RenderProps<TCanvas extends Canvas> {
65
64
  stages?: Stage[];
66
65
  render?: 'auto' | 'manual';
67
66
  }
68
- export interface ReconcilerRoot<TCanvas extends Canvas> {
67
+ export interface ReconcilerRoot<TCanvas extends HTMLCanvasElement | OffscreenCanvas> {
69
68
  configure: (config?: RenderProps<TCanvas>) => ReconcilerRoot<TCanvas>;
70
69
  render: (element: React.ReactNode) => RootStore;
71
70
  unmount: () => void;
72
71
  }
73
- export declare function createRoot<TCanvas extends Canvas>(canvas: TCanvas): ReconcilerRoot<TCanvas>;
74
- export declare function render<TCanvas extends Canvas>(children: React.ReactNode, canvas: TCanvas, config: RenderProps<TCanvas>): RootStore;
75
- export declare function unmountComponentAtNode<TCanvas extends Canvas>(canvas: TCanvas, callback?: (canvas: TCanvas) => void): void;
72
+ export declare function createRoot<TCanvas extends HTMLCanvasElement | OffscreenCanvas>(canvas: TCanvas): ReconcilerRoot<TCanvas>;
73
+ export declare function render<TCanvas extends HTMLCanvasElement | OffscreenCanvas>(children: React.ReactNode, canvas: TCanvas, config: RenderProps<TCanvas>): RootStore;
74
+ export declare function unmountComponentAtNode<TCanvas extends HTMLCanvasElement | OffscreenCanvas>(canvas: TCanvas, callback?: (canvas: TCanvas) => void): void;
76
75
  export type InjectState = Partial<Omit<RootState, 'events'> & {
77
76
  events?: {
78
77
  enabled?: boolean;
@@ -1,7 +1,8 @@
1
1
  /// <reference types="webxr" />
2
2
  import * as THREE from 'three';
3
3
  import * as React from 'react';
4
- import { type StoreApi, type UseBoundStore } from 'zustand';
4
+ import { type StoreApi } from 'zustand';
5
+ import { type UseBoundStoreWithEqualityFn } from 'zustand/traditional';
5
6
  import type { DomEvent, EventManager, PointerCaptureTarget, ThreeEvent } from "./events.js";
6
7
  import { type Camera } from "./utils.js";
7
8
  import type { FixedStage, Stage } from "./stages.js";
@@ -9,7 +10,7 @@ export interface Intersection extends THREE.Intersection {
9
10
  eventObject: THREE.Object3D;
10
11
  }
11
12
  export type Subscription = {
12
- ref: React.MutableRefObject<RenderCallback>;
13
+ ref: React.RefObject<RenderCallback>;
13
14
  priority: number;
14
15
  store: RootStore;
15
16
  };
@@ -67,7 +68,7 @@ export interface InternalState {
67
68
  capturedMap: Map<number, Map<THREE.Object3D, PointerCaptureTarget>>;
68
69
  initialClick: [x: number, y: number];
69
70
  initialHits: THREE.Object3D[];
70
- lastEvent: React.MutableRefObject<DomEvent | null>;
71
+ lastEvent: React.RefObject<DomEvent | null>;
71
72
  active: boolean;
72
73
  priority: number;
73
74
  frames: number;
@@ -77,7 +78,7 @@ export interface InternalState {
77
78
  render: 'auto' | 'manual';
78
79
  /** The max delta time between two frames. */
79
80
  maxDelta: number;
80
- subscribe: (callback: React.MutableRefObject<RenderCallback>, priority: number, store: RootStore) => () => void;
81
+ subscribe: (callback: React.RefObject<RenderCallback>, priority: number, store: RootStore) => () => void;
81
82
  }
82
83
  export interface XRManager {
83
84
  connect: () => void;
@@ -142,6 +143,6 @@ export interface RootState {
142
143
  /** Internals */
143
144
  internal: InternalState;
144
145
  }
145
- export type RootStore = UseBoundStore<StoreApi<RootState>>;
146
+ export type RootStore = UseBoundStoreWithEqualityFn<StoreApi<RootState>>;
146
147
  export declare const context: React.Context<RootStore>;
147
148
  export declare const createStore: (invalidate: (state?: RootState, frames?: number) => void, advance: (timestamp: number, runGlobalEffects?: boolean, state?: RootState, frame?: XRFrame) => void) => RootStore;
@@ -33,7 +33,7 @@ export type Camera = (THREE.OrthographicCamera | THREE.PerspectiveCamera) & {
33
33
  manual?: boolean;
34
34
  };
35
35
  export declare const isOrthographicCamera: (def: Camera) => def is THREE.OrthographicCamera;
36
- export declare const isRef: (obj: any) => obj is React.MutableRefObject<unknown>;
36
+ export declare const isRef: (obj: any) => obj is React.RefObject<unknown>;
37
37
  /**
38
38
  * An SSR-friendly useLayoutEffect.
39
39
  *
@@ -44,7 +44,7 @@ export declare const isRef: (obj: any) => obj is React.MutableRefObject<unknown>
44
44
  * @see https://github.com/facebook/react/issues/14927
45
45
  */
46
46
  export declare const useIsomorphicLayoutEffect: typeof React.useLayoutEffect;
47
- export declare function useMutableCallback<T>(fn: T): React.MutableRefObject<T>;
47
+ export declare function useMutableCallback<T>(fn: T): React.RefObject<T>;
48
48
  export type Bridge = React.FC<{
49
49
  children?: React.ReactNode;
50
50
  }>;
@@ -121,7 +121,7 @@ export declare function resolve(root: any, key: string): {
121
121
  export declare function attach(parent: Instance, child: Instance): void;
122
122
  export declare function detach(parent: Instance, child: Instance): void;
123
123
  export declare const RESERVED_PROPS: string[];
124
- export declare function diffProps<T = any>(instance: Instance<T>, newProps: Instance<T>['props'], resetRemoved?: boolean): Instance<T>['props'];
124
+ export declare function diffProps<T = any>(instance: Instance<T>, newProps: Instance<T>['props']): Instance<T>['props'];
125
125
  export declare function applyProps<T = any>(object: Instance<T>['object'], props: Instance<T>['props']): Instance<T>['object'];
126
126
  export declare function invalidateInstance(instance: Instance): void;
127
127
  export declare function updateCamera(camera: Camera, size: Size): void;
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { View, ViewProps, ViewStyle } from 'react-native';
3
3
  import { RenderProps } from "../core/index.js";
4
- export interface CanvasProps extends Omit<RenderProps<HTMLCanvasElement>, 'size' | 'dpr'>, ViewProps {
4
+ export interface CanvasProps extends Omit<RenderProps<HTMLCanvasElement>, 'size' | 'dpr'>, Omit<ViewProps, 'children'> {
5
5
  children: React.ReactNode;
6
6
  style?: ViewStyle;
7
7
  }
@@ -47,7 +47,7 @@ export interface ThreeElements extends ThreeElementsImpl {
47
47
  object: object;
48
48
  };
49
49
  }
50
- declare global {
50
+ declare module 'react' {
51
51
  namespace JSX {
52
52
  interface IntrinsicElements extends ThreeElements {
53
53
  }
@@ -11,7 +11,7 @@ export interface CanvasProps extends Omit<RenderProps<HTMLCanvasElement>, 'size'
11
11
  */
12
12
  resize?: ResizeOptions;
13
13
  /** The target where events are being subscribed to, default: the div that wraps canvas */
14
- eventSource?: HTMLElement | React.MutableRefObject<HTMLElement>;
14
+ eventSource?: HTMLElement | React.RefObject<HTMLElement>;
15
15
  /** The event prefix that is cast into canvas pointer x/y events, default: "offset" */
16
16
  eventPrefix?: 'offset' | 'client' | 'page' | 'layer' | 'screen';
17
17
  }
@@ -3,7 +3,7 @@
3
3
  var THREE = require('three');
4
4
  var React = require('react');
5
5
  var constants = require('react-reconciler/constants');
6
- var zustand = require('zustand');
6
+ var traditional = require('zustand/traditional');
7
7
  var itsFine = require('its-fine');
8
8
  var _extends = require('@babel/runtime/helpers/extends');
9
9
  var Reconciler = require('react-reconciler');
@@ -50,7 +50,7 @@ const extend = objects => {
50
50
  catalogue[Component] = objects;
51
51
 
52
52
  // Returns a component whose name will be inferred in devtools
53
- // @ts-ignore
53
+ // @ts-expect-error
54
54
  return /*#__PURE__*/React__namespace.forwardRef({
55
55
  [objects.name]: (props, ref) => /*#__PURE__*/React__namespace.createElement(Component, _extends({}, props, {
56
56
  ref: ref
@@ -60,7 +60,8 @@ const extend = objects => {
60
60
  return void Object.assign(catalogue, objects);
61
61
  }
62
62
  };
63
- function createInstance(type, props, root) {
63
+ function createInstance(type, props, root, flushPrimitive = true) {
64
+ var _props$object;
64
65
  // Get target from catalogue
65
66
  const name = `${type[0].toUpperCase()}${type.slice(1)}`;
66
67
  const target = catalogue[name];
@@ -74,10 +75,37 @@ function createInstance(type, props, root) {
74
75
  // Throw if an object or literal was passed for args
75
76
  if (props.args !== undefined && !Array.isArray(props.args)) throw new Error('R3F: The args prop must be an array!');
76
77
 
78
+ // Regenerate the R3F instance for primitives to simulate a new object
79
+ if (flushPrimitive && type === 'primitive' && (_props$object = props.object) != null && _props$object.__r3f) delete props.object.__r3f;
80
+
77
81
  // Create instance
78
82
  const instance = prepare(props.object, root, type, props);
79
83
  return instance;
80
84
  }
85
+ function hideInstance(instance) {
86
+ if (!instance.isHidden) {
87
+ var _instance$parent;
88
+ if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
89
+ detach(instance.parent, instance);
90
+ } else if (isObject3D(instance.object)) {
91
+ instance.object.visible = false;
92
+ }
93
+ instance.isHidden = true;
94
+ invalidateInstance(instance);
95
+ }
96
+ }
97
+ function unhideInstance(instance) {
98
+ if (instance.isHidden) {
99
+ var _instance$parent2;
100
+ if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
101
+ attach(instance.parent, instance);
102
+ } else if (isObject3D(instance.object) && instance.props.visible !== false) {
103
+ instance.object.visible = true;
104
+ }
105
+ instance.isHidden = false;
106
+ invalidateInstance(instance);
107
+ }
108
+ }
81
109
 
82
110
  // https://github.com/facebook/react/issues/20271
83
111
  // This will make sure events and attach are only handled once when trees are complete
@@ -213,8 +241,19 @@ function setFiberInstance(fiber, instance) {
213
241
  }
214
242
  }
215
243
  function switchInstance(oldInstance, type, props, fiber) {
244
+ // If the old instance is hidden, we need to unhide it.
245
+ // React assumes it can discard instances since they're pure for DOM.
246
+ // This isn't true for us since our lifetimes are impure and longliving.
247
+ // So, we manually check if an instance was hidden and unhide it.
248
+ if (oldInstance.isHidden) unhideInstance(oldInstance);
249
+
216
250
  // Create a new instance
217
- const newInstance = createInstance(type, props, oldInstance.root);
251
+ const newInstance = createInstance(type, props, oldInstance.root, false);
252
+
253
+ // Update attach props for primitives since we don't flush them
254
+ if (type === 'primitive') {
255
+ newInstance.props.attach = props.attach;
256
+ }
218
257
 
219
258
  // Move children to new instance
220
259
  for (const child of oldInstance.children) {
@@ -256,38 +295,15 @@ function switchInstance(oldInstance, type, props, fiber) {
256
295
  const handleTextInstance = () => console.warn('R3F: Text is not allowed in JSX! This could be stray whitespace or characters.');
257
296
  const NO_CONTEXT = {};
258
297
  let currentUpdatePriority = constants.NoEventPriority;
259
-
260
- // Effectively removed to diff in commit phase
261
- // https://github.com/facebook/react/pull/27409
262
- function prepareUpdate(instance, _type, oldProps, newProps) {
263
- var _newProps$args, _oldProps$args, _newProps$args2;
264
- // Reconstruct primitives if object prop changes
265
- if (instance.type === 'primitive' && oldProps.object !== newProps.object) return [true];
266
-
267
- // Throw if an object or literal was passed for args
268
- if (newProps.args !== undefined && !Array.isArray(newProps.args)) throw new Error('R3F: The args prop must be an array!');
269
-
270
- // Reconstruct instance if args change
271
- if (((_newProps$args = newProps.args) == null ? void 0 : _newProps$args.length) !== ((_oldProps$args = oldProps.args) == null ? void 0 : _oldProps$args.length)) return [true];
272
- if ((_newProps$args2 = newProps.args) != null && _newProps$args2.some((value, index) => {
273
- var _oldProps$args2;
274
- return value !== ((_oldProps$args2 = oldProps.args) == null ? void 0 : _oldProps$args2[index]);
275
- })) return [true];
276
-
277
- // Create a diff-set, flag if there are any changes
278
- const changedProps = diffProps(instance, newProps, true);
279
- if (Object.keys(changedProps).length) return [false, changedProps];
280
-
281
- // Otherwise do not touch the instance
282
- return null;
283
- }
284
298
  const reconciler = Reconciler__default["default"]({
285
299
  isPrimaryRenderer: false,
286
300
  warnsIfNotActing: false,
287
301
  supportsMutation: true,
288
302
  supportsPersistence: false,
289
303
  supportsHydration: false,
290
- createInstance,
304
+ createInstance(type, props, root) {
305
+ return createInstance(type, props, root);
306
+ },
291
307
  removeChild,
292
308
  appendChild,
293
309
  appendInitialChild: appendChild,
@@ -309,18 +325,32 @@ const reconciler = Reconciler__default["default"]({
309
325
  },
310
326
  getRootHostContext: () => NO_CONTEXT,
311
327
  getChildHostContext: () => NO_CONTEXT,
312
- // @ts-ignore prepareUpdate and updatePayload removed with React 19
328
+ // @ts-expect-error prepareUpdate and updatePayload removed with React 19
313
329
  commitUpdate(instance, type, oldProps, newProps, fiber) {
314
- const diff = prepareUpdate(instance, type, oldProps, newProps);
315
- if (diff === null) return;
316
- const [reconstruct, changedProps] = diff;
330
+ var _newProps$args, _oldProps$args, _newProps$args2;
331
+ let reconstruct = false;
332
+
333
+ // Reconstruct primitives if object prop changes
334
+ if (instance.type === 'primitive' && oldProps.object !== newProps.object) reconstruct = true;
335
+ // Reconstruct instance if args was changed to an invalid value
336
+ else if (newProps.args !== undefined && !Array.isArray(newProps.args)) reconstruct = true;
337
+ // Reconstruct instance if args were added or removed
338
+ else if (((_newProps$args = newProps.args) == null ? void 0 : _newProps$args.length) !== ((_oldProps$args = oldProps.args) == null ? void 0 : _oldProps$args.length)) reconstruct = true;
339
+ // Reconstruct instance if args were changed
340
+ else if ((_newProps$args2 = newProps.args) != null && _newProps$args2.some((value, index) => {
341
+ var _oldProps$args2;
342
+ return value !== ((_oldProps$args2 = oldProps.args) == null ? void 0 : _oldProps$args2[index]);
343
+ })) reconstruct = true;
317
344
 
318
345
  // Reconstruct when args or <primitive object={...} have changes
319
346
  if (reconstruct) return switchInstance(instance, type, newProps, fiber);
320
347
 
321
- // Otherwise just overwrite props
322
- Object.assign(instance.props, changedProps);
323
- applyProps(instance.object, changedProps);
348
+ // Create a diff-set, flag if there are any changes
349
+ const changedProps = diffProps(instance, newProps);
350
+ if (Object.keys(changedProps).length) {
351
+ Object.assign(instance.props, changedProps);
352
+ applyProps(instance.object, changedProps);
353
+ }
324
354
  },
325
355
  finalizeInitialChildren: () => false,
326
356
  commitMount() {},
@@ -330,28 +360,8 @@ const reconciler = Reconciler__default["default"]({
330
360
  resetAfterCommit: () => {},
331
361
  shouldSetTextContent: () => false,
332
362
  clearContainer: () => false,
333
- hideInstance(instance) {
334
- var _instance$parent;
335
- if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
336
- detach(instance.parent, instance);
337
- } else if (isObject3D(instance.object)) {
338
- instance.object.visible = false;
339
- }
340
- instance.isHidden = true;
341
- invalidateInstance(instance);
342
- },
343
- unhideInstance(instance) {
344
- if (instance.isHidden) {
345
- var _instance$parent2;
346
- if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
347
- attach(instance.parent, instance);
348
- } else if (isObject3D(instance.object) && instance.props.visible !== false) {
349
- instance.object.visible = true;
350
- }
351
- }
352
- instance.isHidden = false;
353
- invalidateInstance(instance);
354
- },
363
+ hideInstance,
364
+ unhideInstance,
355
365
  createTextInstance: handleTextInstance,
356
366
  hideTextInstance: handleTextInstance,
357
367
  unhideTextInstance: handleTextInstance,
@@ -362,7 +372,6 @@ const reconciler = Reconciler__default["default"]({
362
372
  beforeActiveInstanceBlur() {},
363
373
  afterActiveInstanceBlur() {},
364
374
  detachDeletedInstance() {},
365
- // @ts-ignore untyped react-experimental options inspired by react-art
366
375
  // TODO: add shell types for these and upstream to DefinitelyTyped
367
376
  // https://github.com/facebook/react/blob/main/packages/react-art/src/ReactFiberConfigART.js
368
377
  shouldAttemptEagerTransition() {
@@ -417,8 +426,7 @@ var _window$document, _window$navigator;
417
426
  */
418
427
  function findInitialRoot(instance) {
419
428
  let root = instance.root;
420
- // TODO: this needs testing https://github.com/pmndrs/react-three-fiber/commit/a4a31ed93c48d1e6dac91329bb5f2ca6a25e5f9c
421
- // while (root.getState().previousRoot) root = root.getState().previousRoot!
429
+ while (root.getState().previousRoot) root = root.getState().previousRoot;
422
430
  return root;
423
431
  }
424
432
 
@@ -688,7 +696,7 @@ const RESERVED_PROPS = [...REACT_INTERNAL_PROPS,
688
696
  const MEMOIZED_PROTOTYPES = new Map();
689
697
 
690
698
  // This function prepares a set of changes to be applied to the instance
691
- function diffProps(instance, newProps, resetRemoved = false) {
699
+ function diffProps(instance, newProps) {
692
700
  const changedProps = {};
693
701
 
694
702
  // Sort through props
@@ -708,31 +716,29 @@ function diffProps(instance, newProps, resetRemoved = false) {
708
716
  }
709
717
 
710
718
  // Reset removed props for HMR
711
- if (resetRemoved) {
712
- for (const prop in instance.props) {
713
- if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
714
- const {
715
- root,
716
- key
717
- } = resolve(instance.object, prop);
718
-
719
- // https://github.com/mrdoob/three.js/issues/21209
720
- // HMR/fast-refresh relies on the ability to cancel out props, but threejs
721
- // has no means to do this. Hence we curate a small collection of value-classes
722
- // with their respective constructor/set arguments
723
- // For removed props, try to set default values, if possible
724
- if (root.constructor && root.constructor.length === 0) {
725
- // create a blank slate of the instance and copy the particular parameter.
726
- let ctor = MEMOIZED_PROTOTYPES.get(root.constructor);
727
- if (!ctor) {
728
- ctor = new root.constructor();
729
- MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
730
- }
731
- changedProps[key] = ctor[key];
732
- } else {
733
- // instance does not have constructor, just set it to 0
734
- changedProps[key] = 0;
719
+ for (const prop in instance.props) {
720
+ if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
721
+ const {
722
+ root,
723
+ key
724
+ } = resolve(instance.object, prop);
725
+
726
+ // https://github.com/mrdoob/three.js/issues/21209
727
+ // HMR/fast-refresh relies on the ability to cancel out props, but threejs
728
+ // has no means to do this. Hence we curate a small collection of value-classes
729
+ // with their respective constructor/set arguments
730
+ // For removed props, try to set default values, if possible
731
+ if (root.constructor && root.constructor.length === 0) {
732
+ // create a blank slate of the instance and copy the particular parameter.
733
+ let ctor = MEMOIZED_PROTOTYPES.get(root.constructor);
734
+ if (!ctor) {
735
+ ctor = new root.constructor();
736
+ MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
735
737
  }
738
+ changedProps[key] = ctor[key];
739
+ } else {
740
+ // instance does not have constructor, just set it to 0
741
+ changedProps[key] = 0;
736
742
  }
737
743
  }
738
744
  return changedProps;
@@ -1282,7 +1288,7 @@ function createEvents(store) {
1282
1288
  const isRenderer = def => !!(def != null && def.render);
1283
1289
  const context = /*#__PURE__*/React__namespace.createContext(null);
1284
1290
  const createStore = (invalidate, advance) => {
1285
- const rootStore = zustand.create((set, get) => {
1291
+ const rootStore = traditional.createWithEqualityFn((set, get) => {
1286
1292
  const position = new THREE__namespace.Vector3();
1287
1293
  const defaultTarget = new THREE__namespace.Vector3();
1288
1294
  const tempTarget = new THREE__namespace.Vector3();
@@ -1684,15 +1690,20 @@ const Lifecycle = [Early, Fixed, Update, Late, Render, After];
1684
1690
 
1685
1691
  /**
1686
1692
  * Exposes an object's {@link Instance}.
1687
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useInstanceHandle
1693
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useinstancehandle
1688
1694
  *
1689
1695
  * **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.
1690
1696
  */
1691
1697
  function useInstanceHandle(ref) {
1692
1698
  const instance = React__namespace.useRef(null);
1693
- useIsomorphicLayoutEffect(() => void (instance.current = ref.current.__r3f), [ref]);
1699
+ React__namespace.useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
1694
1700
  return instance;
1695
1701
  }
1702
+
1703
+ /**
1704
+ * Returns the R3F Canvas' Zustand store. Useful for [transient updates](https://github.com/pmndrs/zustand#transient-updates-for-often-occurring-state-changes).
1705
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usestore
1706
+ */
1696
1707
  function useStore() {
1697
1708
  const store = React__namespace.useContext(context);
1698
1709
  if (!store) throw new Error('R3F: Hooks can only be used within the Canvas component!');
@@ -1704,7 +1715,6 @@ function useStore() {
1704
1715
  * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree
1705
1716
  */
1706
1717
  function useThree(selector = state => state, equalityFn) {
1707
- // TODO: fix this type
1708
1718
  return useStore()(selector, equalityFn);
1709
1719
  }
1710
1720
 
@@ -1725,7 +1735,7 @@ function useFrame(callback, renderPriority = 0) {
1725
1735
 
1726
1736
  /**
1727
1737
  * Executes a callback in a given update stage.
1728
- * Uses the stage instance to indetify which stage to target in the lifecycle.
1738
+ * Uses the stage instance to identify which stage to target in the lifecycle.
1729
1739
  */
1730
1740
  function useUpdate(callback, stage = Stages.Update) {
1731
1741
  const store = useStore();
@@ -1772,6 +1782,7 @@ function loadingFn(extensions, onProgress) {
1772
1782
  return Promise.all(input.map(input => new Promise((res, reject) => loader.load(input, data => res(isObject3D(data == null ? void 0 : data.scene) ? Object.assign(data, buildGraph(data.scene)) : data), onProgress, error => reject(new Error(`Could not load ${input}: ${error == null ? void 0 : error.message}`))))));
1773
1783
  };
1774
1784
  }
1785
+
1775
1786
  /**
1776
1787
  * Synchronously loads and caches assets with a three loader.
1777
1788
  *
@@ -1804,7 +1815,8 @@ useLoader.clear = function (loader, input) {
1804
1815
  return suspendReact.clear([loader, ...keys]);
1805
1816
  };
1806
1817
 
1807
- // TODO: fix type resolve
1818
+ // Shim for OffscreenCanvas since it was removed from DOM types
1819
+ // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/54988
1808
1820
 
1809
1821
  const _roots = new Map();
1810
1822
  const shallowLoose = {
@@ -1994,7 +2006,15 @@ function createRoot(canvas) {
1994
2006
  const camera = isCamera ? cameraOptions : orthographic ? new THREE__namespace.OrthographicCamera(0, 0, 0, 0, 0.1, 1000) : new THREE__namespace.PerspectiveCamera(75, 0, 0.1, 1000);
1995
2007
  if (!isCamera) {
1996
2008
  camera.position.z = 5;
1997
- if (cameraOptions) applyProps(camera, cameraOptions);
2009
+ if (cameraOptions) {
2010
+ applyProps(camera, cameraOptions);
2011
+ // Preserve user-defined frustum if possible
2012
+ // https://github.com/pmndrs/react-three-fiber/issues/3160
2013
+ if ('aspect' in cameraOptions || 'left' in cameraOptions || 'right' in cameraOptions || 'bottom' in cameraOptions || 'top' in cameraOptions) {
2014
+ camera.manual = true;
2015
+ camera.updateProjectionMatrix();
2016
+ }
2017
+ }
1998
2018
  // Always look at center by default
1999
2019
  if (!state.camera && !(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
2000
2020
  }
@@ -2291,7 +2311,7 @@ function Portal({
2291
2311
  };
2292
2312
  });
2293
2313
  const usePortalStore = React__namespace.useMemo(() => {
2294
- const store = zustand.create((set, get) => ({
2314
+ const store = traditional.createWithEqualityFn((set, get) => ({
2295
2315
  ...rest,
2296
2316
  set,
2297
2317
  get