@react-three/fiber 9.0.0-rc.1 → 9.0.0-rc.10

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.
@@ -4,11 +4,11 @@ var THREE = require('three');
4
4
  var React = require('react');
5
5
  var constants = require('react-reconciler/constants');
6
6
  var traditional = require('zustand/traditional');
7
+ var Reconciler = require('react-reconciler');
8
+ var scheduler = require('scheduler');
7
9
  var suspendReact = require('suspend-react');
8
10
  var jsxRuntime = require('react/jsx-runtime');
9
11
  var itsFine = require('its-fine');
10
- var Reconciler = require('react-reconciler');
11
- var scheduler = require('scheduler');
12
12
 
13
13
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
14
14
 
@@ -38,851 +38,414 @@ var threeTypes = /*#__PURE__*/Object.freeze({
38
38
  __proto__: null
39
39
  });
40
40
 
41
- // TODO: upstream to DefinitelyTyped for React 19
42
- // https://github.com/facebook/react/issues/28956
43
-
44
- const createReconciler = Reconciler__default["default"];
45
-
46
- // TODO: handle constructor overloads
47
- // https://github.com/pmndrs/react-three-fiber/pull/2931
48
- // https://github.com/microsoft/TypeScript/issues/37079
49
-
50
- const catalogue = {};
51
- let i = 0;
52
- const isConstructor$1 = object => typeof object === 'function';
53
- function extend(objects) {
54
- if (isConstructor$1(objects)) {
55
- const Component = `${i++}`;
56
- catalogue[Component] = objects;
57
- return Component;
58
- } else {
59
- Object.assign(catalogue, objects);
60
- }
41
+ /**
42
+ * Returns the instance's initial (outmost) root.
43
+ */
44
+ function findInitialRoot(instance) {
45
+ let root = instance.root;
46
+ while (root.getState().previousRoot) root = root.getState().previousRoot;
47
+ return root;
61
48
  }
62
- function validateInstance(type, props) {
63
- // Get target from catalogue
64
- const name = `${type[0].toUpperCase()}${type.slice(1)}`;
65
- const target = catalogue[name];
66
-
67
- // Validate element target
68
- if (type !== 'primitive' && !target) throw new Error(`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`);
49
+ /**
50
+ * Safely flush async effects when testing, simulating a legacy root.
51
+ */
52
+ const act = React__namespace.act;
53
+ const isOrthographicCamera = def => def && def.isOrthographicCamera;
54
+ const isRef = obj => obj && obj.hasOwnProperty('current');
55
+ const isColorRepresentation = value => value != null && (typeof value === 'string' || typeof value === 'number' || value.isColor);
69
56
 
70
- // Validate primitives
71
- if (type === 'primitive' && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
57
+ /**
58
+ * An SSR-friendly useLayoutEffect.
59
+ *
60
+ * React currently throws a warning when using useLayoutEffect on the server.
61
+ * To get around it, we can conditionally useEffect on the server (no-op) and
62
+ * useLayoutEffect elsewhere.
63
+ *
64
+ * @see https://github.com/facebook/react/issues/14927
65
+ */
66
+ const useIsomorphicLayoutEffect = /* @__PURE__ */((_window$document, _window$navigator) => typeof window !== 'undefined' && (((_window$document = window.document) == null ? void 0 : _window$document.createElement) || ((_window$navigator = window.navigator) == null ? void 0 : _window$navigator.product) === 'ReactNative'))() ? React__namespace.useLayoutEffect : React__namespace.useEffect;
67
+ function useMutableCallback(fn) {
68
+ const ref = React__namespace.useRef(fn);
69
+ useIsomorphicLayoutEffect(() => void (ref.current = fn), [fn]);
70
+ return ref;
71
+ }
72
+ /**
73
+ * Bridges renderer Context and StrictMode from a primary renderer.
74
+ */
75
+ function useBridge() {
76
+ const fiber = itsFine.useFiber();
77
+ const ContextBridge = itsFine.useContextBridge();
78
+ return React__namespace.useMemo(() => ({
79
+ children
80
+ }) => {
81
+ const strict = !!itsFine.traverseFiber(fiber, true, node => node.type === React__namespace.StrictMode);
82
+ const Root = strict ? React__namespace.StrictMode : React__namespace.Fragment;
83
+ return /*#__PURE__*/jsxRuntime.jsx(Root, {
84
+ children: /*#__PURE__*/jsxRuntime.jsx(ContextBridge, {
85
+ children: children
86
+ })
87
+ });
88
+ }, [fiber, ContextBridge]);
89
+ }
90
+ function Block({
91
+ set
92
+ }) {
93
+ useIsomorphicLayoutEffect(() => {
94
+ set(new Promise(() => null));
95
+ return () => set(false);
96
+ }, [set]);
97
+ return null;
98
+ }
72
99
 
73
- // Throw if an object or literal was passed for args
74
- if (props.args !== undefined && !Array.isArray(props.args)) throw new Error('R3F: The args prop must be an array!');
100
+ // NOTE: static members get down-level transpiled to mutations which break tree-shaking
101
+ const ErrorBoundary = /* @__PURE__ */(_ErrorBoundary => (_ErrorBoundary = class ErrorBoundary extends React__namespace.Component {
102
+ constructor(...args) {
103
+ super(...args);
104
+ this.state = {
105
+ error: false
106
+ };
107
+ }
108
+ componentDidCatch(err) {
109
+ this.props.set(err);
110
+ }
111
+ render() {
112
+ return this.state.error ? null : this.props.children;
113
+ }
114
+ }, _ErrorBoundary.getDerivedStateFromError = () => ({
115
+ error: true
116
+ }), _ErrorBoundary))();
117
+ function calculateDpr(dpr) {
118
+ var _window$devicePixelRa;
119
+ // Err on the side of progress by assuming 2x dpr if we can't detect it
120
+ // This will happen in workers where window is defined but dpr isn't.
121
+ const target = typeof window !== 'undefined' ? (_window$devicePixelRa = window.devicePixelRatio) != null ? _window$devicePixelRa : 2 : 1;
122
+ return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
75
123
  }
76
- function createInstance(type, props, root) {
77
- var _props$object;
78
- validateInstance(type, props);
79
124
 
80
- // Regenerate the R3F instance for primitives to simulate a new object
81
- if (type === 'primitive' && (_props$object = props.object) != null && _props$object.__r3f) delete props.object.__r3f;
82
- return prepare(props.object, root, type, props);
125
+ /**
126
+ * Returns instance root state
127
+ */
128
+ function getRootState(obj) {
129
+ var _r3f;
130
+ return (_r3f = obj.__r3f) == null ? void 0 : _r3f.root.getState();
83
131
  }
84
- function hideInstance(instance) {
85
- if (!instance.isHidden) {
86
- var _instance$parent;
87
- if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
88
- detach(instance.parent, instance);
89
- } else if (isObject3D(instance.object)) {
90
- instance.object.visible = false;
132
+ // A collection of compare functions
133
+ const is = {
134
+ obj: a => a === Object(a) && !is.arr(a) && typeof a !== 'function',
135
+ fun: a => typeof a === 'function',
136
+ str: a => typeof a === 'string',
137
+ num: a => typeof a === 'number',
138
+ boo: a => typeof a === 'boolean',
139
+ und: a => a === void 0,
140
+ nul: a => a === null,
141
+ arr: a => Array.isArray(a),
142
+ equ(a, b, {
143
+ arrays = 'shallow',
144
+ objects = 'reference',
145
+ strict = true
146
+ } = {}) {
147
+ // Wrong type or one of the two undefined, doesn't match
148
+ if (typeof a !== typeof b || !!a !== !!b) return false;
149
+ // Atomic, just compare a against b
150
+ if (is.str(a) || is.num(a) || is.boo(a)) return a === b;
151
+ const isObj = is.obj(a);
152
+ if (isObj && objects === 'reference') return a === b;
153
+ const isArr = is.arr(a);
154
+ if (isArr && arrays === 'reference') return a === b;
155
+ // Array or Object, shallow compare first to see if it's a match
156
+ if ((isArr || isObj) && a === b) return true;
157
+ // Last resort, go through keys
158
+ let i;
159
+ // Check if a has all the keys of b
160
+ for (i in a) if (!(i in b)) return false;
161
+ // Check if values between keys match
162
+ if (isObj && arrays === 'shallow' && objects === 'shallow') {
163
+ for (i in strict ? b : a) if (!is.equ(a[i], b[i], {
164
+ strict,
165
+ objects: 'reference'
166
+ })) return false;
167
+ } else {
168
+ for (i in strict ? b : a) if (a[i] !== b[i]) return false;
91
169
  }
92
- instance.isHidden = true;
93
- invalidateInstance(instance);
170
+ // If i is undefined
171
+ if (is.und(i)) {
172
+ // If both arrays are empty we consider them equal
173
+ if (isArr && a.length === 0 && b.length === 0) return true;
174
+ // If both objects are empty we consider them equal
175
+ if (isObj && Object.keys(a).length === 0 && Object.keys(b).length === 0) return true;
176
+ // Otherwise match them by value
177
+ if (a !== b) return false;
178
+ }
179
+ return true;
180
+ }
181
+ };
182
+
183
+ // Collects nodes and materials from a THREE.Object3D
184
+ function buildGraph(object) {
185
+ const data = {
186
+ nodes: {},
187
+ materials: {}
188
+ };
189
+ if (object) {
190
+ object.traverse(obj => {
191
+ if (obj.name) data.nodes[obj.name] = obj;
192
+ if (obj.material && !data.materials[obj.material.name]) data.materials[obj.material.name] = obj.material;
193
+ });
94
194
  }
195
+ return data;
95
196
  }
96
- function unhideInstance(instance) {
97
- if (instance.isHidden) {
98
- var _instance$parent2;
99
- if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
100
- attach(instance.parent, instance);
101
- } else if (isObject3D(instance.object) && instance.props.visible !== false) {
102
- instance.object.visible = true;
103
- }
104
- instance.isHidden = false;
105
- invalidateInstance(instance);
197
+ // Disposes an object and all its properties
198
+ function dispose(obj) {
199
+ if (obj.type !== 'Scene') obj.dispose == null ? void 0 : obj.dispose();
200
+ for (const p in obj) {
201
+ const prop = obj[p];
202
+ if ((prop == null ? void 0 : prop.type) !== 'Scene') prop == null ? void 0 : prop.dispose == null ? void 0 : prop.dispose();
106
203
  }
107
204
  }
205
+ const REACT_INTERNAL_PROPS = ['children', 'key', 'ref'];
108
206
 
109
- // https://github.com/facebook/react/issues/20271
110
- // This will make sure events and attach are only handled once when trees are complete
111
- function handleContainerEffects(parent, child, beforeChild) {
112
- // Bail if tree isn't mounted or parent is not a container.
113
- // This ensures that the tree is finalized and React won't discard results to Suspense
114
- const state = child.root.getState();
115
- if (!parent.parent && parent.object !== state.scene) return;
116
-
117
- // Create & link object on first run
118
- if (!child.object) {
119
- var _child$props$object, _child$props$args;
120
- // Get target from catalogue
121
- const name = `${child.type[0].toUpperCase()}${child.type.slice(1)}`;
122
- const target = catalogue[name];
207
+ // Gets only instance props from reconciler fibers
208
+ function getInstanceProps(queue) {
209
+ const props = {};
210
+ for (const key in queue) {
211
+ if (!REACT_INTERNAL_PROPS.includes(key)) props[key] = queue[key];
212
+ }
213
+ return props;
214
+ }
123
215
 
124
- // Create object
125
- child.object = (_child$props$object = child.props.object) != null ? _child$props$object : new target(...((_child$props$args = child.props.args) != null ? _child$props$args : []));
126
- child.object.__r3f = child;
216
+ // Each object in the scene carries a small LocalState descriptor
217
+ function prepare(target, root, type, props) {
218
+ const object = target;
127
219
 
128
- // Set initial props
129
- applyProps(child.object, child.props);
220
+ // Create instance descriptor
221
+ let instance = object == null ? void 0 : object.__r3f;
222
+ if (!instance) {
223
+ instance = {
224
+ root,
225
+ type,
226
+ parent: null,
227
+ children: [],
228
+ props: getInstanceProps(props),
229
+ object,
230
+ eventCount: 0,
231
+ handlers: {},
232
+ isHidden: false
233
+ };
234
+ if (object) object.__r3f = instance;
130
235
  }
236
+ return instance;
237
+ }
238
+ function resolve(root, key) {
239
+ var _target;
240
+ let target = root[key];
241
+ if (!key.includes('-')) return {
242
+ root,
243
+ key,
244
+ target
245
+ };
131
246
 
132
- // Append instance
133
- if (child.props.attach) {
134
- attach(parent, child);
135
- } else if (isObject3D(child.object) && isObject3D(parent.object)) {
136
- const childIndex = parent.object.children.indexOf(beforeChild == null ? void 0 : beforeChild.object);
137
- if (beforeChild && childIndex !== -1) {
138
- child.object.parent = parent.object;
139
- parent.object.children.splice(childIndex, 0, child.object);
140
- child.object.dispatchEvent({
141
- type: 'added'
142
- });
143
- parent.object.dispatchEvent({
144
- type: 'childadded',
145
- child: child.object
146
- });
147
- } else {
148
- parent.object.add(child.object);
149
- }
150
- }
151
-
152
- // Link subtree
153
- for (const childInstance of child.children) handleContainerEffects(child, childInstance);
247
+ // Resolve pierced target
248
+ const chain = key.split('-');
249
+ target = chain.reduce((acc, key) => acc[key], root);
250
+ key = chain.pop();
154
251
 
155
- // Tree was updated, request a frame
156
- invalidateInstance(child);
252
+ // Switch root if atomic
253
+ if (!((_target = target) != null && _target.set)) root = chain.reduce((acc, key) => acc[key], root);
254
+ return {
255
+ root,
256
+ key,
257
+ target
258
+ };
157
259
  }
158
- function appendChild(parent, child) {
159
- if (!child) return;
160
-
161
- // Link instances
162
- child.parent = parent;
163
- parent.children.push(child);
164
260
 
165
- // Attach tree once complete
166
- handleContainerEffects(parent, child);
261
+ // Checks if a dash-cased string ends with an integer
262
+ const INDEX_REGEX = /-\d+$/;
263
+ function attach(parent, child) {
264
+ if (is.str(child.props.attach)) {
265
+ // If attaching into an array (foo-0), create one
266
+ if (INDEX_REGEX.test(child.props.attach)) {
267
+ const index = child.props.attach.replace(INDEX_REGEX, '');
268
+ const {
269
+ root,
270
+ key
271
+ } = resolve(parent.object, index);
272
+ if (!Array.isArray(root[key])) root[key] = [];
273
+ }
274
+ const {
275
+ root,
276
+ key
277
+ } = resolve(parent.object, child.props.attach);
278
+ child.previousAttach = root[key];
279
+ root[key] = child.object;
280
+ } else if (is.fun(child.props.attach)) {
281
+ child.previousAttach = child.props.attach(parent.object, child.object);
282
+ }
167
283
  }
168
- function insertBefore(parent, child, beforeChild) {
169
- if (!child || !beforeChild) return;
170
-
171
- // Link instances
172
- child.parent = parent;
173
- const childIndex = parent.children.indexOf(beforeChild);
174
- if (childIndex !== -1) parent.children.splice(childIndex, 0, child);else parent.children.push(child);
175
-
176
- // Attach tree once complete
177
- handleContainerEffects(parent, child, beforeChild);
284
+ function detach(parent, child) {
285
+ if (is.str(child.props.attach)) {
286
+ const {
287
+ root,
288
+ key
289
+ } = resolve(parent.object, child.props.attach);
290
+ const previous = child.previousAttach;
291
+ // When the previous value was undefined, it means the value was never set to begin with
292
+ if (previous === undefined) delete root[key];
293
+ // Otherwise set the previous value
294
+ else root[key] = previous;
295
+ } else {
296
+ child.previousAttach == null ? void 0 : child.previousAttach(parent.object, child.object);
297
+ }
298
+ delete child.previousAttach;
178
299
  }
179
- function disposeOnIdle(object) {
180
- if (typeof object.dispose === 'function') {
181
- const handleDispose = () => {
182
- try {
183
- object.dispose();
184
- } catch {
185
- // no-op
186
- }
187
- };
188
-
189
- // In a testing environment, cleanup immediately
190
- if (typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined') handleDispose();
191
- // Otherwise, using a real GPU so schedule cleanup to prevent stalls
192
- else scheduler.unstable_scheduleCallback(scheduler.unstable_IdlePriority, handleDispose);
300
+ const RESERVED_PROPS = [...REACT_INTERNAL_PROPS,
301
+ // Instance props
302
+ 'args', 'dispose', 'attach', 'object', 'onUpdate',
303
+ // Behavior flags
304
+ 'dispose'];
305
+ const MEMOIZED_PROTOTYPES = new Map();
306
+ function getMemoizedPrototype(root) {
307
+ let ctor = MEMOIZED_PROTOTYPES.get(root.constructor);
308
+ try {
309
+ if (!ctor) {
310
+ ctor = new root.constructor();
311
+ MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
312
+ }
313
+ } catch (e) {
314
+ // ...
193
315
  }
316
+ return ctor;
194
317
  }
195
- function removeChild(parent, child, dispose) {
196
- if (!child) return;
197
318
 
198
- // Unlink instances
199
- child.parent = null;
200
- const childIndex = parent.children.indexOf(child);
201
- if (childIndex !== -1) parent.children.splice(childIndex, 1);
319
+ // This function prepares a set of changes to be applied to the instance
320
+ function diffProps(instance, newProps) {
321
+ const changedProps = {};
202
322
 
203
- // Eagerly tear down tree
204
- if (child.props.attach) {
205
- detach(parent, child);
206
- } else if (isObject3D(child.object) && isObject3D(parent.object)) {
207
- parent.object.remove(child.object);
208
- removeInteractivity(findInitialRoot(child), child.object);
209
- }
323
+ // Sort through props
324
+ for (const prop in newProps) {
325
+ // Skip reserved keys
326
+ if (RESERVED_PROPS.includes(prop)) continue;
327
+ // Skip if props match
328
+ if (is.equ(newProps[prop], instance.props[prop])) continue;
210
329
 
211
- // Allow objects to bail out of unmount disposal with dispose={null}
212
- const shouldDispose = child.props.dispose !== null && dispose !== false;
330
+ // Props changed, add them
331
+ changedProps[prop] = newProps[prop];
213
332
 
214
- // Recursively remove instance children
215
- for (let i = child.children.length - 1; i >= 0; i--) {
216
- const node = child.children[i];
217
- removeChild(child, node, shouldDispose);
333
+ // Reset pierced props
334
+ for (const other in newProps) {
335
+ if (other.startsWith(`${prop}-`)) changedProps[other] = newProps[other];
336
+ }
218
337
  }
219
- child.children.length = 0;
220
338
 
221
- // Unlink instance object
222
- delete child.object.__r3f;
223
-
224
- // Dispose object whenever the reconciler feels like it.
225
- // Never dispose of primitives because their state may be kept outside of React!
226
- // In order for an object to be able to dispose it
227
- // - has a dispose method
228
- // - cannot be a <primitive object={...} />
229
- // - cannot be a THREE.Scene, because three has broken its own API
230
- if (shouldDispose && child.type !== 'primitive' && child.object.type !== 'Scene') {
231
- disposeOnIdle(child.object);
232
- }
339
+ // Reset removed props for HMR
340
+ for (const prop in instance.props) {
341
+ if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
342
+ const {
343
+ root,
344
+ key
345
+ } = resolve(instance.object, prop);
233
346
 
234
- // Tree was updated, request a frame for top-level instance
235
- if (dispose === undefined) invalidateInstance(child);
236
- }
237
- function setFiberRef(fiber, publicInstance) {
238
- for (const _fiber of [fiber, fiber.alternate]) {
239
- if (_fiber !== null) {
240
- if (typeof _fiber.ref === 'function') {
241
- _fiber.refCleanup == null ? void 0 : _fiber.refCleanup();
242
- const cleanup = _fiber.ref(publicInstance);
243
- if (typeof cleanup === 'function') _fiber.refCleanup = cleanup;
244
- } else if (_fiber.ref) {
245
- _fiber.ref.current = publicInstance;
246
- }
347
+ // https://github.com/mrdoob/three.js/issues/21209
348
+ // HMR/fast-refresh relies on the ability to cancel out props, but threejs
349
+ // has no means to do this. Hence we curate a small collection of value-classes
350
+ // with their respective constructor/set arguments
351
+ // For removed props, try to set default values, if possible
352
+ if (root.constructor && root.constructor.length === 0) {
353
+ // create a blank slate of the instance and copy the particular parameter.
354
+ const ctor = getMemoizedPrototype(root);
355
+ if (!is.und(ctor)) changedProps[key] = ctor[key];
356
+ } else {
357
+ // instance does not have constructor, just set it to 0
358
+ changedProps[key] = 0;
247
359
  }
248
360
  }
361
+ return changedProps;
249
362
  }
250
- const reconstructed = [];
251
- function swapInstances() {
252
- // Detach instance
253
- for (const [instance] of reconstructed) {
254
- const parent = instance.parent;
255
- if (parent) {
256
- if (instance.props.attach) {
257
- detach(parent, instance);
258
- } else if (isObject3D(instance.object) && isObject3D(parent.object)) {
259
- parent.object.remove(instance.object);
260
- }
261
- for (const child of instance.children) {
262
- if (child.props.attach) {
263
- detach(instance, child);
264
- } else if (isObject3D(child.object) && isObject3D(instance.object)) {
265
- instance.object.remove(child.object);
266
- }
267
- }
268
- }
269
363
 
270
- // If the old instance is hidden, we need to unhide it.
271
- // React assumes it can discard instances since they're pure for DOM.
272
- // This isn't true for us since our lifetimes are impure and longliving.
273
- // So, we manually check if an instance was hidden and unhide it.
274
- if (instance.isHidden) unhideInstance(instance);
364
+ // https://github.com/mrdoob/three.js/pull/27042
365
+ // https://github.com/mrdoob/three.js/pull/22748
366
+ const colorMaps = ['map', 'emissiveMap', 'sheenColorMap', 'specularColorMap', 'envMap'];
367
+ const EVENT_REGEX = /^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/;
368
+ // This function applies a set of changes to the instance
369
+ function applyProps(object, props) {
370
+ var _instance$object;
371
+ const instance = object.__r3f;
372
+ const rootState = instance && findInitialRoot(instance).getState();
373
+ const prevHandlers = instance == null ? void 0 : instance.eventCount;
374
+ for (const prop in props) {
375
+ let value = props[prop];
275
376
 
276
- // Dispose of old object if able
277
- if (instance.object.__r3f) delete instance.object.__r3f;
278
- if (instance.type !== 'primitive') disposeOnIdle(instance.object);
279
- }
377
+ // Don't mutate reserved keys
378
+ if (RESERVED_PROPS.includes(prop)) continue;
280
379
 
281
- // Update instance
282
- for (const [instance, props, fiber] of reconstructed) {
283
- instance.props = props;
284
- const parent = instance.parent;
285
- if (parent) {
286
- var _instance$props$objec, _instance$props$args;
287
- // Get target from catalogue
288
- const name = `${instance.type[0].toUpperCase()}${instance.type.slice(1)}`;
289
- const target = catalogue[name];
380
+ // Deal with pointer events, including removing them if undefined
381
+ if (instance && EVENT_REGEX.test(prop)) {
382
+ if (typeof value === 'function') instance.handlers[prop] = value;else delete instance.handlers[prop];
383
+ instance.eventCount = Object.keys(instance.handlers).length;
384
+ continue;
385
+ }
290
386
 
291
- // Create object
292
- instance.object = (_instance$props$objec = instance.props.object) != null ? _instance$props$objec : new target(...((_instance$props$args = instance.props.args) != null ? _instance$props$args : []));
293
- instance.object.__r3f = instance;
294
- setFiberRef(fiber, instance.object);
387
+ // Ignore setting undefined props
388
+ // https://github.com/pmndrs/react-three-fiber/issues/274
389
+ if (value === undefined) continue;
390
+ let {
391
+ root,
392
+ key,
393
+ target
394
+ } = resolve(object, prop);
295
395
 
296
- // Set initial props
297
- applyProps(instance.object, instance.props);
298
- if (instance.props.attach) {
299
- attach(parent, instance);
300
- } else if (isObject3D(instance.object) && isObject3D(parent.object)) {
301
- parent.object.add(instance.object);
302
- }
303
- for (const child of instance.children) {
304
- if (child.props.attach) {
305
- attach(instance, child);
306
- } else if (isObject3D(child.object) && isObject3D(instance.object)) {
307
- instance.object.add(child.object);
308
- }
309
- }
310
-
311
- // Tree was updated, request a frame
312
- invalidateInstance(instance);
313
- }
314
- }
315
- reconstructed.length = 0;
316
- }
317
-
318
- // Don't handle text instances, make it no-op
319
- const handleTextInstance = () => {};
320
- const NO_CONTEXT = {};
321
- let currentUpdatePriority = constants.NoEventPriority;
322
-
323
- // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberFlags.js
324
- const NoFlags = 0;
325
- const Update = 4;
326
- const reconciler = createReconciler({
327
- isPrimaryRenderer: false,
328
- warnsIfNotActing: false,
329
- supportsMutation: true,
330
- supportsPersistence: false,
331
- supportsHydration: false,
332
- createInstance,
333
- removeChild,
334
- appendChild,
335
- appendInitialChild: appendChild,
336
- insertBefore,
337
- appendChildToContainer(container, child) {
338
- const scene = container.getState().scene.__r3f;
339
- if (!child || !scene) return;
340
- appendChild(scene, child);
341
- },
342
- removeChildFromContainer(container, child) {
343
- const scene = container.getState().scene.__r3f;
344
- if (!child || !scene) return;
345
- removeChild(scene, child);
346
- },
347
- insertInContainerBefore(container, child, beforeChild) {
348
- const scene = container.getState().scene.__r3f;
349
- if (!child || !beforeChild || !scene) return;
350
- insertBefore(scene, child, beforeChild);
351
- },
352
- getRootHostContext: () => NO_CONTEXT,
353
- getChildHostContext: () => NO_CONTEXT,
354
- commitUpdate(instance, type, oldProps, newProps, fiber) {
355
- var _newProps$args, _oldProps$args, _newProps$args2;
356
- validateInstance(type, newProps);
357
- let reconstruct = false;
358
-
359
- // Reconstruct primitives if object prop changes
360
- if (instance.type === 'primitive' && oldProps.object !== newProps.object) reconstruct = true;
361
- // Reconstruct instance if args were added or removed
362
- else if (((_newProps$args = newProps.args) == null ? void 0 : _newProps$args.length) !== ((_oldProps$args = oldProps.args) == null ? void 0 : _oldProps$args.length)) reconstruct = true;
363
- // Reconstruct instance if args were changed
364
- else if ((_newProps$args2 = newProps.args) != null && _newProps$args2.some((value, index) => {
365
- var _oldProps$args2;
366
- return value !== ((_oldProps$args2 = oldProps.args) == null ? void 0 : _oldProps$args2[index]);
367
- })) reconstruct = true;
368
-
369
- // Reconstruct when args or <primitive object={...} have changes
370
- if (reconstruct) {
371
- reconstructed.push([instance, {
372
- ...newProps
373
- }, fiber]);
374
- } else {
375
- // Create a diff-set, flag if there are any changes
376
- const changedProps = diffProps(instance, newProps);
377
- if (Object.keys(changedProps).length) {
378
- Object.assign(instance.props, changedProps);
379
- applyProps(instance.object, changedProps);
380
- }
381
- }
382
-
383
- // Flush reconstructed siblings when we hit the last updated child in a sequence
384
- const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
385
- if (isTailSibling) swapInstances();
386
- },
387
- finalizeInitialChildren: () => false,
388
- commitMount() {},
389
- getPublicInstance: instance => instance == null ? void 0 : instance.object,
390
- prepareForCommit: () => null,
391
- preparePortalMount: container => prepare(container.getState().scene, container, '', {}),
392
- resetAfterCommit: () => {},
393
- shouldSetTextContent: () => false,
394
- clearContainer: () => false,
395
- hideInstance,
396
- unhideInstance,
397
- createTextInstance: handleTextInstance,
398
- hideTextInstance: handleTextInstance,
399
- unhideTextInstance: handleTextInstance,
400
- scheduleTimeout: typeof setTimeout === 'function' ? setTimeout : undefined,
401
- cancelTimeout: typeof clearTimeout === 'function' ? clearTimeout : undefined,
402
- noTimeout: -1,
403
- getInstanceFromNode: () => null,
404
- beforeActiveInstanceBlur() {},
405
- afterActiveInstanceBlur() {},
406
- detachDeletedInstance() {},
407
- prepareScopeUpdate() {},
408
- getInstanceFromScope: () => null,
409
- shouldAttemptEagerTransition: () => false,
410
- trackSchedulerEvent: () => {},
411
- resolveEventType: () => null,
412
- resolveEventTimeStamp: () => -1.1,
413
- requestPostPaintCallback() {},
414
- maySuspendCommit: () => false,
415
- preloadInstance: () => true,
416
- // true indicates already loaded
417
- startSuspendingCommit() {},
418
- suspendInstance() {},
419
- waitForCommitToBeReady: () => null,
420
- NotPendingTransition: null,
421
- setCurrentUpdatePriority(newPriority) {
422
- currentUpdatePriority = newPriority;
423
- },
424
- getCurrentUpdatePriority() {
425
- return currentUpdatePriority;
426
- },
427
- resolveUpdatePriority() {
428
- var _window$event;
429
- if (currentUpdatePriority !== constants.NoEventPriority) return currentUpdatePriority;
430
- switch (typeof window !== 'undefined' && ((_window$event = window.event) == null ? void 0 : _window$event.type)) {
431
- case 'click':
432
- case 'contextmenu':
433
- case 'dblclick':
434
- case 'pointercancel':
435
- case 'pointerdown':
436
- case 'pointerup':
437
- return constants.DiscreteEventPriority;
438
- case 'pointermove':
439
- case 'pointerout':
440
- case 'pointerover':
441
- case 'pointerenter':
442
- case 'pointerleave':
443
- case 'wheel':
444
- return constants.ContinuousEventPriority;
445
- default:
446
- return constants.DefaultEventPriority;
447
- }
448
- },
449
- resetFormInstance() {}
450
- });
451
-
452
- var _window$document, _window$navigator;
453
- /**
454
- * Returns the instance's initial (outmost) root.
455
- */
456
- function findInitialRoot(instance) {
457
- let root = instance.root;
458
- while (root.getState().previousRoot) root = root.getState().previousRoot;
459
- return root;
460
- }
461
-
462
- /**
463
- * Returns `true` with correct TS type inference if an object has a configurable color space (since r152).
464
- */
465
- const hasColorSpace = object => 'colorSpace' in object || 'outputColorSpace' in object;
466
- /**
467
- * The current THREE.ColorManagement instance, if present.
468
- */
469
- const getColorManagement = () => {
470
- var _ColorManagement;
471
- return (_ColorManagement = catalogue.ColorManagement) != null ? _ColorManagement : null;
472
- };
473
- /**
474
- * Safely flush async effects when testing, simulating a legacy root.
475
- */
476
- const act = React__namespace.act;
477
- const isOrthographicCamera = def => def && def.isOrthographicCamera;
478
- const isRef = obj => obj && obj.hasOwnProperty('current');
479
-
480
- /**
481
- * An SSR-friendly useLayoutEffect.
482
- *
483
- * React currently throws a warning when using useLayoutEffect on the server.
484
- * To get around it, we can conditionally useEffect on the server (no-op) and
485
- * useLayoutEffect elsewhere.
486
- *
487
- * @see https://github.com/facebook/react/issues/14927
488
- */
489
- const useIsomorphicLayoutEffect = typeof window !== 'undefined' && ((_window$document = window.document) != null && _window$document.createElement || ((_window$navigator = window.navigator) == null ? void 0 : _window$navigator.product) === 'ReactNative') ? React__namespace.useLayoutEffect : React__namespace.useEffect;
490
- function useMutableCallback(fn) {
491
- const ref = React__namespace.useRef(fn);
492
- useIsomorphicLayoutEffect(() => void (ref.current = fn), [fn]);
493
- return ref;
494
- }
495
- /**
496
- * Bridges renderer Context and StrictMode from a primary renderer.
497
- */
498
- function useBridge() {
499
- const fiber = itsFine.useFiber();
500
- const ContextBridge = itsFine.useContextBridge();
501
- return React__namespace.useMemo(() => ({
502
- children
503
- }) => {
504
- const strict = !!itsFine.traverseFiber(fiber, true, node => node.type === React__namespace.StrictMode);
505
- const Root = strict ? React__namespace.StrictMode : React__namespace.Fragment;
506
- return /*#__PURE__*/jsxRuntime.jsx(Root, {
507
- children: /*#__PURE__*/jsxRuntime.jsx(ContextBridge, {
508
- children: children
509
- })
510
- });
511
- }, [fiber, ContextBridge]);
512
- }
513
- function Block({
514
- set
515
- }) {
516
- useIsomorphicLayoutEffect(() => {
517
- set(new Promise(() => null));
518
- return () => set(false);
519
- }, [set]);
520
- return null;
521
- }
522
- class ErrorBoundary extends React__namespace.Component {
523
- constructor(...args) {
524
- super(...args);
525
- this.state = {
526
- error: false
527
- };
528
- }
529
- componentDidCatch(err) {
530
- this.props.set(err);
531
- }
532
- render() {
533
- return this.state.error ? null : this.props.children;
534
- }
535
- }
536
- ErrorBoundary.getDerivedStateFromError = () => ({
537
- error: true
538
- });
539
- function calculateDpr(dpr) {
540
- var _window$devicePixelRa;
541
- // Err on the side of progress by assuming 2x dpr if we can't detect it
542
- // This will happen in workers where window is defined but dpr isn't.
543
- const target = typeof window !== 'undefined' ? (_window$devicePixelRa = window.devicePixelRatio) != null ? _window$devicePixelRa : 2 : 1;
544
- return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
545
- }
546
-
547
- /**
548
- * Returns instance root state
549
- */
550
- function getRootState(obj) {
551
- var _r3f;
552
- return (_r3f = obj.__r3f) == null ? void 0 : _r3f.root.getState();
553
- }
554
- // A collection of compare functions
555
- const is = {
556
- obj: a => a === Object(a) && !is.arr(a) && typeof a !== 'function',
557
- fun: a => typeof a === 'function',
558
- str: a => typeof a === 'string',
559
- num: a => typeof a === 'number',
560
- boo: a => typeof a === 'boolean',
561
- und: a => a === void 0,
562
- arr: a => Array.isArray(a),
563
- equ(a, b, {
564
- arrays = 'shallow',
565
- objects = 'reference',
566
- strict = true
567
- } = {}) {
568
- // Wrong type or one of the two undefined, doesn't match
569
- if (typeof a !== typeof b || !!a !== !!b) return false;
570
- // Atomic, just compare a against b
571
- if (is.str(a) || is.num(a) || is.boo(a)) return a === b;
572
- const isObj = is.obj(a);
573
- if (isObj && objects === 'reference') return a === b;
574
- const isArr = is.arr(a);
575
- if (isArr && arrays === 'reference') return a === b;
576
- // Array or Object, shallow compare first to see if it's a match
577
- if ((isArr || isObj) && a === b) return true;
578
- // Last resort, go through keys
579
- let i;
580
- // Check if a has all the keys of b
581
- for (i in a) if (!(i in b)) return false;
582
- // Check if values between keys match
583
- if (isObj && arrays === 'shallow' && objects === 'shallow') {
584
- for (i in strict ? b : a) if (!is.equ(a[i], b[i], {
585
- strict,
586
- objects: 'reference'
587
- })) return false;
588
- } else {
589
- for (i in strict ? b : a) if (a[i] !== b[i]) return false;
590
- }
591
- // If i is undefined
592
- if (is.und(i)) {
593
- // If both arrays are empty we consider them equal
594
- if (isArr && a.length === 0 && b.length === 0) return true;
595
- // If both objects are empty we consider them equal
596
- if (isObj && Object.keys(a).length === 0 && Object.keys(b).length === 0) return true;
597
- // Otherwise match them by value
598
- if (a !== b) return false;
599
- }
600
- return true;
601
- }
602
- };
603
-
604
- // Collects nodes and materials from a THREE.Object3D
605
- function buildGraph(object) {
606
- const data = {
607
- nodes: {},
608
- materials: {}
609
- };
610
- if (object) {
611
- object.traverse(obj => {
612
- if (obj.name) data.nodes[obj.name] = obj;
613
- if (obj.material && !data.materials[obj.material.name]) data.materials[obj.material.name] = obj.material;
614
- });
615
- }
616
- return data;
617
- }
618
- // Disposes an object and all its properties
619
- function dispose(obj) {
620
- if (obj.type !== 'Scene') obj.dispose == null ? void 0 : obj.dispose();
621
- for (const p in obj) {
622
- const prop = obj[p];
623
- if ((prop == null ? void 0 : prop.type) !== 'Scene') prop == null ? void 0 : prop.dispose == null ? void 0 : prop.dispose();
624
- }
625
- }
626
- const REACT_INTERNAL_PROPS = ['children', 'key', 'ref'];
627
-
628
- // Gets only instance props from reconciler fibers
629
- function getInstanceProps(queue) {
630
- const props = {};
631
- for (const key in queue) {
632
- if (!REACT_INTERNAL_PROPS.includes(key)) props[key] = queue[key];
633
- }
634
- return props;
635
- }
636
-
637
- // Each object in the scene carries a small LocalState descriptor
638
- function prepare(target, root, type, props) {
639
- const object = target;
640
-
641
- // Create instance descriptor
642
- let instance = object == null ? void 0 : object.__r3f;
643
- if (!instance) {
644
- instance = {
645
- root,
646
- type,
647
- parent: null,
648
- children: [],
649
- props: getInstanceProps(props),
650
- object,
651
- eventCount: 0,
652
- handlers: {},
653
- isHidden: false
654
- };
655
- if (object) {
656
- object.__r3f = instance;
657
- if (type) applyProps(object, instance.props);
658
- }
659
- }
660
- return instance;
661
- }
662
- function resolve(root, key) {
663
- var _target;
664
- let target = root[key];
665
- if (!key.includes('-')) return {
666
- root,
667
- key,
668
- target
669
- };
670
-
671
- // Resolve pierced target
672
- const chain = key.split('-');
673
- target = chain.reduce((acc, key) => acc[key], root);
674
- key = chain.pop();
675
-
676
- // Switch root if atomic
677
- if (!((_target = target) != null && _target.set)) root = chain.reduce((acc, key) => acc[key], root);
678
- return {
679
- root,
680
- key,
681
- target
682
- };
683
- }
684
-
685
- // Checks if a dash-cased string ends with an integer
686
- const INDEX_REGEX = /-\d+$/;
687
- function attach(parent, child) {
688
- if (is.str(child.props.attach)) {
689
- // If attaching into an array (foo-0), create one
690
- if (INDEX_REGEX.test(child.props.attach)) {
691
- const index = child.props.attach.replace(INDEX_REGEX, '');
692
- const {
693
- root,
694
- key
695
- } = resolve(parent.object, index);
696
- if (!Array.isArray(root[key])) root[key] = [];
697
- }
698
- const {
699
- root,
700
- key
701
- } = resolve(parent.object, child.props.attach);
702
- child.previousAttach = root[key];
703
- root[key] = child.object;
704
- } else if (is.fun(child.props.attach)) {
705
- child.previousAttach = child.props.attach(parent.object, child.object);
706
- }
707
- }
708
- function detach(parent, child) {
709
- if (is.str(child.props.attach)) {
710
- const {
711
- root,
712
- key
713
- } = resolve(parent.object, child.props.attach);
714
- const previous = child.previousAttach;
715
- // When the previous value was undefined, it means the value was never set to begin with
716
- if (previous === undefined) delete root[key];
717
- // Otherwise set the previous value
718
- else root[key] = previous;
719
- } else {
720
- child.previousAttach == null ? void 0 : child.previousAttach(parent.object, child.object);
721
- }
722
- delete child.previousAttach;
723
- }
724
- const RESERVED_PROPS = [...REACT_INTERNAL_PROPS,
725
- // Instance props
726
- 'args', 'dispose', 'attach', 'object', 'onUpdate',
727
- // Behavior flags
728
- 'dispose'];
729
- const MEMOIZED_PROTOTYPES = new Map();
730
-
731
- // This function prepares a set of changes to be applied to the instance
732
- function diffProps(instance, newProps) {
733
- const changedProps = {};
734
-
735
- // Sort through props
736
- for (const prop in newProps) {
737
- // Skip reserved keys
738
- if (RESERVED_PROPS.includes(prop)) continue;
739
- // Skip if props match
740
- if (is.equ(newProps[prop], instance.props[prop])) continue;
741
-
742
- // Props changed, add them
743
- changedProps[prop] = newProps[prop];
744
-
745
- // Reset pierced props
746
- for (const other in newProps) {
747
- if (other.startsWith(`${prop}-`)) changedProps[other] = newProps[other];
748
- }
749
- }
750
-
751
- // Reset removed props for HMR
752
- for (const prop in instance.props) {
753
- if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
754
- const {
755
- root,
756
- key
757
- } = resolve(instance.object, prop);
758
-
759
- // https://github.com/mrdoob/three.js/issues/21209
760
- // HMR/fast-refresh relies on the ability to cancel out props, but threejs
761
- // has no means to do this. Hence we curate a small collection of value-classes
762
- // with their respective constructor/set arguments
763
- // For removed props, try to set default values, if possible
764
- if (root.constructor && root.constructor.length === 0) {
765
- // create a blank slate of the instance and copy the particular parameter.
766
- let ctor = MEMOIZED_PROTOTYPES.get(root.constructor);
767
- if (!ctor) {
768
- ctor = new root.constructor();
769
- MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
770
- }
771
- changedProps[key] = ctor[key];
772
- } else {
773
- // instance does not have constructor, just set it to 0
774
- changedProps[key] = 0;
775
- }
776
- }
777
- return changedProps;
778
- }
779
-
780
- // const LinearEncoding = 3000
781
- const sRGBEncoding = 3001;
782
- const SRGBColorSpace = 'srgb';
783
- const LinearSRGBColorSpace = 'srgb-linear';
784
-
785
- // https://github.com/mrdoob/three.js/pull/27042
786
- // https://github.com/mrdoob/three.js/pull/22748
787
- const colorMaps = ['map', 'emissiveMap', 'sheenTintMap',
788
- // <r134
789
- 'sheenColorMap', 'specularTintMap',
790
- // <r134
791
- 'specularColorMap', 'envMap'];
792
- const EVENT_REGEX = /^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/;
793
-
794
- // This function applies a set of changes to the instance
795
- function applyProps(object, props) {
796
- const instance = object.__r3f;
797
- const rootState = instance && findInitialRoot(instance).getState();
798
- const prevHandlers = instance == null ? void 0 : instance.eventCount;
799
- for (const prop in props) {
800
- let value = props[prop];
801
-
802
- // Don't mutate reserved keys
803
- if (RESERVED_PROPS.includes(prop)) continue;
804
-
805
- // Deal with pointer events, including removing them if undefined
806
- if (instance && EVENT_REGEX.test(prop)) {
807
- if (typeof value === 'function') instance.handlers[prop] = value;else delete instance.handlers[prop];
808
- instance.eventCount = Object.keys(instance.handlers).length;
809
- }
810
-
811
- // Ignore setting undefined props
812
- // https://github.com/pmndrs/react-three-fiber/issues/274
813
- if (value === undefined) continue;
814
- let {
815
- root,
816
- key,
817
- target
818
- } = resolve(object, prop);
819
-
820
- // Alias (output)encoding => (output)colorSpace (since r152)
821
- // https://github.com/pmndrs/react-three-fiber/pull/2829
822
- if (hasColorSpace(root)) {
823
- if (key === 'encoding') {
824
- key = 'colorSpace';
825
- value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
826
- } else if (key === 'outputEncoding') {
827
- key = 'outputColorSpace';
828
- value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
829
- }
396
+ // Layers must be written to the mask property
397
+ if (target instanceof THREE__namespace.Layers && value instanceof THREE__namespace.Layers) {
398
+ target.mask = value.mask;
399
+ } else if (target instanceof THREE__namespace.Color && isColorRepresentation(value)) {
400
+ target.set(value);
830
401
  }
831
-
832
- // Copy if properties match signatures
833
- if (typeof (target == null ? void 0 : target.copy) === 'function' && target.copy === value.copy) {
402
+ // Copy if properties match signatures and implement math interface (likely read-only)
403
+ else if (target && typeof target.set === 'function' && typeof target.copy === 'function' && value != null && value.constructor && target.constructor === value.constructor) {
834
404
  target.copy(value);
835
405
  }
836
- // Layers have no copy function, we must therefore copy the mask property
837
- else if (target instanceof THREE__namespace.Layers && value instanceof THREE__namespace.Layers) {
838
- target.mask = value.mask;
839
- }
840
406
  // Set array types
841
- else if (target != null && target.set && Array.isArray(value)) {
842
- if (target.fromArray) target.fromArray(value);else target.set(...value);
407
+ else if (target && typeof target.set === 'function' && Array.isArray(value)) {
408
+ if (typeof target.fromArray === 'function') target.fromArray(value);else target.set(...value);
843
409
  }
844
410
  // Set literal types
845
- else if (target != null && target.set && typeof value !== 'object') {
846
- const isColor = target instanceof THREE__namespace.Color;
411
+ else if (target && typeof target.set === 'function' && typeof value === 'number') {
847
412
  // Allow setting array scalars
848
- if (!isColor && target.setScalar && typeof value === 'number') target.setScalar(value);
413
+ if (typeof target.setScalar === 'function') target.setScalar(value);
849
414
  // Otherwise just set single value
850
415
  else target.set(value);
851
-
852
- // Emulate THREE.ColorManagement for older three.js versions
853
- // https://github.com/pmndrs/react-three-fiber/issues/344
854
- if (!getColorManagement() && !(rootState != null && rootState.linear) && isColor) target.convertSRGBToLinear();
855
416
  }
856
417
  // Else, just overwrite the value
857
418
  else {
419
+ var _root$key;
858
420
  root[key] = value;
859
421
 
860
422
  // Auto-convert sRGB texture parameters for built-in materials
861
423
  // https://github.com/pmndrs/react-three-fiber/issues/344
862
424
  // https://github.com/mrdoob/three.js/pull/25857
863
- if (rootState && !rootState.linear && colorMaps.includes(key) && root[key] instanceof THREE__namespace.Texture &&
425
+ if (rootState && !rootState.linear && colorMaps.includes(key) && (_root$key = root[key]) != null && _root$key.isTexture &&
864
426
  // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
865
427
  root[key].format === THREE__namespace.RGBAFormat && root[key].type === THREE__namespace.UnsignedByteType) {
866
428
  // NOTE: this cannot be set from the renderer (e.g. sRGB source textures rendered to P3)
867
- if (hasColorSpace(root[key])) root[key].colorSpace = 'srgb';else root[key].encoding = sRGBEncoding;
429
+ root[key].colorSpace = THREE__namespace.SRGBColorSpace;
868
430
  }
869
431
  }
870
432
  }
871
433
 
872
434
  // Register event handlers
873
- if (instance != null && instance.parent && rootState != null && rootState.internal && instance.object instanceof THREE__namespace.Object3D && prevHandlers !== instance.eventCount) {
435
+ if (instance != null && instance.parent && rootState != null && rootState.internal && (_instance$object = instance.object) != null && _instance$object.isObject3D && prevHandlers !== instance.eventCount) {
436
+ const object = instance.object;
874
437
  // Pre-emptively remove the instance from the interaction manager
875
- const index = rootState.internal.interaction.indexOf(instance.object);
438
+ const index = rootState.internal.interaction.indexOf(object);
876
439
  if (index > -1) rootState.internal.interaction.splice(index, 1);
877
440
  // Add the instance to the interaction manager only when it has handlers
878
- if (instance.eventCount && instance.object.raycast !== null && instance.object instanceof THREE__namespace.Object3D) {
879
- rootState.internal.interaction.push(instance.object);
441
+ if (instance.eventCount && object.raycast !== null) {
442
+ rootState.internal.interaction.push(object);
880
443
  }
881
444
  }
882
445
 
883
446
  // Auto-attach geometries and materials
884
447
  if (instance && instance.props.attach === undefined) {
885
- if (instance.object instanceof THREE__namespace.BufferGeometry) instance.props.attach = 'geometry';else if (instance.object instanceof THREE__namespace.Material) instance.props.attach = 'material';
448
+ if (instance.object.isBufferGeometry) instance.props.attach = 'geometry';else if (instance.object.isMaterial) instance.props.attach = 'material';
886
449
  }
887
450
 
888
451
  // Instance was updated, request a frame
@@ -1054,7 +617,19 @@ function createEvents(store) {
1054
617
  stopped: false
1055
618
  };
1056
619
  for (const hit of intersections) {
1057
- const state = getRootState(hit.object);
620
+ let state = getRootState(hit.object);
621
+
622
+ // If the object is not managed by R3F, it might be parented to an element which is.
623
+ // Traverse upwards until we find a managed parent and use its state instead.
624
+ if (!state) {
625
+ hit.object.traverseAncestors(obj => {
626
+ const parentState = getRootState(obj);
627
+ if (parentState) {
628
+ state = parentState;
629
+ return false;
630
+ }
631
+ });
632
+ }
1058
633
  if (state) {
1059
634
  const {
1060
635
  raycaster,
@@ -1315,7 +890,7 @@ function createEvents(store) {
1315
890
  }
1316
891
 
1317
892
  const isRenderer = def => !!(def != null && def.render);
1318
- const context = /*#__PURE__*/React__namespace.createContext(null);
893
+ const context = /* @__PURE__ */React__namespace.createContext(null);
1319
894
  const createStore = (invalidate, advance) => {
1320
895
  const rootStore = traditional.createWithEqualityFn((set, get) => {
1321
896
  const position = new THREE__namespace.Vector3();
@@ -1329,7 +904,7 @@ const createStore = (invalidate, advance) => {
1329
904
  left
1330
905
  } = size;
1331
906
  const aspect = width / height;
1332
- if (target instanceof THREE__namespace.Vector3) tempTarget.copy(target);else tempTarget.set(...target);
907
+ if (target.isVector3) tempTarget.copy(target);else tempTarget.set(...target);
1333
908
  const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
1334
909
  if (isOrthographicCamera(camera)) {
1335
910
  return {
@@ -1531,161 +1106,576 @@ const createStore = (invalidate, advance) => {
1531
1106
  oldDpr = viewport.dpr;
1532
1107
  // Update camera & renderer
1533
1108
  updateCamera(camera, size);
1534
- gl.setPixelRatio(viewport.dpr);
1109
+ if (viewport.dpr > 0) gl.setPixelRatio(viewport.dpr);
1535
1110
  const updateStyle = typeof HTMLCanvasElement !== 'undefined' && gl.domElement instanceof HTMLCanvasElement;
1536
1111
  gl.setSize(size.width, size.height, updateStyle);
1537
1112
  }
1538
-
1539
- // Update viewport once the camera changes
1540
- if (camera !== oldCamera) {
1541
- oldCamera = camera;
1542
- // Update viewport
1543
- set(state => ({
1544
- viewport: {
1545
- ...state.viewport,
1546
- ...state.viewport.getCurrentViewport(camera)
1547
- }
1548
- }));
1113
+
1114
+ // Update viewport once the camera changes
1115
+ if (camera !== oldCamera) {
1116
+ oldCamera = camera;
1117
+ // Update viewport
1118
+ set(state => ({
1119
+ viewport: {
1120
+ ...state.viewport,
1121
+ ...state.viewport.getCurrentViewport(camera)
1122
+ }
1123
+ }));
1124
+ }
1125
+ });
1126
+
1127
+ // Invalidate on any change
1128
+ rootStore.subscribe(state => invalidate(state));
1129
+
1130
+ // Return root state
1131
+ return rootStore;
1132
+ };
1133
+
1134
+ /**
1135
+ * Exposes an object's {@link Instance}.
1136
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useInstanceHandle
1137
+ *
1138
+ * **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.
1139
+ */
1140
+ function useInstanceHandle(ref) {
1141
+ const instance = React__namespace.useRef(null);
1142
+ React__namespace.useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
1143
+ return instance;
1144
+ }
1145
+
1146
+ /**
1147
+ * Returns the R3F Canvas' Zustand store. Useful for [transient updates](https://github.com/pmndrs/zustand#transient-updates-for-often-occurring-state-changes).
1148
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usestore
1149
+ */
1150
+ function useStore() {
1151
+ const store = React__namespace.useContext(context);
1152
+ if (!store) throw new Error('R3F: Hooks can only be used within the Canvas component!');
1153
+ return store;
1154
+ }
1155
+
1156
+ /**
1157
+ * Accesses R3F's internal state, containing renderer, canvas, scene, etc.
1158
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree
1159
+ */
1160
+ function useThree(selector = state => state, equalityFn) {
1161
+ return useStore()(selector, equalityFn);
1162
+ }
1163
+
1164
+ /**
1165
+ * Executes a callback before render in a shared frame loop.
1166
+ * Can order effects with render priority or manually render with a positive priority.
1167
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe
1168
+ */
1169
+ function useFrame(callback, renderPriority = 0) {
1170
+ const store = useStore();
1171
+ const subscribe = store.getState().internal.subscribe;
1172
+ // Memoize ref
1173
+ const ref = useMutableCallback(callback);
1174
+ // Subscribe on mount, unsubscribe on unmount
1175
+ useIsomorphicLayoutEffect(() => subscribe(ref, renderPriority, store), [renderPriority, subscribe, store]);
1176
+ return null;
1177
+ }
1178
+
1179
+ /**
1180
+ * Returns a node graph of an object with named nodes & materials.
1181
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usegraph
1182
+ */
1183
+ function useGraph(object) {
1184
+ return React__namespace.useMemo(() => buildGraph(object), [object]);
1185
+ }
1186
+ const memoizedLoaders = new WeakMap();
1187
+ const isConstructor$1 = value => {
1188
+ var _value$prototype;
1189
+ return typeof value === 'function' && (value == null ? void 0 : (_value$prototype = value.prototype) == null ? void 0 : _value$prototype.constructor) === value;
1190
+ };
1191
+ function loadingFn(extensions, onProgress) {
1192
+ return function (Proto, ...input) {
1193
+ let loader;
1194
+
1195
+ // Construct and cache loader if constructor was passed
1196
+ if (isConstructor$1(Proto)) {
1197
+ loader = memoizedLoaders.get(Proto);
1198
+ if (!loader) {
1199
+ loader = new Proto();
1200
+ memoizedLoaders.set(Proto, loader);
1201
+ }
1202
+ } else {
1203
+ loader = Proto;
1204
+ }
1205
+
1206
+ // Apply loader extensions
1207
+ if (extensions) extensions(loader);
1208
+
1209
+ // Go through the urls and load them
1210
+ return Promise.all(input.map(input => new Promise((res, reject) => loader.load(input, data => {
1211
+ if (isObject3D(data == null ? void 0 : data.scene)) Object.assign(data, buildGraph(data.scene));
1212
+ res(data);
1213
+ }, onProgress, error => reject(new Error(`Could not load ${input}: ${error == null ? void 0 : error.message}`))))));
1214
+ };
1215
+ }
1216
+
1217
+ /**
1218
+ * Synchronously loads and caches assets with a three loader.
1219
+ *
1220
+ * Note: this hook's caller must be wrapped with `React.Suspense`
1221
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useloader
1222
+ */
1223
+ function useLoader(loader, input, extensions, onProgress) {
1224
+ // Use suspense to load async assets
1225
+ const keys = Array.isArray(input) ? input : [input];
1226
+ const results = suspendReact.suspend(loadingFn(extensions, onProgress), [loader, ...keys], {
1227
+ equal: is.equ
1228
+ });
1229
+ // Return the object(s)
1230
+ return Array.isArray(input) ? results : results[0];
1231
+ }
1232
+
1233
+ /**
1234
+ * Preloads an asset into cache as a side-effect.
1235
+ */
1236
+ useLoader.preload = function (loader, input, extensions) {
1237
+ const keys = Array.isArray(input) ? input : [input];
1238
+ return suspendReact.preload(loadingFn(extensions), [loader, ...keys]);
1239
+ };
1240
+
1241
+ /**
1242
+ * Removes a loaded asset from cache.
1243
+ */
1244
+ useLoader.clear = function (loader, input) {
1245
+ const keys = Array.isArray(input) ? input : [input];
1246
+ return suspendReact.clear([loader, ...keys]);
1247
+ };
1248
+
1249
+ // TODO: upstream to DefinitelyTyped for React 19
1250
+ // https://github.com/facebook/react/issues/28956
1251
+
1252
+ function createReconciler(config) {
1253
+ const reconciler = Reconciler__default["default"](config);
1254
+ reconciler.injectIntoDevTools({
1255
+ bundleType: typeof process !== 'undefined' && process.env.NODE_ENV !== 'production' ? 1 : 0,
1256
+ rendererPackageName: '@react-three/fiber',
1257
+ version: React__namespace.version
1258
+ });
1259
+ return reconciler;
1260
+ }
1261
+ const NoEventPriority = 0;
1262
+
1263
+ // TODO: handle constructor overloads
1264
+ // https://github.com/pmndrs/react-three-fiber/pull/2931
1265
+ // https://github.com/microsoft/TypeScript/issues/37079
1266
+
1267
+ const catalogue = {};
1268
+ const PREFIX_REGEX = /^three(?=[A-Z])/;
1269
+ const toPascalCase = type => `${type[0].toUpperCase()}${type.slice(1)}`;
1270
+ let i = 0;
1271
+ const isConstructor = object => typeof object === 'function';
1272
+ function extend(objects) {
1273
+ if (isConstructor(objects)) {
1274
+ const Component = `${i++}`;
1275
+ catalogue[Component] = objects;
1276
+ return Component;
1277
+ } else {
1278
+ Object.assign(catalogue, objects);
1279
+ }
1280
+ }
1281
+ function validateInstance(type, props) {
1282
+ // Get target from catalogue
1283
+ const name = `${type[0].toUpperCase()}${type.slice(1)}`;
1284
+ const target = catalogue[name];
1285
+
1286
+ // Validate element target
1287
+ if (type !== 'primitive' && !target) throw new Error(`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`);
1288
+
1289
+ // Validate primitives
1290
+ if (type === 'primitive' && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
1291
+
1292
+ // Throw if an object or literal was passed for args
1293
+ if (props.args !== undefined && !Array.isArray(props.args)) throw new Error('R3F: The args prop must be an array!');
1294
+ }
1295
+ function createInstance(type, props, root) {
1296
+ var _props$object;
1297
+ // Remove three* prefix from elements
1298
+ type = type.replace(PREFIX_REGEX, '');
1299
+ validateInstance(type, props);
1300
+
1301
+ // Regenerate the R3F instance for primitives to simulate a new object
1302
+ if (type === 'primitive' && (_props$object = props.object) != null && _props$object.__r3f) delete props.object.__r3f;
1303
+ return prepare(props.object, root, type, props);
1304
+ }
1305
+ function hideInstance(instance) {
1306
+ if (!instance.isHidden) {
1307
+ var _instance$parent;
1308
+ if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
1309
+ detach(instance.parent, instance);
1310
+ } else if (isObject3D(instance.object)) {
1311
+ instance.object.visible = false;
1312
+ }
1313
+ instance.isHidden = true;
1314
+ invalidateInstance(instance);
1315
+ }
1316
+ }
1317
+ function unhideInstance(instance) {
1318
+ if (instance.isHidden) {
1319
+ var _instance$parent2;
1320
+ if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
1321
+ attach(instance.parent, instance);
1322
+ } else if (isObject3D(instance.object) && instance.props.visible !== false) {
1323
+ instance.object.visible = true;
1549
1324
  }
1550
- });
1325
+ instance.isHidden = false;
1326
+ invalidateInstance(instance);
1327
+ }
1328
+ }
1551
1329
 
1552
- // Invalidate on any change
1553
- rootStore.subscribe(state => invalidate(state));
1330
+ // https://github.com/facebook/react/issues/20271
1331
+ // This will make sure events and attach are only handled once when trees are complete
1332
+ function handleContainerEffects(parent, child, beforeChild) {
1333
+ // Bail if tree isn't mounted or parent is not a container.
1334
+ // This ensures that the tree is finalized and React won't discard results to Suspense
1335
+ const state = child.root.getState();
1336
+ if (!parent.parent && parent.object !== state.scene) return;
1554
1337
 
1555
- // Return root state
1556
- return rootStore;
1557
- };
1338
+ // Create & link object on first run
1339
+ if (!child.object) {
1340
+ var _child$props$object, _child$props$args;
1341
+ // Get target from catalogue
1342
+ const target = catalogue[toPascalCase(child.type)];
1558
1343
 
1559
- /**
1560
- * Exposes an object's {@link Instance}.
1561
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useinstancehandle
1562
- *
1563
- * **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.
1564
- */
1565
- function useInstanceHandle(ref) {
1566
- const instance = React__namespace.useRef(null);
1567
- React__namespace.useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
1568
- return instance;
1344
+ // Create object
1345
+ child.object = (_child$props$object = child.props.object) != null ? _child$props$object : new target(...((_child$props$args = child.props.args) != null ? _child$props$args : []));
1346
+ child.object.__r3f = child;
1347
+ }
1348
+
1349
+ // Set initial props
1350
+ applyProps(child.object, child.props);
1351
+
1352
+ // Append instance
1353
+ if (child.props.attach) {
1354
+ attach(parent, child);
1355
+ } else if (isObject3D(child.object) && isObject3D(parent.object)) {
1356
+ const childIndex = parent.object.children.indexOf(beforeChild == null ? void 0 : beforeChild.object);
1357
+ if (beforeChild && childIndex !== -1) {
1358
+ child.object.parent = parent.object;
1359
+ parent.object.children.splice(childIndex, 0, child.object);
1360
+ child.object.dispatchEvent({
1361
+ type: 'added'
1362
+ });
1363
+ parent.object.dispatchEvent({
1364
+ type: 'childadded',
1365
+ child: child.object
1366
+ });
1367
+ } else {
1368
+ parent.object.add(child.object);
1369
+ }
1370
+ }
1371
+
1372
+ // Link subtree
1373
+ for (const childInstance of child.children) handleContainerEffects(child, childInstance);
1374
+
1375
+ // Tree was updated, request a frame
1376
+ invalidateInstance(child);
1569
1377
  }
1378
+ function appendChild(parent, child) {
1379
+ if (!child) return;
1570
1380
 
1571
- /**
1572
- * Returns the R3F Canvas' Zustand store. Useful for [transient updates](https://github.com/pmndrs/zustand#transient-updates-for-often-occurring-state-changes).
1573
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usestore
1574
- */
1575
- function useStore() {
1576
- const store = React__namespace.useContext(context);
1577
- if (!store) throw new Error('R3F: Hooks can only be used within the Canvas component!');
1578
- return store;
1381
+ // Link instances
1382
+ child.parent = parent;
1383
+ parent.children.push(child);
1384
+
1385
+ // Attach tree once complete
1386
+ handleContainerEffects(parent, child);
1579
1387
  }
1388
+ function insertBefore(parent, child, beforeChild) {
1389
+ if (!child || !beforeChild) return;
1580
1390
 
1581
- /**
1582
- * Accesses R3F's internal state, containing renderer, canvas, scene, etc.
1583
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree
1584
- */
1585
- function useThree(selector = state => state, equalityFn) {
1586
- return useStore()(selector, equalityFn);
1391
+ // Link instances
1392
+ child.parent = parent;
1393
+ const childIndex = parent.children.indexOf(beforeChild);
1394
+ if (childIndex !== -1) parent.children.splice(childIndex, 0, child);else parent.children.push(child);
1395
+
1396
+ // Attach tree once complete
1397
+ handleContainerEffects(parent, child, beforeChild);
1587
1398
  }
1399
+ function disposeOnIdle(object) {
1400
+ if (typeof object.dispose === 'function') {
1401
+ const handleDispose = () => {
1402
+ try {
1403
+ object.dispose();
1404
+ } catch {
1405
+ // no-op
1406
+ }
1407
+ };
1588
1408
 
1589
- /**
1590
- * Executes a callback before render in a shared frame loop.
1591
- * Can order effects with render priority or manually render with a positive priority.
1592
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe
1593
- */
1594
- function useFrame(callback, renderPriority = 0) {
1595
- const store = useStore();
1596
- const subscribe = store.getState().internal.subscribe;
1597
- // Memoize ref
1598
- const ref = useMutableCallback(callback);
1599
- // Subscribe on mount, unsubscribe on unmount
1600
- useIsomorphicLayoutEffect(() => subscribe(ref, renderPriority, store), [renderPriority, subscribe, store]);
1601
- return null;
1409
+ // In a testing environment, cleanup immediately
1410
+ if (typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined') handleDispose();
1411
+ // Otherwise, using a real GPU so schedule cleanup to prevent stalls
1412
+ else scheduler.unstable_scheduleCallback(scheduler.unstable_IdlePriority, handleDispose);
1413
+ }
1602
1414
  }
1415
+ function removeChild(parent, child, dispose) {
1416
+ if (!child) return;
1603
1417
 
1604
- /**
1605
- * Returns a node graph of an object with named nodes & materials.
1606
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usegraph
1607
- */
1608
- function useGraph(object) {
1609
- return React__namespace.useMemo(() => buildGraph(object), [object]);
1418
+ // Unlink instances
1419
+ child.parent = null;
1420
+ const childIndex = parent.children.indexOf(child);
1421
+ if (childIndex !== -1) parent.children.splice(childIndex, 1);
1422
+
1423
+ // Eagerly tear down tree
1424
+ if (child.props.attach) {
1425
+ detach(parent, child);
1426
+ } else if (isObject3D(child.object) && isObject3D(parent.object)) {
1427
+ parent.object.remove(child.object);
1428
+ removeInteractivity(findInitialRoot(child), child.object);
1429
+ }
1430
+
1431
+ // Allow objects to bail out of unmount disposal with dispose={null}
1432
+ const shouldDispose = child.props.dispose !== null && dispose !== false;
1433
+
1434
+ // Recursively remove instance children
1435
+ for (let i = child.children.length - 1; i >= 0; i--) {
1436
+ const node = child.children[i];
1437
+ removeChild(child, node, shouldDispose);
1438
+ }
1439
+ child.children.length = 0;
1440
+
1441
+ // Unlink instance object
1442
+ delete child.object.__r3f;
1443
+
1444
+ // Dispose object whenever the reconciler feels like it.
1445
+ // Never dispose of primitives because their state may be kept outside of React!
1446
+ // In order for an object to be able to dispose it
1447
+ // - has a dispose method
1448
+ // - cannot be a <primitive object={...} />
1449
+ // - cannot be a THREE.Scene, because three has broken its own API
1450
+ if (shouldDispose && child.type !== 'primitive' && child.object.type !== 'Scene') {
1451
+ disposeOnIdle(child.object);
1452
+ }
1453
+
1454
+ // Tree was updated, request a frame for top-level instance
1455
+ if (dispose === undefined) invalidateInstance(child);
1610
1456
  }
1611
- const memoizedLoaders = new WeakMap();
1612
- const isConstructor = value => {
1613
- var _value$prototype;
1614
- return typeof value === 'function' && (value == null ? void 0 : (_value$prototype = value.prototype) == null ? void 0 : _value$prototype.constructor) === value;
1615
- };
1616
- function loadingFn(extensions, onProgress) {
1617
- return async function (Proto, ...input) {
1618
- let loader;
1457
+ function setFiberRef(fiber, publicInstance) {
1458
+ for (const _fiber of [fiber, fiber.alternate]) {
1459
+ if (_fiber !== null) {
1460
+ if (typeof _fiber.ref === 'function') {
1461
+ _fiber.refCleanup == null ? void 0 : _fiber.refCleanup();
1462
+ const cleanup = _fiber.ref(publicInstance);
1463
+ if (typeof cleanup === 'function') _fiber.refCleanup = cleanup;
1464
+ } else if (_fiber.ref) {
1465
+ _fiber.ref.current = publicInstance;
1466
+ }
1467
+ }
1468
+ }
1469
+ }
1470
+ const reconstructed = [];
1471
+ function swapInstances() {
1472
+ // Detach instance
1473
+ for (const [instance] of reconstructed) {
1474
+ const parent = instance.parent;
1475
+ if (parent) {
1476
+ if (instance.props.attach) {
1477
+ detach(parent, instance);
1478
+ } else if (isObject3D(instance.object) && isObject3D(parent.object)) {
1479
+ parent.object.remove(instance.object);
1480
+ }
1481
+ for (const child of instance.children) {
1482
+ if (child.props.attach) {
1483
+ detach(instance, child);
1484
+ } else if (isObject3D(child.object) && isObject3D(instance.object)) {
1485
+ instance.object.remove(child.object);
1486
+ }
1487
+ }
1488
+ }
1619
1489
 
1620
- // Construct and cache loader if constructor was passed
1621
- if (isConstructor(Proto)) {
1622
- loader = memoizedLoaders.get(Proto);
1623
- if (!loader) {
1624
- loader = new Proto();
1625
- memoizedLoaders.set(Proto, loader);
1490
+ // If the old instance is hidden, we need to unhide it.
1491
+ // React assumes it can discard instances since they're pure for DOM.
1492
+ // This isn't true for us since our lifetimes are impure and longliving.
1493
+ // So, we manually check if an instance was hidden and unhide it.
1494
+ if (instance.isHidden) unhideInstance(instance);
1495
+
1496
+ // Dispose of old object if able
1497
+ if (instance.object.__r3f) delete instance.object.__r3f;
1498
+ if (instance.type !== 'primitive') disposeOnIdle(instance.object);
1499
+ }
1500
+
1501
+ // Update instance
1502
+ for (const [instance, props, fiber] of reconstructed) {
1503
+ instance.props = props;
1504
+ const parent = instance.parent;
1505
+ if (parent) {
1506
+ var _instance$props$objec, _instance$props$args;
1507
+ // Get target from catalogue
1508
+ const target = catalogue[toPascalCase(instance.type)];
1509
+
1510
+ // Create object
1511
+ instance.object = (_instance$props$objec = instance.props.object) != null ? _instance$props$objec : new target(...((_instance$props$args = instance.props.args) != null ? _instance$props$args : []));
1512
+ instance.object.__r3f = instance;
1513
+ setFiberRef(fiber, instance.object);
1514
+
1515
+ // Set initial props
1516
+ applyProps(instance.object, instance.props);
1517
+ if (instance.props.attach) {
1518
+ attach(parent, instance);
1519
+ } else if (isObject3D(instance.object) && isObject3D(parent.object)) {
1520
+ parent.object.add(instance.object);
1626
1521
  }
1627
- } else {
1628
- loader = Proto;
1522
+ for (const child of instance.children) {
1523
+ if (child.props.attach) {
1524
+ attach(instance, child);
1525
+ } else if (isObject3D(child.object) && isObject3D(instance.object)) {
1526
+ instance.object.add(child.object);
1527
+ }
1528
+ }
1529
+
1530
+ // Tree was updated, request a frame
1531
+ invalidateInstance(instance);
1629
1532
  }
1533
+ }
1534
+ reconstructed.length = 0;
1535
+ }
1630
1536
 
1631
- // Apply loader extensions
1632
- if (extensions) extensions(loader);
1537
+ // Don't handle text instances, make it no-op
1538
+ const handleTextInstance = () => {};
1539
+ const NO_CONTEXT = {};
1540
+ let currentUpdatePriority = NoEventPriority;
1633
1541
 
1634
- // Go through the urls and load them
1635
- 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}`))))));
1636
- };
1637
- }
1542
+ // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberFlags.js
1543
+ const NoFlags = 0;
1544
+ const Update = 4;
1545
+ const reconciler = /* @__PURE__ */createReconciler({
1546
+ isPrimaryRenderer: false,
1547
+ warnsIfNotActing: false,
1548
+ supportsMutation: true,
1549
+ supportsPersistence: false,
1550
+ supportsHydration: false,
1551
+ createInstance,
1552
+ removeChild,
1553
+ appendChild,
1554
+ appendInitialChild: appendChild,
1555
+ insertBefore,
1556
+ appendChildToContainer(container, child) {
1557
+ const scene = container.getState().scene.__r3f;
1558
+ if (!child || !scene) return;
1559
+ appendChild(scene, child);
1560
+ },
1561
+ removeChildFromContainer(container, child) {
1562
+ const scene = container.getState().scene.__r3f;
1563
+ if (!child || !scene) return;
1564
+ removeChild(scene, child);
1565
+ },
1566
+ insertInContainerBefore(container, child, beforeChild) {
1567
+ const scene = container.getState().scene.__r3f;
1568
+ if (!child || !beforeChild || !scene) return;
1569
+ insertBefore(scene, child, beforeChild);
1570
+ },
1571
+ getRootHostContext: () => NO_CONTEXT,
1572
+ getChildHostContext: () => NO_CONTEXT,
1573
+ commitUpdate(instance, type, oldProps, newProps, fiber) {
1574
+ var _newProps$args, _oldProps$args, _newProps$args2;
1575
+ validateInstance(type, newProps);
1576
+ let reconstruct = false;
1638
1577
 
1639
- /**
1640
- * Synchronously loads and caches assets with a three loader.
1641
- *
1642
- * Note: this hook's caller must be wrapped with `React.Suspense`
1643
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useloader
1644
- */
1645
- function useLoader(loader, input, extensions, onProgress) {
1646
- // Use suspense to load async assets
1647
- const keys = Array.isArray(input) ? input : [input];
1648
- const results = suspendReact.suspend(loadingFn(extensions, onProgress), [loader, ...keys], {
1649
- equal: is.equ
1650
- });
1651
- // Return the object(s)
1652
- return Array.isArray(input) ? results : results[0];
1653
- }
1578
+ // Reconstruct primitives if object prop changes
1579
+ if (instance.type === 'primitive' && oldProps.object !== newProps.object) reconstruct = true;
1580
+ // Reconstruct instance if args were added or removed
1581
+ else if (((_newProps$args = newProps.args) == null ? void 0 : _newProps$args.length) !== ((_oldProps$args = oldProps.args) == null ? void 0 : _oldProps$args.length)) reconstruct = true;
1582
+ // Reconstruct instance if args were changed
1583
+ else if ((_newProps$args2 = newProps.args) != null && _newProps$args2.some((value, index) => {
1584
+ var _oldProps$args2;
1585
+ return value !== ((_oldProps$args2 = oldProps.args) == null ? void 0 : _oldProps$args2[index]);
1586
+ })) reconstruct = true;
1654
1587
 
1655
- /**
1656
- * Preloads an asset into cache as a side-effect.
1657
- */
1658
- useLoader.preload = function (loader, input, extensions) {
1659
- const keys = Array.isArray(input) ? input : [input];
1660
- return suspendReact.preload(loadingFn(extensions), [loader, ...keys]);
1661
- };
1588
+ // Reconstruct when args or <primitive object={...} have changes
1589
+ if (reconstruct) {
1590
+ reconstructed.push([instance, {
1591
+ ...newProps
1592
+ }, fiber]);
1593
+ } else {
1594
+ // Create a diff-set, flag if there are any changes
1595
+ const changedProps = diffProps(instance, newProps);
1596
+ if (Object.keys(changedProps).length) {
1597
+ Object.assign(instance.props, changedProps);
1598
+ applyProps(instance.object, changedProps);
1599
+ }
1600
+ }
1662
1601
 
1663
- /**
1664
- * Removes a loaded asset from cache.
1665
- */
1666
- useLoader.clear = function (loader, input) {
1667
- const keys = Array.isArray(input) ? input : [input];
1668
- return suspendReact.clear([loader, ...keys]);
1669
- };
1602
+ // Flush reconstructed siblings when we hit the last updated child in a sequence
1603
+ const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
1604
+ if (isTailSibling) swapInstances();
1605
+ },
1606
+ finalizeInitialChildren: () => false,
1607
+ commitMount() {},
1608
+ getPublicInstance: instance => instance == null ? void 0 : instance.object,
1609
+ prepareForCommit: () => null,
1610
+ preparePortalMount: container => prepare(container.getState().scene, container, '', {}),
1611
+ resetAfterCommit: () => {},
1612
+ shouldSetTextContent: () => false,
1613
+ clearContainer: () => false,
1614
+ hideInstance,
1615
+ unhideInstance,
1616
+ createTextInstance: handleTextInstance,
1617
+ hideTextInstance: handleTextInstance,
1618
+ unhideTextInstance: handleTextInstance,
1619
+ scheduleTimeout: typeof setTimeout === 'function' ? setTimeout : undefined,
1620
+ cancelTimeout: typeof clearTimeout === 'function' ? clearTimeout : undefined,
1621
+ noTimeout: -1,
1622
+ getInstanceFromNode: () => null,
1623
+ beforeActiveInstanceBlur() {},
1624
+ afterActiveInstanceBlur() {},
1625
+ detachDeletedInstance() {},
1626
+ prepareScopeUpdate() {},
1627
+ getInstanceFromScope: () => null,
1628
+ shouldAttemptEagerTransition: () => false,
1629
+ trackSchedulerEvent: () => {},
1630
+ resolveEventType: () => null,
1631
+ resolveEventTimeStamp: () => -1.1,
1632
+ requestPostPaintCallback() {},
1633
+ maySuspendCommit: () => false,
1634
+ preloadInstance: () => true,
1635
+ // true indicates already loaded
1636
+ startSuspendingCommit() {},
1637
+ suspendInstance() {},
1638
+ waitForCommitToBeReady: () => null,
1639
+ NotPendingTransition: null,
1640
+ HostTransitionContext: /* @__PURE__ */React__namespace.createContext(null),
1641
+ setCurrentUpdatePriority(newPriority) {
1642
+ currentUpdatePriority = newPriority;
1643
+ },
1644
+ getCurrentUpdatePriority() {
1645
+ return currentUpdatePriority;
1646
+ },
1647
+ resolveUpdatePriority() {
1648
+ var _window$event;
1649
+ if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
1650
+ switch (typeof window !== 'undefined' && ((_window$event = window.event) == null ? void 0 : _window$event.type)) {
1651
+ case 'click':
1652
+ case 'contextmenu':
1653
+ case 'dblclick':
1654
+ case 'pointercancel':
1655
+ case 'pointerdown':
1656
+ case 'pointerup':
1657
+ return constants.DiscreteEventPriority;
1658
+ case 'pointermove':
1659
+ case 'pointerout':
1660
+ case 'pointerover':
1661
+ case 'pointerenter':
1662
+ case 'pointerleave':
1663
+ case 'wheel':
1664
+ return constants.ContinuousEventPriority;
1665
+ default:
1666
+ return constants.DefaultEventPriority;
1667
+ }
1668
+ },
1669
+ resetFormInstance() {}
1670
+ });
1670
1671
 
1671
1672
  const _roots = new Map();
1672
1673
  const shallowLoose = {
1673
1674
  objects: 'shallow',
1674
1675
  strict: false
1675
1676
  };
1676
- const createRendererInstance = (gl, canvas) => {
1677
- const customRenderer = typeof gl === 'function' ? gl(canvas) : gl;
1678
- if (isRenderer(customRenderer)) return customRenderer;
1679
- return new THREE__namespace.WebGLRenderer({
1680
- powerPreference: 'high-performance',
1681
- canvas: canvas,
1682
- antialias: true,
1683
- alpha: true,
1684
- ...gl
1685
- });
1686
- };
1687
1677
  function computeInitialSize(canvas, size) {
1688
- if (!size && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1678
+ if (!size && typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1689
1679
  const {
1690
1680
  width,
1691
1681
  height,
@@ -1761,10 +1751,13 @@ function createRoot(canvas) {
1761
1751
 
1762
1752
  // Locals
1763
1753
  let onCreated;
1764
- let configured = false;
1765
1754
  let lastCamera;
1755
+ let configured = false;
1756
+ let pending = null;
1766
1757
  return {
1767
- configure(props = {}) {
1758
+ async configure(props = {}) {
1759
+ let resolve;
1760
+ pending = new Promise(_resolve => resolve = _resolve);
1768
1761
  let {
1769
1762
  gl: glConfig,
1770
1763
  size: propsSize,
@@ -1787,9 +1780,26 @@ function createRoot(canvas) {
1787
1780
 
1788
1781
  // Set up renderer (one time only!)
1789
1782
  let gl = state.gl;
1790
- if (!state.gl) state.set({
1791
- gl: gl = createRendererInstance(glConfig, canvas)
1792
- });
1783
+ if (!state.gl) {
1784
+ const defaultProps = {
1785
+ canvas: canvas,
1786
+ powerPreference: 'high-performance',
1787
+ antialias: true,
1788
+ alpha: true
1789
+ };
1790
+ const customRenderer = typeof glConfig === 'function' ? await glConfig(defaultProps) : glConfig;
1791
+ if (isRenderer(customRenderer)) {
1792
+ gl = customRenderer;
1793
+ } else {
1794
+ gl = new THREE__namespace.WebGLRenderer({
1795
+ ...defaultProps,
1796
+ ...glConfig
1797
+ });
1798
+ }
1799
+ state.set({
1800
+ gl
1801
+ });
1802
+ }
1793
1803
 
1794
1804
  // Set up raycaster (one time only!)
1795
1805
  let raycaster = state.raycaster;
@@ -1815,7 +1825,7 @@ function createRoot(canvas) {
1815
1825
  // Create default camera, don't overwrite any user-set state
1816
1826
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
1817
1827
  lastCamera = cameraOptions;
1818
- const isCamera = cameraOptions instanceof THREE__namespace.Camera;
1828
+ const isCamera = cameraOptions == null ? void 0 : cameraOptions.isCamera;
1819
1829
  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);
1820
1830
  if (!isCamera) {
1821
1831
  camera.position.z = 5;
@@ -1845,7 +1855,7 @@ function createRoot(canvas) {
1845
1855
  // Set up scene (one time only!)
1846
1856
  if (!state.scene) {
1847
1857
  let scene;
1848
- if (sceneOptions instanceof THREE__namespace.Scene) {
1858
+ if (sceneOptions != null && sceneOptions.isScene) {
1849
1859
  scene = sceneOptions;
1850
1860
  prepare(scene, store, '', {});
1851
1861
  } else {
@@ -1858,8 +1868,34 @@ function createRoot(canvas) {
1858
1868
  });
1859
1869
  }
1860
1870
 
1871
+ // Store events internally
1872
+ if (events && !state.events.handlers) state.set({
1873
+ events: events(store)
1874
+ });
1875
+ // Check size, allow it to take on container bounds initially
1876
+ const size = computeInitialSize(canvas, propsSize);
1877
+ if (!is.equ(size, state.size, shallowLoose)) {
1878
+ state.setSize(size.width, size.height, size.top, size.left);
1879
+ }
1880
+ // Check pixelratio
1881
+ if (dpr && state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr);
1882
+ // Check frameloop
1883
+ if (state.frameloop !== frameloop) state.setFrameloop(frameloop);
1884
+ // Check pointer missed
1885
+ if (!state.onPointerMissed) state.set({
1886
+ onPointerMissed
1887
+ });
1888
+ // Check performance
1889
+ if (performance && !is.equ(performance, state.performance, shallowLoose)) state.set(state => ({
1890
+ performance: {
1891
+ ...state.performance,
1892
+ ...performance
1893
+ }
1894
+ }));
1895
+
1861
1896
  // Set up XR (one time only!)
1862
1897
  if (!state.xr) {
1898
+ var _gl$xr;
1863
1899
  // Handle frame behavior in WebXR
1864
1900
  const handleXRFrame = (timestamp, frame) => {
1865
1901
  const state = store.getState();
@@ -1890,7 +1926,7 @@ function createRoot(canvas) {
1890
1926
  };
1891
1927
 
1892
1928
  // Subscribe to WebXR session events
1893
- if (gl.xr) xr.connect();
1929
+ if (typeof ((_gl$xr = gl.xr) == null ? void 0 : _gl$xr.addEventListener) === 'function') xr.connect();
1894
1930
  state.set({
1895
1931
  xr
1896
1932
  });
@@ -1917,22 +1953,12 @@ function createRoot(canvas) {
1917
1953
  }
1918
1954
  if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type) gl.shadowMap.needsUpdate = true;
1919
1955
  }
1920
-
1921
- // Safely set color management if available.
1922
- // Avoid accessing THREE.ColorManagement to play nice with older versions
1923
- const ColorManagement = getColorManagement();
1924
- if (ColorManagement) {
1925
- if ('enabled' in ColorManagement) ColorManagement.enabled = !legacy;else if ('legacyMode' in ColorManagement) ColorManagement.legacyMode = legacy;
1926
- }
1956
+ THREE__namespace.ColorManagement.enabled = !legacy;
1927
1957
 
1928
1958
  // Set color space and tonemapping preferences
1929
1959
  if (!configured) {
1930
- const LinearEncoding = 3000;
1931
- const sRGBEncoding = 3001;
1932
- applyProps(gl, {
1933
- outputEncoding: linear ? LinearEncoding : sRGBEncoding,
1934
- toneMapping: flat ? THREE__namespace.NoToneMapping : THREE__namespace.ACESFilmicToneMapping
1935
- });
1960
+ gl.outputColorSpace = linear ? THREE__namespace.LinearSRGBColorSpace : THREE__namespace.SRGBColorSpace;
1961
+ gl.toneMapping = flat ? THREE__namespace.NoToneMapping : THREE__namespace.ACESFilmicToneMapping;
1936
1962
  }
1937
1963
 
1938
1964
  // Update color management state
@@ -1948,45 +1974,24 @@ function createRoot(canvas) {
1948
1974
 
1949
1975
  // Set gl props
1950
1976
  if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, gl, shallowLoose)) applyProps(gl, glConfig);
1951
- // Store events internally
1952
- if (events && !state.events.handlers) state.set({
1953
- events: events(store)
1954
- });
1955
- // Check size, allow it to take on container bounds initially
1956
- const size = computeInitialSize(canvas, propsSize);
1957
- if (!is.equ(size, state.size, shallowLoose)) {
1958
- state.setSize(size.width, size.height, size.top, size.left);
1959
- }
1960
- // Check pixelratio
1961
- if (dpr && state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr);
1962
- // Check frameloop
1963
- if (state.frameloop !== frameloop) state.setFrameloop(frameloop);
1964
- // Check pointer missed
1965
- if (!state.onPointerMissed) state.set({
1966
- onPointerMissed
1967
- });
1968
- // Check performance
1969
- if (performance && !is.equ(performance, state.performance, shallowLoose)) state.set(state => ({
1970
- performance: {
1971
- ...state.performance,
1972
- ...performance
1973
- }
1974
- }));
1975
1977
 
1976
1978
  // Set locals
1977
1979
  onCreated = onCreatedCallback;
1978
1980
  configured = true;
1981
+ resolve();
1979
1982
  return this;
1980
1983
  },
1981
1984
  render(children) {
1982
1985
  // The root has to be configured before it can be rendered
1983
- if (!configured) this.configure();
1984
- reconciler.updateContainer( /*#__PURE__*/jsxRuntime.jsx(Provider, {
1985
- store: store,
1986
- children: children,
1987
- onCreated: onCreated,
1988
- rootElement: canvas
1989
- }), fiber, null, () => undefined);
1986
+ if (!configured && !pending) this.configure();
1987
+ pending.then(() => {
1988
+ reconciler.updateContainer( /*#__PURE__*/jsxRuntime.jsx(Provider, {
1989
+ store: store,
1990
+ children: children,
1991
+ onCreated: onCreated,
1992
+ rootElement: canvas
1993
+ }), fiber, null, () => undefined);
1994
+ });
1990
1995
  return store;
1991
1996
  },
1992
1997
  unmount() {
@@ -1994,12 +1999,6 @@ function createRoot(canvas) {
1994
1999
  }
1995
2000
  };
1996
2001
  }
1997
- function render(children, canvas, config) {
1998
- console.warn('R3F.render is no longer supported in React 18. Use createRoot instead!');
1999
- const root = createRoot(canvas);
2000
- root.configure(config);
2001
- return root.render(children);
2002
- }
2003
2002
  function Provider({
2004
2003
  store,
2005
2004
  children,
@@ -2137,18 +2136,17 @@ function Portal({
2137
2136
  return store;
2138
2137
  // eslint-disable-next-line react-hooks/exhaustive-deps
2139
2138
  }, [previousRoot, container]);
2140
- return /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, {
2141
- children: reconciler.createPortal( /*#__PURE__*/jsxRuntime.jsx(context.Provider, {
2142
- value: usePortalStore,
2143
- children: children
2144
- }), usePortalStore, null)
2145
- });
2139
+ return (
2140
+ /*#__PURE__*/
2141
+ // @ts-ignore, reconciler types are not maintained
2142
+ jsxRuntime.jsx(jsxRuntime.Fragment, {
2143
+ children: reconciler.createPortal( /*#__PURE__*/jsxRuntime.jsx(context.Provider, {
2144
+ value: usePortalStore,
2145
+ children: children
2146
+ }), usePortalStore, null)
2147
+ })
2148
+ );
2146
2149
  }
2147
- reconciler.injectIntoDevTools({
2148
- bundleType: 0 ,
2149
- rendererPackageName: '@react-three/fiber',
2150
- version: React__namespace.version
2151
- });
2152
2150
 
2153
2151
  function createSubs(callback, subs) {
2154
2152
  const sub = {
@@ -2247,7 +2245,7 @@ function loop(timestamp) {
2247
2245
  repeat += update(timestamp, state);
2248
2246
  }
2249
2247
  }
2250
- useFrameInProgress = true;
2248
+ useFrameInProgress = false;
2251
2249
 
2252
2250
  // Run after-effects
2253
2251
  flushGlobalEffects('after', timestamp);
@@ -2410,7 +2408,6 @@ exports.getRootState = getRootState;
2410
2408
  exports.invalidate = invalidate;
2411
2409
  exports.isRef = isRef;
2412
2410
  exports.reconciler = reconciler;
2413
- exports.render = render;
2414
2411
  exports.threeTypes = threeTypes;
2415
2412
  exports.unmountComponentAtNode = unmountComponentAtNode;
2416
2413
  exports.useBridge = useBridge;