@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.
- package/README.md +65 -0
- package/dist/advanced.cjs +61 -24
- package/dist/advanced.cjs.map +1 -1
- package/dist/advanced.d.cts +180 -64
- package/dist/advanced.d.cts.map +1 -1
- package/dist/advanced.d.mts +180 -64
- package/dist/advanced.d.mts.map +1 -1
- package/dist/advanced.mjs +29 -12
- package/dist/advanced.mjs.map +1 -1
- package/dist/devtools.cjs +22 -22
- package/dist/devtools.cjs.map +1 -1
- package/dist/devtools.d.cts +2 -2
- package/dist/devtools.d.cts.map +1 -1
- package/dist/devtools.d.mts +2 -2
- package/dist/devtools.d.mts.map +1 -1
- package/dist/devtools.mjs +2 -2
- package/dist/devtools.mjs.map +1 -1
- package/dist/{hooks-BwY7rRHg.mjs → ecs-3kimUV5Z.mjs} +238 -74
- package/dist/ecs-3kimUV5Z.mjs.map +1 -0
- package/dist/{hooks-DHShH86C.cjs → ecs-B4QrqfvQ.cjs} +320 -108
- package/dist/ecs-B4QrqfvQ.cjs.map +1 -0
- package/dist/hooks-CtP02JNt.cjs +3762 -0
- package/dist/hooks-CtP02JNt.cjs.map +1 -0
- package/dist/hooks-gsQDDE56.mjs +3494 -0
- package/dist/hooks-gsQDDE56.mjs.map +1 -0
- package/dist/index-3GY7T8JM.d.mts +480 -0
- package/dist/index-3GY7T8JM.d.mts.map +1 -0
- package/dist/index-B7B1tRPl.d.cts +480 -0
- package/dist/index-B7B1tRPl.d.cts.map +1 -0
- package/dist/index-DSdbSQ_t.d.cts +1451 -0
- package/dist/index-DSdbSQ_t.d.cts.map +1 -0
- package/dist/index-Dj9odADH.d.mts +1451 -0
- package/dist/index-Dj9odADH.d.mts.map +1 -0
- package/dist/index.cjs +3865 -643
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +315 -138
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +315 -138
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3767 -571
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/SelectionRenderer-CR2PBQwx.d.cts +0 -105
- package/dist/SelectionRenderer-CR2PBQwx.d.cts.map +0 -1
- package/dist/SelectionRenderer-DlsBstAq.d.mts +0 -105
- package/dist/SelectionRenderer-DlsBstAq.d.mts.map +0 -1
- package/dist/WebGLWidgetLayer-BBMuwzHq.cjs +0 -3560
- package/dist/WebGLWidgetLayer-BBMuwzHq.cjs.map +0 -1
- package/dist/WebGLWidgetLayer-C3p1tnpm.mjs +0 -3375
- package/dist/WebGLWidgetLayer-C3p1tnpm.mjs.map +0 -1
- package/dist/engine-BfbvWXSk.d.mts +0 -982
- package/dist/engine-BfbvWXSk.d.mts.map +0 -1
- package/dist/engine-CCjuFMC-.d.cts +0 -982
- package/dist/engine-CCjuFMC-.d.cts.map +0 -1
- package/dist/hooks-BwY7rRHg.mjs.map +0 -1
- 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
|
|
3
|
-
const
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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 =
|
|
106
|
-
exports.GridRenderer =
|
|
107
|
-
exports.Profiler =
|
|
108
|
-
exports.
|
|
109
|
-
exports.
|
|
110
|
-
exports.
|
|
111
|
-
exports.
|
|
112
|
-
exports.
|
|
113
|
-
exports.
|
|
114
|
-
exports.
|
|
115
|
-
exports.
|
|
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
|
package/dist/advanced.cjs.map
CHANGED
|
@@ -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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"}
|
package/dist/advanced.d.cts
CHANGED
|
@@ -1,99 +1,215 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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/
|
|
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
|
|
14
|
-
*
|
|
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/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
*
|
|
80
|
-
*
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
*
|
|
89
|
-
*
|
|
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
|
|
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
|
-
*
|
|
94
|
-
*
|
|
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
|
|
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,
|
|
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
|
package/dist/advanced.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced.d.cts","names":[],"sources":["../src/react/SelectionOverlaySlot.tsx","../src/react/WidgetSlot.tsx","../src/
|
|
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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"}
|