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

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +962 -522
  2. package/dist/declarations/src/core/events.d.ts +91 -69
  3. package/dist/declarations/src/core/hooks.d.ts +53 -27
  4. package/dist/declarations/src/core/index.d.ts +15 -60
  5. package/dist/declarations/src/core/loop.d.ts +31 -15
  6. package/dist/declarations/src/core/reconciler.d.ts +43 -0
  7. package/dist/declarations/src/core/renderer.d.ts +85 -40
  8. package/dist/declarations/src/core/stages.d.ts +64 -59
  9. package/dist/declarations/src/core/store.d.ts +147 -109
  10. package/dist/declarations/src/core/utils.d.ts +128 -80
  11. package/dist/declarations/src/index.d.ts +6 -10
  12. package/dist/declarations/src/native/Canvas.d.ts +14 -8
  13. package/dist/declarations/src/native/events.d.ts +4 -4
  14. package/dist/declarations/src/native.d.ts +6 -8
  15. package/dist/declarations/src/three-types.d.ts +56 -47
  16. package/dist/declarations/src/web/Canvas.d.ts +24 -11
  17. package/dist/declarations/src/web/events.d.ts +4 -4
  18. package/dist/{index-5bd4d3cf.cjs.dev.js → loop-0698c205.cjs.dev.js} +1469 -1268
  19. package/dist/{index-8128f248.cjs.prod.js → loop-a0ef8208.cjs.prod.js} +1469 -1268
  20. package/dist/{index-47b7622a.esm.js → loop-b2aca207.esm.js} +1466 -1268
  21. package/dist/react-three-fiber.cjs.d.ts +1 -0
  22. package/dist/react-three-fiber.cjs.dev.js +126 -115
  23. package/dist/react-three-fiber.cjs.prod.js +126 -115
  24. package/dist/react-three-fiber.esm.js +92 -84
  25. package/native/dist/react-three-fiber-native.cjs.d.ts +1 -0
  26. package/native/dist/react-three-fiber-native.cjs.dev.js +278 -211
  27. package/native/dist/react-three-fiber-native.cjs.prod.js +278 -211
  28. package/native/dist/react-three-fiber-native.esm.js +242 -180
  29. package/native/package.json +5 -5
  30. package/package.json +18 -12
  31. package/readme.md +253 -202
  32. package/dist/declarations/src/native/polyfills.d.ts +0 -1
@@ -1,30 +1,448 @@
1
1
  import * as THREE from 'three';
2
2
  import * as React from 'react';
3
- import { DefaultEventPriority, ContinuousEventPriority, DiscreteEventPriority, ConcurrentRoot } from 'react-reconciler/constants';
4
- import create from 'zustand';
3
+ import { NoEventPriority, DefaultEventPriority, ContinuousEventPriority, DiscreteEventPriority, ConcurrentRoot } from 'react-reconciler/constants';
4
+ import { create } from 'zustand';
5
+ import { useFiber, useContextBridge, traverseFiber } from 'its-fine';
6
+ import _extends from '@babel/runtime/helpers/esm/extends';
5
7
  import Reconciler from 'react-reconciler';
6
8
  import { unstable_scheduleCallback, unstable_IdlePriority } from 'scheduler';
7
9
  import { suspend, preload, clear } from 'suspend-react';
8
10
 
11
+ var threeTypes = /*#__PURE__*/Object.freeze({
12
+ __proto__: null
13
+ });
14
+
15
+ // TODO: handle constructor overloads
16
+ // https://github.com/pmndrs/react-three-fiber/pull/2931
17
+ // https://github.com/microsoft/TypeScript/issues/37079
18
+
19
+ const catalogue = {};
20
+ let i = 0;
21
+ const extend = objects => {
22
+ if (typeof objects === 'function') {
23
+ const Component = `${i++}`;
24
+ catalogue[Component] = objects;
25
+
26
+ // Returns a component whose name will be inferred in devtools
27
+ // @ts-ignore
28
+ return /*#__PURE__*/React.forwardRef({
29
+ [objects.name]: (props, ref) => /*#__PURE__*/React.createElement(Component, _extends({}, props, {
30
+ ref: ref
31
+ }))
32
+ }[objects.name]);
33
+ } else {
34
+ return void Object.assign(catalogue, objects);
35
+ }
36
+ };
37
+ function createInstance(type, props, root) {
38
+ // Get target from catalogue
39
+ const name = `${type[0].toUpperCase()}${type.slice(1)}`;
40
+ const target = catalogue[name];
41
+
42
+ // Validate element target
43
+ 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`);
44
+
45
+ // Validate primitives
46
+ if (type === 'primitive' && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
47
+
48
+ // Throw if an object or literal was passed for args
49
+ if (props.args !== undefined && !Array.isArray(props.args)) throw new Error('R3F: The args prop must be an array!');
50
+
51
+ // Create instance
52
+ const instance = prepare(props.object, root, type, props);
53
+ return instance;
54
+ }
55
+
56
+ // https://github.com/facebook/react/issues/20271
57
+ // This will make sure events and attach are only handled once when trees are complete
58
+ function handleContainerEffects(parent, child, beforeChild) {
59
+ // Bail if tree isn't mounted or parent is not a container.
60
+ // This ensures that the tree is finalized and React won't discard results to Suspense
61
+ const state = child.root.getState();
62
+ if (!parent.parent && parent.object !== state.scene) return;
63
+
64
+ // Create & link object on first run
65
+ if (!child.object) {
66
+ var _child$props$object, _child$props$args;
67
+ // Get target from catalogue
68
+ const name = `${child.type[0].toUpperCase()}${child.type.slice(1)}`;
69
+ const target = catalogue[name];
70
+
71
+ // Create object
72
+ child.object = (_child$props$object = child.props.object) != null ? _child$props$object : new target(...((_child$props$args = child.props.args) != null ? _child$props$args : []));
73
+ child.object.__r3f = child;
74
+
75
+ // Set initial props
76
+ applyProps(child.object, child.props);
77
+ }
78
+
79
+ // Append instance
80
+ if (child.props.attach) {
81
+ attach(parent, child);
82
+ } else if (isObject3D(child.object) && isObject3D(parent.object)) {
83
+ const childIndex = parent.object.children.indexOf(beforeChild == null ? void 0 : beforeChild.object);
84
+ if (beforeChild && childIndex !== -1) {
85
+ child.object.parent = parent.object;
86
+ parent.object.children.splice(childIndex, 0, child.object);
87
+ child.object.dispatchEvent({
88
+ type: 'added'
89
+ });
90
+ parent.object.dispatchEvent({
91
+ type: 'childadded',
92
+ child: child.object
93
+ });
94
+ } else {
95
+ parent.object.add(child.object);
96
+ }
97
+ }
98
+
99
+ // Link subtree
100
+ for (const childInstance of child.children) handleContainerEffects(child, childInstance);
101
+
102
+ // Tree was updated, request a frame
103
+ invalidateInstance(child);
104
+ }
105
+ function appendChild(parent, child) {
106
+ if (!child) return;
107
+
108
+ // Link instances
109
+ child.parent = parent;
110
+ parent.children.push(child);
111
+
112
+ // Attach tree once complete
113
+ handleContainerEffects(parent, child);
114
+ }
115
+ function insertBefore(parent, child, beforeChild) {
116
+ if (!child || !beforeChild) return;
117
+
118
+ // Link instances
119
+ child.parent = parent;
120
+ const childIndex = parent.children.indexOf(beforeChild);
121
+ if (childIndex !== -1) parent.children.splice(childIndex, 0, child);else parent.children.push(child);
122
+
123
+ // Attach tree once complete
124
+ handleContainerEffects(parent, child, beforeChild);
125
+ }
126
+ function removeChild(parent, child, dispose, recursive) {
127
+ if (!child) return;
128
+
129
+ // Unlink instances
130
+ child.parent = null;
131
+ if (recursive === undefined) {
132
+ const childIndex = parent.children.indexOf(child);
133
+ if (childIndex !== -1) parent.children.splice(childIndex, 1);
134
+ }
135
+
136
+ // Eagerly tear down tree
137
+ if (child.props.attach) {
138
+ detach(parent, child);
139
+ } else if (isObject3D(child.object) && isObject3D(parent.object)) {
140
+ parent.object.remove(child.object);
141
+ removeInteractivity(findInitialRoot(child), child.object);
142
+ }
143
+
144
+ // Allow objects to bail out of unmount disposal with dispose={null}
145
+ const shouldDispose = child.props.dispose !== null && dispose !== false;
146
+
147
+ // Recursively remove instance children
148
+ if (recursive !== false) {
149
+ for (const node of child.children) removeChild(child, node, shouldDispose, true);
150
+ child.children.length = 0;
151
+ }
152
+
153
+ // Unlink instance object
154
+ delete child.object.__r3f;
155
+
156
+ // Dispose object whenever the reconciler feels like it.
157
+ // Never dispose of primitives because their state may be kept outside of React!
158
+ // In order for an object to be able to dispose it
159
+ // - has a dispose method
160
+ // - cannot be a <primitive object={...} />
161
+ // - cannot be a THREE.Scene, because three has broken its own API
162
+ if (shouldDispose && child.type !== 'primitive' && child.object.type !== 'Scene') {
163
+ if (typeof child.object.dispose === 'function') {
164
+ const dispose = child.object.dispose.bind(child.object);
165
+ const handleDispose = () => {
166
+ try {
167
+ dispose();
168
+ } catch (e) {
169
+ // no-op
170
+ }
171
+ };
172
+
173
+ // In a testing environment, cleanup immediately
174
+ if (typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined') handleDispose();
175
+ // Otherwise, using a real GPU so schedule cleanup to prevent stalls
176
+ else unstable_scheduleCallback(unstable_IdlePriority, handleDispose);
177
+ }
178
+ }
179
+
180
+ // Tree was updated, request a frame for top-level instance
181
+ if (dispose === undefined) invalidateInstance(child);
182
+ }
183
+ function setFiberInstance(fiber, instance) {
184
+ if (fiber !== null) {
185
+ fiber.stateNode = instance;
186
+ if (typeof fiber.ref === 'function') fiber.ref(instance.object);else if (fiber.ref) fiber.ref.current = instance.object;
187
+ }
188
+ }
189
+ function switchInstance(oldInstance, type, props, fiber) {
190
+ // Create a new instance
191
+ const newInstance = createInstance(type, props, oldInstance.root);
192
+
193
+ // Move children to new instance
194
+ for (const child of oldInstance.children) {
195
+ removeChild(oldInstance, child, false, false);
196
+ appendChild(newInstance, child);
197
+ }
198
+ oldInstance.children.length = 0;
199
+
200
+ // Link up new instance
201
+ const parent = oldInstance.parent;
202
+ if (parent) {
203
+ // Manually handle replace https://github.com/pmndrs/react-three-fiber/pull/2680
204
+
205
+ newInstance.autoRemovedBeforeAppend = !!newInstance.parent;
206
+ if (!oldInstance.autoRemovedBeforeAppend) removeChild(parent, oldInstance);
207
+ appendChild(parent, newInstance);
208
+
209
+ // if (!oldInstance.autoRemovedBeforeAppend) {
210
+ // insertBefore(parent, newInstance, oldInstance)
211
+ // removeChild(parent, oldInstance)
212
+ // } else {
213
+ // appendChild(parent, newInstance)
214
+ // }
215
+ }
216
+
217
+ // This evil hack switches the react-internal fiber instance
218
+ // https://github.com/facebook/react/issues/14983
219
+ // TODO: investigate scheduling key prop change instead of switchInstance entirely
220
+ // https://github.com/facebook/react/pull/15021#issuecomment-480185369
221
+ setFiberInstance(fiber, newInstance);
222
+ setFiberInstance(fiber.alternate, newInstance);
223
+
224
+ // Tree was updated, request a frame
225
+ invalidateInstance(newInstance);
226
+ return newInstance;
227
+ }
228
+
229
+ // Don't handle text instances, warn on undefined behavior
230
+ const handleTextInstance = () => console.warn('R3F: Text is not allowed in JSX! This could be stray whitespace or characters.');
231
+ const NO_CONTEXT = {};
232
+ let currentUpdatePriority = NoEventPriority;
233
+
234
+ // Effectively removed to diff in commit phase
235
+ // https://github.com/facebook/react/pull/27409
236
+ function prepareUpdate(instance, _type, oldProps, newProps) {
237
+ var _newProps$args, _oldProps$args, _newProps$args2;
238
+ // Reconstruct primitives if object prop changes
239
+ if (instance.type === 'primitive' && oldProps.object !== newProps.object) return [true];
240
+
241
+ // Throw if an object or literal was passed for args
242
+ if (newProps.args !== undefined && !Array.isArray(newProps.args)) throw new Error('R3F: The args prop must be an array!');
243
+
244
+ // Reconstruct instance if args change
245
+ if (((_newProps$args = newProps.args) == null ? void 0 : _newProps$args.length) !== ((_oldProps$args = oldProps.args) == null ? void 0 : _oldProps$args.length)) return [true];
246
+ if ((_newProps$args2 = newProps.args) != null && _newProps$args2.some((value, index) => {
247
+ var _oldProps$args2;
248
+ return value !== ((_oldProps$args2 = oldProps.args) == null ? void 0 : _oldProps$args2[index]);
249
+ })) return [true];
250
+
251
+ // Create a diff-set, flag if there are any changes
252
+ const changedProps = diffProps(instance, newProps, true);
253
+ if (Object.keys(changedProps).length) return [false, changedProps];
254
+
255
+ // Otherwise do not touch the instance
256
+ return null;
257
+ }
258
+ const reconciler = Reconciler({
259
+ isPrimaryRenderer: false,
260
+ warnsIfNotActing: false,
261
+ supportsMutation: true,
262
+ supportsPersistence: false,
263
+ supportsHydration: false,
264
+ createInstance,
265
+ removeChild,
266
+ appendChild,
267
+ appendInitialChild: appendChild,
268
+ insertBefore,
269
+ appendChildToContainer(container, child) {
270
+ const scene = container.getState().scene.__r3f;
271
+ if (!child || !scene) return;
272
+ appendChild(scene, child);
273
+ },
274
+ removeChildFromContainer(container, child) {
275
+ const scene = container.getState().scene.__r3f;
276
+ if (!child || !scene) return;
277
+ removeChild(scene, child);
278
+ },
279
+ insertInContainerBefore(container, child, beforeChild) {
280
+ const scene = container.getState().scene.__r3f;
281
+ if (!child || !beforeChild || !scene) return;
282
+ insertBefore(scene, child, beforeChild);
283
+ },
284
+ getRootHostContext: () => NO_CONTEXT,
285
+ getChildHostContext: () => NO_CONTEXT,
286
+ // @ts-ignore prepareUpdate and updatePayload removed with React 19
287
+ commitUpdate(instance, type, oldProps, newProps, fiber) {
288
+ const diff = prepareUpdate(instance, type, oldProps, newProps);
289
+ if (diff === null) return;
290
+ const [reconstruct, changedProps] = diff;
291
+
292
+ // Reconstruct when args or <primitive object={...} have changes
293
+ if (reconstruct) return switchInstance(instance, type, newProps, fiber);
294
+
295
+ // Otherwise just overwrite props
296
+ Object.assign(instance.props, changedProps);
297
+ applyProps(instance.object, changedProps);
298
+ },
299
+ finalizeInitialChildren: () => false,
300
+ commitMount() {},
301
+ getPublicInstance: instance => instance == null ? void 0 : instance.object,
302
+ prepareForCommit: () => null,
303
+ preparePortalMount: container => prepare(container.getState().scene, container, '', {}),
304
+ resetAfterCommit: () => {},
305
+ shouldSetTextContent: () => false,
306
+ clearContainer: () => false,
307
+ hideInstance(instance) {
308
+ var _instance$parent;
309
+ if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
310
+ detach(instance.parent, instance);
311
+ } else if (isObject3D(instance.object)) {
312
+ instance.object.visible = false;
313
+ }
314
+ instance.isHidden = true;
315
+ invalidateInstance(instance);
316
+ },
317
+ unhideInstance(instance) {
318
+ if (instance.isHidden) {
319
+ var _instance$parent2;
320
+ if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
321
+ attach(instance.parent, instance);
322
+ } else if (isObject3D(instance.object) && instance.props.visible !== false) {
323
+ instance.object.visible = true;
324
+ }
325
+ }
326
+ instance.isHidden = false;
327
+ invalidateInstance(instance);
328
+ },
329
+ createTextInstance: handleTextInstance,
330
+ hideTextInstance: handleTextInstance,
331
+ unhideTextInstance: handleTextInstance,
332
+ scheduleTimeout: typeof setTimeout === 'function' ? setTimeout : undefined,
333
+ cancelTimeout: typeof clearTimeout === 'function' ? clearTimeout : undefined,
334
+ noTimeout: -1,
335
+ getInstanceFromNode: () => null,
336
+ beforeActiveInstanceBlur() {},
337
+ afterActiveInstanceBlur() {},
338
+ detachDeletedInstance() {},
339
+ // @ts-ignore untyped react-experimental options inspired by react-art
340
+ // TODO: add shell types for these and upstream to DefinitelyTyped
341
+ // https://github.com/facebook/react/blob/main/packages/react-art/src/ReactFiberConfigART.js
342
+ shouldAttemptEagerTransition() {
343
+ return false;
344
+ },
345
+ requestPostPaintCallback() {},
346
+ maySuspendCommit() {
347
+ return false;
348
+ },
349
+ preloadInstance() {
350
+ return true; // true indicates already loaded
351
+ },
352
+ startSuspendingCommit() {},
353
+ suspendInstance() {},
354
+ waitForCommitToBeReady() {
355
+ return null;
356
+ },
357
+ NotPendingTransition: null,
358
+ setCurrentUpdatePriority(newPriority) {
359
+ currentUpdatePriority = newPriority;
360
+ },
361
+ getCurrentUpdatePriority() {
362
+ return currentUpdatePriority;
363
+ },
364
+ resolveUpdatePriority() {
365
+ var _window$event;
366
+ if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
367
+ switch (typeof window !== 'undefined' && ((_window$event = window.event) == null ? void 0 : _window$event.type)) {
368
+ case 'click':
369
+ case 'contextmenu':
370
+ case 'dblclick':
371
+ case 'pointercancel':
372
+ case 'pointerdown':
373
+ case 'pointerup':
374
+ return DiscreteEventPriority;
375
+ case 'pointermove':
376
+ case 'pointerout':
377
+ case 'pointerover':
378
+ case 'pointerenter':
379
+ case 'pointerleave':
380
+ case 'wheel':
381
+ return ContinuousEventPriority;
382
+ default:
383
+ return DefaultEventPriority;
384
+ }
385
+ }
386
+ });
387
+
9
388
  var _window$document, _window$navigator;
389
+ /**
390
+ * Returns the instance's initial (outmost) root.
391
+ */
392
+ function findInitialRoot(instance) {
393
+ let root = instance.root;
394
+ // TODO: this needs testing https://github.com/pmndrs/react-three-fiber/commit/a4a31ed93c48d1e6dac91329bb5f2ca6a25e5f9c
395
+ // while (root.getState().previousRoot) root = root.getState().previousRoot!
396
+ return root;
397
+ }
398
+
399
+ /**
400
+ * Returns `true` with correct TS type inference if an object has a configurable color space (since r152).
401
+ */
402
+ const hasColorSpace = object => 'colorSpace' in object || 'outputColorSpace' in object;
403
+ /**
404
+ * The current THREE.ColorManagement instance, if present.
405
+ */
406
+ const getColorManagement = () => {
407
+ var _ColorManagement;
408
+ return (_ColorManagement = catalogue.ColorManagement) != null ? _ColorManagement : null;
409
+ };
410
+ /**
411
+ * Safely flush async effects when testing, simulating a legacy root.
412
+ */
413
+ const act = React.act;
10
414
  const isOrthographicCamera = def => def && def.isOrthographicCamera;
11
415
  const isRef = obj => obj && obj.hasOwnProperty('current');
12
- /**
13
- * An SSR-friendly useLayoutEffect.
14
- *
15
- * React currently throws a warning when using useLayoutEffect on the server.
16
- * To get around it, we can conditionally useEffect on the server (no-op) and
17
- * useLayoutEffect elsewhere.
18
- *
19
- * @see https://github.com/facebook/react/issues/14927
20
- */
21
416
 
417
+ /**
418
+ * An SSR-friendly useLayoutEffect.
419
+ *
420
+ * React currently throws a warning when using useLayoutEffect on the server.
421
+ * To get around it, we can conditionally useEffect on the server (no-op) and
422
+ * useLayoutEffect elsewhere.
423
+ *
424
+ * @see https://github.com/facebook/react/issues/14927
425
+ */
22
426
  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.useLayoutEffect : React.useEffect;
23
427
  function useMutableCallback(fn) {
24
428
  const ref = React.useRef(fn);
25
429
  useIsomorphicLayoutEffect(() => void (ref.current = fn), [fn]);
26
430
  return ref;
27
431
  }
432
+ /**
433
+ * Bridges renderer Context and StrictMode from a primary renderer.
434
+ */
435
+ function useBridge() {
436
+ const fiber = useFiber();
437
+ const ContextBridge = useContextBridge();
438
+ return React.useMemo(() => ({
439
+ children
440
+ }) => {
441
+ const strict = !!traverseFiber(fiber, true, node => node.type === React.StrictMode);
442
+ const Root = strict ? React.StrictMode : React.Fragment;
443
+ return /*#__PURE__*/React.createElement(Root, null, /*#__PURE__*/React.createElement(ContextBridge, null, children));
444
+ }, [fiber, ContextBridge]);
445
+ }
28
446
  function Block({
29
447
  set
30
448
  }) {
@@ -41,34 +459,31 @@ class ErrorBoundary extends React.Component {
41
459
  error: false
42
460
  };
43
461
  }
44
-
45
462
  componentDidCatch(err) {
46
463
  this.props.set(err);
47
464
  }
48
-
49
465
  render() {
50
466
  return this.state.error ? null : this.props.children;
51
467
  }
52
-
53
468
  }
54
-
55
469
  ErrorBoundary.getDerivedStateFromError = () => ({
56
470
  error: true
57
471
  });
58
-
59
472
  function calculateDpr(dpr) {
60
- const target = typeof window !== 'undefined' ? window.devicePixelRatio : 1;
473
+ var _window$devicePixelRa;
474
+ // Err on the side of progress by assuming 2x dpr if we can't detect it
475
+ // This will happen in workers where window is defined but dpr isn't.
476
+ const target = typeof window !== 'undefined' ? (_window$devicePixelRa = window.devicePixelRatio) != null ? _window$devicePixelRa : 2 : 1;
61
477
  return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
62
478
  }
63
- /**
64
- * Returns instance root state
65
- */
66
479
 
67
- const getRootState = obj => {
480
+ /**
481
+ * Returns instance root state
482
+ */
483
+ function getRootState(obj) {
68
484
  var _r3f;
69
-
70
485
  return (_r3f = obj.__r3f) == null ? void 0 : _r3f.root.getState();
71
- };
486
+ }
72
487
  // A collection of compare functions
73
488
  const is = {
74
489
  obj: a => a === Object(a) && !is.arr(a) && typeof a !== 'function',
@@ -78,81 +493,86 @@ const is = {
78
493
  boo: a => typeof a === 'boolean',
79
494
  und: a => a === void 0,
80
495
  arr: a => Array.isArray(a),
81
-
82
496
  equ(a, b, {
83
497
  arrays = 'shallow',
84
498
  objects = 'reference',
85
499
  strict = true
86
500
  } = {}) {
87
501
  // Wrong type or one of the two undefined, doesn't match
88
- if (typeof a !== typeof b || !!a !== !!b) return false; // Atomic, just compare a against b
89
-
502
+ if (typeof a !== typeof b || !!a !== !!b) return false;
503
+ // Atomic, just compare a against b
90
504
  if (is.str(a) || is.num(a)) return a === b;
91
505
  const isObj = is.obj(a);
92
506
  if (isObj && objects === 'reference') return a === b;
93
507
  const isArr = is.arr(a);
94
- if (isArr && arrays === 'reference') return a === b; // Array or Object, shallow compare first to see if it's a match
95
-
96
- if ((isArr || isObj) && a === b) return true; // Last resort, go through keys
97
-
508
+ if (isArr && arrays === 'reference') return a === b;
509
+ // Array or Object, shallow compare first to see if it's a match
510
+ if ((isArr || isObj) && a === b) return true;
511
+ // Last resort, go through keys
98
512
  let i;
99
-
513
+ // Check if a has all the keys of b
100
514
  for (i in a) if (!(i in b)) return false;
101
-
102
- for (i in strict ? b : a) if (a[i] !== b[i]) return false;
103
-
515
+ // Check if values between keys match
516
+ if (isObj && arrays === 'shallow' && objects === 'shallow') {
517
+ for (i in strict ? b : a) if (!is.equ(a[i], b[i], {
518
+ strict,
519
+ objects: 'reference'
520
+ })) return false;
521
+ } else {
522
+ for (i in strict ? b : a) if (a[i] !== b[i]) return false;
523
+ }
524
+ // If i is undefined
104
525
  if (is.und(i)) {
526
+ // If both arrays are empty we consider them equal
105
527
  if (isArr && a.length === 0 && b.length === 0) return true;
528
+ // If both objects are empty we consider them equal
106
529
  if (isObj && Object.keys(a).length === 0 && Object.keys(b).length === 0) return true;
530
+ // Otherwise match them by value
107
531
  if (a !== b) return false;
108
532
  }
109
-
110
533
  return true;
111
534
  }
535
+ };
112
536
 
113
- }; // Collects nodes and materials from a THREE.Object3D
114
-
537
+ // Collects nodes and materials from a THREE.Object3D
115
538
  function buildGraph(object) {
116
539
  const data = {
117
540
  nodes: {},
118
541
  materials: {}
119
542
  };
120
-
121
543
  if (object) {
122
544
  object.traverse(obj => {
123
545
  if (obj.name) data.nodes[obj.name] = obj;
124
546
  if (obj.material && !data.materials[obj.material.name]) data.materials[obj.material.name] = obj.material;
125
547
  });
126
548
  }
127
-
128
549
  return data;
129
550
  }
130
551
  // Disposes an object and all its properties
131
552
  function dispose(obj) {
132
553
  if (obj.type !== 'Scene') obj.dispose == null ? void 0 : obj.dispose();
133
-
134
554
  for (const p in obj) {
135
555
  const prop = obj[p];
136
556
  if ((prop == null ? void 0 : prop.type) !== 'Scene') prop == null ? void 0 : prop.dispose == null ? void 0 : prop.dispose();
137
557
  }
138
558
  }
139
- const REACT_INTERNAL_PROPS = ['children', 'key', 'ref']; // Gets only instance props from reconciler fibers
559
+ const REACT_INTERNAL_PROPS = ['children', 'key', 'ref'];
140
560
 
561
+ // Gets only instance props from reconciler fibers
141
562
  function getInstanceProps(queue) {
142
563
  const props = {};
143
-
144
564
  for (const key in queue) {
145
565
  if (!REACT_INTERNAL_PROPS.includes(key)) props[key] = queue[key];
146
566
  }
147
-
148
567
  return props;
149
- } // Each object in the scene carries a small LocalState descriptor
568
+ }
150
569
 
570
+ // Each object in the scene carries a small LocalState descriptor
151
571
  function prepare(target, root, type, props) {
152
- const object = target; // Create instance descriptor
153
-
154
- let instance = object.__r3f;
572
+ const object = target;
155
573
 
574
+ // Create instance descriptor
575
+ let instance = object == null ? void 0 : object.__r3f;
156
576
  if (!instance) {
157
577
  instance = {
158
578
  root,
@@ -165,33 +585,37 @@ function prepare(target, root, type, props) {
165
585
  handlers: {},
166
586
  isHidden: false
167
587
  };
168
- object.__r3f = instance;
588
+ if (object) {
589
+ object.__r3f = instance;
590
+ if (type) applyProps(object, instance.props);
591
+ }
169
592
  }
170
-
171
593
  return instance;
172
594
  }
173
595
  function resolve(root, key) {
174
596
  var _target;
175
-
176
597
  let target = root[key];
177
598
  if (!key.includes('-')) return {
178
599
  root,
179
600
  key,
180
601
  target
181
- }; // Resolve pierced target
602
+ };
182
603
 
604
+ // Resolve pierced target
183
605
  const chain = key.split('-');
184
606
  target = chain.reduce((acc, key) => acc[key], root);
185
- key = chain.pop(); // Switch root if atomic
607
+ key = chain.pop();
186
608
 
609
+ // Switch root if atomic
187
610
  if (!((_target = target) != null && _target.set)) root = chain.reduce((acc, key) => acc[key], root);
188
611
  return {
189
612
  root,
190
613
  key,
191
614
  target
192
615
  };
193
- } // Checks if a dash-cased string ends with an integer
616
+ }
194
617
 
618
+ // Checks if a dash-cased string ends with an integer
195
619
  const INDEX_REGEX = /-\d+$/;
196
620
  function attach(parent, child) {
197
621
  if (is.str(child.props.attach)) {
@@ -204,7 +628,6 @@ function attach(parent, child) {
204
628
  } = resolve(parent.object, index);
205
629
  if (!Array.isArray(root[key])) root[key] = [];
206
630
  }
207
-
208
631
  const {
209
632
  root,
210
633
  key
@@ -221,483 +644,237 @@ function detach(parent, child) {
221
644
  root,
222
645
  key
223
646
  } = resolve(parent.object, child.props.attach);
224
- const previous = child.previousAttach; // When the previous value was undefined, it means the value was never set to begin with
225
-
226
- if (previous === undefined) delete root[key]; // Otherwise set the previous value
647
+ const previous = child.previousAttach;
648
+ // When the previous value was undefined, it means the value was never set to begin with
649
+ if (previous === undefined) delete root[key];
650
+ // Otherwise set the previous value
227
651
  else root[key] = previous;
228
652
  } else {
229
653
  child.previousAttach == null ? void 0 : child.previousAttach(parent.object, child.object);
230
654
  }
231
-
232
655
  delete child.previousAttach;
233
656
  }
234
- const RESERVED_PROPS = [...REACT_INTERNAL_PROPS, // Instance props
235
- 'args', 'dispose', 'attach', 'object', // Behavior flags
236
- 'dispose']; // This function prepares a set of changes to be applied to the instance
237
-
657
+ const RESERVED_PROPS = [...REACT_INTERNAL_PROPS,
658
+ // Instance props
659
+ 'args', 'dispose', 'attach', 'object',
660
+ // Behavior flags
661
+ 'dispose'];
662
+ const MEMOIZED_PROTOTYPES = new Map();
663
+
664
+ // This function prepares a set of changes to be applied to the instance
238
665
  function diffProps(instance, newProps, resetRemoved = false) {
239
- const changedProps = {}; // Sort through props
666
+ const changedProps = {};
240
667
 
668
+ // Sort through props
241
669
  for (const prop in newProps) {
242
670
  // Skip reserved keys
243
- if (RESERVED_PROPS.includes(prop)) continue; // Skip if props match
244
-
245
- if (is.equ(newProps[prop], instance.props[prop])) continue; // Props changed, add them
671
+ if (RESERVED_PROPS.includes(prop)) continue;
672
+ // Skip if props match
673
+ if (is.equ(newProps[prop], instance.props[prop])) continue;
246
674
 
675
+ // Props changed, add them
247
676
  changedProps[prop] = newProps[prop];
248
- } // Reset removed props for HMR
249
677
 
678
+ // Reset pierced props
679
+ for (const other in newProps) {
680
+ if (other.startsWith(`${prop}-`)) changedProps[other] = newProps[other];
681
+ }
682
+ }
250
683
 
684
+ // Reset removed props for HMR
251
685
  if (resetRemoved) {
252
686
  for (const prop in instance.props) {
253
687
  if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
254
688
  const {
255
689
  root,
256
690
  key
257
- } = resolve(instance.object, prop); // https://github.com/mrdoob/three.js/issues/21209
691
+ } = resolve(instance.object, prop);
692
+
693
+ // https://github.com/mrdoob/three.js/issues/21209
258
694
  // HMR/fast-refresh relies on the ability to cancel out props, but threejs
259
695
  // has no means to do this. Hence we curate a small collection of value-classes
260
696
  // with their respective constructor/set arguments
261
697
  // For removed props, try to set default values, if possible
262
-
263
- if (root.constructor) {
264
- var _root$__r3f$props$arg, _root$__r3f;
265
-
698
+ if (root.constructor && root.constructor.length === 0) {
266
699
  // create a blank slate of the instance and copy the particular parameter.
267
- // @ts-ignore
268
- const defaultClassCall = new root.constructor(...((_root$__r3f$props$arg = (_root$__r3f = root.__r3f) == null ? void 0 : _root$__r3f.props.args) != null ? _root$__r3f$props$arg : []));
269
- changedProps[key] = defaultClassCall[key]; // destroy the instance
270
-
271
- if (defaultClassCall.dispose) defaultClassCall.dispose();
700
+ let ctor = MEMOIZED_PROTOTYPES.get(root.constructor);
701
+ if (!ctor) {
702
+ ctor = new root.constructor();
703
+ MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
704
+ }
705
+ changedProps[key] = ctor[key];
272
706
  } else {
273
707
  // instance does not have constructor, just set it to 0
274
708
  changedProps[key] = 0;
275
709
  }
276
710
  }
277
711
  }
278
-
279
712
  return changedProps;
280
- } // This function applies a set of changes to the instance
281
-
713
+ }
714
+ const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production';
715
+
716
+ // const LinearEncoding = 3000
717
+ const sRGBEncoding = 3001;
718
+ const SRGBColorSpace = 'srgb';
719
+ const LinearSRGBColorSpace = 'srgb-linear';
720
+
721
+ // https://github.com/mrdoob/three.js/pull/27042
722
+ // https://github.com/mrdoob/three.js/pull/22748
723
+ const colorMaps = ['map', 'emissiveMap', 'sheenTintMap',
724
+ // <r134
725
+ 'sheenColorMap', 'specularTintMap',
726
+ // <r134
727
+ 'specularColorMap', 'envMap'];
728
+ const EVENT_REGEX = /^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/;
729
+
730
+ // This function applies a set of changes to the instance
282
731
  function applyProps(object, props) {
283
732
  const instance = object.__r3f;
284
- const rootState = instance == null ? void 0 : instance.root.getState();
733
+ const rootState = instance && findInitialRoot(instance).getState();
285
734
  const prevHandlers = instance == null ? void 0 : instance.eventCount;
286
-
287
735
  for (const prop in props) {
288
- let value = props[prop]; // Don't mutate reserved keys
736
+ let value = props[prop];
289
737
 
290
- if (RESERVED_PROPS.includes(prop)) continue; // Deal with pointer events ...
738
+ // Don't mutate reserved keys
739
+ if (RESERVED_PROPS.includes(prop)) continue;
291
740
 
292
- if (instance && /^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/.test(prop)) {
741
+ // Deal with pointer events, including removing them if undefined
742
+ if (instance && EVENT_REGEX.test(prop)) {
293
743
  if (typeof value === 'function') instance.handlers[prop] = value;else delete instance.handlers[prop];
294
744
  instance.eventCount = Object.keys(instance.handlers).length;
295
745
  }
296
746
 
297
- const {
747
+ // Ignore setting undefined props
748
+ // https://github.com/pmndrs/react-three-fiber/issues/274
749
+ if (value === undefined) continue;
750
+ let {
298
751
  root,
299
752
  key,
300
753
  target
301
- } = resolve(object, prop); // Copy if properties match signatures
754
+ } = resolve(object, prop);
755
+
756
+ // Alias (output)encoding => (output)colorSpace (since r152)
757
+ // https://github.com/pmndrs/react-three-fiber/pull/2829
758
+ if (hasColorSpace(root)) {
759
+ if (key === 'encoding') {
760
+ key = 'colorSpace';
761
+ value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
762
+ } else if (key === 'outputEncoding') {
763
+ key = 'outputColorSpace';
764
+ value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
765
+ }
766
+ }
302
767
 
303
- if (target != null && target.copy && (target == null ? void 0 : target.constructor) === (value == null ? void 0 : value.constructor)) {
768
+ // Copy if properties match signatures
769
+ if (target != null && target.copy && (
770
+ // Some environments may break strict identity checks by duplicating versions of three.js.
771
+ // Loosen to unminified names, ignoring descendents.
772
+ // https://github.com/pmndrs/react-three-fiber/issues/2856
773
+ // TODO: fix upstream and remove in v9
774
+ __DEV__ ? target.constructor.name === value.constructor.name : target.constructor === value.constructor)) {
304
775
  target.copy(value);
305
- } // Layers have no copy function, we must therefore copy the mask property
776
+ }
777
+ // Layers have no copy function, we must therefore copy the mask property
306
778
  else if (target instanceof THREE.Layers && value instanceof THREE.Layers) {
307
779
  target.mask = value.mask;
308
- } // Set array types
780
+ }
781
+ // Set array types
309
782
  else if (target != null && target.set && Array.isArray(value)) {
310
783
  if (target.fromArray) target.fromArray(value);else target.set(...value);
311
- } // Set literal types, ignore undefined
312
- // https://github.com/pmndrs/react-three-fiber/issues/274
784
+ }
785
+ // Set literal types
313
786
  else if (target != null && target.set && typeof value !== 'object') {
314
- const isColor = target instanceof THREE.Color; // Allow setting array scalars
787
+ const isColor = target instanceof THREE.Color;
788
+ // Allow setting array scalars
789
+ if (!isColor && target.setScalar && typeof value === 'number') target.setScalar(value);
790
+ // Otherwise just set single value
791
+ else target.set(value);
315
792
 
316
- if (!isColor && target.setScalar && typeof value === 'number') target.setScalar(value); // Otherwise just set ...
317
- else if (value !== undefined) target.set(value);
318
- } // Else, just overwrite the value
319
- else {
320
- root[key] = value; // Auto-convert sRGB textures, for now ...
793
+ // Emulate THREE.ColorManagement for older three.js versions
321
794
  // https://github.com/pmndrs/react-three-fiber/issues/344
795
+ if (!getColorManagement() && !(rootState != null && rootState.linear) && isColor) target.convertSRGBToLinear();
796
+ }
797
+ // Else, just overwrite the value
798
+ else {
799
+ root[key] = value;
322
800
 
323
- if (!(rootState != null && rootState.linear) && root[key] instanceof THREE.Texture) {
324
- root[key].encoding = THREE.sRGBEncoding;
801
+ // Auto-convert sRGB texture parameters for built-in materials
802
+ // https://github.com/pmndrs/react-three-fiber/issues/344
803
+ // https://github.com/mrdoob/three.js/pull/25857
804
+ if (rootState && !rootState.linear && colorMaps.includes(key) && root[key] instanceof THREE.Texture &&
805
+ // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
806
+ root[key].format === THREE.RGBAFormat && root[key].type === THREE.UnsignedByteType) {
807
+ // NOTE: this cannot be set from the renderer (e.g. sRGB source textures rendered to P3)
808
+ if (hasColorSpace(root[key])) root[key].colorSpace = 'srgb';else root[key].encoding = sRGBEncoding;
325
809
  }
326
810
  }
327
811
  }
328
812
 
813
+ // Register event handlers
329
814
  if (instance != null && instance.parent && rootState != null && rootState.internal && instance.object instanceof THREE.Object3D && prevHandlers !== instance.eventCount) {
330
815
  // Pre-emptively remove the instance from the interaction manager
331
816
  const index = rootState.internal.interaction.indexOf(instance.object);
332
- if (index > -1) rootState.internal.interaction.splice(index, 1); // Add the instance to the interaction manager only when it has handlers
333
-
817
+ if (index > -1) rootState.internal.interaction.splice(index, 1);
818
+ // Add the instance to the interaction manager only when it has handlers
334
819
  if (instance.eventCount && instance.object.raycast !== null && instance.object instanceof THREE.Object3D) {
335
820
  rootState.internal.interaction.push(instance.object);
336
821
  }
337
822
  }
338
823
 
824
+ // Auto-attach geometries and materials
825
+ if (instance && instance.props.attach === undefined) {
826
+ if (instance.object instanceof THREE.BufferGeometry) instance.props.attach = 'geometry';else if (instance.object instanceof THREE.Material) instance.props.attach = 'material';
827
+ }
828
+
829
+ // Instance was updated, request a frame
339
830
  if (instance) invalidateInstance(instance);
340
831
  return object;
341
832
  }
342
833
  function invalidateInstance(instance) {
343
834
  var _instance$root;
344
-
345
835
  const state = (_instance$root = instance.root) == null ? void 0 : _instance$root.getState == null ? void 0 : _instance$root.getState();
346
836
  if (state && state.internal.frames === 0) state.invalidate();
347
837
  }
348
838
  function updateCamera(camera, size) {
349
- // https://github.com/pmndrs/react-three-fiber/issues/92
350
839
  // Do not mess with the camera if it belongs to the user
351
- if (!camera.manual) {
352
- if (isOrthographicCamera(camera)) {
353
- camera.left = size.width / -2;
354
- camera.right = size.width / 2;
355
- camera.top = size.height / 2;
356
- camera.bottom = size.height / -2;
357
- } else {
358
- camera.aspect = size.width / size.height;
359
- }
360
-
361
- camera.updateProjectionMatrix(); // https://github.com/pmndrs/react-three-fiber/issues/178
362
- // Update matrix world since the renderer is a frame late
363
-
364
- camera.updateMatrixWorld();
840
+ // https://github.com/pmndrs/react-three-fiber/issues/92
841
+ if (camera.manual) return;
842
+ if (isOrthographicCamera(camera)) {
843
+ camera.left = size.width / -2;
844
+ camera.right = size.width / 2;
845
+ camera.top = size.height / 2;
846
+ camera.bottom = size.height / -2;
847
+ } else {
848
+ camera.aspect = size.width / size.height;
365
849
  }
850
+ camera.updateProjectionMatrix();
366
851
  }
367
-
368
- // Keys that shouldn't be copied between R3F stores
369
- const privateKeys = ['set', 'get', 'setSize', 'setFrameloop', 'setDpr', 'events', 'invalidate', 'advance', 'size', 'viewport'];
370
- const isRenderer = def => !!(def != null && def.render);
371
- const context = /*#__PURE__*/React.createContext(null);
372
-
373
- const createStore = (invalidate, advance) => {
374
- const rootStore = create((set, get) => {
375
- const position = new THREE.Vector3();
376
- const defaultTarget = new THREE.Vector3();
377
- const tempTarget = new THREE.Vector3();
378
-
379
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
380
- const {
381
- width,
382
- height,
383
- top,
384
- left
385
- } = size;
386
- const aspect = width / height;
387
- if (target instanceof THREE.Vector3) tempTarget.copy(target);else tempTarget.set(...target);
388
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
389
-
390
- if (isOrthographicCamera(camera)) {
391
- return {
392
- width: width / camera.zoom,
393
- height: height / camera.zoom,
394
- top,
395
- left,
396
- factor: 1,
397
- distance,
398
- aspect
399
- };
400
- } else {
401
- const fov = camera.fov * Math.PI / 180; // convert vertical fov to radians
402
-
403
- const h = 2 * Math.tan(fov / 2) * distance; // visible height
404
-
405
- const w = h * (width / height);
406
- return {
407
- width: w,
408
- height: h,
409
- top,
410
- left,
411
- factor: width / w,
412
- distance,
413
- aspect
414
- };
415
- }
416
- }
417
-
418
- let performanceTimeout = undefined;
419
-
420
- const setPerformanceCurrent = current => set(state => ({
421
- performance: { ...state.performance,
422
- current
423
- }
424
- }));
425
-
426
- const pointer = new THREE.Vector2();
427
- const rootState = {
428
- set,
429
- get,
430
- // Mock objects that have to be configured
431
- gl: null,
432
- camera: null,
433
- raycaster: null,
434
- events: {
435
- priority: 1,
436
- enabled: true,
437
- connected: false
438
- },
439
- xr: null,
440
- invalidate: (frames = 1) => invalidate(get(), frames),
441
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
442
- legacy: false,
443
- linear: false,
444
- flat: false,
445
- scene: new THREE.Scene(),
446
- controls: null,
447
- clock: new THREE.Clock(),
448
- pointer,
449
- mouse: pointer,
450
- frameloop: 'always',
451
- onPointerMissed: undefined,
452
- performance: {
453
- current: 1,
454
- min: 0.5,
455
- max: 1,
456
- debounce: 200,
457
- regress: () => {
458
- const state = get(); // Clear timeout
459
-
460
- if (performanceTimeout) clearTimeout(performanceTimeout); // Set lower bound performance
461
-
462
- if (state.performance.current !== state.performance.min) setPerformanceCurrent(state.performance.min); // Go back to upper bound performance after a while unless something regresses meanwhile
463
-
464
- performanceTimeout = setTimeout(() => setPerformanceCurrent(get().performance.max), state.performance.debounce);
465
- }
466
- },
467
- size: {
468
- width: 0,
469
- height: 0,
470
- top: 0,
471
- left: 0
472
- },
473
- viewport: {
474
- initialDpr: 0,
475
- dpr: 0,
476
- width: 0,
477
- height: 0,
478
- top: 0,
479
- left: 0,
480
- aspect: 0,
481
- distance: 0,
482
- factor: 0,
483
- getCurrentViewport
484
- },
485
- setEvents: events => set(state => ({ ...state,
486
- events: { ...state.events,
487
- ...events
488
- }
489
- })),
490
- setSize: (width, height, top = 0, left = 0) => {
491
- const camera = get().camera;
492
- const size = {
493
- width,
494
- height,
495
- top,
496
- left
497
- };
498
- set(state => ({
499
- size,
500
- viewport: { ...state.viewport,
501
- ...getCurrentViewport(camera, defaultTarget, size)
502
- }
503
- }));
504
- },
505
- setDpr: dpr => set(state => {
506
- const resolved = calculateDpr(dpr);
507
- return {
508
- viewport: { ...state.viewport,
509
- dpr: resolved,
510
- initialDpr: state.viewport.initialDpr || resolved
511
- }
512
- };
513
- }),
514
- setFrameloop: frameloop => {
515
- var _frameloop$mode, _frameloop$render, _frameloop$maxDelta;
516
-
517
- const state = get();
518
- const mode = typeof frameloop === 'string' ? frameloop : (frameloop == null ? void 0 : frameloop.mode) === 'auto' ? 'always' : (_frameloop$mode = frameloop == null ? void 0 : frameloop.mode) != null ? _frameloop$mode : state.frameloop;
519
- const render = typeof frameloop === 'string' ? state.internal.render : (_frameloop$render = frameloop == null ? void 0 : frameloop.render) != null ? _frameloop$render : state.internal.render;
520
- const maxDelta = typeof frameloop === 'string' ? state.internal.maxDelta : (_frameloop$maxDelta = frameloop == null ? void 0 : frameloop.maxDelta) != null ? _frameloop$maxDelta : state.internal.maxDelta;
521
- const clock = state.clock; // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
522
-
523
- clock.stop();
524
- clock.elapsedTime = 0;
525
-
526
- if (frameloop !== 'never') {
527
- clock.start();
528
- clock.elapsedTime = 0;
529
- }
530
-
531
- set(() => ({
532
- frameloop: mode,
533
- internal: { ...state.internal,
534
- render,
535
- maxDelta
536
- }
537
- }));
538
- },
539
- previousRoot: undefined,
540
- internal: {
541
- // Events
542
- interaction: [],
543
- hovered: new Map(),
544
- subscribers: [],
545
- initialClick: [0, 0],
546
- initialHits: [],
547
- capturedMap: new Map(),
548
- lastEvent: /*#__PURE__*/React.createRef(),
549
- // Updates
550
- active: false,
551
- frames: 0,
552
- stages: [],
553
- render: 'auto',
554
- maxDelta: 1 / 10,
555
- priority: 0,
556
- subscribe: (ref, priority, store) => {
557
- const state = get();
558
- const internal = state.internal; // If this subscription was given a priority, it takes rendering into its own hands
559
- // For that reason we switch off automatic rendering and increase the manual flag
560
- // As long as this flag is positive there can be no internal rendering at all
561
- // because there could be multiple render subscriptions
562
-
563
- internal.priority = internal.priority + (priority > 0 ? 1 : 0); // We use the render flag and deprecate priority
564
-
565
- if (internal.priority && state.internal.render === 'auto') set(() => ({
566
- internal: { ...state.internal,
567
- render: 'manual'
568
- }
569
- }));
570
- internal.subscribers.push({
571
- ref,
572
- priority,
573
- store
574
- }); // Register subscriber and sort layers from lowest to highest, meaning,
575
- // highest priority renders last (on top of the other frames)
576
-
577
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
578
- return () => {
579
- const state = get();
580
- const internal = state.internal;
581
-
582
- if (internal != null && internal.subscribers) {
583
- // Decrease manual flag if this subscription had a priority
584
- internal.priority = internal.priority - (priority > 0 ? 1 : 0); // We use the render flag and deprecate priority
585
-
586
- if (!internal.priority && state.internal.render === 'manual') set(() => ({
587
- internal: { ...state.internal,
588
- render: 'auto'
589
- }
590
- })); // Remove subscriber from list
591
-
592
- internal.subscribers = internal.subscribers.filter(s => s.ref !== ref);
593
- }
594
- };
595
- }
596
- }
597
- };
598
- return rootState;
599
- });
600
- const state = rootStore.getState();
601
- prepare(state.scene, rootStore, '', {});
602
- let oldSize = state.size;
603
- let oldDpr = state.viewport.dpr;
604
- let oldCamera = state.camera;
605
- rootStore.subscribe(() => {
606
- const {
607
- camera,
608
- size,
609
- viewport,
610
- gl,
611
- set
612
- } = rootStore.getState(); // Resize camera and renderer on changes to size and pixelratio
613
-
614
- if (size !== oldSize || viewport.dpr !== oldDpr) {
615
- oldSize = size;
616
- oldDpr = viewport.dpr; // Update camera & renderer
617
-
618
- updateCamera(camera, size);
619
- gl.setPixelRatio(viewport.dpr); // Play nice with offscreen canvas contexts
620
-
621
- const updateStyle = gl.domElement instanceof HTMLCanvasElement;
622
- gl.setSize(size.width, size.height, updateStyle);
623
- } // Update viewport once the camera changes
624
-
625
-
626
- if (camera !== oldCamera) {
627
- oldCamera = camera; // Update viewport
628
-
629
- set(state => ({
630
- viewport: { ...state.viewport,
631
- ...state.viewport.getCurrentViewport(camera)
632
- }
633
- }));
634
- }
635
- }); // Invalidate on any change
636
-
637
- rootStore.subscribe(state => invalidate(state)); // Return root state
638
-
639
- return rootStore;
640
- };
852
+ const isObject3D = object => object == null ? void 0 : object.isObject3D;
641
853
 
642
854
  function makeId(event) {
643
855
  return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
644
- } // https://github.com/facebook/react/tree/main/packages/react-reconciler#getcurrenteventpriority
645
- // Gives React a clue as to how import the current interaction is
646
-
647
-
648
- function getEventPriority() {
649
- var _globalScope$event;
650
-
651
- // Get a handle to the current global scope in window and worker contexts if able
652
- // https://github.com/pmndrs/react-three-fiber/pull/2493
653
- const globalScope = typeof self !== 'undefined' && self || typeof window !== 'undefined' && window;
654
- if (!globalScope) return DefaultEventPriority;
655
- const name = (_globalScope$event = globalScope.event) == null ? void 0 : _globalScope$event.type;
656
-
657
- switch (name) {
658
- case 'click':
659
- case 'contextmenu':
660
- case 'dblclick':
661
- case 'pointercancel':
662
- case 'pointerdown':
663
- case 'pointerup':
664
- return DiscreteEventPriority;
665
-
666
- case 'pointermove':
667
- case 'pointerout':
668
- case 'pointerover':
669
- case 'pointerenter':
670
- case 'pointerleave':
671
- case 'wheel':
672
- return ContinuousEventPriority;
673
-
674
- default:
675
- return DefaultEventPriority;
676
- }
677
856
  }
678
- /**
679
- * Release pointer captures.
680
- * This is called by releasePointerCapture in the API, and when an object is removed.
681
- */
682
857
 
858
+ /**
859
+ * Release pointer captures.
860
+ * This is called by releasePointerCapture in the API, and when an object is removed.
861
+ */
683
862
  function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
684
863
  const captureData = captures.get(obj);
685
-
686
864
  if (captureData) {
687
- captures.delete(obj); // If this was the last capturing object for this pointer
688
-
865
+ captures.delete(obj);
866
+ // If this was the last capturing object for this pointer
689
867
  if (captures.size === 0) {
690
868
  capturedMap.delete(pointerId);
691
869
  captureData.target.releasePointerCapture(pointerId);
692
870
  }
693
871
  }
694
872
  }
695
-
696
873
  function removeInteractivity(store, object) {
697
874
  const {
698
875
  internal
699
- } = store.getState(); // Removes every trace of an object from the data store
700
-
876
+ } = store.getState();
877
+ // Removes every trace of an object from the data store
701
878
  internal.interaction = internal.interaction.filter(o => o !== object);
702
879
  internal.initialHits = internal.initialHits.filter(o => o !== object);
703
880
  internal.hovered.forEach((value, key) => {
@@ -720,106 +897,103 @@ function createEvents(store) {
720
897
  const dy = event.offsetY - internal.initialClick[1];
721
898
  return Math.round(Math.sqrt(dx * dx + dy * dy));
722
899
  }
723
- /** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
724
-
725
900
 
901
+ /** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
726
902
  function filterPointerEvents(objects) {
727
903
  return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
728
904
  var _r3f;
729
-
730
905
  return (_r3f = obj.__r3f) == null ? void 0 : _r3f.handlers['onPointer' + name];
731
906
  }));
732
907
  }
733
-
734
908
  function intersect(event, filter) {
735
909
  const state = store.getState();
736
910
  const duplicates = new Set();
737
- const intersections = []; // Allow callers to eliminate event objects
738
-
739
- const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction; // Reset all raycaster cameras to undefined
740
-
741
- eventsObjects.forEach(obj => {
742
- const state = getRootState(obj);
743
-
911
+ const intersections = [];
912
+ // Allow callers to eliminate event objects
913
+ const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
914
+ // Reset all raycaster cameras to undefined
915
+ for (let i = 0; i < eventsObjects.length; i++) {
916
+ const state = getRootState(eventsObjects[i]);
744
917
  if (state) {
745
918
  state.raycaster.camera = undefined;
746
919
  }
747
- });
748
-
920
+ }
749
921
  if (!state.previousRoot) {
750
922
  // Make sure root-level pointer and ray are set up
751
923
  state.events.compute == null ? void 0 : state.events.compute(event, state);
752
- } // Collect events
753
-
754
-
755
- let hits = eventsObjects // Intersect objects
756
- .flatMap(obj => {
757
- const state = getRootState(obj); // Skip event handling when noEvents is set, or when the raycasters camera is null
758
-
759
- if (!state || !state.events.enabled || state.raycaster.camera === null) return []; // When the camera is undefined we have to call the event layers update function
924
+ }
925
+ function handleRaycast(obj) {
926
+ const state = getRootState(obj);
927
+ // Skip event handling when noEvents is set, or when the raycasters camera is null
928
+ if (!state || !state.events.enabled || state.raycaster.camera === null) return [];
760
929
 
930
+ // When the camera is undefined we have to call the event layers update function
761
931
  if (state.raycaster.camera === undefined) {
762
932
  var _state$previousRoot;
763
-
764
- state.events.compute == null ? void 0 : state.events.compute(event, state, (_state$previousRoot = state.previousRoot) == null ? void 0 : _state$previousRoot.getState()); // If the camera is still undefined we have to skip this layer entirely
765
-
933
+ state.events.compute == null ? void 0 : state.events.compute(event, state, (_state$previousRoot = state.previousRoot) == null ? void 0 : _state$previousRoot.getState());
934
+ // If the camera is still undefined we have to skip this layer entirely
766
935
  if (state.raycaster.camera === undefined) state.raycaster.camera = null;
767
- } // Intersect object by object
768
-
936
+ }
769
937
 
938
+ // Intersect object by object
770
939
  return state.raycaster.camera ? state.raycaster.intersectObject(obj, true) : [];
771
- }) // Sort by event priority and distance
940
+ }
941
+
942
+ // Collect events
943
+ let hits = eventsObjects
944
+ // Intersect objects
945
+ .flatMap(handleRaycast)
946
+ // Sort by event priority and distance
772
947
  .sort((a, b) => {
773
948
  const aState = getRootState(a.object);
774
949
  const bState = getRootState(b.object);
775
- if (!aState || !bState) return 0;
950
+ if (!aState || !bState) return a.distance - b.distance;
776
951
  return bState.events.priority - aState.events.priority || a.distance - b.distance;
777
- }) // Filter out duplicates
952
+ })
953
+ // Filter out duplicates
778
954
  .filter(item => {
779
955
  const id = makeId(item);
780
956
  if (duplicates.has(id)) return false;
781
957
  duplicates.add(id);
782
958
  return true;
783
- }); // https://github.com/mrdoob/three.js/issues/16031
784
- // Allow custom userland intersect sort order, this likely only makes sense on the root filter
959
+ });
785
960
 
786
- if (state.events.filter) hits = state.events.filter(hits, state); // Bubble up the events, find the event source (eventObject)
961
+ // https://github.com/mrdoob/three.js/issues/16031
962
+ // Allow custom userland intersect sort order, this likely only makes sense on the root filter
963
+ if (state.events.filter) hits = state.events.filter(hits, state);
787
964
 
965
+ // Bubble up the events, find the event source (eventObject)
788
966
  for (const hit of hits) {
789
- let eventObject = hit.object; // Bubble event up
790
-
967
+ let eventObject = hit.object;
968
+ // Bubble event up
791
969
  while (eventObject) {
792
970
  var _r3f2;
793
-
794
- if ((_r3f2 = eventObject.__r3f) != null && _r3f2.eventCount) intersections.push({ ...hit,
971
+ if ((_r3f2 = eventObject.__r3f) != null && _r3f2.eventCount) intersections.push({
972
+ ...hit,
795
973
  eventObject
796
974
  });
797
975
  eventObject = eventObject.parent;
798
976
  }
799
- } // If the interaction is captured, make all capturing targets part of the intersect.
800
-
977
+ }
801
978
 
979
+ // If the interaction is captured, make all capturing targets part of the intersect.
802
980
  if ('pointerId' in event && state.internal.capturedMap.has(event.pointerId)) {
803
981
  for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
804
- intersections.push(captureData.intersection);
982
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
805
983
  }
806
984
  }
807
-
808
985
  return intersections;
809
986
  }
810
- /** Handles intersections by forwarding them to handlers */
811
-
812
987
 
988
+ /** Handles intersections by forwarding them to handlers */
813
989
  function handleIntersects(intersections, event, delta, callback) {
814
990
  // If anything has been found, forward it to the event listeners
815
991
  if (intersections.length) {
816
992
  const localState = {
817
993
  stopped: false
818
994
  };
819
-
820
995
  for (const hit of intersections) {
821
996
  const state = getRootState(hit.object);
822
-
823
997
  if (state) {
824
998
  const {
825
999
  raycaster,
@@ -828,19 +1002,15 @@ function createEvents(store) {
828
1002
  internal
829
1003
  } = state;
830
1004
  const unprojectedPoint = new THREE.Vector3(pointer.x, pointer.y, 0).unproject(camera);
831
-
832
1005
  const hasPointerCapture = id => {
833
1006
  var _internal$capturedMap, _internal$capturedMap2;
834
-
835
1007
  return (_internal$capturedMap = (_internal$capturedMap2 = internal.capturedMap.get(id)) == null ? void 0 : _internal$capturedMap2.has(hit.eventObject)) != null ? _internal$capturedMap : false;
836
1008
  };
837
-
838
1009
  const setPointerCapture = id => {
839
1010
  const captureData = {
840
1011
  intersection: hit,
841
1012
  target: event.target
842
1013
  };
843
-
844
1014
  if (internal.capturedMap.has(id)) {
845
1015
  // if the pointerId was previously captured, we add the hit to the
846
1016
  // event capturedMap.
@@ -850,29 +1020,27 @@ function createEvents(store) {
850
1020
  // containing the hitObject, and the hit. hitObject is used for
851
1021
  // faster access.
852
1022
  internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
853
- } // Call the original event now
1023
+ }
854
1024
  event.target.setPointerCapture(id);
855
1025
  };
856
-
857
1026
  const releasePointerCapture = id => {
858
1027
  const captures = internal.capturedMap.get(id);
859
-
860
1028
  if (captures) {
861
1029
  releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
862
1030
  }
863
- }; // Add native event props
864
-
865
-
866
- let extractEventProps = {}; // This iterates over the event's properties including the inherited ones. Native PointerEvents have most of their props as getters which are inherited, but polyfilled PointerEvents have them all as their own properties (i.e. not inherited). We can't use Object.keys() or Object.entries() as they only return "own" properties; nor Object.getPrototypeOf(event) as that *doesn't* return "own" properties, only inherited ones.
1031
+ };
867
1032
 
1033
+ // Add native event props
1034
+ let extractEventProps = {};
1035
+ // This iterates over the event's properties including the inherited ones. Native PointerEvents have most of their props as getters which are inherited, but polyfilled PointerEvents have them all as their own properties (i.e. not inherited). We can't use Object.keys() or Object.entries() as they only return "own" properties; nor Object.getPrototypeOf(event) as that *doesn't* return "own" properties, only inherited ones.
868
1036
  for (let prop in event) {
869
- let property = event[prop]; // Only copy over atomics, leave functions alone as these should be
1037
+ let property = event[prop];
1038
+ // Only copy over atomics, leave functions alone as these should be
870
1039
  // called as event.nativeEvent.fn()
871
-
872
1040
  if (typeof property !== 'function') extractEventProps[prop] = property;
873
1041
  }
874
-
875
- let raycastEvent = { ...hit,
1042
+ let raycastEvent = {
1043
+ ...hit,
876
1044
  ...extractEventProps,
877
1045
  pointer,
878
1046
  intersections,
@@ -882,17 +1050,20 @@ function createEvents(store) {
882
1050
  ray: raycaster.ray,
883
1051
  camera: camera,
884
1052
  // Hijack stopPropagation, which just sets a flag
885
- stopPropagation: () => {
1053
+ stopPropagation() {
886
1054
  // https://github.com/pmndrs/react-three-fiber/issues/596
887
1055
  // Events are not allowed to stop propagation if the pointer has been captured
888
- const capturesForPointer = 'pointerId' in event && internal.capturedMap.get(event.pointerId); // We only authorize stopPropagation...
1056
+ const capturesForPointer = 'pointerId' in event && internal.capturedMap.get(event.pointerId);
889
1057
 
890
- if ( // ...if this pointer hasn't been captured
891
- !capturesForPointer || // ... or if the hit object is capturing the pointer
1058
+ // We only authorize stopPropagation...
1059
+ if (
1060
+ // ...if this pointer hasn't been captured
1061
+ !capturesForPointer ||
1062
+ // ... or if the hit object is capturing the pointer
892
1063
  capturesForPointer.has(hit.eventObject)) {
893
- raycastEvent.stopped = localState.stopped = true; // Propagation is stopped, remove all other hover records
1064
+ raycastEvent.stopped = localState.stopped = true;
1065
+ // Propagation is stopped, remove all other hover records
894
1066
  // An event handler is only allowed to flush other handlers if it is hovered itself
895
-
896
1067
  if (internal.hovered.size && Array.from(internal.hovered.values()).find(i => i.eventObject === hit.eventObject)) {
897
1068
  // Objects cannot flush out higher up objects that have already caught the event
898
1069
  const higher = intersections.slice(0, intersections.indexOf(hit));
@@ -912,111 +1083,136 @@ function createEvents(store) {
912
1083
  releasePointerCapture
913
1084
  },
914
1085
  nativeEvent: event
915
- }; // Call subscribers
916
-
917
- callback(raycastEvent); // Event bubbling may be interrupted by stopPropagation
1086
+ };
918
1087
 
1088
+ // Call subscribers
1089
+ callback(raycastEvent);
1090
+ // Event bubbling may be interrupted by stopPropagation
919
1091
  if (localState.stopped === true) break;
920
1092
  }
921
1093
  }
922
1094
  }
923
-
924
1095
  return intersections;
925
1096
  }
926
-
927
1097
  function cancelPointer(intersections) {
928
1098
  const {
929
1099
  internal
930
1100
  } = store.getState();
931
- Array.from(internal.hovered.values()).forEach(hoveredObj => {
1101
+ for (const hoveredObj of internal.hovered.values()) {
932
1102
  // When no objects were hit or the the hovered object wasn't found underneath the cursor
933
1103
  // we call onPointerOut and delete the object from the hovered-elements map
934
1104
  if (!intersections.length || !intersections.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId)) {
935
1105
  const eventObject = hoveredObj.eventObject;
936
1106
  const instance = eventObject.__r3f;
937
1107
  internal.hovered.delete(makeId(hoveredObj));
938
-
939
1108
  if (instance != null && instance.eventCount) {
940
- const handlers = instance.handlers; // Clear out intersects, they are outdated by now
941
-
942
- const data = { ...hoveredObj,
1109
+ const handlers = instance.handlers;
1110
+ // Clear out intersects, they are outdated by now
1111
+ const data = {
1112
+ ...hoveredObj,
943
1113
  intersections
944
1114
  };
945
1115
  handlers.onPointerOut == null ? void 0 : handlers.onPointerOut(data);
946
1116
  handlers.onPointerLeave == null ? void 0 : handlers.onPointerLeave(data);
947
1117
  }
948
1118
  }
949
- });
1119
+ }
950
1120
  }
951
-
952
- const handlePointer = name => {
1121
+ function pointerMissed(event, objects) {
1122
+ for (let i = 0; i < objects.length; i++) {
1123
+ const instance = objects[i].__r3f;
1124
+ instance == null ? void 0 : instance.handlers.onPointerMissed == null ? void 0 : instance.handlers.onPointerMissed(event);
1125
+ }
1126
+ }
1127
+ function handlePointer(name) {
953
1128
  // Deal with cancelation
954
1129
  switch (name) {
955
1130
  case 'onPointerLeave':
956
1131
  case 'onPointerCancel':
957
1132
  return () => cancelPointer([]);
958
-
959
1133
  case 'onLostPointerCapture':
960
1134
  return event => {
961
1135
  const {
962
1136
  internal
963
1137
  } = store.getState();
964
-
965
- if ('pointerId' in event && !internal.capturedMap.has(event.pointerId)) {
1138
+ if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
966
1139
  // If the object event interface had onLostPointerCapture, we'd call it here on every
967
- // object that's getting removed.
968
- internal.capturedMap.delete(event.pointerId);
969
- cancelPointer([]);
1140
+ // object that's getting removed. We call it on the next frame because onLostPointerCapture
1141
+ // fires before onPointerUp. Otherwise pointerUp would never be called if the event didn't
1142
+ // happen in the object it originated from, leaving components in a in-between state.
1143
+ requestAnimationFrame(() => {
1144
+ // Only release if pointer-up didn't do it already
1145
+ if (internal.capturedMap.has(event.pointerId)) {
1146
+ internal.capturedMap.delete(event.pointerId);
1147
+ cancelPointer([]);
1148
+ }
1149
+ });
970
1150
  }
971
1151
  };
972
- } // Any other pointer goes here ...
973
-
1152
+ }
974
1153
 
975
- return event => {
1154
+ // Any other pointer goes here ...
1155
+ return function handleEvent(event) {
976
1156
  const {
977
1157
  onPointerMissed,
978
1158
  internal
979
- } = store.getState(); //prepareRay(event)
1159
+ } = store.getState();
980
1160
 
981
- internal.lastEvent.current = event; // Get fresh intersects
1161
+ // prepareRay(event)
1162
+ internal.lastEvent.current = event;
982
1163
 
1164
+ // Get fresh intersects
983
1165
  const isPointerMove = name === 'onPointerMove';
984
1166
  const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
985
- const filter = isPointerMove ? filterPointerEvents : undefined; //const hits = patchIntersects(intersect(filter), event)
986
-
1167
+ const filter = isPointerMove ? filterPointerEvents : undefined;
987
1168
  const hits = intersect(event, filter);
988
- const delta = isClickEvent ? calculateDistance(event) : 0; // Save initial coordinates on pointer-down
1169
+ const delta = isClickEvent ? calculateDistance(event) : 0;
989
1170
 
1171
+ // Save initial coordinates on pointer-down
990
1172
  if (name === 'onPointerDown') {
991
1173
  internal.initialClick = [event.offsetX, event.offsetY];
992
1174
  internal.initialHits = hits.map(hit => hit.eventObject);
993
- } // If a click yields no results, pass it back to the user as a miss
994
- // Missed events have to come first in order to establish user-land side-effect clean up
995
-
1175
+ }
996
1176
 
1177
+ // If a click yields no results, pass it back to the user as a miss
1178
+ // Missed events have to come first in order to establish user-land side-effect clean up
997
1179
  if (isClickEvent && !hits.length) {
998
1180
  if (delta <= 2) {
999
1181
  pointerMissed(event, internal.interaction);
1000
1182
  if (onPointerMissed) onPointerMissed(event);
1001
1183
  }
1002
- } // Take care of unhover
1003
-
1004
-
1184
+ }
1185
+ // Take care of unhover
1005
1186
  if (isPointerMove) cancelPointer(hits);
1006
- handleIntersects(hits, event, delta, data => {
1187
+ function onIntersect(data) {
1007
1188
  const eventObject = data.eventObject;
1008
- const instance = eventObject.__r3f; // Check presence of handlers
1189
+ const instance = eventObject.__r3f;
1009
1190
 
1191
+ // Check presence of handlers
1010
1192
  if (!(instance != null && instance.eventCount)) return;
1011
1193
  const handlers = instance.handlers;
1012
1194
 
1195
+ /*
1196
+ MAYBE TODO, DELETE IF NOT:
1197
+ Check if the object is captured, captured events should not have intersects running in parallel
1198
+ But wouldn't it be better to just replace capturedMap with a single entry?
1199
+ Also, are we OK with straight up making picking up multiple objects impossible?
1200
+
1201
+ const pointerId = (data as ThreeEvent<PointerEvent>).pointerId
1202
+ if (pointerId !== undefined) {
1203
+ const capturedMeshSet = internal.capturedMap.get(pointerId)
1204
+ if (capturedMeshSet) {
1205
+ const captured = capturedMeshSet.get(eventObject)
1206
+ if (captured && captured.localState.stopped) return
1207
+ }
1208
+ }*/
1209
+
1013
1210
  if (isPointerMove) {
1014
1211
  // Move event ...
1015
1212
  if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1016
1213
  // When enter or out is present take care of hover-state
1017
1214
  const id = makeId(data);
1018
1215
  const hoveredItem = internal.hovered.get(id);
1019
-
1020
1216
  if (!hoveredItem) {
1021
1217
  // If the object wasn't previously hovered, book it and call its handler
1022
1218
  internal.hovered.set(id, data);
@@ -1026,21 +1222,19 @@ function createEvents(store) {
1026
1222
  // If the object was previously hovered and stopped, we shouldn't allow other items to proceed
1027
1223
  data.stopPropagation();
1028
1224
  }
1029
- } // Call mouse move
1030
-
1031
-
1225
+ }
1226
+ // Call mouse move
1032
1227
  handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
1033
1228
  } else {
1034
1229
  // All other events ...
1035
1230
  const handler = handlers[name];
1036
-
1037
1231
  if (handler) {
1038
1232
  // Forward all events back to their respective handlers with the exception of click events,
1039
1233
  // which must use the initial target
1040
1234
  if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1041
1235
  // Missed events have to come first
1042
- pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object))); // Now call the handler
1043
-
1236
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
1237
+ // Now call the handler
1044
1238
  handler(data);
1045
1239
  }
1046
1240
  } else {
@@ -1050,489 +1244,317 @@ function createEvents(store) {
1050
1244
  }
1051
1245
  }
1052
1246
  }
1053
- });
1247
+ }
1248
+ handleIntersects(hits, event, delta, onIntersect);
1054
1249
  };
1055
- };
1056
-
1057
- function pointerMissed(event, objects) {
1058
- objects.forEach(object => {
1059
- var _r3f3;
1060
-
1061
- return (_r3f3 = object.__r3f) == null ? void 0 : _r3f3.handlers.onPointerMissed == null ? void 0 : _r3f3.handlers.onPointerMissed(event);
1062
- });
1063
1250
  }
1064
-
1065
1251
  return {
1066
1252
  handlePointer
1067
1253
  };
1068
1254
  }
1069
1255
 
1070
- const catalogue = {};
1071
-
1072
- const extend = objects => void Object.assign(catalogue, objects);
1073
-
1074
- function createInstance(type, props, root) {
1075
- var _props$object, _props$args;
1076
-
1077
- // Get target from catalogue
1078
- const name = `${type[0].toUpperCase()}${type.slice(1)}`;
1079
- const target = catalogue[name]; // Validate element target
1080
-
1081
- 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`); // Validate primitives
1082
-
1083
- if (type === 'primitive' && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`); // Throw if an object or literal was passed for args
1084
-
1085
- if (props.args !== undefined && !Array.isArray(props.args)) throw new Error('R3F: The args prop must be an array!'); // Create instance
1086
-
1087
- const object = (_props$object = props.object) != null ? _props$object : new target(...((_props$args = props.args) != null ? _props$args : []));
1088
- const instance = prepare(object, root, type, props); // Auto-attach geometries and materials
1089
-
1090
- if (instance.props.attach === undefined) {
1091
- if (instance.object instanceof THREE.BufferGeometry) instance.props.attach = 'geometry';else if (instance.object instanceof THREE.Material) instance.props.attach = 'material';
1092
- } // Set initial props
1093
-
1094
-
1095
- applyProps(instance.object, props);
1096
- return instance;
1097
- } // https://github.com/facebook/react/issues/20271
1098
- // This will make sure events and attach are only handled once when trees are complete
1099
-
1100
-
1101
- function handleContainerEffects(parent, child) {
1102
- // Bail if tree isn't mounted or parent is not a container.
1103
- // This ensures that the tree is finalized and React won't discard results to Suspense
1104
- const state = child.root.getState();
1105
- if (!parent.parent && parent.object !== state.scene) return; // Handle interactivity
1106
-
1107
- if (child.eventCount > 0 && child.object.raycast !== null && child.object instanceof THREE.Object3D) {
1108
- state.internal.interaction.push(child.object);
1109
- } // Handle attach
1110
-
1111
-
1112
- if (child.props.attach) attach(parent, child);
1113
-
1114
- for (const childInstance of child.children) handleContainerEffects(child, childInstance);
1115
- }
1116
-
1117
- function appendChild(parent, child) {
1118
- if (!child) return; // Link instances
1119
-
1120
- child.parent = parent;
1121
- parent.children.push(child); // Add Object3Ds if able
1122
-
1123
- if (!child.props.attach && parent.object instanceof THREE.Object3D && child.object instanceof THREE.Object3D) {
1124
- parent.object.add(child.object);
1125
- } // Attach tree once complete
1126
-
1127
-
1128
- handleContainerEffects(parent, child); // Tree was updated, request a frame
1129
-
1130
- invalidateInstance(child);
1131
- }
1132
-
1133
- function insertBefore(parent, child, beforeChild, replace = false) {
1134
- if (!child || !beforeChild) return; // Link instances
1135
-
1136
- child.parent = parent;
1137
- const childIndex = parent.children.indexOf(beforeChild);
1138
- if (childIndex !== -1) parent.children.splice(childIndex, replace ? 1 : 0, child);
1139
- if (replace) beforeChild.parent = null; // Manually splice Object3Ds
1140
-
1141
- if (!child.props.attach && parent.object instanceof THREE.Object3D && child.object instanceof THREE.Object3D && beforeChild.object instanceof THREE.Object3D) {
1142
- child.object.parent = parent.object;
1143
- parent.object.children.splice(parent.object.children.indexOf(beforeChild.object), 0, child.object);
1144
- child.object.dispatchEvent({
1145
- type: 'added'
1146
- });
1147
- } // Attach tree once complete
1148
-
1149
-
1150
- handleContainerEffects(parent, child); // Tree was updated, request a frame
1151
-
1152
- invalidateInstance(child);
1153
- }
1154
-
1155
- function removeChild(parent, child, dispose, recursive) {
1156
- if (!child) return; // Unlink instances
1157
-
1158
- child.parent = null;
1159
-
1160
- if (recursive === undefined) {
1161
- const childIndex = parent.children.indexOf(child);
1162
- if (childIndex !== -1) parent.children.splice(childIndex, 1);
1163
- } // Eagerly tear down tree
1164
-
1165
-
1166
- if (child.props.attach) {
1167
- detach(parent, child);
1168
- } else if (child.object instanceof THREE.Object3D && parent.object instanceof THREE.Object3D) {
1169
- parent.object.remove(child.object);
1170
- removeInteractivity(child.root, child.object);
1171
- } // Allow objects to bail out of unmount disposal with dispose={null}
1172
-
1173
-
1174
- const shouldDispose = child.props.dispose !== null && dispose !== false; // Recursively remove instance children
1175
-
1176
- if (recursive !== false) {
1177
- for (const node of child.children) removeChild(child, node, shouldDispose, true);
1178
-
1179
- child.children = [];
1180
- } // Unlink instance object
1181
-
1182
-
1183
- delete child.object.__r3f; // Dispose object whenever the reconciler feels like it.
1184
- // Never dispose of primitives because their state may be kept outside of React!
1185
- // In order for an object to be able to dispose it
1186
- // - has a dispose method
1187
- // - cannot be a <primitive object={...} />
1188
- // - cannot be a THREE.Scene, because three has broken its own API
1189
-
1190
- if (shouldDispose && child.type !== 'primitive' && child.object.type !== 'Scene') {
1191
- const dispose = child.object.dispose;
1192
-
1193
- if (typeof dispose === 'function') {
1194
- unstable_scheduleCallback(unstable_IdlePriority, () => {
1195
- try {
1196
- dispose();
1197
- } catch (e) {
1198
- /* ... */
1199
- }
1200
- });
1201
- }
1202
- } // Tree was updated, request a frame for top-level instance
1203
-
1204
-
1205
- if (dispose === undefined) invalidateInstance(child);
1206
- }
1207
-
1208
- function switchInstance(oldInstance, type, props, fiber) {
1209
- // Create a new instance
1210
- const newInstance = createInstance(type, props, oldInstance.root); // Move children to new instance
1211
-
1212
- for (const child of oldInstance.children) {
1213
- removeChild(oldInstance, child, false, false);
1214
- appendChild(newInstance, child);
1215
- }
1216
-
1217
- oldInstance.children = []; // Link up new instance
1218
-
1219
- const parent = oldInstance.parent;
1220
-
1221
- if (parent) {
1222
- insertBefore(parent, newInstance, oldInstance, true);
1223
- } // This evil hack switches the react-internal fiber node
1224
- [fiber, fiber.alternate].forEach(fiber => {
1225
- if (fiber !== null) {
1226
- fiber.stateNode = newInstance;
1227
-
1228
- if (fiber.ref) {
1229
- if (typeof fiber.ref === 'function') fiber.ref(newInstance.object);else fiber.ref.current = newInstance.object;
1256
+ const isRenderer = def => !!(def != null && def.render);
1257
+ const context = /*#__PURE__*/React.createContext(null);
1258
+ const createStore = (invalidate, advance) => {
1259
+ const rootStore = create((set, get) => {
1260
+ const position = new THREE.Vector3();
1261
+ const defaultTarget = new THREE.Vector3();
1262
+ const tempTarget = new THREE.Vector3();
1263
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
1264
+ const {
1265
+ width,
1266
+ height,
1267
+ top,
1268
+ left
1269
+ } = size;
1270
+ const aspect = width / height;
1271
+ if (target instanceof THREE.Vector3) tempTarget.copy(target);else tempTarget.set(...target);
1272
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
1273
+ if (isOrthographicCamera(camera)) {
1274
+ return {
1275
+ width: width / camera.zoom,
1276
+ height: height / camera.zoom,
1277
+ top,
1278
+ left,
1279
+ factor: 1,
1280
+ distance,
1281
+ aspect
1282
+ };
1283
+ } else {
1284
+ const fov = camera.fov * Math.PI / 180; // convert vertical fov to radians
1285
+ const h = 2 * Math.tan(fov / 2) * distance; // visible height
1286
+ const w = h * (width / height);
1287
+ return {
1288
+ width: w,
1289
+ height: h,
1290
+ top,
1291
+ left,
1292
+ factor: width / w,
1293
+ distance,
1294
+ aspect
1295
+ };
1230
1296
  }
1231
1297
  }
1232
- }); // Tree was updated, request a frame
1233
-
1234
- invalidateInstance(newInstance);
1235
- return newInstance;
1236
- } // Don't handle text instances, warn on undefined behavior
1237
-
1238
-
1239
- const handleTextInstance = () => console.warn('R3F: Text is not allowed in JSX! This could be stray whitespace or characters.');
1240
-
1241
- const reconciler = Reconciler({
1242
- supportsMutation: true,
1243
- isPrimaryRenderer: false,
1244
- supportsPersistence: false,
1245
- supportsHydration: false,
1246
- noTimeout: -1,
1247
- createInstance,
1248
- removeChild,
1249
- appendChild,
1250
- appendInitialChild: appendChild,
1251
- insertBefore,
1252
-
1253
- appendChildToContainer(container, child) {
1254
- const scene = container.getState().scene.__r3f;
1255
-
1256
- if (!child || !scene) return;
1257
- appendChild(scene, child);
1258
- },
1259
-
1260
- removeChildFromContainer(container, child) {
1261
- const scene = container.getState().scene.__r3f;
1262
-
1263
- if (!child || !scene) return;
1264
- removeChild(scene, child);
1265
- },
1266
-
1267
- insertInContainerBefore(container, child, beforeChild) {
1268
- const scene = container.getState().scene.__r3f;
1269
-
1270
- if (!child || !beforeChild || !scene) return;
1271
- insertBefore(scene, child, beforeChild);
1272
- },
1273
-
1274
- getRootHostContext: () => null,
1275
- getChildHostContext: parentHostContext => parentHostContext,
1276
-
1277
- prepareUpdate(instance, _type, oldProps, newProps) {
1278
- var _newProps$args, _oldProps$args, _newProps$args2;
1279
-
1280
- // Reconstruct primitives if object prop changes
1281
- if (instance.type === 'primitive' && oldProps.object !== newProps.object) return [true]; // Throw if an object or literal was passed for args
1282
-
1283
- if (newProps.args !== undefined && !Array.isArray(newProps.args)) throw new Error('R3F: The args prop must be an array!'); // Reconstruct instance if args change
1284
-
1285
- if (((_newProps$args = newProps.args) == null ? void 0 : _newProps$args.length) !== ((_oldProps$args = oldProps.args) == null ? void 0 : _oldProps$args.length)) return [true];
1286
- if ((_newProps$args2 = newProps.args) != null && _newProps$args2.some((value, index) => {
1287
- var _oldProps$args2;
1288
-
1289
- return value !== ((_oldProps$args2 = oldProps.args) == null ? void 0 : _oldProps$args2[index]);
1290
- })) return [true]; // Create a diff-set, flag if there are any changes
1291
-
1292
- const changedProps = diffProps(instance, newProps, true);
1293
- if (Object.keys(changedProps).length) return [false, changedProps]; // Otherwise do not touch the instance
1294
-
1295
- return null;
1296
- },
1297
-
1298
- commitUpdate(instance, diff, type, _oldProps, newProps, fiber) {
1299
- const [reconstruct, changedProps] = diff; // Reconstruct when args or <primitive object={...} have changes
1300
-
1301
- if (reconstruct) return switchInstance(instance, type, newProps, fiber); // Otherwise just overwrite props
1302
-
1303
- Object.assign(instance.props, changedProps);
1304
- applyProps(instance.object, changedProps);
1305
- },
1306
-
1307
- finalizeInitialChildren: () => false,
1308
-
1309
- commitMount() {},
1310
-
1311
- getPublicInstance: instance => instance == null ? void 0 : instance.object,
1312
- prepareForCommit: () => null,
1313
- preparePortalMount: container => prepare(container.getState().scene, container, '', {}),
1314
- resetAfterCommit: () => {},
1315
- shouldSetTextContent: () => false,
1316
- clearContainer: () => false,
1317
-
1318
- hideInstance(instance) {
1319
- var _instance$parent;
1320
-
1321
- if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
1322
- detach(instance.parent, instance);
1323
- } else if (instance.object instanceof THREE.Object3D) {
1324
- instance.object.visible = false;
1325
- }
1326
-
1327
- instance.isHidden = true;
1328
- invalidateInstance(instance);
1329
- },
1330
-
1331
- unhideInstance(instance) {
1332
- if (instance.isHidden) {
1333
- var _instance$parent2;
1334
-
1335
- if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
1336
- attach(instance.parent, instance);
1337
- } else if (instance.object instanceof THREE.Object3D && instance.props.visible !== false) {
1338
- instance.object.visible = true;
1298
+ let performanceTimeout = undefined;
1299
+ const setPerformanceCurrent = current => set(state => ({
1300
+ performance: {
1301
+ ...state.performance,
1302
+ current
1339
1303
  }
1340
- }
1341
-
1342
- instance.isHidden = false;
1343
- invalidateInstance(instance);
1344
- },
1345
-
1346
- createTextInstance: handleTextInstance,
1347
- hideTextInstance: handleTextInstance,
1348
- unhideTextInstance: handleTextInstance,
1349
- // https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r916356874
1350
- // @ts-ignore
1351
- getCurrentEventPriority: () => getEventPriority(),
1352
- beforeActiveInstanceBlur: () => {},
1353
- afterActiveInstanceBlur: () => {},
1354
- detachDeletedInstance: () => {},
1355
- now: typeof performance !== 'undefined' && is.fun(performance.now) ? performance.now : is.fun(Date.now) ? Date.now : () => 0,
1356
- // https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r920883503
1357
- scheduleTimeout: is.fun(setTimeout) ? setTimeout : undefined,
1358
- cancelTimeout: is.fun(clearTimeout) ? clearTimeout : undefined
1359
- });
1360
-
1361
- function createSubs(callback, subs) {
1362
- const sub = {
1363
- callback
1364
- };
1365
- subs.add(sub);
1366
- return () => void subs.delete(sub);
1367
- }
1368
- let globalEffects = new Set();
1369
- let globalAfterEffects = new Set();
1370
- let globalTailEffects = new Set();
1371
- /**
1372
- * Adds a global render callback which is called each frame.
1373
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addEffect
1374
- */
1375
-
1376
- const addEffect = callback => createSubs(callback, globalEffects);
1377
- /**
1378
- * Adds a global after-render callback which is called each frame.
1379
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addAfterEffect
1380
- */
1381
-
1382
- const addAfterEffect = callback => createSubs(callback, globalAfterEffects);
1383
- /**
1384
- * Adds a global callback which is called when rendering stops.
1385
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addTail
1386
- */
1387
-
1388
- const addTail = callback => createSubs(callback, globalTailEffects);
1389
-
1390
- function run(effects, timestamp) {
1391
- if (!effects.size) return;
1392
- effects.forEach(({
1393
- callback
1394
- }) => callback(timestamp));
1395
- }
1396
-
1397
- function flushGlobalEffects(type, timestamp) {
1398
- switch (type) {
1399
- case 'before':
1400
- return run(globalEffects, timestamp);
1401
-
1402
- case 'after':
1403
- return run(globalAfterEffects, timestamp);
1404
-
1405
- case 'tail':
1406
- return run(globalTailEffects, timestamp);
1407
- }
1408
- }
1409
-
1410
- function update(timestamp, state, frame) {
1411
- // Run local effects
1412
- let delta = state.clock.getDelta(); // In frameloop='never' mode, clock times are updated using the provided timestamp
1413
-
1414
- if (state.frameloop === 'never' && typeof timestamp === 'number') {
1415
- delta = timestamp - state.clock.elapsedTime;
1416
- state.clock.oldTime = state.clock.elapsedTime;
1417
- state.clock.elapsedTime = timestamp;
1418
- } else {
1419
- delta = Math.max(Math.min(delta, state.internal.maxDelta), 0);
1420
- } // Call subscribers (useUpdate)
1421
-
1422
-
1423
- for (const stage of state.internal.stages) {
1424
- stage.frame(delta, frame);
1425
- }
1426
-
1427
- state.internal.frames = Math.max(0, state.internal.frames - 1);
1428
- return state.frameloop === 'always' ? 1 : state.internal.frames;
1429
- }
1430
-
1431
- function createLoop(roots) {
1432
- let running = false;
1433
- let repeat;
1434
- let frame;
1435
- let state;
1436
-
1437
- function loop(timestamp) {
1438
- frame = requestAnimationFrame(loop);
1439
- running = true;
1440
- repeat = 0; // Run effects
1441
-
1442
- flushGlobalEffects('before', timestamp); // Render all roots
1443
-
1444
- roots.forEach(root => {
1445
- var _state$gl$xr;
1446
-
1447
- state = root.store.getState(); // If the frameloop is invalidated, do not run another frame
1448
-
1449
- if (state.internal.active && (state.frameloop === 'always' || state.internal.frames > 0) && !((_state$gl$xr = state.gl.xr) != null && _state$gl$xr.isPresenting)) {
1450
- repeat += update(timestamp, state);
1304
+ }));
1305
+ const pointer = new THREE.Vector2();
1306
+ const rootState = {
1307
+ set,
1308
+ get,
1309
+ // Mock objects that have to be configured
1310
+ gl: null,
1311
+ camera: null,
1312
+ raycaster: null,
1313
+ events: {
1314
+ priority: 1,
1315
+ enabled: true,
1316
+ connected: false
1317
+ },
1318
+ scene: null,
1319
+ xr: null,
1320
+ invalidate: (frames = 1) => invalidate(get(), frames),
1321
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1322
+ legacy: false,
1323
+ linear: false,
1324
+ flat: false,
1325
+ controls: null,
1326
+ clock: new THREE.Clock(),
1327
+ pointer,
1328
+ mouse: pointer,
1329
+ frameloop: 'always',
1330
+ onPointerMissed: undefined,
1331
+ performance: {
1332
+ current: 1,
1333
+ min: 0.5,
1334
+ max: 1,
1335
+ debounce: 200,
1336
+ regress: () => {
1337
+ const state = get();
1338
+ // Clear timeout
1339
+ if (performanceTimeout) clearTimeout(performanceTimeout);
1340
+ // Set lower bound performance
1341
+ if (state.performance.current !== state.performance.min) setPerformanceCurrent(state.performance.min);
1342
+ // Go back to upper bound performance after a while unless something regresses meanwhile
1343
+ performanceTimeout = setTimeout(() => setPerformanceCurrent(get().performance.max), state.performance.debounce);
1344
+ }
1345
+ },
1346
+ size: {
1347
+ width: 0,
1348
+ height: 0,
1349
+ top: 0,
1350
+ left: 0
1351
+ },
1352
+ viewport: {
1353
+ initialDpr: 0,
1354
+ dpr: 0,
1355
+ width: 0,
1356
+ height: 0,
1357
+ top: 0,
1358
+ left: 0,
1359
+ aspect: 0,
1360
+ distance: 0,
1361
+ factor: 0,
1362
+ getCurrentViewport
1363
+ },
1364
+ setEvents: events => set(state => ({
1365
+ ...state,
1366
+ events: {
1367
+ ...state.events,
1368
+ ...events
1369
+ }
1370
+ })),
1371
+ setSize: (width, height, top = 0, left = 0) => {
1372
+ const camera = get().camera;
1373
+ const size = {
1374
+ width,
1375
+ height,
1376
+ top,
1377
+ left
1378
+ };
1379
+ set(state => ({
1380
+ size,
1381
+ viewport: {
1382
+ ...state.viewport,
1383
+ ...getCurrentViewport(camera, defaultTarget, size)
1384
+ }
1385
+ }));
1386
+ },
1387
+ setDpr: dpr => set(state => {
1388
+ const resolved = calculateDpr(dpr);
1389
+ return {
1390
+ viewport: {
1391
+ ...state.viewport,
1392
+ dpr: resolved,
1393
+ initialDpr: state.viewport.initialDpr || resolved
1394
+ }
1395
+ };
1396
+ }),
1397
+ setFrameloop: frameloop => {
1398
+ var _frameloop$mode, _frameloop$render, _frameloop$maxDelta;
1399
+ const state = get();
1400
+ const mode = typeof frameloop === 'string' ? frameloop : (frameloop == null ? void 0 : frameloop.mode) === 'auto' ? 'always' : (_frameloop$mode = frameloop == null ? void 0 : frameloop.mode) != null ? _frameloop$mode : state.frameloop;
1401
+ const render = typeof frameloop === 'string' ? state.internal.render : (_frameloop$render = frameloop == null ? void 0 : frameloop.render) != null ? _frameloop$render : state.internal.render;
1402
+ const maxDelta = typeof frameloop === 'string' ? state.internal.maxDelta : (_frameloop$maxDelta = frameloop == null ? void 0 : frameloop.maxDelta) != null ? _frameloop$maxDelta : state.internal.maxDelta;
1403
+ const clock = state.clock;
1404
+ // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
1405
+ clock.stop();
1406
+ clock.elapsedTime = 0;
1407
+ if (frameloop !== 'never') {
1408
+ clock.start();
1409
+ clock.elapsedTime = 0;
1410
+ }
1411
+ set(() => ({
1412
+ frameloop: mode,
1413
+ internal: {
1414
+ ...state.internal,
1415
+ render,
1416
+ maxDelta
1417
+ }
1418
+ }));
1419
+ },
1420
+ previousRoot: undefined,
1421
+ internal: {
1422
+ // Events
1423
+ interaction: [],
1424
+ hovered: new Map(),
1425
+ subscribers: [],
1426
+ initialClick: [0, 0],
1427
+ initialHits: [],
1428
+ capturedMap: new Map(),
1429
+ lastEvent: /*#__PURE__*/React.createRef(),
1430
+ // Updates
1431
+ active: false,
1432
+ frames: 0,
1433
+ stages: [],
1434
+ render: 'auto',
1435
+ maxDelta: 1 / 10,
1436
+ priority: 0,
1437
+ subscribe: (ref, priority, store) => {
1438
+ const state = get();
1439
+ const internal = state.internal;
1440
+ // If this subscription was given a priority, it takes rendering into its own hands
1441
+ // For that reason we switch off automatic rendering and increase the manual flag
1442
+ // As long as this flag is positive there can be no internal rendering at all
1443
+ // because there could be multiple render subscriptions
1444
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1445
+ // We use the render flag and deprecate priority
1446
+ if (internal.priority && state.internal.render === 'auto') set(() => ({
1447
+ internal: {
1448
+ ...state.internal,
1449
+ render: 'manual'
1450
+ }
1451
+ }));
1452
+ internal.subscribers.push({
1453
+ ref,
1454
+ priority,
1455
+ store
1456
+ });
1457
+ // Register subscriber and sort layers from lowest to highest, meaning,
1458
+ // highest priority renders last (on top of the other frames)
1459
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1460
+ return () => {
1461
+ const state = get();
1462
+ const internal = state.internal;
1463
+ if (internal != null && internal.subscribers) {
1464
+ // Decrease manual flag if this subscription had a priority
1465
+ internal.priority = internal.priority - (priority > 0 ? 1 : 0);
1466
+ // We use the render flag and deprecate priority
1467
+ if (!internal.priority && state.internal.render === 'manual') set(() => ({
1468
+ internal: {
1469
+ ...state.internal,
1470
+ render: 'auto'
1471
+ }
1472
+ }));
1473
+ // Remove subscriber from list
1474
+ internal.subscribers = internal.subscribers.filter(s => s.ref !== ref);
1475
+ }
1476
+ };
1477
+ }
1451
1478
  }
1452
- }); // Run after-effects
1453
-
1454
- flushGlobalEffects('after', timestamp); // Stop the loop if nothing invalidates it
1455
-
1456
- if (repeat === 0) {
1457
- // Tail call effects, they are called when rendering stops
1458
- flushGlobalEffects('tail', timestamp); // Flag end of operation
1479
+ };
1480
+ return rootState;
1481
+ });
1482
+ const state = rootStore.getState();
1483
+ let oldSize = state.size;
1484
+ let oldDpr = state.viewport.dpr;
1485
+ let oldCamera = state.camera;
1486
+ rootStore.subscribe(() => {
1487
+ const {
1488
+ camera,
1489
+ size,
1490
+ viewport,
1491
+ gl,
1492
+ set
1493
+ } = rootStore.getState();
1459
1494
 
1460
- running = false;
1461
- return cancelAnimationFrame(frame);
1495
+ // Resize camera and renderer on changes to size and pixelratio
1496
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1497
+ oldSize = size;
1498
+ oldDpr = viewport.dpr;
1499
+ // Update camera & renderer
1500
+ updateCamera(camera, size);
1501
+ gl.setPixelRatio(viewport.dpr);
1502
+ const updateStyle = typeof HTMLCanvasElement !== 'undefined' && gl.domElement instanceof HTMLCanvasElement;
1503
+ gl.setSize(size.width, size.height, updateStyle);
1462
1504
  }
1463
- }
1464
-
1465
- function invalidate(state, frames = 1) {
1466
- var _state$gl$xr2;
1467
-
1468
- if (!state) return roots.forEach(root => invalidate(root.store.getState()), frames);
1469
- if ((_state$gl$xr2 = state.gl.xr) != null && _state$gl$xr2.isPresenting || !state.internal.active || state.frameloop === 'never') return; // Increase frames, do not go higher than 60
1470
-
1471
- state.internal.frames = Math.min(60, state.internal.frames + frames); // If the render-loop isn't active, start it
1472
1505
 
1473
- if (!running) {
1474
- running = true;
1475
- requestAnimationFrame(loop);
1506
+ // Update viewport once the camera changes
1507
+ if (camera !== oldCamera) {
1508
+ oldCamera = camera;
1509
+ // Update viewport
1510
+ set(state => ({
1511
+ viewport: {
1512
+ ...state.viewport,
1513
+ ...state.viewport.getCurrentViewport(camera)
1514
+ }
1515
+ }));
1476
1516
  }
1477
- }
1517
+ });
1478
1518
 
1479
- function advance(timestamp, runGlobalEffects = true, state, frame) {
1480
- if (runGlobalEffects) flushGlobalEffects('before', timestamp);
1481
- if (!state) roots.forEach(root => update(timestamp, root.store.getState()));else update(timestamp, state, frame);
1482
- if (runGlobalEffects) flushGlobalEffects('after', timestamp);
1483
- }
1519
+ // Invalidate on any change
1520
+ rootStore.subscribe(state => invalidate(state));
1484
1521
 
1485
- return {
1486
- loop,
1487
-
1488
- /**
1489
- * Invalidates the view, requesting a frame to be rendered. Will globally invalidate unless passed a root's state.
1490
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#invalidate
1491
- */
1492
- invalidate,
1493
-
1494
- /**
1495
- * Advances the frameloop and runs render effects, useful for when manually rendering via `frameloop="never"`.
1496
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#advance
1497
- */
1498
- advance
1499
- };
1500
- }
1522
+ // Return root state
1523
+ return rootStore;
1524
+ };
1525
+
1526
+ // TODO: Remove deprecated fields in `Subscription`
1501
1527
 
1502
- /**
1503
- * Class representing a stage that updates every frame.
1504
- * Stages are used to build a lifecycle of effects for an app's frameloop.
1528
+ /**
1529
+ * Class representing a stage that updates every frame.
1530
+ * Stages are used to build a lifecycle of effects for an app's frameloop.
1505
1531
  */
1506
1532
  class Stage {
1507
1533
  constructor() {
1508
1534
  this.subscribers = [];
1509
1535
  this._frameTime = 0;
1510
1536
  }
1511
- /**
1512
- * Executes all callback subscriptions on the stage.
1513
- * @param delta - Delta time between frame calls.
1514
- * @param [frame] - The XR frame if it exists.
1515
- */
1516
-
1517
1537
 
1538
+ /**
1539
+ * Executes all callback subscriptions on the stage.
1540
+ * @param delta - Delta time between frame calls.
1541
+ * @param [frame] - The XR frame if it exists.
1542
+ */
1518
1543
  frame(delta, frame) {
1519
1544
  const subs = this.subscribers;
1520
1545
  const initialTime = performance.now();
1521
-
1522
1546
  for (let i = 0; i < subs.length; i++) {
1523
1547
  subs[i].ref.current(subs[i].store.getState(), delta, frame);
1524
1548
  }
1525
-
1526
1549
  this._frameTime = performance.now() - initialTime;
1527
1550
  }
1528
- /**
1529
- * Adds a callback subscriber to the stage.
1530
- * @param ref - The mutable callback reference.
1531
- * @param store - The store to be used with the callback execution.
1532
- * @returns A function to remove the subscription.
1533
- */
1534
-
1535
1551
 
1552
+ /**
1553
+ * Adds a callback subscriber to the stage.
1554
+ * @param ref - The mutable callback reference.
1555
+ * @param store - The store to be used with the callback execution.
1556
+ * @returns A function to remove the subscription.
1557
+ */
1536
1558
  add(ref, store) {
1537
1559
  this.subscribers.push({
1538
1560
  ref,
@@ -1544,21 +1566,20 @@ class Stage {
1544
1566
  });
1545
1567
  };
1546
1568
  }
1547
-
1548
1569
  get frameTime() {
1549
1570
  return this._frameTime;
1550
1571
  }
1572
+ }
1551
1573
 
1552
- } // Using Unity's fixedStep default.
1553
-
1574
+ // Using Unity's fixedStep default.
1554
1575
  const FPS_50 = 1 / 50;
1555
- /**
1556
- * Class representing a stage that updates every frame at a fixed rate.
1557
- * @param name - Name of the stage.
1558
- * @param [fixedStep] - Fixed step rate.
1559
- * @param [maxSubsteps] - Maximum number of substeps.
1560
- */
1561
1576
 
1577
+ /**
1578
+ * Class representing a stage that updates every frame at a fixed rate.
1579
+ * @param name - Name of the stage.
1580
+ * @param [fixedStep] - Fixed step rate.
1581
+ * @param [maxSubsteps] - Maximum number of substeps.
1582
+ */
1562
1583
  class FixedStage extends Stage {
1563
1584
  constructor(fixedStep, maxSubSteps) {
1564
1585
  super();
@@ -1569,74 +1590,62 @@ class FixedStage extends Stage {
1569
1590
  this._fixedFrameTime = 0;
1570
1591
  this._substepTimes = [];
1571
1592
  }
1572
- /**
1573
- * Executes all callback subscriptions on the stage.
1574
- * @param delta - Delta time between frame calls.
1575
- * @param [frame] - The XR frame if it exists.
1576
- */
1577
-
1578
1593
 
1594
+ /**
1595
+ * Executes all callback subscriptions on the stage.
1596
+ * @param delta - Delta time between frame calls.
1597
+ * @param [frame] - The XR frame if it exists.
1598
+ */
1579
1599
  frame(delta, frame) {
1580
1600
  const initialTime = performance.now();
1581
1601
  let substeps = 0;
1582
1602
  this._substepTimes = [];
1583
1603
  this._accumulator += delta;
1584
-
1585
1604
  while (this._accumulator >= this._fixedStep && substeps < this._maxSubsteps) {
1586
1605
  this._accumulator -= this._fixedStep;
1587
1606
  substeps++;
1588
1607
  super.frame(this._fixedStep, frame);
1589
-
1590
1608
  this._substepTimes.push(super.frameTime);
1591
1609
  }
1610
+ this._fixedFrameTime = performance.now() - initialTime;
1592
1611
 
1593
- this._fixedFrameTime = performance.now() - initialTime; // The accumulator will only be larger than the fixed step if we had to
1612
+ // The accumulator will only be larger than the fixed step if we had to
1594
1613
  // bail early due to hitting the max substep limit or execution time lagging.
1595
1614
  // In that case, we want to shave off the excess so we don't fall behind next frame.
1596
-
1597
1615
  this._accumulator = this._accumulator % this._fixedStep;
1598
1616
  this._alpha = this._accumulator / this._fixedStep;
1599
1617
  }
1600
-
1601
1618
  get frameTime() {
1602
1619
  return this._fixedFrameTime;
1603
1620
  }
1604
-
1605
1621
  get substepTimes() {
1606
1622
  return this._substepTimes;
1607
1623
  }
1608
-
1609
1624
  get fixedStep() {
1610
1625
  return this._fixedStep;
1611
1626
  }
1612
-
1613
1627
  set fixedStep(fixedStep) {
1614
1628
  this._fixedStep = fixedStep;
1615
1629
  }
1616
-
1617
1630
  get maxSubsteps() {
1618
1631
  return this._maxSubsteps;
1619
1632
  }
1620
-
1621
1633
  set maxSubsteps(maxSubsteps) {
1622
1634
  this._maxSubsteps = maxSubsteps;
1623
1635
  }
1624
-
1625
1636
  get accumulator() {
1626
1637
  return this._accumulator;
1627
1638
  }
1628
-
1629
1639
  get alpha() {
1630
1640
  return this._alpha;
1631
1641
  }
1632
-
1633
1642
  }
1634
- const Early = new Stage();
1635
- const Fixed = new FixedStage();
1636
- const Update = new Stage();
1637
- const Late = new Stage();
1638
- const Render = new Stage();
1639
- const After = new Stage();
1643
+ const Early = /*#__PURE__*/new Stage();
1644
+ const Fixed = /*#__PURE__*/new FixedStage();
1645
+ const Update = /*#__PURE__*/new Stage();
1646
+ const Late = /*#__PURE__*/new Stage();
1647
+ const Render = /*#__PURE__*/new Stage();
1648
+ const After = /*#__PURE__*/new Stage();
1640
1649
  const Stages = {
1641
1650
  Early,
1642
1651
  Fixed,
@@ -1647,11 +1656,11 @@ const Stages = {
1647
1656
  };
1648
1657
  const Lifecycle = [Early, Fixed, Update, Late, Render, After];
1649
1658
 
1650
- /**
1651
- * Exposes an object's {@link Instance}.
1652
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useInstanceHandle
1653
- *
1654
- * **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.
1659
+ /**
1660
+ * Exposes an object's {@link Instance}.
1661
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useInstanceHandle
1662
+ *
1663
+ * **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.
1655
1664
  */
1656
1665
  function useInstanceHandle(ref) {
1657
1666
  const instance = React.useRef(null);
@@ -1663,113 +1672,123 @@ function useStore() {
1663
1672
  if (!store) throw new Error('R3F: Hooks can only be used within the Canvas component!');
1664
1673
  return store;
1665
1674
  }
1666
- /**
1667
- * Accesses R3F's internal state, containing renderer, canvas, scene, etc.
1668
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree
1669
- */
1670
1675
 
1676
+ /**
1677
+ * Accesses R3F's internal state, containing renderer, canvas, scene, etc.
1678
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree
1679
+ */
1671
1680
  function useThree(selector = state => state, equalityFn) {
1681
+ // TODO: fix this type
1672
1682
  return useStore()(selector, equalityFn);
1673
1683
  }
1674
- /**
1675
- * Executes a callback before render in a shared frame loop.
1676
- * Can order effects with render priority or manually render with a positive priority.
1677
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe
1678
- */
1679
1684
 
1685
+ /**
1686
+ * Executes a callback before render in a shared frame loop.
1687
+ * Can order effects with render priority or manually render with a positive priority.
1688
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe
1689
+ */
1680
1690
  function useFrame(callback, renderPriority = 0) {
1681
1691
  const store = useStore();
1682
- const subscribe = store.getState().internal.subscribe; // Memoize ref
1683
-
1684
- const ref = useMutableCallback(callback); // Subscribe on mount, unsubscribe on unmount
1685
-
1692
+ const subscribe = store.getState().internal.subscribe;
1693
+ // Memoize ref
1694
+ const ref = useMutableCallback(callback);
1695
+ // Subscribe on mount, unsubscribe on unmount
1686
1696
  useIsomorphicLayoutEffect(() => subscribe(ref, renderPriority, store), [renderPriority, subscribe, store]);
1687
1697
  return null;
1688
1698
  }
1689
- /**
1690
- * Executes a callback in a given update stage.
1691
- * Uses the stage instance to indetify which stage to target in the lifecycle.
1692
- */
1693
1699
 
1700
+ /**
1701
+ * Executes a callback in a given update stage.
1702
+ * Uses the stage instance to indetify which stage to target in the lifecycle.
1703
+ */
1694
1704
  function useUpdate(callback, stage = Stages.Update) {
1695
1705
  const store = useStore();
1696
- const stages = store.getState().internal.stages; // Memoize ref
1697
-
1698
- const ref = useMutableCallback(callback); // Throw an error if a stage does not exist in the lifecycle
1699
-
1700
- if (!stages.includes(stage)) throw new Error(`An invoked stage does not exist in the lifecycle.`); // Subscribe on mount, unsubscribe on unmount
1701
-
1706
+ const stages = store.getState().internal.stages;
1707
+ // Memoize ref
1708
+ const ref = useMutableCallback(callback);
1709
+ // Throw an error if a stage does not exist in the lifecycle
1710
+ if (!stages.includes(stage)) throw new Error(`An invoked stage does not exist in the lifecycle.`);
1711
+ // Subscribe on mount, unsubscribe on unmount
1702
1712
  useIsomorphicLayoutEffect(() => stage.add(ref, store), [stage]);
1703
1713
  }
1704
- /**
1705
- * Returns a node graph of an object with named nodes & materials.
1706
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usegraph
1707
- */
1708
1714
 
1715
+ /**
1716
+ * Returns a node graph of an object with named nodes & materials.
1717
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usegraph
1718
+ */
1709
1719
  function useGraph(object) {
1710
1720
  return React.useMemo(() => buildGraph(object), [object]);
1711
1721
  }
1712
-
1722
+ const memoizedLoaders = new WeakMap();
1723
+ const isConstructor = value => {
1724
+ var _value$prototype;
1725
+ return typeof value === 'function' && (value == null ? void 0 : (_value$prototype = value.prototype) == null ? void 0 : _value$prototype.constructor) === value;
1726
+ };
1713
1727
  function loadingFn(extensions, onProgress) {
1714
- return function (Proto, ...input) {
1715
- // Construct new loader and run extensions
1716
- const loader = new Proto();
1717
- if (extensions) extensions(loader); // Go through the urls and load them
1718
-
1719
- return Promise.all(input.map(input => new Promise((res, reject) => loader.load(input, data => {
1720
- if (data.scene) Object.assign(data, buildGraph(data.scene));
1721
- res(data);
1722
- }, onProgress, error => reject(new Error(`Could not load ${input}: ${error.message})`))))));
1728
+ return async function (Proto, ...input) {
1729
+ let loader;
1730
+
1731
+ // Construct and cache loader if constructor was passed
1732
+ if (isConstructor(Proto)) {
1733
+ loader = memoizedLoaders.get(Proto);
1734
+ if (!loader) {
1735
+ loader = new Proto();
1736
+ memoizedLoaders.set(Proto, loader);
1737
+ }
1738
+ } else {
1739
+ loader = Proto;
1740
+ }
1741
+
1742
+ // Apply loader extensions
1743
+ if (extensions) extensions(loader);
1744
+
1745
+ // Go through the urls and load them
1746
+ 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}`))))));
1723
1747
  };
1724
1748
  }
1725
- /**
1726
- * Synchronously loads and caches assets with a three loader.
1727
- *
1728
- * Note: this hook's caller must be wrapped with `React.Suspense`
1729
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useloader
1749
+ /**
1750
+ * Synchronously loads and caches assets with a three loader.
1751
+ *
1752
+ * Note: this hook's caller must be wrapped with `React.Suspense`
1753
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useloader
1730
1754
  */
1731
-
1732
-
1733
- function useLoader(Proto, input, extensions, onProgress) {
1755
+ function useLoader(loader, input, extensions, onProgress) {
1734
1756
  // Use suspense to load async assets
1735
1757
  const keys = Array.isArray(input) ? input : [input];
1736
- const results = suspend(loadingFn(extensions, onProgress), [Proto, ...keys], {
1758
+ const results = suspend(loadingFn(extensions, onProgress), [loader, ...keys], {
1737
1759
  equal: is.equ
1738
- }); // Return the object/s
1739
-
1760
+ });
1761
+ // Return the object(s)
1740
1762
  return Array.isArray(input) ? results : results[0];
1741
1763
  }
1742
- /**
1743
- * Preloads an asset into cache as a side-effect.
1744
- */
1745
1764
 
1746
- useLoader.preload = function (Proto, input, extensions) {
1765
+ /**
1766
+ * Preloads an asset into cache as a side-effect.
1767
+ */
1768
+ useLoader.preload = function (loader, input, extensions) {
1747
1769
  const keys = Array.isArray(input) ? input : [input];
1748
- return preload(loadingFn(extensions), [Proto, ...keys]);
1770
+ return preload(loadingFn(extensions), [loader, ...keys]);
1749
1771
  };
1750
- /**
1751
- * Removes a loaded asset from cache.
1752
- */
1753
-
1754
1772
 
1755
- useLoader.clear = function (Proto, input) {
1773
+ /**
1774
+ * Removes a loaded asset from cache.
1775
+ */
1776
+ useLoader.clear = function (loader, input) {
1756
1777
  const keys = Array.isArray(input) ? input : [input];
1757
- return clear([Proto, ...keys]);
1778
+ return clear([loader, ...keys]);
1758
1779
  };
1759
1780
 
1760
- const roots = new Map();
1761
- const {
1762
- invalidate,
1763
- advance
1764
- } = createLoop(roots);
1781
+ // TODO: fix type resolve
1782
+
1783
+ const _roots = new Map();
1765
1784
  const shallowLoose = {
1766
1785
  objects: 'shallow',
1767
1786
  strict: false
1768
1787
  };
1769
-
1770
1788
  const createRendererInstance = (gl, canvas) => {
1771
1789
  const customRenderer = typeof gl === 'function' ? gl(canvas) : gl;
1772
- if (isRenderer(customRenderer)) return customRenderer;else return new THREE.WebGLRenderer({
1790
+ if (isRenderer(customRenderer)) return customRenderer;
1791
+ return new THREE.WebGLRenderer({
1773
1792
  powerPreference: 'high-performance',
1774
1793
  canvas: canvas,
1775
1794
  antialias: true,
@@ -1777,44 +1796,42 @@ const createRendererInstance = (gl, canvas) => {
1777
1796
  ...gl
1778
1797
  });
1779
1798
  };
1780
-
1781
1799
  const createStages = (stages, store) => {
1782
1800
  const state = store.getState();
1783
1801
  let subscribers;
1784
1802
  let subscription;
1785
-
1786
1803
  const _stages = stages != null ? stages : Lifecycle;
1787
-
1788
1804
  if (!_stages.includes(Stages.Update)) throw 'The Stages.Update stage is required for R3F.';
1789
1805
  if (!_stages.includes(Stages.Render)) throw 'The Stages.Render stage is required for R3F.';
1790
1806
  state.set(({
1791
1807
  internal
1792
1808
  }) => ({
1793
- internal: { ...internal,
1809
+ internal: {
1810
+ ...internal,
1794
1811
  stages: _stages
1795
1812
  }
1796
- })); // Add useFrame loop to update stage
1813
+ }));
1797
1814
 
1815
+ // Add useFrame loop to update stage
1798
1816
  const frameCallback = {
1799
- current: (state, delta, frame) => {
1817
+ current(state, delta, frame) {
1800
1818
  subscribers = state.internal.subscribers;
1801
-
1802
1819
  for (let i = 0; i < subscribers.length; i++) {
1803
1820
  subscription = subscribers[i];
1804
1821
  subscription.ref.current(subscription.store.getState(), delta, frame);
1805
1822
  }
1806
1823
  }
1807
1824
  };
1808
- Stages.Update.add(frameCallback, store); // Add render callback to render stage
1825
+ Stages.Update.add(frameCallback, store);
1809
1826
 
1827
+ // Add render callback to render stage
1810
1828
  const renderCallback = {
1811
- current: state => {
1829
+ current(state) {
1812
1830
  if (state.internal.render === 'auto' && state.gl.render) state.gl.render(state.scene, state.camera);
1813
1831
  }
1814
1832
  };
1815
1833
  Stages.Render.add(renderCallback, store);
1816
1834
  };
1817
-
1818
1835
  function computeInitialSize(canvas, size) {
1819
1836
  if (!size && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1820
1837
  const {
@@ -1829,8 +1846,14 @@ function computeInitialSize(canvas, size) {
1829
1846
  top,
1830
1847
  left
1831
1848
  };
1849
+ } else if (!size && typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
1850
+ return {
1851
+ width: canvas.width,
1852
+ height: canvas.height,
1853
+ top: 0,
1854
+ left: 0
1855
+ };
1832
1856
  }
1833
-
1834
1857
  return {
1835
1858
  width: 0,
1836
1859
  height: 0,
@@ -1839,36 +1862,61 @@ function computeInitialSize(canvas, size) {
1839
1862
  ...size
1840
1863
  };
1841
1864
  }
1842
-
1843
1865
  function createRoot(canvas) {
1844
1866
  // Check against mistaken use of createRoot
1845
- const prevRoot = roots.get(canvas);
1867
+ const prevRoot = _roots.get(canvas);
1846
1868
  const prevFiber = prevRoot == null ? void 0 : prevRoot.fiber;
1847
1869
  const prevStore = prevRoot == null ? void 0 : prevRoot.store;
1848
- if (prevRoot) console.warn('R3F.createRoot should only be called once!'); // Report when an error was detected in a previous render
1849
- // https://github.com/pmndrs/react-three-fiber/pull/2261
1870
+ if (prevRoot) console.warn('R3F.createRoot should only be called once!');
1850
1871
 
1851
- const logRecoverableError = typeof reportError === 'function' ? // In modern browsers, reportError will dispatch an error event,
1872
+ // Report when an error was detected in a previous render
1873
+ // https://github.com/pmndrs/react-three-fiber/pull/2261
1874
+ const logRecoverableError = typeof reportError === 'function' ?
1875
+ // In modern browsers, reportError will dispatch an error event,
1852
1876
  // emulating an uncaught JavaScript error.
1853
- reportError : // In older browsers and test environments, fallback to console.error.
1854
- console.error; // Create store
1855
-
1856
- const store = prevStore || createStore(invalidate, advance); // Create renderer
1857
-
1858
- const fiber = prevFiber || reconciler.createContainer(store, ConcurrentRoot, null, false, null, '', logRecoverableError, null); // Map it
1859
-
1860
- if (!prevRoot) roots.set(canvas, {
1877
+ reportError :
1878
+ // In older browsers and test environments, fallback to console.error.
1879
+ console.error;
1880
+
1881
+ // Create store
1882
+ const store = prevStore || createStore(invalidate, advance);
1883
+ // Create renderer
1884
+ const fiber = prevFiber || reconciler.createContainer(store,
1885
+ // container
1886
+ ConcurrentRoot,
1887
+ // tag
1888
+ null,
1889
+ // hydration callbacks
1890
+ false,
1891
+ // isStrictMode
1892
+ null,
1893
+ // concurrentUpdatesByDefaultOverride
1894
+ '',
1895
+ // identifierPrefix
1896
+ logRecoverableError,
1897
+ // onUncaughtError
1898
+ logRecoverableError,
1899
+ // onCaughtError
1900
+ logRecoverableError,
1901
+ // onRecoverableError
1902
+ null // transitionCallbacks
1903
+ );
1904
+ // Map it
1905
+ if (!prevRoot) _roots.set(canvas, {
1861
1906
  fiber,
1862
1907
  store
1863
- }); // Locals
1908
+ });
1864
1909
 
1910
+ // Locals
1865
1911
  let onCreated;
1866
1912
  let configured = false;
1913
+ let lastCamera;
1867
1914
  return {
1868
1915
  configure(props = {}) {
1869
1916
  let {
1870
1917
  gl: glConfig,
1871
1918
  size: propsSize,
1919
+ scene: sceneOptions,
1872
1920
  events,
1873
1921
  onCreated: onCreatedCallback,
1874
1922
  shadows = false,
@@ -1884,102 +1932,149 @@ function createRoot(canvas) {
1884
1932
  onPointerMissed,
1885
1933
  stages
1886
1934
  } = props;
1887
- let state = store.getState(); // Set up renderer (one time only!)
1935
+ let state = store.getState();
1888
1936
 
1937
+ // Set up renderer (one time only!)
1889
1938
  let gl = state.gl;
1890
1939
  if (!state.gl) state.set({
1891
1940
  gl: gl = createRendererInstance(glConfig, canvas)
1892
- }); // Set up raycaster (one time only!)
1941
+ });
1893
1942
 
1943
+ // Set up raycaster (one time only!)
1894
1944
  let raycaster = state.raycaster;
1895
1945
  if (!raycaster) state.set({
1896
1946
  raycaster: raycaster = new THREE.Raycaster()
1897
- }); // Set raycaster options
1947
+ });
1898
1948
 
1949
+ // Set raycaster options
1899
1950
  const {
1900
1951
  params,
1901
1952
  ...options
1902
1953
  } = raycastOptions || {};
1903
- if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, { ...options
1954
+ if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, {
1955
+ ...options
1904
1956
  });
1905
1957
  if (!is.equ(params, raycaster.params, shallowLoose)) applyProps(raycaster, {
1906
- params: { ...raycaster.params,
1958
+ params: {
1959
+ ...raycaster.params,
1907
1960
  ...params
1908
1961
  }
1909
- }); // Create default camera (one time only!)
1962
+ });
1910
1963
 
1911
- if (!state.camera) {
1964
+ // Create default camera, don't overwrite any user-set state
1965
+ if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
1966
+ lastCamera = cameraOptions;
1912
1967
  const isCamera = cameraOptions instanceof THREE.Camera;
1913
1968
  const camera = isCamera ? cameraOptions : orthographic ? new THREE.OrthographicCamera(0, 0, 0, 0, 0.1, 1000) : new THREE.PerspectiveCamera(75, 0, 0.1, 1000);
1914
-
1915
1969
  if (!isCamera) {
1916
1970
  camera.position.z = 5;
1917
- if (cameraOptions) applyProps(camera, cameraOptions); // Always look at center by default
1918
-
1919
- if (!(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
1971
+ if (cameraOptions) applyProps(camera, cameraOptions);
1972
+ // Always look at center by default
1973
+ if (!state.camera && !(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
1920
1974
  }
1921
-
1922
1975
  state.set({
1923
1976
  camera
1924
1977
  });
1925
- } // Set up XR (one time only!)
1926
1978
 
1979
+ // Configure raycaster
1980
+ // https://github.com/pmndrs/react-xr/issues/300
1981
+ raycaster.camera = camera;
1982
+ }
1983
+
1984
+ // Set up scene (one time only!)
1985
+ if (!state.scene) {
1986
+ let scene;
1987
+ if (sceneOptions instanceof THREE.Scene) {
1988
+ scene = sceneOptions;
1989
+ prepare(scene, store, '', {});
1990
+ } else {
1991
+ scene = new THREE.Scene();
1992
+ prepare(scene, store, '', {});
1993
+ if (sceneOptions) applyProps(scene, sceneOptions);
1994
+ }
1995
+ state.set({
1996
+ scene
1997
+ });
1998
+ }
1927
1999
 
2000
+ // Set up XR (one time only!)
1928
2001
  if (!state.xr) {
1929
2002
  // Handle frame behavior in WebXR
1930
2003
  const handleXRFrame = (timestamp, frame) => {
1931
2004
  const state = store.getState();
1932
2005
  if (state.frameloop === 'never') return;
1933
2006
  advance(timestamp, true, state, frame);
1934
- }; // Toggle render switching on session
1935
-
2007
+ };
1936
2008
 
2009
+ // Toggle render switching on session
1937
2010
  const handleSessionChange = () => {
1938
2011
  const state = store.getState();
1939
2012
  state.gl.xr.enabled = state.gl.xr.isPresenting;
1940
2013
  state.gl.xr.setAnimationLoop(state.gl.xr.isPresenting ? handleXRFrame : null);
1941
2014
  if (!state.gl.xr.isPresenting) invalidate(state);
1942
- }; // WebXR session manager
1943
-
2015
+ };
1944
2016
 
2017
+ // WebXR session manager
1945
2018
  const xr = {
1946
2019
  connect() {
1947
2020
  const gl = store.getState().gl;
1948
2021
  gl.xr.addEventListener('sessionstart', handleSessionChange);
1949
2022
  gl.xr.addEventListener('sessionend', handleSessionChange);
1950
2023
  },
1951
-
1952
2024
  disconnect() {
1953
2025
  const gl = store.getState().gl;
1954
2026
  gl.xr.removeEventListener('sessionstart', handleSessionChange);
1955
2027
  gl.xr.removeEventListener('sessionend', handleSessionChange);
1956
2028
  }
2029
+ };
1957
2030
 
1958
- }; // Subscribe to WebXR session events
1959
-
2031
+ // Subscribe to WebXR session events
1960
2032
  if (gl.xr) xr.connect();
1961
2033
  state.set({
1962
2034
  xr
1963
2035
  });
1964
- } // Set shadowmap
1965
-
2036
+ }
1966
2037
 
2038
+ // Set shadowmap
1967
2039
  if (gl.shadowMap) {
1968
- const isBoolean = is.boo(shadows);
1969
-
1970
- if (isBoolean && gl.shadowMap.enabled !== shadows || !is.equ(shadows, gl.shadowMap, shallowLoose)) {
1971
- const old = gl.shadowMap.enabled;
1972
- gl.shadowMap.enabled = !!shadows;
1973
- if (!isBoolean) Object.assign(gl.shadowMap, shadows);else gl.shadowMap.type = THREE.PCFSoftShadowMap;
1974
- if (old !== gl.shadowMap.enabled) gl.shadowMap.needsUpdate = true;
2040
+ const oldEnabled = gl.shadowMap.enabled;
2041
+ const oldType = gl.shadowMap.type;
2042
+ gl.shadowMap.enabled = !!shadows;
2043
+ if (is.boo(shadows)) {
2044
+ gl.shadowMap.type = THREE.PCFSoftShadowMap;
2045
+ } else if (is.str(shadows)) {
2046
+ var _types$shadows;
2047
+ const types = {
2048
+ basic: THREE.BasicShadowMap,
2049
+ percentage: THREE.PCFShadowMap,
2050
+ soft: THREE.PCFSoftShadowMap,
2051
+ variance: THREE.VSMShadowMap
2052
+ };
2053
+ gl.shadowMap.type = (_types$shadows = types[shadows]) != null ? _types$shadows : THREE.PCFSoftShadowMap;
2054
+ } else if (is.obj(shadows)) {
2055
+ Object.assign(gl.shadowMap, shadows);
1975
2056
  }
1976
- } // Set color management
1977
- THREE.ColorManagement.legacyMode = legacy;
1978
- const outputEncoding = linear ? THREE.LinearEncoding : THREE.sRGBEncoding;
1979
- const toneMapping = flat ? THREE.NoToneMapping : THREE.ACESFilmicToneMapping;
1980
- if (gl.outputEncoding !== outputEncoding) gl.outputEncoding = outputEncoding;
1981
- if (gl.toneMapping !== toneMapping) gl.toneMapping = toneMapping; // Update color management state
2057
+ if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type) gl.shadowMap.needsUpdate = true;
2058
+ }
2059
+
2060
+ // Safely set color management if available.
2061
+ // Avoid accessing THREE.ColorManagement to play nice with older versions
2062
+ const ColorManagement = getColorManagement();
2063
+ if (ColorManagement) {
2064
+ if ('enabled' in ColorManagement) ColorManagement.enabled = !legacy;else if ('legacyMode' in ColorManagement) ColorManagement.legacyMode = legacy;
2065
+ }
1982
2066
 
2067
+ // Set color space and tonemapping preferences
2068
+ if (!configured) {
2069
+ const LinearEncoding = 3000;
2070
+ const sRGBEncoding = 3001;
2071
+ applyProps(gl, {
2072
+ outputEncoding: linear ? LinearEncoding : sRGBEncoding,
2073
+ toneMapping: flat ? THREE.NoToneMapping : THREE.ACESFilmicToneMapping
2074
+ });
2075
+ }
2076
+
2077
+ // Update color management state
1983
2078
  if (state.legacy !== legacy) state.set(() => ({
1984
2079
  legacy
1985
2080
  }));
@@ -1988,42 +2083,43 @@ function createRoot(canvas) {
1988
2083
  }));
1989
2084
  if (state.flat !== flat) state.set(() => ({
1990
2085
  flat
1991
- })); // Set gl props
1992
-
1993
- if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, gl, shallowLoose)) applyProps(gl, glConfig); // Store events internally
2086
+ }));
1994
2087
 
2088
+ // Set gl props
2089
+ if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, gl, shallowLoose)) applyProps(gl, glConfig);
2090
+ // Store events internally
1995
2091
  if (events && !state.events.handlers) state.set({
1996
2092
  events: events(store)
1997
- }); // Check pixelratio
1998
-
1999
- if (dpr && state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr); // Check size, allow it to take on container bounds initially
2000
-
2093
+ });
2094
+ // Check size, allow it to take on container bounds initially
2001
2095
  const size = computeInitialSize(canvas, propsSize);
2002
-
2003
2096
  if (!is.equ(size, state.size, shallowLoose)) {
2004
2097
  state.setSize(size.width, size.height, size.top, size.left);
2005
- } // Check frameloop
2006
-
2007
-
2008
- if (state.frameloop !== frameloop) state.setFrameloop(frameloop); // Check pointer missed
2009
-
2098
+ }
2099
+ // Check pixelratio
2100
+ if (dpr && state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr);
2101
+ // Check frameloop
2102
+ if (state.frameloop !== frameloop) state.setFrameloop(frameloop);
2103
+ // Check pointer missed
2010
2104
  if (!state.onPointerMissed) state.set({
2011
2105
  onPointerMissed
2012
- }); // Check performance
2013
-
2106
+ });
2107
+ // Check performance
2014
2108
  if (performance && !is.equ(performance, state.performance, shallowLoose)) state.set(state => ({
2015
- performance: { ...state.performance,
2109
+ performance: {
2110
+ ...state.performance,
2016
2111
  ...performance
2017
2112
  }
2018
- })); // Create update stages. Only do this once on init
2113
+ }));
2019
2114
 
2020
- if (state.internal.stages.length === 0) createStages(stages, store); // Set locals
2115
+ // Create update stages. Only do this once on init
2116
+ if (state.internal.stages.length === 0) createStages(stages, store);
2021
2117
 
2118
+ // Set locals
2022
2119
  onCreated = onCreatedCallback;
2023
2120
  configured = true;
2024
2121
  return this;
2025
2122
  },
2026
-
2027
2123
  render(children) {
2028
2124
  // The root has to be configured before it can be rendered
2029
2125
  if (!configured) this.configure();
@@ -2035,21 +2131,17 @@ function createRoot(canvas) {
2035
2131
  }), fiber, null, () => undefined);
2036
2132
  return store;
2037
2133
  },
2038
-
2039
2134
  unmount() {
2040
2135
  unmountComponentAtNode(canvas);
2041
2136
  }
2042
-
2043
2137
  };
2044
2138
  }
2045
-
2046
2139
  function render(children, canvas, config) {
2047
2140
  console.warn('R3F.render is no longer supported in React 18. Use createRoot instead!');
2048
2141
  const root = createRoot(canvas);
2049
2142
  root.configure(config);
2050
2143
  return root.render(children);
2051
2144
  }
2052
-
2053
2145
  function Provider({
2054
2146
  store,
2055
2147
  children,
@@ -2057,28 +2149,28 @@ function Provider({
2057
2149
  rootElement
2058
2150
  }) {
2059
2151
  useIsomorphicLayoutEffect(() => {
2060
- const state = store.getState(); // Flag the canvas active, rendering will now begin
2061
-
2152
+ const state = store.getState();
2153
+ // Flag the canvas active, rendering will now begin
2062
2154
  state.set(state => ({
2063
- internal: { ...state.internal,
2155
+ internal: {
2156
+ ...state.internal,
2064
2157
  active: true
2065
2158
  }
2066
- })); // Notifiy that init is completed, the scene graph exists, but nothing has yet rendered
2067
-
2068
- if (onCreated) onCreated(state); // Connect events to the targets parent, this is done to ensure events are registered on
2159
+ }));
2160
+ // Notifiy that init is completed, the scene graph exists, but nothing has yet rendered
2161
+ if (onCreated) onCreated(state);
2162
+ // Connect events to the targets parent, this is done to ensure events are registered on
2069
2163
  // a shared target, and not on the canvas itself
2070
-
2071
- if (!store.getState().events.connected) state.events.connect == null ? void 0 : state.events.connect(rootElement); // eslint-disable-next-line react-hooks/exhaustive-deps
2164
+ if (!store.getState().events.connected) state.events.connect == null ? void 0 : state.events.connect(rootElement);
2165
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2072
2166
  }, []);
2073
2167
  return /*#__PURE__*/React.createElement(context.Provider, {
2074
2168
  value: store
2075
2169
  }, children);
2076
2170
  }
2077
-
2078
2171
  function unmountComponentAtNode(canvas, callback) {
2079
- const root = roots.get(canvas);
2172
+ const root = _roots.get(canvas);
2080
2173
  const fiber = root == null ? void 0 : root.fiber;
2081
-
2082
2174
  if (fiber) {
2083
2175
  const state = root == null ? void 0 : root.store.getState();
2084
2176
  if (state) state.internal.active = false;
@@ -2087,13 +2179,12 @@ function unmountComponentAtNode(canvas, callback) {
2087
2179
  setTimeout(() => {
2088
2180
  try {
2089
2181
  var _state$gl, _state$gl$renderLists, _state$gl2, _state$gl3;
2090
-
2091
2182
  state.events.disconnect == null ? void 0 : state.events.disconnect();
2092
2183
  (_state$gl = state.gl) == null ? void 0 : (_state$gl$renderLists = _state$gl.renderLists) == null ? void 0 : _state$gl$renderLists.dispose == null ? void 0 : _state$gl$renderLists.dispose();
2093
2184
  (_state$gl2 = state.gl) == null ? void 0 : _state$gl2.forceContextLoss == null ? void 0 : _state$gl2.forceContextLoss();
2094
2185
  if ((_state$gl3 = state.gl) != null && _state$gl3.xr) state.xr.disconnect();
2095
2186
  dispose(state.scene);
2096
- roots.delete(canvas);
2187
+ _roots.delete(canvas);
2097
2188
  if (callback) callback(canvas);
2098
2189
  } catch (e) {
2099
2190
  /* ... */
@@ -2103,25 +2194,22 @@ function unmountComponentAtNode(canvas, callback) {
2103
2194
  });
2104
2195
  }
2105
2196
  }
2106
-
2107
2197
  function createPortal(children, container, state) {
2108
2198
  return /*#__PURE__*/React.createElement(Portal, {
2109
- key: container.uuid,
2110
2199
  children: children,
2111
2200
  container: container,
2112
2201
  state: state
2113
2202
  });
2114
2203
  }
2115
-
2116
2204
  function Portal({
2117
2205
  state = {},
2118
2206
  children,
2119
2207
  container
2120
2208
  }) {
2121
- /** This has to be a component because it would not be able to call useThree/useStore otherwise since
2122
- * if this is our environment, then we are not in r3f's renderer but in react-dom, it would trigger
2123
- * the "R3F hooks can only be used within the Canvas component!" warning:
2124
- * <Canvas>
2209
+ /** This has to be a component because it would not be able to call useThree/useStore otherwise since
2210
+ * if this is our environment, then we are not in r3f's renderer but in react-dom, it would trigger
2211
+ * the "R3F hooks can only be used within the Canvas component!" warning:
2212
+ * <Canvas>
2125
2213
  * {createPortal(...)} */
2126
2214
  const {
2127
2215
  events,
@@ -2131,32 +2219,20 @@ function Portal({
2131
2219
  const previousRoot = useStore();
2132
2220
  const [raycaster] = React.useState(() => new THREE.Raycaster());
2133
2221
  const [pointer] = React.useState(() => new THREE.Vector2());
2134
- const inject = React.useCallback((rootState, injectState) => {
2135
- const intersect = { ...rootState
2136
- }; // all prev state props
2137
- // Only the fields of "rootState" that do not differ from injectState
2138
- // Some props should be off-limits
2139
- // Otherwise filter out the props that are different and let the inject layer take precedence
2140
-
2141
- Object.keys(rootState).forEach(key => {
2142
- if ( // Some props should be off-limits
2143
- privateKeys.includes(key) || // Otherwise filter out the props that are different and let the inject layer take precedence
2144
- rootState[key] !== injectState[key]) {
2145
- delete intersect[key];
2146
- }
2147
- });
2148
- let viewport = undefined;
2149
-
2150
- if (injectState && size) {
2151
- const camera = injectState.camera; // Calculate the override viewport, if present
2152
-
2153
- viewport = rootState.viewport.getCurrentViewport(camera, new THREE.Vector3(), size); // Update the portal camera, if it differs from the previous layer
2154
-
2222
+ const inject = useMutableCallback((rootState, injectState) => {
2223
+ let viewport;
2224
+ if (injectState.camera && size) {
2225
+ const camera = injectState.camera;
2226
+ // Calculate the override viewport, if present
2227
+ viewport = rootState.viewport.getCurrentViewport(camera, new THREE.Vector3(), size);
2228
+ // Update the portal camera, if it differs from the previous layer
2155
2229
  if (camera !== rootState.camera) updateCamera(camera, size);
2156
2230
  }
2157
-
2158
- return { // The intersect consists of the previous root state
2159
- ...intersect,
2231
+ return {
2232
+ // The intersect consists of the previous root state
2233
+ ...rootState,
2234
+ get: injectState.get,
2235
+ set: injectState.set,
2160
2236
  // Portals have their own scene, which forms the root, a raycaster and a pointer
2161
2237
  scene: container,
2162
2238
  raycaster,
@@ -2165,72 +2241,194 @@ function Portal({
2165
2241
  // Their previous root is the layer before it
2166
2242
  previousRoot,
2167
2243
  // Events, size and viewport can be overridden by the inject layer
2168
- events: { ...rootState.events,
2169
- ...(injectState == null ? void 0 : injectState.events),
2244
+ events: {
2245
+ ...rootState.events,
2246
+ ...injectState.events,
2170
2247
  ...events
2171
2248
  },
2172
- size: { ...rootState.size,
2249
+ size: {
2250
+ ...rootState.size,
2173
2251
  ...size
2174
2252
  },
2175
- viewport: { ...rootState.viewport,
2253
+ viewport: {
2254
+ ...rootState.viewport,
2176
2255
  ...viewport
2177
2256
  },
2178
- ...rest
2179
- };
2180
- }, [state]);
2181
- const [usePortalStore] = React.useState(() => {
2182
- // Create a mirrored store, based on the previous root with a few overrides ...
2183
- const previousState = previousRoot.getState();
2184
- const store = create((set, get) => ({ ...previousState,
2185
- scene: container,
2186
- raycaster,
2187
- pointer,
2188
- mouse: pointer,
2189
- previousRoot,
2190
- events: { ...previousState.events,
2191
- ...events
2192
- },
2193
- size: { ...previousState.size,
2194
- ...size
2195
- },
2196
- ...rest,
2197
- // Set and get refer to this root-state
2198
- set,
2199
- get,
2200
2257
  // Layers are allowed to override events
2201
- setEvents: events => set(state => ({ ...state,
2202
- events: { ...state.events,
2258
+ setEvents: events => injectState.set(state => ({
2259
+ ...state,
2260
+ events: {
2261
+ ...state.events,
2203
2262
  ...events
2204
2263
  }
2205
2264
  }))
2206
- }));
2207
- return store;
2265
+ };
2208
2266
  });
2209
- React.useEffect(() => {
2267
+ const usePortalStore = React.useMemo(() => {
2268
+ const store = create((set, get) => ({
2269
+ ...rest,
2270
+ set,
2271
+ get
2272
+ }));
2273
+
2210
2274
  // Subscribe to previous root-state and copy changes over to the mirrored portal-state
2211
- const unsub = previousRoot.subscribe(prev => usePortalStore.setState(state => inject(prev, state)));
2212
- return () => {
2213
- unsub();
2214
- usePortalStore.destroy();
2215
- };
2216
- }, []);
2217
- React.useEffect(() => {
2218
- usePortalStore.setState(injectState => inject(previousRoot.getState(), injectState));
2219
- }, [inject]);
2275
+ const onMutate = prev => store.setState(state => inject.current(prev, state));
2276
+ onMutate(previousRoot.getState());
2277
+ previousRoot.subscribe(onMutate);
2278
+ return store;
2279
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2280
+ }, [previousRoot, container]);
2220
2281
  return /*#__PURE__*/React.createElement(React.Fragment, null, reconciler.createPortal( /*#__PURE__*/React.createElement(context.Provider, {
2221
2282
  value: usePortalStore
2222
2283
  }, children), usePortalStore, null));
2223
2284
  }
2224
-
2225
2285
  reconciler.injectIntoDevTools({
2226
2286
  bundleType: process.env.NODE_ENV === 'production' ? 0 : 1,
2227
2287
  rendererPackageName: '@react-three/fiber',
2228
2288
  version: React.version
2229
2289
  });
2230
2290
 
2231
- /**
2232
- * Safely flush async effects when testing, simulating a legacy root.
2291
+ function createSubs(callback, subs) {
2292
+ const sub = {
2293
+ callback
2294
+ };
2295
+ subs.add(sub);
2296
+ return () => void subs.delete(sub);
2297
+ }
2298
+ const globalEffects = new Set();
2299
+ const globalAfterEffects = new Set();
2300
+ const globalTailEffects = new Set();
2301
+
2302
+ /**
2303
+ * Adds a global render callback which is called each frame.
2304
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addEffect
2305
+ */
2306
+ const addEffect = callback => createSubs(callback, globalEffects);
2307
+
2308
+ /**
2309
+ * Adds a global after-render callback which is called each frame.
2310
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addAfterEffect
2311
+ */
2312
+ const addAfterEffect = callback => createSubs(callback, globalAfterEffects);
2313
+
2314
+ /**
2315
+ * Adds a global callback which is called when rendering stops.
2316
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#addTail
2317
+ */
2318
+ const addTail = callback => createSubs(callback, globalTailEffects);
2319
+ function run(effects, timestamp) {
2320
+ if (!effects.size) return;
2321
+ for (const {
2322
+ callback
2323
+ } of effects.values()) {
2324
+ callback(timestamp);
2325
+ }
2326
+ }
2327
+ function flushGlobalEffects(type, timestamp) {
2328
+ switch (type) {
2329
+ case 'before':
2330
+ return run(globalEffects, timestamp);
2331
+ case 'after':
2332
+ return run(globalAfterEffects, timestamp);
2333
+ case 'tail':
2334
+ return run(globalTailEffects, timestamp);
2335
+ }
2336
+ }
2337
+ function update(timestamp, state, frame) {
2338
+ // Run local effects
2339
+ let delta = state.clock.getDelta();
2340
+ // In frameloop='never' mode, clock times are updated using the provided timestamp
2341
+ if (state.frameloop === 'never' && typeof timestamp === 'number') {
2342
+ delta = timestamp - state.clock.elapsedTime;
2343
+ state.clock.oldTime = state.clock.elapsedTime;
2344
+ state.clock.elapsedTime = timestamp;
2345
+ } else {
2346
+ delta = Math.max(Math.min(delta, state.internal.maxDelta), 0);
2347
+ }
2348
+ // Call subscribers (useUpdate)
2349
+ for (const stage of state.internal.stages) {
2350
+ stage.frame(delta, frame);
2351
+ }
2352
+ state.internal.frames = Math.max(0, state.internal.frames - 1);
2353
+ return state.frameloop === 'always' ? 1 : state.internal.frames;
2354
+ }
2355
+ let running = false;
2356
+ let useFrameInProgress = false;
2357
+ let repeat;
2358
+ let frame;
2359
+ let state;
2360
+ function loop(timestamp) {
2361
+ frame = requestAnimationFrame(loop);
2362
+ running = true;
2363
+ repeat = 0;
2364
+
2365
+ // Run effects
2366
+ flushGlobalEffects('before', timestamp);
2367
+
2368
+ // Render all roots
2369
+ useFrameInProgress = true;
2370
+ for (const root of _roots.values()) {
2371
+ var _state$gl$xr;
2372
+ state = root.store.getState();
2373
+
2374
+ // If the frameloop is invalidated, do not run another frame
2375
+ if (state.internal.active && (state.frameloop === 'always' || state.internal.frames > 0) && !((_state$gl$xr = state.gl.xr) != null && _state$gl$xr.isPresenting)) {
2376
+ repeat += update(timestamp, state);
2377
+ }
2378
+ }
2379
+ useFrameInProgress = true;
2380
+
2381
+ // Run after-effects
2382
+ flushGlobalEffects('after', timestamp);
2383
+
2384
+ // Stop the loop if nothing invalidates it
2385
+ if (repeat === 0) {
2386
+ // Tail call effects, they are called when rendering stops
2387
+ flushGlobalEffects('tail', timestamp);
2388
+
2389
+ // Flag end of operation
2390
+ running = false;
2391
+ return cancelAnimationFrame(frame);
2392
+ }
2393
+ }
2394
+
2395
+ /**
2396
+ * Invalidates the view, requesting a frame to be rendered. Will globally invalidate unless passed a root's state.
2397
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#invalidate
2398
+ */
2399
+ function invalidate(state, frames = 1) {
2400
+ var _state$gl$xr2;
2401
+ if (!state) return _roots.forEach(root => invalidate(root.store.getState(), frames));
2402
+ if ((_state$gl$xr2 = state.gl.xr) != null && _state$gl$xr2.isPresenting || !state.internal.active || state.frameloop === 'never') return;
2403
+ if (frames > 1) {
2404
+ // legacy support for people using frames parameters
2405
+ // Increase frames, do not go higher than 60
2406
+ state.internal.frames = Math.min(60, state.internal.frames + frames);
2407
+ } else {
2408
+ if (useFrameInProgress) {
2409
+ //called from within a useFrame, it means the user wants an additional frame
2410
+ state.internal.frames = 2;
2411
+ } else {
2412
+ //the user need a new frame, no need to increment further than 1
2413
+ state.internal.frames = 1;
2414
+ }
2415
+ }
2416
+
2417
+ // If the render-loop isn't active, start it
2418
+ if (!running) {
2419
+ running = true;
2420
+ requestAnimationFrame(loop);
2421
+ }
2422
+ }
2423
+
2424
+ /**
2425
+ * Advances the frameloop and runs render effects, useful for when manually rendering via `frameloop="never"`.
2426
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#advance
2233
2427
  */
2234
- const act = React.unstable_act;
2428
+ function advance(timestamp, runGlobalEffects = true, state, frame) {
2429
+ if (runGlobalEffects) flushGlobalEffects('before', timestamp);
2430
+ if (!state) for (const root of _roots.values()) update(timestamp, root.store.getState());else update(timestamp, state, frame);
2431
+ if (runGlobalEffects) flushGlobalEffects('after', timestamp);
2432
+ }
2235
2433
 
2236
- export { useUpdate as A, Block as B, useGraph as C, useLoader as D, ErrorBoundary as E, FixedStage as F, Stage as S, createRoot as a, useIsomorphicLayoutEffect as b, createEvents as c, unmountComponentAtNode as d, extend as e, Stages as f, context as g, createPortal as h, isRef as i, reconciler as j, applyProps as k, dispose as l, invalidate as m, advance as n, addEffect as o, addAfterEffect as p, addTail as q, render as r, getRootState as s, act as t, useMutableCallback as u, roots as v, useInstanceHandle as w, useStore as x, useThree as y, useFrame as z };
2434
+ export { useStore as A, Block as B, useThree as C, useFrame as D, ErrorBoundary as E, FixedStage as F, useUpdate as G, useGraph as H, useLoader as I, Stage as S, _roots as _, useMutableCallback as a, useIsomorphicLayoutEffect as b, createEvents as c, createRoot as d, extend as e, unmountComponentAtNode as f, flushGlobalEffects as g, addEffect as h, isRef as i, addAfterEffect as j, addTail as k, invalidate as l, advance as m, render as n, createPortal as o, Stages as p, context as q, reconciler as r, applyProps as s, threeTypes as t, useBridge as u, getRootState as v, dispose as w, act as x, buildGraph as y, useInstanceHandle as z };