@jamesyong42/infinite-canvas 1.2.0 → 1.3.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.
Files changed (56) hide show
  1. package/README.md +65 -0
  2. package/dist/advanced.cjs +61 -24
  3. package/dist/advanced.cjs.map +1 -1
  4. package/dist/advanced.d.cts +180 -64
  5. package/dist/advanced.d.cts.map +1 -1
  6. package/dist/advanced.d.mts +180 -64
  7. package/dist/advanced.d.mts.map +1 -1
  8. package/dist/advanced.mjs +29 -12
  9. package/dist/advanced.mjs.map +1 -1
  10. package/dist/devtools.cjs +22 -22
  11. package/dist/devtools.cjs.map +1 -1
  12. package/dist/devtools.d.cts +2 -2
  13. package/dist/devtools.d.cts.map +1 -1
  14. package/dist/devtools.d.mts +2 -2
  15. package/dist/devtools.d.mts.map +1 -1
  16. package/dist/devtools.mjs +2 -2
  17. package/dist/devtools.mjs.map +1 -1
  18. package/dist/{hooks-BwY7rRHg.mjs → ecs-3kimUV5Z.mjs} +238 -74
  19. package/dist/ecs-3kimUV5Z.mjs.map +1 -0
  20. package/dist/{hooks-DHShH86C.cjs → ecs-B4QrqfvQ.cjs} +320 -108
  21. package/dist/ecs-B4QrqfvQ.cjs.map +1 -0
  22. package/dist/hooks-CtP02JNt.cjs +3762 -0
  23. package/dist/hooks-CtP02JNt.cjs.map +1 -0
  24. package/dist/hooks-gsQDDE56.mjs +3494 -0
  25. package/dist/hooks-gsQDDE56.mjs.map +1 -0
  26. package/dist/index-3GY7T8JM.d.mts +480 -0
  27. package/dist/index-3GY7T8JM.d.mts.map +1 -0
  28. package/dist/index-B7B1tRPl.d.cts +480 -0
  29. package/dist/index-B7B1tRPl.d.cts.map +1 -0
  30. package/dist/index-DSdbSQ_t.d.cts +1451 -0
  31. package/dist/index-DSdbSQ_t.d.cts.map +1 -0
  32. package/dist/index-Dj9odADH.d.mts +1451 -0
  33. package/dist/index-Dj9odADH.d.mts.map +1 -0
  34. package/dist/index.cjs +3865 -643
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +315 -138
  37. package/dist/index.d.cts.map +1 -1
  38. package/dist/index.d.mts +315 -138
  39. package/dist/index.d.mts.map +1 -1
  40. package/dist/index.mjs +3767 -571
  41. package/dist/index.mjs.map +1 -1
  42. package/package.json +1 -1
  43. package/dist/SelectionRenderer-CR2PBQwx.d.cts +0 -105
  44. package/dist/SelectionRenderer-CR2PBQwx.d.cts.map +0 -1
  45. package/dist/SelectionRenderer-DlsBstAq.d.mts +0 -105
  46. package/dist/SelectionRenderer-DlsBstAq.d.mts.map +0 -1
  47. package/dist/WebGLWidgetLayer-BBMuwzHq.cjs +0 -3560
  48. package/dist/WebGLWidgetLayer-BBMuwzHq.cjs.map +0 -1
  49. package/dist/WebGLWidgetLayer-C3p1tnpm.mjs +0 -3375
  50. package/dist/WebGLWidgetLayer-C3p1tnpm.mjs.map +0 -1
  51. package/dist/engine-BfbvWXSk.d.mts +0 -982
  52. package/dist/engine-BfbvWXSk.d.mts.map +0 -1
  53. package/dist/engine-CCjuFMC-.d.cts +0 -982
  54. package/dist/engine-CCjuFMC-.d.cts.map +0 -1
  55. package/dist/hooks-BwY7rRHg.mjs.map +0 -1
  56. package/dist/hooks-DHShH86C.cjs.map +0 -1
package/README.md CHANGED
@@ -352,6 +352,71 @@ engine.spawn('my-3d', { at: { x: 100, y: 100 } });
352
352
 
353
353
  WebGL widgets get a transparent R3F canvas layered between the grid and DOM layers. The R3F camera is synced with the engine camera every frame.
354
354
 
355
+ ## Pointer Events
356
+
357
+ Both DOM and R3F widgets dispatch pointer events naturally — write `onClick`, `onPointerOver`, `onPointerEnter` etc. the way you would in any React or R3F app. The canvas's engine state machine (drag, select, resize, marquee, double-click navigation) runs on the same event after your handlers, so the two layers compose without you wiring anything.
358
+
359
+ To opt a specific event out of engine semantics — e.g. a button inside a widget that should not start a drag — call `stopPropagation()` from your handler. Native HTML interactives (`<button>`, `<input>`, `<textarea>`, `<select>`, `[contenteditable]`) opt out automatically.
360
+
361
+ ### DOM widget
362
+
363
+ ```tsx
364
+ function MyDomWidget({ entityId }: DomWidgetProps) {
365
+ return (
366
+ <div>
367
+ <span>drag me anywhere — engine handles it</span>
368
+
369
+ <button onClick={(e) => { e.stopPropagation(); doThing(); }}>
370
+ clicking me runs doThing(), no drag
371
+ </button>
372
+ </div>
373
+ );
374
+ }
375
+ ```
376
+
377
+ `e.stopPropagation()` on the React synthetic event halts both widget-internal bubbling and the canvas-level engine call.
378
+
379
+ ### R3F widget
380
+
381
+ ```tsx
382
+ function MyR3FWidget({ entityId, width, height }: R3FWidgetProps) {
383
+ const [hover, setHover] = useState(false);
384
+ return (
385
+ <group>
386
+ {/* Drag handle: no stopPropagation — engine starts a drag normally. */}
387
+ <mesh
388
+ onPointerOver={() => setHover(true)}
389
+ onPointerOut={() => setHover(false)}>
390
+ <planeGeometry args={[width, height]} />
391
+ <meshBasicMaterial color={hover ? 'lightblue' : 'white'} />
392
+ </mesh>
393
+
394
+ {/* Button mesh: stopPropagation halts R3F bubble + nativeEvent halts engine. */}
395
+ <mesh
396
+ position={[0, 0, 1]}
397
+ onClick={(e) => {
398
+ e.stopPropagation(); // halts R3F bubble within widget
399
+ e.nativeEvent.stopPropagation(); // halts engine drag/select
400
+ doThing();
401
+ }}>
402
+ <boxGeometry args={[40, 20, 5]} />
403
+ <meshStandardMaterial color="orange" />
404
+ </mesh>
405
+ </group>
406
+ );
407
+ }
408
+ ```
409
+
410
+ R3F's `event.stopPropagation()` only halts further dispatch within the widget's own scene. To prevent the canvas-level engine routing from firing too — for example, a clickable mesh that should never start a drag — additionally call `event.nativeEvent.stopPropagation()`. The standard DOM idiom: stop the native event, the bus never sees it.
411
+
412
+ `event.point`, `event.uv`, `event.face`, `event.intersections` are populated by a widget-local raycast against the widget's own scene with widget-local coordinates — `event.point.x = 0` is the widget centre, not the canvas centre.
413
+
414
+ ### Hover and capture
415
+
416
+ Cross-widget hover transitions (cursor moves from widget A's mesh into widget B's mesh) fire `onPointerLeave` on A's last hit and `onPointerEnter` on B's first hit automatically. Within a widget, R3F handles enter/leave between meshes natively.
417
+
418
+ When the engine takes a drag (`capture-drag`), `capture-resize`, or `passthrough-track-drag` directive, the bus calls `setPointerCapture` on the canvas container. While captured, R3F's mesh-level events suspend — the engine owns the pointer until release. This matches the native browser pointer-capture model, so existing R3F patterns like `setPointerCapture` on a mesh continue to work for widget-internal drags that don't conflict with engine semantics.
419
+
355
420
  ## Configuration
356
421
 
357
422
  ### Grid
package/dist/advanced.cjs CHANGED
@@ -1,23 +1,24 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_WebGLWidgetLayer = require("./WebGLWidgetLayer-BBMuwzHq.cjs");
3
- const require_hooks = require("./hooks-DHShH86C.cjs");
4
- //#region src/serialization.ts
2
+ const require_hooks = require("./hooks-CtP02JNt.cjs");
3
+ const require_ecs = require("./ecs-B4QrqfvQ.cjs");
4
+ //#region src/ecs/serialization.ts
5
5
  /**
6
6
  * Serializes all entities, components, and tags to a JSON-compatible document.
7
7
  * Requires registries of known component and tag types for enumeration.
8
8
  */
9
- function serializeWorld(world, componentTypes, tagTypes, camera, navigationFrames) {
9
+ function serializeWorld(world, componentTypes, tagTypes, camera, rootCamera) {
10
10
  const entities = [];
11
11
  const allEntities = world.query();
12
12
  for (const entityId of allEntities) {
13
13
  const components = {};
14
14
  const tags = [];
15
15
  for (const type of componentTypes) {
16
+ if (type.name === "PreDragLayer" || type.name === "TransformTween" || type.name === "CardOverlapHotPoint") continue;
16
17
  const data = world.getComponent(entityId, type);
17
18
  if (data !== void 0) components[type.name] = structuredClone(data);
18
19
  }
19
20
  for (const type of tagTypes) if (world.hasTag(entityId, type)) {
20
- if (type.name !== "Active" && type.name !== "Visible") tags.push(type.name);
21
+ if (type.name !== "Active" && type.name !== "Visible" && type.name !== "Culled" && type.name !== "OverlapCandidate" && type.name !== "OverlapTarget") tags.push(type.name);
21
22
  }
22
23
  if (Object.keys(components).length > 0 || tags.length > 0) entities.push({
23
24
  id: entityId,
@@ -30,7 +31,7 @@ function serializeWorld(world, componentTypes, tagTypes, camera, navigationFrame
30
31
  entities,
31
32
  resources: {
32
33
  camera: { ...camera },
33
- navigationStack: structuredClone(navigationFrames)
34
+ rootCamera: { ...rootCamera }
34
35
  }
35
36
  };
36
37
  }
@@ -59,16 +60,31 @@ function deserializeWorld(world, doc, componentTypes, tagTypes) {
59
60
  }
60
61
  }
61
62
  for (const [_oldId, newId] of idMap) {
62
- const parent = world.getComponent(newId, require_hooks.Parent);
63
+ const parent = world.getComponent(newId, require_ecs.ParentFrame);
63
64
  if (parent && idMap.has(parent.id)) {
64
65
  const mappedId = idMap.get(parent.id);
65
- if (mappedId !== void 0) world.setComponent(newId, require_hooks.Parent, { id: mappedId });
66
+ if (mappedId !== void 0) world.setComponent(newId, require_ecs.ParentFrame, { id: mappedId });
67
+ }
68
+ const children = world.getComponent(newId, require_ecs.Children);
69
+ if (children) world.setComponent(newId, require_ecs.Children, { ids: children.ids.map((id) => idMap.get(id) ?? id) });
70
+ const containerChildren = world.getComponent(newId, require_ecs.ContainerChildren);
71
+ if (containerChildren) {
72
+ const mapped = [];
73
+ for (const id of containerChildren.ids) {
74
+ const remapped = idMap.get(id);
75
+ if (remapped !== void 0) mapped.push(remapped);
76
+ }
77
+ world.setComponent(newId, require_ecs.ContainerChildren, { ids: mapped });
66
78
  }
67
- const children = world.getComponent(newId, require_hooks.Children);
68
- if (children) world.setComponent(newId, require_hooks.Children, { ids: children.ids.map((id) => idMap.get(id) ?? id) });
69
- const handleSet = world.getComponent(newId, require_hooks.HandleSet);
70
- if (handleSet) world.setComponent(newId, require_hooks.HandleSet, { ids: handleSet.ids.map((id) => idMap.get(id) ?? id) });
71
79
  }
80
+ const live = doc.resources.camera;
81
+ world.setResource(require_ecs.CameraResource, {
82
+ x: live.x,
83
+ y: live.y,
84
+ zoom: live.zoom,
85
+ gesturing: false
86
+ });
87
+ world.setResource(require_ecs.RootCameraResource, { ...doc.resources.rootCamera });
72
88
  }
73
89
  /**
74
90
  * Serializes a subset of entities (e.g., for copy/paste).
@@ -83,11 +99,12 @@ function serializeEntities(world, entityIds, componentTypes, tagTypes) {
83
99
  const components = {};
84
100
  const tags = [];
85
101
  for (const type of componentTypes) {
102
+ if (type.name === "PreDragLayer" || type.name === "TransformTween" || type.name === "CardOverlapHotPoint") continue;
86
103
  const data = world.getComponent(entityId, type);
87
104
  if (data !== void 0) components[type.name] = structuredClone(data);
88
105
  }
89
106
  for (const type of tagTypes) if (world.hasTag(entityId, type)) {
90
- if (type.name !== "Active" && type.name !== "Visible") tags.push(type.name);
107
+ if (type.name !== "Active" && type.name !== "Visible" && type.name !== "Culled") tags.push(type.name);
91
108
  }
92
109
  result.push({
93
110
  id: entityId,
@@ -101,20 +118,40 @@ function serializeEntities(world, entityIds, componentTypes, tagTypes) {
101
118
  return result;
102
119
  }
103
120
  //#endregion
121
+ exports.Compositor = require_hooks.Compositor;
122
+ exports.CompositorContext = require_hooks.CompositorContext;
104
123
  exports.ContainerRefProvider = require_hooks.ContainerRefProvider;
105
- exports.EngineProvider = require_hooks.EngineProvider;
106
- exports.GridRenderer = require_WebGLWidgetLayer.GridRenderer;
107
- exports.Profiler = require_WebGLWidgetLayer.Profiler;
108
- exports.SelectionOverlaySlot = require_WebGLWidgetLayer.SelectionOverlaySlot;
109
- exports.SelectionRenderer = require_WebGLWidgetLayer.SelectionRenderer;
110
- exports.SpatialIndex = require_WebGLWidgetLayer.SpatialIndex;
111
- exports.SpatialIndexResource = require_WebGLWidgetLayer.SpatialIndexResource;
112
- exports.WebGLWidgetLayer = require_WebGLWidgetLayer.WebGLWidgetLayer;
113
- exports.WebGLWidgetSlot = require_WebGLWidgetLayer.WebGLWidgetSlot;
114
- exports.WidgetSlot = require_WebGLWidgetLayer.WidgetSlot;
115
- exports.computeSnapGuides = require_WebGLWidgetLayer.computeSnapGuides;
124
+ exports.EngineProvider = require_ecs.EngineProvider;
125
+ exports.GridRenderer = require_hooks.GridRenderer;
126
+ exports.Profiler = require_hooks.Profiler;
127
+ exports.ProfilerProbe = require_hooks.ProfilerProbe;
128
+ exports.R3FAnimationSignal = require_hooks.R3FAnimationSignal;
129
+ exports.R3FManager = require_hooks.R3FManager;
130
+ exports.R3FRenderBudget = require_hooks.R3FRenderBudget;
131
+ exports.R3FRenderState = require_hooks.R3FRenderState;
132
+ exports.ResourceRegistry = require_hooks.ResourceRegistry;
133
+ exports.SelectionOverlaySlot = require_hooks.SelectionOverlaySlot;
134
+ exports.SelectionRenderer = require_hooks.SelectionRenderer;
135
+ exports.SpatialIndex = require_hooks.SpatialIndex;
136
+ exports.SpatialIndexResource = require_ecs.SpatialIndexResource;
137
+ exports.VirtualWidget = require_hooks.VirtualWidget;
138
+ exports.WebGLManager = require_hooks.WebGLManager;
139
+ exports.WidgetRenderTargetPool = require_hooks.WidgetRenderTargetPool;
140
+ exports.WidgetSlot = require_hooks.WidgetSlot;
141
+ exports.WidgetStateMachine = require_hooks.WidgetStateMachine;
142
+ exports.ZOOM_BANDS = require_hooks.ZOOM_BANDS;
143
+ exports.computeSnapGuides = require_hooks.computeSnapGuides;
116
144
  exports.deserializeWorld = deserializeWorld;
145
+ exports.isOutOfBand = require_hooks.isOutOfBand;
146
+ exports.selectBand = require_hooks.selectBand;
117
147
  exports.serializeEntities = serializeEntities;
118
148
  exports.serializeWorld = serializeWorld;
149
+ exports.useCompositor = require_hooks.useCompositor;
150
+ exports.useSharedGeometry = require_hooks.useSharedGeometry;
151
+ exports.useSharedMaterial = require_hooks.useSharedMaterial;
152
+ exports.useSharedTexture = require_hooks.useSharedTexture;
153
+ exports.useWidgetAnimation = require_hooks.useWidgetAnimation;
154
+ exports.useWidgetInvalidate = require_hooks.useWidgetInvalidate;
155
+ exports.useWidgetPhase = require_hooks.useWidgetPhase;
119
156
 
120
157
  //# sourceMappingURL=advanced.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"advanced.cjs","names":["Parent","Children","HandleSet"],"sources":["../src/serialization.ts"],"sourcesContent":["import type { ComponentType, EntityId, TagType, World } from '@jamesyong42/reactive-ecs';\nimport { Children, HandleSet, Parent } from './components.js';\nimport type { NavigationFrame } from './resources.js';\n\n// === Serialization Types ===\n\n/** JSON-serializable snapshot of the canvas state, including all entities and camera. */\nexport interface CanvasDocument {\n\tversion: number;\n\tentities: SerializedEntity[];\n\tresources: {\n\t\tcamera: { x: number; y: number; zoom: number };\n\t\tnavigationStack: NavigationFrame[];\n\t};\n}\n\n/** A single serialized entity with its components and tags. */\nexport interface SerializedEntity {\n\tid: EntityId;\n\tcomponents: Record<string, unknown>;\n\ttags: string[];\n}\n\n// === Serialize/Deserialize ===\n\n/**\n * Serializes all entities, components, and tags to a JSON-compatible document.\n * Requires registries of known component and tag types for enumeration.\n */\nexport function serializeWorld(\n\tworld: World,\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n\tcamera: { x: number; y: number; zoom: number },\n\tnavigationFrames: NavigationFrame[],\n): CanvasDocument {\n\tconst entities: SerializedEntity[] = [];\n\n\t// Get all entity IDs (use a broad query)\n\tconst allEntities = world.query();\n\n\tfor (const entityId of allEntities) {\n\t\tconst components: Record<string, unknown> = {};\n\t\tconst tags: string[] = [];\n\n\t\tfor (const type of componentTypes) {\n\t\t\tconst data = world.getComponent(entityId, type);\n\t\t\tif (data !== undefined) {\n\t\t\t\tcomponents[type.name] = structuredClone(data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const type of tagTypes) {\n\t\t\tif (world.hasTag(entityId, type)) {\n\t\t\t\t// Skip runtime-only tags (Active, Visible — they're recomputed)\n\t\t\t\tif (type.name !== 'Active' && type.name !== 'Visible') {\n\t\t\t\t\ttags.push(type.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(components).length > 0 || tags.length > 0) {\n\t\t\tentities.push({ id: entityId, components, tags });\n\t\t}\n\t}\n\n\treturn {\n\t\tversion: 1,\n\t\tentities,\n\t\tresources: {\n\t\t\tcamera: { ...camera },\n\t\t\tnavigationStack: structuredClone(navigationFrames),\n\t\t},\n\t};\n}\n\n/**\n * Restores entities from a serialized document into the world.\n * Clears existing state first and remaps entity IDs automatically.\n */\nexport function deserializeWorld(\n\tworld: World,\n\tdoc: CanvasDocument,\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n): void {\n\tif (doc.version !== 1) {\n\t\tthrow new Error(`Unsupported canvas document version: ${doc.version}. Expected version 1.`);\n\t}\n\n\t// Build lookup maps\n\tconst compByName = new Map<string, ComponentType>();\n\tfor (const t of componentTypes) compByName.set(t.name, t);\n\n\tconst tagByName = new Map<string, TagType>();\n\tfor (const t of tagTypes) tagByName.set(t.name, t);\n\n\t// Destroy all existing entities\n\tfor (const entityId of world.query()) {\n\t\tworld.destroyEntity(entityId);\n\t}\n\n\t// First pass: create entities and build old-to-new ID mapping\n\tconst idMap = new Map<EntityId, EntityId>();\n\n\tfor (const entry of doc.entities) {\n\t\tconst newId = world.createEntity();\n\t\tidMap.set(entry.id as EntityId, newId);\n\n\t\tfor (const [compName, data] of Object.entries(entry.components)) {\n\t\t\tconst type = compByName.get(compName);\n\t\t\tif (type) {\n\t\t\t\tworld.addComponent(newId, type, data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const tagName of entry.tags) {\n\t\t\tconst type = tagByName.get(tagName);\n\t\t\tif (type) {\n\t\t\t\tworld.addTag(newId, type);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Second pass: remap cross-reference components (Parent, Children, HandleSet)\n\tfor (const [_oldId, newId] of idMap) {\n\t\tconst parent = world.getComponent(newId, Parent);\n\t\tif (parent && idMap.has(parent.id)) {\n\t\t\tconst mappedId = idMap.get(parent.id);\n\t\t\tif (mappedId !== undefined) {\n\t\t\t\tworld.setComponent(newId, Parent, { id: mappedId });\n\t\t\t}\n\t\t}\n\n\t\tconst children = world.getComponent(newId, Children);\n\t\tif (children) {\n\t\t\tworld.setComponent(newId, Children, {\n\t\t\t\tids: children.ids.map((id: EntityId) => idMap.get(id) ?? id),\n\t\t\t});\n\t\t}\n\n\t\tconst handleSet = world.getComponent(newId, HandleSet);\n\t\tif (handleSet) {\n\t\t\tworld.setComponent(newId, HandleSet, {\n\t\t\t\tids: handleSet.ids.map((id: EntityId) => idMap.get(id) ?? id),\n\t\t\t});\n\t\t}\n\t}\n}\n\n/**\n * Serializes a subset of entities (e.g., for copy/paste).\n * Recursively includes children of the specified entities.\n */\nexport function serializeEntities(\n\tworld: World,\n\tentityIds: EntityId[],\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n): SerializedEntity[] {\n\tconst result: SerializedEntity[] = [];\n\tconst visited = new Set<EntityId>();\n\n\tfunction visit(entityId: EntityId) {\n\t\tif (visited.has(entityId)) return;\n\t\tvisited.add(entityId);\n\n\t\tconst components: Record<string, unknown> = {};\n\t\tconst tags: string[] = [];\n\n\t\tfor (const type of componentTypes) {\n\t\t\tconst data = world.getComponent(entityId, type);\n\t\t\tif (data !== undefined) {\n\t\t\t\tcomponents[type.name] = structuredClone(data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const type of tagTypes) {\n\t\t\tif (world.hasTag(entityId, type)) {\n\t\t\t\tif (type.name !== 'Active' && type.name !== 'Visible') {\n\t\t\t\t\ttags.push(type.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresult.push({ id: entityId, components, tags });\n\n\t\t// Recurse into children. components.Children is typed as unknown via\n\t\t// the Record<string, unknown> shape, so narrow through a cast.\n\t\tconst children = components.Children as { ids?: EntityId[] } | undefined;\n\t\tif (children?.ids) {\n\t\t\tfor (const childId of children.ids) {\n\t\t\t\tvisit(childId);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const id of entityIds) {\n\t\tvisit(id);\n\t}\n\n\treturn result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"advanced.cjs","names":["ParentFrame","Children","ContainerChildren","CameraResource","RootCameraResource"],"sources":["../src/ecs/serialization.ts"],"sourcesContent":["import type { ComponentType, EntityId, TagType, World } from '@jamesyong42/reactive-ecs';\nimport { Children, ContainerChildren, ParentFrame } from './components.js';\nimport type { FrameCameraState } from './resources.js';\nimport { CameraResource, RootCameraResource } from './resources.js';\n\n// === Serialization Types ===\n\n/**\n * JSON-serializable snapshot of the canvas state, including all entities,\n * the current camera, and the persisted root-frame camera.\n *\n * The navigation stack is **not** serialized — reloading a canvas always\n * drops the user at the root frame (RFC-004 § Phase 0c). Per-container\n * camera state is persisted via the `ContainerCamera` component on each\n * container entity (serialized as part of its component set).\n */\nexport interface CanvasDocument {\n\tversion: number;\n\tentities: SerializedEntity[];\n\tresources: {\n\t\t/** Current live camera (whatever frame the user was in at serialize time). */\n\t\tcamera: FrameCameraState;\n\t\t/** Persisted root-frame camera — restored on load. */\n\t\trootCamera: FrameCameraState;\n\t};\n}\n\n/** A single serialized entity with its components and tags. */\nexport interface SerializedEntity {\n\tid: EntityId;\n\tcomponents: Record<string, unknown>;\n\ttags: string[];\n}\n\n// === Serialize/Deserialize ===\n\n/**\n * Serializes all entities, components, and tags to a JSON-compatible document.\n * Requires registries of known component and tag types for enumeration.\n */\nexport function serializeWorld(\n\tworld: World,\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n\tcamera: FrameCameraState,\n\trootCamera: FrameCameraState,\n): CanvasDocument {\n\tconst entities: SerializedEntity[] = [];\n\n\t// Get all entity IDs (use a broad query)\n\tconst allEntities = world.query();\n\n\tfor (const entityId of allEntities) {\n\t\tconst components: Record<string, unknown> = {};\n\t\tconst tags: string[] = [];\n\n\t\tfor (const type of componentTypes) {\n\t\t\t// Skip runtime-only components: `PreDragLayer` is recomputed by\n\t\t\t// dragPromoteSystem on the next Dragging flip; `TransformTween`\n\t\t\t// is an in-flight animation that should not be paused / resumed\n\t\t\t// across reloads (the destination `Transform2D` persists instead);\n\t\t\t// `CardOverlapHotPoint` is drag-scoped visual state with no\n\t\t\t// meaning outside an active drag.\n\t\t\tif (\n\t\t\t\ttype.name === 'PreDragLayer' ||\n\t\t\t\ttype.name === 'TransformTween' ||\n\t\t\t\ttype.name === 'CardOverlapHotPoint'\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst data = world.getComponent(entityId, type);\n\t\t\tif (data !== undefined) {\n\t\t\t\tcomponents[type.name] = structuredClone(data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const type of tagTypes) {\n\t\t\tif (world.hasTag(entityId, type)) {\n\t\t\t\t// Skip runtime-only tags: Active/Visible/Culled are recomputed\n\t\t\t\t// by the cull pipeline; OverlapCandidate/OverlapTarget are\n\t\t\t\t// drag-scoped and meaningless outside an active drag.\n\t\t\t\tif (\n\t\t\t\t\ttype.name !== 'Active' &&\n\t\t\t\t\ttype.name !== 'Visible' &&\n\t\t\t\t\ttype.name !== 'Culled' &&\n\t\t\t\t\ttype.name !== 'OverlapCandidate' &&\n\t\t\t\t\ttype.name !== 'OverlapTarget'\n\t\t\t\t) {\n\t\t\t\t\ttags.push(type.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(components).length > 0 || tags.length > 0) {\n\t\t\tentities.push({ id: entityId, components, tags });\n\t\t}\n\t}\n\n\treturn {\n\t\tversion: 1,\n\t\tentities,\n\t\tresources: {\n\t\t\tcamera: { ...camera },\n\t\t\trootCamera: { ...rootCamera },\n\t\t},\n\t};\n}\n\n/**\n * Restores entities from a serialized document into the world.\n * Clears existing state first and remaps entity IDs automatically.\n */\nexport function deserializeWorld(\n\tworld: World,\n\tdoc: CanvasDocument,\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n): void {\n\tif (doc.version !== 1) {\n\t\tthrow new Error(`Unsupported canvas document version: ${doc.version}. Expected version 1.`);\n\t}\n\n\t// Build lookup maps\n\tconst compByName = new Map<string, ComponentType>();\n\tfor (const t of componentTypes) compByName.set(t.name, t);\n\n\tconst tagByName = new Map<string, TagType>();\n\tfor (const t of tagTypes) tagByName.set(t.name, t);\n\n\t// Destroy all existing entities\n\tfor (const entityId of world.query()) {\n\t\tworld.destroyEntity(entityId);\n\t}\n\n\t// First pass: create entities and build old-to-new ID mapping\n\tconst idMap = new Map<EntityId, EntityId>();\n\n\tfor (const entry of doc.entities) {\n\t\tconst newId = world.createEntity();\n\t\tidMap.set(entry.id as EntityId, newId);\n\n\t\tfor (const [compName, data] of Object.entries(entry.components)) {\n\t\t\tconst type = compByName.get(compName);\n\t\t\tif (type) {\n\t\t\t\tworld.addComponent(newId, type, data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const tagName of entry.tags) {\n\t\t\tconst type = tagByName.get(tagName);\n\t\t\tif (type) {\n\t\t\t\tworld.addTag(newId, type);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Second pass: remap cross-reference components (ParentFrame, Children,\n\t// ContainerChildren).\n\tfor (const [_oldId, newId] of idMap) {\n\t\tconst parent = world.getComponent(newId, ParentFrame);\n\t\tif (parent && idMap.has(parent.id)) {\n\t\t\tconst mappedId = idMap.get(parent.id);\n\t\t\tif (mappedId !== undefined) {\n\t\t\t\tworld.setComponent(newId, ParentFrame, { id: mappedId });\n\t\t\t}\n\t\t}\n\n\t\tconst children = world.getComponent(newId, Children);\n\t\tif (children) {\n\t\t\tworld.setComponent(newId, Children, {\n\t\t\t\tids: children.ids.map((id: EntityId) => idMap.get(id) ?? id),\n\t\t\t});\n\t\t}\n\n\t\tconst containerChildren = world.getComponent(newId, ContainerChildren);\n\t\tif (containerChildren) {\n\t\t\t// Drop ids that didn't round-trip (child was destroyed before save\n\t\t\t// and still lingered in the list) rather than falling back to the\n\t\t\t// raw pre-save id — that id may have been recycled to an unrelated\n\t\t\t// entity by the post-load world and would leak into the container's\n\t\t\t// child count / navigation target list.\n\t\t\tconst mapped: EntityId[] = [];\n\t\t\tfor (const id of containerChildren.ids) {\n\t\t\t\tconst remapped = idMap.get(id);\n\t\t\t\tif (remapped !== undefined) mapped.push(remapped);\n\t\t\t}\n\t\t\tworld.setComponent(newId, ContainerChildren, { ids: mapped });\n\t\t}\n\t}\n\n\t// Restore camera resources. `gesturing` resets to false on load — it's\n\t// transient interaction state, not persisted view state.\n\tconst live = doc.resources.camera;\n\tworld.setResource(CameraResource, { x: live.x, y: live.y, zoom: live.zoom, gesturing: false });\n\tworld.setResource(RootCameraResource, { ...doc.resources.rootCamera });\n\n\t// NavigationStack is deliberately not restored — users always return\n\t// to the root frame on load (RFC-004 § Phase 0c).\n}\n\n/**\n * Serializes a subset of entities (e.g., for copy/paste).\n * Recursively includes children of the specified entities.\n */\nexport function serializeEntities(\n\tworld: World,\n\tentityIds: EntityId[],\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n): SerializedEntity[] {\n\tconst result: SerializedEntity[] = [];\n\tconst visited = new Set<EntityId>();\n\n\tfunction visit(entityId: EntityId) {\n\t\tif (visited.has(entityId)) return;\n\t\tvisited.add(entityId);\n\n\t\tconst components: Record<string, unknown> = {};\n\t\tconst tags: string[] = [];\n\n\t\tfor (const type of componentTypes) {\n\t\t\t// Skip runtime-only components: `PreDragLayer` is recomputed by\n\t\t\t// dragPromoteSystem on the next Dragging flip; `TransformTween`\n\t\t\t// is an in-flight animation that should not be paused / resumed\n\t\t\t// across reloads (the destination `Transform2D` persists instead);\n\t\t\t// `CardOverlapHotPoint` is drag-scoped visual state with no\n\t\t\t// meaning outside an active drag.\n\t\t\tif (\n\t\t\t\ttype.name === 'PreDragLayer' ||\n\t\t\t\ttype.name === 'TransformTween' ||\n\t\t\t\ttype.name === 'CardOverlapHotPoint'\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst data = world.getComponent(entityId, type);\n\t\t\tif (data !== undefined) {\n\t\t\t\tcomponents[type.name] = structuredClone(data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const type of tagTypes) {\n\t\t\tif (world.hasTag(entityId, type)) {\n\t\t\t\tif (type.name !== 'Active' && type.name !== 'Visible' && type.name !== 'Culled') {\n\t\t\t\t\ttags.push(type.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresult.push({ id: entityId, components, tags });\n\n\t\t// Recurse into children. components.Children is typed as unknown via\n\t\t// the Record<string, unknown> shape, so narrow through a cast.\n\t\tconst children = components.Children as { ids?: EntityId[] } | undefined;\n\t\tif (children?.ids) {\n\t\t\tfor (const childId of children.ids) {\n\t\t\t\tvisit(childId);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const id of entityIds) {\n\t\tvisit(id);\n\t}\n\n\treturn result;\n}\n"],"mappings":"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"}
@@ -1,99 +1,215 @@
1
- import { A as NavigationFrame, C as computeSnapGuides, G as FrameTimeStats, J as R3FSample, K as Profiler, L as R3FWidgetProps, Q as WebGLStats, W as EcsStats, X as TickSample, Y as R3FStats, Z as WebGLPass, l as SpatialIndex, n as LayoutEngine, o as SpatialIndexResource, q as ProfilerStats } from "./engine-CCjuFMC-.cjs";
2
- import { c as ContainerRefProvider, i as SelectionRenderer, l as EngineProvider, s as GridRenderer, u as ResolvedWidget } from "./SelectionRenderer-CR2PBQwx.cjs";
1
+ import { $ as FrameCameraState, B as WebGLPass, F as ProfilerStats, I as R3FPhaseHistogram, K as computeSnapGuides, L as R3FSample, M as EcsStats, N as FrameTimeStats, P as Profiler, R as R3FStats, U as EqualSpacingIndicator, V as WebGLStats, W as SnapGuide, at as SpatialIndexResource, ct as SpatialIndex, r as LayoutEngine, z as TickSample } from "./index-DSdbSQ_t.cjs";
2
+ import { A as SelectionConfig, B as ContainerRefProvider, C as useCompositor, D as SnapGuideConfig, F as ResolvedWidget, N as GridConfig, P as GridRenderer, R as EngineProvider, S as CompositorWidgetEntry, T as ResourceRegistry, _ as R3FRenderState, a as VirtualWidget, b as CompositorContext, c as useSharedTexture, d as useWidgetPhase, f as R3FAnimationSignal, g as R3FRenderBudgetData, h as R3FRenderBudget, i as WidgetStateMachine, j as SelectionRenderer, k as SelectionBounds, l as useWidgetAnimation, m as R3FPhase, n as isOutOfBand, o as useSharedGeometry, p as R3FPaintedAt, r as selectBand, s as useSharedMaterial, t as ZOOM_BANDS, u as useWidgetInvalidate, v as R3FRenderStateData, w as WidgetRenderTargetPool, x as CompositorContextValue, y as Compositor } from "./index-B7B1tRPl.cjs";
3
3
  import { ComponentType, EntityId, TagType, World } from "@jamesyong42/reactive-ecs";
4
4
  import * as _$react from "react";
5
5
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
6
+ import * as THREE from "three";
6
7
 
7
- //#region src/react/SelectionOverlaySlot.d.ts
8
+ //#region src/ecs/serialization.d.ts
9
+ /**
10
+ * JSON-serializable snapshot of the canvas state, including all entities,
11
+ * the current camera, and the persisted root-frame camera.
12
+ *
13
+ * The navigation stack is **not** serialized — reloading a canvas always
14
+ * drops the user at the root frame (RFC-004 § Phase 0c). Per-container
15
+ * camera state is persisted via the `ContainerCamera` component on each
16
+ * container entity (serialized as part of its component set).
17
+ */
18
+ interface CanvasDocument {
19
+ version: number;
20
+ entities: SerializedEntity[];
21
+ resources: {
22
+ /** Current live camera (whatever frame the user was in at serialize time). */camera: FrameCameraState; /** Persisted root-frame camera — restored on load. */
23
+ rootCamera: FrameCameraState;
24
+ };
25
+ }
26
+ /** A single serialized entity with its components and tags. */
27
+ interface SerializedEntity {
28
+ id: EntityId;
29
+ components: Record<string, unknown>;
30
+ tags: string[];
31
+ }
32
+ /**
33
+ * Serializes all entities, components, and tags to a JSON-compatible document.
34
+ * Requires registries of known component and tag types for enumeration.
35
+ */
36
+ declare function serializeWorld(world: World, componentTypes: ComponentType[], tagTypes: TagType[], camera: FrameCameraState, rootCamera: FrameCameraState): CanvasDocument;
37
+ /**
38
+ * Restores entities from a serialized document into the world.
39
+ * Clears existing state first and remaps entity IDs automatically.
40
+ */
41
+ declare function deserializeWorld(world: World, doc: CanvasDocument, componentTypes: ComponentType[], tagTypes: TagType[]): void;
42
+ /**
43
+ * Serializes a subset of entities (e.g., for copy/paste).
44
+ * Recursively includes children of the specified entities.
45
+ */
46
+ declare function serializeEntities(world: World, entityIds: EntityId[], componentTypes: ComponentType[], tagTypes: TagType[]): SerializedEntity[];
47
+ //#endregion
48
+ //#region src/react/overlays/SelectionOverlaySlot.d.ts
8
49
  interface SelectionOverlaySlotProps {
9
50
  entityId: EntityId;
10
51
  slotRef: (entityId: EntityId, el: HTMLDivElement | null) => void;
11
52
  }
12
53
  /**
13
- * DOM overlay for WebGL widgets — provides selection frame and pointer
14
- * interaction (select, drag, resize) without rendering widget content.
54
+ * DOM chrome overlay for R3F widgets — renders the selection frame /
55
+ * card decoration and positions itself at the widget's world AABB.
56
+ *
57
+ * Decoration only. Pointer events bypass this wrapper
58
+ * (`pointer-events: none`) so they reach the R3F canvas underneath,
59
+ * where the `EventRouter` raycasts the widget's local scene (RFC-006).
60
+ * Engine semantics — drag, select, resize, double-click — are dispatched
61
+ * by the canvas-level `PointerEventBus` after the widget's R3F handlers
62
+ * have had a chance to call `event.stopPropagation()`.
15
63
  */
16
64
  declare const SelectionOverlaySlot: _$react.MemoExoticComponent<({
17
65
  entityId,
18
66
  slotRef
19
67
  }: SelectionOverlaySlotProps) => _$react_jsx_runtime0.JSX.Element>;
20
68
  //#endregion
21
- //#region src/react/WidgetSlot.d.ts
69
+ //#region src/react/widgets/WidgetSlot.d.ts
22
70
  interface WidgetSlotProps {
23
71
  entityId: EntityId;
24
72
  slotRef: (entityId: EntityId, el: HTMLDivElement | null) => void;
25
73
  }
74
+ /**
75
+ * Wrapper for a DOM widget — owns the slot's positioning, registers its
76
+ * ref with the rAF batcher, and renders the user's widget component.
77
+ *
78
+ * Pointer routing lives in the canvas-level `PointerEventBus` (RFC-006).
79
+ * The slot does not call the engine; user widget React handlers run on
80
+ * the natural DOM event path and bubble to the bus, which decides
81
+ * whether to invoke engine semantics. Authors call `e.stopPropagation()`
82
+ * from inside their widget to opt out of engine drag/select.
83
+ */
26
84
  declare const WidgetSlot: _$react.MemoExoticComponent<({
27
85
  entityId,
28
86
  slotRef
29
87
  }: WidgetSlotProps) => _$react_jsx_runtime0.JSX.Element>;
30
88
  //#endregion
31
- //#region src/react/webgl/WebGLWidgetLayer.d.ts
32
- interface WebGLWidgetLayerProps {
33
- engine: LayoutEngine;
34
- entities: EntityId[];
35
- resolve: (entityId: EntityId) => ResolvedWidget | null;
36
- }
37
- declare function WebGLWidgetLayer({
38
- engine,
39
- entities,
40
- resolve
41
- }: WebGLWidgetLayerProps): _$react_jsx_runtime0.JSX.Element;
42
- //#endregion
43
- //#region src/react/webgl/WebGLWidgetSlot.d.ts
44
- interface WebGLWidgetSlotProps {
45
- entityId: EntityId;
46
- component: React.ComponentType<R3FWidgetProps>;
89
+ //#region src/webgl/WebGLManager.d.ts
90
+ /** Construction options for {@link WebGLManager}. */
91
+ interface WebGLManagerOptions {
92
+ /** Grid rendering config. Pass `false` to disable the grid entirely. */
93
+ grid?: Partial<GridConfig> | false;
94
+ /** Selection overlay config (outline color, handle size, etc.). */
95
+ selection?: Partial<SelectionConfig>;
96
+ /** Snap guide overlay config (color, line width, alpha). */
97
+ snapGuides?: Partial<SnapGuideConfig>;
47
98
  }
48
- /**
49
- * Positions a Three.js Group at the entity's world-space center.
50
- * The widget component renders in local space: origin at center,
51
- * X right, Y up, dimensions = (width, height) in world units.
52
- */
53
- declare function WebGLWidgetSlot({
54
- entityId,
55
- component: WidgetComponent
56
- }: WebGLWidgetSlotProps): _$react_jsx_runtime0.JSX.Element | null;
57
- //#endregion
58
- //#region src/serialization.d.ts
59
- /** JSON-serializable snapshot of the canvas state, including all entities and camera. */
60
- interface CanvasDocument {
61
- version: number;
62
- entities: SerializedEntity[];
63
- resources: {
64
- camera: {
65
- x: number;
66
- y: number;
67
- zoom: number;
68
- };
69
- navigationStack: NavigationFrame[];
99
+ /** Per-frame input passed to {@link WebGLManager.render}. */
100
+ interface WebGLFrameInput {
101
+ camera: {
102
+ x: number;
103
+ y: number;
104
+ zoom: number;
70
105
  };
71
- }
72
- /** A single serialized entity with its components and tags. */
73
- interface SerializedEntity {
74
- id: EntityId;
75
- components: Record<string, unknown>;
76
- tags: string[];
106
+ /** Selection outlines + 8 resize handles + hover. */
107
+ selection: {
108
+ bounds: SelectionBounds[];
109
+ hovered: SelectionBounds | null;
110
+ };
111
+ /**
112
+ * Alignment guides + equal-spacing indicators. Independent of
113
+ * `selection` — guides display whenever the engine is computing
114
+ * snap math, regardless of whether anything is selected. Set
115
+ * `visible: false` to skip the snap-guide pass entirely (the
116
+ * guides are still computed by the engine; only the render is
117
+ * suppressed).
118
+ */
119
+ snap: {
120
+ guides: SnapGuide[];
121
+ spacings: EqualSpacingIndicator[];
122
+ visible: boolean;
123
+ };
124
+ /** Optional — if supplied, the manager records per-pass timings + GL info. */
125
+ profiler?: Profiler;
126
+ /** Forwarded into the profiler sample (count of DOM slot transform writes this tick). */
127
+ domPositionsUpdated?: number;
77
128
  }
78
129
  /**
79
- * Serializes all entities, components, and tags to a JSON-compatible document.
80
- * Requires registries of known component and tag types for enumeration.
130
+ * Top-level coordinator for the library's vanilla-WebGL layer.
131
+ *
132
+ * Owns a single `THREE.WebGLRenderer` and drives the built-in renderers
133
+ * (dot grid, selection overlay, snap guides) through it. This replaces
134
+ * the previous pattern where `GridRenderer` owned the renderer and
135
+ * `SelectionRenderer` piggy-backed on it via an implicit side-effect —
136
+ * a manager makes the sharing explicit and gives {@link InfiniteCanvas}
137
+ * a single surface to talk to instead of three.
81
138
  */
82
- declare function serializeWorld(world: World, componentTypes: ComponentType[], tagTypes: TagType[], camera: {
83
- x: number;
84
- y: number;
85
- zoom: number;
86
- }, navigationFrames: NavigationFrame[]): CanvasDocument;
139
+ declare class WebGLManager {
140
+ private renderer;
141
+ private grid;
142
+ private selection;
143
+ private snapGuides;
144
+ constructor(canvas: HTMLCanvasElement, opts?: WebGLManagerOptions);
145
+ /** Resize the drawing buffer. Call on mount and ResizeObserver events. */
146
+ setSize(width: number, height: number, dpr?: number): void;
147
+ /** Update grid visuals (colors, spacings, fade ranges, etc.). */
148
+ setGridConfig(config: Partial<GridConfig>): void;
149
+ /** Update selection overlay visuals (outline color, handle size, etc.). */
150
+ setSelectionConfig(config: Partial<SelectionConfig>): void;
151
+ /** Update snap-guide visuals (color, line width, alpha). */
152
+ setSnapGuideConfig(config: Partial<SnapGuideConfig>): void;
153
+ /** Render one frame: grid → selection → snap guides, in stacking order. */
154
+ render(input: WebGLFrameInput): void;
155
+ /** Release GL resources — call on unmount. */
156
+ dispose(): void;
157
+ /** Escape hatch if an advanced consumer needs the underlying three renderer. */
158
+ getWebGLRenderer(): THREE.WebGLRenderer;
159
+ }
160
+ //#endregion
161
+ //#region src/r3f/ProfilerProbe.d.ts
87
162
  /**
88
- * Restores entities from a serialized document into the world.
89
- * Clears existing state first and remaps entity IDs automatically.
163
+ * Reports one R3F frame sample per animation frame to the engine profiler.
164
+ * Reads `renderer.info` from three.js draw calls / triangles / memory /
165
+ * programs — which is maintained by R3F's default render loop regardless
166
+ * of whether we opt in. Only samples when the profiler is enabled.
90
167
  */
91
- declare function deserializeWorld(world: World, doc: CanvasDocument, componentTypes: ComponentType[], tagTypes: TagType[]): void;
168
+ declare function ProfilerProbe({
169
+ engine,
170
+ widgetCount
171
+ }: {
172
+ engine: LayoutEngine;
173
+ widgetCount: number;
174
+ }): null;
175
+ //#endregion
176
+ //#region src/r3f/R3FManager.d.ts
177
+ interface R3FManagerProps {
178
+ engine: LayoutEngine;
179
+ entities: EntityId[];
180
+ resolve: (entityId: EntityId) => ResolvedWidget | null;
181
+ /**
182
+ * Optional R3F nodes mounted at the Canvas root as siblings of the
183
+ * Compositor (so they live in the Canvas's default scene, not inside
184
+ * any widget portal). Canonical use: drei's `<Environment>` — the
185
+ * Compositor's `sharedEnv` propagation logic checks the root scene
186
+ * before iterating widget scenes, so a root-level env stays alive
187
+ * across widget navigation (RFC-004 Phase 5 follow-up).
188
+ */
189
+ r3fRoot?: _$react.ReactNode;
190
+ /**
191
+ * Late-bound handle for the R3F event manager. RFC-008's `R3FRouter`
192
+ * (constructed in `InfiniteCanvas`) reads this ref to dispatch into
193
+ * R3F's mesh handlers from the InputManager pipeline. The factory
194
+ * writes the produced manager into `.current` once R3F invokes it.
195
+ */
196
+ eventManagerRef?: _$react.MutableRefObject<any>;
197
+ }
92
198
  /**
93
- * Serializes a subset of entities (e.g., for copy/paste).
94
- * Recursively includes children of the specified entities.
199
+ * Top-level coordinator for the R3F (React Three Fiber) rendering layer.
200
+ *
201
+ * Mounts a single `<Canvas>` and lets the {@link Compositor} drive the
202
+ * render loop — each R3F widget paints into its own `WebGLRenderTarget`
203
+ * via {@link VirtualWidget} and a final composition pass samples those
204
+ * textures into the visible canvas (RFC-002 Phase 4).
95
205
  */
96
- declare function serializeEntities(world: World, entityIds: EntityId[], componentTypes: ComponentType[], tagTypes: TagType[]): SerializedEntity[];
206
+ declare function R3FManager({
207
+ engine,
208
+ entities,
209
+ resolve,
210
+ r3fRoot,
211
+ eventManagerRef
212
+ }: R3FManagerProps): _$react_jsx_runtime0.JSX.Element;
97
213
  //#endregion
98
- export { type CanvasDocument, ContainerRefProvider, type EcsStats, EngineProvider, type FrameTimeStats, GridRenderer, Profiler, type ProfilerStats, type R3FSample, type R3FStats, SelectionOverlaySlot, SelectionRenderer, type SerializedEntity, SpatialIndex, SpatialIndexResource, type TickSample, type WebGLPass, type WebGLStats, WebGLWidgetLayer, WebGLWidgetSlot, WidgetSlot, computeSnapGuides, deserializeWorld, serializeEntities, serializeWorld };
214
+ export { type CanvasDocument, Compositor, CompositorContext, type CompositorContextValue, type CompositorWidgetEntry, ContainerRefProvider, type EcsStats, EngineProvider, type FrameTimeStats, GridRenderer, Profiler, ProfilerProbe, type ProfilerStats, R3FAnimationSignal, R3FManager, type R3FPaintedAt, type R3FPhase, type R3FPhaseHistogram, R3FRenderBudget, type R3FRenderBudgetData, R3FRenderState, type R3FRenderStateData, type R3FSample, type R3FStats, ResourceRegistry, SelectionOverlaySlot, SelectionRenderer, type SerializedEntity, SpatialIndex, SpatialIndexResource, type TickSample, VirtualWidget, type WebGLFrameInput, WebGLManager, type WebGLManagerOptions, type WebGLPass, type WebGLStats, WidgetRenderTargetPool, WidgetSlot, WidgetStateMachine, ZOOM_BANDS, computeSnapGuides, deserializeWorld, isOutOfBand, selectBand, serializeEntities, serializeWorld, useCompositor, useSharedGeometry, useSharedMaterial, useSharedTexture, useWidgetAnimation, useWidgetInvalidate, useWidgetPhase };
99
215
  //# sourceMappingURL=advanced.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"advanced.d.cts","names":[],"sources":["../src/react/SelectionOverlaySlot.tsx","../src/react/WidgetSlot.tsx","../src/react/webgl/WebGLWidgetLayer.tsx","../src/react/webgl/WebGLWidgetSlot.tsx","../src/serialization.ts"],"mappings":";;;;;;;UAMU,yBAAA;EACT,QAAA,EAAU,QAAA;EACV,OAAA,GAAU,QAAA,EAAU,QAAA,EAAU,EAAA,EAAI,cAAA;AAAA;;;AARuB;;cAmB7C,oBAAA,EAGe,OAAA,CAHK,mBAAA;EAAA,QAAA;EAAA;AAAA,GAG9B,yBAAA,KAAyB,oBAAA,CAAA,GAAA,CAAA,OAAA;;;UCflB,eAAA;EACT,QAAA,EAAU,QAAA;EACV,OAAA,GAAU,QAAA,EAAU,QAAA,EAAU,EAAA,EAAI,cAAA;AAAA;AAAA,cAOtB,UAAA,EAA4E,OAAA,CAAlE,mBAAA;EAAA,QAAA;EAAA;AAAA,GAAmD,eAAA,KAAe,oBAAA,CAAA,GAAA,CAAA,OAAA;;;UCgG/E,qBAAA;EACT,MAAA,EAAQ,YAAA;EACR,QAAA,EAAU,QAAA;EACV,OAAA,GAAU,QAAA,EAAU,QAAA,KAAa,cAAA;AAAA;AAAA,iBAGlB,gBAAA,CAAA;EAAmB,MAAA;EAAQ,QAAA;EAAU;AAAA,GAAW,qBAAA,GAAqB,oBAAA,CAAA,GAAA,CAAA,OAAA;;;UC7G3E,oBAAA;EACT,QAAA,EAAU,QAAA;EACV,SAAA,EAAW,KAAA,CAAM,aAAA,CAAc,cAAA;AAAA;;AHX0B;;;;iBGmB1C,eAAA,CAAA;EAAkB,QAAA;EAAU,SAAA,EAAW;AAAA,GAAmB,oBAAA,GAAoB,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;UCZ7E,cAAA;EAChB,OAAA;EACA,QAAA,EAAU,gBAAA;EACV,SAAA;IACC,MAAA;MAAU,CAAA;MAAW,CAAA;MAAW,IAAA;IAAA;IAChC,eAAA,EAAiB,eAAA;EAAA;AAAA;;UAKF,gBAAA;EAChB,EAAA,EAAI,QAAA;EACJ,UAAA,EAAY,MAAA;EACZ,IAAA;AAAA;;;;AJDD;iBIUgB,cAAA,CACf,KAAA,EAAO,KAAA,EACP,cAAA,EAAgB,aAAA,IAChB,QAAA,EAAU,OAAA,IACV,MAAA;EAAU,CAAA;EAAW,CAAA;EAAW,IAAA;AAAA,GAChC,gBAAA,EAAkB,eAAA,KAChB,cAAA;;;;;iBA6Ca,gBAAA,CACf,KAAA,EAAO,KAAA,EACP,GAAA,EAAK,cAAA,EACL,cAAA,EAAgB,aAAA,IAChB,QAAA,EAAU,OAAA;;;;;iBAsEK,iBAAA,CACf,KAAA,EAAO,KAAA,EACP,SAAA,EAAW,QAAA,IACX,cAAA,EAAgB,aAAA,IAChB,QAAA,EAAU,OAAA,KACR,gBAAA"}
1
+ {"version":3,"file":"advanced.d.cts","names":[],"sources":["../src/ecs/serialization.ts","../src/react/overlays/SelectionOverlaySlot.tsx","../src/react/widgets/WidgetSlot.tsx","../src/webgl/WebGLManager.ts","../src/r3f/ProfilerProbe.tsx","../src/r3f/R3FManager.tsx"],"mappings":"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"}