@vibecook/ice 0.1.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/LICENSE +21 -0
- package/README.md +76 -0
- package/dist/chunk-5S7GSXJG.js +6003 -0
- package/dist/chunk-5S7GSXJG.js.map +1 -0
- package/dist/chunk-BCJ47SX7.js +550 -0
- package/dist/chunk-BCJ47SX7.js.map +1 -0
- package/dist/chunk-FIAXPNNY.js +1446 -0
- package/dist/chunk-FIAXPNNY.js.map +1 -0
- package/dist/devtools.js +417 -0
- package/dist/devtools.js.map +1 -0
- package/dist/dom.js +41 -0
- package/dist/dom.js.map +1 -0
- package/dist/index.js +450 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel.js +63 -0
- package/dist/kernel.js.map +1 -0
- package/dist/r3f.js +1101 -0
- package/dist/r3f.js.map +1 -0
- package/dist/react.js +440 -0
- package/dist/react.js.map +1 -0
- package/dist/types/packages/core/src/boot/hmr.d.ts +12 -0
- package/dist/types/packages/core/src/catalog/camera-derived.d.ts +144 -0
- package/dist/types/packages/core/src/catalog/gesture.d.ts +254 -0
- package/dist/types/packages/core/src/catalog/graph.d.ts +55 -0
- package/dist/types/packages/core/src/catalog/index.d.ts +32 -0
- package/dist/types/packages/core/src/catalog/pointer.d.ts +111 -0
- package/dist/types/packages/core/src/catalog/scene.d.ts +34 -0
- package/dist/types/packages/core/src/catalog/selection-presence.d.ts +53 -0
- package/dist/types/packages/core/src/catalog/settings-resources.d.ts +46 -0
- package/dist/types/packages/core/src/doc/autosave.d.ts +69 -0
- package/dist/types/packages/core/src/doc/bootstrap.d.ts +74 -0
- package/dist/types/packages/core/src/doc/broadcast-relay.d.ts +52 -0
- package/dist/types/packages/core/src/doc/channels.d.ts +49 -0
- package/dist/types/packages/core/src/doc/doc-commit-sink.d.ts +22 -0
- package/dist/types/packages/core/src/doc/doc-kit.d.ts +50 -0
- package/dist/types/packages/core/src/doc/envelope.d.ts +36 -0
- package/dist/types/packages/core/src/doc/migrate.d.ts +69 -0
- package/dist/types/packages/core/src/doc/version-gate.d.ts +49 -0
- package/dist/types/packages/core/src/engine/commit-sink.d.ts +61 -0
- package/dist/types/packages/core/src/engine/engine.d.ts +62 -0
- package/dist/types/packages/core/src/engine/frame-info.d.ts +16 -0
- package/dist/types/packages/core/src/engine/instantiate.d.ts +45 -0
- package/dist/types/packages/core/src/engine/pipeline.d.ts +17 -0
- package/dist/types/packages/core/src/engine/reflectors.d.ts +56 -0
- package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +120 -0
- package/dist/types/packages/core/src/guards/dev.d.ts +2 -0
- package/dist/types/packages/core/src/guards/guarded-tx.d.ts +51 -0
- package/dist/types/packages/core/src/guards/live-writer.d.ts +33 -0
- package/dist/types/packages/core/src/guards/resource-writer.d.ts +17 -0
- package/dist/types/packages/core/src/helpers/phase-set.d.ts +70 -0
- package/dist/types/packages/core/src/helpers/version-stamps.d.ts +55 -0
- package/dist/types/packages/core/src/index.d.ts +63 -0
- package/dist/types/packages/core/src/input/measure-queue.d.ts +30 -0
- package/dist/types/packages/core/src/input/queue.d.ts +60 -0
- package/dist/types/packages/core/src/interaction/install.d.ts +58 -0
- package/dist/types/packages/core/src/nav/nested-canvas.d.ts +62 -0
- package/dist/types/packages/core/src/ops/cascade.d.ts +18 -0
- package/dist/types/packages/core/src/ops/claims.d.ts +15 -0
- package/dist/types/packages/core/src/ops/gestures.d.ts +10 -0
- package/dist/types/packages/core/src/ops/point-pick.d.ts +42 -0
- package/dist/types/packages/core/src/ops/port-geometry.d.ts +17 -0
- package/dist/types/packages/core/src/ops/selection.d.ts +14 -0
- package/dist/types/packages/core/src/presence/index.d.ts +7 -0
- package/dist/types/packages/core/src/presence/presence-kit.d.ts +39 -0
- package/dist/types/packages/core/src/presence/publish.d.ts +14 -0
- package/dist/types/packages/core/src/presence/remote-cursors.d.ts +18 -0
- package/dist/types/packages/core/src/schema/meta.d.ts +43 -0
- package/dist/types/packages/core/src/schema/prefab.d.ts +69 -0
- package/dist/types/packages/core/src/settings/defaults.d.ts +56 -0
- package/dist/types/packages/core/src/systems/camera-sim.d.ts +19 -0
- package/dist/types/packages/core/src/systems/chrome.d.ts +48 -0
- package/dist/types/packages/core/src/systems/cleanup.d.ts +19 -0
- package/dist/types/packages/core/src/systems/l0-input.d.ts +31 -0
- package/dist/types/packages/core/src/systems/l1-pick.d.ts +44 -0
- package/dist/types/packages/core/src/systems/l1-wires.d.ts +30 -0
- package/dist/types/packages/core/src/systems/l2-arbitrate.d.ts +40 -0
- package/dist/types/packages/core/src/systems/l2-recognize.d.ts +45 -0
- package/dist/types/packages/core/src/systems/l3-behave.d.ts +24 -0
- package/dist/types/packages/core/src/systems/l3-claim.d.ts +26 -0
- package/dist/types/packages/core/src/systems/l3-connect.d.ts +57 -0
- package/dist/types/packages/core/src/systems/l3-draw.d.ts +21 -0
- package/dist/types/packages/core/src/systems/l3-drop.d.ts +21 -0
- package/dist/types/packages/core/src/systems/l3-marquee.d.ts +35 -0
- package/dist/types/packages/core/src/systems/l3-ports.d.ts +30 -0
- package/dist/types/packages/core/src/systems/l3-resize.d.ts +17 -0
- package/dist/types/packages/core/src/systems/l3-snap.d.ts +19 -0
- package/dist/types/packages/core/src/systems/l4-cursor.d.ts +24 -0
- package/dist/types/packages/core/src/systems/measure-ingest.d.ts +26 -0
- package/dist/types/packages/core/src/tools/define-tool.d.ts +79 -0
- package/dist/types/packages/core/src/widget/define-widget.d.ts +124 -0
- package/dist/types/packages/core/src/widget/equip.d.ts +14 -0
- package/dist/types/packages/core/src/widget/mount-store.d.ts +47 -0
- package/dist/types/packages/core/src/widget/props.d.ts +98 -0
- package/dist/types/packages/core/src/widget/spawn.d.ts +30 -0
- package/dist/types/packages/devtools/src/index.d.ts +8 -0
- package/dist/types/packages/devtools/src/panel.d.ts +48 -0
- package/dist/types/packages/dom/src/host.d.ts +24 -0
- package/dist/types/packages/dom/src/index.d.ts +19 -0
- package/dist/types/packages/dom/src/loop.d.ts +11 -0
- package/dist/types/packages/dom/src/measure-adapter.d.ts +31 -0
- package/dist/types/packages/dom/src/measure-wiring.d.ts +49 -0
- package/dist/types/packages/dom/src/planes.d.ts +33 -0
- package/dist/types/packages/dom/src/pointer-adapter.d.ts +48 -0
- package/dist/types/packages/dom/src/reflectors/chrome.d.ts +33 -0
- package/dist/types/packages/dom/src/reflectors/cursor.d.ts +21 -0
- package/dist/types/packages/dom/src/reflectors/dom-widgets.d.ts +54 -0
- package/dist/types/packages/dom/src/reflectors/graybox.d.ts +31 -0
- package/dist/types/packages/dom/src/reflectors/grid.d.ts +63 -0
- package/dist/types/packages/dom/src/reflectors/plane-transform.d.ts +38 -0
- package/dist/types/packages/dom/src/reflectors/remote-cursors.d.ts +20 -0
- package/dist/types/packages/dom/src/reflectors/wires.d.ts +88 -0
- package/dist/types/packages/ice/src/devtools.d.ts +1 -0
- package/dist/types/packages/ice/src/dom.d.ts +1 -0
- package/dist/types/packages/ice/src/index.d.ts +1 -0
- package/dist/types/packages/ice/src/kernel.d.ts +1 -0
- package/dist/types/packages/ice/src/r3f.d.ts +1 -0
- package/dist/types/packages/ice/src/react.d.ts +1 -0
- package/dist/types/packages/kernel/src/anchors.d.ts +18 -0
- package/dist/types/packages/kernel/src/bezier.d.ts +38 -0
- package/dist/types/packages/kernel/src/coords.d.ts +67 -0
- package/dist/types/packages/kernel/src/eviction.d.ts +23 -0
- package/dist/types/packages/kernel/src/index.d.ts +13 -0
- package/dist/types/packages/kernel/src/shapes.d.ts +22 -0
- package/dist/types/packages/kernel/src/snap.d.ts +44 -0
- package/dist/types/packages/kernel/src/spatial-index.d.ts +16 -0
- package/dist/types/packages/kernel/src/zoom-bands.d.ts +19 -0
- package/dist/types/packages/r3f/src/bridge.d.ts +68 -0
- package/dist/types/packages/r3f/src/composite-material.d.ts +17 -0
- package/dist/types/packages/r3f/src/compositor-pass.d.ts +83 -0
- package/dist/types/packages/r3f/src/dev-write-trap.d.ts +30 -0
- package/dist/types/packages/r3f/src/gl-root.d.ts +21 -0
- package/dist/types/packages/r3f/src/gl-router.d.ts +60 -0
- package/dist/types/packages/r3f/src/index.d.ts +16 -0
- package/dist/types/packages/r3f/src/island-state.d.ts +68 -0
- package/dist/types/packages/r3f/src/island.d.ts +13 -0
- package/dist/types/packages/r3f/src/pool.d.ts +56 -0
- package/dist/types/packages/r3f/src/resource-registry.d.ts +30 -0
- package/dist/types/packages/r3f/src/use-island-frame.d.ts +24 -0
- package/dist/types/packages/react/src/engine-context.d.ts +31 -0
- package/dist/types/packages/react/src/hooks.d.ts +40 -0
- package/dist/types/packages/react/src/index.d.ts +14 -0
- package/dist/types/packages/react/src/infinite-canvas.d.ts +71 -0
- package/dist/types/packages/react/src/keymap.d.ts +42 -0
- package/dist/types/packages/react/src/use-commit.d.ts +35 -0
- package/dist/types/packages/react/src/use-presence.d.ts +23 -0
- package/dist/types/packages/react/src/use-tool.d.ts +4 -0
- package/dist/types/packages/react/src/widget-root.d.ts +33 -0
- package/package.json +112 -0
|
@@ -0,0 +1,1446 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Camera,
|
|
3
|
+
CursorVisual,
|
|
4
|
+
Follows,
|
|
5
|
+
Grab,
|
|
6
|
+
HandleSpec,
|
|
7
|
+
MeasuredSize,
|
|
8
|
+
NO_MODS,
|
|
9
|
+
Port,
|
|
10
|
+
PortAnchor,
|
|
11
|
+
Position,
|
|
12
|
+
PrefabId,
|
|
13
|
+
PresenceInfo,
|
|
14
|
+
RoutedConnect,
|
|
15
|
+
Selected,
|
|
16
|
+
SelectionBox,
|
|
17
|
+
Size,
|
|
18
|
+
Wire,
|
|
19
|
+
WireFrom,
|
|
20
|
+
WirePorts,
|
|
21
|
+
WireTo,
|
|
22
|
+
defineQuery,
|
|
23
|
+
widgets
|
|
24
|
+
} from "./chunk-5S7GSXJG.js";
|
|
25
|
+
import {
|
|
26
|
+
planeCssTransform,
|
|
27
|
+
portAnchor,
|
|
28
|
+
wireCubic,
|
|
29
|
+
worldToScreen
|
|
30
|
+
} from "./chunk-BCJ47SX7.js";
|
|
31
|
+
|
|
32
|
+
// ../dom/src/host.ts
|
|
33
|
+
var CONTAINER_STYLE = {
|
|
34
|
+
overflow: "hidden",
|
|
35
|
+
touchAction: "none",
|
|
36
|
+
userSelect: "none"
|
|
37
|
+
};
|
|
38
|
+
var PLANE_STYLE = {
|
|
39
|
+
position: "absolute",
|
|
40
|
+
left: "0",
|
|
41
|
+
top: "0",
|
|
42
|
+
transformOrigin: "0 0",
|
|
43
|
+
willChange: "transform"
|
|
44
|
+
};
|
|
45
|
+
function createCanvasHost(container) {
|
|
46
|
+
const view = container.ownerDocument.defaultView;
|
|
47
|
+
const position = container.style.position !== "" ? container.style.position : view?.getComputedStyle(container).position ?? "";
|
|
48
|
+
const promoteToRelative = position === "" || position === "static";
|
|
49
|
+
if (promoteToRelative) container.style.position = "relative";
|
|
50
|
+
Object.assign(container.style, CONTAINER_STYLE);
|
|
51
|
+
const contentPlane = container.ownerDocument.createElement("div");
|
|
52
|
+
Object.assign(contentPlane.style, PLANE_STYLE);
|
|
53
|
+
container.appendChild(contentPlane);
|
|
54
|
+
return {
|
|
55
|
+
container,
|
|
56
|
+
contentPlane,
|
|
57
|
+
dispose() {
|
|
58
|
+
contentPlane.remove();
|
|
59
|
+
if (promoteToRelative) container.style.removeProperty("position");
|
|
60
|
+
for (const prop of Object.keys(CONTAINER_STYLE)) {
|
|
61
|
+
container.style.removeProperty(
|
|
62
|
+
// camelCase → kebab-case for removeProperty (touchAction → touch-action).
|
|
63
|
+
prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ../dom/src/planes.ts
|
|
71
|
+
var LIFTED_PLANE_STYLE = {
|
|
72
|
+
position: "absolute",
|
|
73
|
+
left: "0",
|
|
74
|
+
top: "0",
|
|
75
|
+
transformOrigin: "0 0",
|
|
76
|
+
willChange: "transform"
|
|
77
|
+
};
|
|
78
|
+
function createPlanes(host) {
|
|
79
|
+
const lifted = host.container.ownerDocument.createElement("div");
|
|
80
|
+
Object.assign(lifted.style, LIFTED_PLANE_STYLE);
|
|
81
|
+
host.container.appendChild(lifted);
|
|
82
|
+
return {
|
|
83
|
+
content: host.contentPlane,
|
|
84
|
+
lifted,
|
|
85
|
+
// Without this a React StrictMode remount stacks a second P3
|
|
86
|
+
// (2026-07-11 reflector-teardown sweep, with the grid-canvas fix).
|
|
87
|
+
dispose() {
|
|
88
|
+
lifted.remove();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ../dom/src/reflectors/plane-transform.ts
|
|
94
|
+
function createPlaneTransformReflector(host) {
|
|
95
|
+
let writes = 0;
|
|
96
|
+
return {
|
|
97
|
+
name: "plane-transform",
|
|
98
|
+
observe: { resources: [Camera] },
|
|
99
|
+
flush(world) {
|
|
100
|
+
const camera = world.getResource(Camera);
|
|
101
|
+
if (camera === void 0) return;
|
|
102
|
+
const { tx, ty, scale } = planeCssTransform(camera);
|
|
103
|
+
const transform = `translate(${tx}px, ${ty}px) scale(${scale})`;
|
|
104
|
+
host.contentPlane.style.transform = transform;
|
|
105
|
+
if (host.liftedPlane !== void 0) host.liftedPlane.style.transform = transform;
|
|
106
|
+
writes++;
|
|
107
|
+
},
|
|
108
|
+
transformWrites: () => writes
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ../dom/src/reflectors/graybox.ts
|
|
113
|
+
var grayboxQuery = defineQuery([Position, Size]);
|
|
114
|
+
function fillFor(entity) {
|
|
115
|
+
const n = entity;
|
|
116
|
+
const hash = (Math.imul(n, 2654435761) >>> 0) % 360;
|
|
117
|
+
return `hsl(${hash}, 55%, 55%)`;
|
|
118
|
+
}
|
|
119
|
+
function writeGeom(div, x, y, w, h) {
|
|
120
|
+
div.style.left = `${x}px`;
|
|
121
|
+
div.style.top = `${y}px`;
|
|
122
|
+
div.style.width = `${w}px`;
|
|
123
|
+
div.style.height = `${h}px`;
|
|
124
|
+
}
|
|
125
|
+
function createGrayboxReflector(host) {
|
|
126
|
+
const divs = /* @__PURE__ */ new Map();
|
|
127
|
+
const geom = /* @__PURE__ */ new Map();
|
|
128
|
+
const seen = /* @__PURE__ */ new Set();
|
|
129
|
+
const doc = host.container.ownerDocument;
|
|
130
|
+
let styleWrites = 0;
|
|
131
|
+
return {
|
|
132
|
+
name: "graybox",
|
|
133
|
+
observe: { queries: [{ q: grayboxQuery, cols: [Position, Size] }] },
|
|
134
|
+
flush(world) {
|
|
135
|
+
seen.clear();
|
|
136
|
+
let fragment = null;
|
|
137
|
+
world.query(grayboxQuery).each((batch) => {
|
|
138
|
+
const px = batch.col(Position).x;
|
|
139
|
+
const py = batch.col(Position).y;
|
|
140
|
+
const sw = batch.col(Size).w;
|
|
141
|
+
const sh = batch.col(Size).h;
|
|
142
|
+
for (const r of batch) {
|
|
143
|
+
const entity = batch.entity(r);
|
|
144
|
+
seen.add(entity);
|
|
145
|
+
const x = px[r];
|
|
146
|
+
const y = py[r];
|
|
147
|
+
const w = sw[r];
|
|
148
|
+
const h = sh[r];
|
|
149
|
+
const existing = divs.get(entity);
|
|
150
|
+
if (existing === void 0) {
|
|
151
|
+
const div = doc.createElement("div");
|
|
152
|
+
div.style.position = "absolute";
|
|
153
|
+
div.style.background = fillFor(entity);
|
|
154
|
+
writeGeom(div, x, y, w, h);
|
|
155
|
+
styleWrites++;
|
|
156
|
+
divs.set(entity, div);
|
|
157
|
+
geom.set(entity, { x, y, w, h });
|
|
158
|
+
if (fragment === null) fragment = doc.createDocumentFragment();
|
|
159
|
+
fragment.appendChild(div);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const prev = geom.get(entity);
|
|
163
|
+
if (prev === void 0) {
|
|
164
|
+
writeGeom(existing, x, y, w, h);
|
|
165
|
+
styleWrites++;
|
|
166
|
+
geom.set(entity, { x, y, w, h });
|
|
167
|
+
} else if (prev.x !== x || prev.y !== y || prev.w !== w || prev.h !== h) {
|
|
168
|
+
writeGeom(existing, x, y, w, h);
|
|
169
|
+
styleWrites++;
|
|
170
|
+
prev.x = x;
|
|
171
|
+
prev.y = y;
|
|
172
|
+
prev.w = w;
|
|
173
|
+
prev.h = h;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
if (fragment !== null) host.contentPlane.appendChild(fragment);
|
|
178
|
+
for (const [entity, div] of divs) {
|
|
179
|
+
if (seen.has(entity)) continue;
|
|
180
|
+
div.remove();
|
|
181
|
+
divs.delete(entity);
|
|
182
|
+
geom.delete(entity);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
styleWrites: () => styleWrites,
|
|
186
|
+
nodeCount: () => divs.size
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ../dom/src/reflectors/dom-widgets.ts
|
|
191
|
+
var geometryQuery = defineQuery([Position, Size]);
|
|
192
|
+
var measuredQuery = defineQuery([MeasuredSize]);
|
|
193
|
+
var grabQuery = defineQuery([Grab]);
|
|
194
|
+
function writeGeom2(el, x, y, w, h) {
|
|
195
|
+
el.style.left = `${x}px`;
|
|
196
|
+
el.style.top = `${y}px`;
|
|
197
|
+
el.style.width = `${w}px`;
|
|
198
|
+
el.style.height = `${h}px`;
|
|
199
|
+
}
|
|
200
|
+
function createDomWidgetsReflector(host, world, store) {
|
|
201
|
+
const doc = host.contentPlane.ownerDocument;
|
|
202
|
+
const hosts = /* @__PURE__ */ new Map();
|
|
203
|
+
let lastSnapshot;
|
|
204
|
+
let geometryWrites = 0;
|
|
205
|
+
let inert = false;
|
|
206
|
+
let geometryDirty = false;
|
|
207
|
+
let promoteDirty = false;
|
|
208
|
+
const unsubs = [
|
|
209
|
+
world.reactive.observeQuery(geometryQuery, () => {
|
|
210
|
+
geometryDirty = true;
|
|
211
|
+
}, { cols: [Position, Size] }),
|
|
212
|
+
// MeasuredSize rides its own observer: adding it to geometryQuery's cols
|
|
213
|
+
// would require it in the query and drop widgets without the rider.
|
|
214
|
+
world.reactive.observeQuery(measuredQuery, () => {
|
|
215
|
+
geometryDirty = true;
|
|
216
|
+
}, { cols: [MeasuredSize] }),
|
|
217
|
+
world.reactive.observeQuery(grabQuery, () => {
|
|
218
|
+
promoteDirty = true;
|
|
219
|
+
}, { cols: [] })
|
|
220
|
+
];
|
|
221
|
+
function readGeom(e) {
|
|
222
|
+
const p = world.get(e, Position);
|
|
223
|
+
const measured = world.get(e, MeasuredSize);
|
|
224
|
+
const s = measured !== void 0 && measured.w > 0 ? measured : world.get(e, Size);
|
|
225
|
+
return { x: p?.x ?? 0, y: p?.y ?? 0, w: s?.w ?? 0, h: s?.h ?? 0 };
|
|
226
|
+
}
|
|
227
|
+
function createHost(e) {
|
|
228
|
+
const el = doc.createElement("div");
|
|
229
|
+
el.style.position = "absolute";
|
|
230
|
+
el.setAttribute("data-ice-entity", String(e));
|
|
231
|
+
const content = doc.createElement("div");
|
|
232
|
+
content.setAttribute("data-ice-content", "");
|
|
233
|
+
content.style.width = "100%";
|
|
234
|
+
content.style.height = "100%";
|
|
235
|
+
el.appendChild(content);
|
|
236
|
+
const g = readGeom(e);
|
|
237
|
+
writeGeom2(el, g.x, g.y, g.w, g.h);
|
|
238
|
+
geometryWrites++;
|
|
239
|
+
const lifted = world.has(e, Grab);
|
|
240
|
+
(lifted ? host.liftedPlane : host.contentPlane).appendChild(el);
|
|
241
|
+
return { host: el, content, x: g.x, y: g.y, w: g.w, h: g.h, hidden: false, lifted };
|
|
242
|
+
}
|
|
243
|
+
function reconcile(snapshot) {
|
|
244
|
+
let membershipChanged = false;
|
|
245
|
+
const present = /* @__PURE__ */ new Set();
|
|
246
|
+
for (const entry of snapshot) {
|
|
247
|
+
present.add(entry.entity);
|
|
248
|
+
let rec = hosts.get(entry.entity);
|
|
249
|
+
if (rec === void 0) {
|
|
250
|
+
rec = createHost(entry.entity);
|
|
251
|
+
hosts.set(entry.entity, rec);
|
|
252
|
+
membershipChanged = true;
|
|
253
|
+
}
|
|
254
|
+
if (entry.hidden !== rec.hidden) {
|
|
255
|
+
rec.hidden = entry.hidden;
|
|
256
|
+
rec.host.style.display = entry.hidden ? "none" : "";
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const [e, rec] of hosts) {
|
|
260
|
+
if (present.has(e)) continue;
|
|
261
|
+
rec.host.remove();
|
|
262
|
+
hosts.delete(e);
|
|
263
|
+
membershipChanged = true;
|
|
264
|
+
}
|
|
265
|
+
return membershipChanged;
|
|
266
|
+
}
|
|
267
|
+
function updateGeometry() {
|
|
268
|
+
for (const [e, rec] of hosts) {
|
|
269
|
+
const g = readGeom(e);
|
|
270
|
+
if (g.x !== rec.x || g.y !== rec.y || g.w !== rec.w || g.h !== rec.h) {
|
|
271
|
+
writeGeom2(rec.host, g.x, g.y, g.w, g.h);
|
|
272
|
+
rec.x = g.x;
|
|
273
|
+
rec.y = g.y;
|
|
274
|
+
rec.w = g.w;
|
|
275
|
+
rec.h = g.h;
|
|
276
|
+
geometryWrites++;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function updatePromote() {
|
|
281
|
+
let anyLifted = false;
|
|
282
|
+
for (const [e, rec] of hosts) {
|
|
283
|
+
const shouldLift = world.has(e, Grab);
|
|
284
|
+
if (shouldLift) anyLifted = true;
|
|
285
|
+
if (shouldLift !== rec.lifted) {
|
|
286
|
+
rec.lifted = shouldLift;
|
|
287
|
+
(shouldLift ? host.liftedPlane : host.contentPlane).appendChild(rec.host);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (anyLifted !== inert) {
|
|
291
|
+
inert = anyLifted;
|
|
292
|
+
host.contentPlane.style.pointerEvents = inert ? "none" : "";
|
|
293
|
+
host.liftedPlane.style.pointerEvents = inert ? "none" : "";
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
name: "domWidgets",
|
|
298
|
+
always: true,
|
|
299
|
+
// store membership has no ECS stamp; the flush self-gates cheaply
|
|
300
|
+
flush(_world) {
|
|
301
|
+
const snapshot = store.getSnapshot();
|
|
302
|
+
let membershipChanged = false;
|
|
303
|
+
if (snapshot !== lastSnapshot) {
|
|
304
|
+
lastSnapshot = snapshot;
|
|
305
|
+
membershipChanged = reconcile(snapshot);
|
|
306
|
+
}
|
|
307
|
+
if (geometryDirty) {
|
|
308
|
+
geometryDirty = false;
|
|
309
|
+
updateGeometry();
|
|
310
|
+
}
|
|
311
|
+
if (promoteDirty || membershipChanged) {
|
|
312
|
+
promoteDirty = false;
|
|
313
|
+
updatePromote();
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
hostFor: (entity) => hosts.get(entity)?.content,
|
|
317
|
+
hostCount: () => hosts.size,
|
|
318
|
+
geometryWrites: () => geometryWrites,
|
|
319
|
+
dispose() {
|
|
320
|
+
for (const u of unsubs) u();
|
|
321
|
+
unsubs.length = 0;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ../dom/src/loop.ts
|
|
327
|
+
function startRafLoop(engine) {
|
|
328
|
+
let handle = 0;
|
|
329
|
+
let stopped = false;
|
|
330
|
+
const tick = (now) => {
|
|
331
|
+
if (stopped) return;
|
|
332
|
+
engine.step(now);
|
|
333
|
+
handle = requestAnimationFrame(tick);
|
|
334
|
+
};
|
|
335
|
+
handle = requestAnimationFrame(tick);
|
|
336
|
+
return () => {
|
|
337
|
+
stopped = true;
|
|
338
|
+
cancelAnimationFrame(handle);
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ../dom/src/pointer-adapter.ts
|
|
343
|
+
var WHEEL_LINE_PX = 16;
|
|
344
|
+
var INTERACTIVE_SELECTOR = 'input, textarea, select, button, a[href], [contenteditable=""], [contenteditable="true"], audio[controls], video[controls], [data-canvas-interactive]';
|
|
345
|
+
function crossesInteractive(target, container) {
|
|
346
|
+
if (!(target instanceof Element)) return false;
|
|
347
|
+
const match = target.closest(INTERACTIVE_SELECTOR);
|
|
348
|
+
return match !== null && container.contains(match);
|
|
349
|
+
}
|
|
350
|
+
function pointerIdOf(e) {
|
|
351
|
+
if (e.pointerType === "touch") return `touch:${e.pointerId}`;
|
|
352
|
+
if (e.pointerType === "pen") return "pen";
|
|
353
|
+
return "mouse";
|
|
354
|
+
}
|
|
355
|
+
function deviceOf(e) {
|
|
356
|
+
if (e.pointerType === "touch") return "touch";
|
|
357
|
+
if (e.pointerType === "pen") return "pen";
|
|
358
|
+
return "mouse";
|
|
359
|
+
}
|
|
360
|
+
function attachPointerAdapter(host, queue, opts = {}) {
|
|
361
|
+
const { container } = host;
|
|
362
|
+
const view = container.ownerDocument.defaultView;
|
|
363
|
+
const { glRoute } = opts;
|
|
364
|
+
let spaceHeld = false;
|
|
365
|
+
const live = /* @__PURE__ */ new Map();
|
|
366
|
+
const CAPTURE_SLOP_PX = 4;
|
|
367
|
+
const capture = (pointerId) => {
|
|
368
|
+
if (typeof container.setPointerCapture !== "function") return;
|
|
369
|
+
try {
|
|
370
|
+
container.setPointerCapture(pointerId);
|
|
371
|
+
} catch {
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
let lastMods = { shift: false, ctrl: false, alt: false, meta: false, space: false };
|
|
375
|
+
const relative = (clientX, clientY) => {
|
|
376
|
+
const rect = container.getBoundingClientRect();
|
|
377
|
+
return { x: clientX - rect.left, y: clientY - rect.top };
|
|
378
|
+
};
|
|
379
|
+
const pointerMods = (e) => ({
|
|
380
|
+
shift: e.shiftKey,
|
|
381
|
+
ctrl: e.ctrlKey,
|
|
382
|
+
alt: e.altKey,
|
|
383
|
+
meta: e.metaKey,
|
|
384
|
+
space: spaceHeld
|
|
385
|
+
});
|
|
386
|
+
const onPointerDown = (e) => {
|
|
387
|
+
const id = pointerIdOf(e);
|
|
388
|
+
const device = deviceOf(e);
|
|
389
|
+
const { x, y } = relative(e.clientX, e.clientY);
|
|
390
|
+
const native = crossesInteractive(e.target, container);
|
|
391
|
+
const glClaimed = glRoute?.("down", x, y, e) === true;
|
|
392
|
+
live.set(id, { device, x, y, downX: x, downY: y, native, captured: glClaimed });
|
|
393
|
+
if (glClaimed) capture(e.pointerId);
|
|
394
|
+
const surfaceHandled = native || glClaimed;
|
|
395
|
+
queue.enqueue({
|
|
396
|
+
kind: "down",
|
|
397
|
+
pointerId: id,
|
|
398
|
+
device,
|
|
399
|
+
screenX: x,
|
|
400
|
+
screenY: y,
|
|
401
|
+
buttons: e.buttons,
|
|
402
|
+
mods: pointerMods(e),
|
|
403
|
+
tMs: e.timeStamp,
|
|
404
|
+
...surfaceHandled ? { surfaceHandled: true } : {}
|
|
405
|
+
});
|
|
406
|
+
};
|
|
407
|
+
const onPointerMove = (e) => {
|
|
408
|
+
const id = pointerIdOf(e);
|
|
409
|
+
const device = deviceOf(e);
|
|
410
|
+
const { x, y } = relative(e.clientX, e.clientY);
|
|
411
|
+
const seen = live.get(id);
|
|
412
|
+
if (seen !== void 0) {
|
|
413
|
+
seen.x = x;
|
|
414
|
+
seen.y = y;
|
|
415
|
+
if (!seen.captured && !seen.native && e.buttons !== 0 && Math.hypot(x - seen.downX, y - seen.downY) > CAPTURE_SLOP_PX) {
|
|
416
|
+
capture(e.pointerId);
|
|
417
|
+
seen.captured = true;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const glHandled = glRoute?.("move", x, y, e) === true;
|
|
421
|
+
queue.enqueue({
|
|
422
|
+
kind: "move",
|
|
423
|
+
pointerId: id,
|
|
424
|
+
device,
|
|
425
|
+
screenX: x,
|
|
426
|
+
screenY: y,
|
|
427
|
+
buttons: e.buttons,
|
|
428
|
+
mods: pointerMods(e),
|
|
429
|
+
tMs: e.timeStamp,
|
|
430
|
+
...glHandled ? { surfaceHandled: true } : {}
|
|
431
|
+
});
|
|
432
|
+
};
|
|
433
|
+
const endPointer = (kind) => (e) => {
|
|
434
|
+
const id = pointerIdOf(e);
|
|
435
|
+
const device = deviceOf(e);
|
|
436
|
+
const { x, y } = relative(e.clientX, e.clientY);
|
|
437
|
+
live.delete(id);
|
|
438
|
+
if (typeof container.releasePointerCapture === "function") {
|
|
439
|
+
try {
|
|
440
|
+
container.releasePointerCapture(e.pointerId);
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const glHandled = glRoute?.(kind, x, y, e) === true;
|
|
445
|
+
queue.enqueue({
|
|
446
|
+
kind,
|
|
447
|
+
pointerId: id,
|
|
448
|
+
device,
|
|
449
|
+
screenX: x,
|
|
450
|
+
screenY: y,
|
|
451
|
+
buttons: e.buttons,
|
|
452
|
+
mods: pointerMods(e),
|
|
453
|
+
...glHandled ? { surfaceHandled: true } : {}
|
|
454
|
+
});
|
|
455
|
+
};
|
|
456
|
+
const onPointerUp = endPointer("up");
|
|
457
|
+
const onPointerCancel = endPointer("cancel");
|
|
458
|
+
const onWheel = (e) => {
|
|
459
|
+
e.preventDefault();
|
|
460
|
+
const { x, y } = relative(e.clientX, e.clientY);
|
|
461
|
+
const scale = e.deltaMode === 1 ? WHEEL_LINE_PX : 1;
|
|
462
|
+
const wheel = e.ctrlKey ? { dx: 0, dy: 0, pinch: e.deltaY } : { dx: e.deltaX * scale, dy: e.deltaY * scale, pinch: 0 };
|
|
463
|
+
queue.enqueue({ kind: "wheel", pointerId: "mouse", device: "mouse", screenX: x, screenY: y, buttons: e.buttons, mods: pointerMods(e), wheel });
|
|
464
|
+
};
|
|
465
|
+
const emitKeyIfChanged = (e) => {
|
|
466
|
+
const mods = { shift: e.shiftKey, ctrl: e.ctrlKey, alt: e.altKey, meta: e.metaKey, space: spaceHeld };
|
|
467
|
+
if (mods.shift === lastMods.shift && mods.ctrl === lastMods.ctrl && mods.alt === lastMods.alt && mods.meta === lastMods.meta && mods.space === lastMods.space) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
lastMods = { ...mods };
|
|
471
|
+
queue.enqueue({ kind: "key", pointerId: "", device: "mouse", screenX: 0, screenY: 0, buttons: 0, mods });
|
|
472
|
+
};
|
|
473
|
+
const isSpace = (e) => e.key === " " || e.code === "Space";
|
|
474
|
+
const onKeyDown = (e) => {
|
|
475
|
+
if (isSpace(e)) {
|
|
476
|
+
e.preventDefault();
|
|
477
|
+
spaceHeld = true;
|
|
478
|
+
}
|
|
479
|
+
emitKeyIfChanged(e);
|
|
480
|
+
};
|
|
481
|
+
const onKeyUp = (e) => {
|
|
482
|
+
if (isSpace(e)) {
|
|
483
|
+
e.preventDefault();
|
|
484
|
+
spaceHeld = false;
|
|
485
|
+
}
|
|
486
|
+
emitKeyIfChanged(e);
|
|
487
|
+
};
|
|
488
|
+
const onBlur = () => {
|
|
489
|
+
for (const [id, s] of live) {
|
|
490
|
+
queue.enqueue({ kind: "cancel", pointerId: id, device: s.device, screenX: s.x, screenY: s.y, buttons: 0, mods: { ...NO_MODS, space: spaceHeld } });
|
|
491
|
+
}
|
|
492
|
+
live.clear();
|
|
493
|
+
spaceHeld = false;
|
|
494
|
+
if (lastMods.space || lastMods.shift || lastMods.ctrl || lastMods.alt || lastMods.meta) {
|
|
495
|
+
lastMods = { ...NO_MODS };
|
|
496
|
+
queue.enqueue({
|
|
497
|
+
kind: "key",
|
|
498
|
+
pointerId: "",
|
|
499
|
+
device: "mouse",
|
|
500
|
+
screenX: 0,
|
|
501
|
+
screenY: 0,
|
|
502
|
+
buttons: 0,
|
|
503
|
+
mods: { ...NO_MODS }
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
container.addEventListener("pointerdown", onPointerDown);
|
|
508
|
+
container.addEventListener("pointermove", onPointerMove);
|
|
509
|
+
container.addEventListener("pointerup", onPointerUp);
|
|
510
|
+
container.addEventListener("pointercancel", onPointerCancel);
|
|
511
|
+
container.addEventListener("wheel", onWheel, { passive: false });
|
|
512
|
+
view?.addEventListener("keydown", onKeyDown);
|
|
513
|
+
view?.addEventListener("keyup", onKeyUp);
|
|
514
|
+
view?.addEventListener("blur", onBlur);
|
|
515
|
+
return () => {
|
|
516
|
+
container.removeEventListener("pointerdown", onPointerDown);
|
|
517
|
+
container.removeEventListener("pointermove", onPointerMove);
|
|
518
|
+
container.removeEventListener("pointerup", onPointerUp);
|
|
519
|
+
container.removeEventListener("pointercancel", onPointerCancel);
|
|
520
|
+
container.removeEventListener("wheel", onWheel);
|
|
521
|
+
view?.removeEventListener("keydown", onKeyDown);
|
|
522
|
+
view?.removeEventListener("keyup", onKeyUp);
|
|
523
|
+
view?.removeEventListener("blur", onBlur);
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// ../dom/src/reflectors/cursor.ts
|
|
528
|
+
function createCursorReflector(host, readCursor) {
|
|
529
|
+
let writes = 0;
|
|
530
|
+
let last;
|
|
531
|
+
return {
|
|
532
|
+
name: "cursor",
|
|
533
|
+
always: true,
|
|
534
|
+
flush(_world) {
|
|
535
|
+
const next = readCursor();
|
|
536
|
+
if (next === last) return;
|
|
537
|
+
last = next;
|
|
538
|
+
host.container.style.cursor = next;
|
|
539
|
+
writes++;
|
|
540
|
+
},
|
|
541
|
+
writes: () => writes
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// ../dom/src/reflectors/remote-cursors.ts
|
|
546
|
+
var remoteCursorQ = defineQuery([Position, CursorVisual]);
|
|
547
|
+
function createRemoteCursorsReflector(host, world) {
|
|
548
|
+
const doc = host.container.ownerDocument;
|
|
549
|
+
const plane = doc.createElement("div");
|
|
550
|
+
Object.assign(plane.style, {
|
|
551
|
+
position: "absolute",
|
|
552
|
+
inset: "0",
|
|
553
|
+
overflow: "hidden",
|
|
554
|
+
pointerEvents: "none"
|
|
555
|
+
});
|
|
556
|
+
host.container.appendChild(plane);
|
|
557
|
+
const pool = /* @__PURE__ */ new Map();
|
|
558
|
+
const ensure = (e) => {
|
|
559
|
+
let n = pool.get(e);
|
|
560
|
+
if (n !== void 0) return n;
|
|
561
|
+
const root = doc.createElement("div");
|
|
562
|
+
Object.assign(root.style, {
|
|
563
|
+
position: "absolute",
|
|
564
|
+
left: "0",
|
|
565
|
+
top: "0",
|
|
566
|
+
willChange: "transform",
|
|
567
|
+
pointerEvents: "none"
|
|
568
|
+
});
|
|
569
|
+
const arrow = doc.createElement("div");
|
|
570
|
+
Object.assign(arrow.style, {
|
|
571
|
+
width: "0",
|
|
572
|
+
height: "0",
|
|
573
|
+
borderLeft: "6px solid transparent",
|
|
574
|
+
borderRight: "6px solid transparent",
|
|
575
|
+
borderBottom: "14px solid currentColor",
|
|
576
|
+
transform: "rotate(-32deg)"
|
|
577
|
+
});
|
|
578
|
+
const label = doc.createElement("div");
|
|
579
|
+
Object.assign(label.style, {
|
|
580
|
+
marginTop: "2px",
|
|
581
|
+
marginLeft: "8px",
|
|
582
|
+
padding: "1px 6px",
|
|
583
|
+
borderRadius: "8px",
|
|
584
|
+
font: "11px system-ui, sans-serif",
|
|
585
|
+
color: "#fff",
|
|
586
|
+
whiteSpace: "nowrap"
|
|
587
|
+
});
|
|
588
|
+
root.appendChild(arrow);
|
|
589
|
+
root.appendChild(label);
|
|
590
|
+
plane.appendChild(root);
|
|
591
|
+
n = { root, label, color: "", name: "", x: Number.NaN, y: Number.NaN };
|
|
592
|
+
pool.set(e, n);
|
|
593
|
+
return n;
|
|
594
|
+
};
|
|
595
|
+
const reflector = {
|
|
596
|
+
name: "remoteCursors",
|
|
597
|
+
observe: {
|
|
598
|
+
resources: [Camera],
|
|
599
|
+
queries: [{ q: remoteCursorQ, cols: [Position, CursorVisual] }]
|
|
600
|
+
},
|
|
601
|
+
flush: (w) => {
|
|
602
|
+
const cam = w.getResource(Camera) ?? { x: 0, y: 0, zoom: 1 };
|
|
603
|
+
const seen = /* @__PURE__ */ new Set();
|
|
604
|
+
w.query(remoteCursorQ).each((b) => {
|
|
605
|
+
for (const r of b) {
|
|
606
|
+
const e = b.entity(r);
|
|
607
|
+
if (w.get(e, CursorVisual)?.kind !== "remote") continue;
|
|
608
|
+
seen.add(e);
|
|
609
|
+
const p = w.read(e, Position);
|
|
610
|
+
const s = worldToScreen(p.x, p.y, cam);
|
|
611
|
+
const n = ensure(e);
|
|
612
|
+
if (n.x !== s.x || n.y !== s.y) {
|
|
613
|
+
n.root.style.transform = `translate(${s.x}px, ${s.y}px)`;
|
|
614
|
+
n.x = s.x;
|
|
615
|
+
n.y = s.y;
|
|
616
|
+
}
|
|
617
|
+
const peer = w.getRelation(e, Follows);
|
|
618
|
+
const info = peer !== void 0 ? w.get(peer, PresenceInfo) : void 0;
|
|
619
|
+
const color = info?.color ?? "#7a8aa0";
|
|
620
|
+
const name = info?.name ?? "";
|
|
621
|
+
if (n.color !== color) {
|
|
622
|
+
n.root.style.color = color;
|
|
623
|
+
n.label.style.background = color;
|
|
624
|
+
n.color = color;
|
|
625
|
+
}
|
|
626
|
+
if (n.name !== name) {
|
|
627
|
+
n.label.textContent = name;
|
|
628
|
+
n.label.style.display = name === "" ? "none" : "block";
|
|
629
|
+
n.name = name;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
for (const [e, n] of pool) {
|
|
634
|
+
if (!seen.has(e)) {
|
|
635
|
+
n.root.remove();
|
|
636
|
+
pool.delete(e);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
return {
|
|
642
|
+
reflector,
|
|
643
|
+
destroy() {
|
|
644
|
+
pool.clear();
|
|
645
|
+
plane.remove();
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// ../dom/src/reflectors/grid.ts
|
|
651
|
+
var DEFAULT_GRID_CONFIG = {
|
|
652
|
+
spacings: [20, 100, 500],
|
|
653
|
+
dotColor: [0.75, 0.77, 0.8],
|
|
654
|
+
dotAlpha: 1,
|
|
655
|
+
fadeIn: [8, 16],
|
|
656
|
+
fadeOut: [120, 200],
|
|
657
|
+
dotRadius: [0.75, 0.75],
|
|
658
|
+
levelWeight: [1, 0]
|
|
659
|
+
};
|
|
660
|
+
var VERTEX_SHADER = (
|
|
661
|
+
/* glsl */
|
|
662
|
+
`
|
|
663
|
+
attribute vec2 position;
|
|
664
|
+
void main() {
|
|
665
|
+
gl_Position = vec4(position, 0.0, 1.0);
|
|
666
|
+
}
|
|
667
|
+
`
|
|
668
|
+
);
|
|
669
|
+
var FRAGMENT_SHADER = (
|
|
670
|
+
/* glsl */
|
|
671
|
+
`
|
|
672
|
+
precision highp float;
|
|
673
|
+
|
|
674
|
+
uniform vec2 u_resolution; // device pixels
|
|
675
|
+
uniform vec2 u_camera; // world-space top-left
|
|
676
|
+
uniform float u_zoom; // CSS zoom
|
|
677
|
+
uniform float u_dpr; // device pixel ratio
|
|
678
|
+
uniform vec3 u_spacings; // world-unit grid spacings
|
|
679
|
+
uniform vec3 u_dotColor; // dot RGB
|
|
680
|
+
uniform float u_dotAlpha; // dot base alpha
|
|
681
|
+
uniform vec2 u_fadeIn; // CSS-px [start, end]
|
|
682
|
+
uniform vec2 u_fadeOut; // CSS-px [start, end]
|
|
683
|
+
uniform vec2 u_dotRadius; // CSS-px [min, max]
|
|
684
|
+
uniform vec2 u_levelWeight; // [base, step]
|
|
685
|
+
|
|
686
|
+
void main() {
|
|
687
|
+
vec2 devicePos = gl_FragCoord.xy;
|
|
688
|
+
devicePos.y = u_resolution.y - devicePos.y;
|
|
689
|
+
|
|
690
|
+
float effectiveZoom = u_zoom * u_dpr;
|
|
691
|
+
vec2 worldPos = devicePos / effectiveZoom + u_camera;
|
|
692
|
+
|
|
693
|
+
float totalAlpha = 0.0;
|
|
694
|
+
|
|
695
|
+
for (int i = 0; i < 3; i++) {
|
|
696
|
+
float spacing;
|
|
697
|
+
if (i == 0) spacing = u_spacings.x;
|
|
698
|
+
else if (i == 1) spacing = u_spacings.y;
|
|
699
|
+
else spacing = u_spacings.z;
|
|
700
|
+
|
|
701
|
+
// Screen spacing in CSS pixels (DPR-independent for consistent fading)
|
|
702
|
+
float cssSpacing = spacing * u_zoom;
|
|
703
|
+
|
|
704
|
+
// Fade curve
|
|
705
|
+
float opacity = 0.0;
|
|
706
|
+
if (cssSpacing >= u_fadeIn.x && cssSpacing < u_fadeIn.y) {
|
|
707
|
+
opacity = (cssSpacing - u_fadeIn.x) / (u_fadeIn.y - u_fadeIn.x);
|
|
708
|
+
} else if (cssSpacing >= u_fadeIn.y && cssSpacing < u_fadeOut.x) {
|
|
709
|
+
opacity = 1.0;
|
|
710
|
+
} else if (cssSpacing >= u_fadeOut.x && cssSpacing < u_fadeOut.y) {
|
|
711
|
+
opacity = 1.0 - (cssSpacing - u_fadeOut.x) / (u_fadeOut.y - u_fadeOut.x);
|
|
712
|
+
}
|
|
713
|
+
if (opacity <= 0.001) continue;
|
|
714
|
+
|
|
715
|
+
// Distance to nearest grid intersection in device pixels
|
|
716
|
+
vec2 f = fract(worldPos / spacing + 0.5) - 0.5;
|
|
717
|
+
float dist = length(f) * spacing * effectiveZoom;
|
|
718
|
+
|
|
719
|
+
// Dot radius in device pixels \u2014 optionally grows for sparser levels
|
|
720
|
+
// (set u_dotRadius.x == u_dotRadius.y for Freeform/FigJam-style
|
|
721
|
+
// constant-size dots)
|
|
722
|
+
float t = clamp((cssSpacing - u_fadeIn.x) / 40.0, 0.0, 1.0);
|
|
723
|
+
float radius = mix(u_dotRadius.x, u_dotRadius.y, t) * u_dpr;
|
|
724
|
+
|
|
725
|
+
// Anti-aliased dot (0.5 device pixel smoothstep)
|
|
726
|
+
float dot = 1.0 - smoothstep(radius - 0.5, radius + 0.5, dist);
|
|
727
|
+
|
|
728
|
+
// Per-level weight: base + i * step. Step=0 keeps all levels at equal
|
|
729
|
+
// intensity; positive step emphasizes coarser levels (CAD feel).
|
|
730
|
+
float weight = u_levelWeight.x + float(i) * u_levelWeight.y;
|
|
731
|
+
|
|
732
|
+
// Composite with max, not sum. Additive compositing causes anti-
|
|
733
|
+
// aliased dot rims to stack at joint intersections (every N-th dot
|
|
734
|
+
// visibly fatter \u2014 a CAD tell). max() guarantees a joint intersection
|
|
735
|
+
// looks identical to a single-level dot, matching Freeform / FigJam.
|
|
736
|
+
totalAlpha = max(totalAlpha, dot * opacity * weight);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
gl_FragColor = vec4(u_dotColor, clamp(totalAlpha * u_dotAlpha, 0.0, 1.0));
|
|
740
|
+
}
|
|
741
|
+
`
|
|
742
|
+
);
|
|
743
|
+
var FULLSCREEN_TRIANGLE = new Float32Array([-1, -1, 3, -1, -1, 3]);
|
|
744
|
+
var UNIFORM_NAMES = [
|
|
745
|
+
"u_resolution",
|
|
746
|
+
"u_camera",
|
|
747
|
+
"u_zoom",
|
|
748
|
+
"u_dpr",
|
|
749
|
+
"u_spacings",
|
|
750
|
+
"u_dotColor",
|
|
751
|
+
"u_dotAlpha",
|
|
752
|
+
"u_fadeIn",
|
|
753
|
+
"u_fadeOut",
|
|
754
|
+
"u_dotRadius",
|
|
755
|
+
"u_levelWeight"
|
|
756
|
+
];
|
|
757
|
+
function compileShader(gl, type, source) {
|
|
758
|
+
const shader = gl.createShader(type);
|
|
759
|
+
if (shader === null) return null;
|
|
760
|
+
gl.shaderSource(shader, source);
|
|
761
|
+
gl.compileShader(shader);
|
|
762
|
+
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) !== true) {
|
|
763
|
+
gl.deleteShader(shader);
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
return shader;
|
|
767
|
+
}
|
|
768
|
+
function linkProgram(gl) {
|
|
769
|
+
const vs = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);
|
|
770
|
+
const fs = compileShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
|
|
771
|
+
if (vs === null || fs === null) return null;
|
|
772
|
+
const program = gl.createProgram();
|
|
773
|
+
if (program === null) return null;
|
|
774
|
+
gl.attachShader(program, vs);
|
|
775
|
+
gl.attachShader(program, fs);
|
|
776
|
+
gl.linkProgram(program);
|
|
777
|
+
const linked = gl.getProgramParameter(program, gl.LINK_STATUS) === true;
|
|
778
|
+
gl.deleteShader(vs);
|
|
779
|
+
gl.deleteShader(fs);
|
|
780
|
+
if (!linked) {
|
|
781
|
+
gl.deleteProgram(program);
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
return program;
|
|
785
|
+
}
|
|
786
|
+
function setupGeometry(gl, program) {
|
|
787
|
+
const buffer = gl.createBuffer();
|
|
788
|
+
if (buffer === null) return false;
|
|
789
|
+
const loc = gl.getAttribLocation(program, "position");
|
|
790
|
+
if (loc < 0) return false;
|
|
791
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
792
|
+
gl.bufferData(gl.ARRAY_BUFFER, FULLSCREEN_TRIANGLE, gl.STATIC_DRAW);
|
|
793
|
+
gl.enableVertexAttribArray(loc);
|
|
794
|
+
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
|
|
795
|
+
return true;
|
|
796
|
+
}
|
|
797
|
+
function collectUniforms(gl, program) {
|
|
798
|
+
const out = {};
|
|
799
|
+
for (const name of UNIFORM_NAMES) out[name] = gl.getUniformLocation(program, name);
|
|
800
|
+
return out;
|
|
801
|
+
}
|
|
802
|
+
function createGridReflector(host, opts = {}) {
|
|
803
|
+
const config = { ...DEFAULT_GRID_CONFIG, ...opts };
|
|
804
|
+
const doc = host.container.ownerDocument;
|
|
805
|
+
const canvas = doc.createElement("canvas");
|
|
806
|
+
canvas.style.position = "absolute";
|
|
807
|
+
canvas.style.left = "0";
|
|
808
|
+
canvas.style.top = "0";
|
|
809
|
+
canvas.style.width = "100%";
|
|
810
|
+
canvas.style.height = "100%";
|
|
811
|
+
canvas.style.display = "block";
|
|
812
|
+
canvas.style.pointerEvents = "none";
|
|
813
|
+
host.container.insertBefore(canvas, host.container.firstChild);
|
|
814
|
+
let gl = null;
|
|
815
|
+
let program = null;
|
|
816
|
+
let uniforms = null;
|
|
817
|
+
let dpr = 1;
|
|
818
|
+
let lastCamera = { x: 0, y: 0, zoom: 1 };
|
|
819
|
+
try {
|
|
820
|
+
const attrs = { alpha: true, antialias: false, premultipliedAlpha: false };
|
|
821
|
+
gl = canvas.getContext("webgl2", attrs) ?? canvas.getContext("webgl", attrs);
|
|
822
|
+
if (gl !== null) {
|
|
823
|
+
const prog = linkProgram(gl);
|
|
824
|
+
if (prog !== null && setupGeometry(gl, prog)) {
|
|
825
|
+
program = prog;
|
|
826
|
+
uniforms = collectUniforms(gl, prog);
|
|
827
|
+
} else {
|
|
828
|
+
gl = null;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
} catch {
|
|
832
|
+
gl = null;
|
|
833
|
+
program = null;
|
|
834
|
+
uniforms = null;
|
|
835
|
+
}
|
|
836
|
+
function draw(camera) {
|
|
837
|
+
if (gl === null || program === null || uniforms === null) return;
|
|
838
|
+
lastCamera = camera;
|
|
839
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
840
|
+
gl.useProgram(program);
|
|
841
|
+
gl.uniform2f(uniforms.u_resolution, canvas.width, canvas.height);
|
|
842
|
+
gl.uniform2f(uniforms.u_camera, camera.x, camera.y);
|
|
843
|
+
gl.uniform1f(uniforms.u_zoom, camera.zoom);
|
|
844
|
+
gl.uniform1f(uniforms.u_dpr, dpr);
|
|
845
|
+
gl.uniform3f(uniforms.u_spacings, ...config.spacings);
|
|
846
|
+
gl.uniform3f(uniforms.u_dotColor, ...config.dotColor);
|
|
847
|
+
gl.uniform1f(uniforms.u_dotAlpha, config.dotAlpha);
|
|
848
|
+
gl.uniform2f(uniforms.u_fadeIn, ...config.fadeIn);
|
|
849
|
+
gl.uniform2f(uniforms.u_fadeOut, ...config.fadeOut);
|
|
850
|
+
gl.uniform2f(uniforms.u_dotRadius, ...config.dotRadius);
|
|
851
|
+
gl.uniform2f(uniforms.u_levelWeight, ...config.levelWeight);
|
|
852
|
+
gl.clearColor(0, 0, 0, 0);
|
|
853
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
854
|
+
gl.enable(gl.BLEND);
|
|
855
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
856
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
857
|
+
}
|
|
858
|
+
function applySize(cssWidth, cssHeight) {
|
|
859
|
+
dpr = doc.defaultView?.devicePixelRatio ?? 1;
|
|
860
|
+
const bufW = Math.max(1, Math.round(cssWidth * dpr));
|
|
861
|
+
const bufH = Math.max(1, Math.round(cssHeight * dpr));
|
|
862
|
+
if (canvas.width !== bufW) canvas.width = bufW;
|
|
863
|
+
if (canvas.height !== bufH) canvas.height = bufH;
|
|
864
|
+
try {
|
|
865
|
+
draw(lastCamera);
|
|
866
|
+
} catch {
|
|
867
|
+
gl = null;
|
|
868
|
+
program = null;
|
|
869
|
+
uniforms = null;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
const initialRect = host.container.getBoundingClientRect();
|
|
873
|
+
applySize(initialRect.width, initialRect.height);
|
|
874
|
+
let ro;
|
|
875
|
+
try {
|
|
876
|
+
ro = new ResizeObserver((entries) => {
|
|
877
|
+
const entry = entries[0];
|
|
878
|
+
if (entry === void 0) return;
|
|
879
|
+
applySize(entry.contentRect.width, entry.contentRect.height);
|
|
880
|
+
});
|
|
881
|
+
ro.observe(host.container);
|
|
882
|
+
} catch {
|
|
883
|
+
}
|
|
884
|
+
return {
|
|
885
|
+
name: "grid",
|
|
886
|
+
observe: { resources: [Camera] },
|
|
887
|
+
flush(world) {
|
|
888
|
+
if (gl === null) return;
|
|
889
|
+
const camera = world.getResource(Camera);
|
|
890
|
+
if (camera === void 0) return;
|
|
891
|
+
try {
|
|
892
|
+
draw(camera);
|
|
893
|
+
} catch {
|
|
894
|
+
gl = null;
|
|
895
|
+
program = null;
|
|
896
|
+
uniforms = null;
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
available: () => gl !== null,
|
|
900
|
+
// Live re-tune (theme switches, settings panels): draw() uploads every
|
|
901
|
+
// config uniform per call, so a merge + one redraw at the last camera is
|
|
902
|
+
// the whole story. Outside the frame contract by design — config is host
|
|
903
|
+
// input, not world state (same class as the RO resize path).
|
|
904
|
+
configure(next) {
|
|
905
|
+
Object.assign(config, next);
|
|
906
|
+
if (gl !== null) {
|
|
907
|
+
try {
|
|
908
|
+
draw(lastCamera);
|
|
909
|
+
} catch {
|
|
910
|
+
gl = null;
|
|
911
|
+
program = null;
|
|
912
|
+
uniforms = null;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
// Unregistering a reflector only stops its flushes — the canvas this
|
|
917
|
+
// factory inserted (and its ResizeObserver) must be torn down explicitly,
|
|
918
|
+
// or a React StrictMode remount stacks a second frozen grid under the
|
|
919
|
+
// live one (field report 2026-07-11).
|
|
920
|
+
dispose() {
|
|
921
|
+
ro?.disconnect();
|
|
922
|
+
gl = null;
|
|
923
|
+
program = null;
|
|
924
|
+
uniforms = null;
|
|
925
|
+
canvas.remove();
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// ../dom/src/reflectors/wires.ts
|
|
931
|
+
var INACTIVE_PREVIEW = { sx: 0, sy: 0, tx: 0, ty: 0, active: false, compatible: false };
|
|
932
|
+
var DEFAULT_WIRES_CONFIG = {
|
|
933
|
+
wireColor: "rgba(120, 132, 145, 0.9)",
|
|
934
|
+
selectedColor: "#4a90d9",
|
|
935
|
+
wireWidth: 1.5,
|
|
936
|
+
selectedWidth: 2,
|
|
937
|
+
portRadius: 4,
|
|
938
|
+
portColor: "rgba(120, 132, 145, 0.9)",
|
|
939
|
+
portActiveColor: "#4a90d9"
|
|
940
|
+
};
|
|
941
|
+
var wireQ = defineQuery([Wire]);
|
|
942
|
+
var geometryQ = defineQuery([Position, Size]);
|
|
943
|
+
var selectedQ = defineQuery([Selected]);
|
|
944
|
+
var portQ = defineQuery([Port, PortAnchor]);
|
|
945
|
+
var routedConnectQ = defineQuery([RoutedConnect]);
|
|
946
|
+
function readRect(world, e) {
|
|
947
|
+
const p = world.get(e, Position);
|
|
948
|
+
if (p === void 0) return void 0;
|
|
949
|
+
const measured = world.get(e, MeasuredSize);
|
|
950
|
+
const s = measured !== void 0 && measured.w > 0 ? measured : world.get(e, Size);
|
|
951
|
+
if (s === void 0) return void 0;
|
|
952
|
+
return { x: p.x, y: p.y, width: s.w, height: s.h };
|
|
953
|
+
}
|
|
954
|
+
function makePortSlotResolver() {
|
|
955
|
+
const cache = /* @__PURE__ */ new Map();
|
|
956
|
+
const build = (wt) => {
|
|
957
|
+
const bySide = /* @__PURE__ */ new Map();
|
|
958
|
+
for (const d of wt.ports) {
|
|
959
|
+
let list = bySide.get(d.side);
|
|
960
|
+
if (list === void 0) {
|
|
961
|
+
list = [];
|
|
962
|
+
bySide.set(d.side, list);
|
|
963
|
+
}
|
|
964
|
+
list.push(d);
|
|
965
|
+
}
|
|
966
|
+
const out = /* @__PURE__ */ new Map();
|
|
967
|
+
for (const [side, list] of bySide) {
|
|
968
|
+
for (let i = 0; i < list.length; i++) {
|
|
969
|
+
const d = list[i];
|
|
970
|
+
if (d === void 0) continue;
|
|
971
|
+
out.set(d.id, { side, index: d.index ?? i, count: list.length });
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return out;
|
|
975
|
+
};
|
|
976
|
+
return (wt, portId) => {
|
|
977
|
+
let slots = cache.get(wt.type);
|
|
978
|
+
if (slots === void 0) {
|
|
979
|
+
slots = build(wt);
|
|
980
|
+
cache.set(wt.type, slots);
|
|
981
|
+
}
|
|
982
|
+
return slots.get(portId);
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
function cubicToScreen(c, cam) {
|
|
986
|
+
const p0 = worldToScreen(c.x0, c.y0, cam);
|
|
987
|
+
const p1 = worldToScreen(c.x1, c.y1, cam);
|
|
988
|
+
const p2 = worldToScreen(c.x2, c.y2, cam);
|
|
989
|
+
const p3 = worldToScreen(c.x3, c.y3, cam);
|
|
990
|
+
return { x0: p0.x, y0: p0.y, x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y, x3: p3.x, y3: p3.y };
|
|
991
|
+
}
|
|
992
|
+
function createWiresReflector(host, world, opts = {}) {
|
|
993
|
+
const config = { ...DEFAULT_WIRES_CONFIG, ...opts.config };
|
|
994
|
+
const readPreview = opts.readPreview ?? (() => INACTIVE_PREVIEW);
|
|
995
|
+
const doc = host.container.ownerDocument;
|
|
996
|
+
const canvas = doc.createElement("canvas");
|
|
997
|
+
canvas.style.position = "absolute";
|
|
998
|
+
canvas.style.left = "0";
|
|
999
|
+
canvas.style.top = "0";
|
|
1000
|
+
canvas.style.width = "100%";
|
|
1001
|
+
canvas.style.height = "100%";
|
|
1002
|
+
canvas.style.display = "block";
|
|
1003
|
+
canvas.style.pointerEvents = "none";
|
|
1004
|
+
host.container.insertBefore(canvas, host.contentPlane);
|
|
1005
|
+
let ctx = null;
|
|
1006
|
+
try {
|
|
1007
|
+
ctx = canvas.getContext("2d");
|
|
1008
|
+
} catch {
|
|
1009
|
+
ctx = null;
|
|
1010
|
+
}
|
|
1011
|
+
let dpr = 1;
|
|
1012
|
+
const slotOf = makePortSlotResolver();
|
|
1013
|
+
let dirty = true;
|
|
1014
|
+
let lastPreviewKey = "";
|
|
1015
|
+
let redraws = 0;
|
|
1016
|
+
const wake = () => {
|
|
1017
|
+
dirty = true;
|
|
1018
|
+
};
|
|
1019
|
+
const unsubs = [
|
|
1020
|
+
world.reactive.observeResource(Camera, wake),
|
|
1021
|
+
world.reactive.observeQuery(wireQ, wake, { cols: [] }),
|
|
1022
|
+
world.reactive.observeQuery(geometryQ, wake, { cols: [Position, Size] }),
|
|
1023
|
+
world.reactive.observeQuery(selectedQ, wake, { cols: [] }),
|
|
1024
|
+
world.reactive.observeQuery(portQ, wake, { cols: [PortAnchor] }),
|
|
1025
|
+
world.reactive.observeQuery(routedConnectQ, wake, { cols: [] })
|
|
1026
|
+
];
|
|
1027
|
+
function applySize(cssWidth, cssHeight) {
|
|
1028
|
+
dpr = doc.defaultView?.devicePixelRatio ?? 1;
|
|
1029
|
+
const bufW = Math.max(1, Math.round(cssWidth * dpr));
|
|
1030
|
+
const bufH = Math.max(1, Math.round(cssHeight * dpr));
|
|
1031
|
+
if (canvas.width !== bufW) canvas.width = bufW;
|
|
1032
|
+
if (canvas.height !== bufH) canvas.height = bufH;
|
|
1033
|
+
dirty = true;
|
|
1034
|
+
}
|
|
1035
|
+
const initialRect = host.container.getBoundingClientRect();
|
|
1036
|
+
applySize(initialRect.width, initialRect.height);
|
|
1037
|
+
let ro = null;
|
|
1038
|
+
try {
|
|
1039
|
+
ro = new ResizeObserver((entries) => {
|
|
1040
|
+
const entry = entries[0];
|
|
1041
|
+
if (entry === void 0) return;
|
|
1042
|
+
applySize(entry.contentRect.width, entry.contentRect.height);
|
|
1043
|
+
});
|
|
1044
|
+
ro.observe(host.container);
|
|
1045
|
+
} catch {
|
|
1046
|
+
ro = null;
|
|
1047
|
+
}
|
|
1048
|
+
function strokeCubic(c2d, s) {
|
|
1049
|
+
c2d.beginPath();
|
|
1050
|
+
c2d.moveTo(s.x0, s.y0);
|
|
1051
|
+
c2d.bezierCurveTo(s.x1, s.y1, s.x2, s.y2, s.x3, s.y3);
|
|
1052
|
+
c2d.stroke();
|
|
1053
|
+
}
|
|
1054
|
+
function endpointAnchor(widget, portId) {
|
|
1055
|
+
if (widget === void 0) return void 0;
|
|
1056
|
+
const rect = readRect(world, widget);
|
|
1057
|
+
if (rect === void 0) return void 0;
|
|
1058
|
+
const type = world.get(widget, PrefabId)?.id;
|
|
1059
|
+
if (typeof type !== "string") return void 0;
|
|
1060
|
+
const wt = widgets.get(type);
|
|
1061
|
+
if (wt === void 0) return void 0;
|
|
1062
|
+
const slot = slotOf(wt, portId);
|
|
1063
|
+
if (slot === void 0) return void 0;
|
|
1064
|
+
return { anchor: rect, slot };
|
|
1065
|
+
}
|
|
1066
|
+
function drawWires(c2d, cam) {
|
|
1067
|
+
world.query(wireQ).each((batch) => {
|
|
1068
|
+
for (const r of batch) {
|
|
1069
|
+
const wire = batch.entity(r);
|
|
1070
|
+
const ports = world.get(wire, WirePorts);
|
|
1071
|
+
if (ports === void 0) continue;
|
|
1072
|
+
const from = endpointAnchor(world.getRelation(wire, WireFrom), ports.from ?? "");
|
|
1073
|
+
const to = endpointAnchor(world.getRelation(wire, WireTo), ports.to ?? "");
|
|
1074
|
+
if (from === void 0 || to === void 0) continue;
|
|
1075
|
+
const a0 = portAnchor(from.anchor, from.slot);
|
|
1076
|
+
const a1 = portAnchor(to.anchor, to.slot);
|
|
1077
|
+
const cubic = wireCubic(a0, from.slot.side, a1, to.slot.side);
|
|
1078
|
+
const selected = world.hasTag(wire, Selected);
|
|
1079
|
+
c2d.strokeStyle = selected ? config.selectedColor : config.wireColor;
|
|
1080
|
+
c2d.lineWidth = selected ? config.selectedWidth : config.wireWidth;
|
|
1081
|
+
c2d.setLineDash([]);
|
|
1082
|
+
strokeCubic(c2d, cubicToScreen(cubic, cam));
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
function drawPreview(c2d, cam, preview) {
|
|
1087
|
+
if (!preview.active) return;
|
|
1088
|
+
const stub = Math.max(24, Math.abs(preview.tx - preview.sx) * 0.5);
|
|
1089
|
+
const cubic = {
|
|
1090
|
+
x0: preview.sx,
|
|
1091
|
+
y0: preview.sy,
|
|
1092
|
+
x1: preview.sx + stub,
|
|
1093
|
+
y1: preview.sy,
|
|
1094
|
+
x2: preview.tx - stub,
|
|
1095
|
+
y2: preview.ty,
|
|
1096
|
+
x3: preview.tx,
|
|
1097
|
+
y3: preview.ty
|
|
1098
|
+
};
|
|
1099
|
+
c2d.strokeStyle = preview.compatible ? config.selectedColor : config.wireColor;
|
|
1100
|
+
c2d.lineWidth = config.wireWidth;
|
|
1101
|
+
c2d.setLineDash(preview.compatible ? [] : [6, 4]);
|
|
1102
|
+
strokeCubic(c2d, cubicToScreen(cubic, cam));
|
|
1103
|
+
c2d.setLineDash([]);
|
|
1104
|
+
}
|
|
1105
|
+
function drawPorts(c2d, cam) {
|
|
1106
|
+
const connecting = world.firstOf(routedConnectQ) !== void 0;
|
|
1107
|
+
c2d.fillStyle = connecting ? config.portActiveColor : config.portColor;
|
|
1108
|
+
world.query(portQ).each((batch) => {
|
|
1109
|
+
const ax = batch.col(PortAnchor).x;
|
|
1110
|
+
const ay = batch.col(PortAnchor).y;
|
|
1111
|
+
for (const r of batch) {
|
|
1112
|
+
const s = worldToScreen(ax[r], ay[r], cam);
|
|
1113
|
+
c2d.beginPath();
|
|
1114
|
+
c2d.arc(s.x, s.y, config.portRadius, 0, Math.PI * 2);
|
|
1115
|
+
c2d.fill();
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
function redraw(c2d, cam, preview) {
|
|
1120
|
+
c2d.setTransform(1, 0, 0, 1, 0, 0);
|
|
1121
|
+
c2d.clearRect(0, 0, canvas.width, canvas.height);
|
|
1122
|
+
c2d.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1123
|
+
redraws++;
|
|
1124
|
+
drawWires(c2d, cam);
|
|
1125
|
+
drawPreview(c2d, cam, preview);
|
|
1126
|
+
drawPorts(c2d, cam);
|
|
1127
|
+
}
|
|
1128
|
+
return {
|
|
1129
|
+
name: "wires",
|
|
1130
|
+
// The preview buffer has no ECS stamp; self-gate on a private dirty flag
|
|
1131
|
+
// (chrome/dom-widgets pattern) rather than the registry's observe dirt.
|
|
1132
|
+
always: true,
|
|
1133
|
+
flush(w) {
|
|
1134
|
+
if (ctx === null) return;
|
|
1135
|
+
const cam = w.getResource(Camera);
|
|
1136
|
+
if (cam === void 0) return;
|
|
1137
|
+
const preview = readPreview();
|
|
1138
|
+
const previewKey = preview.active ? `${preview.sx},${preview.sy},${preview.tx},${preview.ty},${preview.compatible ? 1 : 0}` : "";
|
|
1139
|
+
if (previewKey !== lastPreviewKey) {
|
|
1140
|
+
lastPreviewKey = previewKey;
|
|
1141
|
+
dirty = true;
|
|
1142
|
+
}
|
|
1143
|
+
if (!dirty) return;
|
|
1144
|
+
dirty = false;
|
|
1145
|
+
try {
|
|
1146
|
+
redraw(ctx, cam, preview);
|
|
1147
|
+
} catch {
|
|
1148
|
+
ctx = null;
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
available: () => ctx !== null,
|
|
1152
|
+
redraws: () => redraws,
|
|
1153
|
+
dispose() {
|
|
1154
|
+
for (const u of unsubs) u();
|
|
1155
|
+
unsubs.length = 0;
|
|
1156
|
+
ro?.disconnect();
|
|
1157
|
+
ro = null;
|
|
1158
|
+
canvas.remove();
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// ../dom/src/reflectors/chrome.ts
|
|
1164
|
+
var HANDLE_DRAW_PX = 8;
|
|
1165
|
+
var boxQ = defineQuery([SelectionBox]);
|
|
1166
|
+
var handleQ = defineQuery([HandleSpec, Position, Size]);
|
|
1167
|
+
function makeNode(doc, extra) {
|
|
1168
|
+
const node = doc.createElement("div");
|
|
1169
|
+
Object.assign(node.style, { position: "absolute", boxSizing: "border-box", pointerEvents: "none" }, extra);
|
|
1170
|
+
return node;
|
|
1171
|
+
}
|
|
1172
|
+
function createChromeReflector(host, _world, marqueeBuffer) {
|
|
1173
|
+
const doc = host.container.ownerDocument;
|
|
1174
|
+
const plane = makeNode(doc, { left: "0", top: "0", width: "0", height: "0" });
|
|
1175
|
+
host.container.appendChild(plane);
|
|
1176
|
+
const boxNodes = /* @__PURE__ */ new Map();
|
|
1177
|
+
const handleNodes = /* @__PURE__ */ new Map();
|
|
1178
|
+
let marqueeNode = null;
|
|
1179
|
+
const boxCache = /* @__PURE__ */ new Map();
|
|
1180
|
+
const handleCache = /* @__PURE__ */ new Map();
|
|
1181
|
+
let marqueeCache = null;
|
|
1182
|
+
let drewLast = false;
|
|
1183
|
+
const seenBox = /* @__PURE__ */ new Set();
|
|
1184
|
+
const seenHandle = /* @__PURE__ */ new Set();
|
|
1185
|
+
let writes = 0;
|
|
1186
|
+
const writeRect = (node, left, top, w, h) => {
|
|
1187
|
+
node.style.left = `${left}px`;
|
|
1188
|
+
node.style.top = `${top}px`;
|
|
1189
|
+
node.style.width = `${w}px`;
|
|
1190
|
+
node.style.height = `${h}px`;
|
|
1191
|
+
writes++;
|
|
1192
|
+
};
|
|
1193
|
+
return {
|
|
1194
|
+
name: "chrome",
|
|
1195
|
+
always: true,
|
|
1196
|
+
observe: {
|
|
1197
|
+
queries: [
|
|
1198
|
+
{ q: boxQ, cols: [SelectionBox] },
|
|
1199
|
+
{ q: handleQ, cols: [Position, Size] }
|
|
1200
|
+
],
|
|
1201
|
+
resources: [Camera]
|
|
1202
|
+
},
|
|
1203
|
+
flush(w) {
|
|
1204
|
+
const cam = w.getResource(Camera);
|
|
1205
|
+
if (cam === void 0) return;
|
|
1206
|
+
const marqueeRect = marqueeBuffer.rect;
|
|
1207
|
+
const hasContent = marqueeRect !== null || w.firstOf(boxQ) !== void 0 || w.firstOf(handleQ) !== void 0;
|
|
1208
|
+
if (!hasContent && !drewLast) return;
|
|
1209
|
+
seenBox.clear();
|
|
1210
|
+
seenHandle.clear();
|
|
1211
|
+
w.query(boxQ).each((batch) => {
|
|
1212
|
+
const bx = batch.col(SelectionBox).x;
|
|
1213
|
+
const by = batch.col(SelectionBox).y;
|
|
1214
|
+
const bw = batch.col(SelectionBox).w;
|
|
1215
|
+
const bh = batch.col(SelectionBox).h;
|
|
1216
|
+
for (const r of batch) {
|
|
1217
|
+
const e = batch.entity(r);
|
|
1218
|
+
seenBox.add(e);
|
|
1219
|
+
const tl = worldToScreen(bx[r], by[r], cam);
|
|
1220
|
+
const sw = bw[r] * cam.zoom;
|
|
1221
|
+
const sh = bh[r] * cam.zoom;
|
|
1222
|
+
const key = `${tl.x}:${tl.y}:${sw}:${sh}`;
|
|
1223
|
+
let node = boxNodes.get(e);
|
|
1224
|
+
if (node === void 0) {
|
|
1225
|
+
node = makeNode(doc, { border: "1px solid #4a90d9" });
|
|
1226
|
+
node.dataset.chrome = "box";
|
|
1227
|
+
boxNodes.set(e, node);
|
|
1228
|
+
plane.appendChild(node);
|
|
1229
|
+
}
|
|
1230
|
+
if (boxCache.get(e) !== key) {
|
|
1231
|
+
writeRect(node, tl.x, tl.y, sw, sh);
|
|
1232
|
+
boxCache.set(e, key);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
for (const [e, node] of boxNodes) {
|
|
1237
|
+
if (seenBox.has(e)) continue;
|
|
1238
|
+
node.remove();
|
|
1239
|
+
boxNodes.delete(e);
|
|
1240
|
+
boxCache.delete(e);
|
|
1241
|
+
}
|
|
1242
|
+
w.query(handleQ).each((batch) => {
|
|
1243
|
+
const px = batch.col(Position).x;
|
|
1244
|
+
const py = batch.col(Position).y;
|
|
1245
|
+
const sw = batch.col(Size).w;
|
|
1246
|
+
const sh = batch.col(Size).h;
|
|
1247
|
+
for (const r of batch) {
|
|
1248
|
+
const e = batch.entity(r);
|
|
1249
|
+
seenHandle.add(e);
|
|
1250
|
+
const cx = px[r] + sw[r] / 2;
|
|
1251
|
+
const cy = py[r] + sh[r] / 2;
|
|
1252
|
+
const sc = worldToScreen(cx, cy, cam);
|
|
1253
|
+
const left = sc.x - HANDLE_DRAW_PX / 2;
|
|
1254
|
+
const top = sc.y - HANDLE_DRAW_PX / 2;
|
|
1255
|
+
const key = `${left}:${top}`;
|
|
1256
|
+
let node = handleNodes.get(e);
|
|
1257
|
+
if (node === void 0) {
|
|
1258
|
+
node = makeNode(doc, {
|
|
1259
|
+
width: `${HANDLE_DRAW_PX}px`,
|
|
1260
|
+
height: `${HANDLE_DRAW_PX}px`,
|
|
1261
|
+
background: "#fff",
|
|
1262
|
+
border: "1px solid #4a90d9"
|
|
1263
|
+
});
|
|
1264
|
+
node.dataset.chrome = "handle";
|
|
1265
|
+
handleNodes.set(e, node);
|
|
1266
|
+
plane.appendChild(node);
|
|
1267
|
+
}
|
|
1268
|
+
if (handleCache.get(e) !== key) {
|
|
1269
|
+
node.style.left = `${left}px`;
|
|
1270
|
+
node.style.top = `${top}px`;
|
|
1271
|
+
writes++;
|
|
1272
|
+
handleCache.set(e, key);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
for (const [e, node] of handleNodes) {
|
|
1277
|
+
if (seenHandle.has(e)) continue;
|
|
1278
|
+
node.remove();
|
|
1279
|
+
handleNodes.delete(e);
|
|
1280
|
+
handleCache.delete(e);
|
|
1281
|
+
}
|
|
1282
|
+
if (marqueeRect !== null) {
|
|
1283
|
+
const tl = worldToScreen(marqueeRect.x, marqueeRect.y, cam);
|
|
1284
|
+
const mw = marqueeRect.w * cam.zoom;
|
|
1285
|
+
const mh = marqueeRect.h * cam.zoom;
|
|
1286
|
+
const key = `${tl.x}:${tl.y}:${mw}:${mh}`;
|
|
1287
|
+
if (marqueeNode === null) {
|
|
1288
|
+
marqueeNode = makeNode(doc, { border: "1px dashed #4a90d9", background: "rgba(74,144,217,0.1)" });
|
|
1289
|
+
marqueeNode.dataset.chrome = "marquee";
|
|
1290
|
+
plane.appendChild(marqueeNode);
|
|
1291
|
+
}
|
|
1292
|
+
if (marqueeCache !== key) {
|
|
1293
|
+
writeRect(marqueeNode, tl.x, tl.y, mw, mh);
|
|
1294
|
+
marqueeCache = key;
|
|
1295
|
+
}
|
|
1296
|
+
} else if (marqueeNode !== null) {
|
|
1297
|
+
marqueeNode.remove();
|
|
1298
|
+
marqueeNode = null;
|
|
1299
|
+
marqueeCache = null;
|
|
1300
|
+
}
|
|
1301
|
+
drewLast = boxNodes.size > 0 || handleNodes.size > 0 || marqueeNode !== null;
|
|
1302
|
+
},
|
|
1303
|
+
writes: () => writes,
|
|
1304
|
+
// The P4 overlay plane is this factory's insertion — unregistering the
|
|
1305
|
+
// reflector alone leaves it behind (2026-07-11 teardown sweep).
|
|
1306
|
+
dispose() {
|
|
1307
|
+
plane.remove();
|
|
1308
|
+
}
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// ../dom/src/measure-adapter.ts
|
|
1313
|
+
function isHidden(el) {
|
|
1314
|
+
if (el.offsetParent !== null) return false;
|
|
1315
|
+
const display = el.ownerDocument.defaultView?.getComputedStyle(el).display;
|
|
1316
|
+
return display === "none";
|
|
1317
|
+
}
|
|
1318
|
+
function attachMeasureAdapter(hostFor, queue) {
|
|
1319
|
+
const byElement = /* @__PURE__ */ new Map();
|
|
1320
|
+
const byEntity = /* @__PURE__ */ new Map();
|
|
1321
|
+
const ro = new ResizeObserver((entries) => {
|
|
1322
|
+
for (const entry of entries) {
|
|
1323
|
+
const el = entry.target;
|
|
1324
|
+
const entity = byElement.get(el);
|
|
1325
|
+
if (entity === void 0) continue;
|
|
1326
|
+
if (isHidden(el)) continue;
|
|
1327
|
+
const box = entry.borderBoxSize?.[0];
|
|
1328
|
+
const w = box !== void 0 ? box.inlineSize : entry.contentRect.width;
|
|
1329
|
+
const h = box !== void 0 ? box.blockSize : entry.contentRect.height;
|
|
1330
|
+
if (w === 0 && h === 0) continue;
|
|
1331
|
+
queue.enqueue({ entity, w, h });
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
return {
|
|
1335
|
+
observe(entity) {
|
|
1336
|
+
const el = hostFor(entity);
|
|
1337
|
+
if (el === void 0) return;
|
|
1338
|
+
const prev = byEntity.get(entity);
|
|
1339
|
+
if (prev !== void 0 && prev !== el) {
|
|
1340
|
+
ro.unobserve(prev);
|
|
1341
|
+
byElement.delete(prev);
|
|
1342
|
+
}
|
|
1343
|
+
byElement.set(el, entity);
|
|
1344
|
+
byEntity.set(entity, el);
|
|
1345
|
+
ro.observe(el);
|
|
1346
|
+
},
|
|
1347
|
+
unobserve(entity) {
|
|
1348
|
+
const el = byEntity.get(entity);
|
|
1349
|
+
if (el === void 0) return;
|
|
1350
|
+
ro.unobserve(el);
|
|
1351
|
+
byElement.delete(el);
|
|
1352
|
+
byEntity.delete(entity);
|
|
1353
|
+
},
|
|
1354
|
+
dispose() {
|
|
1355
|
+
ro.disconnect();
|
|
1356
|
+
byElement.clear();
|
|
1357
|
+
byEntity.clear();
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// ../dom/src/measure-wiring.ts
|
|
1363
|
+
function defaultMeasure(el) {
|
|
1364
|
+
const rect = el.getBoundingClientRect();
|
|
1365
|
+
return { w: rect.width, h: rect.height };
|
|
1366
|
+
}
|
|
1367
|
+
function wireMeasurement(store, hosts, queue, opts = {}) {
|
|
1368
|
+
const adapter = attachMeasureAdapter((e) => hosts.hostFor(e), queue);
|
|
1369
|
+
const measure = opts.measure ?? defaultMeasure;
|
|
1370
|
+
const state = /* @__PURE__ */ new Map();
|
|
1371
|
+
let scheduled = false;
|
|
1372
|
+
let disposed = false;
|
|
1373
|
+
const forceRemeasure = (e) => {
|
|
1374
|
+
const el = hosts.hostFor(e);
|
|
1375
|
+
if (el === void 0) return;
|
|
1376
|
+
const { w, h } = measure(el);
|
|
1377
|
+
if (w <= 0 && h <= 0) return;
|
|
1378
|
+
queue.enqueue({ entity: e, w, h });
|
|
1379
|
+
};
|
|
1380
|
+
const reconcile = () => {
|
|
1381
|
+
if (disposed) return;
|
|
1382
|
+
const snapshot = store.getSnapshot();
|
|
1383
|
+
const present = /* @__PURE__ */ new Set();
|
|
1384
|
+
for (const entry of snapshot) {
|
|
1385
|
+
present.add(entry.entity);
|
|
1386
|
+
const prev = state.get(entry.entity);
|
|
1387
|
+
if (entry.hidden) {
|
|
1388
|
+
if (prev === "visible") adapter.unobserve(entry.entity);
|
|
1389
|
+
state.set(entry.entity, "hidden");
|
|
1390
|
+
} else {
|
|
1391
|
+
if (prev === void 0) {
|
|
1392
|
+
adapter.observe(entry.entity);
|
|
1393
|
+
} else if (prev === "hidden") {
|
|
1394
|
+
adapter.observe(entry.entity);
|
|
1395
|
+
forceRemeasure(entry.entity);
|
|
1396
|
+
}
|
|
1397
|
+
state.set(entry.entity, "visible");
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
for (const [e, s] of [...state]) {
|
|
1401
|
+
if (present.has(e)) continue;
|
|
1402
|
+
if (s === "visible") adapter.unobserve(e);
|
|
1403
|
+
state.delete(e);
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
const onStoreChange = () => {
|
|
1407
|
+
if (scheduled || disposed) return;
|
|
1408
|
+
scheduled = true;
|
|
1409
|
+
queueMicrotask(() => {
|
|
1410
|
+
scheduled = false;
|
|
1411
|
+
reconcile();
|
|
1412
|
+
});
|
|
1413
|
+
};
|
|
1414
|
+
const unsub = store.subscribe(onStoreChange);
|
|
1415
|
+
onStoreChange();
|
|
1416
|
+
return () => {
|
|
1417
|
+
disposed = true;
|
|
1418
|
+
unsub();
|
|
1419
|
+
adapter.dispose();
|
|
1420
|
+
state.clear();
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// ../dom/src/index.ts
|
|
1425
|
+
var DOM_VERSION = "0.0.0";
|
|
1426
|
+
|
|
1427
|
+
export {
|
|
1428
|
+
createCanvasHost,
|
|
1429
|
+
createPlanes,
|
|
1430
|
+
createPlaneTransformReflector,
|
|
1431
|
+
createGrayboxReflector,
|
|
1432
|
+
createDomWidgetsReflector,
|
|
1433
|
+
startRafLoop,
|
|
1434
|
+
attachPointerAdapter,
|
|
1435
|
+
createCursorReflector,
|
|
1436
|
+
createRemoteCursorsReflector,
|
|
1437
|
+
DEFAULT_GRID_CONFIG,
|
|
1438
|
+
createGridReflector,
|
|
1439
|
+
DEFAULT_WIRES_CONFIG,
|
|
1440
|
+
createWiresReflector,
|
|
1441
|
+
createChromeReflector,
|
|
1442
|
+
attachMeasureAdapter,
|
|
1443
|
+
wireMeasurement,
|
|
1444
|
+
DOM_VERSION
|
|
1445
|
+
};
|
|
1446
|
+
//# sourceMappingURL=chunk-FIAXPNNY.js.map
|