@react-three/fiber 10.0.0-canary.4bc4a97 → 10.0.0-canary.4bf885a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/legacy.cjs CHANGED
@@ -10,6 +10,7 @@ const scheduler = require('@pmndrs/scheduler');
10
10
  const Tb = require('scheduler');
11
11
  const GroundedSkybox_js = require('three/examples/jsm/objects/GroundedSkybox.js');
12
12
  const HDRLoader_js = require('three/examples/jsm/loaders/HDRLoader.js');
13
+ const HDRCubeTextureLoader_js = require('three/examples/jsm/loaders/HDRCubeTextureLoader.js');
13
14
  const EXRLoader_js = require('three/examples/jsm/loaders/EXRLoader.js');
14
15
  const UltraHDRLoader_js = require('three/examples/jsm/loaders/UltraHDRLoader.js');
15
16
  const gainmapJs = require('@monogrid/gainmap-js');
@@ -50,16 +51,14 @@ const Tb__default = /*#__PURE__*/_interopDefaultCompat(Tb);
50
51
 
51
52
  const R3F_BUILD_LEGACY = true;
52
53
  const R3F_BUILD_WEBGPU = false;
53
- const Inspector = class Inspector2 {
54
- constructor() {
55
- throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
56
- }
57
- };
58
- const WebGPURenderer = class WebGPURenderer2 {
54
+ async function loadInspector() {
55
+ throw new Error("Inspector is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
56
+ }
57
+ class WebGPURenderer {
59
58
  constructor() {
60
59
  throw new Error("WebGPURenderer is not available in legacy builds. Use @react-three/fiber/webgpu instead.");
61
60
  }
62
- };
61
+ }
63
62
  const RenderTarget = null;
64
63
  const CubeRenderTarget = null;
65
64
 
@@ -67,12 +66,12 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
67
66
  __proto__: null,
68
67
  CubeRenderTarget: CubeRenderTarget,
69
68
  CubeRenderTargetCompat: three.WebGLCubeRenderTarget,
70
- Inspector: Inspector,
71
69
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
72
70
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
73
71
  RenderTarget: RenderTarget,
74
72
  RenderTargetCompat: three.WebGLRenderTarget,
75
- WebGPURenderer: WebGPURenderer
73
+ WebGPURenderer: WebGPURenderer,
74
+ loadInspector: loadInspector
76
75
  }, [three__namespace]);
77
76
 
78
77
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -319,7 +318,6 @@ function prepare(target, root, type, props) {
319
318
  }
320
319
  function invalidateInstance(instance) {
321
320
  if (!instance.parent) return;
322
- instance.props.onUpdate?.(instance.object);
323
321
  const state = instance.root?.getState?.();
324
322
  if (state && state.internal.frames === 0) state.invalidate();
325
323
  }
@@ -535,8 +533,10 @@ function hasVisibilityHandlers(handlers) {
535
533
  }
536
534
 
537
535
  const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
538
- function fromRef(ref) {
539
- return { [FROM_REF]: ref };
536
+ function fromRef(ref, transform) {
537
+ const marker = { [FROM_REF]: ref };
538
+ if (transform) marker.transform = transform;
539
+ return marker;
540
540
  }
541
541
  function isFromRef(value) {
542
542
  return value !== null && typeof value === "object" && FROM_REF in value;
@@ -550,6 +550,63 @@ function isOnce(value) {
550
550
  return value !== null && typeof value === "object" && ONCE in value;
551
551
  }
552
552
 
553
+ const shownNotices = /* @__PURE__ */ new Set();
554
+ function notifyDepreciated({ heading, body, link }) {
555
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
556
+ return;
557
+ }
558
+ if (shownNotices.has(heading)) return;
559
+ shownNotices.add(heading);
560
+ const caller = getCallerFrame();
561
+ if (caller) {
562
+ console.log();
563
+ }
564
+ const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
565
+ if (caller) {
566
+ console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
567
+ }
568
+ if (body || link) {
569
+ let message = "";
570
+ if (body) message += body;
571
+ if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
572
+ console.warn(`%c${message}`, "font-weight: bold;");
573
+ }
574
+ }
575
+ function getCallerFrame(depth = 3) {
576
+ const stack = new Error().stack;
577
+ if (!stack) return null;
578
+ const lines = stack.split("\n");
579
+ const frame = lines[depth];
580
+ if (!frame) return null;
581
+ let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
582
+ if (!match) {
583
+ match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
584
+ }
585
+ if (!match) return null;
586
+ const [, fn, url, line] = match;
587
+ return {
588
+ functionName: fn ?? "<anonymous>",
589
+ location: formatLocation(url, Number(line))
590
+ };
591
+ }
592
+ function formatLocation(url, line) {
593
+ const clean = url.split("?")[0];
594
+ const file = clean.split("/").pop() ?? clean;
595
+ return `${file}:${line}`;
596
+ }
597
+ function notifyAlpha({ message, link }) {
598
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
599
+ return;
600
+ }
601
+ if (shownNotices.has(message)) return;
602
+ shownNotices.add(message);
603
+ const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
604
+ console.log(`%c\u{1F52C} ${message}`, boxStyle);
605
+ {
606
+ console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
607
+ }
608
+ }
609
+
553
610
  const RESERVED_PROPS = [
554
611
  "children",
555
612
  "key",
@@ -559,7 +616,6 @@ const RESERVED_PROPS = [
559
616
  "dispose",
560
617
  "attach",
561
618
  "object",
562
- "onUpdate",
563
619
  // Behavior flags
564
620
  "dispose"
565
621
  ];
@@ -637,11 +693,9 @@ function diffProps(instance, newProps) {
637
693
  for (const prop in instance.props) {
638
694
  if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
639
695
  const { root, key } = resolve(instance.object, prop);
640
- if (root.constructor && root.constructor.length === 0) {
696
+ if (typeof root?.constructor === "function") {
641
697
  const ctor = getMemoizedPrototype(root);
642
698
  if (!is.und(ctor)) changedProps[prop] = ctor[key];
643
- } else {
644
- changedProps[prop] = 0;
645
699
  }
646
700
  }
647
701
  return changedProps;
@@ -653,6 +707,13 @@ function applyProps(object, props) {
653
707
  for (const prop in props) {
654
708
  const value = props[prop];
655
709
  if (RESERVED_PROPS.includes(prop)) continue;
710
+ if (prop === "onUpdate") {
711
+ notifyDepreciated({
712
+ heading: "onUpdate is no longer an R3F prop",
713
+ body: "v10 removed the onUpdate hook: R3F no longer calls it after prop updates. The value is assigned to the object by name. On a Texture that sets three's Texture.onUpdate, which the renderer calls after a GPU upload, not after prop changes. On a node it replaces the Node.onUpdate() method. On other objects it is never called.\n\nUse an effect keyed on the props that change, read the object in useFrame, or use a ref callback.",
714
+ link: "https://docs.pmnd.rs/react-three-fiber/migration/v10#onupdate-removed"
715
+ });
716
+ }
656
717
  if (instance && EVENT_REGEX.test(prop)) {
657
718
  if (typeof value === "function") instance.handlers[prop] = value;
658
719
  else delete instance.handlers[prop];
@@ -758,682 +819,378 @@ function applyProps(object, props) {
758
819
  return object;
759
820
  }
760
821
 
761
- const shownNotices = /* @__PURE__ */ new Set();
762
- function notifyDepreciated({ heading, body, link }) {
763
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
764
- return;
765
- }
766
- if (shownNotices.has(heading)) return;
767
- shownNotices.add(heading);
768
- const caller = getCallerFrame();
769
- if (caller) {
770
- console.log();
771
- }
772
- const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
773
- if (caller) {
774
- console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
775
- }
776
- if (body || link) {
777
- let message = "";
778
- if (body) message += body;
779
- if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
780
- console.warn(`%c${message}`, "font-weight: bold;");
781
- }
782
- }
783
- function getCallerFrame(depth = 3) {
784
- const stack = new Error().stack;
785
- if (!stack) return null;
786
- const lines = stack.split("\n");
787
- const frame = lines[depth];
788
- if (!frame) return null;
789
- let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
790
- if (!match) {
791
- match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
792
- }
793
- if (!match) return null;
794
- const [, fn, url, line] = match;
795
- return {
796
- functionName: fn ?? "<anonymous>",
797
- location: formatLocation(url, Number(line))
798
- };
822
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
823
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
824
+
825
+ function useStore() {
826
+ const store = React.useContext(context);
827
+ if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
828
+ return store;
799
829
  }
800
- function formatLocation(url, line) {
801
- const clean = url.split("?")[0];
802
- const file = clean.split("/").pop() ?? clean;
803
- return `${file}:${line}`;
830
+ function useThree$1(selector = (state) => state, equalityFn) {
831
+ return useStore()(selector, equalityFn);
804
832
  }
805
- function notifyAlpha({ message, link }) {
806
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
807
- return;
808
- }
809
- if (shownNotices.has(message)) return;
810
- shownNotices.add(message);
811
- const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
812
- console.log(`%c\u{1F52C} ${message}`, boxStyle);
813
- {
814
- console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
833
+
834
+ const memoizedLoaders = /* @__PURE__ */ new WeakMap();
835
+ const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
836
+ function getLoader$1(Proto) {
837
+ if (isConstructor$1(Proto)) {
838
+ let loader = memoizedLoaders.get(Proto);
839
+ if (!loader) {
840
+ loader = new Proto();
841
+ memoizedLoaders.set(Proto, loader);
842
+ }
843
+ return loader;
815
844
  }
845
+ return Proto;
816
846
  }
817
-
818
- const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
819
- function isInternalRendererAccess(stack, callerDepth = 2) {
820
- if (!stack) return false;
821
- const caller = stack.split("\n")[callerDepth] ?? "";
822
- return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
847
+ function loadingFn(extensions, onProgress) {
848
+ return function(Proto, input) {
849
+ const loader = getLoader$1(Proto);
850
+ if (extensions) extensions(loader);
851
+ if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
852
+ return loader.loadAsync(input, onProgress).then((data) => {
853
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
854
+ return data;
855
+ });
856
+ }
857
+ return new Promise(
858
+ (res, reject) => loader.load(
859
+ input,
860
+ (data) => {
861
+ if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
862
+ res(data);
863
+ },
864
+ onProgress,
865
+ (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
866
+ )
867
+ );
868
+ };
869
+ }
870
+ function useLoader(loader, input, extensions, onProgress, cacheKey) {
871
+ const inputs = Array.isArray(input) ? input : [input];
872
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
873
+ const fn = loadingFn(extensions, onProgress);
874
+ const results = cacheKeys.map(
875
+ (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
876
+ );
877
+ const stableRef = React.useRef(null);
878
+ const previous = stableRef.current;
879
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
880
+ stableRef.current = stable;
881
+ return Array.isArray(input) ? stable : stable[0];
823
882
  }
883
+ useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
884
+ const inputs = Array.isArray(input) ? input : [input];
885
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
886
+ cacheKeys.forEach(
887
+ (key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
888
+ );
889
+ };
890
+ useLoader.clear = function(loader, input, cacheKey) {
891
+ const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
892
+ cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
893
+ };
894
+ useLoader.loader = getLoader$1;
824
895
 
825
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
826
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React__namespace.createContext(null));
827
- const createStore = (invalidate, advance) => {
828
- const rootStore = traditional.createWithEqualityFn((set, get) => {
829
- const position = new three.Vector3();
830
- const defaultTarget = new three.Vector3();
831
- const tempTarget = new three.Vector3();
832
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
833
- const { width, height, top, left } = size;
834
- const aspect = width / height;
835
- if (target.isVector3) tempTarget.copy(target);
836
- else tempTarget.set(...target);
837
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
838
- if (isOrthographicCamera(camera)) {
839
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
840
- } else {
841
- const fov = camera.fov * Math.PI / 180;
842
- const h = 2 * Math.tan(fov / 2) * distance;
843
- const w = h * (width / height);
844
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
896
+ function useFrame$1(callback, priorityOrOptions) {
897
+ const store = React__namespace.useContext(context);
898
+ const isInsideCanvas = store !== null;
899
+ const scheduler$1 = scheduler.getScheduler();
900
+ const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
901
+ id: priorityOrOptions.id,
902
+ phase: priorityOrOptions.phase,
903
+ priority: priorityOrOptions.priority,
904
+ fps: priorityOrOptions.fps,
905
+ drop: priorityOrOptions.drop,
906
+ enabled: priorityOrOptions.enabled,
907
+ before: priorityOrOptions.before,
908
+ after: priorityOrOptions.after
909
+ }) : "";
910
+ const options = React__namespace.useMemo(() => {
911
+ return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
912
+ }, [optionsKey]);
913
+ const reactId = React__namespace.useId();
914
+ const id = options.id ?? reactId;
915
+ const callbackRef = useMutableCallback(callback);
916
+ const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
917
+ useIsomorphicLayoutEffect(() => {
918
+ if (!callback) return;
919
+ if (isInsideCanvas) {
920
+ const state = store.getState();
921
+ const rootId = state.internal.rootId;
922
+ if (isLegacyPriority) {
923
+ state.internal.priority++;
924
+ let parentRoot = state.previousRoot;
925
+ while (parentRoot) {
926
+ const parentState = parentRoot.getState();
927
+ if (parentState?.internal) parentState.internal.priority++;
928
+ parentRoot = parentState?.previousRoot;
929
+ }
930
+ notifyDepreciated({
931
+ heading: "useFrame with numeric priority is deprecated",
932
+ body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
933
+ link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
934
+ });
845
935
  }
846
- }
847
- let performanceTimeout = void 0;
848
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
849
- const pointer = new three.Vector2();
850
- const rootState = {
851
- set,
852
- get,
853
- // Mock objects that have to be configured
854
- // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
855
- primaryStore: null,
856
- gl: null,
857
- renderer: null,
858
- camera: null,
859
- frustum: new three.Frustum(),
860
- autoUpdateFrustum: true,
861
- raycaster: null,
862
- events: {
863
- priority: 1,
864
- enabled: true,
865
- connected: false,
866
- frameTimedRaycasts: true,
867
- alwaysFireOnScroll: true,
868
- updateOnFrame: false
869
- },
870
- scene: null,
871
- rootScene: null,
872
- xr: null,
873
- inspector: null,
874
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
875
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
876
- textureColorSpace: three.SRGBColorSpace,
877
- isLegacy: false,
878
- webGPUSupported: false,
879
- isNative: false,
880
- controls: null,
881
- pointer,
882
- mouse: pointer,
883
- frameloop: "always",
884
- onPointerMissed: void 0,
885
- onDragOverMissed: void 0,
886
- onDropMissed: void 0,
887
- performance: {
888
- current: 1,
889
- min: 0.5,
890
- max: 1,
891
- debounce: 200,
892
- regress: () => {
893
- const state2 = get();
894
- if (performanceTimeout) clearTimeout(performanceTimeout);
895
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
896
- performanceTimeout = setTimeout(
897
- () => setPerformanceCurrent(get().performance.max),
898
- state2.performance.debounce
899
- );
900
- }
901
- },
902
- size: { width: 0, height: 0, top: 0, left: 0 },
903
- viewport: {
904
- initialDpr: 0,
905
- dpr: 0,
906
- width: 0,
907
- height: 0,
908
- top: 0,
909
- left: 0,
910
- aspect: 0,
911
- distance: 0,
912
- factor: 0,
913
- getCurrentViewport
914
- },
915
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
916
- setSize: (width, height, top, left) => {
917
- const state2 = get();
918
- if (width === void 0) {
919
- set({ _sizeImperative: false });
920
- if (state2._sizeProps) {
921
- const { width: propW, height: propH } = state2._sizeProps;
922
- if (propW !== void 0 || propH !== void 0) {
923
- const currentSize = state2.size;
924
- const newSize = {
925
- width: propW ?? currentSize.width,
926
- height: propH ?? currentSize.height,
927
- top: currentSize.top,
928
- left: currentSize.left
929
- };
930
- set((s) => ({
931
- size: newSize,
932
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
933
- }));
934
- scheduler.getScheduler().invalidate();
936
+ const wrappedCallback = (frameState, delta) => {
937
+ const localState = store.getState();
938
+ const mergedState = {
939
+ ...localState,
940
+ time: frameState.time,
941
+ delta: frameState.delta,
942
+ elapsed: frameState.elapsed,
943
+ frame: frameState.frame
944
+ };
945
+ callbackRef.current?.(mergedState, delta);
946
+ };
947
+ const unregister = scheduler$1.register(wrappedCallback, {
948
+ id,
949
+ rootId,
950
+ ...options
951
+ });
952
+ return () => {
953
+ unregister();
954
+ if (isLegacyPriority) {
955
+ const currentState = store.getState();
956
+ if (currentState.internal) {
957
+ currentState.internal.priority--;
958
+ let parentRoot = currentState.previousRoot;
959
+ while (parentRoot) {
960
+ const parentState = parentRoot.getState();
961
+ if (parentState?.internal) parentState.internal.priority--;
962
+ parentRoot = parentState?.previousRoot;
935
963
  }
936
964
  }
937
- return;
938
965
  }
939
- const w = width;
940
- const h = height ?? width;
941
- const t = top ?? state2.size.top;
942
- const l = left ?? state2.size.left;
943
- const size = { width: w, height: h, top: t, left: l };
944
- set((s) => ({
945
- size,
946
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
947
- _sizeImperative: true
948
- }));
949
- scheduler.getScheduler().invalidate();
966
+ };
967
+ } else {
968
+ return scheduler$1.register(
969
+ (state, delta) => {
970
+ const frameState = state;
971
+ if (!frameState.renderer) return;
972
+ callbackRef.current?.(frameState, delta);
973
+ },
974
+ { id, ...options }
975
+ );
976
+ }
977
+ }, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
978
+ const isPaused = React__namespace.useSyncExternalStore(
979
+ // Subscribe function
980
+ React__namespace.useCallback(
981
+ (onStoreChange) => {
982
+ return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
950
983
  },
951
- setDpr: (dpr) => set((state2) => {
952
- const resolved = calculateDpr(dpr);
953
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
954
- }),
955
- setFrameloop: (frameloop = "always") => {
956
- set(() => ({ frameloop }));
957
- scheduler.getScheduler().frameloop = frameloop;
984
+ [id]
985
+ ),
986
+ // getSnapshot function
987
+ React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
988
+ // getServerSnapshot function (SSR)
989
+ React__namespace.useCallback(() => false, [])
990
+ );
991
+ const controls = React__namespace.useMemo(() => {
992
+ const scheduler2 = scheduler.getScheduler();
993
+ return {
994
+ /** The job's unique ID */
995
+ id,
996
+ /**
997
+ * Access to the global scheduler for frame loop control.
998
+ * Use for controlling the entire frame loop, adding phases, etc.
999
+ */
1000
+ scheduler: scheduler2,
1001
+ /**
1002
+ * The root that currently owns this job. Resolve on access because ambient
1003
+ * jobs can be adopted by a Canvas after the controls object is created.
1004
+ */
1005
+ get rootId() {
1006
+ return scheduler.getScheduler().getJobRootId(id);
958
1007
  },
959
- setError: (error) => set(() => ({ error })),
960
- error: null,
961
- //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
962
- uniforms: {},
963
- nodes: {},
964
- buffers: {},
965
- gpuStorage: {},
966
- textures: /* @__PURE__ */ new Map(),
967
- _textureRefs: /* @__PURE__ */ new Map(),
968
- renderPipeline: null,
969
- passes: {},
970
- _hmrVersion: 0,
971
- _sizeImperative: false,
972
- _sizeProps: null,
973
- previousRoot: void 0,
974
- internal: {
975
- // Events
976
- interaction: [],
977
- subscribers: [],
978
- // Per-pointer state (new unified structure)
979
- pointerMap: /* @__PURE__ */ new Map(),
980
- pointerDirty: /* @__PURE__ */ new Map(),
981
- lastEvent: React__namespace.createRef(),
982
- // Deprecated but kept for backwards compatibility
983
- hovered: /* @__PURE__ */ new Map(),
984
- initialClick: [0, 0],
985
- initialHits: [],
986
- capturedMap: /* @__PURE__ */ new Map(),
987
- // Visibility tracking (onFramed, onOccluded, onVisible)
988
- visibilityRegistry: /* @__PURE__ */ new Map(),
989
- // Occlusion system (WebGPU only)
990
- occlusionEnabled: false,
991
- occlusionObserver: null,
992
- occlusionCache: /* @__PURE__ */ new Map(),
993
- helperGroup: null,
994
- // Updates
995
- active: false,
996
- frames: 0,
997
- priority: 0,
998
- subscribe: (ref, priority, store) => {
999
- const internal = get().internal;
1000
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
1001
- internal.subscribers.push({ ref, priority, store });
1002
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1003
- return () => {
1004
- const internal2 = get().internal;
1005
- if (internal2?.subscribers) {
1006
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
1007
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
1008
- }
1009
- };
1010
- },
1011
- // Renderer Storage (single source of truth)
1012
- actualRenderer: null,
1013
- // Scheduler for useFrameNext (initialized in renderer.tsx)
1014
- scheduler: null
1015
- }
1008
+ /**
1009
+ * Manually step this job only.
1010
+ * Bypasses FPS limiting - always runs.
1011
+ * @param timestamp Optional timestamp (defaults to performance.now())
1012
+ */
1013
+ step: (timestamp) => {
1014
+ scheduler.getScheduler().stepJob(id, timestamp);
1015
+ },
1016
+ /**
1017
+ * Manually step ALL jobs in the scheduler.
1018
+ * Useful for frameloop='never' mode.
1019
+ * @param timestamp Optional timestamp (defaults to performance.now())
1020
+ */
1021
+ stepAll: (timestamp) => {
1022
+ scheduler.getScheduler().step(timestamp);
1023
+ },
1024
+ /**
1025
+ * Request frames for this job's owning root without waking sibling roots.
1026
+ */
1027
+ invalidate: (frames, stackFrames) => {
1028
+ const scheduler3 = scheduler.getScheduler();
1029
+ const rootId = scheduler3.getJobRootId(id);
1030
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1031
+ },
1032
+ /**
1033
+ * Pause this job (set enabled=false).
1034
+ * Job remains registered but won't run.
1035
+ */
1036
+ pause: () => {
1037
+ scheduler.getScheduler().pauseJob(id);
1038
+ },
1039
+ /**
1040
+ * Resume this job (set enabled=true).
1041
+ */
1042
+ resume: () => {
1043
+ scheduler.getScheduler().resumeJob(id);
1044
+ },
1045
+ /**
1046
+ * Reactive paused state - automatically updates when pause/resume is called.
1047
+ * No need for forceUpdate() in your components.
1048
+ */
1049
+ isPaused
1016
1050
  };
1017
- return rootState;
1018
- });
1019
- const state = rootStore.getState();
1020
- Object.defineProperty(state, "gl", {
1021
- get() {
1022
- const currentState = rootStore.getState();
1023
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1024
- const stack = new Error().stack || "";
1025
- if (!isInternalRendererAccess(stack)) {
1026
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1027
- notifyDepreciated({
1028
- heading: "Accessing state.gl in WebGPU mode",
1029
- body: "Please use state.renderer instead. state.gl is deprecated and will be removed in future versions.\n\nFor backwards compatibility, state.gl currently maps to state.renderer, but this may cause issues with libraries expecting WebGLRenderer.\n\nAccessed from:\n" + cleanedStack
1030
- });
1031
- }
1032
- }
1033
- return currentState.internal.actualRenderer;
1034
- },
1035
- set(value) {
1036
- rootStore.getState().internal.actualRenderer = value;
1037
- },
1038
- enumerable: true,
1039
- configurable: true
1040
- });
1041
- Object.defineProperty(state, "renderer", {
1042
- get() {
1043
- return rootStore.getState().internal.actualRenderer;
1044
- },
1045
- set(value) {
1046
- rootStore.getState().internal.actualRenderer = value;
1047
- },
1048
- enumerable: true,
1049
- configurable: true
1050
- });
1051
- let oldScene = state.scene;
1052
- rootStore.subscribe(() => {
1053
- const currentState = rootStore.getState();
1054
- const { scene, rootScene, set } = currentState;
1055
- if (scene !== oldScene) {
1056
- oldScene = scene;
1057
- if (scene?.isScene && scene !== rootScene) {
1058
- set({ rootScene: scene });
1059
- }
1060
- }
1061
- });
1062
- let oldSize = state.size;
1063
- let oldDpr = state.viewport.dpr;
1064
- let oldCamera = state.camera;
1065
- rootStore.subscribe(() => {
1066
- const { camera, size, viewport, set, internal } = rootStore.getState();
1067
- const actualRenderer = internal.actualRenderer;
1068
- const canvasTarget = internal.canvasTarget;
1069
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1070
- oldSize = size;
1071
- oldDpr = viewport.dpr;
1072
- updateCamera(camera, size);
1073
- if (internal.isSecondary && canvasTarget) {
1074
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1075
- canvasTarget.setSize(size.width, size.height, false);
1076
- } else {
1077
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1078
- actualRenderer.setSize(size.width, size.height, false);
1079
- if (canvasTarget) {
1080
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1081
- canvasTarget.setSize(size.width, size.height, false);
1082
- }
1083
- }
1084
- }
1085
- if (camera !== oldCamera) {
1086
- oldCamera = camera;
1087
- const { rootScene } = rootStore.getState();
1088
- if (camera && rootScene && !camera.parent) {
1089
- rootScene.add(camera);
1090
- }
1091
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1092
- const currentState = rootStore.getState();
1093
- if (currentState.autoUpdateFrustum && camera) {
1094
- updateFrustum(camera, currentState.frustum);
1095
- }
1096
- }
1097
- });
1098
- rootStore.subscribe((state2) => invalidate(state2));
1099
- return rootStore;
1100
- };
1051
+ }, [id, isPaused]);
1052
+ return controls;
1053
+ }
1101
1054
 
1102
- const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1103
- const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
1104
- function getLoader$1(Proto) {
1105
- if (isConstructor$1(Proto)) {
1106
- let loader = memoizedLoaders.get(Proto);
1107
- if (!loader) {
1108
- loader = new Proto();
1109
- memoizedLoaders.set(Proto, loader);
1055
+ const IsObject = (url) => url === Object(url) && !Array.isArray(url) && typeof url !== "function";
1056
+ function getUrls(input) {
1057
+ if (typeof input === "string") return [input];
1058
+ if (Array.isArray(input)) return input;
1059
+ return Object.values(input);
1060
+ }
1061
+ function inputSignature(input) {
1062
+ if (typeof input === "string") return input;
1063
+ if (Array.isArray(input)) return input.join("\0");
1064
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1065
+ }
1066
+ function allUrlsCached(urls, textureCache) {
1067
+ return urls.every((url) => textureCache.has(url));
1068
+ }
1069
+ function buildFromCache(input, textureCache) {
1070
+ if (typeof input === "string") {
1071
+ return textureCache.get(input);
1072
+ } else if (Array.isArray(input)) {
1073
+ return input.map((url) => textureCache.get(url));
1074
+ } else {
1075
+ const result = {};
1076
+ const objInput = input;
1077
+ for (const key in objInput) {
1078
+ result[key] = textureCache.get(objInput[key]);
1110
1079
  }
1111
- return loader;
1080
+ return result;
1112
1081
  }
1113
- return Proto;
1114
1082
  }
1115
- function loadingFn(extensions, onProgress) {
1116
- return function(Proto, input) {
1117
- const loader = getLoader$1(Proto);
1118
- if (extensions) extensions(loader);
1119
- if ("loadAsync" in loader && typeof loader.loadAsync === "function") {
1120
- return loader.loadAsync(input, onProgress).then((data) => {
1121
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1122
- return data;
1083
+ function useTexture(input, optionsOrOnLoad) {
1084
+ const renderer = useThree$1((state) => state.internal.actualRenderer);
1085
+ const store = useStore();
1086
+ const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1087
+ const { onLoad, cache = true } = options;
1088
+ const onLoadRef = React.useRef(onLoad);
1089
+ onLoadRef.current = onLoad;
1090
+ const onLoadCalledForRef = React.useRef(null);
1091
+ const inputKey = React.useMemo(() => inputSignature(input), [input]);
1092
+ const stableInput = React.useMemo(() => input, [inputKey]);
1093
+ const urls = React.useMemo(() => getUrls(stableInput), [stableInput]);
1094
+ const cachedResult = React.useMemo(() => {
1095
+ if (!cache) return null;
1096
+ const textures = store.getState().textures;
1097
+ if (!allUrlsCached(urls, textures)) return null;
1098
+ return buildFromCache(stableInput, textures);
1099
+ }, [cache, urls, stableInput, store]);
1100
+ const loadedTextures = useLoader(
1101
+ three.TextureLoader,
1102
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1103
+ );
1104
+ const mappedTextures = React.useMemo(() => {
1105
+ if (cachedResult) return cachedResult;
1106
+ if (IsObject(stableInput)) {
1107
+ const keyed = {};
1108
+ const textureArray = loadedTextures;
1109
+ let i = 0;
1110
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1111
+ return keyed;
1112
+ } else {
1113
+ return loadedTextures;
1114
+ }
1115
+ }, [stableInput, loadedTextures, cachedResult]);
1116
+ React.useLayoutEffect(() => {
1117
+ if (cachedResult) return;
1118
+ if (onLoadCalledForRef.current === inputKey) return;
1119
+ onLoadCalledForRef.current = inputKey;
1120
+ onLoadRef.current?.(mappedTextures);
1121
+ }, [cachedResult, mappedTextures, inputKey]);
1122
+ React.useEffect(() => {
1123
+ if (cachedResult) return;
1124
+ if ("initTexture" in renderer) {
1125
+ let textureArray = [];
1126
+ if (Array.isArray(loadedTextures)) {
1127
+ textureArray = loadedTextures;
1128
+ } else if (loadedTextures instanceof three.Texture) {
1129
+ textureArray = [loadedTextures];
1130
+ } else if (IsObject(loadedTextures)) {
1131
+ textureArray = Object.values(loadedTextures);
1132
+ }
1133
+ textureArray.forEach((texture) => {
1134
+ if (texture instanceof three.Texture) {
1135
+ renderer.initTexture(texture);
1136
+ }
1123
1137
  });
1124
1138
  }
1125
- return new Promise(
1126
- (res, reject) => loader.load(
1127
- input,
1128
- (data) => {
1129
- if (isObject3D(data?.scene)) Object.assign(data, buildGraph(data.scene));
1130
- res(data);
1131
- },
1132
- onProgress,
1133
- (error) => reject(new Error(`Could not load ${input}: ${error?.message}`))
1134
- )
1135
- );
1136
- };
1137
- }
1138
- function useLoader(loader, input, extensions, onProgress, cacheKey) {
1139
- const inputs = Array.isArray(input) ? input : [input];
1140
- const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1141
- const fn = loadingFn(extensions, onProgress);
1142
- const results = cacheKeys.map(
1143
- (key, index) => suspendReact.suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1144
- );
1145
- return Array.isArray(input) ? results : results[0];
1139
+ }, [renderer, loadedTextures, cachedResult]);
1140
+ React.useEffect(() => {
1141
+ if (!cache) return;
1142
+ const urlTextureMap = [];
1143
+ if (typeof stableInput === "string") {
1144
+ urlTextureMap.push([stableInput, mappedTextures]);
1145
+ } else if (Array.isArray(stableInput)) {
1146
+ const textureArray = mappedTextures;
1147
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1148
+ } else if (IsObject(stableInput)) {
1149
+ const textureRecord = mappedTextures;
1150
+ for (const key in stableInput) {
1151
+ const url = stableInput[key];
1152
+ urlTextureMap.push([url, textureRecord[key]]);
1153
+ }
1154
+ }
1155
+ store.setState((state) => {
1156
+ const refs = new Map(state._textureRefs);
1157
+ let textures = state.textures;
1158
+ let added = false;
1159
+ for (const [url, texture] of urlTextureMap) {
1160
+ if (!textures.has(url)) {
1161
+ if (!added) {
1162
+ textures = new Map(textures);
1163
+ added = true;
1164
+ }
1165
+ textures.set(url, texture);
1166
+ }
1167
+ refs.set(url, (refs.get(url) ?? 0) + 1);
1168
+ }
1169
+ return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
1170
+ });
1171
+ return () => store.setState((state) => {
1172
+ const refs = new Map(state._textureRefs);
1173
+ for (const [url] of urlTextureMap) {
1174
+ const next = (refs.get(url) ?? 0) - 1;
1175
+ if (next <= 0) refs.delete(url);
1176
+ else refs.set(url, next);
1177
+ }
1178
+ return { _textureRefs: refs };
1179
+ });
1180
+ }, [cache, stableInput, mappedTextures, store]);
1181
+ return mappedTextures;
1146
1182
  }
1147
- useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1148
- const inputs = Array.isArray(input) ? input : [input];
1149
- const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1150
- cacheKeys.forEach(
1151
- (key, index) => suspendReact.preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
1152
- );
1153
- };
1154
- useLoader.clear = function(loader, input, cacheKey) {
1155
- const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
1156
- cacheKeys.forEach((key) => suspendReact.clear([loader, key]));
1157
- };
1158
- useLoader.loader = getLoader$1;
1159
-
1160
- function useFrame(callback, priorityOrOptions) {
1161
- const store = React__namespace.useContext(context);
1162
- const isInsideCanvas = store !== null;
1163
- const scheduler$1 = scheduler.getScheduler();
1164
- const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
1165
- id: priorityOrOptions.id,
1166
- phase: priorityOrOptions.phase,
1167
- priority: priorityOrOptions.priority,
1168
- fps: priorityOrOptions.fps,
1169
- drop: priorityOrOptions.drop,
1170
- enabled: priorityOrOptions.enabled,
1171
- before: priorityOrOptions.before,
1172
- after: priorityOrOptions.after
1173
- }) : "";
1174
- const options = React__namespace.useMemo(() => {
1175
- return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
1176
- }, [optionsKey]);
1177
- const reactId = React__namespace.useId();
1178
- const id = options.id ?? reactId;
1179
- const callbackRef = useMutableCallback(callback);
1180
- const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
1181
- useIsomorphicLayoutEffect(() => {
1182
- if (!callback) return;
1183
- if (isInsideCanvas) {
1184
- const state = store.getState();
1185
- const rootId = state.internal.rootId;
1186
- if (isLegacyPriority) {
1187
- state.internal.priority++;
1188
- let parentRoot = state.previousRoot;
1189
- while (parentRoot) {
1190
- const parentState = parentRoot.getState();
1191
- if (parentState?.internal) parentState.internal.priority++;
1192
- parentRoot = parentState?.previousRoot;
1193
- }
1194
- notifyDepreciated({
1195
- heading: "useFrame with numeric priority is deprecated",
1196
- body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
1197
- link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
1198
- });
1199
- }
1200
- const wrappedCallback = (frameState, delta) => {
1201
- const localState = store.getState();
1202
- const mergedState = {
1203
- ...localState,
1204
- time: frameState.time,
1205
- delta: frameState.delta,
1206
- elapsed: frameState.elapsed,
1207
- frame: frameState.frame
1208
- };
1209
- callbackRef.current?.(mergedState, delta);
1210
- };
1211
- const unregister = scheduler$1.register(wrappedCallback, {
1212
- id,
1213
- rootId,
1214
- ...options
1215
- });
1216
- return () => {
1217
- unregister();
1218
- if (isLegacyPriority) {
1219
- const currentState = store.getState();
1220
- if (currentState.internal) {
1221
- currentState.internal.priority--;
1222
- let parentRoot = currentState.previousRoot;
1223
- while (parentRoot) {
1224
- const parentState = parentRoot.getState();
1225
- if (parentState?.internal) parentState.internal.priority--;
1226
- parentRoot = parentState?.previousRoot;
1227
- }
1228
- }
1229
- }
1230
- };
1231
- } else {
1232
- return scheduler$1.register(
1233
- (state, delta) => {
1234
- const frameState = state;
1235
- if (!frameState.renderer) return;
1236
- callbackRef.current?.(frameState, delta);
1237
- },
1238
- { id, ...options }
1239
- );
1240
- }
1241
- }, [store, scheduler$1, id, optionsKey, isLegacyPriority, isInsideCanvas]);
1242
- const isPaused = React__namespace.useSyncExternalStore(
1243
- // Subscribe function
1244
- React__namespace.useCallback(
1245
- (onStoreChange) => {
1246
- return scheduler.getScheduler().subscribeJobState(id, onStoreChange);
1247
- },
1248
- [id]
1249
- ),
1250
- // getSnapshot function
1251
- React__namespace.useCallback(() => scheduler.getScheduler().isJobPaused(id), [id]),
1252
- // getServerSnapshot function (SSR)
1253
- React__namespace.useCallback(() => false, [])
1254
- );
1255
- const controls = React__namespace.useMemo(() => {
1256
- const scheduler2 = scheduler.getScheduler();
1257
- return {
1258
- /** The job's unique ID */
1259
- id,
1260
- /**
1261
- * Access to the global scheduler for frame loop control.
1262
- * Use for controlling the entire frame loop, adding phases, etc.
1263
- */
1264
- scheduler: scheduler2,
1265
- /**
1266
- * Manually step this job only.
1267
- * Bypasses FPS limiting - always runs.
1268
- * @param timestamp Optional timestamp (defaults to performance.now())
1269
- */
1270
- step: (timestamp) => {
1271
- scheduler.getScheduler().stepJob(id, timestamp);
1272
- },
1273
- /**
1274
- * Manually step ALL jobs in the scheduler.
1275
- * Useful for frameloop='never' mode.
1276
- * @param timestamp Optional timestamp (defaults to performance.now())
1277
- */
1278
- stepAll: (timestamp) => {
1279
- scheduler.getScheduler().step(timestamp);
1280
- },
1281
- /**
1282
- * Pause this job (set enabled=false).
1283
- * Job remains registered but won't run.
1284
- */
1285
- pause: () => {
1286
- scheduler.getScheduler().pauseJob(id);
1287
- },
1288
- /**
1289
- * Resume this job (set enabled=true).
1290
- */
1291
- resume: () => {
1292
- scheduler.getScheduler().resumeJob(id);
1293
- },
1294
- /**
1295
- * Reactive paused state - automatically updates when pause/resume is called.
1296
- * No need for forceUpdate() in your components.
1297
- */
1298
- isPaused
1299
- };
1300
- }, [id, isPaused]);
1301
- return controls;
1302
- }
1303
-
1304
- const IsObject = (url) => url === Object(url) && !Array.isArray(url) && typeof url !== "function";
1305
- function getUrls(input) {
1306
- if (typeof input === "string") return [input];
1307
- if (Array.isArray(input)) return input;
1308
- return Object.values(input);
1309
- }
1310
- function allUrlsCached(urls, textureCache) {
1311
- return urls.every((url) => textureCache.has(url));
1312
- }
1313
- function buildFromCache(input, textureCache) {
1314
- if (typeof input === "string") {
1315
- return textureCache.get(input);
1316
- } else if (Array.isArray(input)) {
1317
- return input.map((url) => textureCache.get(url));
1318
- } else {
1319
- const result = {};
1320
- const objInput = input;
1321
- for (const key in objInput) {
1322
- result[key] = textureCache.get(objInput[key]);
1323
- }
1324
- return result;
1325
- }
1326
- }
1327
- function useTexture(input, optionsOrOnLoad) {
1328
- const renderer = useThree((state) => state.internal.actualRenderer);
1329
- const store = useStore();
1330
- const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1331
- const { onLoad, cache = true } = options;
1332
- const onLoadRef = React.useRef(onLoad);
1333
- onLoadRef.current = onLoad;
1334
- const onLoadCalledForRef = React.useRef(null);
1335
- const urls = React.useMemo(() => getUrls(input), [input]);
1336
- const cachedResult = React.useMemo(() => {
1337
- if (!cache) return null;
1338
- const textures = store.getState().textures;
1339
- if (!allUrlsCached(urls, textures)) return null;
1340
- return buildFromCache(input, textures);
1341
- }, [cache, urls, input, store]);
1342
- const loadedTextures = useLoader(
1343
- three.TextureLoader,
1344
- IsObject(input) ? Object.values(input) : input
1345
- );
1346
- const inputKey = urls.join("\0");
1347
- React.useLayoutEffect(() => {
1348
- if (cachedResult) return;
1349
- if (onLoadCalledForRef.current === inputKey) return;
1350
- onLoadCalledForRef.current = inputKey;
1351
- onLoadRef.current?.(loadedTextures);
1352
- }, [cachedResult, loadedTextures, inputKey]);
1353
- React.useEffect(() => {
1354
- if (cachedResult) return;
1355
- if ("initTexture" in renderer) {
1356
- let textureArray = [];
1357
- if (Array.isArray(loadedTextures)) {
1358
- textureArray = loadedTextures;
1359
- } else if (loadedTextures instanceof three.Texture) {
1360
- textureArray = [loadedTextures];
1361
- } else if (IsObject(loadedTextures)) {
1362
- textureArray = Object.values(loadedTextures);
1363
- }
1364
- textureArray.forEach((texture) => {
1365
- if (texture instanceof three.Texture) {
1366
- renderer.initTexture(texture);
1367
- }
1368
- });
1369
- }
1370
- }, [renderer, loadedTextures, cachedResult]);
1371
- const mappedTextures = React.useMemo(() => {
1372
- if (cachedResult) return cachedResult;
1373
- if (IsObject(input)) {
1374
- const keyed = {};
1375
- const textureArray = loadedTextures;
1376
- let i = 0;
1377
- for (const key in input) keyed[key] = textureArray[i++];
1378
- return keyed;
1379
- } else {
1380
- return loadedTextures;
1381
- }
1382
- }, [input, loadedTextures, cachedResult]);
1383
- React.useEffect(() => {
1384
- if (!cache) return;
1385
- const urlTextureMap = [];
1386
- if (typeof input === "string") {
1387
- urlTextureMap.push([input, mappedTextures]);
1388
- } else if (Array.isArray(input)) {
1389
- const textureArray = mappedTextures;
1390
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1391
- } else if (IsObject(input)) {
1392
- const textureRecord = mappedTextures;
1393
- for (const key in input) {
1394
- const url = input[key];
1395
- urlTextureMap.push([url, textureRecord[key]]);
1396
- }
1397
- }
1398
- store.setState((state) => {
1399
- const refs = new Map(state._textureRefs);
1400
- let textures = state.textures;
1401
- let added = false;
1402
- for (const [url, texture] of urlTextureMap) {
1403
- if (!textures.has(url)) {
1404
- if (!added) {
1405
- textures = new Map(textures);
1406
- added = true;
1407
- }
1408
- textures.set(url, texture);
1409
- }
1410
- refs.set(url, (refs.get(url) ?? 0) + 1);
1411
- }
1412
- return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
1413
- });
1414
- return () => store.setState((state) => {
1415
- const refs = new Map(state._textureRefs);
1416
- for (const [url] of urlTextureMap) {
1417
- const next = (refs.get(url) ?? 0) - 1;
1418
- if (next <= 0) refs.delete(url);
1419
- else refs.set(url, next);
1420
- }
1421
- return { _textureRefs: refs };
1422
- });
1423
- }, [cache, input, mappedTextures, store]);
1424
- return mappedTextures;
1425
- }
1426
- useTexture.preload = (url) => useLoader.preload(three.TextureLoader, url);
1427
- useTexture.clear = (input) => useLoader.clear(three.TextureLoader, input);
1428
- const Texture = ({
1429
- children,
1430
- input,
1431
- onLoad,
1432
- cache
1433
- }) => {
1434
- const options = typeof onLoad === "function" ? { onLoad, cache } : { ...onLoad, cache };
1435
- const ret = useTexture(input, options);
1436
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
1183
+ useTexture.preload = (url) => useLoader.preload(three.TextureLoader, url);
1184
+ useTexture.clear = (input) => useLoader.clear(three.TextureLoader, input);
1185
+ const Texture = ({
1186
+ children,
1187
+ input,
1188
+ onLoad,
1189
+ cache
1190
+ }) => {
1191
+ const options = typeof onLoad === "function" ? { onLoad, cache } : { ...onLoad, cache };
1192
+ const ret = useTexture(input, options);
1193
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children?.(ret) });
1437
1194
  };
1438
1195
 
1439
1196
  function useTextures(selector) {
@@ -1491,13 +1248,13 @@ function useTextures(selector) {
1491
1248
  };
1492
1249
  }, [store]);
1493
1250
  const subscribe = selector ? () => selector(registry) : (state) => state.textures;
1494
- const selected = useThree(subscribe);
1251
+ const selected = useThree$1(subscribe);
1495
1252
  return selector ? selected : registry;
1496
1253
  }
1497
1254
 
1498
1255
  function useRenderTarget(widthOrOptions, heightOrOptions, options) {
1499
- const isLegacy = useThree((s) => s.isLegacy);
1500
- const size = useThree((s) => s.size);
1256
+ const isLegacy = useThree$1((s) => s.isLegacy);
1257
+ const size = useThree$1((s) => s.size);
1501
1258
  let width;
1502
1259
  let height;
1503
1260
  let opts;
@@ -1554,7 +1311,7 @@ function useEnvironment({
1554
1311
  const { extension, isCubemap } = getExtension(files);
1555
1312
  const loader = getLoader(extension);
1556
1313
  if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
1557
- const renderer = useThree((state) => state.renderer);
1314
+ const renderer = useThree$1((state) => state.renderer);
1558
1315
  React.useLayoutEffect(() => {
1559
1316
  if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
1560
1317
  function clearGainmapTexture() {
@@ -1581,7 +1338,7 @@ function useEnvironment({
1581
1338
  texture = texture.renderTarget?.texture;
1582
1339
  }
1583
1340
  texture.mapping = isCubemap ? three.CubeReflectionMapping : three.EquirectangularReflectionMapping;
1584
- texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
1341
+ texture.colorSpace = colorSpace ?? (isCubemap && extension !== "hdr-cube" ? "srgb" : "srgb-linear");
1585
1342
  return texture;
1586
1343
  }
1587
1344
  const preloadDefaultOptions = {
@@ -1634,22 +1391,15 @@ function getExtension(files) {
1634
1391
  const isCubemap = isArray(files) && files.length === 6;
1635
1392
  const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
1636
1393
  const firstEntry = isArray(files) ? files[0] : files;
1637
- const extension = isCubemap ? "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
1394
+ const firstExtension = firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
1395
+ const extension = isCubemap ? firstExtension === "hdr" ? "hdr-cube" : "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstExtension;
1638
1396
  return { extension, isCubemap, isGainmap };
1639
1397
  }
1640
1398
  function getLoader(extension) {
1641
- const loader = extension === "cube" ? three.CubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
1399
+ const loader = extension === "cube" ? three.CubeTextureLoader : extension === "hdr-cube" ? HDRCubeTextureLoader_js.HDRCubeTextureLoader : extension === "hdr" ? HDRLoader_js.HDRLoader : extension === "exr" ? EXRLoader_js.EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader_js.UltraHDRLoader : extension === "webp" ? gainmapJs.GainMapLoader : null;
1642
1400
  return loader;
1643
1401
  }
1644
1402
 
1645
- function useStore() {
1646
- const store = React.useContext(context);
1647
- if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1648
- return store;
1649
- }
1650
- function useThree(selector = (state) => state, equalityFn) {
1651
- return useStore()(selector, equalityFn);
1652
- }
1653
1403
  function useInstanceHandle(ref) {
1654
1404
  const instance = React.useRef(null);
1655
1405
  React.useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
@@ -1689,13 +1439,19 @@ function addTail(callback) {
1689
1439
  return scheduler.getScheduler().onIdle(callback);
1690
1440
  }
1691
1441
  function invalidate(state, frames = 1, stackFrames = false) {
1692
- scheduler.getScheduler().invalidate(frames, stackFrames);
1442
+ const rootId = state?.internal?.rootId;
1443
+ const scheduler$1 = scheduler.getScheduler();
1444
+ if (rootId) scheduler$1.invalidateRoot(rootId, frames, stackFrames);
1445
+ else scheduler$1.invalidate(frames, stackFrames);
1693
1446
  }
1694
- function advance(timestamp) {
1695
- scheduler.getScheduler().step(timestamp);
1447
+ function advance(timestamp, runGlobalEffects, state) {
1448
+ const rootId = state?.internal?.rootId;
1449
+ const scheduler$1 = scheduler.getScheduler();
1450
+ if (rootId) scheduler$1.stepRoot(rootId, timestamp);
1451
+ else scheduler$1.step(timestamp);
1696
1452
  }
1697
1453
 
1698
- const version = "10.0.0-alpha.3";
1454
+ const version = "10.0.0-alpha.5";
1699
1455
  const packageData = {
1700
1456
  version: version};
1701
1457
 
@@ -13554,7 +13310,10 @@ function extend(objects) {
13554
13310
  catalogue[Component] = objects;
13555
13311
  return Component;
13556
13312
  } else {
13557
- Object.assign(catalogue, objects);
13313
+ for (const name in objects) {
13314
+ const object = objects[name];
13315
+ if (isConstructor(object)) catalogue[name] = object;
13316
+ }
13558
13317
  }
13559
13318
  }
13560
13319
  function validateInstance(type, props) {
@@ -13902,7 +13661,7 @@ const reconciler = /* @__PURE__ */ createReconciler({
13902
13661
  getRootHostContext: () => NO_CONTEXT,
13903
13662
  getChildHostContext: () => NO_CONTEXT,
13904
13663
  commitUpdate(instance, type, oldProps, newProps, fiber) {
13905
- validateInstance(type, newProps);
13664
+ validateInstance(instance.type, newProps);
13906
13665
  let reconstruct = false;
13907
13666
  if (instance.type === "primitive" && oldProps.object !== newProps.object) reconstruct = true;
13908
13667
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
@@ -13917,162 +13676,491 @@ const reconciler = /* @__PURE__ */ createReconciler({
13917
13676
  else delete instance.props.attach;
13918
13677
  if (Object.keys(changedProps).length) applyProps(instance.object, changedProps);
13919
13678
  }
13920
- },
13921
- finalizeInitialChildren: (instance) => {
13922
- for (const prop in instance.props) {
13923
- if (isFromRef(instance.props[prop])) return true;
13679
+ },
13680
+ finalizeInitialChildren: (instance) => {
13681
+ for (const prop in instance.props) {
13682
+ if (isFromRef(instance.props[prop])) return true;
13683
+ }
13684
+ return false;
13685
+ },
13686
+ commitMount(instance) {
13687
+ const resolved = {};
13688
+ for (const prop in instance.props) {
13689
+ const value = instance.props[prop];
13690
+ if (isFromRef(value)) {
13691
+ const { [FROM_REF]: ref, transform } = value;
13692
+ if (ref.current != null) resolved[prop] = transform ? transform(ref.current) : ref.current;
13693
+ }
13694
+ }
13695
+ if (Object.keys(resolved).length) applyProps(instance.object, resolved);
13696
+ },
13697
+ getPublicInstance: (instance) => instance?.object,
13698
+ prepareForCommit: () => null,
13699
+ preparePortalMount: (container) => {
13700
+ const target = container.getState().internal.container ?? container.getState().scene;
13701
+ return prepare(target, container, "", {});
13702
+ },
13703
+ // Reconstructed instances are swapped once all mutations are committed,
13704
+ // before layout effects run so refs point to the new objects
13705
+ resetAfterCommit: flushReconstructedInstances,
13706
+ shouldSetTextContent: () => false,
13707
+ clearContainer: () => false,
13708
+ hideInstance,
13709
+ unhideInstance,
13710
+ createTextInstance: handleTextInstance,
13711
+ hideTextInstance: handleTextInstance,
13712
+ unhideTextInstance: handleTextInstance,
13713
+ // Mirrors react-dom, which flushes discrete work in microtasks
13714
+ supportsMicrotasks: true,
13715
+ scheduleMicrotask,
13716
+ scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
13717
+ cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
13718
+ noTimeout: -1,
13719
+ getInstanceFromNode: () => null,
13720
+ beforeActiveInstanceBlur() {
13721
+ },
13722
+ afterActiveInstanceBlur() {
13723
+ },
13724
+ detachDeletedInstance() {
13725
+ },
13726
+ prepareScopeUpdate() {
13727
+ },
13728
+ getInstanceFromScope: () => null,
13729
+ shouldAttemptEagerTransition: () => false,
13730
+ trackSchedulerEvent: () => {
13731
+ },
13732
+ resolveEventType: () => null,
13733
+ resolveEventTimeStamp: () => -1.1,
13734
+ requestPostPaintCallback() {
13735
+ },
13736
+ maySuspendCommit: () => false,
13737
+ preloadInstance: () => true,
13738
+ // true indicates already loaded
13739
+ suspendInstance() {
13740
+ },
13741
+ waitForCommitToBeReady: () => null,
13742
+ NotPendingTransition: null,
13743
+ // The reconciler types use the internal ReactContext with all the hidden properties
13744
+ // so we have to cast from the public React.Context type
13745
+ HostTransitionContext: /* @__PURE__ */ React__namespace.createContext(
13746
+ null
13747
+ ),
13748
+ setCurrentUpdatePriority(newPriority) {
13749
+ currentUpdatePriority = newPriority;
13750
+ },
13751
+ getCurrentUpdatePriority() {
13752
+ return currentUpdatePriority;
13753
+ },
13754
+ resolveUpdatePriority() {
13755
+ if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
13756
+ const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
13757
+ if (eventType === void 0) return r;
13758
+ return getEventPriority(eventType);
13759
+ },
13760
+ resetFormInstance() {
13761
+ },
13762
+ // @ts-ignore DefinitelyTyped is not up to date
13763
+ rendererPackageName: "@react-three/fiber",
13764
+ rendererVersion: packageData.version,
13765
+ // 19.2 Reconciler
13766
+ // https://github.com/facebook/react/pull/31975
13767
+ // https://github.com/facebook/react/pull/31999
13768
+ applyViewTransitionName(_instance, _name, _className) {
13769
+ },
13770
+ restoreViewTransitionName(_instance, _props) {
13771
+ },
13772
+ cancelViewTransitionName(_instance, _name, _props) {
13773
+ },
13774
+ cancelRootViewTransitionName(_rootContainer) {
13775
+ },
13776
+ restoreRootViewTransitionName(_rootContainer) {
13777
+ },
13778
+ InstanceMeasurement: null,
13779
+ measureInstance: (_instance) => null,
13780
+ wasInstanceInViewport: (_measurement) => true,
13781
+ hasInstanceChanged: (_oldMeasurement, _newMeasurement) => false,
13782
+ hasInstanceAffectedParent: (_oldMeasurement, _newMeasurement) => false,
13783
+ // https://github.com/facebook/react/pull/32002
13784
+ // https://github.com/facebook/react/pull/34486
13785
+ suspendOnActiveViewTransition(_state, _container) {
13786
+ },
13787
+ // https://github.com/facebook/react/pull/32451
13788
+ // https://github.com/facebook/react/pull/32760
13789
+ startGestureTransition: () => null,
13790
+ startViewTransition: () => null,
13791
+ stopViewTransition(_transition) {
13792
+ },
13793
+ // https://github.com/facebook/react/pull/32038
13794
+ createViewTransitionInstance: (_name) => null,
13795
+ // https://github.com/facebook/react/pull/32379
13796
+ // https://github.com/facebook/react/pull/32786
13797
+ getCurrentGestureOffset(_provider) {
13798
+ throw new Error("startGestureTransition is not yet supported in react-three-fiber.");
13799
+ },
13800
+ // https://github.com/facebook/react/pull/32500
13801
+ cloneMutableInstance(instance, _keepChildren) {
13802
+ return instance;
13803
+ },
13804
+ cloneMutableTextInstance(textInstance) {
13805
+ return textInstance;
13806
+ },
13807
+ // React 19.2 added these for root-level view transitions. They only run for hosts that opt into
13808
+ // view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
13809
+ // into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
13810
+ // React never calls them. They stay as no-ops rather than throws: the host config is shared by
13811
+ // every root, and a throw here would turn a React-internal probe into a hard crash for users.
13812
+ // Guarded by a test asserting a full render + unmount never reaches them.
13813
+ cloneRootViewTransitionContainer(_rootContainer) {
13814
+ },
13815
+ removeRootViewTransitionClone(_rootContainer, _clone) {
13816
+ },
13817
+ // https://github.com/facebook/react/pull/32465
13818
+ createFragmentInstance: (_fiber) => null,
13819
+ updateFragmentInstanceFiber(_fiber, _instance) {
13820
+ },
13821
+ commitNewChildToFragmentInstance(_child, _fragmentInstance) {
13822
+ },
13823
+ deleteChildFromFragmentInstance(_child, _fragmentInstance) {
13824
+ },
13825
+ // https://github.com/facebook/react/pull/32653
13826
+ measureClonedInstance: (_instance) => null,
13827
+ // https://github.com/facebook/react/pull/32819
13828
+ maySuspendCommitOnUpdate: (_type, _oldProps, _newProps) => false,
13829
+ maySuspendCommitInSyncRender: (_type, _props) => false,
13830
+ // https://github.com/facebook/react/pull/34486
13831
+ startSuspendingCommit: () => null,
13832
+ // https://github.com/facebook/react/pull/34522
13833
+ getSuspendedCommitReason: (_state, _rootContainer) => null
13834
+ });
13835
+
13836
+ const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
13837
+ function isInternalRendererAccess(stack, callerDepth = 2) {
13838
+ if (!stack) return false;
13839
+ const caller = stack.split("\n")[callerDepth] ?? "";
13840
+ return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
13841
+ }
13842
+
13843
+ const createStore = (invalidate, advance) => {
13844
+ const rootStore = traditional.createWithEqualityFn((set, get) => {
13845
+ const position = new three.Vector3();
13846
+ const defaultTarget = new three.Vector3();
13847
+ const tempTarget = new three.Vector3();
13848
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
13849
+ const { width, height, top, left } = size;
13850
+ const aspect = width / height;
13851
+ if (target.isVector3) tempTarget.copy(target);
13852
+ else tempTarget.set(...target);
13853
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
13854
+ if (isOrthographicCamera(camera)) {
13855
+ return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
13856
+ } else {
13857
+ const fov = camera.fov * Math.PI / 180;
13858
+ const h = 2 * Math.tan(fov / 2) * distance;
13859
+ const w = h * (width / height);
13860
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
13861
+ }
13862
+ }
13863
+ let performanceTimeout = void 0;
13864
+ const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
13865
+ const pointer = new three.Vector2();
13866
+ const rootState = {
13867
+ set,
13868
+ get,
13869
+ // Mock objects that have to be configured
13870
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
13871
+ primaryStore: null,
13872
+ gl: null,
13873
+ renderer: null,
13874
+ camera: null,
13875
+ frustum: new three.Frustum(),
13876
+ autoUpdateFrustum: true,
13877
+ raycaster: null,
13878
+ events: {
13879
+ priority: 1,
13880
+ enabled: true,
13881
+ connected: false,
13882
+ frameTimedRaycasts: true,
13883
+ alwaysFireOnScroll: true,
13884
+ updateOnFrame: false
13885
+ },
13886
+ scene: null,
13887
+ rootScene: null,
13888
+ xr: null,
13889
+ inspector: null,
13890
+ invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
13891
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
13892
+ textureColorSpace: three.SRGBColorSpace,
13893
+ isLegacy: false,
13894
+ webGPUSupported: false,
13895
+ isNative: false,
13896
+ controls: null,
13897
+ pointer,
13898
+ mouse: pointer,
13899
+ frameloop: "always",
13900
+ onPointerMissed: void 0,
13901
+ onDragOverMissed: void 0,
13902
+ onDropMissed: void 0,
13903
+ performance: {
13904
+ current: 1,
13905
+ min: 0.5,
13906
+ max: 1,
13907
+ debounce: 200,
13908
+ regress: () => {
13909
+ const state2 = get();
13910
+ if (performanceTimeout) clearTimeout(performanceTimeout);
13911
+ if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
13912
+ performanceTimeout = setTimeout(
13913
+ () => setPerformanceCurrent(get().performance.max),
13914
+ state2.performance.debounce
13915
+ );
13916
+ }
13917
+ },
13918
+ size: { width: 0, height: 0, top: 0, left: 0 },
13919
+ viewport: {
13920
+ initialDpr: 0,
13921
+ dpr: 0,
13922
+ width: 0,
13923
+ height: 0,
13924
+ top: 0,
13925
+ left: 0,
13926
+ aspect: 0,
13927
+ distance: 0,
13928
+ factor: 0,
13929
+ getCurrentViewport
13930
+ },
13931
+ setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
13932
+ setSize: (width, height, top, left) => {
13933
+ const state2 = get();
13934
+ if (width === void 0) {
13935
+ set({ _sizeImperative: false });
13936
+ if (state2._sizeProps) {
13937
+ const { width: propW, height: propH } = state2._sizeProps;
13938
+ if (propW !== void 0 || propH !== void 0) {
13939
+ const currentSize = state2.size;
13940
+ const newSize = {
13941
+ width: propW ?? currentSize.width,
13942
+ height: propH ?? currentSize.height,
13943
+ top: currentSize.top,
13944
+ left: currentSize.left
13945
+ };
13946
+ set((s) => ({
13947
+ size: newSize,
13948
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
13949
+ }));
13950
+ get().invalidate();
13951
+ }
13952
+ }
13953
+ return;
13954
+ }
13955
+ const w = width;
13956
+ const h = height ?? width;
13957
+ const t = top ?? state2.size.top;
13958
+ const l = left ?? state2.size.left;
13959
+ const size = { width: w, height: h, top: t, left: l };
13960
+ set((s) => ({
13961
+ size,
13962
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
13963
+ _sizeImperative: true
13964
+ }));
13965
+ get().invalidate();
13966
+ },
13967
+ setDpr: (dpr) => set((state2) => {
13968
+ const resolved = calculateDpr(dpr);
13969
+ return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
13970
+ }),
13971
+ setFrameloop: (frameloop = "always") => {
13972
+ const rootId = get().internal.rootId;
13973
+ if (rootId) scheduler.getScheduler().setRootFrameloop(rootId, frameloop);
13974
+ set(() => ({ frameloop }));
13975
+ },
13976
+ setError: (error) => set(() => ({ error })),
13977
+ error: null,
13978
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
13979
+ uniforms: {},
13980
+ nodes: {},
13981
+ buffers: {},
13982
+ gpuStorage: {},
13983
+ textures: /* @__PURE__ */ new Map(),
13984
+ _textureRefs: /* @__PURE__ */ new Map(),
13985
+ renderPipeline: null,
13986
+ passes: {},
13987
+ _hmrVersion: 0,
13988
+ _sizeImperative: false,
13989
+ _sizeProps: null,
13990
+ previousRoot: void 0,
13991
+ internal: {
13992
+ // Events
13993
+ interaction: [],
13994
+ subscribers: [],
13995
+ // Per-pointer state (new unified structure)
13996
+ pointerMap: /* @__PURE__ */ new Map(),
13997
+ pointerDirty: /* @__PURE__ */ new Map(),
13998
+ lastEvent: React__namespace.createRef(),
13999
+ // Deprecated but kept for backwards compatibility
14000
+ hovered: /* @__PURE__ */ new Map(),
14001
+ initialClick: [0, 0],
14002
+ initialHits: [],
14003
+ capturedMap: /* @__PURE__ */ new Map(),
14004
+ // Visibility tracking (onFramed, onOccluded, onVisible)
14005
+ visibilityRegistry: /* @__PURE__ */ new Map(),
14006
+ // Occlusion system (WebGPU only)
14007
+ occlusionEnabled: false,
14008
+ occlusionObserver: null,
14009
+ occlusionCache: /* @__PURE__ */ new Map(),
14010
+ helperGroup: null,
14011
+ // Updates
14012
+ active: false,
14013
+ frames: 0,
14014
+ priority: 0,
14015
+ subscribe: (ref, priority, store) => {
14016
+ const internal = get().internal;
14017
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
14018
+ internal.subscribers.push({ ref, priority, store });
14019
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
14020
+ return () => {
14021
+ const internal2 = get().internal;
14022
+ if (internal2?.subscribers) {
14023
+ internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
14024
+ internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
14025
+ }
14026
+ };
14027
+ },
14028
+ // Renderer Storage (single source of truth)
14029
+ actualRenderer: null,
14030
+ // Scheduler for useFrameNext (initialized in renderer.tsx)
14031
+ scheduler: null,
14032
+ // Replaces the default render call when set (see setRenderOverride)
14033
+ renderOverride: null
14034
+ }
14035
+ };
14036
+ return rootState;
14037
+ });
14038
+ const state = rootStore.getState();
14039
+ Object.defineProperty(state, "gl", {
14040
+ get() {
14041
+ const currentState = rootStore.getState();
14042
+ if (!currentState.isLegacy && currentState.internal.actualRenderer) {
14043
+ const stack = new Error().stack || "";
14044
+ if (!isInternalRendererAccess(stack)) {
14045
+ const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
14046
+ notifyDepreciated({
14047
+ heading: "Accessing state.gl in WebGPU mode",
14048
+ body: "Please use state.renderer instead. state.gl is deprecated and will be removed in future versions.\n\nFor backwards compatibility, state.gl currently maps to state.renderer, but this may cause issues with libraries expecting WebGLRenderer.\n\nAccessed from:\n" + cleanedStack
14049
+ });
14050
+ }
14051
+ }
14052
+ return currentState.internal.actualRenderer;
14053
+ },
14054
+ set(value) {
14055
+ rootStore.getState().internal.actualRenderer = value;
14056
+ },
14057
+ enumerable: true,
14058
+ configurable: true
14059
+ });
14060
+ Object.defineProperty(state, "renderer", {
14061
+ get() {
14062
+ return rootStore.getState().internal.actualRenderer;
14063
+ },
14064
+ set(value) {
14065
+ rootStore.getState().internal.actualRenderer = value;
14066
+ },
14067
+ enumerable: true,
14068
+ configurable: true
14069
+ });
14070
+ let oldScene = state.scene;
14071
+ rootStore.subscribe(() => {
14072
+ const currentState = rootStore.getState();
14073
+ const { scene, rootScene, set } = currentState;
14074
+ if (scene !== oldScene) {
14075
+ oldScene = scene;
14076
+ if (scene?.isScene && scene !== rootScene) {
14077
+ set({ rootScene: scene });
14078
+ }
14079
+ }
14080
+ });
14081
+ let oldSize = state.size;
14082
+ let oldDpr = state.viewport.dpr;
14083
+ let oldCamera = state.camera;
14084
+ rootStore.subscribe(() => {
14085
+ const { camera, size, viewport, set, internal } = rootStore.getState();
14086
+ const actualRenderer = internal.actualRenderer;
14087
+ const canvasTarget = internal.canvasTarget;
14088
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
14089
+ oldSize = size;
14090
+ oldDpr = viewport.dpr;
14091
+ updateCamera(camera, size);
14092
+ const resizeTarget = canvasTarget ?? actualRenderer;
14093
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
14094
+ resizeTarget.setSize(size.width, size.height, false);
14095
+ const activeTarget = actualRenderer.getCanvasTarget?.();
14096
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
13924
14097
  }
13925
- return false;
13926
- },
13927
- commitMount(instance) {
13928
- const resolved = {};
13929
- for (const prop in instance.props) {
13930
- const value = instance.props[prop];
13931
- if (isFromRef(value)) {
13932
- const ref = value[FROM_REF];
13933
- if (ref.current != null) resolved[prop] = ref.current;
14098
+ if (camera !== oldCamera) {
14099
+ oldCamera = camera;
14100
+ const { rootScene } = rootStore.getState();
14101
+ if (camera && rootScene && !camera.parent) {
14102
+ rootScene.add(camera);
14103
+ }
14104
+ set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
14105
+ const currentState = rootStore.getState();
14106
+ if (currentState.autoUpdateFrustum && camera) {
14107
+ updateFrustum(camera, currentState.frustum);
13934
14108
  }
13935
14109
  }
13936
- if (Object.keys(resolved).length) applyProps(instance.object, resolved);
13937
- },
13938
- getPublicInstance: (instance) => instance?.object,
13939
- prepareForCommit: () => null,
13940
- preparePortalMount: (container) => {
13941
- const target = container.getState().internal.container ?? container.getState().scene;
13942
- return prepare(target, container, "", {});
13943
- },
13944
- // Reconstructed instances are swapped once all mutations are committed,
13945
- // before layout effects run so refs point to the new objects
13946
- resetAfterCommit: flushReconstructedInstances,
13947
- shouldSetTextContent: () => false,
13948
- clearContainer: () => false,
13949
- hideInstance,
13950
- unhideInstance,
13951
- createTextInstance: handleTextInstance,
13952
- hideTextInstance: handleTextInstance,
13953
- unhideTextInstance: handleTextInstance,
13954
- // Mirrors react-dom, which flushes discrete work in microtasks
13955
- supportsMicrotasks: true,
13956
- scheduleMicrotask,
13957
- scheduleTimeout: typeof setTimeout === "function" ? setTimeout : void 0,
13958
- cancelTimeout: typeof clearTimeout === "function" ? clearTimeout : void 0,
13959
- noTimeout: -1,
13960
- getInstanceFromNode: () => null,
13961
- beforeActiveInstanceBlur() {
13962
- },
13963
- afterActiveInstanceBlur() {
13964
- },
13965
- detachDeletedInstance() {
13966
- },
13967
- prepareScopeUpdate() {
13968
- },
13969
- getInstanceFromScope: () => null,
13970
- shouldAttemptEagerTransition: () => false,
13971
- trackSchedulerEvent: () => {
13972
- },
13973
- resolveEventType: () => null,
13974
- resolveEventTimeStamp: () => -1.1,
13975
- requestPostPaintCallback() {
13976
- },
13977
- maySuspendCommit: () => false,
13978
- preloadInstance: () => true,
13979
- // true indicates already loaded
13980
- suspendInstance() {
13981
- },
13982
- waitForCommitToBeReady: () => null,
13983
- NotPendingTransition: null,
13984
- // The reconciler types use the internal ReactContext with all the hidden properties
13985
- // so we have to cast from the public React.Context type
13986
- HostTransitionContext: /* @__PURE__ */ React__namespace.createContext(
13987
- null
13988
- ),
13989
- setCurrentUpdatePriority(newPriority) {
13990
- currentUpdatePriority = newPriority;
13991
- },
13992
- getCurrentUpdatePriority() {
13993
- return currentUpdatePriority;
13994
- },
13995
- resolveUpdatePriority() {
13996
- if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
13997
- const eventType = typeof window !== "undefined" ? window.event?.type : void 0;
13998
- if (eventType === void 0) return r;
13999
- return getEventPriority(eventType);
14000
- },
14001
- resetFormInstance() {
14002
- },
14003
- // @ts-ignore DefinitelyTyped is not up to date
14004
- rendererPackageName: "@react-three/fiber",
14005
- rendererVersion: packageData.version,
14006
- // 19.2 Reconciler
14007
- // https://github.com/facebook/react/pull/31975
14008
- // https://github.com/facebook/react/pull/31999
14009
- applyViewTransitionName(_instance, _name, _className) {
14010
- },
14011
- restoreViewTransitionName(_instance, _props) {
14012
- },
14013
- cancelViewTransitionName(_instance, _name, _props) {
14014
- },
14015
- cancelRootViewTransitionName(_rootContainer) {
14016
- },
14017
- restoreRootViewTransitionName(_rootContainer) {
14018
- },
14019
- InstanceMeasurement: null,
14020
- measureInstance: (_instance) => null,
14021
- wasInstanceInViewport: (_measurement) => true,
14022
- hasInstanceChanged: (_oldMeasurement, _newMeasurement) => false,
14023
- hasInstanceAffectedParent: (_oldMeasurement, _newMeasurement) => false,
14024
- // https://github.com/facebook/react/pull/32002
14025
- // https://github.com/facebook/react/pull/34486
14026
- suspendOnActiveViewTransition(_state, _container) {
14027
- },
14028
- // https://github.com/facebook/react/pull/32451
14029
- // https://github.com/facebook/react/pull/32760
14030
- startGestureTransition: () => null,
14031
- startViewTransition: () => null,
14032
- stopViewTransition(_transition) {
14033
- },
14034
- // https://github.com/facebook/react/pull/32038
14035
- createViewTransitionInstance: (_name) => null,
14036
- // https://github.com/facebook/react/pull/32379
14037
- // https://github.com/facebook/react/pull/32786
14038
- getCurrentGestureOffset(_provider) {
14039
- throw new Error("startGestureTransition is not yet supported in react-three-fiber.");
14040
- },
14041
- // https://github.com/facebook/react/pull/32500
14042
- cloneMutableInstance(instance, _keepChildren) {
14043
- return instance;
14044
- },
14045
- cloneMutableTextInstance(textInstance) {
14046
- return textInstance;
14047
- },
14048
- // React 19.2 added these for root-level view transitions. They only run for hosts that opt into
14049
- // view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
14050
- // into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
14051
- // React never calls them. They stay as no-ops rather than throws: the host config is shared by
14052
- // every root, and a throw here would turn a React-internal probe into a hard crash for users.
14053
- // Guarded by a test asserting a full render + unmount never reaches them.
14054
- cloneRootViewTransitionContainer(_rootContainer) {
14055
- },
14056
- removeRootViewTransitionClone(_rootContainer, _clone) {
14057
- },
14058
- // https://github.com/facebook/react/pull/32465
14059
- createFragmentInstance: (_fiber) => null,
14060
- updateFragmentInstanceFiber(_fiber, _instance) {
14061
- },
14062
- commitNewChildToFragmentInstance(_child, _fragmentInstance) {
14063
- },
14064
- deleteChildFromFragmentInstance(_child, _fragmentInstance) {
14065
- },
14066
- // https://github.com/facebook/react/pull/32653
14067
- measureClonedInstance: (_instance) => null,
14068
- // https://github.com/facebook/react/pull/32819
14069
- maySuspendCommitOnUpdate: (_type, _oldProps, _newProps) => false,
14070
- maySuspendCommitInSyncRender: (_type, _props) => false,
14071
- // https://github.com/facebook/react/pull/34486
14072
- startSuspendingCommit: () => null,
14073
- // https://github.com/facebook/react/pull/34522
14074
- getSuspendedCommitReason: (_state, _rootContainer) => null
14075
- });
14110
+ });
14111
+ rootStore.subscribe((state2) => invalidate(state2));
14112
+ return rootStore;
14113
+ };
14114
+
14115
+ const R3F_EXTENSIONS = Symbol.for("@react-three/fiber.extensions");
14116
+ function getRegistry() {
14117
+ return globalThis[R3F_EXTENSIONS] ?? (globalThis[R3F_EXTENSIONS] = {
14118
+ extensions: /* @__PURE__ */ new Map(),
14119
+ roots: /* @__PURE__ */ new Set(),
14120
+ applied: /* @__PURE__ */ new WeakMap()
14121
+ });
14122
+ }
14123
+ function setupRoot(registry, store, extension) {
14124
+ let applied = registry.applied.get(store);
14125
+ if (!applied) registry.applied.set(store, applied = /* @__PURE__ */ new Map());
14126
+ if (applied.has(extension.name)) return;
14127
+ applied.set(extension.name, extension);
14128
+ const patch = extension.setup?.(store);
14129
+ if (patch) store.setState(patch);
14130
+ }
14131
+ function registerRootExtension(extension) {
14132
+ const registry = getRegistry();
14133
+ registry.extensions.set(extension.name, extension);
14134
+ for (const store of registry.roots) setupRoot(registry, store, extension);
14135
+ return () => {
14136
+ if (registry.extensions.get(extension.name) === extension) registry.extensions.delete(extension.name);
14137
+ };
14138
+ }
14139
+ function setRenderOverride(store, render) {
14140
+ store.getState().internal.renderOverride = render;
14141
+ }
14142
+ function attachRootExtensions(store) {
14143
+ const registry = getRegistry();
14144
+ registry.roots.add(store);
14145
+ for (const extension of registry.extensions.values()) setupRoot(registry, store, extension);
14146
+ }
14147
+ function current(registry, setUpBy) {
14148
+ return registry.extensions.get(setUpBy.name) ?? setUpBy;
14149
+ }
14150
+ function detachRootExtensions(store) {
14151
+ const registry = getRegistry();
14152
+ if (!registry.roots.delete(store)) return;
14153
+ const applied = registry.applied.get(store);
14154
+ registry.applied.delete(store);
14155
+ if (!applied) return;
14156
+ for (const setUpBy of applied.values()) current(registry, setUpBy).dispose?.(store);
14157
+ }
14158
+ function notifyRootExtensionsHmr(store) {
14159
+ const registry = getRegistry();
14160
+ const applied = registry.applied.get(store);
14161
+ if (!applied) return;
14162
+ for (const setUpBy of applied.values()) current(registry, setUpBy).hmr?.(store);
14163
+ }
14076
14164
 
14077
14165
  const isRenderer = (def) => !!def?.render;
14078
14166
  const _roots = /* @__PURE__ */ new Map();
@@ -14307,7 +14395,7 @@ function createRoot(canvas) {
14307
14395
  const handleXRFrame = (timestamp, _frame) => {
14308
14396
  const state2 = store.getState();
14309
14397
  if (state2.frameloop === "never") return;
14310
- advance(timestamp);
14398
+ advance(timestamp, true, state2);
14311
14399
  };
14312
14400
  const actualRenderer = state.internal.actualRenderer;
14313
14401
  const handleSessionChange = () => {
@@ -14315,7 +14403,7 @@ function createRoot(canvas) {
14315
14403
  const renderer2 = state2.internal.actualRenderer;
14316
14404
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14317
14405
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14318
- if (!renderer2.xr.isPresenting) invalidate();
14406
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14319
14407
  };
14320
14408
  const xr = {
14321
14409
  connect() {
@@ -14397,14 +14485,23 @@ function createRoot(canvas) {
14397
14485
  const newRootId = canvasId || scheduler$1.generateRootId();
14398
14486
  const unregisterRoot = scheduler$1.registerRoot(newRootId, {
14399
14487
  getState: () => store.getState(),
14400
- onError: (err) => store.getState().setError(err)
14488
+ onError: (err) => store.getState().setError(err),
14489
+ frameloop: store.getState().frameloop,
14490
+ before: schedulerConfig?.before,
14491
+ after: schedulerConfig?.after,
14492
+ order: schedulerConfig?.order
14401
14493
  });
14402
14494
  const unregisterCanvasTarget = scheduler$1.register(
14403
14495
  () => {
14404
14496
  const state2 = store.getState();
14405
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14497
+ const canvasTarget = state2.internal.canvasTarget;
14498
+ if (canvasTarget) {
14406
14499
  const renderer2 = state2.internal.actualRenderer;
14407
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14500
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14501
+ if (state2.internal.canvasTargetSizeDirty) {
14502
+ state2.internal.canvasTargetSizeDirty = false;
14503
+ renderer2.backend?.updateSize?.();
14504
+ }
14408
14505
  }
14409
14506
  },
14410
14507
  {
@@ -14460,7 +14557,8 @@ function createRoot(canvas) {
14460
14557
  const userHandlesRender = scheduler$1.hasUserJobsInPhase("render", newRootId);
14461
14558
  if (userHandlesRender || state2.internal.priority) return;
14462
14559
  try {
14463
- if (state2.renderPipeline?.render) state2.renderPipeline.render();
14560
+ const renderOverride = state2.internal.renderOverride;
14561
+ if (renderOverride) renderOverride();
14464
14562
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14465
14563
  } catch (error) {
14466
14564
  state2.setError(error instanceof Error ? error : new Error(String(error)));
@@ -14473,8 +14571,7 @@ function createRoot(canvas) {
14473
14571
  phase: "render",
14474
14572
  system: true,
14475
14573
  // Internal flag: this is a system job, not user-controlled
14476
- // Apply scheduler config for render ordering and rate limiting
14477
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14574
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14478
14575
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14479
14576
  }
14480
14577
  );
@@ -14493,8 +14590,22 @@ function createRoot(canvas) {
14493
14590
  scheduler: scheduler$1
14494
14591
  }
14495
14592
  }));
14593
+ } else {
14594
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14595
+ if (constraintsChanged) {
14596
+ scheduler$1.setRootConstraints(rootId, {
14597
+ before: schedulerConfig?.before,
14598
+ after: schedulerConfig?.after
14599
+ });
14600
+ }
14601
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14602
+ scheduler$1.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14603
+ }
14496
14604
  }
14497
- scheduler$1.frameloop = frameloop;
14605
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14606
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14607
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14608
+ attachRootExtensions(store);
14498
14609
  onCreated = onCreatedCallback;
14499
14610
  configured = true;
14500
14611
  resolve();
@@ -14536,18 +14647,24 @@ function unmountComponentAtNode(canvas, callback) {
14536
14647
  const fiber = root?.fiber;
14537
14648
  if (fiber) {
14538
14649
  const state = root?.store.getState();
14539
- if (state) state.internal.active = false;
14650
+ if (state) {
14651
+ state.internal.active = false;
14652
+ const unregisterRoot = state.internal.unregisterRoot;
14653
+ if (unregisterRoot) {
14654
+ unregisterRoot();
14655
+ state.internal.unregisterRoot = void 0;
14656
+ }
14657
+ }
14540
14658
  reconciler.updateContainer(null, fiber, null, () => {
14659
+ if (root?.store) detachRootExtensions(root.store);
14541
14660
  if (state) {
14542
14661
  setTimeout(() => {
14543
14662
  try {
14544
14663
  const renderer = state.internal.actualRenderer;
14545
- const unregisterRoot = state.internal.unregisterRoot;
14546
- if (unregisterRoot) unregisterRoot();
14547
14664
  const unregisterPrimary = state.internal.unregisterPrimary;
14548
14665
  if (unregisterPrimary) unregisterPrimary();
14549
14666
  const canvasTarget = state.internal.canvasTarget;
14550
- if (canvasTarget?.dispose) canvasTarget.dispose();
14667
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14551
14668
  state.events.disconnect?.();
14552
14669
  cleanupHelperGroup(root.store);
14553
14670
  if (state.isLegacy && renderer) {
@@ -14688,16 +14805,11 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14688
14805
  const oldbg = target.background;
14689
14806
  const oldenv = target.environment;
14690
14807
  const oldSceneProps = {
14691
- // @ts-ignore
14692
14808
  backgroundBlurriness: target.backgroundBlurriness,
14693
- // @ts-ignore
14694
14809
  backgroundIntensity: target.backgroundIntensity,
14695
- // @ts-ignore
14696
- backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
14697
- // @ts-ignore
14810
+ backgroundRotation: target.backgroundRotation.clone(),
14698
14811
  environmentIntensity: target.environmentIntensity,
14699
- // @ts-ignore
14700
- environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
14812
+ environmentRotation: target.environmentRotation.clone()
14701
14813
  };
14702
14814
  if (background !== "only") target.environment = texture;
14703
14815
  if (background) target.background = texture;
@@ -14709,7 +14821,7 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14709
14821
  };
14710
14822
  }
14711
14823
  function EnvironmentMap({ scene, background = false, map, ...config }) {
14712
- const defaultScene = useThree((state) => state.scene);
14824
+ const defaultScene = useThree$1((state) => state.scene);
14713
14825
  React__namespace.useLayoutEffect(() => {
14714
14826
  if (map) return setEnvProps(background, scene, defaultScene, map, config);
14715
14827
  });
@@ -14727,7 +14839,7 @@ function EnvironmentCube({
14727
14839
  ...rest
14728
14840
  }) {
14729
14841
  const texture = useEnvironment(rest);
14730
- const defaultScene = useThree((state) => state.scene);
14842
+ const defaultScene = useThree$1((state) => state.scene);
14731
14843
  React__namespace.useLayoutEffect(() => {
14732
14844
  return setEnvProps(background, scene, defaultScene, texture, {
14733
14845
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -14764,9 +14876,9 @@ function EnvironmentPortal({
14764
14876
  preset = void 0,
14765
14877
  extensions
14766
14878
  }) {
14767
- const renderer = useThree((state) => state.renderer);
14768
- const isLegacy = useThree((state) => state.isLegacy);
14769
- const defaultScene = useThree((state) => state.scene);
14879
+ const renderer = useThree$1((state) => state.renderer);
14880
+ const isLegacy = useThree$1((state) => state.isLegacy);
14881
+ const defaultScene = useThree$1((state) => state.scene);
14770
14882
  const camera = React__namespace.useRef(null);
14771
14883
  const [virtualScene] = React__namespace.useState(() => new three.Scene());
14772
14884
  const fbo = React__namespace.useMemo(() => {
@@ -14810,7 +14922,7 @@ function EnvironmentPortal({
14810
14922
  environmentRotation
14811
14923
  ]);
14812
14924
  let count = 1;
14813
- useFrame(() => {
14925
+ useFrame$1(() => {
14814
14926
  if (frames === Infinity || count < frames) {
14815
14927
  const autoClear = renderer.autoClear;
14816
14928
  renderer.autoClear = true;
@@ -14850,7 +14962,7 @@ function EnvironmentGround(props) {
14850
14962
  ] });
14851
14963
  }
14852
14964
  function EnvironmentColor({ color, scene }) {
14853
- const defaultScene = useThree((state) => state.scene);
14965
+ const defaultScene = useThree$1((state) => state.scene);
14854
14966
  React__namespace.useLayoutEffect(() => {
14855
14967
  if (color === void 0) return;
14856
14968
  const target = resolveScene(scene || defaultScene);
@@ -14922,38 +15034,6 @@ function parseRendererConfig(rendererProp) {
14922
15034
  return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14923
15035
  }
14924
15036
 
14925
- const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14926
- const registries = /* @__PURE__ */ new WeakMap();
14927
- function getKindRegistry(store, kind) {
14928
- let registry = registries.get(store);
14929
- if (!registry) {
14930
- registry = {
14931
- nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14932
- uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14933
- buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14934
- gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14935
- };
14936
- registries.set(store, registry);
14937
- }
14938
- return registry[kind];
14939
- }
14940
- function invalidateResource(store, kind, scopeKey) {
14941
- const registry = getKindRegistry(store, kind);
14942
- {
14943
- registry.valid.clear();
14944
- registry.staged.clear();
14945
- }
14946
- }
14947
- function invalidateAllResources(store) {
14948
- for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
14949
- }
14950
-
14951
- function clearHmrCaches(store) {
14952
- const primary = store.getState().primaryStore ?? store;
14953
- invalidateAllResources(primary);
14954
- primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
14955
- }
14956
-
14957
15037
  function CanvasImpl({
14958
15038
  ref,
14959
15039
  children,
@@ -14991,9 +15071,9 @@ function CanvasImpl({
14991
15071
  React__namespace.useMemo(() => extend(THREE), []);
14992
15072
  const Bridge = useBridge();
14993
15073
  const backgroundProps = React__namespace.useMemo(() => parseBackground(background), [background]);
14994
- const hasInitialSizeRef = React__namespace.useRef(false);
15074
+ const [hasInitialSize, setHasInitialSize] = React__namespace.useState(false);
14995
15075
  const measureConfig = React__namespace.useMemo(() => {
14996
- if (!hasInitialSizeRef.current) {
15076
+ if (!hasInitialSize) {
14997
15077
  return {
14998
15078
  ...resize,
14999
15079
  scroll: resize?.scroll ?? true,
@@ -15005,7 +15085,7 @@ function CanvasImpl({
15005
15085
  debounce: { scroll: 50, resize: 0 },
15006
15086
  ...resize
15007
15087
  };
15008
- }, [resize, hasInitialSizeRef.current]);
15088
+ }, [hasInitialSize, resize]);
15009
15089
  const [containerRef, containerRect] = useMeasure__default(measureConfig);
15010
15090
  const effectiveSize = React__namespace.useMemo(() => {
15011
15091
  let w = width ?? containerRect.width;
@@ -15021,9 +15101,7 @@ function CanvasImpl({
15021
15101
  left: containerRect.left
15022
15102
  };
15023
15103
  }, [width, height, containerRect, forceEven]);
15024
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15025
- hasInitialSizeRef.current = true;
15026
- }
15104
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15027
15105
  const canvasRef = React__namespace.useRef(null);
15028
15106
  const divRef = React__namespace.useRef(null);
15029
15107
  React__namespace.useImperativeHandle(ref, () => canvasRef.current);
@@ -15137,6 +15215,7 @@ function CanvasImpl({
15137
15215
  const canvas = canvasRef.current;
15138
15216
  if (canvas) {
15139
15217
  return () => {
15218
+ if (canvas.isConnected) return;
15140
15219
  unmountComponentAtNode(canvas);
15141
15220
  root.current = null;
15142
15221
  };
@@ -15149,7 +15228,7 @@ function CanvasImpl({
15149
15228
  const handleHMR = () => {
15150
15229
  queueMicrotask(() => {
15151
15230
  const rootEntry = _roots.get(canvas);
15152
- if (rootEntry?.store) clearHmrCaches(rootEntry.store);
15231
+ if (rootEntry?.store) notifyRootExtensionsHmr(rootEntry.store);
15153
15232
  });
15154
15233
  };
15155
15234
  if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('legacy.cjs', document.baseURI).href)) }) !== "undefined" && undefined) {
@@ -15195,11 +15274,14 @@ function CanvasImpl({
15195
15274
  }
15196
15275
  );
15197
15276
  }
15198
- function Canvas(props) {
15277
+ function Canvas$1(props) {
15199
15278
  return /* @__PURE__ */ jsxRuntime.jsx(itsFine.FiberProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(CanvasImpl, { ...props }) });
15200
15279
  }
15201
15280
 
15202
15281
  extend(THREE);
15282
+ const useThree = useThree$1;
15283
+ const useFrame = useFrame$1;
15284
+ const Canvas = Canvas$1;
15203
15285
 
15204
15286
  exports.Scheduler = scheduler.Scheduler;
15205
15287
  exports.getScheduler = scheduler.getScheduler;
@@ -15267,8 +15349,10 @@ exports.prepare = prepare;
15267
15349
  exports.presetsObj = presetsObj;
15268
15350
  exports.reconciler = reconciler;
15269
15351
  exports.registerPrimary = registerPrimary;
15352
+ exports.registerRootExtension = registerRootExtension;
15270
15353
  exports.removeInteractivity = removeInteractivity;
15271
15354
  exports.resolve = resolve;
15355
+ exports.setRenderOverride = setRenderOverride;
15272
15356
  exports.swapInteractivity = swapInteractivity;
15273
15357
  exports.unmountComponentAtNode = unmountComponentAtNode;
15274
15358
  exports.unregisterPrimary = unregisterPrimary;