@react-three-dom/core 0.3.0 → 0.5.0

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/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Object3D, Scene, Camera, WebGLRenderer, Intersection, Vector3 } from 'three';
2
2
 
3
- declare const version = "0.3.0";
3
+ declare const version = "0.5.0";
4
4
 
5
5
  /**
6
6
  * Enable or disable debug logging globally.
@@ -261,6 +261,17 @@ interface R3FDOM {
261
261
  setInspectMode(on: boolean): void;
262
262
  /** Returns true if inspect mode is currently active. */
263
263
  getInspectMode(): boolean;
264
+ /**
265
+ * Manually register a Three.js object (and materialize its mirror DOM node).
266
+ * Useful in `manual` mode or to add specific objects in `auto` mode that
267
+ * were excluded by a filter.
268
+ */
269
+ r3fRegister(obj: Object3D): void;
270
+ /**
271
+ * Unregister a previously registered object and its descendants.
272
+ * Removes the mirror DOM node and store entry.
273
+ */
274
+ r3fUnregister(obj: Object3D): void;
264
275
  /**
265
276
  * Manually sweep orphaned objects from the store.
266
277
  * Removes objects that are no longer in any tracked scene graph.
@@ -400,6 +411,8 @@ declare class ObjectStore {
400
411
  /**
401
412
  * Unregister an entire subtree (object + all descendants).
402
413
  */
414
+ /** Mark a root as tracked without traversing/registering its children. */
415
+ addTrackedRoot(root: Object3D): void;
403
416
  unregisterTree(root: Object3D): void;
404
417
  /**
405
418
  * Refresh dynamic Tier 1 fields from the live Three.js object.
@@ -1010,7 +1023,7 @@ declare class InspectController {
1010
1023
  updateCamera(camera: Camera): void;
1011
1024
  /** Activate inspect mode — creates overlay on top of canvas. */
1012
1025
  enable(): void;
1013
- /** Deactivate inspect mode — removes overlay and clears hover state. */
1026
+ /** Deactivate inspect mode — removes overlay and clears all inspect state. */
1014
1027
  disable(): void;
1015
1028
  /** Disable and release all resources. */
1016
1029
  dispose(): void;
@@ -1044,6 +1057,19 @@ interface ThreeDomProps {
1044
1057
  * Default: true when canvasId is omitted, false otherwise.
1045
1058
  */
1046
1059
  primary?: boolean;
1060
+ /**
1061
+ * Registration mode:
1062
+ * - "auto" (default): traverses and registers all objects in the scene.
1063
+ * - "manual": nothing is auto-registered. Use `useR3FRegister` hook or
1064
+ * `__R3F_DOM__.r3fRegister()` to add objects explicitly.
1065
+ */
1066
+ mode?: 'auto' | 'manual';
1067
+ /**
1068
+ * Filter function for auto mode. Only objects that pass the filter are
1069
+ * registered. Ignored in manual mode.
1070
+ * Example: `filter={(obj) => !!obj.userData.testId}`
1071
+ */
1072
+ filter?: (obj: Object3D) => boolean;
1047
1073
  /** CSS selector or HTMLElement for the mirror DOM root. Default: "#three-dom-root" */
1048
1074
  root?: string | HTMLElement;
1049
1075
  /** Objects to process per amortized batch per frame. Default: 500 */
@@ -1073,7 +1099,7 @@ declare function getHighlighter(canvasId?: string): Highlighter | null;
1073
1099
  declare function getInspectController(canvasId?: string): InspectController | null;
1074
1100
  /** List all active canvas IDs. */
1075
1101
  declare function getCanvasIds(): string[];
1076
- declare function ThreeDom({ canvasId, primary, root, batchSize, syncBudgetMs, maxDomNodes, initialDepth, enabled, debug, inspect: inspectProp, }?: ThreeDomProps): null;
1102
+ declare function ThreeDom({ canvasId, primary, root, mode, filter, batchSize, syncBudgetMs, maxDomNodes, initialDepth, enabled, debug, inspect: inspectProp, }?: ThreeDomProps): null;
1077
1103
 
1078
1104
  /**
1079
1105
  * Patch Object3D.prototype.add and Object3D.prototype.remove to intercept
@@ -1083,7 +1109,7 @@ declare function ThreeDom({ canvasId, primary, root, batchSize, syncBudgetMs, ma
1083
1109
  * @param mirror - The DomMirror managing the parallel DOM tree
1084
1110
  * @returns A cleanup function that unregisters this store/mirror pair
1085
1111
  */
1086
- declare function patchObject3D(store: ObjectStore, mirror: DomMirror): () => void;
1112
+ declare function patchObject3D(store: ObjectStore, mirror: DomMirror, instanceKey?: string): () => void;
1087
1113
  /**
1088
1114
  * Restore the original Object3D.prototype.add and remove methods.
1089
1115
  * Called automatically when the last store/mirror pair is removed.
@@ -1766,4 +1792,36 @@ declare function circlePath(center: {
1766
1792
  */
1767
1793
  declare function resolveObject(idOrUuid: string): Object3D;
1768
1794
 
1769
- export { type CameraState, type CanvasSize, type Click3DOptions, type Click3DResult, DomMirror, type Drag3DOptions, type Drag3DResult, type DragOptions, type DrawPathOptions, type DrawPathResult, type DrawPoint, type GeometryInspection, Highlighter, type HighlighterOptions, type Hover3DOptions, type Hover3DResult, InspectController, type InspectOptions, MANAGED_ATTRIBUTES, type MaterialInspection, type ObjectInspection, type ObjectMetadata, ObjectStore, type PointerMiss3DOptions, type ProjectionResult, type R3FDOM, RaycastAccelerator, type RaycastResult, type SceneSnapshot, type ScreenDelta, type ScreenPoint, type SelectionListener, SelectionManager, type SnapshotNode, type StoreEvent, type StoreEventType, type StoreListener, TAG_MAP, ThreeDom, type ThreeDomProps, ThreeElement, type ThreeTagName, type Wheel3DOptions, type Wheel3DResult, type WheelOptions, type WorldDelta, applyAttributes, circlePath, click3D, computeAttributes, contextMenu3D, createFlatSnapshot, createSnapshot, curvePath, dispatchClick, dispatchContextMenu, dispatchDoubleClick, dispatchDrag, dispatchHover, dispatchPointerMiss, dispatchUnhover, dispatchWheel, doubleClick3D, drag3D, drawPath, enableDebug, ensureCustomElements, getCanvasIds, getHighlighter, getInspectController, getMirror, getSelectionManager, getStore, getTagForType, hover3D, isDebugEnabled, isInFrustum, isPatched, linePath, patchObject3D, pointerMiss3D, previewDragWorldDelta, projectAllSamplePoints, projectToScreen, r3fLog, rectPath, resolveObject, restoreObject3D, screenDeltaToWorld, unhover3D, verifyRaycastHit, verifyRaycastHitMultiPoint, version, wheel3D };
1795
+ /**
1796
+ * Registers a Three.js object with the react-three-dom bridge on mount and
1797
+ * unregisters it on unmount. Works in both `auto` and `manual` mode.
1798
+ *
1799
+ * - In `manual` mode this is the primary way to opt objects into the mirror DOM.
1800
+ * - In `auto` mode it can force-register objects excluded by a `filter`.
1801
+ * - Handles `ref.current` identity changes automatically.
1802
+ * - If the bridge isn't ready on mount, retries each frame until available.
1803
+ * - Supports multi-canvas via the optional `canvasId` parameter.
1804
+ *
1805
+ * @param ref - Ref to the Three.js object to register
1806
+ * @param canvasId - Optional canvas ID for multi-canvas setups. When omitted,
1807
+ * uses the primary bridge (`window.__R3F_DOM__`).
1808
+ *
1809
+ * @example
1810
+ * ```tsx
1811
+ * function Wall({ geometry }) {
1812
+ * const ref = useRef<Mesh>(null!);
1813
+ * useR3FRegister(ref);
1814
+ * return <mesh ref={ref} geometry={geometry} userData={{ testId: 'wall' }} />;
1815
+ * }
1816
+ *
1817
+ * // Multi-canvas
1818
+ * function Overlay() {
1819
+ * const ref = useRef<Mesh>(null!);
1820
+ * useR3FRegister(ref, 'overlay');
1821
+ * return <mesh ref={ref} />;
1822
+ * }
1823
+ * ```
1824
+ */
1825
+ declare function useR3FRegister(ref: React.RefObject<Object3D | null>, canvasId?: string): void;
1826
+
1827
+ export { type CameraState, type CanvasSize, type Click3DOptions, type Click3DResult, DomMirror, type Drag3DOptions, type Drag3DResult, type DragOptions, type DrawPathOptions, type DrawPathResult, type DrawPoint, type GeometryInspection, Highlighter, type HighlighterOptions, type Hover3DOptions, type Hover3DResult, InspectController, type InspectOptions, MANAGED_ATTRIBUTES, type MaterialInspection, type ObjectInspection, type ObjectMetadata, ObjectStore, type PointerMiss3DOptions, type ProjectionResult, type R3FDOM, RaycastAccelerator, type RaycastResult, type SceneSnapshot, type ScreenDelta, type ScreenPoint, type SelectionListener, SelectionManager, type SnapshotNode, type StoreEvent, type StoreEventType, type StoreListener, TAG_MAP, ThreeDom, type ThreeDomProps, ThreeElement, type ThreeTagName, type Wheel3DOptions, type Wheel3DResult, type WheelOptions, type WorldDelta, applyAttributes, circlePath, click3D, computeAttributes, contextMenu3D, createFlatSnapshot, createSnapshot, curvePath, dispatchClick, dispatchContextMenu, dispatchDoubleClick, dispatchDrag, dispatchHover, dispatchPointerMiss, dispatchUnhover, dispatchWheel, doubleClick3D, drag3D, drawPath, enableDebug, ensureCustomElements, getCanvasIds, getHighlighter, getInspectController, getMirror, getSelectionManager, getStore, getTagForType, hover3D, isDebugEnabled, isInFrustum, isPatched, linePath, patchObject3D, pointerMiss3D, previewDragWorldDelta, projectAllSamplePoints, projectToScreen, r3fLog, rectPath, resolveObject, restoreObject3D, screenDeltaToWorld, unhover3D, useR3FRegister, verifyRaycastHit, verifyRaycastHitMultiPoint, version, wheel3D };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Object3D, Scene, Camera, WebGLRenderer, Intersection, Vector3 } from 'three';
2
2
 
3
- declare const version = "0.3.0";
3
+ declare const version = "0.5.0";
4
4
 
5
5
  /**
6
6
  * Enable or disable debug logging globally.
@@ -261,6 +261,17 @@ interface R3FDOM {
261
261
  setInspectMode(on: boolean): void;
262
262
  /** Returns true if inspect mode is currently active. */
263
263
  getInspectMode(): boolean;
264
+ /**
265
+ * Manually register a Three.js object (and materialize its mirror DOM node).
266
+ * Useful in `manual` mode or to add specific objects in `auto` mode that
267
+ * were excluded by a filter.
268
+ */
269
+ r3fRegister(obj: Object3D): void;
270
+ /**
271
+ * Unregister a previously registered object and its descendants.
272
+ * Removes the mirror DOM node and store entry.
273
+ */
274
+ r3fUnregister(obj: Object3D): void;
264
275
  /**
265
276
  * Manually sweep orphaned objects from the store.
266
277
  * Removes objects that are no longer in any tracked scene graph.
@@ -400,6 +411,8 @@ declare class ObjectStore {
400
411
  /**
401
412
  * Unregister an entire subtree (object + all descendants).
402
413
  */
414
+ /** Mark a root as tracked without traversing/registering its children. */
415
+ addTrackedRoot(root: Object3D): void;
403
416
  unregisterTree(root: Object3D): void;
404
417
  /**
405
418
  * Refresh dynamic Tier 1 fields from the live Three.js object.
@@ -1010,7 +1023,7 @@ declare class InspectController {
1010
1023
  updateCamera(camera: Camera): void;
1011
1024
  /** Activate inspect mode — creates overlay on top of canvas. */
1012
1025
  enable(): void;
1013
- /** Deactivate inspect mode — removes overlay and clears hover state. */
1026
+ /** Deactivate inspect mode — removes overlay and clears all inspect state. */
1014
1027
  disable(): void;
1015
1028
  /** Disable and release all resources. */
1016
1029
  dispose(): void;
@@ -1044,6 +1057,19 @@ interface ThreeDomProps {
1044
1057
  * Default: true when canvasId is omitted, false otherwise.
1045
1058
  */
1046
1059
  primary?: boolean;
1060
+ /**
1061
+ * Registration mode:
1062
+ * - "auto" (default): traverses and registers all objects in the scene.
1063
+ * - "manual": nothing is auto-registered. Use `useR3FRegister` hook or
1064
+ * `__R3F_DOM__.r3fRegister()` to add objects explicitly.
1065
+ */
1066
+ mode?: 'auto' | 'manual';
1067
+ /**
1068
+ * Filter function for auto mode. Only objects that pass the filter are
1069
+ * registered. Ignored in manual mode.
1070
+ * Example: `filter={(obj) => !!obj.userData.testId}`
1071
+ */
1072
+ filter?: (obj: Object3D) => boolean;
1047
1073
  /** CSS selector or HTMLElement for the mirror DOM root. Default: "#three-dom-root" */
1048
1074
  root?: string | HTMLElement;
1049
1075
  /** Objects to process per amortized batch per frame. Default: 500 */
@@ -1073,7 +1099,7 @@ declare function getHighlighter(canvasId?: string): Highlighter | null;
1073
1099
  declare function getInspectController(canvasId?: string): InspectController | null;
1074
1100
  /** List all active canvas IDs. */
1075
1101
  declare function getCanvasIds(): string[];
1076
- declare function ThreeDom({ canvasId, primary, root, batchSize, syncBudgetMs, maxDomNodes, initialDepth, enabled, debug, inspect: inspectProp, }?: ThreeDomProps): null;
1102
+ declare function ThreeDom({ canvasId, primary, root, mode, filter, batchSize, syncBudgetMs, maxDomNodes, initialDepth, enabled, debug, inspect: inspectProp, }?: ThreeDomProps): null;
1077
1103
 
1078
1104
  /**
1079
1105
  * Patch Object3D.prototype.add and Object3D.prototype.remove to intercept
@@ -1083,7 +1109,7 @@ declare function ThreeDom({ canvasId, primary, root, batchSize, syncBudgetMs, ma
1083
1109
  * @param mirror - The DomMirror managing the parallel DOM tree
1084
1110
  * @returns A cleanup function that unregisters this store/mirror pair
1085
1111
  */
1086
- declare function patchObject3D(store: ObjectStore, mirror: DomMirror): () => void;
1112
+ declare function patchObject3D(store: ObjectStore, mirror: DomMirror, instanceKey?: string): () => void;
1087
1113
  /**
1088
1114
  * Restore the original Object3D.prototype.add and remove methods.
1089
1115
  * Called automatically when the last store/mirror pair is removed.
@@ -1766,4 +1792,36 @@ declare function circlePath(center: {
1766
1792
  */
1767
1793
  declare function resolveObject(idOrUuid: string): Object3D;
1768
1794
 
1769
- export { type CameraState, type CanvasSize, type Click3DOptions, type Click3DResult, DomMirror, type Drag3DOptions, type Drag3DResult, type DragOptions, type DrawPathOptions, type DrawPathResult, type DrawPoint, type GeometryInspection, Highlighter, type HighlighterOptions, type Hover3DOptions, type Hover3DResult, InspectController, type InspectOptions, MANAGED_ATTRIBUTES, type MaterialInspection, type ObjectInspection, type ObjectMetadata, ObjectStore, type PointerMiss3DOptions, type ProjectionResult, type R3FDOM, RaycastAccelerator, type RaycastResult, type SceneSnapshot, type ScreenDelta, type ScreenPoint, type SelectionListener, SelectionManager, type SnapshotNode, type StoreEvent, type StoreEventType, type StoreListener, TAG_MAP, ThreeDom, type ThreeDomProps, ThreeElement, type ThreeTagName, type Wheel3DOptions, type Wheel3DResult, type WheelOptions, type WorldDelta, applyAttributes, circlePath, click3D, computeAttributes, contextMenu3D, createFlatSnapshot, createSnapshot, curvePath, dispatchClick, dispatchContextMenu, dispatchDoubleClick, dispatchDrag, dispatchHover, dispatchPointerMiss, dispatchUnhover, dispatchWheel, doubleClick3D, drag3D, drawPath, enableDebug, ensureCustomElements, getCanvasIds, getHighlighter, getInspectController, getMirror, getSelectionManager, getStore, getTagForType, hover3D, isDebugEnabled, isInFrustum, isPatched, linePath, patchObject3D, pointerMiss3D, previewDragWorldDelta, projectAllSamplePoints, projectToScreen, r3fLog, rectPath, resolveObject, restoreObject3D, screenDeltaToWorld, unhover3D, verifyRaycastHit, verifyRaycastHitMultiPoint, version, wheel3D };
1795
+ /**
1796
+ * Registers a Three.js object with the react-three-dom bridge on mount and
1797
+ * unregisters it on unmount. Works in both `auto` and `manual` mode.
1798
+ *
1799
+ * - In `manual` mode this is the primary way to opt objects into the mirror DOM.
1800
+ * - In `auto` mode it can force-register objects excluded by a `filter`.
1801
+ * - Handles `ref.current` identity changes automatically.
1802
+ * - If the bridge isn't ready on mount, retries each frame until available.
1803
+ * - Supports multi-canvas via the optional `canvasId` parameter.
1804
+ *
1805
+ * @param ref - Ref to the Three.js object to register
1806
+ * @param canvasId - Optional canvas ID for multi-canvas setups. When omitted,
1807
+ * uses the primary bridge (`window.__R3F_DOM__`).
1808
+ *
1809
+ * @example
1810
+ * ```tsx
1811
+ * function Wall({ geometry }) {
1812
+ * const ref = useRef<Mesh>(null!);
1813
+ * useR3FRegister(ref);
1814
+ * return <mesh ref={ref} geometry={geometry} userData={{ testId: 'wall' }} />;
1815
+ * }
1816
+ *
1817
+ * // Multi-canvas
1818
+ * function Overlay() {
1819
+ * const ref = useRef<Mesh>(null!);
1820
+ * useR3FRegister(ref, 'overlay');
1821
+ * return <mesh ref={ref} />;
1822
+ * }
1823
+ * ```
1824
+ */
1825
+ declare function useR3FRegister(ref: React.RefObject<Object3D | null>, canvasId?: string): void;
1826
+
1827
+ export { type CameraState, type CanvasSize, type Click3DOptions, type Click3DResult, DomMirror, type Drag3DOptions, type Drag3DResult, type DragOptions, type DrawPathOptions, type DrawPathResult, type DrawPoint, type GeometryInspection, Highlighter, type HighlighterOptions, type Hover3DOptions, type Hover3DResult, InspectController, type InspectOptions, MANAGED_ATTRIBUTES, type MaterialInspection, type ObjectInspection, type ObjectMetadata, ObjectStore, type PointerMiss3DOptions, type ProjectionResult, type R3FDOM, RaycastAccelerator, type RaycastResult, type SceneSnapshot, type ScreenDelta, type ScreenPoint, type SelectionListener, SelectionManager, type SnapshotNode, type StoreEvent, type StoreEventType, type StoreListener, TAG_MAP, ThreeDom, type ThreeDomProps, ThreeElement, type ThreeTagName, type Wheel3DOptions, type Wheel3DResult, type WheelOptions, type WorldDelta, applyAttributes, circlePath, click3D, computeAttributes, contextMenu3D, createFlatSnapshot, createSnapshot, curvePath, dispatchClick, dispatchContextMenu, dispatchDoubleClick, dispatchDrag, dispatchHover, dispatchPointerMiss, dispatchUnhover, dispatchWheel, doubleClick3D, drag3D, drawPath, enableDebug, ensureCustomElements, getCanvasIds, getHighlighter, getInspectController, getMirror, getSelectionManager, getStore, getTagForType, hover3D, isDebugEnabled, isInFrustum, isPatched, linePath, patchObject3D, pointerMiss3D, previewDragWorldDelta, projectAllSamplePoints, projectToScreen, r3fLog, rectPath, resolveObject, restoreObject3D, screenDeltaToWorld, unhover3D, useR3FRegister, verifyRaycastHit, verifyRaycastHitMultiPoint, version, wheel3D };
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Box3, Object3D, Vector3, Matrix4, Frustum, Raycaster, Vector2, BufferGeometry, Material, Color, MeshBasicMaterial, DoubleSide, Mesh, BoxGeometry, EdgesGeometry, LineBasicMaterial, LineSegments, InstancedMesh, PerspectiveCamera, OrthographicCamera } from 'three';
2
- import { useRef, useEffect } from 'react';
2
+ import { useRef, useEffect, useCallback } from 'react';
3
3
  import { useThree, useFrame } from '@react-three/fiber';
4
4
  import { computeBoundsTree, disposeBoundsTree, acceleratedRaycast } from 'three-mesh-bvh';
5
5
 
6
6
  // src/version.ts
7
- var version = "0.3.0";
7
+ var version = "0.5.0";
8
8
 
9
9
  // src/debug.ts
10
10
  var _enabled = false;
@@ -399,6 +399,7 @@ var ObjectStore = class {
399
399
  this._dirtyQueue.delete(obj);
400
400
  this._flatListDirty = true;
401
401
  delete obj.userData.__r3fdom_tracked;
402
+ delete obj.userData.__r3fdom_manual;
402
403
  if (meta.testId) {
403
404
  this._objectsByTestId.delete(meta.testId);
404
405
  }
@@ -419,6 +420,10 @@ var ObjectStore = class {
419
420
  /**
420
421
  * Unregister an entire subtree (object + all descendants).
421
422
  */
423
+ /** Mark a root as tracked without traversing/registering its children. */
424
+ addTrackedRoot(root) {
425
+ this._trackedRoots.add(root);
426
+ }
422
427
  unregisterTree(root) {
423
428
  root.traverse((obj) => {
424
429
  this.unregister(obj);
@@ -1515,16 +1520,17 @@ function findTrackingPair(obj) {
1515
1520
  }
1516
1521
  return null;
1517
1522
  }
1518
- function registerSubtree(obj, store, mirror) {
1523
+ function registerSubtree(obj, store, mirror, instanceKey) {
1519
1524
  obj.traverse((child) => {
1520
- if (!store.has(child)) {
1525
+ if (!store.has(child) && shouldRegister(instanceKey, child)) {
1526
+ ensureAncestorChain(child, store, mirror);
1521
1527
  store.register(child);
1522
1528
  mirror.onObjectAdded(child);
1523
1529
  }
1524
1530
  });
1525
1531
  }
1526
- function patchObject3D(store, mirror) {
1527
- _activePairs.push({ store, mirror });
1532
+ function patchObject3D(store, mirror, instanceKey = "") {
1533
+ _activePairs.push({ store, mirror, instanceKey });
1528
1534
  if (!_patched) {
1529
1535
  r3fLog("patch", "Patching Object3D.prototype.add and .remove");
1530
1536
  _originalAdd = Object3D.prototype.add;
@@ -1537,7 +1543,7 @@ function patchObject3D(store, mirror) {
1537
1543
  if (obj === this) continue;
1538
1544
  try {
1539
1545
  r3fLog("patch", `patchedAdd: "${obj.name || obj.type}" added to "${this.name || this.type}"`);
1540
- registerSubtree(obj, pair.store, pair.mirror);
1546
+ registerSubtree(obj, pair.store, pair.mirror, pair.instanceKey);
1541
1547
  } catch (err) {
1542
1548
  r3fLog("patch", `patchedAdd: failed to register "${obj.name || obj.type}"`, err);
1543
1549
  }
@@ -3096,7 +3102,7 @@ var InspectController = class {
3096
3102
  this._overlay = overlay;
3097
3103
  r3fLog("inspect", "Inspect mode enabled \u2014 hover to highlight, click to select");
3098
3104
  }
3099
- /** Deactivate inspect mode — removes overlay and clears hover state. */
3105
+ /** Deactivate inspect mode — removes overlay and clears all inspect state. */
3100
3106
  disable() {
3101
3107
  if (!this._active) return;
3102
3108
  this._active = false;
@@ -3343,6 +3349,30 @@ var _mirrors = /* @__PURE__ */ new Map();
3343
3349
  var _selectionManagers = /* @__PURE__ */ new Map();
3344
3350
  var _highlighters = /* @__PURE__ */ new Map();
3345
3351
  var _inspectControllers = /* @__PURE__ */ new Map();
3352
+ var _filters = /* @__PURE__ */ new Map();
3353
+ var _modes = /* @__PURE__ */ new Map();
3354
+ function shouldRegister(instanceKey, obj) {
3355
+ const mode = _modes.get(instanceKey);
3356
+ if (mode === "manual") return false;
3357
+ const filter = _filters.get(instanceKey);
3358
+ if (filter) return filter(obj);
3359
+ return true;
3360
+ }
3361
+ function ensureAncestorChain(obj, store, mirror) {
3362
+ const chain = [];
3363
+ let cursor = obj.parent;
3364
+ while (cursor) {
3365
+ if (store.has(cursor)) break;
3366
+ chain.push(cursor);
3367
+ cursor = cursor.parent;
3368
+ }
3369
+ for (let i = chain.length - 1; i >= 0; i--) {
3370
+ const ancestor = chain[i];
3371
+ store.register(ancestor);
3372
+ mirror.onObjectAdded(ancestor);
3373
+ mirror.materialize(ancestor.uuid);
3374
+ }
3375
+ }
3346
3376
  function getStore2(canvasId = "") {
3347
3377
  return _stores.get(canvasId) ?? null;
3348
3378
  }
@@ -3493,6 +3523,38 @@ function exposeGlobalAPI(store, gl, cameraRef, selMgr, inspCtrl, mirror, canvasI
3493
3523
  }
3494
3524
  return state;
3495
3525
  },
3526
+ r3fRegister: (obj) => {
3527
+ if (store.has(obj)) return;
3528
+ if (!store.isInTrackedScene(obj)) {
3529
+ console.warn(
3530
+ `[react-three-dom] r3fRegister: object "${obj.userData?.testId || obj.name || obj.uuid.slice(0, 8)}" is not in a tracked scene. Add it to the scene graph first.`
3531
+ );
3532
+ return;
3533
+ }
3534
+ obj.userData.__r3fdom_manual = true;
3535
+ ensureAncestorChain(obj, store, mirror);
3536
+ let count = 0;
3537
+ obj.traverse((child) => {
3538
+ if (!store.has(child)) {
3539
+ store.register(child);
3540
+ mirror?.onObjectAdded(child);
3541
+ mirror?.materialize(child.uuid);
3542
+ count++;
3543
+ }
3544
+ });
3545
+ r3fLog("bridge", `r3fRegister: "${obj.userData?.testId || obj.name || obj.uuid.slice(0, 8)}" (${obj.type}) \u2014 ${count} objects`);
3546
+ },
3547
+ r3fUnregister: (obj) => {
3548
+ if (!store.has(obj)) return;
3549
+ if (!obj.userData?.__r3fdom_manual) {
3550
+ r3fLog("bridge", `r3fUnregister skipped \u2014 "${obj.userData?.testId || obj.name || obj.uuid.slice(0, 8)}" was auto-registered`);
3551
+ return;
3552
+ }
3553
+ delete obj.userData.__r3fdom_manual;
3554
+ mirror?.onObjectRemoved(obj);
3555
+ obj.traverse((child) => store.unregister(child));
3556
+ r3fLog("bridge", `r3fUnregister: "${obj.userData?.testId || obj.name || obj.uuid.slice(0, 8)}" (${obj.type})`);
3557
+ },
3496
3558
  fuzzyFind: (query, limit = 5) => {
3497
3559
  const q = query.toLowerCase();
3498
3560
  const results = [];
@@ -3615,6 +3677,10 @@ function createStubBridge(error, canvasId) {
3615
3677
  setInspectMode: () => {
3616
3678
  },
3617
3679
  getInspectMode: () => false,
3680
+ r3fRegister: () => {
3681
+ },
3682
+ r3fUnregister: () => {
3683
+ },
3618
3684
  sweepOrphans: () => 0,
3619
3685
  getDiagnostics: () => ({
3620
3686
  version,
@@ -3649,6 +3715,8 @@ function ThreeDom({
3649
3715
  canvasId,
3650
3716
  primary,
3651
3717
  root = "#three-dom-root",
3718
+ mode = "auto",
3719
+ filter,
3652
3720
  batchSize = 500,
3653
3721
  syncBudgetMs = 0.5,
3654
3722
  maxDomNodes = 2e3,
@@ -3723,18 +3791,42 @@ function ThreeDom({
3723
3791
  mirror.setRoot(rootElement);
3724
3792
  r3fLog("setup", "Store and mirror created");
3725
3793
  ensureCustomElements(store);
3726
- unpatch = patchObject3D(store, mirror);
3794
+ _modes.set(instanceKey, mode);
3795
+ _filters.set(instanceKey, filter ?? null);
3796
+ unpatch = patchObject3D(store, mirror, instanceKey);
3727
3797
  setInteractionState(store, camera, gl, size);
3728
- r3fLog("setup", "Object3D patched, interaction state set");
3729
- store.registerTree(scene);
3730
- if (!store.has(camera)) {
3731
- const camMeta = store.register(camera);
3732
- camMeta.parentUuid = scene.uuid;
3733
- mirror.materialize(camera.uuid);
3798
+ r3fLog("setup", `Object3D patched (mode=${mode}), interaction state set`);
3799
+ if (mode === "auto") {
3800
+ if (filter) {
3801
+ store.addTrackedRoot(scene);
3802
+ store.register(scene);
3803
+ mirror.onObjectAdded(scene);
3804
+ scene.traverse((obj) => {
3805
+ if (obj === scene) return;
3806
+ if (filter(obj)) {
3807
+ ensureAncestorChain(obj, store, mirror);
3808
+ store.register(obj);
3809
+ mirror.onObjectAdded(obj);
3810
+ }
3811
+ });
3812
+ } else {
3813
+ store.registerTree(scene);
3814
+ }
3815
+ if (!store.has(camera)) {
3816
+ const camMeta = store.register(camera);
3817
+ camMeta.parentUuid = scene.uuid;
3818
+ mirror.materialize(camera.uuid);
3819
+ }
3820
+ mirror.materializeSubtree(scene.uuid, initialDepth);
3821
+ if (!filter) {
3822
+ cancelAsyncReg = store.registerTreeAsync(scene);
3823
+ }
3824
+ } else {
3825
+ store.addTrackedRoot(scene);
3826
+ store.register(scene);
3827
+ mirror.onObjectAdded(scene);
3734
3828
  }
3735
- mirror.materializeSubtree(scene.uuid, initialDepth);
3736
- cancelAsyncReg = store.registerTreeAsync(scene);
3737
- r3fLog("setup", `Scene registered: ${store.getCount()} objects, async watcher started`);
3829
+ r3fLog("setup", `Scene registered (mode=${mode}): ${store.getCount()} objects`);
3738
3830
  selectionManager = new SelectionManager();
3739
3831
  highlighter = new Highlighter();
3740
3832
  highlighter.attach(scene, selectionManager, camera, gl, store);
@@ -3806,6 +3898,8 @@ function ThreeDom({
3806
3898
  _selectionManagers.delete(instanceKey);
3807
3899
  _highlighters.delete(instanceKey);
3808
3900
  _inspectControllers.delete(instanceKey);
3901
+ _modes.delete(instanceKey);
3902
+ _filters.delete(instanceKey);
3809
3903
  if (debug) enableDebug(false);
3810
3904
  r3fLog("setup", "ThreeDom cleanup complete");
3811
3905
  };
@@ -3853,7 +3947,42 @@ function ThreeDom({
3853
3947
  });
3854
3948
  return null;
3855
3949
  }
3950
+ function getAPI(canvasId) {
3951
+ if (canvasId) {
3952
+ return window.__R3F_DOM_INSTANCES__?.[canvasId];
3953
+ }
3954
+ return window.__R3F_DOM__;
3955
+ }
3956
+ function useR3FRegister(ref, canvasId) {
3957
+ const trackedObj = useRef(null);
3958
+ const canvasIdRef = useRef(canvasId);
3959
+ canvasIdRef.current = canvasId;
3960
+ const register = useCallback((obj) => {
3961
+ const api = getAPI(canvasIdRef.current);
3962
+ if (!api) return false;
3963
+ api.r3fRegister(obj);
3964
+ trackedObj.current = obj;
3965
+ return true;
3966
+ }, []);
3967
+ const unregister = useCallback(() => {
3968
+ if (!trackedObj.current) return;
3969
+ const api = getAPI(canvasIdRef.current);
3970
+ api?.r3fUnregister(trackedObj.current);
3971
+ trackedObj.current = null;
3972
+ }, []);
3973
+ useEffect(() => {
3974
+ const obj = ref.current;
3975
+ if (obj) register(obj);
3976
+ return () => unregister();
3977
+ }, [ref, register, unregister]);
3978
+ useFrame(() => {
3979
+ const current = ref.current;
3980
+ if (current === trackedObj.current) return;
3981
+ unregister();
3982
+ if (current) register(current);
3983
+ });
3984
+ }
3856
3985
 
3857
- export { DomMirror, Highlighter, InspectController, MANAGED_ATTRIBUTES, ObjectStore, RaycastAccelerator, SelectionManager, TAG_MAP, ThreeDom, ThreeElement, applyAttributes, circlePath, click3D, computeAttributes, contextMenu3D, createFlatSnapshot, createSnapshot, curvePath, dispatchClick, dispatchContextMenu, dispatchDoubleClick, dispatchDrag, dispatchHover, dispatchPointerMiss, dispatchUnhover, dispatchWheel, doubleClick3D, drag3D, drawPath, enableDebug, ensureCustomElements, getCanvasIds, getHighlighter, getInspectController, getMirror, getSelectionManager, getStore2 as getStore, getTagForType, hover3D, isDebugEnabled, isInFrustum, isPatched, linePath, patchObject3D, pointerMiss3D, previewDragWorldDelta, projectAllSamplePoints, projectToScreen, r3fLog, rectPath, resolveObject, restoreObject3D, screenDeltaToWorld, unhover3D, verifyRaycastHit, verifyRaycastHitMultiPoint, version, wheel3D };
3986
+ export { DomMirror, Highlighter, InspectController, MANAGED_ATTRIBUTES, ObjectStore, RaycastAccelerator, SelectionManager, TAG_MAP, ThreeDom, ThreeElement, applyAttributes, circlePath, click3D, computeAttributes, contextMenu3D, createFlatSnapshot, createSnapshot, curvePath, dispatchClick, dispatchContextMenu, dispatchDoubleClick, dispatchDrag, dispatchHover, dispatchPointerMiss, dispatchUnhover, dispatchWheel, doubleClick3D, drag3D, drawPath, enableDebug, ensureCustomElements, getCanvasIds, getHighlighter, getInspectController, getMirror, getSelectionManager, getStore2 as getStore, getTagForType, hover3D, isDebugEnabled, isInFrustum, isPatched, linePath, patchObject3D, pointerMiss3D, previewDragWorldDelta, projectAllSamplePoints, projectToScreen, r3fLog, rectPath, resolveObject, restoreObject3D, screenDeltaToWorld, unhover3D, useR3FRegister, verifyRaycastHit, verifyRaycastHitMultiPoint, version, wheel3D };
3858
3987
  //# sourceMappingURL=index.js.map
3859
3988
  //# sourceMappingURL=index.js.map