@jamesyong42/infinite-canvas 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advanced.cjs +2 -2
- package/dist/advanced.d.cts +2 -2
- package/dist/advanced.d.mts +2 -2
- package/dist/advanced.mjs +2 -2
- package/dist/devtools.cjs +3 -6
- package/dist/devtools.cjs.map +1 -1
- package/dist/devtools.d.cts +1 -1
- package/dist/devtools.d.mts +1 -1
- package/dist/devtools.mjs +2 -5
- package/dist/devtools.mjs.map +1 -1
- package/dist/{ecs-B4QrqfvQ.cjs → ecs-BtX_rCS3.cjs} +57 -2
- package/dist/ecs-BtX_rCS3.cjs.map +1 -0
- package/dist/{ecs-3kimUV5Z.mjs → ecs-O6AR7iFp.mjs} +33 -2
- package/dist/ecs-O6AR7iFp.mjs.map +1 -0
- package/dist/{hooks-CtP02JNt.cjs → hooks-B-UPFgGj.cjs} +2 -5
- package/dist/{hooks-CtP02JNt.cjs.map → hooks-B-UPFgGj.cjs.map} +1 -1
- package/dist/{hooks-gsQDDE56.mjs → hooks-Cpu0rEMv.mjs} +2 -5
- package/dist/{hooks-gsQDDE56.mjs.map → hooks-Cpu0rEMv.mjs.map} +1 -1
- package/dist/{index-DSdbSQ_t.d.cts → index-BXRsEYL9.d.cts} +248 -242
- package/dist/index-BXRsEYL9.d.cts.map +1 -0
- package/dist/{index-Dj9odADH.d.mts → index-Cxrz-hoe.d.mts} +248 -242
- package/dist/index-Cxrz-hoe.d.mts.map +1 -0
- package/dist/{index-B7B1tRPl.d.cts → index-DSGDEjP7.d.cts} +2 -2
- package/dist/{index-3GY7T8JM.d.mts.map → index-DSGDEjP7.d.cts.map} +1 -1
- package/dist/{index-3GY7T8JM.d.mts → index-h56yzXZy.d.mts} +2 -2
- package/dist/{index-B7B1tRPl.d.cts.map → index-h56yzXZy.d.mts.map} +1 -1
- package/dist/index.cjs +391 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +391 -166
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/ecs-3kimUV5Z.mjs.map +0 -1
- package/dist/ecs-B4QrqfvQ.cjs.map +0 -1
- package/dist/index-DSdbSQ_t.d.cts.map +0 -1
- package/dist/index-Dj9odADH.d.mts.map +0 -1
package/dist/advanced.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_hooks = require("./hooks-
|
|
3
|
-
const require_ecs = require("./ecs-
|
|
2
|
+
const require_hooks = require("./hooks-B-UPFgGj.cjs");
|
|
3
|
+
const require_ecs = require("./ecs-BtX_rCS3.cjs");
|
|
4
4
|
//#region src/ecs/serialization.ts
|
|
5
5
|
/**
|
|
6
6
|
* Serializes all entities, components, and tags to a JSON-compatible document.
|
package/dist/advanced.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
1
|
+
import { D as computeSnapGuides, O as SpatialIndex, T as SnapGuide, _t as WebGLPass, c as FrameCameraState, dt as Profiler, ft as ProfilerStats, gt as TickSample, ht as R3FStats, lt as EcsStats, m as SpatialIndexResource, mt as R3FSample, pt as R3FPhaseHistogram, ut as FrameTimeStats, v as LayoutEngine, vt as WebGLStats, w as EqualSpacingIndicator } from "./index-BXRsEYL9.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-DSGDEjP7.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";
|
package/dist/advanced.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
1
|
+
import { D as computeSnapGuides, O as SpatialIndex, T as SnapGuide, _t as WebGLPass, c as FrameCameraState, dt as Profiler, ft as ProfilerStats, gt as TickSample, ht as R3FStats, lt as EcsStats, m as SpatialIndexResource, mt as R3FSample, pt as R3FPhaseHistogram, ut as FrameTimeStats, v as LayoutEngine, vt as WebGLStats, w as EqualSpacingIndicator } from "./index-Cxrz-hoe.mjs";
|
|
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-h56yzXZy.mjs";
|
|
3
3
|
import { ComponentType, EntityId, TagType, World } from "@jamesyong42/reactive-ecs";
|
|
4
4
|
import * as _$react from "react";
|
|
5
5
|
import * as THREE from "three";
|
package/dist/advanced.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as useCompositor, B as Profiler, C as selectBand, D as R3FRenderState, E as R3FRenderBudget, I as ContainerRefProvider, O as ResourceRegistry, R as computeSnapGuides, S as isOutOfBand, T as R3FAnimationSignal, _ as ProfilerProbe, a as useWidgetInvalidate, b as Compositor, c as SelectionOverlaySlot, g as R3FManager, h as GridRenderer, i as useWidgetAnimation, k as CompositorContext, n as useSharedMaterial, o as useWidgetPhase, p as SelectionRenderer, r as useSharedTexture, s as WidgetSlot, t as useSharedGeometry, u as WebGLManager, v as WidgetStateMachine, w as WidgetRenderTargetPool, x as ZOOM_BANDS, y as VirtualWidget, z as SpatialIndex } from "./hooks-
|
|
1
|
+
import { C as RootCameraResource, F as ContainerChildren, G as ParentFrame, M as Children, f as EngineProvider, h as CameraResource, w as SpatialIndexResource } from "./ecs-O6AR7iFp.mjs";
|
|
2
|
+
import { A as useCompositor, B as Profiler, C as selectBand, D as R3FRenderState, E as R3FRenderBudget, I as ContainerRefProvider, O as ResourceRegistry, R as computeSnapGuides, S as isOutOfBand, T as R3FAnimationSignal, _ as ProfilerProbe, a as useWidgetInvalidate, b as Compositor, c as SelectionOverlaySlot, g as R3FManager, h as GridRenderer, i as useWidgetAnimation, k as CompositorContext, n as useSharedMaterial, o as useWidgetPhase, p as SelectionRenderer, r as useSharedTexture, s as WidgetSlot, t as useSharedGeometry, u as WebGLManager, v as WidgetStateMachine, w as WidgetRenderTargetPool, x as ZOOM_BANDS, y as VirtualWidget, z as SpatialIndex } from "./hooks-Cpu0rEMv.mjs";
|
|
3
3
|
//#region src/ecs/serialization.ts
|
|
4
4
|
/**
|
|
5
5
|
* Serializes all entities, components, and tags to a JSON-compatible document.
|
package/dist/devtools.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
require("./hooks-
|
|
3
|
-
const require_ecs = require("./ecs-
|
|
2
|
+
require("./hooks-B-UPFgGj.cjs");
|
|
3
|
+
const require_ecs = require("./ecs-BtX_rCS3.cjs");
|
|
4
4
|
let react = require("react");
|
|
5
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
6
|
//#region src/devtools/EcsDevtools.tsx
|
|
@@ -34,9 +34,7 @@ function EcsDevtoolsInner({ onClose }) {
|
|
|
34
34
|
]);
|
|
35
35
|
const handleSpawn = () => {
|
|
36
36
|
if (!spawnType) return;
|
|
37
|
-
|
|
38
|
-
engine.markDirty();
|
|
39
|
-
setManualFocusId(id);
|
|
37
|
+
setManualFocusId(engine.spawnAtCameraCenter(spawnType));
|
|
40
38
|
};
|
|
41
39
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
42
40
|
className: "ic-ecs-root",
|
|
@@ -136,7 +134,6 @@ function EntityRow({ entity, focused, onClick }) {
|
|
|
136
134
|
className: "ic-ecs-btn ic-ecs-btn-danger ic-ecs-btn-sm",
|
|
137
135
|
onClick: () => {
|
|
138
136
|
engine.destroyEntity(entity);
|
|
139
|
-
engine.markDirty();
|
|
140
137
|
},
|
|
141
138
|
title: "Destroy entity",
|
|
142
139
|
children: "×"
|
package/dist/devtools.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devtools.cjs","names":["EngineProvider","useLayoutEngine","useAllEntities","useTaggedEntities","Selected","Widget","useComponent","useEntityComponents","useEntityTags","useRegisteredComponents","useRegisteredTags","WidgetData"],"sources":["../src/devtools/EcsDevtools.tsx"],"sourcesContent":["import type { ComponentType, EntityId, TagType } from '@jamesyong42/reactive-ecs';\nimport { useEffect, useMemo, useState } from 'react';\nimport { Selected, Widget, WidgetData } from '../ecs/components.js';\nimport type { LayoutEngine } from '../ecs/engine/index.js';\nimport { EngineProvider, useLayoutEngine } from '../react/context/engine-context.js';\nimport {\n\tuseAllEntities,\n\tuseComponent,\n\tuseEntityComponents,\n\tuseEntityTags,\n\tuseRegisteredComponents,\n\tuseRegisteredTags,\n\tuseTaggedEntities,\n} from '../react/hooks/ecs.js';\n\ninterface EcsDevtoolsProps {\n\t/**\n\t * Engine to inspect. If omitted, reads from the nearest InfiniteCanvas context —\n\t * supply this prop when the panel is rendered outside the `<InfiniteCanvas>` subtree.\n\t */\n\tengine?: LayoutEngine;\n\tonClose?: () => void;\n}\n\n/**\n * Live ECS editor: spawn widgets, browse entities, edit components, toggle tags.\n * Ship in a dev mode or behind a feature flag — not intended for production users.\n */\nexport function EcsDevtools({ engine, onClose }: EcsDevtoolsProps) {\n\tif (engine) {\n\t\treturn (\n\t\t\t<EngineProvider value={engine}>\n\t\t\t\t<EcsDevtoolsInner onClose={onClose} />\n\t\t\t</EngineProvider>\n\t\t);\n\t}\n\treturn <EcsDevtoolsInner onClose={onClose} />;\n}\n\nfunction EcsDevtoolsInner({ onClose }: { onClose?: () => void }) {\n\tconst engine = useLayoutEngine();\n\tconst allEntities = useAllEntities();\n\tconst selectedIds = useTaggedEntities(Selected);\n\tconst widgets = useMemo(() => engine.getWidgets(), [engine]);\n\n\tconst [showAll, setShowAll] = useState(false);\n\tconst [spawnType, setSpawnType] = useState(widgets[0]?.type ?? '');\n\tconst [manualFocusId, setManualFocusId] = useState<EntityId | null>(null);\n\n\tconst focusId = selectedIds[0] ?? manualFocusId;\n\n\tconst entityList = useMemo(() => {\n\t\tif (showAll) return allEntities;\n\t\treturn allEntities.filter((id) => engine.has(id, Widget));\n\t}, [engine, allEntities, showAll]);\n\n\tconst handleSpawn = () => {\n\t\tif (!spawnType) return;\n\t\tconst id = engine.spawnAtCameraCenter(spawnType);\n\t\tengine.markDirty();\n\t\tsetManualFocusId(id);\n\t};\n\n\treturn (\n\t\t<div className=\"ic-ecs-root\">\n\t\t\t<StyleTag />\n\n\t\t\t<div className=\"ic-ecs-header\">\n\t\t\t\t<span className=\"ic-ecs-title\">ECS Editor</span>\n\t\t\t\t{onClose && (\n\t\t\t\t\t<button type=\"button\" className=\"ic-ecs-close\" onClick={onClose} title=\"Close\">\n\t\t\t\t\t\t×\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t<div className=\"ic-ecs-section\">\n\t\t\t\t<div className=\"ic-ecs-label\">Spawn widget</div>\n\t\t\t\t<div className=\"ic-ecs-row\">\n\t\t\t\t\t<select\n\t\t\t\t\t\tclassName=\"ic-ecs-select\"\n\t\t\t\t\t\tvalue={spawnType}\n\t\t\t\t\t\tonChange={(e) => setSpawnType(e.target.value)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{widgets.map((w) => (\n\t\t\t\t\t\t\t<option key={w.type} value={w.type}>\n\t\t\t\t\t\t\t\t{w.type}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</select>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"ic-ecs-btn ic-ecs-btn-primary\"\n\t\t\t\t\t\tonClick={handleSpawn}\n\t\t\t\t\t\tdisabled={!spawnType}\n\t\t\t\t\t>\n\t\t\t\t\t\t+ Spawn\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div className=\"ic-ecs-section\">\n\t\t\t\t<div className=\"ic-ecs-label-row\">\n\t\t\t\t\t<span className=\"ic-ecs-label\">\n\t\t\t\t\t\tEntities ({entityList.length}\n\t\t\t\t\t\t{showAll ? '' : ' widgets'})\n\t\t\t\t\t</span>\n\t\t\t\t\t<label className=\"ic-ecs-check\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tchecked={showAll}\n\t\t\t\t\t\t\tonChange={(e) => setShowAll(e.target.checked)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\tshow all\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"ic-ecs-list\">\n\t\t\t\t\t{entityList.map((id) => (\n\t\t\t\t\t\t<EntityRow\n\t\t\t\t\t\t\tkey={id}\n\t\t\t\t\t\t\tentity={id}\n\t\t\t\t\t\t\tfocused={id === focusId}\n\t\t\t\t\t\t\tonClick={() => setManualFocusId(id)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t\t{entityList.length === 0 && <div className=\"ic-ecs-empty\">no entities</div>}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{focusId !== null && focusId !== undefined && engine.world.entityExists(focusId) && (\n\t\t\t\t<EntityInspector entity={focusId} />\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction EntityRow({\n\tentity,\n\tfocused,\n\tonClick,\n}: {\n\tentity: EntityId;\n\tfocused: boolean;\n\tonClick: () => void;\n}) {\n\tconst engine = useLayoutEngine();\n\tconst widget = useComponent(entity, Widget);\n\tconst label = widget?.type ?? 'entity';\n\n\treturn (\n\t\t<div className={`ic-ecs-entity-row ${focused ? 'is-focused' : ''}`}>\n\t\t\t<button type=\"button\" className=\"ic-ecs-entity-btn\" onClick={onClick}>\n\t\t\t\t<span className=\"ic-ecs-entity-id\">e{entity}</span>\n\t\t\t\t<span className=\"ic-ecs-entity-label\">{label}</span>\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"ic-ecs-btn ic-ecs-btn-danger ic-ecs-btn-sm\"\n\t\t\t\tonClick={() => {\n\t\t\t\t\tengine.destroyEntity(entity);\n\t\t\t\t\tengine.markDirty();\n\t\t\t\t}}\n\t\t\t\ttitle=\"Destroy entity\"\n\t\t\t>\n\t\t\t\t×\n\t\t\t</button>\n\t\t</div>\n\t);\n}\n\nfunction EntityInspector({ entity }: { entity: EntityId }) {\n\tconst engine = useLayoutEngine();\n\tconst components = useEntityComponents(entity);\n\tconst tags = useEntityTags(entity);\n\tconst registeredComponents = useRegisteredComponents();\n\tconst registeredTags = useRegisteredTags();\n\tconst widget = useComponent(entity, Widget);\n\n\tconst absentComponents = useMemo(() => {\n\t\tconst present = new Set(components.map((c) => c.name));\n\t\treturn registeredComponents.filter((c) => !present.has(c.name));\n\t}, [components, registeredComponents]);\n\n\tconst [componentToAdd, setComponentToAdd] = useState('');\n\tuseEffect(() => {\n\t\tsetComponentToAdd(absentComponents[0]?.name ?? '');\n\t}, [absentComponents]);\n\n\tconst handleAddComponent = () => {\n\t\tconst type = absentComponents.find((c) => c.name === componentToAdd);\n\t\tif (!type) return;\n\t\tengine.addComponent(entity, type);\n\t};\n\n\treturn (\n\t\t<div className=\"ic-ecs-section\">\n\t\t\t<div className=\"ic-ecs-inspect-head\">\n\t\t\t\t<span className=\"ic-ecs-label\">\n\t\t\t\t\tEntity <span className=\"ic-ecs-entity-id\">e{entity}</span>\n\t\t\t\t\t{widget?.type && <span className=\"ic-ecs-entity-label\"> · {widget.type}</span>}\n\t\t\t\t</span>\n\t\t\t</div>\n\n\t\t\t<div className=\"ic-ecs-sub-label\">Components</div>\n\t\t\t<div className=\"ic-ecs-components\">\n\t\t\t\t{components.map((type) => (\n\t\t\t\t\t<ComponentEditor key={type.name} entity={entity} type={type} />\n\t\t\t\t))}\n\t\t\t</div>\n\n\t\t\t{absentComponents.length > 0 && (\n\t\t\t\t<div className=\"ic-ecs-row\" style={{ marginTop: 6 }}>\n\t\t\t\t\t<select\n\t\t\t\t\t\tclassName=\"ic-ecs-select\"\n\t\t\t\t\t\tvalue={componentToAdd}\n\t\t\t\t\t\tonChange={(e) => setComponentToAdd(e.target.value)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{absentComponents.map((c) => (\n\t\t\t\t\t\t\t<option key={c.name} value={c.name}>\n\t\t\t\t\t\t\t\t{c.name}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</select>\n\t\t\t\t\t<button type=\"button\" className=\"ic-ecs-btn\" onClick={handleAddComponent}>\n\t\t\t\t\t\t+ Add component\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t<div className=\"ic-ecs-sub-label\">Tags</div>\n\t\t\t<div className=\"ic-ecs-tags\">\n\t\t\t\t{registeredTags.map((type) => (\n\t\t\t\t\t<TagPill\n\t\t\t\t\t\tkey={type.name}\n\t\t\t\t\t\tentity={entity}\n\t\t\t\t\t\ttype={type}\n\t\t\t\t\t\tactive={tags.some((t) => t.name === type.name)}\n\t\t\t\t\t/>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction ComponentEditor({ entity, type }: { entity: EntityId; type: ComponentType }) {\n\tconst engine = useLayoutEngine();\n\tconst value = useComponent(entity, type);\n\tconst [collapsed, setCollapsed] = useState(false);\n\n\tif (!value) return null;\n\n\tconst isWidgetData = type.name === 'WidgetData';\n\n\treturn (\n\t\t<div className=\"ic-ecs-component\">\n\t\t\t<div className=\"ic-ecs-component-head\">\n\t\t\t\t<button type=\"button\" className=\"ic-ecs-toggle\" onClick={() => setCollapsed((c) => !c)}>\n\t\t\t\t\t{collapsed ? '▶' : '▼'} {type.name}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"ic-ecs-btn ic-ecs-btn-danger ic-ecs-btn-sm\"\n\t\t\t\t\tonClick={() => engine.removeComponent(entity, type)}\n\t\t\t\t\ttitle=\"Remove component\"\n\t\t\t\t>\n\t\t\t\t\t×\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t{!collapsed && (\n\t\t\t\t<div className=\"ic-ecs-fields\">\n\t\t\t\t\t{isWidgetData ? (\n\t\t\t\t\t\t<WidgetDataEditor entity={entity} value={value as { data: Record<string, unknown> }} />\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<GenericFieldsEditor entity={entity} type={type} value={value} />\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction GenericFieldsEditor<T>({\n\tentity,\n\ttype,\n\tvalue,\n}: {\n\tentity: EntityId;\n\ttype: ComponentType<T>;\n\tvalue: T;\n}) {\n\tconst engine = useLayoutEngine();\n\tconst defaults = type.defaults as Record<string, unknown>;\n\tconst val = value as Record<string, unknown>;\n\tconst keys = Object.keys(defaults);\n\n\treturn (\n\t\t<>\n\t\t\t{keys.map((key) => (\n\t\t\t\t<FieldRow\n\t\t\t\t\tkey={key}\n\t\t\t\t\tlabel={key}\n\t\t\t\t\tvalue={val[key]}\n\t\t\t\t\tonChange={(next) => {\n\t\t\t\t\t\tengine.set(entity, type, { [key]: next } as Partial<T>);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</>\n\t);\n}\n\nfunction WidgetDataEditor({\n\tentity,\n\tvalue,\n}: {\n\tentity: EntityId;\n\tvalue: { data: Record<string, unknown> };\n}) {\n\tconst engine = useLayoutEngine();\n\tconst data = value.data ?? {};\n\tconst keys = Object.keys(data);\n\n\tif (keys.length === 0) {\n\t\treturn <div className=\"ic-ecs-empty\">(no fields)</div>;\n\t}\n\n\treturn (\n\t\t<>\n\t\t\t{keys.map((key) => (\n\t\t\t\t<FieldRow\n\t\t\t\t\tkey={key}\n\t\t\t\t\tlabel={key}\n\t\t\t\t\tvalue={data[key]}\n\t\t\t\t\tonChange={(next) => {\n\t\t\t\t\t\tengine.set(entity, WidgetData, { data: { ...data, [key]: next } });\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</>\n\t);\n}\n\nfunction FieldRow({\n\tlabel,\n\tvalue,\n\tonChange,\n}: {\n\tlabel: string;\n\tvalue: unknown;\n\tonChange: (next: unknown) => void;\n}) {\n\treturn (\n\t\t<div className=\"ic-ecs-field\">\n\t\t\t<span className=\"ic-ecs-field-label\">{label}</span>\n\t\t\t<FieldInput value={value} onChange={onChange} />\n\t\t</div>\n\t);\n}\n\nfunction FieldInput({ value, onChange }: { value: unknown; onChange: (next: unknown) => void }) {\n\tif (typeof value === 'number') {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"number\"\n\t\t\t\tclassName=\"ic-ecs-input\"\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => {\n\t\t\t\t\tconst n = Number.parseFloat(e.target.value);\n\t\t\t\t\tif (!Number.isNaN(n)) onChange(n);\n\t\t\t\t}}\n\t\t\t/>\n\t\t);\n\t}\n\tif (typeof value === 'boolean') {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"checkbox\"\n\t\t\t\tclassName=\"ic-ecs-checkbox\"\n\t\t\t\tchecked={value}\n\t\t\t\tonChange={(e) => onChange(e.target.checked)}\n\t\t\t/>\n\t\t);\n\t}\n\tif (typeof value === 'string') {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"text\"\n\t\t\t\tclassName=\"ic-ecs-input\"\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => onChange(e.target.value)}\n\t\t\t/>\n\t\t);\n\t}\n\treturn <JsonInput value={value} onChange={onChange} />;\n}\n\nfunction JsonInput({ value, onChange }: { value: unknown; onChange: (next: unknown) => void }) {\n\tconst serialized = JSON.stringify(value);\n\tconst [text, setText] = useState(serialized);\n\tconst [error, setError] = useState(false);\n\n\tuseEffect(() => {\n\t\tsetText(JSON.stringify(value));\n\t\tsetError(false);\n\t}, [value]);\n\n\tconst commit = () => {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(text);\n\t\t\tsetError(false);\n\t\t\tonChange(parsed);\n\t\t} catch {\n\t\t\tsetError(true);\n\t\t}\n\t};\n\n\treturn (\n\t\t<input\n\t\t\ttype=\"text\"\n\t\t\tclassName={`ic-ecs-input ic-ecs-input-json ${error ? 'is-error' : ''}`}\n\t\t\tvalue={text}\n\t\t\tonChange={(e) => setText(e.target.value)}\n\t\t\tonBlur={commit}\n\t\t\tonKeyDown={(e) => {\n\t\t\t\tif (e.key === 'Enter') commit();\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction TagPill({ entity, type, active }: { entity: EntityId; type: TagType; active: boolean }) {\n\tconst engine = useLayoutEngine();\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclassName={`ic-ecs-tag ${active ? 'is-active' : ''}`}\n\t\t\tonClick={() => {\n\t\t\t\tif (active) engine.removeTag(entity, type);\n\t\t\t\telse engine.addTag(entity, type);\n\t\t\t}}\n\t\t>\n\t\t\t{type.name}\n\t\t</button>\n\t);\n}\n\n// Scoped styles, inlined once on mount. Dark-mode-aware via prefers-color-scheme\n// and a parent `.dark` class (matches the playground's toggle convention).\nconst STYLE_ID = 'ic-ecs-devtools-style';\nconst CSS = `\n.ic-ecs-root {\n\t--ic-ecs-bg: rgba(255, 255, 255, 0.96);\n\t--ic-ecs-bg-elev: #f7f7f7;\n\t--ic-ecs-fg: #1a1a1a;\n\t--ic-ecs-fg-muted: #6b7280;\n\t--ic-ecs-fg-faint: #9ca3af;\n\t--ic-ecs-border: #e5e7eb;\n\t--ic-ecs-accent: #0d99ff;\n\t--ic-ecs-accent-fg: #ffffff;\n\t--ic-ecs-danger: #dc2626;\n\t--ic-ecs-focus-bg: rgba(13, 153, 255, 0.12);\n\tposition: absolute;\n\ttop: 4rem;\n\tright: 1rem;\n\tz-index: 50;\n\twidth: 360px;\n\tmax-height: calc(100vh - 6rem);\n\toverflow-y: auto;\n\tbackground: var(--ic-ecs-bg);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 8px;\n\tbox-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);\n\tbackdrop-filter: blur(6px);\n\tfont-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n\tfont-size: 11px;\n\tline-height: 1.4;\n}\n.dark .ic-ecs-root,\n:root[data-theme=\"dark\"] .ic-ecs-root {\n\t--ic-ecs-bg: rgba(23, 23, 23, 0.96);\n\t--ic-ecs-bg-elev: #1f1f1f;\n\t--ic-ecs-fg: #e5e5e5;\n\t--ic-ecs-fg-muted: #a1a1aa;\n\t--ic-ecs-fg-faint: #525252;\n\t--ic-ecs-border: #2a2a2a;\n\t--ic-ecs-focus-bg: rgba(13, 153, 255, 0.18);\n}\n.ic-ecs-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--ic-ecs-border);\n}\n.ic-ecs-title {\n\tfont-weight: 600;\n\tfont-size: 12px;\n}\n.ic-ecs-close {\n\tbackground: transparent;\n\tborder: 0;\n\tcolor: var(--ic-ecs-fg-muted);\n\tcursor: pointer;\n\tfont-size: 16px;\n\tline-height: 1;\n\tpadding: 0 4px;\n}\n.ic-ecs-close:hover { color: var(--ic-ecs-fg); }\n.ic-ecs-section {\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--ic-ecs-border);\n}\n.ic-ecs-section:last-child { border-bottom: 0; }\n.ic-ecs-label {\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.05em;\n\ttext-transform: uppercase;\n\tcolor: var(--ic-ecs-fg-muted);\n}\n.ic-ecs-label-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin-bottom: 4px;\n}\n.ic-ecs-sub-label {\n\tfont-size: 9px;\n\tfont-weight: 600;\n\tletter-spacing: 0.05em;\n\ttext-transform: uppercase;\n\tcolor: var(--ic-ecs-fg-faint);\n\tmargin: 8px 0 4px;\n}\n.ic-ecs-row {\n\tdisplay: flex;\n\tgap: 6px;\n\talign-items: center;\n\tmargin-top: 4px;\n}\n.ic-ecs-select {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tbackground: var(--ic-ecs-bg-elev);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tpadding: 4px 6px;\n\tfont: inherit;\n}\n.ic-ecs-input {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tbackground: var(--ic-ecs-bg-elev);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tpadding: 2px 6px;\n\tfont: inherit;\n}\n.ic-ecs-input:focus { outline: 1px solid var(--ic-ecs-accent); outline-offset: -1px; }\n.ic-ecs-input-json.is-error { border-color: var(--ic-ecs-danger); }\n.ic-ecs-checkbox {\n\taccent-color: var(--ic-ecs-accent);\n}\n.ic-ecs-btn {\n\tbackground: var(--ic-ecs-bg-elev);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tpadding: 3px 8px;\n\tfont: inherit;\n\tcursor: pointer;\n\twhite-space: nowrap;\n}\n.ic-ecs-btn:hover { background: var(--ic-ecs-border); }\n.ic-ecs-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n.ic-ecs-btn-primary {\n\tbackground: var(--ic-ecs-accent);\n\tcolor: var(--ic-ecs-accent-fg);\n\tborder-color: var(--ic-ecs-accent);\n}\n.ic-ecs-btn-primary:hover { filter: brightness(1.08); background: var(--ic-ecs-accent); }\n.ic-ecs-btn-danger { color: var(--ic-ecs-danger); }\n.ic-ecs-btn-sm { padding: 0 6px; font-size: 11px; }\n.ic-ecs-check {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 10px;\n\tcolor: var(--ic-ecs-fg-muted);\n\tcursor: pointer;\n}\n.ic-ecs-list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmax-height: 200px;\n\toverflow-y: auto;\n\tmargin-top: 4px;\n}\n.ic-ecs-empty {\n\tcolor: var(--ic-ecs-fg-faint);\n\tfont-style: italic;\n\tpadding: 4px 0;\n}\n.ic-ecs-entity-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n\tpadding: 2px 4px;\n\tborder-radius: 4px;\n}\n.ic-ecs-entity-row.is-focused { background: var(--ic-ecs-focus-bg); }\n.ic-ecs-entity-btn {\n\tflex: 1 1 auto;\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tbackground: transparent;\n\tborder: 0;\n\tcolor: inherit;\n\tcursor: pointer;\n\ttext-align: left;\n\tpadding: 2px 0;\n\tfont: inherit;\n\tmin-width: 0;\n}\n.ic-ecs-entity-id {\n\tcolor: var(--ic-ecs-fg-muted);\n\tfont-variant-numeric: tabular-nums;\n}\n.ic-ecs-entity-label {\n\tcolor: var(--ic-ecs-fg);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.ic-ecs-inspect-head {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin-bottom: 4px;\n}\n.ic-ecs-components {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n.ic-ecs-component {\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tbackground: var(--ic-ecs-bg-elev);\n}\n.ic-ecs-component-head {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 2px 6px;\n}\n.ic-ecs-toggle {\n\tbackground: transparent;\n\tborder: 0;\n\tcolor: inherit;\n\tcursor: pointer;\n\tfont: inherit;\n\tfont-weight: 600;\n\tpadding: 2px 0;\n}\n.ic-ecs-fields {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n\tpadding: 4px 6px 6px;\n\tborder-top: 1px dashed var(--ic-ecs-border);\n}\n.ic-ecs-field {\n\tdisplay: grid;\n\tgrid-template-columns: 70px 1fr;\n\talign-items: center;\n\tgap: 6px;\n}\n.ic-ecs-field-label {\n\tcolor: var(--ic-ecs-fg-muted);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.ic-ecs-tags {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n}\n.ic-ecs-tag {\n\tbackground: transparent;\n\tcolor: var(--ic-ecs-fg-faint);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 999px;\n\tpadding: 2px 8px;\n\tfont: inherit;\n\tcursor: pointer;\n}\n.ic-ecs-tag:hover { color: var(--ic-ecs-fg); }\n.ic-ecs-tag.is-active {\n\tbackground: var(--ic-ecs-accent);\n\tcolor: var(--ic-ecs-accent-fg);\n\tborder-color: var(--ic-ecs-accent);\n}\n`;\n\nfunction StyleTag() {\n\tuseEffect(() => {\n\t\tif (typeof document === 'undefined') return;\n\t\tif (document.getElementById(STYLE_ID)) return;\n\t\tconst el = document.createElement('style');\n\t\tel.id = STYLE_ID;\n\t\tel.textContent = CSS;\n\t\tdocument.head.appendChild(el);\n\t}, []);\n\treturn null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"devtools.cjs","names":["EngineProvider","useLayoutEngine","useAllEntities","useTaggedEntities","Selected","Widget","useComponent","useEntityComponents","useEntityTags","useRegisteredComponents","useRegisteredTags","WidgetData"],"sources":["../src/devtools/EcsDevtools.tsx"],"sourcesContent":["import type { ComponentType, EntityId, TagType } from '@jamesyong42/reactive-ecs';\nimport { useEffect, useMemo, useState } from 'react';\nimport { Selected, Widget, WidgetData } from '../ecs/components.js';\nimport type { LayoutEngine } from '../ecs/engine/index.js';\nimport { EngineProvider, useLayoutEngine } from '../react/context/engine-context.js';\nimport {\n\tuseAllEntities,\n\tuseComponent,\n\tuseEntityComponents,\n\tuseEntityTags,\n\tuseRegisteredComponents,\n\tuseRegisteredTags,\n\tuseTaggedEntities,\n} from '../react/hooks/ecs.js';\n\ninterface EcsDevtoolsProps {\n\t/**\n\t * Engine to inspect. If omitted, reads from the nearest InfiniteCanvas context —\n\t * supply this prop when the panel is rendered outside the `<InfiniteCanvas>` subtree.\n\t */\n\tengine?: LayoutEngine;\n\tonClose?: () => void;\n}\n\n/**\n * Live ECS editor: spawn widgets, browse entities, edit components, toggle tags.\n * Ship in a dev mode or behind a feature flag — not intended for production users.\n */\nexport function EcsDevtools({ engine, onClose }: EcsDevtoolsProps) {\n\tif (engine) {\n\t\treturn (\n\t\t\t<EngineProvider value={engine}>\n\t\t\t\t<EcsDevtoolsInner onClose={onClose} />\n\t\t\t</EngineProvider>\n\t\t);\n\t}\n\treturn <EcsDevtoolsInner onClose={onClose} />;\n}\n\nfunction EcsDevtoolsInner({ onClose }: { onClose?: () => void }) {\n\tconst engine = useLayoutEngine();\n\tconst allEntities = useAllEntities();\n\tconst selectedIds = useTaggedEntities(Selected);\n\tconst widgets = useMemo(() => engine.getWidgets(), [engine]);\n\n\tconst [showAll, setShowAll] = useState(false);\n\tconst [spawnType, setSpawnType] = useState(widgets[0]?.type ?? '');\n\tconst [manualFocusId, setManualFocusId] = useState<EntityId | null>(null);\n\n\tconst focusId = selectedIds[0] ?? manualFocusId;\n\n\tconst entityList = useMemo(() => {\n\t\tif (showAll) return allEntities;\n\t\treturn allEntities.filter((id) => engine.has(id, Widget));\n\t}, [engine, allEntities, showAll]);\n\n\tconst handleSpawn = () => {\n\t\tif (!spawnType) return;\n\t\tconst id = engine.spawnAtCameraCenter(spawnType); // proxy auto-dirties\n\t\tsetManualFocusId(id);\n\t};\n\n\treturn (\n\t\t<div className=\"ic-ecs-root\">\n\t\t\t<StyleTag />\n\n\t\t\t<div className=\"ic-ecs-header\">\n\t\t\t\t<span className=\"ic-ecs-title\">ECS Editor</span>\n\t\t\t\t{onClose && (\n\t\t\t\t\t<button type=\"button\" className=\"ic-ecs-close\" onClick={onClose} title=\"Close\">\n\t\t\t\t\t\t×\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t<div className=\"ic-ecs-section\">\n\t\t\t\t<div className=\"ic-ecs-label\">Spawn widget</div>\n\t\t\t\t<div className=\"ic-ecs-row\">\n\t\t\t\t\t<select\n\t\t\t\t\t\tclassName=\"ic-ecs-select\"\n\t\t\t\t\t\tvalue={spawnType}\n\t\t\t\t\t\tonChange={(e) => setSpawnType(e.target.value)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{widgets.map((w) => (\n\t\t\t\t\t\t\t<option key={w.type} value={w.type}>\n\t\t\t\t\t\t\t\t{w.type}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</select>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"ic-ecs-btn ic-ecs-btn-primary\"\n\t\t\t\t\t\tonClick={handleSpawn}\n\t\t\t\t\t\tdisabled={!spawnType}\n\t\t\t\t\t>\n\t\t\t\t\t\t+ Spawn\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div className=\"ic-ecs-section\">\n\t\t\t\t<div className=\"ic-ecs-label-row\">\n\t\t\t\t\t<span className=\"ic-ecs-label\">\n\t\t\t\t\t\tEntities ({entityList.length}\n\t\t\t\t\t\t{showAll ? '' : ' widgets'})\n\t\t\t\t\t</span>\n\t\t\t\t\t<label className=\"ic-ecs-check\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tchecked={showAll}\n\t\t\t\t\t\t\tonChange={(e) => setShowAll(e.target.checked)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\tshow all\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"ic-ecs-list\">\n\t\t\t\t\t{entityList.map((id) => (\n\t\t\t\t\t\t<EntityRow\n\t\t\t\t\t\t\tkey={id}\n\t\t\t\t\t\t\tentity={id}\n\t\t\t\t\t\t\tfocused={id === focusId}\n\t\t\t\t\t\t\tonClick={() => setManualFocusId(id)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t\t{entityList.length === 0 && <div className=\"ic-ecs-empty\">no entities</div>}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{focusId !== null && focusId !== undefined && engine.world.entityExists(focusId) && (\n\t\t\t\t<EntityInspector entity={focusId} />\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction EntityRow({\n\tentity,\n\tfocused,\n\tonClick,\n}: {\n\tentity: EntityId;\n\tfocused: boolean;\n\tonClick: () => void;\n}) {\n\tconst engine = useLayoutEngine();\n\tconst widget = useComponent(entity, Widget);\n\tconst label = widget?.type ?? 'entity';\n\n\treturn (\n\t\t<div className={`ic-ecs-entity-row ${focused ? 'is-focused' : ''}`}>\n\t\t\t<button type=\"button\" className=\"ic-ecs-entity-btn\" onClick={onClick}>\n\t\t\t\t<span className=\"ic-ecs-entity-id\">e{entity}</span>\n\t\t\t\t<span className=\"ic-ecs-entity-label\">{label}</span>\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"ic-ecs-btn ic-ecs-btn-danger ic-ecs-btn-sm\"\n\t\t\t\tonClick={() => {\n\t\t\t\t\tengine.destroyEntity(entity); // proxy auto-dirties\n\t\t\t\t}}\n\t\t\t\ttitle=\"Destroy entity\"\n\t\t\t>\n\t\t\t\t×\n\t\t\t</button>\n\t\t</div>\n\t);\n}\n\nfunction EntityInspector({ entity }: { entity: EntityId }) {\n\tconst engine = useLayoutEngine();\n\tconst components = useEntityComponents(entity);\n\tconst tags = useEntityTags(entity);\n\tconst registeredComponents = useRegisteredComponents();\n\tconst registeredTags = useRegisteredTags();\n\tconst widget = useComponent(entity, Widget);\n\n\tconst absentComponents = useMemo(() => {\n\t\tconst present = new Set(components.map((c) => c.name));\n\t\treturn registeredComponents.filter((c) => !present.has(c.name));\n\t}, [components, registeredComponents]);\n\n\tconst [componentToAdd, setComponentToAdd] = useState('');\n\tuseEffect(() => {\n\t\tsetComponentToAdd(absentComponents[0]?.name ?? '');\n\t}, [absentComponents]);\n\n\tconst handleAddComponent = () => {\n\t\tconst type = absentComponents.find((c) => c.name === componentToAdd);\n\t\tif (!type) return;\n\t\tengine.addComponent(entity, type);\n\t};\n\n\treturn (\n\t\t<div className=\"ic-ecs-section\">\n\t\t\t<div className=\"ic-ecs-inspect-head\">\n\t\t\t\t<span className=\"ic-ecs-label\">\n\t\t\t\t\tEntity <span className=\"ic-ecs-entity-id\">e{entity}</span>\n\t\t\t\t\t{widget?.type && <span className=\"ic-ecs-entity-label\"> · {widget.type}</span>}\n\t\t\t\t</span>\n\t\t\t</div>\n\n\t\t\t<div className=\"ic-ecs-sub-label\">Components</div>\n\t\t\t<div className=\"ic-ecs-components\">\n\t\t\t\t{components.map((type) => (\n\t\t\t\t\t<ComponentEditor key={type.name} entity={entity} type={type} />\n\t\t\t\t))}\n\t\t\t</div>\n\n\t\t\t{absentComponents.length > 0 && (\n\t\t\t\t<div className=\"ic-ecs-row\" style={{ marginTop: 6 }}>\n\t\t\t\t\t<select\n\t\t\t\t\t\tclassName=\"ic-ecs-select\"\n\t\t\t\t\t\tvalue={componentToAdd}\n\t\t\t\t\t\tonChange={(e) => setComponentToAdd(e.target.value)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{absentComponents.map((c) => (\n\t\t\t\t\t\t\t<option key={c.name} value={c.name}>\n\t\t\t\t\t\t\t\t{c.name}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</select>\n\t\t\t\t\t<button type=\"button\" className=\"ic-ecs-btn\" onClick={handleAddComponent}>\n\t\t\t\t\t\t+ Add component\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t<div className=\"ic-ecs-sub-label\">Tags</div>\n\t\t\t<div className=\"ic-ecs-tags\">\n\t\t\t\t{registeredTags.map((type) => (\n\t\t\t\t\t<TagPill\n\t\t\t\t\t\tkey={type.name}\n\t\t\t\t\t\tentity={entity}\n\t\t\t\t\t\ttype={type}\n\t\t\t\t\t\tactive={tags.some((t) => t.name === type.name)}\n\t\t\t\t\t/>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction ComponentEditor({ entity, type }: { entity: EntityId; type: ComponentType }) {\n\tconst engine = useLayoutEngine();\n\tconst value = useComponent(entity, type);\n\tconst [collapsed, setCollapsed] = useState(false);\n\n\tif (!value) return null;\n\n\tconst isWidgetData = type.name === 'WidgetData';\n\n\treturn (\n\t\t<div className=\"ic-ecs-component\">\n\t\t\t<div className=\"ic-ecs-component-head\">\n\t\t\t\t<button type=\"button\" className=\"ic-ecs-toggle\" onClick={() => setCollapsed((c) => !c)}>\n\t\t\t\t\t{collapsed ? '▶' : '▼'} {type.name}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"ic-ecs-btn ic-ecs-btn-danger ic-ecs-btn-sm\"\n\t\t\t\t\tonClick={() => engine.removeComponent(entity, type)}\n\t\t\t\t\ttitle=\"Remove component\"\n\t\t\t\t>\n\t\t\t\t\t×\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t{!collapsed && (\n\t\t\t\t<div className=\"ic-ecs-fields\">\n\t\t\t\t\t{isWidgetData ? (\n\t\t\t\t\t\t<WidgetDataEditor entity={entity} value={value as { data: Record<string, unknown> }} />\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<GenericFieldsEditor entity={entity} type={type} value={value} />\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction GenericFieldsEditor<T>({\n\tentity,\n\ttype,\n\tvalue,\n}: {\n\tentity: EntityId;\n\ttype: ComponentType<T>;\n\tvalue: T;\n}) {\n\tconst engine = useLayoutEngine();\n\tconst defaults = type.defaults as Record<string, unknown>;\n\tconst val = value as Record<string, unknown>;\n\tconst keys = Object.keys(defaults);\n\n\treturn (\n\t\t<>\n\t\t\t{keys.map((key) => (\n\t\t\t\t<FieldRow\n\t\t\t\t\tkey={key}\n\t\t\t\t\tlabel={key}\n\t\t\t\t\tvalue={val[key]}\n\t\t\t\t\tonChange={(next) => {\n\t\t\t\t\t\tengine.set(entity, type, { [key]: next } as Partial<T>);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</>\n\t);\n}\n\nfunction WidgetDataEditor({\n\tentity,\n\tvalue,\n}: {\n\tentity: EntityId;\n\tvalue: { data: Record<string, unknown> };\n}) {\n\tconst engine = useLayoutEngine();\n\tconst data = value.data ?? {};\n\tconst keys = Object.keys(data);\n\n\tif (keys.length === 0) {\n\t\treturn <div className=\"ic-ecs-empty\">(no fields)</div>;\n\t}\n\n\treturn (\n\t\t<>\n\t\t\t{keys.map((key) => (\n\t\t\t\t<FieldRow\n\t\t\t\t\tkey={key}\n\t\t\t\t\tlabel={key}\n\t\t\t\t\tvalue={data[key]}\n\t\t\t\t\tonChange={(next) => {\n\t\t\t\t\t\tengine.set(entity, WidgetData, { data: { ...data, [key]: next } });\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</>\n\t);\n}\n\nfunction FieldRow({\n\tlabel,\n\tvalue,\n\tonChange,\n}: {\n\tlabel: string;\n\tvalue: unknown;\n\tonChange: (next: unknown) => void;\n}) {\n\treturn (\n\t\t<div className=\"ic-ecs-field\">\n\t\t\t<span className=\"ic-ecs-field-label\">{label}</span>\n\t\t\t<FieldInput value={value} onChange={onChange} />\n\t\t</div>\n\t);\n}\n\nfunction FieldInput({ value, onChange }: { value: unknown; onChange: (next: unknown) => void }) {\n\tif (typeof value === 'number') {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"number\"\n\t\t\t\tclassName=\"ic-ecs-input\"\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => {\n\t\t\t\t\tconst n = Number.parseFloat(e.target.value);\n\t\t\t\t\tif (!Number.isNaN(n)) onChange(n);\n\t\t\t\t}}\n\t\t\t/>\n\t\t);\n\t}\n\tif (typeof value === 'boolean') {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"checkbox\"\n\t\t\t\tclassName=\"ic-ecs-checkbox\"\n\t\t\t\tchecked={value}\n\t\t\t\tonChange={(e) => onChange(e.target.checked)}\n\t\t\t/>\n\t\t);\n\t}\n\tif (typeof value === 'string') {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"text\"\n\t\t\t\tclassName=\"ic-ecs-input\"\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => onChange(e.target.value)}\n\t\t\t/>\n\t\t);\n\t}\n\treturn <JsonInput value={value} onChange={onChange} />;\n}\n\nfunction JsonInput({ value, onChange }: { value: unknown; onChange: (next: unknown) => void }) {\n\tconst serialized = JSON.stringify(value);\n\tconst [text, setText] = useState(serialized);\n\tconst [error, setError] = useState(false);\n\n\tuseEffect(() => {\n\t\tsetText(JSON.stringify(value));\n\t\tsetError(false);\n\t}, [value]);\n\n\tconst commit = () => {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(text);\n\t\t\tsetError(false);\n\t\t\tonChange(parsed);\n\t\t} catch {\n\t\t\tsetError(true);\n\t\t}\n\t};\n\n\treturn (\n\t\t<input\n\t\t\ttype=\"text\"\n\t\t\tclassName={`ic-ecs-input ic-ecs-input-json ${error ? 'is-error' : ''}`}\n\t\t\tvalue={text}\n\t\t\tonChange={(e) => setText(e.target.value)}\n\t\t\tonBlur={commit}\n\t\t\tonKeyDown={(e) => {\n\t\t\t\tif (e.key === 'Enter') commit();\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction TagPill({ entity, type, active }: { entity: EntityId; type: TagType; active: boolean }) {\n\tconst engine = useLayoutEngine();\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclassName={`ic-ecs-tag ${active ? 'is-active' : ''}`}\n\t\t\tonClick={() => {\n\t\t\t\tif (active) engine.removeTag(entity, type);\n\t\t\t\telse engine.addTag(entity, type);\n\t\t\t}}\n\t\t>\n\t\t\t{type.name}\n\t\t</button>\n\t);\n}\n\n// Scoped styles, inlined once on mount. Dark-mode-aware via prefers-color-scheme\n// and a parent `.dark` class (matches the playground's toggle convention).\nconst STYLE_ID = 'ic-ecs-devtools-style';\nconst CSS = `\n.ic-ecs-root {\n\t--ic-ecs-bg: rgba(255, 255, 255, 0.96);\n\t--ic-ecs-bg-elev: #f7f7f7;\n\t--ic-ecs-fg: #1a1a1a;\n\t--ic-ecs-fg-muted: #6b7280;\n\t--ic-ecs-fg-faint: #9ca3af;\n\t--ic-ecs-border: #e5e7eb;\n\t--ic-ecs-accent: #0d99ff;\n\t--ic-ecs-accent-fg: #ffffff;\n\t--ic-ecs-danger: #dc2626;\n\t--ic-ecs-focus-bg: rgba(13, 153, 255, 0.12);\n\tposition: absolute;\n\ttop: 4rem;\n\tright: 1rem;\n\tz-index: 50;\n\twidth: 360px;\n\tmax-height: calc(100vh - 6rem);\n\toverflow-y: auto;\n\tbackground: var(--ic-ecs-bg);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 8px;\n\tbox-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);\n\tbackdrop-filter: blur(6px);\n\tfont-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n\tfont-size: 11px;\n\tline-height: 1.4;\n}\n.dark .ic-ecs-root,\n:root[data-theme=\"dark\"] .ic-ecs-root {\n\t--ic-ecs-bg: rgba(23, 23, 23, 0.96);\n\t--ic-ecs-bg-elev: #1f1f1f;\n\t--ic-ecs-fg: #e5e5e5;\n\t--ic-ecs-fg-muted: #a1a1aa;\n\t--ic-ecs-fg-faint: #525252;\n\t--ic-ecs-border: #2a2a2a;\n\t--ic-ecs-focus-bg: rgba(13, 153, 255, 0.18);\n}\n.ic-ecs-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--ic-ecs-border);\n}\n.ic-ecs-title {\n\tfont-weight: 600;\n\tfont-size: 12px;\n}\n.ic-ecs-close {\n\tbackground: transparent;\n\tborder: 0;\n\tcolor: var(--ic-ecs-fg-muted);\n\tcursor: pointer;\n\tfont-size: 16px;\n\tline-height: 1;\n\tpadding: 0 4px;\n}\n.ic-ecs-close:hover { color: var(--ic-ecs-fg); }\n.ic-ecs-section {\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--ic-ecs-border);\n}\n.ic-ecs-section:last-child { border-bottom: 0; }\n.ic-ecs-label {\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.05em;\n\ttext-transform: uppercase;\n\tcolor: var(--ic-ecs-fg-muted);\n}\n.ic-ecs-label-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin-bottom: 4px;\n}\n.ic-ecs-sub-label {\n\tfont-size: 9px;\n\tfont-weight: 600;\n\tletter-spacing: 0.05em;\n\ttext-transform: uppercase;\n\tcolor: var(--ic-ecs-fg-faint);\n\tmargin: 8px 0 4px;\n}\n.ic-ecs-row {\n\tdisplay: flex;\n\tgap: 6px;\n\talign-items: center;\n\tmargin-top: 4px;\n}\n.ic-ecs-select {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tbackground: var(--ic-ecs-bg-elev);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tpadding: 4px 6px;\n\tfont: inherit;\n}\n.ic-ecs-input {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tbackground: var(--ic-ecs-bg-elev);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tpadding: 2px 6px;\n\tfont: inherit;\n}\n.ic-ecs-input:focus { outline: 1px solid var(--ic-ecs-accent); outline-offset: -1px; }\n.ic-ecs-input-json.is-error { border-color: var(--ic-ecs-danger); }\n.ic-ecs-checkbox {\n\taccent-color: var(--ic-ecs-accent);\n}\n.ic-ecs-btn {\n\tbackground: var(--ic-ecs-bg-elev);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tpadding: 3px 8px;\n\tfont: inherit;\n\tcursor: pointer;\n\twhite-space: nowrap;\n}\n.ic-ecs-btn:hover { background: var(--ic-ecs-border); }\n.ic-ecs-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n.ic-ecs-btn-primary {\n\tbackground: var(--ic-ecs-accent);\n\tcolor: var(--ic-ecs-accent-fg);\n\tborder-color: var(--ic-ecs-accent);\n}\n.ic-ecs-btn-primary:hover { filter: brightness(1.08); background: var(--ic-ecs-accent); }\n.ic-ecs-btn-danger { color: var(--ic-ecs-danger); }\n.ic-ecs-btn-sm { padding: 0 6px; font-size: 11px; }\n.ic-ecs-check {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 10px;\n\tcolor: var(--ic-ecs-fg-muted);\n\tcursor: pointer;\n}\n.ic-ecs-list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmax-height: 200px;\n\toverflow-y: auto;\n\tmargin-top: 4px;\n}\n.ic-ecs-empty {\n\tcolor: var(--ic-ecs-fg-faint);\n\tfont-style: italic;\n\tpadding: 4px 0;\n}\n.ic-ecs-entity-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n\tpadding: 2px 4px;\n\tborder-radius: 4px;\n}\n.ic-ecs-entity-row.is-focused { background: var(--ic-ecs-focus-bg); }\n.ic-ecs-entity-btn {\n\tflex: 1 1 auto;\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tbackground: transparent;\n\tborder: 0;\n\tcolor: inherit;\n\tcursor: pointer;\n\ttext-align: left;\n\tpadding: 2px 0;\n\tfont: inherit;\n\tmin-width: 0;\n}\n.ic-ecs-entity-id {\n\tcolor: var(--ic-ecs-fg-muted);\n\tfont-variant-numeric: tabular-nums;\n}\n.ic-ecs-entity-label {\n\tcolor: var(--ic-ecs-fg);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.ic-ecs-inspect-head {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin-bottom: 4px;\n}\n.ic-ecs-components {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n.ic-ecs-component {\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tbackground: var(--ic-ecs-bg-elev);\n}\n.ic-ecs-component-head {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 2px 6px;\n}\n.ic-ecs-toggle {\n\tbackground: transparent;\n\tborder: 0;\n\tcolor: inherit;\n\tcursor: pointer;\n\tfont: inherit;\n\tfont-weight: 600;\n\tpadding: 2px 0;\n}\n.ic-ecs-fields {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n\tpadding: 4px 6px 6px;\n\tborder-top: 1px dashed var(--ic-ecs-border);\n}\n.ic-ecs-field {\n\tdisplay: grid;\n\tgrid-template-columns: 70px 1fr;\n\talign-items: center;\n\tgap: 6px;\n}\n.ic-ecs-field-label {\n\tcolor: var(--ic-ecs-fg-muted);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.ic-ecs-tags {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n}\n.ic-ecs-tag {\n\tbackground: transparent;\n\tcolor: var(--ic-ecs-fg-faint);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 999px;\n\tpadding: 2px 8px;\n\tfont: inherit;\n\tcursor: pointer;\n}\n.ic-ecs-tag:hover { color: var(--ic-ecs-fg); }\n.ic-ecs-tag.is-active {\n\tbackground: var(--ic-ecs-accent);\n\tcolor: var(--ic-ecs-accent-fg);\n\tborder-color: var(--ic-ecs-accent);\n}\n`;\n\nfunction StyleTag() {\n\tuseEffect(() => {\n\t\tif (typeof document === 'undefined') return;\n\t\tif (document.getElementById(STYLE_ID)) return;\n\t\tconst el = document.createElement('style');\n\t\tel.id = STYLE_ID;\n\t\tel.textContent = CSS;\n\t\tdocument.head.appendChild(el);\n\t}, []);\n\treturn null;\n}\n"],"mappings":";;;;;;;;;;AA4BA,SAAgB,YAAY,EAAE,QAAQ,WAA6B;AAClE,KAAI,OACH,QACC,iBAAA,GAAA,kBAAA,KAACA,YAAAA,gBAAD;EAAgB,OAAO;YACtB,iBAAA,GAAA,kBAAA,KAAC,kBAAD,EAA2B,SAAW,CAAA;EACtB,CAAA;AAGnB,QAAO,iBAAA,GAAA,kBAAA,KAAC,kBAAD,EAA2B,SAAW,CAAA;;AAG9C,SAAS,iBAAiB,EAAE,WAAqC;CAChE,MAAM,SAASC,YAAAA,iBAAiB;CAChC,MAAM,cAAcC,YAAAA,gBAAgB;CACpC,MAAM,cAAcC,YAAAA,kBAAkBC,YAAAA,SAAS;CAC/C,MAAM,WAAA,GAAA,MAAA,eAAwB,OAAO,YAAY,EAAE,CAAC,OAAO,CAAC;CAE5D,MAAM,CAAC,SAAS,eAAA,GAAA,MAAA,UAAuB,MAAM;CAC7C,MAAM,CAAC,WAAW,iBAAA,GAAA,MAAA,UAAyB,QAAQ,IAAI,QAAQ,GAAG;CAClE,MAAM,CAAC,eAAe,qBAAA,GAAA,MAAA,UAA8C,KAAK;CAEzE,MAAM,UAAU,YAAY,MAAM;CAElC,MAAM,cAAA,GAAA,MAAA,eAA2B;AAChC,MAAI,QAAS,QAAO;AACpB,SAAO,YAAY,QAAQ,OAAO,OAAO,IAAI,IAAIC,YAAAA,OAAO,CAAC;IACvD;EAAC;EAAQ;EAAa;EAAQ,CAAC;CAElC,MAAM,oBAAoB;AACzB,MAAI,CAAC,UAAW;AAEhB,mBADW,OAAO,oBAAoB,UACnB,CAAC;;AAGrB,QACC,iBAAA,GAAA,kBAAA,MAAC,OAAD;EAAK,WAAU;YAAf;GACC,iBAAA,GAAA,kBAAA,KAAC,UAAD,EAAY,CAAA;GAEZ,iBAAA,GAAA,kBAAA,MAAC,OAAD;IAAK,WAAU;cAAf,CACC,iBAAA,GAAA,kBAAA,KAAC,QAAD;KAAM,WAAU;eAAe;KAAiB,CAAA,EAC/C,WACA,iBAAA,GAAA,kBAAA,KAAC,UAAD;KAAQ,MAAK;KAAS,WAAU;KAAe,SAAS;KAAS,OAAM;eAAQ;KAEtE,CAAA,CAEL;;GAEN,iBAAA,GAAA,kBAAA,MAAC,OAAD;IAAK,WAAU;cAAf,CACC,iBAAA,GAAA,kBAAA,KAAC,OAAD;KAAK,WAAU;eAAe;KAAkB,CAAA,EAChD,iBAAA,GAAA,kBAAA,MAAC,OAAD;KAAK,WAAU;eAAf,CACC,iBAAA,GAAA,kBAAA,KAAC,UAAD;MACC,WAAU;MACV,OAAO;MACP,WAAW,MAAM,aAAa,EAAE,OAAO,MAAM;gBAE5C,QAAQ,KAAK,MACb,iBAAA,GAAA,kBAAA,KAAC,UAAD;OAAqB,OAAO,EAAE;iBAC5B,EAAE;OACK,EAFI,EAAE,KAEN,CACR;MACM,CAAA,EACT,iBAAA,GAAA,kBAAA,KAAC,UAAD;MACC,MAAK;MACL,WAAU;MACV,SAAS;MACT,UAAU,CAAC;gBACX;MAEQ,CAAA,CACJ;OACD;;GAEN,iBAAA,GAAA,kBAAA,MAAC,OAAD;IAAK,WAAU;cAAf,CACC,iBAAA,GAAA,kBAAA,MAAC,OAAD;KAAK,WAAU;eAAf,CACC,iBAAA,GAAA,kBAAA,MAAC,QAAD;MAAM,WAAU;gBAAhB;OAA+B;OACnB,WAAW;OACrB,UAAU,KAAK;OAAW;OACrB;SACP,iBAAA,GAAA,kBAAA,MAAC,SAAD;MAAO,WAAU;gBAAjB,CACC,iBAAA,GAAA,kBAAA,KAAC,SAAD;OACC,MAAK;OACL,SAAS;OACT,WAAW,MAAM,WAAW,EAAE,OAAO,QAAQ;OAC5C,CAAA,EAAA,WAEK;QACH;QACN,iBAAA,GAAA,kBAAA,MAAC,OAAD;KAAK,WAAU;eAAf,CACE,WAAW,KAAK,OAChB,iBAAA,GAAA,kBAAA,KAAC,WAAD;MAEC,QAAQ;MACR,SAAS,OAAO;MAChB,eAAe,iBAAiB,GAAG;MAClC,EAJI,GAIJ,CACD,EACD,WAAW,WAAW,KAAK,iBAAA,GAAA,kBAAA,KAAC,OAAD;MAAK,WAAU;gBAAe;MAAiB,CAAA,CACtE;OACD;;GAEL,YAAY,QAAQ,YAAY,KAAA,KAAa,OAAO,MAAM,aAAa,QAAQ,IAC/E,iBAAA,GAAA,kBAAA,KAAC,iBAAD,EAAiB,QAAQ,SAAW,CAAA;GAEhC;;;AAIR,SAAS,UAAU,EAClB,QACA,SACA,WAKE;CACF,MAAM,SAASJ,YAAAA,iBAAiB;CAEhC,MAAM,QADSK,YAAAA,aAAa,QAAQD,YAAAA,OAChB,EAAE,QAAQ;AAE9B,QACC,iBAAA,GAAA,kBAAA,MAAC,OAAD;EAAK,WAAW,qBAAqB,UAAU,eAAe;YAA9D,CACC,iBAAA,GAAA,kBAAA,MAAC,UAAD;GAAQ,MAAK;GAAS,WAAU;GAA6B;aAA7D,CACC,iBAAA,GAAA,kBAAA,MAAC,QAAD;IAAM,WAAU;cAAhB,CAAmC,KAAE,OAAc;OACnD,iBAAA,GAAA,kBAAA,KAAC,QAAD;IAAM,WAAU;cAAuB;IAAa,CAAA,CAC5C;MACT,iBAAA,GAAA,kBAAA,KAAC,UAAD;GACC,MAAK;GACL,WAAU;GACV,eAAe;AACd,WAAO,cAAc,OAAO;;GAE7B,OAAM;aACN;GAEQ,CAAA,CACJ;;;AAIR,SAAS,gBAAgB,EAAE,UAAgC;CAC1D,MAAM,SAASJ,YAAAA,iBAAiB;CAChC,MAAM,aAAaM,YAAAA,oBAAoB,OAAO;CAC9C,MAAM,OAAOC,YAAAA,cAAc,OAAO;CAClC,MAAM,uBAAuBC,YAAAA,yBAAyB;CACtD,MAAM,iBAAiBC,YAAAA,mBAAmB;CAC1C,MAAM,SAASJ,YAAAA,aAAa,QAAQD,YAAAA,OAAO;CAE3C,MAAM,oBAAA,GAAA,MAAA,eAAiC;EACtC,MAAM,UAAU,IAAI,IAAI,WAAW,KAAK,MAAM,EAAE,KAAK,CAAC;AACtD,SAAO,qBAAqB,QAAQ,MAAM,CAAC,QAAQ,IAAI,EAAE,KAAK,CAAC;IAC7D,CAAC,YAAY,qBAAqB,CAAC;CAEtC,MAAM,CAAC,gBAAgB,sBAAA,GAAA,MAAA,UAA8B,GAAG;AACxD,EAAA,GAAA,MAAA,iBAAgB;AACf,oBAAkB,iBAAiB,IAAI,QAAQ,GAAG;IAChD,CAAC,iBAAiB,CAAC;CAEtB,MAAM,2BAA2B;EAChC,MAAM,OAAO,iBAAiB,MAAM,MAAM,EAAE,SAAS,eAAe;AACpE,MAAI,CAAC,KAAM;AACX,SAAO,aAAa,QAAQ,KAAK;;AAGlC,QACC,iBAAA,GAAA,kBAAA,MAAC,OAAD;EAAK,WAAU;YAAf;GACC,iBAAA,GAAA,kBAAA,KAAC,OAAD;IAAK,WAAU;cACd,iBAAA,GAAA,kBAAA,MAAC,QAAD;KAAM,WAAU;eAAhB;MAA+B;MACvB,iBAAA,GAAA,kBAAA,MAAC,QAAD;OAAM,WAAU;iBAAhB,CAAmC,KAAE,OAAc;;MACzD,QAAQ,QAAQ,iBAAA,GAAA,kBAAA,MAAC,QAAD;OAAM,WAAU;iBAAhB,CAAsC,OAAI,OAAO,KAAY;;MACxE;;IACF,CAAA;GAEN,iBAAA,GAAA,kBAAA,KAAC,OAAD;IAAK,WAAU;cAAmB;IAAgB,CAAA;GAClD,iBAAA,GAAA,kBAAA,KAAC,OAAD;IAAK,WAAU;cACb,WAAW,KAAK,SAChB,iBAAA,GAAA,kBAAA,KAAC,iBAAD;KAAyC;KAAc;KAAQ,EAAzC,KAAK,KAAoC,CAC9D;IACG,CAAA;GAEL,iBAAiB,SAAS,KAC1B,iBAAA,GAAA,kBAAA,MAAC,OAAD;IAAK,WAAU;IAAa,OAAO,EAAE,WAAW,GAAG;cAAnD,CACC,iBAAA,GAAA,kBAAA,KAAC,UAAD;KACC,WAAU;KACV,OAAO;KACP,WAAW,MAAM,kBAAkB,EAAE,OAAO,MAAM;eAEjD,iBAAiB,KAAK,MACtB,iBAAA,GAAA,kBAAA,KAAC,UAAD;MAAqB,OAAO,EAAE;gBAC5B,EAAE;MACK,EAFI,EAAE,KAEN,CACR;KACM,CAAA,EACT,iBAAA,GAAA,kBAAA,KAAC,UAAD;KAAQ,MAAK;KAAS,WAAU;KAAa,SAAS;eAAoB;KAEjE,CAAA,CACJ;;GAGP,iBAAA,GAAA,kBAAA,KAAC,OAAD;IAAK,WAAU;cAAmB;IAAU,CAAA;GAC5C,iBAAA,GAAA,kBAAA,KAAC,OAAD;IAAK,WAAU;cACb,eAAe,KAAK,SACpB,iBAAA,GAAA,kBAAA,KAAC,SAAD;KAES;KACF;KACN,QAAQ,KAAK,MAAM,MAAM,EAAE,SAAS,KAAK,KAAK;KAC7C,EAJI,KAAK,KAIT,CACD;IACG,CAAA;GACD;;;AAIR,SAAS,gBAAgB,EAAE,QAAQ,QAAmD;CACrF,MAAM,SAASJ,YAAAA,iBAAiB;CAChC,MAAM,QAAQK,YAAAA,aAAa,QAAQ,KAAK;CACxC,MAAM,CAAC,WAAW,iBAAA,GAAA,MAAA,UAAyB,MAAM;AAEjD,KAAI,CAAC,MAAO,QAAO;CAEnB,MAAM,eAAe,KAAK,SAAS;AAEnC,QACC,iBAAA,GAAA,kBAAA,MAAC,OAAD;EAAK,WAAU;YAAf,CACC,iBAAA,GAAA,kBAAA,MAAC,OAAD;GAAK,WAAU;aAAf,CACC,iBAAA,GAAA,kBAAA,MAAC,UAAD;IAAQ,MAAK;IAAS,WAAU;IAAgB,eAAe,cAAc,MAAM,CAAC,EAAE;cAAtF;KACE,YAAY,MAAM;KAAI;KAAE,KAAK;KACtB;OACT,iBAAA,GAAA,kBAAA,KAAC,UAAD;IACC,MAAK;IACL,WAAU;IACV,eAAe,OAAO,gBAAgB,QAAQ,KAAK;IACnD,OAAM;cACN;IAEQ,CAAA,CACJ;MACL,CAAC,aACD,iBAAA,GAAA,kBAAA,KAAC,OAAD;GAAK,WAAU;aACb,eACA,iBAAA,GAAA,kBAAA,KAAC,kBAAD;IAA0B;IAAe;IAA8C,CAAA,GAEvF,iBAAA,GAAA,kBAAA,KAAC,qBAAD;IAA6B;IAAc;IAAa;IAAS,CAAA;GAE7D,CAAA,CAEF;;;AAIR,SAAS,oBAAuB,EAC/B,QACA,MACA,SAKE;CACF,MAAM,SAASL,YAAAA,iBAAiB;CAChC,MAAM,WAAW,KAAK;CACtB,MAAM,MAAM;AAGZ,QACC,iBAAA,GAAA,kBAAA,KAAA,kBAAA,UAAA,EAAA,UAHY,OAAO,KAAK,SAIlB,CAAC,KAAK,QACV,iBAAA,GAAA,kBAAA,KAAC,UAAD;EAEC,OAAO;EACP,OAAO,IAAI;EACX,WAAW,SAAS;AACnB,UAAO,IAAI,QAAQ,MAAM,GAAG,MAAM,MAAM,CAAe;;EAEvD,EANI,IAMJ,CACD,EACA,CAAA;;AAIL,SAAS,iBAAiB,EACzB,QACA,SAIE;CACF,MAAM,SAASA,YAAAA,iBAAiB;CAChC,MAAM,OAAO,MAAM,QAAQ,EAAE;CAC7B,MAAM,OAAO,OAAO,KAAK,KAAK;AAE9B,KAAI,KAAK,WAAW,EACnB,QAAO,iBAAA,GAAA,kBAAA,KAAC,OAAD;EAAK,WAAU;YAAe;EAAiB,CAAA;AAGvD,QACC,iBAAA,GAAA,kBAAA,KAAA,kBAAA,UAAA,EAAA,UACE,KAAK,KAAK,QACV,iBAAA,GAAA,kBAAA,KAAC,UAAD;EAEC,OAAO;EACP,OAAO,KAAK;EACZ,WAAW,SAAS;AACnB,UAAO,IAAI,QAAQU,YAAAA,YAAY,EAAE,MAAM;IAAE,GAAG;KAAO,MAAM;IAAM,EAAE,CAAC;;EAElE,EANI,IAMJ,CACD,EACA,CAAA;;AAIL,SAAS,SAAS,EACjB,OACA,OACA,YAKE;AACF,QACC,iBAAA,GAAA,kBAAA,MAAC,OAAD;EAAK,WAAU;YAAf,CACC,iBAAA,GAAA,kBAAA,KAAC,QAAD;GAAM,WAAU;aAAsB;GAAa,CAAA,EACnD,iBAAA,GAAA,kBAAA,KAAC,YAAD;GAAmB;GAAiB;GAAY,CAAA,CAC3C;;;AAIR,SAAS,WAAW,EAAE,OAAO,YAAmE;AAC/F,KAAI,OAAO,UAAU,SACpB,QACC,iBAAA,GAAA,kBAAA,KAAC,SAAD;EACC,MAAK;EACL,WAAU;EACH;EACP,WAAW,MAAM;GAChB,MAAM,IAAI,OAAO,WAAW,EAAE,OAAO,MAAM;AAC3C,OAAI,CAAC,OAAO,MAAM,EAAE,CAAE,UAAS,EAAE;;EAEjC,CAAA;AAGJ,KAAI,OAAO,UAAU,UACpB,QACC,iBAAA,GAAA,kBAAA,KAAC,SAAD;EACC,MAAK;EACL,WAAU;EACV,SAAS;EACT,WAAW,MAAM,SAAS,EAAE,OAAO,QAAQ;EAC1C,CAAA;AAGJ,KAAI,OAAO,UAAU,SACpB,QACC,iBAAA,GAAA,kBAAA,KAAC,SAAD;EACC,MAAK;EACL,WAAU;EACH;EACP,WAAW,MAAM,SAAS,EAAE,OAAO,MAAM;EACxC,CAAA;AAGJ,QAAO,iBAAA,GAAA,kBAAA,KAAC,WAAD;EAAkB;EAAiB;EAAY,CAAA;;AAGvD,SAAS,UAAU,EAAE,OAAO,YAAmE;CAE9F,MAAM,CAAC,MAAM,YAAA,GAAA,MAAA,UADM,KAAK,UAAU,MACS,CAAC;CAC5C,MAAM,CAAC,OAAO,aAAA,GAAA,MAAA,UAAqB,MAAM;AAEzC,EAAA,GAAA,MAAA,iBAAgB;AACf,UAAQ,KAAK,UAAU,MAAM,CAAC;AAC9B,WAAS,MAAM;IACb,CAAC,MAAM,CAAC;CAEX,MAAM,eAAe;AACpB,MAAI;GACH,MAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,YAAS,MAAM;AACf,YAAS,OAAO;UACT;AACP,YAAS,KAAK;;;AAIhB,QACC,iBAAA,GAAA,kBAAA,KAAC,SAAD;EACC,MAAK;EACL,WAAW,kCAAkC,QAAQ,aAAa;EAClE,OAAO;EACP,WAAW,MAAM,QAAQ,EAAE,OAAO,MAAM;EACxC,QAAQ;EACR,YAAY,MAAM;AACjB,OAAI,EAAE,QAAQ,QAAS,SAAQ;;EAE/B,CAAA;;AAIJ,SAAS,QAAQ,EAAE,QAAQ,MAAM,UAAgE;CAChG,MAAM,SAASV,YAAAA,iBAAiB;AAChC,QACC,iBAAA,GAAA,kBAAA,KAAC,UAAD;EACC,MAAK;EACL,WAAW,cAAc,SAAS,cAAc;EAChD,eAAe;AACd,OAAI,OAAQ,QAAO,UAAU,QAAQ,KAAK;OACrC,QAAO,OAAO,QAAQ,KAAK;;YAGhC,KAAK;EACE,CAAA;;AAMX,MAAM,WAAW;AACjB,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsQZ,SAAS,WAAW;AACnB,EAAA,GAAA,MAAA,iBAAgB;AACf,MAAI,OAAO,aAAa,YAAa;AACrC,MAAI,SAAS,eAAe,SAAS,CAAE;EACvC,MAAM,KAAK,SAAS,cAAc,QAAQ;AAC1C,KAAG,KAAK;AACR,KAAG,cAAc;AACjB,WAAS,KAAK,YAAY,GAAG;IAC3B,EAAE,CAAC;AACN,QAAO"}
|
package/dist/devtools.d.cts
CHANGED
package/dist/devtools.d.mts
CHANGED
package/dist/devtools.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Y as Selected, a as useEntityTags, c as useRegisteredTags, d as useTaggedEntities, f as EngineProvider, i as useEntityComponents, it as WidgetData, nt as Widget, p as useLayoutEngine, r as useComponent, s as useRegisteredComponents, t as useAllEntities } from "./ecs-O6AR7iFp.mjs";
|
|
2
2
|
import { useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
//#region src/devtools/EcsDevtools.tsx
|
|
@@ -32,9 +32,7 @@ function EcsDevtoolsInner({ onClose }) {
|
|
|
32
32
|
]);
|
|
33
33
|
const handleSpawn = () => {
|
|
34
34
|
if (!spawnType) return;
|
|
35
|
-
|
|
36
|
-
engine.markDirty();
|
|
37
|
-
setManualFocusId(id);
|
|
35
|
+
setManualFocusId(engine.spawnAtCameraCenter(spawnType));
|
|
38
36
|
};
|
|
39
37
|
return /* @__PURE__ */ jsxs("div", {
|
|
40
38
|
className: "ic-ecs-root",
|
|
@@ -134,7 +132,6 @@ function EntityRow({ entity, focused, onClick }) {
|
|
|
134
132
|
className: "ic-ecs-btn ic-ecs-btn-danger ic-ecs-btn-sm",
|
|
135
133
|
onClick: () => {
|
|
136
134
|
engine.destroyEntity(entity);
|
|
137
|
-
engine.markDirty();
|
|
138
135
|
},
|
|
139
136
|
title: "Destroy entity",
|
|
140
137
|
children: "×"
|
package/dist/devtools.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devtools.mjs","names":[],"sources":["../src/devtools/EcsDevtools.tsx"],"sourcesContent":["import type { ComponentType, EntityId, TagType } from '@jamesyong42/reactive-ecs';\nimport { useEffect, useMemo, useState } from 'react';\nimport { Selected, Widget, WidgetData } from '../ecs/components.js';\nimport type { LayoutEngine } from '../ecs/engine/index.js';\nimport { EngineProvider, useLayoutEngine } from '../react/context/engine-context.js';\nimport {\n\tuseAllEntities,\n\tuseComponent,\n\tuseEntityComponents,\n\tuseEntityTags,\n\tuseRegisteredComponents,\n\tuseRegisteredTags,\n\tuseTaggedEntities,\n} from '../react/hooks/ecs.js';\n\ninterface EcsDevtoolsProps {\n\t/**\n\t * Engine to inspect. If omitted, reads from the nearest InfiniteCanvas context —\n\t * supply this prop when the panel is rendered outside the `<InfiniteCanvas>` subtree.\n\t */\n\tengine?: LayoutEngine;\n\tonClose?: () => void;\n}\n\n/**\n * Live ECS editor: spawn widgets, browse entities, edit components, toggle tags.\n * Ship in a dev mode or behind a feature flag — not intended for production users.\n */\nexport function EcsDevtools({ engine, onClose }: EcsDevtoolsProps) {\n\tif (engine) {\n\t\treturn (\n\t\t\t<EngineProvider value={engine}>\n\t\t\t\t<EcsDevtoolsInner onClose={onClose} />\n\t\t\t</EngineProvider>\n\t\t);\n\t}\n\treturn <EcsDevtoolsInner onClose={onClose} />;\n}\n\nfunction EcsDevtoolsInner({ onClose }: { onClose?: () => void }) {\n\tconst engine = useLayoutEngine();\n\tconst allEntities = useAllEntities();\n\tconst selectedIds = useTaggedEntities(Selected);\n\tconst widgets = useMemo(() => engine.getWidgets(), [engine]);\n\n\tconst [showAll, setShowAll] = useState(false);\n\tconst [spawnType, setSpawnType] = useState(widgets[0]?.type ?? '');\n\tconst [manualFocusId, setManualFocusId] = useState<EntityId | null>(null);\n\n\tconst focusId = selectedIds[0] ?? manualFocusId;\n\n\tconst entityList = useMemo(() => {\n\t\tif (showAll) return allEntities;\n\t\treturn allEntities.filter((id) => engine.has(id, Widget));\n\t}, [engine, allEntities, showAll]);\n\n\tconst handleSpawn = () => {\n\t\tif (!spawnType) return;\n\t\tconst id = engine.spawnAtCameraCenter(spawnType);\n\t\tengine.markDirty();\n\t\tsetManualFocusId(id);\n\t};\n\n\treturn (\n\t\t<div className=\"ic-ecs-root\">\n\t\t\t<StyleTag />\n\n\t\t\t<div className=\"ic-ecs-header\">\n\t\t\t\t<span className=\"ic-ecs-title\">ECS Editor</span>\n\t\t\t\t{onClose && (\n\t\t\t\t\t<button type=\"button\" className=\"ic-ecs-close\" onClick={onClose} title=\"Close\">\n\t\t\t\t\t\t×\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t<div className=\"ic-ecs-section\">\n\t\t\t\t<div className=\"ic-ecs-label\">Spawn widget</div>\n\t\t\t\t<div className=\"ic-ecs-row\">\n\t\t\t\t\t<select\n\t\t\t\t\t\tclassName=\"ic-ecs-select\"\n\t\t\t\t\t\tvalue={spawnType}\n\t\t\t\t\t\tonChange={(e) => setSpawnType(e.target.value)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{widgets.map((w) => (\n\t\t\t\t\t\t\t<option key={w.type} value={w.type}>\n\t\t\t\t\t\t\t\t{w.type}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</select>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"ic-ecs-btn ic-ecs-btn-primary\"\n\t\t\t\t\t\tonClick={handleSpawn}\n\t\t\t\t\t\tdisabled={!spawnType}\n\t\t\t\t\t>\n\t\t\t\t\t\t+ Spawn\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div className=\"ic-ecs-section\">\n\t\t\t\t<div className=\"ic-ecs-label-row\">\n\t\t\t\t\t<span className=\"ic-ecs-label\">\n\t\t\t\t\t\tEntities ({entityList.length}\n\t\t\t\t\t\t{showAll ? '' : ' widgets'})\n\t\t\t\t\t</span>\n\t\t\t\t\t<label className=\"ic-ecs-check\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tchecked={showAll}\n\t\t\t\t\t\t\tonChange={(e) => setShowAll(e.target.checked)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\tshow all\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"ic-ecs-list\">\n\t\t\t\t\t{entityList.map((id) => (\n\t\t\t\t\t\t<EntityRow\n\t\t\t\t\t\t\tkey={id}\n\t\t\t\t\t\t\tentity={id}\n\t\t\t\t\t\t\tfocused={id === focusId}\n\t\t\t\t\t\t\tonClick={() => setManualFocusId(id)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t\t{entityList.length === 0 && <div className=\"ic-ecs-empty\">no entities</div>}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{focusId !== null && focusId !== undefined && engine.world.entityExists(focusId) && (\n\t\t\t\t<EntityInspector entity={focusId} />\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction EntityRow({\n\tentity,\n\tfocused,\n\tonClick,\n}: {\n\tentity: EntityId;\n\tfocused: boolean;\n\tonClick: () => void;\n}) {\n\tconst engine = useLayoutEngine();\n\tconst widget = useComponent(entity, Widget);\n\tconst label = widget?.type ?? 'entity';\n\n\treturn (\n\t\t<div className={`ic-ecs-entity-row ${focused ? 'is-focused' : ''}`}>\n\t\t\t<button type=\"button\" className=\"ic-ecs-entity-btn\" onClick={onClick}>\n\t\t\t\t<span className=\"ic-ecs-entity-id\">e{entity}</span>\n\t\t\t\t<span className=\"ic-ecs-entity-label\">{label}</span>\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"ic-ecs-btn ic-ecs-btn-danger ic-ecs-btn-sm\"\n\t\t\t\tonClick={() => {\n\t\t\t\t\tengine.destroyEntity(entity);\n\t\t\t\t\tengine.markDirty();\n\t\t\t\t}}\n\t\t\t\ttitle=\"Destroy entity\"\n\t\t\t>\n\t\t\t\t×\n\t\t\t</button>\n\t\t</div>\n\t);\n}\n\nfunction EntityInspector({ entity }: { entity: EntityId }) {\n\tconst engine = useLayoutEngine();\n\tconst components = useEntityComponents(entity);\n\tconst tags = useEntityTags(entity);\n\tconst registeredComponents = useRegisteredComponents();\n\tconst registeredTags = useRegisteredTags();\n\tconst widget = useComponent(entity, Widget);\n\n\tconst absentComponents = useMemo(() => {\n\t\tconst present = new Set(components.map((c) => c.name));\n\t\treturn registeredComponents.filter((c) => !present.has(c.name));\n\t}, [components, registeredComponents]);\n\n\tconst [componentToAdd, setComponentToAdd] = useState('');\n\tuseEffect(() => {\n\t\tsetComponentToAdd(absentComponents[0]?.name ?? '');\n\t}, [absentComponents]);\n\n\tconst handleAddComponent = () => {\n\t\tconst type = absentComponents.find((c) => c.name === componentToAdd);\n\t\tif (!type) return;\n\t\tengine.addComponent(entity, type);\n\t};\n\n\treturn (\n\t\t<div className=\"ic-ecs-section\">\n\t\t\t<div className=\"ic-ecs-inspect-head\">\n\t\t\t\t<span className=\"ic-ecs-label\">\n\t\t\t\t\tEntity <span className=\"ic-ecs-entity-id\">e{entity}</span>\n\t\t\t\t\t{widget?.type && <span className=\"ic-ecs-entity-label\"> · {widget.type}</span>}\n\t\t\t\t</span>\n\t\t\t</div>\n\n\t\t\t<div className=\"ic-ecs-sub-label\">Components</div>\n\t\t\t<div className=\"ic-ecs-components\">\n\t\t\t\t{components.map((type) => (\n\t\t\t\t\t<ComponentEditor key={type.name} entity={entity} type={type} />\n\t\t\t\t))}\n\t\t\t</div>\n\n\t\t\t{absentComponents.length > 0 && (\n\t\t\t\t<div className=\"ic-ecs-row\" style={{ marginTop: 6 }}>\n\t\t\t\t\t<select\n\t\t\t\t\t\tclassName=\"ic-ecs-select\"\n\t\t\t\t\t\tvalue={componentToAdd}\n\t\t\t\t\t\tonChange={(e) => setComponentToAdd(e.target.value)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{absentComponents.map((c) => (\n\t\t\t\t\t\t\t<option key={c.name} value={c.name}>\n\t\t\t\t\t\t\t\t{c.name}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</select>\n\t\t\t\t\t<button type=\"button\" className=\"ic-ecs-btn\" onClick={handleAddComponent}>\n\t\t\t\t\t\t+ Add component\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t<div className=\"ic-ecs-sub-label\">Tags</div>\n\t\t\t<div className=\"ic-ecs-tags\">\n\t\t\t\t{registeredTags.map((type) => (\n\t\t\t\t\t<TagPill\n\t\t\t\t\t\tkey={type.name}\n\t\t\t\t\t\tentity={entity}\n\t\t\t\t\t\ttype={type}\n\t\t\t\t\t\tactive={tags.some((t) => t.name === type.name)}\n\t\t\t\t\t/>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction ComponentEditor({ entity, type }: { entity: EntityId; type: ComponentType }) {\n\tconst engine = useLayoutEngine();\n\tconst value = useComponent(entity, type);\n\tconst [collapsed, setCollapsed] = useState(false);\n\n\tif (!value) return null;\n\n\tconst isWidgetData = type.name === 'WidgetData';\n\n\treturn (\n\t\t<div className=\"ic-ecs-component\">\n\t\t\t<div className=\"ic-ecs-component-head\">\n\t\t\t\t<button type=\"button\" className=\"ic-ecs-toggle\" onClick={() => setCollapsed((c) => !c)}>\n\t\t\t\t\t{collapsed ? '▶' : '▼'} {type.name}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"ic-ecs-btn ic-ecs-btn-danger ic-ecs-btn-sm\"\n\t\t\t\t\tonClick={() => engine.removeComponent(entity, type)}\n\t\t\t\t\ttitle=\"Remove component\"\n\t\t\t\t>\n\t\t\t\t\t×\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t{!collapsed && (\n\t\t\t\t<div className=\"ic-ecs-fields\">\n\t\t\t\t\t{isWidgetData ? (\n\t\t\t\t\t\t<WidgetDataEditor entity={entity} value={value as { data: Record<string, unknown> }} />\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<GenericFieldsEditor entity={entity} type={type} value={value} />\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction GenericFieldsEditor<T>({\n\tentity,\n\ttype,\n\tvalue,\n}: {\n\tentity: EntityId;\n\ttype: ComponentType<T>;\n\tvalue: T;\n}) {\n\tconst engine = useLayoutEngine();\n\tconst defaults = type.defaults as Record<string, unknown>;\n\tconst val = value as Record<string, unknown>;\n\tconst keys = Object.keys(defaults);\n\n\treturn (\n\t\t<>\n\t\t\t{keys.map((key) => (\n\t\t\t\t<FieldRow\n\t\t\t\t\tkey={key}\n\t\t\t\t\tlabel={key}\n\t\t\t\t\tvalue={val[key]}\n\t\t\t\t\tonChange={(next) => {\n\t\t\t\t\t\tengine.set(entity, type, { [key]: next } as Partial<T>);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</>\n\t);\n}\n\nfunction WidgetDataEditor({\n\tentity,\n\tvalue,\n}: {\n\tentity: EntityId;\n\tvalue: { data: Record<string, unknown> };\n}) {\n\tconst engine = useLayoutEngine();\n\tconst data = value.data ?? {};\n\tconst keys = Object.keys(data);\n\n\tif (keys.length === 0) {\n\t\treturn <div className=\"ic-ecs-empty\">(no fields)</div>;\n\t}\n\n\treturn (\n\t\t<>\n\t\t\t{keys.map((key) => (\n\t\t\t\t<FieldRow\n\t\t\t\t\tkey={key}\n\t\t\t\t\tlabel={key}\n\t\t\t\t\tvalue={data[key]}\n\t\t\t\t\tonChange={(next) => {\n\t\t\t\t\t\tengine.set(entity, WidgetData, { data: { ...data, [key]: next } });\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</>\n\t);\n}\n\nfunction FieldRow({\n\tlabel,\n\tvalue,\n\tonChange,\n}: {\n\tlabel: string;\n\tvalue: unknown;\n\tonChange: (next: unknown) => void;\n}) {\n\treturn (\n\t\t<div className=\"ic-ecs-field\">\n\t\t\t<span className=\"ic-ecs-field-label\">{label}</span>\n\t\t\t<FieldInput value={value} onChange={onChange} />\n\t\t</div>\n\t);\n}\n\nfunction FieldInput({ value, onChange }: { value: unknown; onChange: (next: unknown) => void }) {\n\tif (typeof value === 'number') {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"number\"\n\t\t\t\tclassName=\"ic-ecs-input\"\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => {\n\t\t\t\t\tconst n = Number.parseFloat(e.target.value);\n\t\t\t\t\tif (!Number.isNaN(n)) onChange(n);\n\t\t\t\t}}\n\t\t\t/>\n\t\t);\n\t}\n\tif (typeof value === 'boolean') {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"checkbox\"\n\t\t\t\tclassName=\"ic-ecs-checkbox\"\n\t\t\t\tchecked={value}\n\t\t\t\tonChange={(e) => onChange(e.target.checked)}\n\t\t\t/>\n\t\t);\n\t}\n\tif (typeof value === 'string') {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"text\"\n\t\t\t\tclassName=\"ic-ecs-input\"\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => onChange(e.target.value)}\n\t\t\t/>\n\t\t);\n\t}\n\treturn <JsonInput value={value} onChange={onChange} />;\n}\n\nfunction JsonInput({ value, onChange }: { value: unknown; onChange: (next: unknown) => void }) {\n\tconst serialized = JSON.stringify(value);\n\tconst [text, setText] = useState(serialized);\n\tconst [error, setError] = useState(false);\n\n\tuseEffect(() => {\n\t\tsetText(JSON.stringify(value));\n\t\tsetError(false);\n\t}, [value]);\n\n\tconst commit = () => {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(text);\n\t\t\tsetError(false);\n\t\t\tonChange(parsed);\n\t\t} catch {\n\t\t\tsetError(true);\n\t\t}\n\t};\n\n\treturn (\n\t\t<input\n\t\t\ttype=\"text\"\n\t\t\tclassName={`ic-ecs-input ic-ecs-input-json ${error ? 'is-error' : ''}`}\n\t\t\tvalue={text}\n\t\t\tonChange={(e) => setText(e.target.value)}\n\t\t\tonBlur={commit}\n\t\t\tonKeyDown={(e) => {\n\t\t\t\tif (e.key === 'Enter') commit();\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction TagPill({ entity, type, active }: { entity: EntityId; type: TagType; active: boolean }) {\n\tconst engine = useLayoutEngine();\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclassName={`ic-ecs-tag ${active ? 'is-active' : ''}`}\n\t\t\tonClick={() => {\n\t\t\t\tif (active) engine.removeTag(entity, type);\n\t\t\t\telse engine.addTag(entity, type);\n\t\t\t}}\n\t\t>\n\t\t\t{type.name}\n\t\t</button>\n\t);\n}\n\n// Scoped styles, inlined once on mount. Dark-mode-aware via prefers-color-scheme\n// and a parent `.dark` class (matches the playground's toggle convention).\nconst STYLE_ID = 'ic-ecs-devtools-style';\nconst CSS = `\n.ic-ecs-root {\n\t--ic-ecs-bg: rgba(255, 255, 255, 0.96);\n\t--ic-ecs-bg-elev: #f7f7f7;\n\t--ic-ecs-fg: #1a1a1a;\n\t--ic-ecs-fg-muted: #6b7280;\n\t--ic-ecs-fg-faint: #9ca3af;\n\t--ic-ecs-border: #e5e7eb;\n\t--ic-ecs-accent: #0d99ff;\n\t--ic-ecs-accent-fg: #ffffff;\n\t--ic-ecs-danger: #dc2626;\n\t--ic-ecs-focus-bg: rgba(13, 153, 255, 0.12);\n\tposition: absolute;\n\ttop: 4rem;\n\tright: 1rem;\n\tz-index: 50;\n\twidth: 360px;\n\tmax-height: calc(100vh - 6rem);\n\toverflow-y: auto;\n\tbackground: var(--ic-ecs-bg);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 8px;\n\tbox-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);\n\tbackdrop-filter: blur(6px);\n\tfont-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n\tfont-size: 11px;\n\tline-height: 1.4;\n}\n.dark .ic-ecs-root,\n:root[data-theme=\"dark\"] .ic-ecs-root {\n\t--ic-ecs-bg: rgba(23, 23, 23, 0.96);\n\t--ic-ecs-bg-elev: #1f1f1f;\n\t--ic-ecs-fg: #e5e5e5;\n\t--ic-ecs-fg-muted: #a1a1aa;\n\t--ic-ecs-fg-faint: #525252;\n\t--ic-ecs-border: #2a2a2a;\n\t--ic-ecs-focus-bg: rgba(13, 153, 255, 0.18);\n}\n.ic-ecs-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--ic-ecs-border);\n}\n.ic-ecs-title {\n\tfont-weight: 600;\n\tfont-size: 12px;\n}\n.ic-ecs-close {\n\tbackground: transparent;\n\tborder: 0;\n\tcolor: var(--ic-ecs-fg-muted);\n\tcursor: pointer;\n\tfont-size: 16px;\n\tline-height: 1;\n\tpadding: 0 4px;\n}\n.ic-ecs-close:hover { color: var(--ic-ecs-fg); }\n.ic-ecs-section {\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--ic-ecs-border);\n}\n.ic-ecs-section:last-child { border-bottom: 0; }\n.ic-ecs-label {\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.05em;\n\ttext-transform: uppercase;\n\tcolor: var(--ic-ecs-fg-muted);\n}\n.ic-ecs-label-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin-bottom: 4px;\n}\n.ic-ecs-sub-label {\n\tfont-size: 9px;\n\tfont-weight: 600;\n\tletter-spacing: 0.05em;\n\ttext-transform: uppercase;\n\tcolor: var(--ic-ecs-fg-faint);\n\tmargin: 8px 0 4px;\n}\n.ic-ecs-row {\n\tdisplay: flex;\n\tgap: 6px;\n\talign-items: center;\n\tmargin-top: 4px;\n}\n.ic-ecs-select {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tbackground: var(--ic-ecs-bg-elev);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tpadding: 4px 6px;\n\tfont: inherit;\n}\n.ic-ecs-input {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tbackground: var(--ic-ecs-bg-elev);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tpadding: 2px 6px;\n\tfont: inherit;\n}\n.ic-ecs-input:focus { outline: 1px solid var(--ic-ecs-accent); outline-offset: -1px; }\n.ic-ecs-input-json.is-error { border-color: var(--ic-ecs-danger); }\n.ic-ecs-checkbox {\n\taccent-color: var(--ic-ecs-accent);\n}\n.ic-ecs-btn {\n\tbackground: var(--ic-ecs-bg-elev);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tpadding: 3px 8px;\n\tfont: inherit;\n\tcursor: pointer;\n\twhite-space: nowrap;\n}\n.ic-ecs-btn:hover { background: var(--ic-ecs-border); }\n.ic-ecs-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n.ic-ecs-btn-primary {\n\tbackground: var(--ic-ecs-accent);\n\tcolor: var(--ic-ecs-accent-fg);\n\tborder-color: var(--ic-ecs-accent);\n}\n.ic-ecs-btn-primary:hover { filter: brightness(1.08); background: var(--ic-ecs-accent); }\n.ic-ecs-btn-danger { color: var(--ic-ecs-danger); }\n.ic-ecs-btn-sm { padding: 0 6px; font-size: 11px; }\n.ic-ecs-check {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 10px;\n\tcolor: var(--ic-ecs-fg-muted);\n\tcursor: pointer;\n}\n.ic-ecs-list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmax-height: 200px;\n\toverflow-y: auto;\n\tmargin-top: 4px;\n}\n.ic-ecs-empty {\n\tcolor: var(--ic-ecs-fg-faint);\n\tfont-style: italic;\n\tpadding: 4px 0;\n}\n.ic-ecs-entity-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n\tpadding: 2px 4px;\n\tborder-radius: 4px;\n}\n.ic-ecs-entity-row.is-focused { background: var(--ic-ecs-focus-bg); }\n.ic-ecs-entity-btn {\n\tflex: 1 1 auto;\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tbackground: transparent;\n\tborder: 0;\n\tcolor: inherit;\n\tcursor: pointer;\n\ttext-align: left;\n\tpadding: 2px 0;\n\tfont: inherit;\n\tmin-width: 0;\n}\n.ic-ecs-entity-id {\n\tcolor: var(--ic-ecs-fg-muted);\n\tfont-variant-numeric: tabular-nums;\n}\n.ic-ecs-entity-label {\n\tcolor: var(--ic-ecs-fg);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.ic-ecs-inspect-head {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin-bottom: 4px;\n}\n.ic-ecs-components {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n.ic-ecs-component {\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tbackground: var(--ic-ecs-bg-elev);\n}\n.ic-ecs-component-head {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 2px 6px;\n}\n.ic-ecs-toggle {\n\tbackground: transparent;\n\tborder: 0;\n\tcolor: inherit;\n\tcursor: pointer;\n\tfont: inherit;\n\tfont-weight: 600;\n\tpadding: 2px 0;\n}\n.ic-ecs-fields {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n\tpadding: 4px 6px 6px;\n\tborder-top: 1px dashed var(--ic-ecs-border);\n}\n.ic-ecs-field {\n\tdisplay: grid;\n\tgrid-template-columns: 70px 1fr;\n\talign-items: center;\n\tgap: 6px;\n}\n.ic-ecs-field-label {\n\tcolor: var(--ic-ecs-fg-muted);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.ic-ecs-tags {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n}\n.ic-ecs-tag {\n\tbackground: transparent;\n\tcolor: var(--ic-ecs-fg-faint);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 999px;\n\tpadding: 2px 8px;\n\tfont: inherit;\n\tcursor: pointer;\n}\n.ic-ecs-tag:hover { color: var(--ic-ecs-fg); }\n.ic-ecs-tag.is-active {\n\tbackground: var(--ic-ecs-accent);\n\tcolor: var(--ic-ecs-accent-fg);\n\tborder-color: var(--ic-ecs-accent);\n}\n`;\n\nfunction StyleTag() {\n\tuseEffect(() => {\n\t\tif (typeof document === 'undefined') return;\n\t\tif (document.getElementById(STYLE_ID)) return;\n\t\tconst el = document.createElement('style');\n\t\tel.id = STYLE_ID;\n\t\tel.textContent = CSS;\n\t\tdocument.head.appendChild(el);\n\t}, []);\n\treturn null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"devtools.mjs","names":[],"sources":["../src/devtools/EcsDevtools.tsx"],"sourcesContent":["import type { ComponentType, EntityId, TagType } from '@jamesyong42/reactive-ecs';\nimport { useEffect, useMemo, useState } from 'react';\nimport { Selected, Widget, WidgetData } from '../ecs/components.js';\nimport type { LayoutEngine } from '../ecs/engine/index.js';\nimport { EngineProvider, useLayoutEngine } from '../react/context/engine-context.js';\nimport {\n\tuseAllEntities,\n\tuseComponent,\n\tuseEntityComponents,\n\tuseEntityTags,\n\tuseRegisteredComponents,\n\tuseRegisteredTags,\n\tuseTaggedEntities,\n} from '../react/hooks/ecs.js';\n\ninterface EcsDevtoolsProps {\n\t/**\n\t * Engine to inspect. If omitted, reads from the nearest InfiniteCanvas context —\n\t * supply this prop when the panel is rendered outside the `<InfiniteCanvas>` subtree.\n\t */\n\tengine?: LayoutEngine;\n\tonClose?: () => void;\n}\n\n/**\n * Live ECS editor: spawn widgets, browse entities, edit components, toggle tags.\n * Ship in a dev mode or behind a feature flag — not intended for production users.\n */\nexport function EcsDevtools({ engine, onClose }: EcsDevtoolsProps) {\n\tif (engine) {\n\t\treturn (\n\t\t\t<EngineProvider value={engine}>\n\t\t\t\t<EcsDevtoolsInner onClose={onClose} />\n\t\t\t</EngineProvider>\n\t\t);\n\t}\n\treturn <EcsDevtoolsInner onClose={onClose} />;\n}\n\nfunction EcsDevtoolsInner({ onClose }: { onClose?: () => void }) {\n\tconst engine = useLayoutEngine();\n\tconst allEntities = useAllEntities();\n\tconst selectedIds = useTaggedEntities(Selected);\n\tconst widgets = useMemo(() => engine.getWidgets(), [engine]);\n\n\tconst [showAll, setShowAll] = useState(false);\n\tconst [spawnType, setSpawnType] = useState(widgets[0]?.type ?? '');\n\tconst [manualFocusId, setManualFocusId] = useState<EntityId | null>(null);\n\n\tconst focusId = selectedIds[0] ?? manualFocusId;\n\n\tconst entityList = useMemo(() => {\n\t\tif (showAll) return allEntities;\n\t\treturn allEntities.filter((id) => engine.has(id, Widget));\n\t}, [engine, allEntities, showAll]);\n\n\tconst handleSpawn = () => {\n\t\tif (!spawnType) return;\n\t\tconst id = engine.spawnAtCameraCenter(spawnType); // proxy auto-dirties\n\t\tsetManualFocusId(id);\n\t};\n\n\treturn (\n\t\t<div className=\"ic-ecs-root\">\n\t\t\t<StyleTag />\n\n\t\t\t<div className=\"ic-ecs-header\">\n\t\t\t\t<span className=\"ic-ecs-title\">ECS Editor</span>\n\t\t\t\t{onClose && (\n\t\t\t\t\t<button type=\"button\" className=\"ic-ecs-close\" onClick={onClose} title=\"Close\">\n\t\t\t\t\t\t×\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t<div className=\"ic-ecs-section\">\n\t\t\t\t<div className=\"ic-ecs-label\">Spawn widget</div>\n\t\t\t\t<div className=\"ic-ecs-row\">\n\t\t\t\t\t<select\n\t\t\t\t\t\tclassName=\"ic-ecs-select\"\n\t\t\t\t\t\tvalue={spawnType}\n\t\t\t\t\t\tonChange={(e) => setSpawnType(e.target.value)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{widgets.map((w) => (\n\t\t\t\t\t\t\t<option key={w.type} value={w.type}>\n\t\t\t\t\t\t\t\t{w.type}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</select>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"ic-ecs-btn ic-ecs-btn-primary\"\n\t\t\t\t\t\tonClick={handleSpawn}\n\t\t\t\t\t\tdisabled={!spawnType}\n\t\t\t\t\t>\n\t\t\t\t\t\t+ Spawn\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div className=\"ic-ecs-section\">\n\t\t\t\t<div className=\"ic-ecs-label-row\">\n\t\t\t\t\t<span className=\"ic-ecs-label\">\n\t\t\t\t\t\tEntities ({entityList.length}\n\t\t\t\t\t\t{showAll ? '' : ' widgets'})\n\t\t\t\t\t</span>\n\t\t\t\t\t<label className=\"ic-ecs-check\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tchecked={showAll}\n\t\t\t\t\t\t\tonChange={(e) => setShowAll(e.target.checked)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\tshow all\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"ic-ecs-list\">\n\t\t\t\t\t{entityList.map((id) => (\n\t\t\t\t\t\t<EntityRow\n\t\t\t\t\t\t\tkey={id}\n\t\t\t\t\t\t\tentity={id}\n\t\t\t\t\t\t\tfocused={id === focusId}\n\t\t\t\t\t\t\tonClick={() => setManualFocusId(id)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t\t{entityList.length === 0 && <div className=\"ic-ecs-empty\">no entities</div>}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{focusId !== null && focusId !== undefined && engine.world.entityExists(focusId) && (\n\t\t\t\t<EntityInspector entity={focusId} />\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction EntityRow({\n\tentity,\n\tfocused,\n\tonClick,\n}: {\n\tentity: EntityId;\n\tfocused: boolean;\n\tonClick: () => void;\n}) {\n\tconst engine = useLayoutEngine();\n\tconst widget = useComponent(entity, Widget);\n\tconst label = widget?.type ?? 'entity';\n\n\treturn (\n\t\t<div className={`ic-ecs-entity-row ${focused ? 'is-focused' : ''}`}>\n\t\t\t<button type=\"button\" className=\"ic-ecs-entity-btn\" onClick={onClick}>\n\t\t\t\t<span className=\"ic-ecs-entity-id\">e{entity}</span>\n\t\t\t\t<span className=\"ic-ecs-entity-label\">{label}</span>\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"ic-ecs-btn ic-ecs-btn-danger ic-ecs-btn-sm\"\n\t\t\t\tonClick={() => {\n\t\t\t\t\tengine.destroyEntity(entity); // proxy auto-dirties\n\t\t\t\t}}\n\t\t\t\ttitle=\"Destroy entity\"\n\t\t\t>\n\t\t\t\t×\n\t\t\t</button>\n\t\t</div>\n\t);\n}\n\nfunction EntityInspector({ entity }: { entity: EntityId }) {\n\tconst engine = useLayoutEngine();\n\tconst components = useEntityComponents(entity);\n\tconst tags = useEntityTags(entity);\n\tconst registeredComponents = useRegisteredComponents();\n\tconst registeredTags = useRegisteredTags();\n\tconst widget = useComponent(entity, Widget);\n\n\tconst absentComponents = useMemo(() => {\n\t\tconst present = new Set(components.map((c) => c.name));\n\t\treturn registeredComponents.filter((c) => !present.has(c.name));\n\t}, [components, registeredComponents]);\n\n\tconst [componentToAdd, setComponentToAdd] = useState('');\n\tuseEffect(() => {\n\t\tsetComponentToAdd(absentComponents[0]?.name ?? '');\n\t}, [absentComponents]);\n\n\tconst handleAddComponent = () => {\n\t\tconst type = absentComponents.find((c) => c.name === componentToAdd);\n\t\tif (!type) return;\n\t\tengine.addComponent(entity, type);\n\t};\n\n\treturn (\n\t\t<div className=\"ic-ecs-section\">\n\t\t\t<div className=\"ic-ecs-inspect-head\">\n\t\t\t\t<span className=\"ic-ecs-label\">\n\t\t\t\t\tEntity <span className=\"ic-ecs-entity-id\">e{entity}</span>\n\t\t\t\t\t{widget?.type && <span className=\"ic-ecs-entity-label\"> · {widget.type}</span>}\n\t\t\t\t</span>\n\t\t\t</div>\n\n\t\t\t<div className=\"ic-ecs-sub-label\">Components</div>\n\t\t\t<div className=\"ic-ecs-components\">\n\t\t\t\t{components.map((type) => (\n\t\t\t\t\t<ComponentEditor key={type.name} entity={entity} type={type} />\n\t\t\t\t))}\n\t\t\t</div>\n\n\t\t\t{absentComponents.length > 0 && (\n\t\t\t\t<div className=\"ic-ecs-row\" style={{ marginTop: 6 }}>\n\t\t\t\t\t<select\n\t\t\t\t\t\tclassName=\"ic-ecs-select\"\n\t\t\t\t\t\tvalue={componentToAdd}\n\t\t\t\t\t\tonChange={(e) => setComponentToAdd(e.target.value)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{absentComponents.map((c) => (\n\t\t\t\t\t\t\t<option key={c.name} value={c.name}>\n\t\t\t\t\t\t\t\t{c.name}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</select>\n\t\t\t\t\t<button type=\"button\" className=\"ic-ecs-btn\" onClick={handleAddComponent}>\n\t\t\t\t\t\t+ Add component\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t<div className=\"ic-ecs-sub-label\">Tags</div>\n\t\t\t<div className=\"ic-ecs-tags\">\n\t\t\t\t{registeredTags.map((type) => (\n\t\t\t\t\t<TagPill\n\t\t\t\t\t\tkey={type.name}\n\t\t\t\t\t\tentity={entity}\n\t\t\t\t\t\ttype={type}\n\t\t\t\t\t\tactive={tags.some((t) => t.name === type.name)}\n\t\t\t\t\t/>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction ComponentEditor({ entity, type }: { entity: EntityId; type: ComponentType }) {\n\tconst engine = useLayoutEngine();\n\tconst value = useComponent(entity, type);\n\tconst [collapsed, setCollapsed] = useState(false);\n\n\tif (!value) return null;\n\n\tconst isWidgetData = type.name === 'WidgetData';\n\n\treturn (\n\t\t<div className=\"ic-ecs-component\">\n\t\t\t<div className=\"ic-ecs-component-head\">\n\t\t\t\t<button type=\"button\" className=\"ic-ecs-toggle\" onClick={() => setCollapsed((c) => !c)}>\n\t\t\t\t\t{collapsed ? '▶' : '▼'} {type.name}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"ic-ecs-btn ic-ecs-btn-danger ic-ecs-btn-sm\"\n\t\t\t\t\tonClick={() => engine.removeComponent(entity, type)}\n\t\t\t\t\ttitle=\"Remove component\"\n\t\t\t\t>\n\t\t\t\t\t×\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t{!collapsed && (\n\t\t\t\t<div className=\"ic-ecs-fields\">\n\t\t\t\t\t{isWidgetData ? (\n\t\t\t\t\t\t<WidgetDataEditor entity={entity} value={value as { data: Record<string, unknown> }} />\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<GenericFieldsEditor entity={entity} type={type} value={value} />\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction GenericFieldsEditor<T>({\n\tentity,\n\ttype,\n\tvalue,\n}: {\n\tentity: EntityId;\n\ttype: ComponentType<T>;\n\tvalue: T;\n}) {\n\tconst engine = useLayoutEngine();\n\tconst defaults = type.defaults as Record<string, unknown>;\n\tconst val = value as Record<string, unknown>;\n\tconst keys = Object.keys(defaults);\n\n\treturn (\n\t\t<>\n\t\t\t{keys.map((key) => (\n\t\t\t\t<FieldRow\n\t\t\t\t\tkey={key}\n\t\t\t\t\tlabel={key}\n\t\t\t\t\tvalue={val[key]}\n\t\t\t\t\tonChange={(next) => {\n\t\t\t\t\t\tengine.set(entity, type, { [key]: next } as Partial<T>);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</>\n\t);\n}\n\nfunction WidgetDataEditor({\n\tentity,\n\tvalue,\n}: {\n\tentity: EntityId;\n\tvalue: { data: Record<string, unknown> };\n}) {\n\tconst engine = useLayoutEngine();\n\tconst data = value.data ?? {};\n\tconst keys = Object.keys(data);\n\n\tif (keys.length === 0) {\n\t\treturn <div className=\"ic-ecs-empty\">(no fields)</div>;\n\t}\n\n\treturn (\n\t\t<>\n\t\t\t{keys.map((key) => (\n\t\t\t\t<FieldRow\n\t\t\t\t\tkey={key}\n\t\t\t\t\tlabel={key}\n\t\t\t\t\tvalue={data[key]}\n\t\t\t\t\tonChange={(next) => {\n\t\t\t\t\t\tengine.set(entity, WidgetData, { data: { ...data, [key]: next } });\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</>\n\t);\n}\n\nfunction FieldRow({\n\tlabel,\n\tvalue,\n\tonChange,\n}: {\n\tlabel: string;\n\tvalue: unknown;\n\tonChange: (next: unknown) => void;\n}) {\n\treturn (\n\t\t<div className=\"ic-ecs-field\">\n\t\t\t<span className=\"ic-ecs-field-label\">{label}</span>\n\t\t\t<FieldInput value={value} onChange={onChange} />\n\t\t</div>\n\t);\n}\n\nfunction FieldInput({ value, onChange }: { value: unknown; onChange: (next: unknown) => void }) {\n\tif (typeof value === 'number') {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"number\"\n\t\t\t\tclassName=\"ic-ecs-input\"\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => {\n\t\t\t\t\tconst n = Number.parseFloat(e.target.value);\n\t\t\t\t\tif (!Number.isNaN(n)) onChange(n);\n\t\t\t\t}}\n\t\t\t/>\n\t\t);\n\t}\n\tif (typeof value === 'boolean') {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"checkbox\"\n\t\t\t\tclassName=\"ic-ecs-checkbox\"\n\t\t\t\tchecked={value}\n\t\t\t\tonChange={(e) => onChange(e.target.checked)}\n\t\t\t/>\n\t\t);\n\t}\n\tif (typeof value === 'string') {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"text\"\n\t\t\t\tclassName=\"ic-ecs-input\"\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => onChange(e.target.value)}\n\t\t\t/>\n\t\t);\n\t}\n\treturn <JsonInput value={value} onChange={onChange} />;\n}\n\nfunction JsonInput({ value, onChange }: { value: unknown; onChange: (next: unknown) => void }) {\n\tconst serialized = JSON.stringify(value);\n\tconst [text, setText] = useState(serialized);\n\tconst [error, setError] = useState(false);\n\n\tuseEffect(() => {\n\t\tsetText(JSON.stringify(value));\n\t\tsetError(false);\n\t}, [value]);\n\n\tconst commit = () => {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(text);\n\t\t\tsetError(false);\n\t\t\tonChange(parsed);\n\t\t} catch {\n\t\t\tsetError(true);\n\t\t}\n\t};\n\n\treturn (\n\t\t<input\n\t\t\ttype=\"text\"\n\t\t\tclassName={`ic-ecs-input ic-ecs-input-json ${error ? 'is-error' : ''}`}\n\t\t\tvalue={text}\n\t\t\tonChange={(e) => setText(e.target.value)}\n\t\t\tonBlur={commit}\n\t\t\tonKeyDown={(e) => {\n\t\t\t\tif (e.key === 'Enter') commit();\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction TagPill({ entity, type, active }: { entity: EntityId; type: TagType; active: boolean }) {\n\tconst engine = useLayoutEngine();\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclassName={`ic-ecs-tag ${active ? 'is-active' : ''}`}\n\t\t\tonClick={() => {\n\t\t\t\tif (active) engine.removeTag(entity, type);\n\t\t\t\telse engine.addTag(entity, type);\n\t\t\t}}\n\t\t>\n\t\t\t{type.name}\n\t\t</button>\n\t);\n}\n\n// Scoped styles, inlined once on mount. Dark-mode-aware via prefers-color-scheme\n// and a parent `.dark` class (matches the playground's toggle convention).\nconst STYLE_ID = 'ic-ecs-devtools-style';\nconst CSS = `\n.ic-ecs-root {\n\t--ic-ecs-bg: rgba(255, 255, 255, 0.96);\n\t--ic-ecs-bg-elev: #f7f7f7;\n\t--ic-ecs-fg: #1a1a1a;\n\t--ic-ecs-fg-muted: #6b7280;\n\t--ic-ecs-fg-faint: #9ca3af;\n\t--ic-ecs-border: #e5e7eb;\n\t--ic-ecs-accent: #0d99ff;\n\t--ic-ecs-accent-fg: #ffffff;\n\t--ic-ecs-danger: #dc2626;\n\t--ic-ecs-focus-bg: rgba(13, 153, 255, 0.12);\n\tposition: absolute;\n\ttop: 4rem;\n\tright: 1rem;\n\tz-index: 50;\n\twidth: 360px;\n\tmax-height: calc(100vh - 6rem);\n\toverflow-y: auto;\n\tbackground: var(--ic-ecs-bg);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 8px;\n\tbox-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);\n\tbackdrop-filter: blur(6px);\n\tfont-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n\tfont-size: 11px;\n\tline-height: 1.4;\n}\n.dark .ic-ecs-root,\n:root[data-theme=\"dark\"] .ic-ecs-root {\n\t--ic-ecs-bg: rgba(23, 23, 23, 0.96);\n\t--ic-ecs-bg-elev: #1f1f1f;\n\t--ic-ecs-fg: #e5e5e5;\n\t--ic-ecs-fg-muted: #a1a1aa;\n\t--ic-ecs-fg-faint: #525252;\n\t--ic-ecs-border: #2a2a2a;\n\t--ic-ecs-focus-bg: rgba(13, 153, 255, 0.18);\n}\n.ic-ecs-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--ic-ecs-border);\n}\n.ic-ecs-title {\n\tfont-weight: 600;\n\tfont-size: 12px;\n}\n.ic-ecs-close {\n\tbackground: transparent;\n\tborder: 0;\n\tcolor: var(--ic-ecs-fg-muted);\n\tcursor: pointer;\n\tfont-size: 16px;\n\tline-height: 1;\n\tpadding: 0 4px;\n}\n.ic-ecs-close:hover { color: var(--ic-ecs-fg); }\n.ic-ecs-section {\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--ic-ecs-border);\n}\n.ic-ecs-section:last-child { border-bottom: 0; }\n.ic-ecs-label {\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.05em;\n\ttext-transform: uppercase;\n\tcolor: var(--ic-ecs-fg-muted);\n}\n.ic-ecs-label-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin-bottom: 4px;\n}\n.ic-ecs-sub-label {\n\tfont-size: 9px;\n\tfont-weight: 600;\n\tletter-spacing: 0.05em;\n\ttext-transform: uppercase;\n\tcolor: var(--ic-ecs-fg-faint);\n\tmargin: 8px 0 4px;\n}\n.ic-ecs-row {\n\tdisplay: flex;\n\tgap: 6px;\n\talign-items: center;\n\tmargin-top: 4px;\n}\n.ic-ecs-select {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tbackground: var(--ic-ecs-bg-elev);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tpadding: 4px 6px;\n\tfont: inherit;\n}\n.ic-ecs-input {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tbackground: var(--ic-ecs-bg-elev);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tpadding: 2px 6px;\n\tfont: inherit;\n}\n.ic-ecs-input:focus { outline: 1px solid var(--ic-ecs-accent); outline-offset: -1px; }\n.ic-ecs-input-json.is-error { border-color: var(--ic-ecs-danger); }\n.ic-ecs-checkbox {\n\taccent-color: var(--ic-ecs-accent);\n}\n.ic-ecs-btn {\n\tbackground: var(--ic-ecs-bg-elev);\n\tcolor: var(--ic-ecs-fg);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tpadding: 3px 8px;\n\tfont: inherit;\n\tcursor: pointer;\n\twhite-space: nowrap;\n}\n.ic-ecs-btn:hover { background: var(--ic-ecs-border); }\n.ic-ecs-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n.ic-ecs-btn-primary {\n\tbackground: var(--ic-ecs-accent);\n\tcolor: var(--ic-ecs-accent-fg);\n\tborder-color: var(--ic-ecs-accent);\n}\n.ic-ecs-btn-primary:hover { filter: brightness(1.08); background: var(--ic-ecs-accent); }\n.ic-ecs-btn-danger { color: var(--ic-ecs-danger); }\n.ic-ecs-btn-sm { padding: 0 6px; font-size: 11px; }\n.ic-ecs-check {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 10px;\n\tcolor: var(--ic-ecs-fg-muted);\n\tcursor: pointer;\n}\n.ic-ecs-list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmax-height: 200px;\n\toverflow-y: auto;\n\tmargin-top: 4px;\n}\n.ic-ecs-empty {\n\tcolor: var(--ic-ecs-fg-faint);\n\tfont-style: italic;\n\tpadding: 4px 0;\n}\n.ic-ecs-entity-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n\tpadding: 2px 4px;\n\tborder-radius: 4px;\n}\n.ic-ecs-entity-row.is-focused { background: var(--ic-ecs-focus-bg); }\n.ic-ecs-entity-btn {\n\tflex: 1 1 auto;\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: center;\n\tbackground: transparent;\n\tborder: 0;\n\tcolor: inherit;\n\tcursor: pointer;\n\ttext-align: left;\n\tpadding: 2px 0;\n\tfont: inherit;\n\tmin-width: 0;\n}\n.ic-ecs-entity-id {\n\tcolor: var(--ic-ecs-fg-muted);\n\tfont-variant-numeric: tabular-nums;\n}\n.ic-ecs-entity-label {\n\tcolor: var(--ic-ecs-fg);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.ic-ecs-inspect-head {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin-bottom: 4px;\n}\n.ic-ecs-components {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n.ic-ecs-component {\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 4px;\n\tbackground: var(--ic-ecs-bg-elev);\n}\n.ic-ecs-component-head {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 2px 6px;\n}\n.ic-ecs-toggle {\n\tbackground: transparent;\n\tborder: 0;\n\tcolor: inherit;\n\tcursor: pointer;\n\tfont: inherit;\n\tfont-weight: 600;\n\tpadding: 2px 0;\n}\n.ic-ecs-fields {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n\tpadding: 4px 6px 6px;\n\tborder-top: 1px dashed var(--ic-ecs-border);\n}\n.ic-ecs-field {\n\tdisplay: grid;\n\tgrid-template-columns: 70px 1fr;\n\talign-items: center;\n\tgap: 6px;\n}\n.ic-ecs-field-label {\n\tcolor: var(--ic-ecs-fg-muted);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.ic-ecs-tags {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 4px;\n}\n.ic-ecs-tag {\n\tbackground: transparent;\n\tcolor: var(--ic-ecs-fg-faint);\n\tborder: 1px solid var(--ic-ecs-border);\n\tborder-radius: 999px;\n\tpadding: 2px 8px;\n\tfont: inherit;\n\tcursor: pointer;\n}\n.ic-ecs-tag:hover { color: var(--ic-ecs-fg); }\n.ic-ecs-tag.is-active {\n\tbackground: var(--ic-ecs-accent);\n\tcolor: var(--ic-ecs-accent-fg);\n\tborder-color: var(--ic-ecs-accent);\n}\n`;\n\nfunction StyleTag() {\n\tuseEffect(() => {\n\t\tif (typeof document === 'undefined') return;\n\t\tif (document.getElementById(STYLE_ID)) return;\n\t\tconst el = document.createElement('style');\n\t\tel.id = STYLE_ID;\n\t\tel.textContent = CSS;\n\t\tdocument.head.appendChild(el);\n\t}, []);\n\treturn null;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require("./hooks-
|
|
1
|
+
require("./hooks-B-UPFgGj.cjs");
|
|
2
2
|
let _jamesyong42_reactive_ecs = require("@jamesyong42/reactive-ecs");
|
|
3
3
|
let react = require("react");
|
|
4
4
|
//#region src/ecs/components.ts
|
|
@@ -348,6 +348,37 @@ const LayerOrderResource = (0, _jamesyong42_reactive_ecs.defineResource)("LayerO
|
|
|
348
348
|
"base",
|
|
349
349
|
"overlay"
|
|
350
350
|
] });
|
|
351
|
+
const VisibleEntitiesResource = (0, _jamesyong42_reactive_ecs.defineResource)("VisibleEntities", {
|
|
352
|
+
current: [],
|
|
353
|
+
prev: /* @__PURE__ */ new Set()
|
|
354
|
+
});
|
|
355
|
+
const FrameChangesResource = (0, _jamesyong42_reactive_ecs.defineResource)("FrameChanges", { changes: {
|
|
356
|
+
positionsChanged: [],
|
|
357
|
+
breakpointsChanged: [],
|
|
358
|
+
zIndicesChanged: [],
|
|
359
|
+
entered: [],
|
|
360
|
+
exited: [],
|
|
361
|
+
cameraChanged: false,
|
|
362
|
+
navigationChanged: false,
|
|
363
|
+
selectionChanged: false,
|
|
364
|
+
layersChanged: false
|
|
365
|
+
} });
|
|
366
|
+
const TickFlagsResource = (0, _jamesyong42_reactive_ecs.defineResource)("TickFlags", {
|
|
367
|
+
cameraChanged: false,
|
|
368
|
+
selectionChanged: false,
|
|
369
|
+
navigationChangedSnapshot: false
|
|
370
|
+
});
|
|
371
|
+
/**
|
|
372
|
+
* Engine-level dirty flag. Set by mutation APIs in `createLayoutEngine`
|
|
373
|
+
* and by `tweenKeepaliveSystem` (when any `TransformTween` is alive).
|
|
374
|
+
* Cleared by the cleanup-phase `clearDirtySystem`. Read by `flushIfDirty`
|
|
375
|
+
* to gate the next rAF tick.
|
|
376
|
+
*
|
|
377
|
+
* RFC-010 Phase 4 — replaces the closure var `dirty` in
|
|
378
|
+
* `createLayoutEngine`. Phase 5 will fold most of the explicit setters
|
|
379
|
+
* into a mutation proxy.
|
|
380
|
+
*/
|
|
381
|
+
const EngineDirtyResource = (0, _jamesyong42_reactive_ecs.defineResource)("EngineDirty", { dirty: false });
|
|
351
382
|
//#endregion
|
|
352
383
|
//#region src/react/context/engine-context.ts
|
|
353
384
|
const EngineContext = (0, react.createContext)(null);
|
|
@@ -681,12 +712,24 @@ Object.defineProperty(exports, "Dragging", {
|
|
|
681
712
|
return Dragging;
|
|
682
713
|
}
|
|
683
714
|
});
|
|
715
|
+
Object.defineProperty(exports, "EngineDirtyResource", {
|
|
716
|
+
enumerable: true,
|
|
717
|
+
get: function() {
|
|
718
|
+
return EngineDirtyResource;
|
|
719
|
+
}
|
|
720
|
+
});
|
|
684
721
|
Object.defineProperty(exports, "EngineProvider", {
|
|
685
722
|
enumerable: true,
|
|
686
723
|
get: function() {
|
|
687
724
|
return EngineProvider;
|
|
688
725
|
}
|
|
689
726
|
});
|
|
727
|
+
Object.defineProperty(exports, "FrameChangesResource", {
|
|
728
|
+
enumerable: true,
|
|
729
|
+
get: function() {
|
|
730
|
+
return FrameChangesResource;
|
|
731
|
+
}
|
|
732
|
+
});
|
|
690
733
|
Object.defineProperty(exports, "InteractionRole", {
|
|
691
734
|
enumerable: true,
|
|
692
735
|
get: function() {
|
|
@@ -789,6 +832,12 @@ Object.defineProperty(exports, "SpatialIndexResource", {
|
|
|
789
832
|
return SpatialIndexResource;
|
|
790
833
|
}
|
|
791
834
|
});
|
|
835
|
+
Object.defineProperty(exports, "TickFlagsResource", {
|
|
836
|
+
enumerable: true,
|
|
837
|
+
get: function() {
|
|
838
|
+
return TickFlagsResource;
|
|
839
|
+
}
|
|
840
|
+
});
|
|
792
841
|
Object.defineProperty(exports, "Transform2D", {
|
|
793
842
|
enumerable: true,
|
|
794
843
|
get: function() {
|
|
@@ -813,6 +862,12 @@ Object.defineProperty(exports, "Visible", {
|
|
|
813
862
|
return Visible;
|
|
814
863
|
}
|
|
815
864
|
});
|
|
865
|
+
Object.defineProperty(exports, "VisibleEntitiesResource", {
|
|
866
|
+
enumerable: true,
|
|
867
|
+
get: function() {
|
|
868
|
+
return VisibleEntitiesResource;
|
|
869
|
+
}
|
|
870
|
+
});
|
|
816
871
|
Object.defineProperty(exports, "Widget", {
|
|
817
872
|
enumerable: true,
|
|
818
873
|
get: function() {
|
|
@@ -916,4 +971,4 @@ Object.defineProperty(exports, "useTaggedEntities", {
|
|
|
916
971
|
}
|
|
917
972
|
});
|
|
918
973
|
|
|
919
|
-
//# sourceMappingURL=ecs-
|
|
974
|
+
//# sourceMappingURL=ecs-BtX_rCS3.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ecs-BtX_rCS3.cjs","names":[],"sources":["../src/ecs/components.ts","../src/ecs/resources.ts","../src/react/context/engine-context.ts","../src/react/hooks/ecs.ts"],"sourcesContent":["import type { EntityId } from '@jamesyong42/reactive-ecs';\nimport { defineComponent, defineTag } from '@jamesyong42/reactive-ecs';\n\n// === Spatial ===\n\n/** Position, size, and rotation of an entity in local coordinates (world units). */\nexport const Transform2D = defineComponent('Transform2D', {\n\tx: 0,\n\ty: 0,\n\twidth: 100,\n\theight: 100,\n\trotation: 0,\n});\n\n/** Rendering and hit-test ordering. Higher values render on top. */\nexport const ZIndex = defineComponent('ZIndex', { value: 0 });\n\n/** Easing curves supported by `transformTweenSystem` (RFC-004 § Phase 2). */\nexport type TweenEasing = 'linear' | 'ease-out' | 'ease-in-out' | 'spring';\n\n/**\n * Generic animated transition of `Transform2D.x` / `.y` over time.\n * Runtime-only: the tween component is auto-removed on completion,\n * and in-flight tweens are not serialized (the destination Transform2D\n * is what survives a save/load).\n *\n * Introduced for RFC-004 Phase 4 fly-back but designed to be reusable\n * for any future position-animation need (snap, drop-in, consume-pop).\n * Starting a new tween on an entity that already has one overwrites.\n */\nexport const TransformTween = defineComponent('TransformTween', {\n\tfromX: 0,\n\tfromY: 0,\n\ttoX: 0,\n\ttoY: 0,\n\t/** `performance.now()`-ish timestamp captured at tween start. */\n\tstartMs: 0,\n\t/** Total duration in ms. */\n\tdurationMs: 250,\n\teasing: 'ease-out' as TweenEasing,\n\t/**\n\t * Discriminator so downstream systems can react per kind\n\t * (e.g. `'flyback'`, `'snap'`, `'spawn'`). The tween system\n\t * itself is kind-agnostic.\n\t */\n\tkind: 'generic',\n});\n\n// === Hierarchy ===\n\n/**\n * Container-hierarchy parenthood: the entity lives inside another\n * entity's sub-canvas. Read by `navigationFilterSystem` to filter the\n * active widget set by the current navigation frame.\n *\n * Container children have their own `Transform2D` in the container's\n * local coord space — **no coord accumulation** through this reference.\n * Replaces the old `Parent` component's container-hierarchy usage\n * (RFC-005).\n */\nexport const ParentFrame = defineComponent('ParentFrame', { id: 0 as EntityId });\n\n/** Child entity IDs. Used for nested containers and handle sync. */\nexport const Children = defineComponent('Children', { ids: [] as EntityId[] });\n\n/**\n * Ordered list of the entities a container owns (RFC-004 § Phase 5).\n * Redundant with `ParentFrame` (the inverse relation) but materialised\n * so UI / compositor paths can cheaply read \"give me this container's\n * children\" without a reverse-index scan. `applyMutation` /\n * `revertMutation` keep the two in sync. Serialized; IDs are remapped\n * alongside `ParentFrame` on load.\n */\nexport const ContainerChildren = defineComponent('ContainerChildren', {\n\tids: [] as EntityId[],\n});\n\n// === Widget ===\n\n/** Marks an entity as a renderable widget with a type identifier and rendering surface. */\nexport const Widget = defineComponent('Widget', {\n\tsurface: 'dom' as 'dom' | 'webgl' | 'webview',\n\ttype: '' as string,\n});\n\n/** Arbitrary application data attached to a widget entity. Access via useWidgetData(). */\nexport const WidgetData = defineComponent('WidgetData', {\n\tdata: {} as Record<string, unknown>,\n});\n\n/** Computed responsive breakpoint based on screen-space size. Read-only. */\nexport const WidgetBreakpoint = defineComponent('WidgetBreakpoint', {\n\tcurrent: 'normal' as 'micro' | 'compact' | 'normal' | 'expanded' | 'detailed',\n\tscreenWidth: 0,\n\tscreenHeight: 0,\n});\n\n// === Card ===\n\n/** iOS-style card size presets. Actual dimensions live in CardPresetsResource. */\nexport type CardPreset = 'small' | 'medium' | 'large' | 'xl';\n\n/**\n * Marks an entity as an iOS-style card with a fixed preset size, AND\n * opts the entity into the full card-shaped behavior bundle:\n *\n * - DOM `<CardChrome>` slot (rounded body, hairline ring, shadow,\n * CSS lift transition on Dragging)\n * - drag-promote to the 'overlay' layer for DOM cards (so dragged\n * DOM cards visually pop above the R3F canvas)\n * - composition discard rect for R3F cards (so other R3F widgets\n * are clipped out of the dragged card's screen rect — defends\n * the chrome from being painted over)\n * - drop-to-consume contracts (`accepts` / `provides`) — RFC-004.\n *\n * Widgets *without* `Card` get none of this — they render bare (no\n * chrome, no lift transition), and on drag they only get the\n * compositor's renderOrder bump if they're R3F (so they stack on\n * top of other R3F widgets they overlap).\n *\n * The `cardSystem` reconciles `Transform2D.width/height` from the\n * preset each tick, so cards cannot be resized freely — change\n * `preset` instead.\n */\nexport const Card = defineComponent('Card', {\n\tpreset: 'small' as CardPreset,\n\t/**\n\t * CSS background for the chrome's surface (any valid background value;\n\t * defaults to the dark iOS card colour). Picked up by the\n\t * `<CardChrome>` component rendered for this entity.\n\t */\n\tbackground: '#1C1C1E',\n\t/**\n\t * Drop-to-consume contract — what this card accepts as a *parent*.\n\t * An incoming dragged card's `provides` must intersect this list\n\t * for the consume mechanic to fire (RFC-004 § Phase 3). Empty\n\t * array = card never consumes anything.\n\t */\n\taccepts: [] as readonly string[],\n\t/**\n\t * Drop-to-consume contract — what this card offers when dropped\n\t * as a *child*. Empty array = card is never consumed by anything.\n\t */\n\tprovides: [] as readonly string[],\n});\n\n/**\n * Transient — set by the card-overlap pass on every card whose AABB\n * intersects the dragged card's AABB during drag. Layer 1 of the\n * two-layer overlap visual state (RFC-004 § Phase 3). Cleared on drag\n * end. Runtime-only — not serialized.\n */\nexport const OverlapCandidate = defineTag('OverlapCandidate');\n\n/**\n * Transient — set on the single primary candidate (closest centre\n * distance) iff its `accepts` contract intersects the dragged card's\n * `provides` contract AND the optional `canAccept` gate passes.\n * Layer 2 of the two-layer overlap visual state. Cleared on drag end\n * or when match becomes false. Runtime-only — not serialized.\n */\nexport const OverlapTarget = defineTag('OverlapTarget');\n\n/**\n * Per-candidate \"hot point\" for the position-dependent radial glow\n * (RFC-004 § Phase 3). `x` and `y` are normalized [0..1] local coords\n * within the overlapped card, pointing at the intersection centroid.\n * `strength` ramps between 0 and 1 for the fade-in / fade-out.\n * Runtime-only — not serialized.\n */\nexport const CardOverlapHotPoint = defineComponent('CardOverlapHotPoint', {\n\tx: 0.5,\n\ty: 0.5,\n\tstrength: 0,\n});\n\n// === Container ===\n\n/** Marks an entity as an enterable container (double-click/double-tap to enter). */\nexport const Container = defineComponent('Container', { enterable: true });\n\n/**\n * Per-container persistent camera state. When the user navigates out of\n * a container, their current pan/zoom is snapshotted here; when they\n * navigate back in, it's restored. Serialized — containers remember\n * their view across save/load (RFC-004 § Phase 0c).\n */\nexport const ContainerCamera = defineComponent('ContainerCamera', {\n\tx: 0,\n\ty: 0,\n\tzoom: 1,\n});\n\n// === Interaction ===\n\n/** Resize handle positions — 4 edges + 4 corners. */\nexport type ResizeHandlePos = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';\n\n/** Discriminated union of interaction roles an entity can fulfil. */\nexport type InteractionRoleType =\n\t| { type: 'drag' }\n\t| { type: 'select' }\n\t| { type: 'resize'; handle: ResizeHandlePos }\n\t| { type: 'rotate' }\n\t| { type: 'connect' }\n\t| { type: 'canvas' };\n\nexport type InteractionRoleData = {\n\t/** Hit-test priority — higher wins when multiple entities contain the point. */\n\tlayer: number;\n\t/** Discriminated role + role-specific data. */\n\trole: InteractionRoleType;\n};\n\n/**\n * Declares what happens when this entity is hit, plus its hit-test priority.\n * Canonical layers: 0=canvas, 5=widget body, 20=reserved.\n *\n * Resize corner/edge roles are emitted inline by `interaction.ts` from the\n * selected `Resizable` widget's Transform2D; they are NOT stored as\n * per-handle entities (RFC-005).\n */\nexport const InteractionRole = defineComponent<InteractionRoleData>('InteractionRole', {\n\tlayer: 0,\n\trole: { type: 'canvas' },\n});\n\n/** CSS cursor values the canvas may request. */\nexport type CSSCursor =\n\t| 'default'\n\t| 'grab'\n\t| 'grabbing'\n\t| 'crosshair'\n\t| 'n-resize'\n\t| 's-resize'\n\t| 'e-resize'\n\t| 'w-resize'\n\t| 'ne-resize'\n\t| 'nw-resize'\n\t| 'se-resize'\n\t| 'sw-resize';\n\nexport type CursorHintData = {\n\t/** Cursor when this entity is hovered in idle state. */\n\thover: CSSCursor;\n\t/** Cursor while this entity is being dragged/resized. */\n\tactive: CSSCursor;\n};\n\n/** Declares the cursor this entity requests when hovered and when active. */\nexport const CursorHint = defineComponent<CursorHintData>('CursorHint', {\n\thover: 'default',\n\tactive: 'default',\n});\n\n// === Tags ===\n\n/** Marks an entity as selectable by click or marquee. */\nexport const Selectable = defineTag('Selectable');\n/** Marks an entity as draggable via pointer interaction. */\nexport const Draggable = defineTag('Draggable');\n/** Marks an entity as resizable via edge/corner handles. */\nexport const Resizable = defineTag('Resizable');\n/**\n * Marks an entity that, when dragged, runs alignment-snap math against\n * the set of `SnapTarget` entities. Without this tag, dragging proceeds\n * with raw pointer deltas (no snapping). Independent of `SnapTarget`:\n * an entity can be a source without being a target (rare) or a target\n * without being a source (e.g. cards — they participate as references\n * for other widgets but never snap themselves).\n *\n * Multi-select drag: only the first selected entity's bounds drive the\n * snap calculation; followers receive the same correction delta. Tag a\n * follower-only entity with `SnapSource` if you also want it to lead a\n * single-entity drag, but be aware its geometry is ignored when it is\n * being dragged as part of a multi-select group.\n */\nexport const SnapSource = defineTag('SnapSource');\n/**\n * Marks an entity whose bounds are usable as a snap reference when\n * another `SnapSource` entity is being dragged. References are further\n * filtered by `Active`, so only entities in the current navigation\n * frame can pull a drag — a `SnapTarget` inside a closed container\n * does not leak into a root-level drag. Visibility of the resulting\n * guide lines is controlled separately by the engine's\n * `snap.guidesVisible` config — this tag only governs participation\n * in the math.\n */\nexport const SnapTarget = defineTag('SnapTarget');\n/** Prevents an entity from being moved or resized. */\nexport const Locked = defineTag('Locked');\n/** Indicates the entity is currently selected. */\nexport const Selected = defineTag('Selected');\n/**\n * Indicates the entity is currently being dragged by the user.\n * Added after the drag dead-zone is crossed; removed on pointer up/cancel.\n * Renderers read this to apply transient drag affordances (e.g. scale/shadow lift).\n */\nexport const Dragging = defineTag('Dragging');\n/**\n * Entities with this tag get the engine-drawn selection + hover outline frame.\n * Granted automatically to Selectable entities unless explicitly disabled via\n * `Archetype.interactive.selectionFrame: false`. Widgets that render their own\n * selected/hover chrome (e.g. iOS-style cards) opt out.\n */\nexport const SelectionFrame = defineTag('SelectionFrame');\n/** Indicates the entity is currently being interacted with (drag, resize). */\nexport const Active = defineTag('Active');\n/** Indicates the entity is within the visible viewport. Set by the cull system. */\nexport const Visible = defineTag('Visible');\n/**\n * Indicates the entity is `Active` but **outside** the visible viewport\n * (+overscan). The complement of `Visible` for Active entities — the cull\n * system maintains the invariant that every Active entity carries exactly\n * one of `Visible` or `Culled`.\n *\n * Render layers consume this to keep state cached without rendering: DOM\n * widgets may stay mounted-but-hidden for fast re-reveal, and the R3F\n * compositor (RFC-002) holds widget render targets in its Cold pool.\n */\nexport const Culled = defineTag('Culled');\n\n// === Layer system (RFC-003) ===\n\n/**\n * Named DOM stacking layer a widget renders into. Three layers are\n * rendered out of the box by `<InfiniteCanvas>`:\n *\n * `'background'` — DOM widgets behind everything user-content.\n * `'base'` — default; DOM widgets and R3F card chrome.\n * `'overlay'` — DOM widgets and R3F chrome promoted above the R3F\n * canvas (e.g. dragged widget, future tooltips).\n *\n * Per-widget `ZIndex` continues to control intra-layer ordering;\n * `Layer.name` picks which layer container the widget mounts into.\n *\n * R3F widgets always render through the R3F canvas regardless of layer\n * — `Layer.name` only controls where their CSS chrome / interaction\n * surface mounts.\n */\nexport type LayerName = 'background' | 'base' | 'overlay';\n\nexport type LayerData = {\n\tname: LayerName;\n};\n\nexport const Layer = defineComponent<LayerData>('Layer', { name: 'base' });\n\n/**\n * Sidecar component on a widget that has been promoted to a higher\n * layer by `dragPromoteSystem`; stores the widget's pre-drag\n * `Layer.name` so it can be restored when `Dragging` is removed.\n *\n * Internal — not part of the public API. Serialization-skipped because\n * it only carries transient interaction state.\n */\nexport type PreDragLayerData = {\n\tname: LayerName;\n};\n\nexport const PreDragLayer = defineComponent<PreDragLayerData>('PreDragLayer', {\n\tname: 'base',\n});\n","import type { EntityId } from '@jamesyong42/reactive-ecs';\nimport { defineResource } from '@jamesyong42/reactive-ecs';\nimport type { CardPreset, CSSCursor } from './components.js';\nimport type { FrameChanges, VisibleEntity } from './engine/types.js';\nimport type { SpatialIndex } from './spatial/SpatialIndex.js';\n\n/**\n * A single frame in the navigation stack. `containerId === null` is the\n * root canvas; any other value is the entity id of the container whose\n * sub-canvas the user is currently inside. Camera state lives on a\n * `ContainerCamera` component per container (or `RootCameraResource`\n * for the root frame), not in the stack itself — see RFC-004 § Phase 0c.\n */\nexport interface NavigationFrame {\n\tcontainerId: EntityId | null;\n}\n\n/** Data shape for the CursorResource. */\nexport type CursorResourceData = {\n\tcursor: CSSCursor;\n};\n\n/**\n * Output sink for the cursor system. Written by cursorSystem each tick;\n * read by the RAF loop to apply style.cursor on the root container div.\n */\nexport const CursorResource = defineResource<CursorResourceData>('Cursor', {\n\tcursor: 'default',\n});\n\n/**\n * Camera state: world-space position (x, y) and zoom level.\n *\n * `gesturing` is true while the user is actively manipulating the camera\n * (continuous wheel zoom, pinch, two-finger pan). Set/cleared by gesture\n * handlers via {@link LayoutEngine.setGesturing}; render layers can use it\n * to defer expensive work (e.g. the R3F compositor skips zoom-band\n * repaints while gesturing so a continuous pinch doesn't trigger a\n * repaint storm across every visible widget).\n */\nexport const CameraResource = defineResource('Camera', {\n\tx: 0,\n\ty: 0,\n\tzoom: 1,\n\tgesturing: false,\n});\n\n/** Viewport dimensions in CSS pixels and device pixel ratio. Updated on resize. */\nexport const ViewportResource = defineResource('Viewport', {\n\twidth: 0,\n\theight: 0,\n\tdpr: 1,\n});\n\n/** Minimum and maximum zoom levels. */\nexport const ZoomConfigResource = defineResource('ZoomConfig', {\n\tmin: 0.1,\n\tmax: 5.0,\n});\n\n/** Screen-space pixel thresholds for responsive breakpoints (micro/compact/normal/expanded/detailed). */\nexport const BreakpointConfigResource = defineResource('BreakpointConfig', {\n\tmicro: 40,\n\tcompact: 120,\n\tnormal: 500,\n\texpanded: 1200,\n});\n\n/**\n * Navigation stack for hierarchical container traversal. Always has at\n * least one frame (the root). The last element is the current frame;\n * `containerId === null` means the user is at the root canvas.\n *\n * Runtime-only view state — deliberately not serialized, so reloading a\n * saved canvas always drops the user at the root frame (RFC-004 § Phase 0c).\n */\nexport const NavigationStackResource = defineResource('NavigationStack', {\n\tframes: [{ containerId: null }] as NavigationFrame[],\n\tchanged: false,\n});\n\n/** Shape shared by `ContainerCamera` components and `RootCameraResource`. */\nexport type FrameCameraState = { x: number; y: number; zoom: number };\n\n/**\n * Camera state for the root canvas. Persisted (serialized) so the root\n * view returns to its previous pan/zoom across navigation push/pop and\n * across save/load. Container frames use the `ContainerCamera` component\n * on the container entity instead (RFC-004 § Phase 0c).\n */\nexport const RootCameraResource = defineResource<FrameCameraState>('RootCamera', {\n\tx: 0,\n\ty: 0,\n\tzoom: 1,\n});\n\n/** Responsive breakpoint name derived from a widget's screen-space size. */\nexport type Breakpoint = 'micro' | 'compact' | 'normal' | 'expanded' | 'detailed';\n\n/**\n * Built-in card preset sizes (iOS widget conventions — 155×155 tile).\n * Single source of truth consumed by:\n * - {@link CardPresetsResource} — runtime lookup by the cardSystem.\n * - `createCardWidget` / `createGeometryCardWidget` — widget-registration-\n * time `defaultSize`, which must be known before the engine is built.\n */\nexport const DEFAULT_CARD_PRESET_SIZES: Record<CardPreset, { width: number; height: number }> = {\n\tsmall: { width: 155, height: 155 },\n\tmedium: { width: 329, height: 155 },\n\tlarge: { width: 329, height: 345 },\n\txl: { width: 329, height: 535 },\n};\n\n/**\n * iOS-style card preset size map. Lookup happens by `Card.preset`; the\n * `cardSystem` stamps `Transform2D.width/height` from the resolved size.\n *\n * Override at `createLayoutEngine({ cardPresets })` for tablet-scale or\n * custom design systems.\n */\nexport const CardPresetsResource = defineResource('CardPresets', {\n\tpresets: { ...DEFAULT_CARD_PRESET_SIZES },\n\t/** Gap between adjacent tiles (future tile-snap system reads this). */\n\tgap: 19,\n});\n\n/** ECS resource holding the SpatialIndex instance for viewport culling and hit testing. */\nexport const SpatialIndexResource = defineResource('SpatialIndex', {\n\tinstance: null as SpatialIndex | null,\n});\n\n/**\n * Render-layer order, low → high. `<InfiniteCanvas>` mounts a DOM\n * container for each entry; widgets render into the container that\n * matches their `Layer.name`. Per-widget `ZIndex` controls intra-layer\n * ordering. RFC-003.\n *\n * Default: `['background', 'base', 'overlay']`. Out-of-the-box the\n * three names map to fixed DOM positions in `<InfiniteCanvas>`'s\n * stacking sandwich: background and base sit beneath the R3F canvas\n * (zIndex < 1), overlay sits above it (zIndex 2). Custom layer names\n * are not yet rendered by the default `<InfiniteCanvas>`.\n */\nexport type LayerOrderData = {\n\tlayers: import('./components.js').LayerName[];\n};\n\nexport const LayerOrderResource = defineResource<LayerOrderData>('LayerOrder', {\n\tlayers: ['background', 'base', 'overlay'],\n});\n\n// === RFC-010 Phase 4 — per-frame engine state ===\n\n/**\n * Output of `visibilitySystem` (`present` phase). `current` is what\n * `engine.getVisibleEntities()` returns; `prev` is the previous tick's\n * entityId set, used by `frameChangesSystem` to compute entered/exited.\n *\n * RFC-010 Phase 4 — replaces the closure vars `currentVisible` /\n * `prevVisible` in `createLayoutEngine`.\n */\nexport type VisibleEntitiesResourceData = {\n\tcurrent: VisibleEntity[];\n\tprev: Set<EntityId>;\n};\n\nexport const VisibleEntitiesResource = defineResource<VisibleEntitiesResourceData>(\n\t'VisibleEntities',\n\t{\n\t\tcurrent: [],\n\t\tprev: new Set<EntityId>(),\n\t},\n);\n\n/**\n * Output of `frameChangesSystem` (`present` phase, after `visibility`).\n * What `engine.getFrameChanges()` returns. RFC-010 Phase 4 — replaces the\n * closure var `frameChanges` in `createLayoutEngine`.\n */\nexport type FrameChangesResourceData = {\n\tchanges: FrameChanges;\n};\n\nexport const FrameChangesResource = defineResource<FrameChangesResourceData>('FrameChanges', {\n\tchanges: {\n\t\tpositionsChanged: [],\n\t\tbreakpointsChanged: [],\n\t\tzIndicesChanged: [],\n\t\tentered: [],\n\t\texited: [],\n\t\tcameraChanged: false,\n\t\tnavigationChanged: false,\n\t\tselectionChanged: false,\n\t\tlayersChanged: false,\n\t},\n});\n\n/**\n * Per-tick boolean flags set by engine APIs and read by\n * `frameChangesSystem` to populate `FrameChanges.cameraChanged` /\n * `selectionChanged` / `navigationChanged`.\n *\n * - `cameraChanged` — set by camera/zoom/setGesturing/zoomToFit/\n * enterContainer/exitContainer; read + reset by `frameChangesSystem`.\n * - `selectionChanged` — set via the interaction runtime's\n * `notifySelectionChanged` callback; read + reset by `frameChangesSystem`.\n * - `navigationChangedSnapshot` — captured by `navStackCaptureSystem`\n * (`input` phase) **before** `navigationFilterSystem` resets\n * `NavigationStackResource.changed`. Read by `frameChangesSystem`.\n *\n * RFC-010 Phase 4 — replaces the closure vars `cameraChangedThisTick` /\n * `selectionChangedThisTick` and the inline `navStackPreTick` capture in\n * `createLayoutEngine`.\n */\nexport type TickFlagsResourceData = {\n\tcameraChanged: boolean;\n\tselectionChanged: boolean;\n\tnavigationChangedSnapshot: boolean;\n};\n\nexport const TickFlagsResource = defineResource<TickFlagsResourceData>('TickFlags', {\n\tcameraChanged: false,\n\tselectionChanged: false,\n\tnavigationChangedSnapshot: false,\n});\n\n/**\n * Engine-level dirty flag. Set by mutation APIs in `createLayoutEngine`\n * and by `tweenKeepaliveSystem` (when any `TransformTween` is alive).\n * Cleared by the cleanup-phase `clearDirtySystem`. Read by `flushIfDirty`\n * to gate the next rAF tick.\n *\n * RFC-010 Phase 4 — replaces the closure var `dirty` in\n * `createLayoutEngine`. Phase 5 will fold most of the explicit setters\n * into a mutation proxy.\n */\nexport const EngineDirtyResource = defineResource<{ dirty: boolean }>('EngineDirty', {\n\tdirty: false,\n});\n","import { createContext, useContext } from 'react';\nimport type { LayoutEngine } from '../../ecs/engine/index.js';\n\nconst EngineContext = createContext<LayoutEngine | null>(null);\n\nexport const EngineProvider = EngineContext.Provider;\n\n/**\n * Returns the LayoutEngine instance from the nearest InfiniteCanvas context.\n * Throws if used outside an InfiniteCanvas provider.\n */\nexport function useLayoutEngine(): LayoutEngine {\n\tconst engine = useContext(EngineContext);\n\tif (!engine) {\n\t\tthrow new Error('useLayoutEngine must be used within an <InfiniteCanvas>');\n\t}\n\treturn engine;\n}\n","import type { ComponentType, EntityId, ResourceType, TagType } from '@jamesyong42/reactive-ecs';\nimport { useEffect, useRef, useState } from 'react';\nimport { CameraResource } from '../../ecs/resources.js';\nimport { useLayoutEngine } from '../context/engine-context.js';\n\nfunction shallowEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean {\n\tconst keysA = Object.keys(a);\n\tconst keysB = Object.keys(b);\n\tif (keysA.length !== keysB.length) return false;\n\tfor (const key of keysA) {\n\t\tif (a[key] !== b[key]) return false;\n\t}\n\treturn true;\n}\n\n/**\n * Reactively reads an ECS component from an entity.\n * Returns undefined if the entity doesn't have the component. Re-renders when the component changes.\n */\nexport function useComponent<T>(entity: EntityId, type: ComponentType<T>): T | undefined {\n\tconst engine = useLayoutEngine();\n\tconst [value, setValue] = useState<T | undefined>(() => engine.get(entity, type));\n\n\tuseEffect(() => {\n\t\tconst current = engine.get(entity, type);\n\t\tsetValue(current === undefined ? undefined : { ...current });\n\n\t\tconst unsub = engine.world.onComponentChanged(\n\t\t\ttype,\n\t\t\t(_id, _prev, next) => {\n\t\t\t\tsetValue(next === undefined ? undefined : { ...next });\n\t\t\t},\n\t\t\tentity,\n\t\t);\n\n\t\treturn unsub;\n\t}, [engine, entity, type]);\n\n\treturn value;\n}\n\n/**\n * Reactively checks whether an entity has a tag.\n * Re-renders when the tag is added or removed.\n */\nexport function useTag(entity: EntityId, type: TagType): boolean {\n\tconst engine = useLayoutEngine();\n\tconst [has, setHas] = useState(() => engine.world.hasTag(entity, type));\n\n\tuseEffect(() => {\n\t\tsetHas(engine.world.hasTag(entity, type));\n\n\t\tconst unsub1 = engine.world.onTagAdded(type, () => setHas(true), entity);\n\t\tconst unsub2 = engine.world.onTagRemoved(type, () => setHas(false), entity);\n\n\t\treturn () => {\n\t\t\tunsub1();\n\t\t\tunsub2();\n\t\t};\n\t}, [engine, entity, type]);\n\n\treturn has;\n}\n\n/**\n * Reactively reads an ECS resource (singleton data).\n * Re-renders when any field of the resource changes (shallow comparison).\n */\nexport function useResource<T>(type: ResourceType<T>): T {\n\tconst engine = useLayoutEngine();\n\tconst [value, setValue] = useState<T>(() => ({ ...engine.world.getResource(type) }));\n\tconst prevRef = useRef<T | undefined>(undefined);\n\n\tuseEffect(() => {\n\t\t// Immediately sync to current value on (re-)subscription\n\t\tconst current = engine.world.getResource(type);\n\t\tif (current !== undefined) {\n\t\t\tprevRef.current = current;\n\t\t\tsetValue({ ...current });\n\t\t}\n\n\t\tconst unsub = engine.onFrame(() => {\n\t\t\tconst current = engine.world.getResource(type);\n\t\t\tif (\n\t\t\t\tprevRef.current === undefined ||\n\t\t\t\t!shallowEqual(\n\t\t\t\t\tcurrent as unknown as Record<string, unknown>,\n\t\t\t\t\tprevRef.current as unknown as Record<string, unknown>,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tprevRef.current = current;\n\t\t\t\tsetValue({ ...current });\n\t\t\t}\n\t\t});\n\t\treturn unsub;\n\t}, [engine, type]);\n\n\treturn value;\n}\n\n/**\n * Returns entity IDs matching all specified component/tag types.\n * Re-renders when the result set changes.\n */\nexport function useQuery(...types: (ComponentType | TagType)[]): EntityId[] {\n\tconst engine = useLayoutEngine();\n\tconst typesRef = useRef(types);\n\ttypesRef.current = types;\n\tconst typesKey = types.map((t) => t.name).join('\\0');\n\n\tconst [result, setResult] = useState<EntityId[]>(() => engine.world.query(...types));\n\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: typesKey is a stable proxy for the types array\n\tuseEffect(() => {\n\t\tsetResult(engine.world.query(...typesRef.current));\n\n\t\tconst unsub = engine.onFrame(() => {\n\t\t\tconst next = engine.world.query(...typesRef.current);\n\t\t\tsetResult((prev) => {\n\t\t\t\tif (prev.length !== next.length) return next;\n\t\t\t\tfor (let i = 0; i < prev.length; i++) {\n\t\t\t\t\tif (prev[i] !== next[i]) return next;\n\t\t\t\t}\n\t\t\t\treturn prev;\n\t\t\t});\n\t\t});\n\t\treturn unsub;\n\t}, [engine, typesKey]);\n\n\treturn result;\n}\n\n/**\n * Returns all entity IDs that have the specified tag.\n * Re-renders when entities are tagged or untagged.\n */\nexport function useTaggedEntities(type: TagType): EntityId[] {\n\tconst engine = useLayoutEngine();\n\tconst [result, setResult] = useState<EntityId[]>(() => engine.world.queryTagged(type));\n\n\tuseEffect(() => {\n\t\tsetResult([...engine.world.queryTagged(type)]);\n\n\t\tconst update = () => setResult([...engine.world.queryTagged(type)]);\n\t\tconst unsub1 = engine.world.onTagAdded(type, update);\n\t\tconst unsub2 = engine.world.onTagRemoved(type, update);\n\t\treturn () => {\n\t\t\tunsub1();\n\t\t\tunsub2();\n\t\t};\n\t}, [engine, type]);\n\n\treturn result;\n}\n\n/**\n * Returns the current camera state {x, y, zoom}.\n * Shorthand for useResource(CameraResource).\n */\nexport function useCamera(): { x: number; y: number; zoom: number } {\n\tconst cam = useResource(CameraResource);\n\treturn { x: cam?.x ?? 0, y: cam?.y ?? 0, zoom: cam?.zoom ?? 1 };\n}\n\nfunction sameIdList(a: readonly number[], b: readonly number[]): boolean {\n\tif (a.length !== b.length) return false;\n\tfor (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n\treturn true;\n}\n\nfunction sameTypeList<T extends { name: string }>(a: readonly T[], b: readonly T[]): boolean {\n\tif (a.length !== b.length) return false;\n\tfor (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n\treturn true;\n}\n\n/**\n * Reactively returns the IDs of every live entity in the world.\n * Updates on entity create and destroy.\n */\nexport function useAllEntities(): EntityId[] {\n\tconst engine = useLayoutEngine();\n\tconst [entities, setEntities] = useState<EntityId[]>(() => engine.world.getAllEntities());\n\n\tuseEffect(() => {\n\t\tconst refresh = () => {\n\t\t\tconst next = engine.world.getAllEntities();\n\t\t\tsetEntities((prev) => (sameIdList(prev, next) ? prev : next));\n\t\t};\n\t\trefresh();\n\t\tconst unsub1 = engine.world.onEntityCreated(refresh);\n\t\tconst unsub2 = engine.world.onEntityDestroyed(refresh);\n\t\treturn () => {\n\t\t\tunsub1();\n\t\t\tunsub2();\n\t\t};\n\t}, [engine]);\n\n\treturn entities;\n}\n\n/**\n * Reactively returns the ComponentTypes currently attached to an entity.\n * Polls per frame (engine only ticks when dirty), but never re-renders unless the set changes.\n */\nexport function useEntityComponents(entity: EntityId): ComponentType[] {\n\tconst engine = useLayoutEngine();\n\tconst [types, setTypes] = useState<ComponentType[]>(() => engine.world.getComponentsOf(entity));\n\n\tuseEffect(() => {\n\t\tsetTypes(engine.world.getComponentsOf(entity));\n\t\tconst unsub = engine.onFrame(() => {\n\t\t\tconst next = engine.world.getComponentsOf(entity);\n\t\t\tsetTypes((prev) => (sameTypeList(prev, next) ? prev : next));\n\t\t});\n\t\treturn unsub;\n\t}, [engine, entity]);\n\n\treturn types;\n}\n\n/**\n * Reactively returns the TagTypes currently attached to an entity.\n */\nexport function useEntityTags(entity: EntityId): TagType[] {\n\tconst engine = useLayoutEngine();\n\tconst [types, setTypes] = useState<TagType[]>(() => engine.world.getTagsOf(entity));\n\n\tuseEffect(() => {\n\t\tsetTypes(engine.world.getTagsOf(entity));\n\t\tconst unsub = engine.onFrame(() => {\n\t\t\tconst next = engine.world.getTagsOf(entity);\n\t\t\tsetTypes((prev) => (sameTypeList(prev, next) ? prev : next));\n\t\t});\n\t\treturn unsub;\n\t}, [engine, entity]);\n\n\treturn types;\n}\n\n/**\n * Reactively returns every ComponentType the world has observed.\n * Grows over time as new component types are first used.\n */\nexport function useRegisteredComponents(): ComponentType[] {\n\tconst engine = useLayoutEngine();\n\tconst [types, setTypes] = useState<ComponentType[]>(() => engine.world.getRegisteredComponents());\n\n\tuseEffect(() => {\n\t\tsetTypes(engine.world.getRegisteredComponents());\n\t\tconst unsub = engine.onFrame(() => {\n\t\t\tconst next = engine.world.getRegisteredComponents();\n\t\t\tsetTypes((prev) => (sameTypeList(prev, next) ? prev : next));\n\t\t});\n\t\treturn unsub;\n\t}, [engine]);\n\n\treturn types;\n}\n\n/**\n * Reactively returns every TagType the world has observed.\n */\nexport function useRegisteredTags(): TagType[] {\n\tconst engine = useLayoutEngine();\n\tconst [types, setTypes] = useState<TagType[]>(() => engine.world.getRegisteredTags());\n\n\tuseEffect(() => {\n\t\tsetTypes(engine.world.getRegisteredTags());\n\t\tconst unsub = engine.onFrame(() => {\n\t\t\tconst next = engine.world.getRegisteredTags();\n\t\t\tsetTypes((prev) => (sameTypeList(prev, next) ? prev : next));\n\t\t});\n\t\treturn unsub;\n\t}, [engine]);\n\n\treturn types;\n}\n"],"mappings":"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"}
|