@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.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +76 -0
  3. package/dist/chunk-5S7GSXJG.js +6003 -0
  4. package/dist/chunk-5S7GSXJG.js.map +1 -0
  5. package/dist/chunk-BCJ47SX7.js +550 -0
  6. package/dist/chunk-BCJ47SX7.js.map +1 -0
  7. package/dist/chunk-FIAXPNNY.js +1446 -0
  8. package/dist/chunk-FIAXPNNY.js.map +1 -0
  9. package/dist/devtools.js +417 -0
  10. package/dist/devtools.js.map +1 -0
  11. package/dist/dom.js +41 -0
  12. package/dist/dom.js.map +1 -0
  13. package/dist/index.js +450 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/kernel.js +63 -0
  16. package/dist/kernel.js.map +1 -0
  17. package/dist/r3f.js +1101 -0
  18. package/dist/r3f.js.map +1 -0
  19. package/dist/react.js +440 -0
  20. package/dist/react.js.map +1 -0
  21. package/dist/types/packages/core/src/boot/hmr.d.ts +12 -0
  22. package/dist/types/packages/core/src/catalog/camera-derived.d.ts +144 -0
  23. package/dist/types/packages/core/src/catalog/gesture.d.ts +254 -0
  24. package/dist/types/packages/core/src/catalog/graph.d.ts +55 -0
  25. package/dist/types/packages/core/src/catalog/index.d.ts +32 -0
  26. package/dist/types/packages/core/src/catalog/pointer.d.ts +111 -0
  27. package/dist/types/packages/core/src/catalog/scene.d.ts +34 -0
  28. package/dist/types/packages/core/src/catalog/selection-presence.d.ts +53 -0
  29. package/dist/types/packages/core/src/catalog/settings-resources.d.ts +46 -0
  30. package/dist/types/packages/core/src/doc/autosave.d.ts +69 -0
  31. package/dist/types/packages/core/src/doc/bootstrap.d.ts +74 -0
  32. package/dist/types/packages/core/src/doc/broadcast-relay.d.ts +52 -0
  33. package/dist/types/packages/core/src/doc/channels.d.ts +49 -0
  34. package/dist/types/packages/core/src/doc/doc-commit-sink.d.ts +22 -0
  35. package/dist/types/packages/core/src/doc/doc-kit.d.ts +50 -0
  36. package/dist/types/packages/core/src/doc/envelope.d.ts +36 -0
  37. package/dist/types/packages/core/src/doc/migrate.d.ts +69 -0
  38. package/dist/types/packages/core/src/doc/version-gate.d.ts +49 -0
  39. package/dist/types/packages/core/src/engine/commit-sink.d.ts +61 -0
  40. package/dist/types/packages/core/src/engine/engine.d.ts +62 -0
  41. package/dist/types/packages/core/src/engine/frame-info.d.ts +16 -0
  42. package/dist/types/packages/core/src/engine/instantiate.d.ts +45 -0
  43. package/dist/types/packages/core/src/engine/pipeline.d.ts +17 -0
  44. package/dist/types/packages/core/src/engine/reflectors.d.ts +56 -0
  45. package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +120 -0
  46. package/dist/types/packages/core/src/guards/dev.d.ts +2 -0
  47. package/dist/types/packages/core/src/guards/guarded-tx.d.ts +51 -0
  48. package/dist/types/packages/core/src/guards/live-writer.d.ts +33 -0
  49. package/dist/types/packages/core/src/guards/resource-writer.d.ts +17 -0
  50. package/dist/types/packages/core/src/helpers/phase-set.d.ts +70 -0
  51. package/dist/types/packages/core/src/helpers/version-stamps.d.ts +55 -0
  52. package/dist/types/packages/core/src/index.d.ts +63 -0
  53. package/dist/types/packages/core/src/input/measure-queue.d.ts +30 -0
  54. package/dist/types/packages/core/src/input/queue.d.ts +60 -0
  55. package/dist/types/packages/core/src/interaction/install.d.ts +58 -0
  56. package/dist/types/packages/core/src/nav/nested-canvas.d.ts +62 -0
  57. package/dist/types/packages/core/src/ops/cascade.d.ts +18 -0
  58. package/dist/types/packages/core/src/ops/claims.d.ts +15 -0
  59. package/dist/types/packages/core/src/ops/gestures.d.ts +10 -0
  60. package/dist/types/packages/core/src/ops/point-pick.d.ts +42 -0
  61. package/dist/types/packages/core/src/ops/port-geometry.d.ts +17 -0
  62. package/dist/types/packages/core/src/ops/selection.d.ts +14 -0
  63. package/dist/types/packages/core/src/presence/index.d.ts +7 -0
  64. package/dist/types/packages/core/src/presence/presence-kit.d.ts +39 -0
  65. package/dist/types/packages/core/src/presence/publish.d.ts +14 -0
  66. package/dist/types/packages/core/src/presence/remote-cursors.d.ts +18 -0
  67. package/dist/types/packages/core/src/schema/meta.d.ts +43 -0
  68. package/dist/types/packages/core/src/schema/prefab.d.ts +69 -0
  69. package/dist/types/packages/core/src/settings/defaults.d.ts +56 -0
  70. package/dist/types/packages/core/src/systems/camera-sim.d.ts +19 -0
  71. package/dist/types/packages/core/src/systems/chrome.d.ts +48 -0
  72. package/dist/types/packages/core/src/systems/cleanup.d.ts +19 -0
  73. package/dist/types/packages/core/src/systems/l0-input.d.ts +31 -0
  74. package/dist/types/packages/core/src/systems/l1-pick.d.ts +44 -0
  75. package/dist/types/packages/core/src/systems/l1-wires.d.ts +30 -0
  76. package/dist/types/packages/core/src/systems/l2-arbitrate.d.ts +40 -0
  77. package/dist/types/packages/core/src/systems/l2-recognize.d.ts +45 -0
  78. package/dist/types/packages/core/src/systems/l3-behave.d.ts +24 -0
  79. package/dist/types/packages/core/src/systems/l3-claim.d.ts +26 -0
  80. package/dist/types/packages/core/src/systems/l3-connect.d.ts +57 -0
  81. package/dist/types/packages/core/src/systems/l3-draw.d.ts +21 -0
  82. package/dist/types/packages/core/src/systems/l3-drop.d.ts +21 -0
  83. package/dist/types/packages/core/src/systems/l3-marquee.d.ts +35 -0
  84. package/dist/types/packages/core/src/systems/l3-ports.d.ts +30 -0
  85. package/dist/types/packages/core/src/systems/l3-resize.d.ts +17 -0
  86. package/dist/types/packages/core/src/systems/l3-snap.d.ts +19 -0
  87. package/dist/types/packages/core/src/systems/l4-cursor.d.ts +24 -0
  88. package/dist/types/packages/core/src/systems/measure-ingest.d.ts +26 -0
  89. package/dist/types/packages/core/src/tools/define-tool.d.ts +79 -0
  90. package/dist/types/packages/core/src/widget/define-widget.d.ts +124 -0
  91. package/dist/types/packages/core/src/widget/equip.d.ts +14 -0
  92. package/dist/types/packages/core/src/widget/mount-store.d.ts +47 -0
  93. package/dist/types/packages/core/src/widget/props.d.ts +98 -0
  94. package/dist/types/packages/core/src/widget/spawn.d.ts +30 -0
  95. package/dist/types/packages/devtools/src/index.d.ts +8 -0
  96. package/dist/types/packages/devtools/src/panel.d.ts +48 -0
  97. package/dist/types/packages/dom/src/host.d.ts +24 -0
  98. package/dist/types/packages/dom/src/index.d.ts +19 -0
  99. package/dist/types/packages/dom/src/loop.d.ts +11 -0
  100. package/dist/types/packages/dom/src/measure-adapter.d.ts +31 -0
  101. package/dist/types/packages/dom/src/measure-wiring.d.ts +49 -0
  102. package/dist/types/packages/dom/src/planes.d.ts +33 -0
  103. package/dist/types/packages/dom/src/pointer-adapter.d.ts +48 -0
  104. package/dist/types/packages/dom/src/reflectors/chrome.d.ts +33 -0
  105. package/dist/types/packages/dom/src/reflectors/cursor.d.ts +21 -0
  106. package/dist/types/packages/dom/src/reflectors/dom-widgets.d.ts +54 -0
  107. package/dist/types/packages/dom/src/reflectors/graybox.d.ts +31 -0
  108. package/dist/types/packages/dom/src/reflectors/grid.d.ts +63 -0
  109. package/dist/types/packages/dom/src/reflectors/plane-transform.d.ts +38 -0
  110. package/dist/types/packages/dom/src/reflectors/remote-cursors.d.ts +20 -0
  111. package/dist/types/packages/dom/src/reflectors/wires.d.ts +88 -0
  112. package/dist/types/packages/ice/src/devtools.d.ts +1 -0
  113. package/dist/types/packages/ice/src/dom.d.ts +1 -0
  114. package/dist/types/packages/ice/src/index.d.ts +1 -0
  115. package/dist/types/packages/ice/src/kernel.d.ts +1 -0
  116. package/dist/types/packages/ice/src/r3f.d.ts +1 -0
  117. package/dist/types/packages/ice/src/react.d.ts +1 -0
  118. package/dist/types/packages/kernel/src/anchors.d.ts +18 -0
  119. package/dist/types/packages/kernel/src/bezier.d.ts +38 -0
  120. package/dist/types/packages/kernel/src/coords.d.ts +67 -0
  121. package/dist/types/packages/kernel/src/eviction.d.ts +23 -0
  122. package/dist/types/packages/kernel/src/index.d.ts +13 -0
  123. package/dist/types/packages/kernel/src/shapes.d.ts +22 -0
  124. package/dist/types/packages/kernel/src/snap.d.ts +44 -0
  125. package/dist/types/packages/kernel/src/spatial-index.d.ts +16 -0
  126. package/dist/types/packages/kernel/src/zoom-bands.d.ts +19 -0
  127. package/dist/types/packages/r3f/src/bridge.d.ts +68 -0
  128. package/dist/types/packages/r3f/src/composite-material.d.ts +17 -0
  129. package/dist/types/packages/r3f/src/compositor-pass.d.ts +83 -0
  130. package/dist/types/packages/r3f/src/dev-write-trap.d.ts +30 -0
  131. package/dist/types/packages/r3f/src/gl-root.d.ts +21 -0
  132. package/dist/types/packages/r3f/src/gl-router.d.ts +60 -0
  133. package/dist/types/packages/r3f/src/index.d.ts +16 -0
  134. package/dist/types/packages/r3f/src/island-state.d.ts +68 -0
  135. package/dist/types/packages/r3f/src/island.d.ts +13 -0
  136. package/dist/types/packages/r3f/src/pool.d.ts +56 -0
  137. package/dist/types/packages/r3f/src/resource-registry.d.ts +30 -0
  138. package/dist/types/packages/r3f/src/use-island-frame.d.ts +24 -0
  139. package/dist/types/packages/react/src/engine-context.d.ts +31 -0
  140. package/dist/types/packages/react/src/hooks.d.ts +40 -0
  141. package/dist/types/packages/react/src/index.d.ts +14 -0
  142. package/dist/types/packages/react/src/infinite-canvas.d.ts +71 -0
  143. package/dist/types/packages/react/src/keymap.d.ts +42 -0
  144. package/dist/types/packages/react/src/use-commit.d.ts +35 -0
  145. package/dist/types/packages/react/src/use-presence.d.ts +23 -0
  146. package/dist/types/packages/react/src/use-tool.d.ts +4 -0
  147. package/dist/types/packages/react/src/widget-root.d.ts +33 -0
  148. 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