@react-three-dom/core 0.1.0 → 0.2.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.js CHANGED
@@ -4,6 +4,25 @@ import { useThree, useFrame } from '@react-three/fiber';
4
4
 
5
5
  // src/version.ts
6
6
  var version = "0.1.0";
7
+
8
+ // src/debug.ts
9
+ var _enabled = false;
10
+ function enableDebug(on = true) {
11
+ _enabled = on;
12
+ }
13
+ function isDebugEnabled() {
14
+ return _enabled || typeof window !== "undefined" && !!window.__R3F_DOM_DEBUG__;
15
+ }
16
+ function r3fLog(area, msg, data) {
17
+ if (!_enabled && !(typeof window !== "undefined" && window.__R3F_DOM_DEBUG__)) {
18
+ return;
19
+ }
20
+ if (data !== void 0) {
21
+ console.log(`[r3f-dom:${area}]`, msg, data);
22
+ } else {
23
+ console.log(`[r3f-dom:${area}]`, msg);
24
+ }
25
+ }
7
26
  function extractMetadata(obj) {
8
27
  const meta = {
9
28
  uuid: obj.uuid,
@@ -18,31 +37,42 @@ function extractMetadata(obj) {
18
37
  childrenUuids: obj.children.map((c) => c.uuid),
19
38
  boundsDirty: true
20
39
  };
21
- if ("geometry" in obj) {
22
- const geom = obj.geometry;
23
- if (geom instanceof BufferGeometry) {
24
- meta.geometryType = geom.type;
25
- const posAttr = geom.getAttribute("position");
26
- if (posAttr) {
27
- meta.vertexCount = posAttr.count;
28
- if (geom.index) {
29
- meta.triangleCount = Math.floor(geom.index.count / 3);
30
- } else {
31
- meta.triangleCount = Math.floor(posAttr.count / 3);
40
+ try {
41
+ if ("geometry" in obj) {
42
+ const geom = obj.geometry;
43
+ if (geom instanceof BufferGeometry) {
44
+ meta.geometryType = geom.type;
45
+ const posAttr = geom.getAttribute("position");
46
+ if (posAttr) {
47
+ meta.vertexCount = posAttr.count;
48
+ if (geom.index) {
49
+ meta.triangleCount = Math.floor(geom.index.count / 3);
50
+ } else {
51
+ meta.triangleCount = Math.floor(posAttr.count / 3);
52
+ }
32
53
  }
33
54
  }
34
55
  }
56
+ } catch {
57
+ r3fLog("store", `extractMetadata: geometry access failed for "${obj.name || obj.uuid}"`);
35
58
  }
36
- if ("material" in obj) {
37
- const mat = obj.material;
38
- if (mat instanceof Material) {
39
- meta.materialType = mat.type;
40
- } else if (Array.isArray(mat) && mat.length > 0) {
41
- meta.materialType = mat[0].type + (mat.length > 1 ? ` (+${mat.length - 1})` : "");
59
+ try {
60
+ if ("material" in obj) {
61
+ const mat = obj.material;
62
+ if (mat instanceof Material) {
63
+ meta.materialType = mat.type;
64
+ } else if (Array.isArray(mat) && mat.length > 0) {
65
+ meta.materialType = mat[0].type + (mat.length > 1 ? ` (+${mat.length - 1})` : "");
66
+ }
42
67
  }
68
+ } catch {
69
+ r3fLog("store", `extractMetadata: material access failed for "${obj.name || obj.uuid}"`);
43
70
  }
44
- if (obj instanceof InstancedMesh) {
45
- meta.instanceCount = obj.count;
71
+ try {
72
+ if (obj instanceof InstancedMesh) {
73
+ meta.instanceCount = obj.count;
74
+ }
75
+ } catch {
46
76
  }
47
77
  return meta;
48
78
  }
@@ -51,81 +81,101 @@ function hasChanged(prev, curr) {
51
81
  }
52
82
  var _box3 = new Box3();
53
83
  function inspectObject(obj, metadata) {
54
- obj.updateWorldMatrix(true, false);
55
- const worldMatrix = Array.from(obj.matrixWorld.elements);
56
- _box3.setFromObject(obj);
57
- const boundsMin = [_box3.min.x, _box3.min.y, _box3.min.z];
58
- const boundsMax = [_box3.max.x, _box3.max.y, _box3.max.z];
84
+ let worldMatrix = Array(16).fill(0);
85
+ let boundsMin = [0, 0, 0];
86
+ let boundsMax = [0, 0, 0];
87
+ try {
88
+ obj.updateWorldMatrix(true, false);
89
+ worldMatrix = Array.from(obj.matrixWorld.elements);
90
+ _box3.setFromObject(obj);
91
+ boundsMin = [_box3.min.x, _box3.min.y, _box3.min.z];
92
+ boundsMax = [_box3.max.x, _box3.max.y, _box3.max.z];
93
+ } catch {
94
+ r3fLog("store", `inspectObject: world matrix / bounds failed for "${obj.name || obj.uuid}"`);
95
+ }
59
96
  const inspection = {
60
97
  metadata,
61
98
  worldMatrix,
62
99
  bounds: { min: boundsMin, max: boundsMax },
63
- userData: { ...obj.userData }
100
+ userData: {}
64
101
  };
65
- if ("geometry" in obj) {
66
- const geom = obj.geometry;
67
- if (geom instanceof BufferGeometry) {
68
- const geoInspection = {
69
- type: geom.type,
70
- attributes: {}
71
- };
72
- for (const [name, attr] of Object.entries(geom.attributes)) {
73
- geoInspection.attributes[name] = {
74
- itemSize: attr.itemSize,
75
- count: attr.count
102
+ try {
103
+ inspection.userData = { ...obj.userData };
104
+ } catch {
105
+ r3fLog("store", `inspectObject: userData copy failed for "${obj.name || obj.uuid}"`);
106
+ }
107
+ try {
108
+ if ("geometry" in obj) {
109
+ const geom = obj.geometry;
110
+ if (geom instanceof BufferGeometry) {
111
+ const geoInspection = {
112
+ type: geom.type,
113
+ attributes: {}
76
114
  };
115
+ for (const [name, attr] of Object.entries(geom.attributes)) {
116
+ geoInspection.attributes[name] = {
117
+ itemSize: attr.itemSize,
118
+ count: attr.count
119
+ };
120
+ }
121
+ if (geom.index) {
122
+ geoInspection.index = { count: geom.index.count };
123
+ }
124
+ geom.computeBoundingSphere();
125
+ const sphere = geom.boundingSphere;
126
+ if (sphere) {
127
+ geoInspection.boundingSphere = {
128
+ center: [sphere.center.x, sphere.center.y, sphere.center.z],
129
+ radius: sphere.radius
130
+ };
131
+ }
132
+ inspection.geometry = geoInspection;
77
133
  }
78
- if (geom.index) {
79
- geoInspection.index = { count: geom.index.count };
80
- }
81
- geom.computeBoundingSphere();
82
- const sphere = geom.boundingSphere;
83
- if (sphere) {
84
- geoInspection.boundingSphere = {
85
- center: [sphere.center.x, sphere.center.y, sphere.center.z],
86
- radius: sphere.radius
134
+ }
135
+ } catch {
136
+ r3fLog("store", `inspectObject: geometry inspection failed for "${obj.name || obj.uuid}"`);
137
+ }
138
+ try {
139
+ if ("material" in obj) {
140
+ const rawMat = obj.material;
141
+ const mat = Array.isArray(rawMat) ? rawMat[0] : rawMat;
142
+ if (mat instanceof Material) {
143
+ const matInspection = {
144
+ type: mat.type,
145
+ transparent: mat.transparent,
146
+ opacity: mat.opacity,
147
+ side: mat.side
87
148
  };
88
- }
89
- inspection.geometry = geoInspection;
90
- }
91
- }
92
- if ("material" in obj) {
93
- const rawMat = obj.material;
94
- const mat = Array.isArray(rawMat) ? rawMat[0] : rawMat;
95
- if (mat instanceof Material) {
96
- const matInspection = {
97
- type: mat.type,
98
- transparent: mat.transparent,
99
- opacity: mat.opacity,
100
- side: mat.side
101
- };
102
- if ("color" in mat && mat.color instanceof Color) {
103
- matInspection.color = "#" + mat.color.getHexString();
104
- }
105
- if ("map" in mat) {
106
- const map = mat.map;
107
- if (map) {
108
- matInspection.map = map.name || map.uuid || "unnamed";
149
+ if ("color" in mat && mat.color instanceof Color) {
150
+ matInspection.color = "#" + mat.color.getHexString();
109
151
  }
110
- }
111
- if ("uniforms" in mat) {
112
- const uniforms = mat.uniforms;
113
- matInspection.uniforms = {};
114
- for (const [key, uniform] of Object.entries(uniforms)) {
115
- const val = uniform.value;
116
- if (val === null || val === void 0) {
117
- matInspection.uniforms[key] = val;
118
- } else if (typeof val === "number" || typeof val === "boolean" || typeof val === "string") {
119
- matInspection.uniforms[key] = val;
120
- } else if (typeof val === "object" && "toArray" in val) {
121
- matInspection.uniforms[key] = val.toArray();
122
- } else {
123
- matInspection.uniforms[key] = `[${typeof val}]`;
152
+ if ("map" in mat) {
153
+ const map = mat.map;
154
+ if (map) {
155
+ matInspection.map = map.name || map.uuid || "unnamed";
156
+ }
157
+ }
158
+ if ("uniforms" in mat) {
159
+ const uniforms = mat.uniforms;
160
+ matInspection.uniforms = {};
161
+ for (const [key, uniform] of Object.entries(uniforms)) {
162
+ const val = uniform.value;
163
+ if (val === null || val === void 0) {
164
+ matInspection.uniforms[key] = val;
165
+ } else if (typeof val === "number" || typeof val === "boolean" || typeof val === "string") {
166
+ matInspection.uniforms[key] = val;
167
+ } else if (typeof val === "object" && "toArray" in val) {
168
+ matInspection.uniforms[key] = val.toArray();
169
+ } else {
170
+ matInspection.uniforms[key] = `[${typeof val}]`;
171
+ }
124
172
  }
125
173
  }
174
+ inspection.material = matInspection;
126
175
  }
127
- inspection.material = matInspection;
128
176
  }
177
+ } catch {
178
+ r3fLog("store", `inspectObject: material inspection failed for "${obj.name || obj.uuid}"`);
129
179
  }
130
180
  return inspection;
131
181
  }
@@ -175,15 +225,25 @@ var ObjectStore = class {
175
225
  nameSet.add(obj);
176
226
  }
177
227
  this._emit({ type: "add", object: obj, metadata: meta });
228
+ if (meta.testId) {
229
+ r3fLog("store", `Registered "${meta.testId}" (${meta.type})`);
230
+ }
178
231
  return meta;
179
232
  }
180
233
  /**
181
234
  * Register an entire subtree (object + all descendants).
235
+ * Individual objects that fail to register are skipped (logged when debug
236
+ * is enabled) so that one bad object doesn't prevent the rest from being
237
+ * tracked.
182
238
  */
183
239
  registerTree(root) {
184
240
  this._trackedRoots.add(root);
185
241
  root.traverse((obj) => {
186
- this.register(obj);
242
+ try {
243
+ this.register(obj);
244
+ } catch (err) {
245
+ r3fLog("store", `registerTree: failed to register "${obj.name || obj.uuid}"`, err);
246
+ }
187
247
  });
188
248
  }
189
249
  /**
@@ -208,6 +268,9 @@ var ObjectStore = class {
208
268
  }
209
269
  }
210
270
  }
271
+ if (meta.testId) {
272
+ r3fLog("store", `Unregistered "${meta.testId}" (${meta.type})`);
273
+ }
211
274
  this._emit({ type: "remove", object: obj, metadata: meta });
212
275
  }
213
276
  /**
@@ -225,13 +288,22 @@ var ObjectStore = class {
225
288
  /**
226
289
  * Refresh Tier 1 metadata from the live Three.js object.
227
290
  * Returns true if any values changed.
291
+ * Returns false (no change) if extracting metadata throws so that the
292
+ * previous metadata is preserved.
228
293
  */
229
294
  update(obj) {
230
295
  const prev = this._metaByObject.get(obj);
231
296
  if (!prev) return false;
232
- const curr = extractMetadata(obj);
297
+ let curr;
298
+ try {
299
+ curr = extractMetadata(obj);
300
+ } catch (err) {
301
+ r3fLog("store", `update: extractMetadata failed for "${obj.name || obj.uuid}"`, err);
302
+ return false;
303
+ }
233
304
  if (hasChanged(prev, curr)) {
234
305
  if (prev.testId !== curr.testId) {
306
+ r3fLog("store", `testId changed: "${prev.testId}" \u2192 "${curr.testId}" (${curr.type})`);
235
307
  if (prev.testId) this._objectsByTestId.delete(prev.testId);
236
308
  if (curr.testId) this._objectsByTestId.set(curr.testId, obj);
237
309
  }
@@ -299,6 +371,64 @@ var ObjectStore = class {
299
371
  }
300
372
  return results;
301
373
  }
374
+ /**
375
+ * Batch lookup: get metadata for multiple objects by testId or uuid.
376
+ * Returns a Map from the requested id to its metadata (or null if not found).
377
+ * Single round-trip — much more efficient than calling getByTestId/getByUuid
378
+ * in a loop for BIM/CAD scenes with many objects.
379
+ * O(k) where k is the number of requested ids.
380
+ */
381
+ getObjects(ids) {
382
+ const results = /* @__PURE__ */ new Map();
383
+ for (const id of ids) {
384
+ const meta = this.getByTestId(id) ?? this.getByUuid(id);
385
+ results.set(id, meta);
386
+ }
387
+ return results;
388
+ }
389
+ /**
390
+ * Get all objects of a given Three.js type (e.g. "Mesh", "Group", "Line").
391
+ * Linear scan — O(n) where n is total tracked objects.
392
+ */
393
+ getByType(type) {
394
+ const results = [];
395
+ for (const obj of this.getFlatList()) {
396
+ const meta = this._metaByObject.get(obj);
397
+ if (meta && meta.type === type) results.push(meta);
398
+ }
399
+ return results;
400
+ }
401
+ /**
402
+ * Get all objects that have a specific userData key.
403
+ * If `value` is provided, only returns objects where `userData[key]` matches.
404
+ * Uses JSON.stringify for deep equality on complex values.
405
+ * Linear scan — O(n).
406
+ */
407
+ getByUserData(key, value) {
408
+ const results = [];
409
+ const checkValue = value !== void 0;
410
+ const valueJson = checkValue ? JSON.stringify(value) : "";
411
+ for (const obj of this.getFlatList()) {
412
+ if (!(key in obj.userData)) continue;
413
+ if (checkValue && JSON.stringify(obj.userData[key]) !== valueJson) continue;
414
+ const meta = this._metaByObject.get(obj);
415
+ if (meta) results.push(meta);
416
+ }
417
+ return results;
418
+ }
419
+ /**
420
+ * Count objects of a given Three.js type.
421
+ * More efficient than getByType().length — no array allocation.
422
+ * Linear scan — O(n).
423
+ */
424
+ getCountByType(type) {
425
+ let count = 0;
426
+ for (const obj of this.getFlatList()) {
427
+ const meta = this._metaByObject.get(obj);
428
+ if (meta && meta.type === type) count++;
429
+ }
430
+ return count;
431
+ }
302
432
  /** Get the raw Three.js Object3D by testId or uuid. */
303
433
  getObject3D(idOrUuid) {
304
434
  return this._objectsByTestId.get(idOrUuid) ?? this._objectByUuid.get(idOrUuid) ?? null;
@@ -1032,6 +1162,7 @@ function registerSubtree(obj, store, mirror) {
1032
1162
  function patchObject3D(store, mirror) {
1033
1163
  _activePairs.push({ store, mirror });
1034
1164
  if (!_patched) {
1165
+ r3fLog("patch", "Patching Object3D.prototype.add and .remove");
1035
1166
  _originalAdd = Object3D.prototype.add;
1036
1167
  _originalRemove = Object3D.prototype.remove;
1037
1168
  Object3D.prototype.add = function patchedAdd(...objects) {
@@ -1040,8 +1171,15 @@ function patchObject3D(store, mirror) {
1040
1171
  if (pair) {
1041
1172
  for (const obj of objects) {
1042
1173
  if (obj === this) continue;
1043
- registerSubtree(obj, pair.store, pair.mirror);
1174
+ try {
1175
+ r3fLog("patch", `patchedAdd: "${obj.name || obj.type}" added to "${this.name || this.type}"`);
1176
+ registerSubtree(obj, pair.store, pair.mirror);
1177
+ } catch (err) {
1178
+ r3fLog("patch", `patchedAdd: failed to register "${obj.name || obj.type}"`, err);
1179
+ }
1044
1180
  }
1181
+ pair.store.update(this);
1182
+ pair.store.markDirty(this);
1045
1183
  }
1046
1184
  return this;
1047
1185
  };
@@ -1050,11 +1188,18 @@ function patchObject3D(store, mirror) {
1050
1188
  if (pair) {
1051
1189
  for (const obj of objects) {
1052
1190
  if (obj === this) continue;
1053
- pair.mirror.onObjectRemoved(obj);
1054
- obj.traverse((child) => {
1055
- pair.store.unregister(child);
1056
- });
1191
+ try {
1192
+ r3fLog("patch", `patchedRemove: "${obj.name || obj.type}" removed from "${this.name || this.type}"`);
1193
+ pair.mirror.onObjectRemoved(obj);
1194
+ obj.traverse((child) => {
1195
+ pair.store.unregister(child);
1196
+ });
1197
+ } catch (err) {
1198
+ r3fLog("patch", `patchedRemove: failed to unregister "${obj.name || obj.type}"`, err);
1199
+ }
1057
1200
  }
1201
+ pair.store.update(this);
1202
+ pair.store.markDirty(this);
1058
1203
  }
1059
1204
  _originalRemove.call(this, ...objects);
1060
1205
  return this;
@@ -1075,6 +1220,7 @@ function patchObject3D(store, mirror) {
1075
1220
  }
1076
1221
  function restoreObject3D() {
1077
1222
  if (!_patched) return;
1223
+ r3fLog("patch", "Restoring original Object3D.prototype.add and .remove");
1078
1224
  if (_originalAdd) {
1079
1225
  Object3D.prototype.add = _originalAdd;
1080
1226
  _originalAdd = null;
@@ -1098,7 +1244,7 @@ function buildNodeTree(store, meta) {
1098
1244
  children.push(buildNodeTree(store, childMeta));
1099
1245
  }
1100
1246
  }
1101
- return {
1247
+ const node = {
1102
1248
  uuid: meta.uuid,
1103
1249
  name: meta.name,
1104
1250
  type: meta.type,
@@ -1109,6 +1255,12 @@ function buildNodeTree(store, meta) {
1109
1255
  scale: [...meta.scale],
1110
1256
  children
1111
1257
  };
1258
+ if (meta.geometryType) node.geometryType = meta.geometryType;
1259
+ if (meta.materialType) node.materialType = meta.materialType;
1260
+ if (meta.vertexCount != null) node.vertexCount = meta.vertexCount;
1261
+ if (meta.triangleCount != null) node.triangleCount = meta.triangleCount;
1262
+ if (meta.instanceCount != null) node.instanceCount = meta.instanceCount;
1263
+ return node;
1112
1264
  }
1113
1265
  function findRoot(store) {
1114
1266
  const allObjects = store.getFlatList();
@@ -1697,6 +1849,7 @@ function click3D(idOrUuid, options = {}) {
1697
1849
  const camera = getCamera();
1698
1850
  const gl = getRenderer();
1699
1851
  const size = getCanvasSize();
1852
+ r3fLog("click", `click3D("${idOrUuid}") \u2014 resolving projection`);
1700
1853
  const projection = projectToScreen(obj, camera, size);
1701
1854
  if (!projection) {
1702
1855
  throw new Error(
@@ -1789,6 +1942,7 @@ function hover3D(idOrUuid, options = {}) {
1789
1942
  const camera = getCamera();
1790
1943
  const gl = getRenderer();
1791
1944
  const size = getCanvasSize();
1945
+ r3fLog("hover", `hover3D("${idOrUuid}") \u2014 resolving projection`);
1792
1946
  const projection = projectToScreen(obj, camera, size);
1793
1947
  if (!projection) {
1794
1948
  throw new Error(
@@ -1823,6 +1977,7 @@ async function drag3D(idOrUuid, delta, options = {}) {
1823
1977
  const camera = getCamera();
1824
1978
  const gl = getRenderer();
1825
1979
  const size = getCanvasSize();
1980
+ r3fLog("drag", `drag3D("${idOrUuid}") mode=${mode}`, delta);
1826
1981
  const projection = projectToScreen(obj, camera, size);
1827
1982
  if (!projection) {
1828
1983
  throw new Error(
@@ -1892,6 +2047,162 @@ function pointerMiss3D(options = {}) {
1892
2047
  dispatchPointerMiss(canvas, options.point);
1893
2048
  }
1894
2049
 
2050
+ // src/interactions/drawPath.ts
2051
+ var _nextDrawPointerId = 5e3;
2052
+ function makeDrawPointerInit(canvas, point, pointerId, pointerType, overrides) {
2053
+ const rect = canvas.getBoundingClientRect();
2054
+ const clientX = rect.left + point.x;
2055
+ const clientY = rect.top + point.y;
2056
+ return {
2057
+ bubbles: true,
2058
+ cancelable: true,
2059
+ composed: true,
2060
+ clientX,
2061
+ clientY,
2062
+ screenX: clientX,
2063
+ screenY: clientY,
2064
+ pointerId,
2065
+ pointerType,
2066
+ isPrimary: true,
2067
+ button: 0,
2068
+ buttons: 1,
2069
+ width: 1,
2070
+ height: 1,
2071
+ pressure: point.pressure ?? 0.5,
2072
+ ...overrides
2073
+ };
2074
+ }
2075
+ async function drawPath(points, options = {}) {
2076
+ if (points.length < 2) {
2077
+ throw new Error(
2078
+ `[react-three-dom] drawPath requires at least 2 points, got ${points.length}.`
2079
+ );
2080
+ }
2081
+ const {
2082
+ stepDelayMs = 0,
2083
+ pointerType = "mouse",
2084
+ clickAtEnd = false,
2085
+ canvas: explicitCanvas
2086
+ } = options;
2087
+ const canvas = explicitCanvas ?? getRenderer().domElement;
2088
+ const pointerId = _nextDrawPointerId++;
2089
+ let eventCount = 0;
2090
+ r3fLog("draw", `drawPath \u2014 ${points.length} points, delay=${stepDelayMs}ms, pointerType=${pointerType}`);
2091
+ const first = points[0];
2092
+ canvas.dispatchEvent(
2093
+ new PointerEvent("pointerdown", makeDrawPointerInit(canvas, first, pointerId, pointerType))
2094
+ );
2095
+ eventCount++;
2096
+ for (let i = 1; i < points.length; i++) {
2097
+ if (stepDelayMs > 0) {
2098
+ await sleep2(stepDelayMs);
2099
+ }
2100
+ canvas.dispatchEvent(
2101
+ new PointerEvent(
2102
+ "pointermove",
2103
+ makeDrawPointerInit(canvas, points[i], pointerId, pointerType)
2104
+ )
2105
+ );
2106
+ eventCount++;
2107
+ }
2108
+ const last = points[points.length - 1];
2109
+ canvas.dispatchEvent(
2110
+ new PointerEvent(
2111
+ "pointerup",
2112
+ makeDrawPointerInit(canvas, last, pointerId, pointerType, {
2113
+ buttons: 0,
2114
+ pressure: 0
2115
+ })
2116
+ )
2117
+ );
2118
+ eventCount++;
2119
+ if (clickAtEnd) {
2120
+ const rect = canvas.getBoundingClientRect();
2121
+ canvas.dispatchEvent(
2122
+ new MouseEvent("click", {
2123
+ bubbles: true,
2124
+ cancelable: true,
2125
+ clientX: rect.left + last.x,
2126
+ clientY: rect.top + last.y,
2127
+ button: 0
2128
+ })
2129
+ );
2130
+ eventCount++;
2131
+ }
2132
+ r3fLog("draw", `drawPath complete \u2014 ${eventCount} events dispatched`);
2133
+ return {
2134
+ eventCount,
2135
+ pointCount: points.length,
2136
+ startPoint: first,
2137
+ endPoint: last
2138
+ };
2139
+ }
2140
+ function linePath(start, end, steps = 10, pressure = 0.5) {
2141
+ const points = [];
2142
+ const totalSteps = steps + 1;
2143
+ for (let i = 0; i <= totalSteps; i++) {
2144
+ const t = i / totalSteps;
2145
+ points.push({
2146
+ x: start.x + (end.x - start.x) * t,
2147
+ y: start.y + (end.y - start.y) * t,
2148
+ pressure
2149
+ });
2150
+ }
2151
+ return points;
2152
+ }
2153
+ function curvePath(start, control, end, steps = 20, pressure = 0.5) {
2154
+ const points = [];
2155
+ for (let i = 0; i <= steps; i++) {
2156
+ const t = i / steps;
2157
+ const invT = 1 - t;
2158
+ points.push({
2159
+ x: invT * invT * start.x + 2 * invT * t * control.x + t * t * end.x,
2160
+ y: invT * invT * start.y + 2 * invT * t * control.y + t * t * end.y,
2161
+ pressure
2162
+ });
2163
+ }
2164
+ return points;
2165
+ }
2166
+ function rectPath(topLeft, bottomRight, pointsPerSide = 5, pressure = 0.5) {
2167
+ const topRight = { x: bottomRight.x, y: topLeft.y };
2168
+ const bottomLeft = { x: topLeft.x, y: bottomRight.y };
2169
+ const sides = [
2170
+ [topLeft, topRight],
2171
+ [topRight, bottomRight],
2172
+ [bottomRight, bottomLeft],
2173
+ [bottomLeft, topLeft]
2174
+ ];
2175
+ const points = [];
2176
+ for (const [from, to] of sides) {
2177
+ for (let i = 0; i < pointsPerSide; i++) {
2178
+ const t = i / pointsPerSide;
2179
+ points.push({
2180
+ x: from.x + (to.x - from.x) * t,
2181
+ y: from.y + (to.y - from.y) * t,
2182
+ pressure
2183
+ });
2184
+ }
2185
+ }
2186
+ points.push({ x: topLeft.x, y: topLeft.y, pressure });
2187
+ return points;
2188
+ }
2189
+ function circlePath(center, radiusX, radiusY, steps = 36, pressure = 0.5) {
2190
+ const ry = radiusY ?? radiusX;
2191
+ const points = [];
2192
+ for (let i = 0; i <= steps; i++) {
2193
+ const angle = i / steps * Math.PI * 2;
2194
+ points.push({
2195
+ x: center.x + Math.cos(angle) * radiusX,
2196
+ y: center.y + Math.sin(angle) * ry,
2197
+ pressure
2198
+ });
2199
+ }
2200
+ return points;
2201
+ }
2202
+ function sleep2(ms) {
2203
+ return new Promise((resolve) => setTimeout(resolve, ms));
2204
+ }
2205
+
1895
2206
  // src/highlight/SelectionManager.ts
1896
2207
  var SelectionManager = class {
1897
2208
  constructor() {
@@ -2046,10 +2357,22 @@ function projectToScreenRect(obj, camera, canvasRect) {
2046
2357
  }
2047
2358
  function exposeGlobalAPI(store) {
2048
2359
  const api = {
2360
+ _ready: true,
2049
2361
  getByTestId: (id) => store.getByTestId(id),
2050
2362
  getByUuid: (uuid) => store.getByUuid(uuid),
2051
2363
  getByName: (name) => store.getByName(name),
2052
2364
  getCount: () => store.getCount(),
2365
+ getByType: (type) => store.getByType(type),
2366
+ getByUserData: (key, value) => store.getByUserData(key, value),
2367
+ getCountByType: (type) => store.getCountByType(type),
2368
+ getObjects: (ids) => {
2369
+ const map = store.getObjects(ids);
2370
+ const result = {};
2371
+ for (const [id, meta] of map) {
2372
+ result[id] = meta;
2373
+ }
2374
+ return result;
2375
+ },
2053
2376
  snapshot: () => createSnapshot(store),
2054
2377
  inspect: (idOrUuid) => store.inspect(idOrUuid),
2055
2378
  click: (idOrUuid) => {
@@ -2064,8 +2387,8 @@ function exposeGlobalAPI(store) {
2064
2387
  hover: (idOrUuid) => {
2065
2388
  hover3D(idOrUuid);
2066
2389
  },
2067
- drag: (idOrUuid, delta) => {
2068
- void drag3D(idOrUuid, delta);
2390
+ drag: async (idOrUuid, delta) => {
2391
+ await drag3D(idOrUuid, delta);
2069
2392
  },
2070
2393
  wheel: (idOrUuid, options) => {
2071
2394
  wheel3D(idOrUuid, options);
@@ -2073,6 +2396,10 @@ function exposeGlobalAPI(store) {
2073
2396
  pointerMiss: () => {
2074
2397
  pointerMiss3D();
2075
2398
  },
2399
+ drawPath: async (points, options) => {
2400
+ const result = await drawPath(points, options);
2401
+ return { eventCount: result.eventCount, pointCount: result.pointCount };
2402
+ },
2076
2403
  select: (idOrUuid) => {
2077
2404
  const obj = store.getObject3D(idOrUuid);
2078
2405
  if (obj && _selectionManager) _selectionManager.select(obj);
@@ -2080,13 +2407,28 @@ function exposeGlobalAPI(store) {
2080
2407
  clearSelection: () => {
2081
2408
  _selectionManager?.clearSelection();
2082
2409
  },
2410
+ getSelection: () => _selectionManager ? _selectionManager.getSelected().map((o) => o.uuid) : [],
2083
2411
  getObject3D: (idOrUuid) => store.getObject3D(idOrUuid),
2084
2412
  version
2085
2413
  };
2086
2414
  window.__R3F_DOM__ = api;
2087
2415
  }
2088
- function removeGlobalAPI() {
2089
- delete window.__R3F_DOM__;
2416
+ function removeGlobalAPI(onlyIfEquals) {
2417
+ r3fLog("bridge", "removeGlobalAPI called (deferred)");
2418
+ if (onlyIfEquals !== void 0) {
2419
+ const ref = onlyIfEquals;
2420
+ queueMicrotask(() => {
2421
+ if (window.__R3F_DOM__ === ref) {
2422
+ delete window.__R3F_DOM__;
2423
+ r3fLog("bridge", "Global API removed");
2424
+ } else {
2425
+ r3fLog("bridge", "Global API not removed \u2014 replaced by new instance (Strict Mode remount)");
2426
+ }
2427
+ });
2428
+ } else {
2429
+ delete window.__R3F_DOM__;
2430
+ r3fLog("bridge", "Global API removed (immediate)");
2431
+ }
2090
2432
  }
2091
2433
  function setElementRect(el, l, t, w, h) {
2092
2434
  const d = el.dataset;
@@ -2107,7 +2449,8 @@ function ThreeDom({
2107
2449
  timeBudgetMs = 0.5,
2108
2450
  maxDomNodes = 2e3,
2109
2451
  initialDepth = 3,
2110
- enabled = true
2452
+ enabled = true,
2453
+ debug = false
2111
2454
  } = {}) {
2112
2455
  const scene = useThree((s) => s.scene);
2113
2456
  const camera = useThree((s) => s.camera);
@@ -2117,6 +2460,8 @@ function ThreeDom({
2117
2460
  const positionCursorRef = useRef(0);
2118
2461
  useEffect(() => {
2119
2462
  if (!enabled) return;
2463
+ if (debug) enableDebug(true);
2464
+ r3fLog("setup", "ThreeDom effect started", { enabled, debug, root, maxDomNodes });
2120
2465
  const canvas = gl.domElement;
2121
2466
  const canvasParent = canvas.parentElement;
2122
2467
  let rootElement = null;
@@ -2143,57 +2488,115 @@ function ThreeDom({
2143
2488
  "overflow: hidden",
2144
2489
  "z-index: 10"
2145
2490
  ].join(";");
2146
- const store = new ObjectStore();
2147
- const mirror = new DomMirror(store, maxDomNodes);
2148
- mirror.setRoot(rootElement);
2149
- ensureCustomElements(store);
2150
- store.registerTree(scene);
2151
- mirror.materializeSubtree(scene.uuid, initialDepth);
2152
- const unpatch = patchObject3D(store, mirror);
2153
- setInteractionState(store, camera, gl, size);
2154
- const selectionManager = new SelectionManager();
2155
- _selectionManager = selectionManager;
2156
- _highlighter = null;
2157
- exposeGlobalAPI(store);
2158
- _store3 = store;
2159
- _mirror = mirror;
2160
- const initialCanvasRect = canvas.getBoundingClientRect();
2161
- const allObjects = store.getFlatList();
2162
- for (const obj of allObjects) {
2163
- if (obj.userData?.__r3fdom_internal) continue;
2164
- const el = mirror.getElement(obj.uuid);
2165
- if (!el) continue;
2166
- if (obj.type === "Scene") {
2167
- setElementRect(el, 0, 0, Math.round(initialCanvasRect.width), Math.round(initialCanvasRect.height));
2168
- continue;
2169
- }
2170
- const rect = projectToScreenRect(obj, camera, initialCanvasRect);
2171
- if (rect) {
2172
- let parentLeft = 0;
2173
- let parentTop = 0;
2174
- if (obj.parent && obj.parent.type !== "Scene") {
2175
- const parentRect = projectToScreenRect(obj.parent, camera, initialCanvasRect);
2176
- if (parentRect) {
2177
- parentLeft = Math.round(parentRect.left);
2178
- parentTop = Math.round(parentRect.top);
2491
+ let store = null;
2492
+ let mirror = null;
2493
+ let unpatch = null;
2494
+ let selectionManager = null;
2495
+ let currentApi;
2496
+ try {
2497
+ store = new ObjectStore();
2498
+ mirror = new DomMirror(store, maxDomNodes);
2499
+ mirror.setRoot(rootElement);
2500
+ r3fLog("setup", "Store and mirror created");
2501
+ ensureCustomElements(store);
2502
+ store.registerTree(scene);
2503
+ r3fLog("setup", `Registered scene tree: ${store.getCount()} objects`);
2504
+ mirror.materializeSubtree(scene.uuid, initialDepth);
2505
+ unpatch = patchObject3D(store, mirror);
2506
+ setInteractionState(store, camera, gl, size);
2507
+ r3fLog("setup", "Object3D patched, interaction state set");
2508
+ selectionManager = new SelectionManager();
2509
+ _selectionManager = selectionManager;
2510
+ _highlighter = null;
2511
+ exposeGlobalAPI(store);
2512
+ r3fLog("bridge", "exposeGlobalAPI called \u2014 bridge is live, _ready=true");
2513
+ currentApi = window.__R3F_DOM__;
2514
+ _store3 = store;
2515
+ _mirror = mirror;
2516
+ const initialCanvasRect = canvas.getBoundingClientRect();
2517
+ const allObjects = store.getFlatList();
2518
+ for (const obj of allObjects) {
2519
+ if (obj.userData?.__r3fdom_internal) continue;
2520
+ const el = mirror.getElement(obj.uuid);
2521
+ if (!el) continue;
2522
+ if (obj.type === "Scene") {
2523
+ setElementRect(el, 0, 0, Math.round(initialCanvasRect.width), Math.round(initialCanvasRect.height));
2524
+ continue;
2525
+ }
2526
+ const rect = projectToScreenRect(obj, camera, initialCanvasRect);
2527
+ if (rect) {
2528
+ let parentLeft = 0;
2529
+ let parentTop = 0;
2530
+ if (obj.parent && obj.parent.type !== "Scene") {
2531
+ const parentRect = projectToScreenRect(obj.parent, camera, initialCanvasRect);
2532
+ if (parentRect) {
2533
+ parentLeft = Math.round(parentRect.left);
2534
+ parentTop = Math.round(parentRect.top);
2535
+ }
2179
2536
  }
2537
+ setElementRect(
2538
+ el,
2539
+ Math.round(rect.left) - parentLeft,
2540
+ Math.round(rect.top) - parentTop,
2541
+ Math.round(rect.width),
2542
+ Math.round(rect.height)
2543
+ );
2180
2544
  }
2181
- setElementRect(
2182
- el,
2183
- Math.round(rect.left) - parentLeft,
2184
- Math.round(rect.top) - parentTop,
2185
- Math.round(rect.width),
2186
- Math.round(rect.height)
2187
- );
2188
2545
  }
2546
+ } catch (err) {
2547
+ const errorMsg = err instanceof Error ? err.message : String(err);
2548
+ r3fLog("setup", "ThreeDom setup failed", err);
2549
+ console.error("[react-three-dom] Setup failed:", err);
2550
+ window.__R3F_DOM__ = {
2551
+ _ready: false,
2552
+ _error: errorMsg,
2553
+ getByTestId: () => null,
2554
+ getByUuid: () => null,
2555
+ getByName: () => [],
2556
+ getCount: () => 0,
2557
+ getByType: () => [],
2558
+ getByUserData: () => [],
2559
+ getCountByType: () => 0,
2560
+ getObjects: (ids) => {
2561
+ const result = {};
2562
+ for (const id of ids) result[id] = null;
2563
+ return result;
2564
+ },
2565
+ snapshot: () => ({ timestamp: 0, objectCount: 0, tree: { uuid: "", name: "", type: "Scene", visible: true, position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1], children: [] } }),
2566
+ inspect: () => null,
2567
+ click: () => {
2568
+ },
2569
+ doubleClick: () => {
2570
+ },
2571
+ contextMenu: () => {
2572
+ },
2573
+ hover: () => {
2574
+ },
2575
+ drag: async () => {
2576
+ },
2577
+ wheel: () => {
2578
+ },
2579
+ pointerMiss: () => {
2580
+ },
2581
+ drawPath: async () => ({ eventCount: 0, pointCount: 0 }),
2582
+ select: () => {
2583
+ },
2584
+ clearSelection: () => {
2585
+ },
2586
+ getSelection: () => [],
2587
+ getObject3D: () => null,
2588
+ version
2589
+ };
2590
+ currentApi = window.__R3F_DOM__;
2189
2591
  }
2190
2592
  return () => {
2191
- unpatch();
2192
- removeGlobalAPI();
2593
+ r3fLog("setup", "ThreeDom cleanup started");
2594
+ if (unpatch) unpatch();
2595
+ removeGlobalAPI(currentApi);
2193
2596
  clearInteractionState();
2194
- selectionManager.dispose();
2195
- mirror.dispose();
2196
- store.dispose();
2597
+ if (selectionManager) selectionManager.dispose();
2598
+ if (mirror) mirror.dispose();
2599
+ if (store) store.dispose();
2197
2600
  if (createdRoot && rootElement?.parentNode) {
2198
2601
  rootElement.parentNode.removeChild(rootElement);
2199
2602
  }
@@ -2201,69 +2604,75 @@ function ThreeDom({
2201
2604
  _mirror = null;
2202
2605
  _selectionManager = null;
2203
2606
  _highlighter = null;
2607
+ if (debug) enableDebug(false);
2608
+ r3fLog("setup", "ThreeDom cleanup complete");
2204
2609
  };
2205
- }, [scene, camera, gl, size, enabled, root, maxDomNodes, initialDepth]);
2610
+ }, [scene, camera, gl, size, enabled, root, maxDomNodes, initialDepth, debug]);
2206
2611
  useFrame(() => {
2207
2612
  if (!enabled || !_store3 || !_mirror) return;
2208
- setInteractionState(_store3, camera, gl, size);
2209
- const store = _store3;
2210
- const mirror = _mirror;
2211
- const canvas = gl.domElement;
2212
- const canvasRect = canvas.getBoundingClientRect();
2213
- const start = performance.now();
2214
- const dirtyObjects = store.drainDirtyQueue();
2215
- for (const obj of dirtyObjects) {
2216
- store.update(obj);
2217
- mirror.syncAttributes(obj);
2218
- }
2219
- const budgetRemaining = timeBudgetMs - (performance.now() - start);
2220
- if (budgetRemaining > 0.1) {
2221
- const objects2 = store.getFlatList();
2222
- if (objects2.length > 0) {
2223
- const end = Math.min(cursorRef.current + batchSize, objects2.length);
2224
- for (let i = cursorRef.current; i < end; i++) {
2225
- if (performance.now() - start > timeBudgetMs) break;
2226
- const obj = objects2[i];
2227
- const changed = store.update(obj);
2228
- if (changed) mirror.syncAttributes(obj);
2229
- }
2230
- cursorRef.current = end >= objects2.length ? 0 : end;
2613
+ try {
2614
+ setInteractionState(_store3, camera, gl, size);
2615
+ const store = _store3;
2616
+ const mirror = _mirror;
2617
+ const canvas = gl.domElement;
2618
+ const canvasRect = canvas.getBoundingClientRect();
2619
+ const start = performance.now();
2620
+ const dirtyObjects = store.drainDirtyQueue();
2621
+ for (const obj of dirtyObjects) {
2622
+ store.update(obj);
2623
+ mirror.syncAttributes(obj);
2231
2624
  }
2232
- }
2233
- const objects = store.getFlatList();
2234
- if (objects.length > 0) {
2235
- const posEnd = Math.min(positionCursorRef.current + 50, objects.length);
2236
- for (let i = positionCursorRef.current; i < posEnd; i++) {
2237
- const obj = objects[i];
2238
- if (obj.userData?.__r3fdom_internal) continue;
2239
- const el = mirror.getElement(obj.uuid);
2240
- if (!el) continue;
2241
- if (obj.type === "Scene") {
2242
- setElementRect(el, 0, 0, Math.round(canvasRect.width), Math.round(canvasRect.height));
2243
- continue;
2625
+ const budgetRemaining = timeBudgetMs - (performance.now() - start);
2626
+ if (budgetRemaining > 0.1) {
2627
+ const objects2 = store.getFlatList();
2628
+ if (objects2.length > 0) {
2629
+ const end = Math.min(cursorRef.current + batchSize, objects2.length);
2630
+ for (let i = cursorRef.current; i < end; i++) {
2631
+ if (performance.now() - start > timeBudgetMs) break;
2632
+ const obj = objects2[i];
2633
+ const changed = store.update(obj);
2634
+ if (changed) mirror.syncAttributes(obj);
2635
+ }
2636
+ cursorRef.current = end >= objects2.length ? 0 : end;
2244
2637
  }
2245
- const rect = projectToScreenRect(obj, camera, canvasRect);
2246
- if (rect) {
2247
- let parentLeft = 0;
2248
- let parentTop = 0;
2249
- if (obj.parent && obj.parent.type !== "Scene") {
2250
- const parentRect = projectToScreenRect(obj.parent, camera, canvasRect);
2251
- if (parentRect) {
2252
- parentLeft = Math.round(parentRect.left);
2253
- parentTop = Math.round(parentRect.top);
2638
+ }
2639
+ const objects = store.getFlatList();
2640
+ if (objects.length > 0) {
2641
+ const posEnd = Math.min(positionCursorRef.current + 50, objects.length);
2642
+ for (let i = positionCursorRef.current; i < posEnd; i++) {
2643
+ const obj = objects[i];
2644
+ if (obj.userData?.__r3fdom_internal) continue;
2645
+ const el = mirror.getElement(obj.uuid);
2646
+ if (!el) continue;
2647
+ if (obj.type === "Scene") {
2648
+ setElementRect(el, 0, 0, Math.round(canvasRect.width), Math.round(canvasRect.height));
2649
+ continue;
2650
+ }
2651
+ const rect = projectToScreenRect(obj, camera, canvasRect);
2652
+ if (rect) {
2653
+ let parentLeft = 0;
2654
+ let parentTop = 0;
2655
+ if (obj.parent && obj.parent.type !== "Scene") {
2656
+ const parentRect = projectToScreenRect(obj.parent, camera, canvasRect);
2657
+ if (parentRect) {
2658
+ parentLeft = Math.round(parentRect.left);
2659
+ parentTop = Math.round(parentRect.top);
2660
+ }
2254
2661
  }
2662
+ const l = Math.round(rect.left) - parentLeft;
2663
+ const t = Math.round(rect.top) - parentTop;
2664
+ const w = Math.round(rect.width);
2665
+ const h = Math.round(rect.height);
2666
+ setElementRect(el, l, t, w, h);
2667
+ if (el.style.display === "none") el.style.display = "block";
2668
+ } else {
2669
+ if (el.style.display !== "none") el.style.display = "none";
2255
2670
  }
2256
- const l = Math.round(rect.left) - parentLeft;
2257
- const t = Math.round(rect.top) - parentTop;
2258
- const w = Math.round(rect.width);
2259
- const h = Math.round(rect.height);
2260
- setElementRect(el, l, t, w, h);
2261
- if (el.style.display === "none") el.style.display = "block";
2262
- } else {
2263
- if (el.style.display !== "none") el.style.display = "none";
2264
2671
  }
2672
+ positionCursorRef.current = posEnd >= objects.length ? 0 : posEnd;
2265
2673
  }
2266
- positionCursorRef.current = posEnd >= objects.length ? 0 : posEnd;
2674
+ } catch (err) {
2675
+ r3fLog("sync", "Per-frame sync error", err);
2267
2676
  }
2268
2677
  });
2269
2678
  return null;
@@ -2614,6 +3023,6 @@ var Highlighter = class {
2614
3023
  }
2615
3024
  };
2616
3025
 
2617
- export { DomMirror, Highlighter, MANAGED_ATTRIBUTES, ObjectStore, SelectionManager, TAG_MAP, ThreeDom, ThreeElement, applyAttributes, click3D, computeAttributes, contextMenu3D, createFlatSnapshot, createSnapshot, dispatchClick, dispatchContextMenu, dispatchDoubleClick, dispatchDrag, dispatchHover, dispatchPointerMiss, dispatchUnhover, dispatchWheel, doubleClick3D, drag3D, ensureCustomElements, getHighlighter, getMirror, getSelectionManager, getStore2 as getStore, getTagForType, hover3D, isInFrustum, isPatched, patchObject3D, pointerMiss3D, previewDragWorldDelta, projectAllSamplePoints, projectToScreen, resolveObject, restoreObject3D, screenDeltaToWorld, unhover3D, verifyRaycastHit, verifyRaycastHitMultiPoint, version, wheel3D };
3026
+ export { DomMirror, Highlighter, MANAGED_ATTRIBUTES, ObjectStore, 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, getHighlighter, 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 };
2618
3027
  //# sourceMappingURL=index.js.map
2619
3028
  //# sourceMappingURL=index.js.map