@atolljs/islands 0.1.12 → 0.1.13

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.
@@ -0,0 +1,3159 @@
1
+ import {
2
+ CALLBACK_EVENT,
3
+ assertCloneableProps,
4
+ connectIslandWorker,
5
+ contractOf,
6
+ cssStyleValue,
7
+ defineIslandContract,
8
+ isEventRef,
9
+ isIslandContract,
10
+ isKeepAliveClient,
11
+ islandApp,
12
+ islandAppNameOf,
13
+ marshalCallbackProps,
14
+ mountIsland,
15
+ propsPreview,
16
+ registerSubIslandMounter,
17
+ renderMemory,
18
+ unmarshalCallbackProps,
19
+ withContract
20
+ } from "../chunk-TX4FAHRB.js";
21
+
22
+ // src/worker/defineWorkers.ts
23
+ import { defineWorker } from "@atolljs/core";
24
+
25
+ // src/metrics.ts
26
+ var proxyMetrics = {
27
+ enabled: false,
28
+ /** Frames currently inside instrumented proxy code (re-entrancy guard). */
29
+ depth: 0,
30
+ /** ms inside outermost instrumented frames since reset(). */
31
+ recordMs: 0,
32
+ /** Ops pushed since reset() — a count, free to keep. */
33
+ opsPushed: 0,
34
+ reset() {
35
+ this.depth = 0;
36
+ this.recordMs = 0;
37
+ this.opsPushed = 0;
38
+ }
39
+ };
40
+ var enterProxyFrame = () => {
41
+ proxyMetrics.depth++;
42
+ return proxyMetrics.depth === 1 ? performance.now() : -1;
43
+ };
44
+ var exitProxyFrame = (t0) => {
45
+ proxyMetrics.depth--;
46
+ if (t0 >= 0) proxyMetrics.recordMs += performance.now() - t0;
47
+ };
48
+
49
+ // src/worker/instance.ts
50
+ var timed = (body) => {
51
+ if (!proxyMetrics.enabled) return body();
52
+ const t0 = enterProxyFrame();
53
+ try {
54
+ return body();
55
+ } finally {
56
+ exitProxyFrame(t0);
57
+ }
58
+ };
59
+ var ROOT_CONTAINER = Object.freeze({ id: 0 });
60
+ var opsByInstance = /* @__PURE__ */ new Map();
61
+ var activeInstance = "";
62
+ var instances = /* @__PURE__ */ new Map();
63
+ var handlers = /* @__PURE__ */ new Map();
64
+ var DETACHED_CAP = 4096;
65
+ var detachedInstances = /* @__PURE__ */ new Map();
66
+ var detachedHandlers = /* @__PURE__ */ new Map();
67
+ var evictOldest = (map) => {
68
+ if (map.size <= DETACHED_CAP) return;
69
+ const oldest = map.keys().next().value;
70
+ if (oldest !== void 0) map.delete(oldest);
71
+ };
72
+ var detachInstance = (id, handlerIds) => {
73
+ const record = instances.get(id);
74
+ if (record === void 0) return;
75
+ instances.delete(id);
76
+ detachedInstances.set(id, record);
77
+ evictOldest(detachedInstances);
78
+ for (const hid of handlerIds) {
79
+ const entry = handlers.get(hid);
80
+ if (entry !== void 0) {
81
+ handlers.delete(hid);
82
+ detachedHandlers.set(hid, entry);
83
+ evictOldest(detachedHandlers);
84
+ }
85
+ }
86
+ };
87
+ var attachInstance = (id, handlerIds) => {
88
+ const record = detachedInstances.get(id);
89
+ if (record !== void 0) {
90
+ detachedInstances.delete(id);
91
+ instances.set(id, record);
92
+ }
93
+ for (const hid of handlerIds) {
94
+ const entry = detachedHandlers.get(hid);
95
+ if (entry !== void 0) {
96
+ detachedHandlers.delete(hid);
97
+ handlers.set(hid, entry);
98
+ }
99
+ }
100
+ };
101
+ var lookupInstance = (id) => instances.get(id) ?? detachedInstances.get(id);
102
+ var nextId = 1;
103
+ var nextHandlerId = 1;
104
+ var instanceContracts = /* @__PURE__ */ new Map();
105
+ var setInstanceContract = (instance, contract) => {
106
+ if (contract === void 0) instanceContracts.delete(instance);
107
+ else instanceContracts.set(instance, contract);
108
+ };
109
+ var allocId = () => nextId++;
110
+ var lastInstance = "";
111
+ var setActiveInstance = (instance) => {
112
+ const prev = activeInstance;
113
+ activeInstance = instance;
114
+ if (instance !== "") lastInstance = instance;
115
+ return prev;
116
+ };
117
+ var getActiveInstance = () => activeInstance;
118
+ var getLastActiveInstance = () => lastInstance;
119
+ var touchedInstance = "";
120
+ var getLastTouchedInstance = () => touchedInstance;
121
+ var markInstanceActive = (instance) => {
122
+ if (instance) lastInstance = instance;
123
+ };
124
+ var runInInstance = (instance, fn) => {
125
+ const prev = setActiveInstance(instance);
126
+ try {
127
+ return fn();
128
+ } finally {
129
+ setActiveInstance(prev);
130
+ }
131
+ };
132
+ var pushOp = (instance, op) => timed(() => {
133
+ if (instance !== "") touchedInstance = instance;
134
+ let queue = opsByInstance.get(instance);
135
+ if (!queue) opsByInstance.set(instance, queue = []);
136
+ queue.push(op);
137
+ proxyMetrics.opsPushed++;
138
+ });
139
+ var doorbell = renderMemory;
140
+ var setDoorbellContract = (contract) => {
141
+ doorbell = contract;
142
+ };
143
+ var bumpOpsVersion = () => {
144
+ if (!doorbell.bound) return;
145
+ const bell = doorbell.connector("opsVersion");
146
+ bell.write(Number(bell.read() ?? 0) + 1);
147
+ };
148
+ var takeOps = (instance) => timed(() => {
149
+ const queue = opsByInstance.get(instance);
150
+ if (!queue || queue.length === 0) return [];
151
+ opsByInstance.set(instance, []);
152
+ return queue;
153
+ });
154
+ var proxyInstanceStats = () => ({
155
+ liveNodes: instances.size,
156
+ handlers: handlers.size,
157
+ queuedOps: [...opsByInstance.values()].reduce((a, q) => a + q.length, 0),
158
+ queuedByInstance: Object.fromEntries(
159
+ [...opsByInstance.entries()].map(([k, q]) => [k, q.length])
160
+ )
161
+ });
162
+ var getHandler = (id) => handlers.get(id) ?? detachedHandlers.get(id);
163
+ var instanceSizes = /* @__PURE__ */ new Map();
164
+ var getInstanceSize = (instance) => instanceSizes.get(instance);
165
+ var setInstanceSize = (instance, w, h) => {
166
+ instanceSizes.set(instance, { w, h });
167
+ };
168
+ var instanceRects = /* @__PURE__ */ new Map();
169
+ var getInstanceRect = (instance, id) => instanceRects.get(instance)?.get(id);
170
+ var setInstanceRect = (instance, rect) => {
171
+ let table = instanceRects.get(instance);
172
+ if (table === void 0) instanceRects.set(instance, table = /* @__PURE__ */ new Map());
173
+ table.set(rect.id, { x: rect.x, y: rect.y, w: rect.w, h: rect.h });
174
+ };
175
+ var clearInstanceRects = (instance) => {
176
+ instanceRects.delete(instance);
177
+ };
178
+ var registerHandler = (fn, instance, instanceId) => timed(() => {
179
+ const id = nextHandlerId++;
180
+ handlers.set(id, { fn, instance, instanceId });
181
+ return id;
182
+ });
183
+ var unregisterHandler = (id) => {
184
+ timed(() => {
185
+ handlers.delete(id);
186
+ detachedHandlers.delete(id);
187
+ });
188
+ };
189
+ var emit = (name, payload) => {
190
+ timed(() => {
191
+ const schema = instanceContracts.get(activeInstance)?.events?.[name];
192
+ if (schema !== void 0) {
193
+ try {
194
+ payload = schema.parse(payload);
195
+ } catch (err) {
196
+ throw new Error(
197
+ `emit("${name}") rejected by the island contract for "${activeInstance}": ` + (err instanceof Error ? err.message : String(err))
198
+ );
199
+ }
200
+ }
201
+ pushOp(activeInstance, { t: "emit", name, payload });
202
+ });
203
+ };
204
+ function serializeProps(instance, props) {
205
+ return timed(() => {
206
+ const out = {};
207
+ for (const [name, value] of Object.entries(props)) {
208
+ if (value === void 0 || value === null) continue;
209
+ if (name === "children" || name === "key" || name === "ref" || name === "dangerouslySetInnerHTML") {
210
+ continue;
211
+ }
212
+ if (typeof value === "function") {
213
+ if (name.length > 2 && name.startsWith("on")) {
214
+ let slot = instance.listenerSlots[name];
215
+ if (slot === void 0) {
216
+ slot = nextHandlerId++;
217
+ instance.listenerSlots[name] = slot;
218
+ }
219
+ handlers.set(slot, {
220
+ fn: value,
221
+ instance: instance.instance,
222
+ instanceId: instance.id
223
+ });
224
+ out[name] = { __evt: slot };
225
+ }
226
+ continue;
227
+ }
228
+ out[name] = value;
229
+ }
230
+ return out;
231
+ });
232
+ }
233
+ function newElement(type, props, ns, instance) {
234
+ return timed(() => {
235
+ const record = {
236
+ kind: "element",
237
+ id: allocId(),
238
+ type,
239
+ ns,
240
+ instance,
241
+ props: {},
242
+ listenerSlots: {}
243
+ };
244
+ record.props = serializeProps(record, props);
245
+ instances.set(record.id, record);
246
+ return record;
247
+ });
248
+ }
249
+ function newText(text, instance) {
250
+ return timed(() => {
251
+ const record = { kind: "text", id: allocId(), text, instance };
252
+ instances.set(record.id, record);
253
+ return record;
254
+ });
255
+ }
256
+
257
+ // src/worker/dom/node.ts
258
+ var normalizeListenerOptions = (options) => {
259
+ const o = typeof options === "boolean" ? { capture: options } : options ?? {};
260
+ return { once: o.once === true, passive: o.passive === true, capture: o.capture === true };
261
+ };
262
+ var listenerOptsForWire = (options) => {
263
+ const o = normalizeListenerOptions(options);
264
+ return o.once || o.passive || o.capture ? { once: o.once, passive: o.passive, capture: o.capture } : void 0;
265
+ };
266
+ var hyphenate = (k) => k.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
267
+ var camelize = (k) => k.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
268
+ var nextPhantomId = -1;
269
+ var allocPhantomId = () => nextPhantomId--;
270
+ var rejectForeignChild = (child) => {
271
+ throw new Error(
272
+ "proxyDom.insertBefore: child is not a proxy node \u2014 the ambient `document`/`createElement` resolved a foreign DOM (a stale override or the real one), so this node was created outside the instance that owns the parent"
273
+ );
274
+ };
275
+ var detachFromParent = (child) => {
276
+ const p = child._parent;
277
+ if (p instanceof ProxyNode) p._detach(child);
278
+ else child._parent = null;
279
+ };
280
+ var asChild = (doc, n) => typeof n === "string" ? doc.createTextNode(n) : n;
281
+ var ProxyNode = class _ProxyNode {
282
+ /** The shared host-instance record — same objects `instances` holds. */
283
+ instance;
284
+ /** The document that created/wrapped this node (internal fields below). */
285
+ doc;
286
+ _parent = null;
287
+ _children = [];
288
+ constructor(doc, instance) {
289
+ this.doc = doc;
290
+ this.instance = instance;
291
+ }
292
+ get nodeType() {
293
+ return 0;
294
+ }
295
+ /**
296
+ * '#text'/'#comment'/'#document-fragment' for character/fragment nodes;
297
+ * the tag name for elements — uppercase in the HTML namespace, verbatim
298
+ * for namespaced elements (svg 'foreignObject' keeps its case, matching
299
+ * the real DOM).
300
+ */
301
+ get nodeName() {
302
+ switch (this.nodeType) {
303
+ case 1: {
304
+ const inst = this.instance;
305
+ return inst.ns === void 0 ? inst.type.toUpperCase() : inst.type;
306
+ }
307
+ case 3:
308
+ return "#text";
309
+ case 8:
310
+ return "#comment";
311
+ case 11:
312
+ return "#document-fragment";
313
+ default:
314
+ return "#node";
315
+ }
316
+ }
317
+ /** The proxy document that owns this node — what real DOM code reads as
318
+ * `el.ownerDocument`. */
319
+ get ownerDocument() {
320
+ return this.doc;
321
+ }
322
+ get parentNode() {
323
+ return this._parent;
324
+ }
325
+ get parentElement() {
326
+ return this._parent !== null && this._parent.nodeType === 1 ? this._parent : null;
327
+ }
328
+ /** A copy of the shadow child list — structural changes go through
329
+ * appendChild/insertBefore/removeChild so ops stay in sync. */
330
+ get childNodes() {
331
+ return this._children.slice();
332
+ }
333
+ get firstChild() {
334
+ return this._children[0] ?? null;
335
+ }
336
+ get lastChild() {
337
+ return this._children[this._children.length - 1] ?? null;
338
+ }
339
+ get nextSibling() {
340
+ const p = this._parent;
341
+ if (p === null) return null;
342
+ return p._children[p._children.indexOf(this) + 1] ?? null;
343
+ }
344
+ get previousSibling() {
345
+ const p = this._parent;
346
+ if (p === null) return null;
347
+ const i = p._children.indexOf(this);
348
+ return i > 0 ? p._children[i - 1] : null;
349
+ }
350
+ /** Attached to the document's root (driver-side id 0 container)? */
351
+ get isConnected() {
352
+ let n = this;
353
+ while (n !== null) {
354
+ if (n === this.doc._root) return true;
355
+ n = n._parent;
356
+ }
357
+ return false;
358
+ }
359
+ /**
360
+ * The topmost shadow-tree ancestor — the root container element when the
361
+ * node is connected. (The facade has no separate Document node object;
362
+ * `doc.body`/`documentElement` ARE that root.)
363
+ */
364
+ getRootNode() {
365
+ let n = this;
366
+ while (n._parent !== null) n = n._parent;
367
+ return n;
368
+ }
369
+ /** Concatenated descendant text — walked from the shadow tree. Comments
370
+ * are excluded: the DOM's descendant text content is Text-node data only. */
371
+ get textContent() {
372
+ let out = "";
373
+ for (const child of this._children) {
374
+ if (child.nodeType !== 8) out += child.textContent;
375
+ }
376
+ return out;
377
+ }
378
+ set textContent(value) {
379
+ this.doc._assertAlive();
380
+ const text = String(value);
381
+ for (const child of this._children) child._parent = null;
382
+ this._children = text === "" ? [] : [this.doc._phantomText(this, text)];
383
+ this._op({ t: "utext", id: this.instance.id, text });
384
+ }
385
+ contains(other) {
386
+ let n = other;
387
+ while (n !== null) {
388
+ if (n === this) return true;
389
+ n = n._parent;
390
+ }
391
+ return false;
392
+ }
393
+ appendChild(child) {
394
+ return this.insertBefore(child, null);
395
+ }
396
+ insertBefore(child, ref) {
397
+ this.doc._assertAlive();
398
+ if (this.nodeType === 3 || this.nodeType === 8) {
399
+ throw new Error(
400
+ `proxyDom.insertBefore: ${this.nodeName} nodes cannot have children`
401
+ );
402
+ }
403
+ if (!(child instanceof _ProxyNode)) rejectForeignChild(child);
404
+ if (child instanceof ProxyFragment) {
405
+ for (const c of child._children.slice()) this.insertBefore(c, ref);
406
+ return child;
407
+ }
408
+ let beforeId;
409
+ if (ref === null) {
410
+ detachFromParent(child);
411
+ child._parent = this;
412
+ this._children.push(child);
413
+ } else {
414
+ const index = this._children.indexOf(ref);
415
+ if (index === -1) {
416
+ throw new Error("proxyDom.insertBefore: reference node is not a child of this node");
417
+ }
418
+ detachFromParent(child);
419
+ child._parent = this;
420
+ this._children.splice(index, 0, child);
421
+ if (ref.instance.id > 0) beforeId = ref.instance.id;
422
+ }
423
+ child._attachRecords();
424
+ if (this.instance.id >= 0 && child.instance.id > 0) {
425
+ this._op({ t: "append", parent: this.instance.id, child: child.instance.id, before: beforeId });
426
+ if (child.nodeType === 1 && this.doc._measureRequested.has(child.instance.id)) {
427
+ child._op({ t: "measure", id: child.instance.id });
428
+ }
429
+ }
430
+ return child;
431
+ }
432
+ removeChild(child) {
433
+ this.doc._assertAlive();
434
+ const index = this._children.indexOf(child);
435
+ if (index === -1) {
436
+ throw new Error("proxyDom.removeChild: node is not a child of this node");
437
+ }
438
+ this._children.splice(index, 1);
439
+ child._parent = null;
440
+ child._detachRecords();
441
+ if (this.instance.id >= 0 && child.instance.id > 0) {
442
+ this._op({ t: "remove", child: child.instance.id });
443
+ }
444
+ return child;
445
+ }
446
+ /** Detach from the parent — no-op when already orphaned (like the DOM). */
447
+ remove() {
448
+ this._parent?.removeChild(this);
449
+ }
450
+ /* ── ChildNode / ParentNode conveniences ───────────────────────────────
451
+ * Strings become text nodes like the DOM. before/after/replaceWith on a
452
+ * parentless node are no-ops (per spec). append/prepend/replaceChildren
453
+ * live on the base so elements AND fragments share them — real DOM also
454
+ * exposes them on DocumentFragment. */
455
+ before(...nodes) {
456
+ const p = this._parent;
457
+ if (p === null) return;
458
+ for (const n of nodes) p.insertBefore(asChild(this.doc, n), this);
459
+ }
460
+ after(...nodes) {
461
+ const p = this._parent;
462
+ if (p === null) return;
463
+ const ref = this.nextSibling;
464
+ for (const n of nodes) p.insertBefore(asChild(this.doc, n), ref);
465
+ }
466
+ replaceWith(...nodes) {
467
+ const p = this._parent;
468
+ if (p === null) return;
469
+ for (const n of nodes) p.insertBefore(asChild(this.doc, n), this);
470
+ p.removeChild(this);
471
+ }
472
+ /** append(...nodes) — strings become text nodes, like the DOM. */
473
+ append(...nodes) {
474
+ this.doc._assertAlive();
475
+ for (const n of nodes) this.appendChild(asChild(this.doc, n));
476
+ }
477
+ /** prepend(...nodes) — inserts at the front, in argument order. */
478
+ prepend(...nodes) {
479
+ this.doc._assertAlive();
480
+ const ref = this.firstChild;
481
+ for (const n of nodes) this.insertBefore(asChild(this.doc, n), ref);
482
+ }
483
+ /** replaceChildren(...nodes) — remove every child, then append the set. */
484
+ replaceChildren(...nodes) {
485
+ this.doc._assertAlive();
486
+ for (const child of this._children.slice()) this.removeChild(child);
487
+ this.append(...nodes);
488
+ }
489
+ /**
490
+ * Structural clone — each concrete kind implements it. The copy is a
491
+ * FRESH instance (own id, own create op), not a shared record; listeners
492
+ * never cross, matching the DOM.
493
+ */
494
+ cloneNode(_deep) {
495
+ throw new Error("proxyDom.cloneNode: unsupported node kind");
496
+ }
497
+ /**
498
+ * Queue an op on this node's instance — the same routing hostConfig uses for
499
+ * instance-bound ops. Outside a instance task the op still lands on the right
500
+ * queue, and the doorbell is bumped so push-mode islands pick it up.
501
+ */
502
+ _op(op) {
503
+ pushOp(this.instance.instance, op);
504
+ if (getActiveInstance() !== this.instance.instance) bumpOpsVersion();
505
+ }
506
+ /** Detach a child from the shadow list WITHOUT emitting an op — used when
507
+ * a following append op will move the real node on the main thread. */
508
+ _detach(child) {
509
+ const i = this._children.indexOf(child);
510
+ if (i !== -1) this._children.splice(i, 1);
511
+ child._parent = null;
512
+ }
513
+ /** Handler ids this node's records own: addEventListener hids (element
514
+ * subclass only) plus the prop-listener slots on the instance record. */
515
+ _recordHandlerIds() {
516
+ const listeners = this._listeners;
517
+ const slots = this.instance.listenerSlots;
518
+ return [...listeners?.map((l) => l.hid) ?? [], ...Object.values(slots ?? {})];
519
+ }
520
+ /** Tombstone this subtree's instance/handler records (removeChild). */
521
+ _detachRecords() {
522
+ if (this.instance.id > 0) detachInstance(this.instance.id, this._recordHandlerIds());
523
+ for (const c of this._children) c._detachRecords();
524
+ }
525
+ /** Restore this subtree's records on re-append (idempotent — a live node
526
+ * is simply not in the tombstone maps). */
527
+ _attachRecords() {
528
+ if (this.instance.id > 0) attachInstance(this.instance.id, this._recordHandlerIds());
529
+ for (const c of this._children) c._attachRecords();
530
+ }
531
+ };
532
+ var ProxyText = class extends ProxyNode {
533
+ constructor(doc, instance) {
534
+ super(doc, instance);
535
+ }
536
+ get nodeType() {
537
+ return 3;
538
+ }
539
+ /**
540
+ * The driver-side id a text write targets. Real text instances write
541
+ * their own id; phantoms (created by an element's `textContent` setter)
542
+ * have no driver-side node, so writes retarget the parent — whose real
543
+ * `textContent` IS this node's whole text.
544
+ */
545
+ get _utextTarget() {
546
+ return this.instance.id > 0 ? this.instance.id : this._parent?.instance.id ?? this.instance.id;
547
+ }
548
+ get data() {
549
+ return this.instance.text;
550
+ }
551
+ set data(v) {
552
+ this._write(v);
553
+ }
554
+ get nodeValue() {
555
+ return this.instance.text;
556
+ }
557
+ set nodeValue(v) {
558
+ this._write(v);
559
+ }
560
+ get textContent() {
561
+ return this.instance.text;
562
+ }
563
+ set textContent(v) {
564
+ this._write(v);
565
+ }
566
+ _write(v) {
567
+ this.doc._assertAlive();
568
+ const text = String(v);
569
+ this.instance.text = text;
570
+ this._op({ t: "utext", id: this._utextTarget, text });
571
+ }
572
+ /** Fresh text node — the clone is a new `text` op, not a shared record. */
573
+ cloneNode(_deep) {
574
+ return this.doc.createTextNode(this.instance.text);
575
+ }
576
+ };
577
+ var ProxyComment = class extends ProxyText {
578
+ get nodeType() {
579
+ return 8;
580
+ }
581
+ get data() {
582
+ return this.instance.text;
583
+ }
584
+ set data(v) {
585
+ this._shadowWrite(v);
586
+ }
587
+ get nodeValue() {
588
+ return this.instance.text;
589
+ }
590
+ set nodeValue(v) {
591
+ this._shadowWrite(v);
592
+ }
593
+ get textContent() {
594
+ return this.instance.text;
595
+ }
596
+ set textContent(v) {
597
+ this._shadowWrite(v);
598
+ }
599
+ _shadowWrite(v) {
600
+ this.doc._assertAlive();
601
+ this.instance.text = String(v);
602
+ }
603
+ cloneNode(_deep) {
604
+ return this.doc.createComment(this.data);
605
+ }
606
+ };
607
+ var ProxyFragment = class extends ProxyNode {
608
+ get nodeType() {
609
+ return 11;
610
+ }
611
+ /** Fresh fragment; `deep` clones children polymorphically so comment
612
+ * anchors keep nodeType 8. */
613
+ cloneNode(deep) {
614
+ const copy = this.doc.createDocumentFragment();
615
+ if (deep === true) {
616
+ for (const child of this._children) copy.appendChild(child.cloneNode(true));
617
+ }
618
+ return copy;
619
+ }
620
+ };
621
+
622
+ // src/worker/dom/html.ts
623
+ import { parseDocument, ElementType } from "htmlparser2";
624
+ function parseChildren(doc, html) {
625
+ const parsed = parseDocument(String(html));
626
+ const build = (nodes) => {
627
+ const out = [];
628
+ for (const node of nodes) {
629
+ switch (node.type) {
630
+ case ElementType.Tag:
631
+ case ElementType.Script:
632
+ case ElementType.Style: {
633
+ const el = doc.createElement(node.name);
634
+ for (const [name, value] of Object.entries(node.attribs)) el.setAttribute(name, value);
635
+ for (const child of build(node.children)) el.appendChild(child);
636
+ out.push(el);
637
+ break;
638
+ }
639
+ case ElementType.Text: {
640
+ if (node.data !== "") out.push(doc.createTextNode(node.data));
641
+ break;
642
+ }
643
+ case ElementType.Comment: {
644
+ out.push(doc.createComment(node.data));
645
+ break;
646
+ }
647
+ case ElementType.CDATA: {
648
+ for (const child of build(node.children)) out.push(child);
649
+ break;
650
+ }
651
+ default:
652
+ break;
653
+ }
654
+ }
655
+ return out;
656
+ };
657
+ return build(parsed.children);
658
+ }
659
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
660
+ "area",
661
+ "base",
662
+ "br",
663
+ "col",
664
+ "embed",
665
+ "hr",
666
+ "img",
667
+ "input",
668
+ "link",
669
+ "meta",
670
+ "param",
671
+ "source",
672
+ "track",
673
+ "wbr"
674
+ ]);
675
+ var escapeText = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
676
+ var escapeAttr = (s) => s.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
677
+ function serializeNode(node) {
678
+ if (node instanceof ProxyComment) return `<!--${node.data}-->`;
679
+ if (node instanceof ProxyText) return escapeText(node.textContent);
680
+ if (!(node instanceof ProxyElement)) return "";
681
+ const tag = node.instance.type;
682
+ const attrs = new Map(node._attrs);
683
+ const styleText = Object.entries(node._styleProps).filter(([, v]) => v !== "").map(([k, v]) => `${hyphenate(k)}: ${v};`).join(" ");
684
+ if (styleText !== "") attrs.set("style", styleText);
685
+ let out = `<${tag}`;
686
+ for (const [name, value] of attrs) out += ` ${name}="${escapeAttr(value)}"`;
687
+ if (VOID_ELEMENTS.has(tag)) return `${out}>`;
688
+ out += ">";
689
+ for (const child of node._children) out += serializeNode(child);
690
+ return `${out}</${tag}>`;
691
+ }
692
+
693
+ // src/worker/dom/selectors.ts
694
+ var unsupportedSelector = (sel) => {
695
+ throw new Error(
696
+ `proxyDom.querySelector: unsupported selector "${sel}" \u2014 only tag, .class, #id, [attr], [attr="v"] and descendant combinators are supported`
697
+ );
698
+ };
699
+ function parseCompound(part, sel) {
700
+ const out = { classes: [], attrs: [] };
701
+ let i = 0;
702
+ const tag = /^[a-zA-Z][\w-]*/.exec(part);
703
+ if (tag !== null) {
704
+ out.tag = tag[0].toLowerCase();
705
+ i = tag[0].length;
706
+ }
707
+ while (i < part.length) {
708
+ const ch = part[i];
709
+ if (ch === "#") {
710
+ const m = /^[\w-]+/.exec(part.slice(i + 1));
711
+ if (m === null) unsupportedSelector(sel);
712
+ out.id = m[0];
713
+ i += 1 + m[0].length;
714
+ } else if (ch === ".") {
715
+ const m = /^[\w-]+/.exec(part.slice(i + 1));
716
+ if (m === null) unsupportedSelector(sel);
717
+ out.classes.push(m[0]);
718
+ i += 1 + m[0].length;
719
+ } else if (ch === "[") {
720
+ const end = part.indexOf("]", i);
721
+ if (end === -1) unsupportedSelector(sel);
722
+ const body = part.slice(i + 1, end).trim();
723
+ const m = /^([\w-]+)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^'"\s]+)))?$/.exec(body);
724
+ if (m === null) unsupportedSelector(sel);
725
+ out.attrs.push({ name: m[1], value: m[2] ?? m[3] ?? m[4] });
726
+ i = end + 1;
727
+ } else {
728
+ unsupportedSelector(sel);
729
+ }
730
+ }
731
+ if (out.tag === void 0 && out.id === void 0 && out.classes.length === 0 && out.attrs.length === 0) {
732
+ unsupportedSelector(sel);
733
+ }
734
+ return out;
735
+ }
736
+ function parseSelector(selector) {
737
+ const sel = selector.trim();
738
+ if (sel === "" || sel.includes(",") || sel.includes(">") || sel.includes("+") || sel.includes("~") || sel.includes(":")) {
739
+ unsupportedSelector(selector);
740
+ }
741
+ return sel.split(/\s+/).map((part) => parseCompound(part, selector));
742
+ }
743
+ function matchCompound(el, c) {
744
+ if (c.tag !== void 0 && el.tagName.toLowerCase() !== c.tag) return false;
745
+ if (c.id !== void 0 && el._attrs.get("id") !== c.id) return false;
746
+ for (const cls of c.classes) if (!el._classes.has(cls)) return false;
747
+ for (const a of c.attrs) {
748
+ const v = el._attrs.get(a.name);
749
+ if (v === void 0) return false;
750
+ if (a.value !== void 0 && v !== a.value) return false;
751
+ }
752
+ return true;
753
+ }
754
+ function matchesChain(el, chain) {
755
+ if (!matchCompound(el, chain[chain.length - 1])) return false;
756
+ let cur = el._parent;
757
+ for (let i = chain.length - 2; i >= 0; i--) {
758
+ let found = false;
759
+ while (cur !== null) {
760
+ if (cur instanceof ProxyElement && matchCompound(cur, chain[i])) {
761
+ found = true;
762
+ cur = cur._parent;
763
+ break;
764
+ }
765
+ cur = cur._parent;
766
+ }
767
+ if (!found) return false;
768
+ }
769
+ return true;
770
+ }
771
+
772
+ // src/worker/dom/css.ts
773
+ var classLists = /* @__PURE__ */ new WeakMap();
774
+ var styles = /* @__PURE__ */ new WeakMap();
775
+ var datasets = /* @__PURE__ */ new WeakMap();
776
+ function syncClasses(el) {
777
+ const v = [...el._classes].join(" ");
778
+ el._attrs.set("class", v);
779
+ el._op({ t: "attr", id: el.instance.id, name: "class", value: v === "" ? null : v });
780
+ }
781
+ function classListFor(el) {
782
+ let cl = classLists.get(el);
783
+ if (cl === void 0) {
784
+ cl = {
785
+ add: (...tokens) => {
786
+ let changed = false;
787
+ for (const t of tokens) {
788
+ if (t !== "" && !el._classes.has(t)) {
789
+ el._classes.add(t);
790
+ changed = true;
791
+ }
792
+ }
793
+ if (changed) syncClasses(el);
794
+ },
795
+ remove: (...tokens) => {
796
+ let changed = false;
797
+ for (const t of tokens) changed = el._classes.delete(t) || changed;
798
+ if (changed) syncClasses(el);
799
+ },
800
+ toggle: (token, force) => {
801
+ const has = el._classes.has(token);
802
+ const want = force ?? !has;
803
+ if (want === has) return has;
804
+ if (want) el._classes.add(token);
805
+ else el._classes.delete(token);
806
+ syncClasses(el);
807
+ return want;
808
+ },
809
+ contains: (token) => el._classes.has(token),
810
+ get value() {
811
+ return el._attrs.get("class") ?? "";
812
+ }
813
+ };
814
+ classLists.set(el, cl);
815
+ }
816
+ return cl;
817
+ }
818
+ function styleProxyFor(el) {
819
+ let style = styles.get(el);
820
+ if (style === void 0) {
821
+ style = new Proxy(el._styleProps, {
822
+ get: (t, prop) => {
823
+ if (prop === "setProperty") {
824
+ return (k, v, priority) => {
825
+ const key = k.startsWith("--") ? k : camelize(k);
826
+ el._writeStyle(key, priority === "important" && v !== "" ? `${v} !important` : v);
827
+ };
828
+ }
829
+ if (prop === "removeProperty")
830
+ return (k) => el._writeStyle(k.startsWith("--") ? k : camelize(k), "");
831
+ if (prop === "getPropertyValue")
832
+ return (k) => {
833
+ const key = k.startsWith("--") ? k : camelize(k);
834
+ return String(t[key] ?? "").replace(/\s*!important$/, "");
835
+ };
836
+ if (prop === "cssText") {
837
+ return Object.entries(t).filter(([, v]) => v !== "").map(([k, v]) => `${hyphenate(k)}: ${v};`).join(" ");
838
+ }
839
+ if (typeof prop === "string") return t[prop] ?? "";
840
+ return void 0;
841
+ },
842
+ set: (_t, prop, v) => {
843
+ if (prop === "cssText") el._replaceStyleText(String(v));
844
+ else if (typeof prop === "string") el._writeStyle(prop, String(v));
845
+ return true;
846
+ },
847
+ deleteProperty: (_t, prop) => {
848
+ if (typeof prop === "string") el._writeStyle(prop, "");
849
+ return true;
850
+ },
851
+ has: (t, prop) => prop in t,
852
+ ownKeys: (t) => Reflect.ownKeys(t),
853
+ getOwnPropertyDescriptor: (t, prop) => typeof prop === "string" && prop in t ? { configurable: true, enumerable: true, value: t[prop] } : void 0
854
+ });
855
+ styles.set(el, style);
856
+ }
857
+ return style;
858
+ }
859
+ function datasetProxyFor(el) {
860
+ let ds = datasets.get(el);
861
+ if (ds === void 0) {
862
+ ds = new Proxy({}, {
863
+ get: (_t, prop) => {
864
+ if (typeof prop !== "string") return void 0;
865
+ return el.getAttribute(`data-${hyphenate(prop)}`) ?? void 0;
866
+ },
867
+ set: (_t, prop, v) => {
868
+ if (typeof prop === "string") el.setAttribute(`data-${hyphenate(prop)}`, v);
869
+ return true;
870
+ },
871
+ deleteProperty: (_t, prop) => {
872
+ if (typeof prop === "string") el.removeAttribute(`data-${hyphenate(prop)}`);
873
+ return true;
874
+ },
875
+ has: (_t, prop) => typeof prop === "string" && el._attrs.has(`data-${hyphenate(prop)}`),
876
+ ownKeys: () => [...el._attrs.keys()].filter((k) => k.startsWith("data-")).map((k) => camelize(k.slice(5))),
877
+ getOwnPropertyDescriptor: (_t, prop) => {
878
+ if (typeof prop !== "string") return void 0;
879
+ const v = el.getAttribute(`data-${hyphenate(prop)}`);
880
+ return v === null ? void 0 : { configurable: true, enumerable: true, value: v };
881
+ }
882
+ });
883
+ datasets.set(el, ds);
884
+ }
885
+ return ds;
886
+ }
887
+
888
+ // src/worker/dom/element.ts
889
+ var templateContents = /* @__PURE__ */ new WeakMap();
890
+ var ProxyElement = class _ProxyElement extends ProxyNode {
891
+ _attrs = /* @__PURE__ */ new Map();
892
+ _classes = /* @__PURE__ */ new Set();
893
+ _styleProps = {};
894
+ _listeners = [];
895
+ get nodeType() {
896
+ return 1;
897
+ }
898
+ get tagName() {
899
+ return this.instance.type.toUpperCase();
900
+ }
901
+ /** The element's namespace URI — HTML for ordinary elements, the `create`
902
+ * op's `ns` for namespaced (svg/mathml) ones. */
903
+ get namespaceURI() {
904
+ return this.instance.ns ?? "http://www.w3.org/1999/xhtml";
905
+ }
906
+ /** Element children only — the shadow tree's HTMLCollection equivalent. */
907
+ get children() {
908
+ return this._children.filter((c) => c instanceof _ProxyElement);
909
+ }
910
+ get id() {
911
+ return this._attrs.get("id") ?? "";
912
+ }
913
+ set id(v) {
914
+ this._setAttr("id", v);
915
+ }
916
+ get className() {
917
+ return this._attrs.get("class") ?? "";
918
+ }
919
+ set className(v) {
920
+ this._setAttr("class", v);
921
+ }
922
+ getAttribute(name) {
923
+ return this._attrs.get(name) ?? null;
924
+ }
925
+ hasAttribute(name) {
926
+ return this._attrs.has(name);
927
+ }
928
+ setAttribute(name, value) {
929
+ this._setAttr(name, value);
930
+ }
931
+ removeAttribute(name) {
932
+ this.doc._assertAlive();
933
+ if (!this._attrs.has(name)) return;
934
+ this._attrs.delete(name);
935
+ this._afterAttrChange(name);
936
+ this._op({ t: "attr", id: this.instance.id, name, value: null });
937
+ }
938
+ /* Namespaced attribute surface — the wire `attr` op has no namespace
939
+ * field, so the qualified name ('xlink:href') is the whole shadow key
940
+ * and the driver's setAttribute covers prefixed names. Limitation: two
941
+ * same-local-name attributes in different namespaces can't be told
942
+ * apart. get/remove follow the DOM convention of receiving the LOCAL
943
+ * name — resolve it to the stored qualified key (or accept a qualified
944
+ * name directly, since some callers pass both ways). */
945
+ setAttributeNS(_ns, qualifiedName, value) {
946
+ this.setAttribute(qualifiedName, value);
947
+ }
948
+ getAttributeNS(_ns, name) {
949
+ const direct = this.getAttribute(name);
950
+ if (direct !== null) return direct;
951
+ for (const key of this._attrs.keys()) {
952
+ if (key.endsWith(`:${name}`)) return this.getAttribute(key);
953
+ }
954
+ return null;
955
+ }
956
+ removeAttributeNS(_ns, name) {
957
+ if (this.getAttribute(name) !== null) {
958
+ this.removeAttribute(name);
959
+ return;
960
+ }
961
+ for (const key of [...this._attrs.keys()]) {
962
+ if (key.endsWith(`:${name}`)) this.removeAttribute(key);
963
+ }
964
+ }
965
+ /* classList — a live view over _classes synced to the `class` attr. */
966
+ get classList() {
967
+ return classListFor(this);
968
+ }
969
+ /**
970
+ * Inline style — a Proxy over a plain prop record. Writes re-send only
971
+ * the changed key; `delete style.x`/`style.removeProperty('x')` send ''
972
+ * (the protocol's "clear this key"). `setProperty`/`getPropertyValue`
973
+ * accept kebab-case and camelize it, like the real CSSStyleDeclaration.
974
+ */
975
+ get style() {
976
+ return styleProxyFor(this);
977
+ }
978
+ /**
979
+ * dataset — a Proxy mapping camelCase keys to `data-*` attribute ops.
980
+ * `swatch.dataset.color = c` emits attr {name:'data-color', value:c};
981
+ * `delete el.dataset.x` emits the removal form.
982
+ */
983
+ get dataset() {
984
+ return datasetProxyFor(this);
985
+ }
986
+ /**
987
+ * Focus/blur — NO-OPS. Real focus can't be delivered over async postMessage
988
+ * (the driver's dispatch fires after the event; there is no focus op).
989
+ * They exist because gesture libraries call `el.focus()` unconditionally
990
+ * during mousedown paths (Leaflet's Keyboard handler does).
991
+ */
992
+ focus() {
993
+ }
994
+ blur() {
995
+ }
996
+ /* Events — registers a worker handler-table entry and emits listen.
997
+ * The driver's listenerFor dispatches EventPayloads back into the worker;
998
+ * the payload is enriched with a synthesized `target`/`composedPath` and
999
+ * the handler runs with `this` = this element, like the real DOM's
1000
+ * currentTarget. `{once, passive, capture}` ride the op's `opts` field;
1001
+ * `once` additionally auto-detaches worker-side after the first dispatch
1002
+ * (matching the real listener's own auto-removal driver-side). */
1003
+ addEventListener(type, fn, options) {
1004
+ this.doc._assertAlive();
1005
+ const opts = normalizeListenerOptions(options);
1006
+ if (this._listeners.some((l) => l.type === type && l.fn === fn && l.capture === opts.capture)) {
1007
+ return;
1008
+ }
1009
+ const hid = registerHandler(
1010
+ (p) => {
1011
+ try {
1012
+ fn.call(this, this.doc._enrichEvent(p, this));
1013
+ } finally {
1014
+ if (opts.once) this.removeEventListener(type, fn, { capture: opts.capture });
1015
+ }
1016
+ },
1017
+ this.instance.instance,
1018
+ this.instance.id
1019
+ );
1020
+ this._listeners.push({ type, fn, hid, capture: opts.capture });
1021
+ this.doc._handlerIds.add(hid);
1022
+ this._op({
1023
+ t: "listen",
1024
+ id: this.instance.id,
1025
+ type,
1026
+ handler: hid,
1027
+ opts: listenerOptsForWire(options)
1028
+ });
1029
+ }
1030
+ removeEventListener(type, fn, options) {
1031
+ this.doc._assertAlive();
1032
+ const capture = normalizeListenerOptions(options).capture;
1033
+ const index = this._listeners.findIndex(
1034
+ (l) => l.type === type && l.fn === fn && l.capture === capture
1035
+ );
1036
+ if (index === -1) return;
1037
+ const [entry] = this._listeners.splice(index, 1);
1038
+ unregisterHandler(entry.hid);
1039
+ this.doc._handlerIds.delete(entry.hid);
1040
+ this._op({
1041
+ t: "unlisten",
1042
+ id: this.instance.id,
1043
+ type,
1044
+ handler: entry.hid,
1045
+ opts: entry.capture ? { capture: true } : void 0
1046
+ });
1047
+ }
1048
+ /** Live-descendant search over the shadow tree. Nodes mounted by the
1049
+ * reconciler aren't reachable (the op stream doesn't mirror React's
1050
+ * structure into _children) — adopted subtrees match only their own
1051
+ * proxy-built descendants. Class matching reads _classes AND the
1052
+ * instance's serialized className prop, so adopted React elements with
1053
+ * className set still match. */
1054
+ getElementsByClassName(name) {
1055
+ this.doc._assertAlive();
1056
+ const out = [];
1057
+ const visit = (el) => {
1058
+ for (const child of el._children) {
1059
+ if (!(child instanceof _ProxyElement)) continue;
1060
+ const viaProps = (child.instance.props?.className ?? "").split(/\s+/).includes(name);
1061
+ if (child._classes.has(name) || viaProps) out.push(child);
1062
+ visit(child);
1063
+ }
1064
+ };
1065
+ visit(this);
1066
+ return out;
1067
+ }
1068
+ getElementsByTagName(tag) {
1069
+ this.doc._assertAlive();
1070
+ const want = tag.toLowerCase();
1071
+ const out = [];
1072
+ const visit = (el) => {
1073
+ for (const child of el._children) {
1074
+ if (!(child instanceof _ProxyElement)) continue;
1075
+ if (want === "*" || child.instance.type.toLowerCase() === want) out.push(child);
1076
+ visit(child);
1077
+ }
1078
+ };
1079
+ visit(this);
1080
+ return out;
1081
+ }
1082
+ /* SVG measurement APIs — no layout/text engine exists worker-side, so
1083
+ * these report honest zeros (warned once), exactly like the rest of the
1084
+ * geometry surface. Libraries doing animation sizing (recharts'
1085
+ * getTotalLength paths) degrade to instant/no animation. */
1086
+ getBBox() {
1087
+ this.doc._assertAlive();
1088
+ this.doc._warn("getBBox");
1089
+ return { x: 0, y: 0, width: 0, height: 0, top: 0, right: 0, bottom: 0, left: 0 };
1090
+ }
1091
+ getTotalLength() {
1092
+ this.doc._assertAlive();
1093
+ this.doc._warn("getTotalLength");
1094
+ return 0;
1095
+ }
1096
+ getComputedTextLength() {
1097
+ this.doc._assertAlive();
1098
+ this.doc._warn("getComputedTextLength");
1099
+ return 0;
1100
+ }
1101
+ /* Selector matching — compound selectors only (see parseSelector). */
1102
+ matches(selector) {
1103
+ return matchesChain(this, parseSelector(selector));
1104
+ }
1105
+ /** Nearest element (self inclusive) matching the selector — walks the
1106
+ * shadow tree upward through ancestors. */
1107
+ closest(selector) {
1108
+ const chain = parseSelector(selector);
1109
+ let n = this;
1110
+ while (n !== null) {
1111
+ if (n instanceof _ProxyElement && matchesChain(n, chain)) return n;
1112
+ n = n._parent;
1113
+ }
1114
+ return null;
1115
+ }
1116
+ /** Scoped local-tree query — descendants only, same engine and the same
1117
+ * supported-selector subset as document.querySelectorAll. */
1118
+ querySelector(selector) {
1119
+ return this.querySelectorAll(selector)[0] ?? null;
1120
+ }
1121
+ querySelectorAll(selector) {
1122
+ const chain = parseSelector(selector);
1123
+ const out = [];
1124
+ const walk = (node) => {
1125
+ for (const child of node._children) {
1126
+ if (child instanceof _ProxyElement && matchesChain(child, chain)) out.push(child);
1127
+ walk(child);
1128
+ }
1129
+ };
1130
+ walk(this);
1131
+ return out;
1132
+ }
1133
+ /* ── innerHTML + friends: real HTML parsing worker-side ──────────────── */
1134
+ /** Serialized shadow-tree children — the only innerHTML there CAN be over
1135
+ * an async op channel (it reflects writes made through this facade, not
1136
+ * the real DOM's current markup). */
1137
+ get innerHTML() {
1138
+ return this._children.map((child) => serializeNode(child)).join("");
1139
+ }
1140
+ get outerHTML() {
1141
+ return serializeNode(this);
1142
+ }
1143
+ /**
1144
+ * Parse `html` worker-side (htmlparser2 — pure JS, worker-safe) and
1145
+ * rebuild this element's children as proxy nodes: each parsed element is
1146
+ * createElement + setAttribute + recursive children, each text run a
1147
+ * createTextNode — all emitting ordinary create/attr/append ops. Existing
1148
+ * children are removed first (one remove op each). Comments and
1149
+ * directives are skipped.
1150
+ */
1151
+ set innerHTML(html) {
1152
+ this.doc._assertAlive();
1153
+ for (const child of this._children.slice()) this.removeChild(child);
1154
+ for (const node of parseChildren(this.doc, html)) this.appendChild(node);
1155
+ }
1156
+ /**
1157
+ * Parse `html` and insert the resulting nodes relative to this element —
1158
+ * 'beforebegin'/'afterend' require a parent and no-op without one (like
1159
+ * the DOM on detached elements).
1160
+ */
1161
+ insertAdjacentHTML(position, html) {
1162
+ this.doc._assertAlive();
1163
+ this._insertAt(position, parseChildren(this.doc, html));
1164
+ }
1165
+ /** insertAdjacentElement — returns the inserted element like the DOM. */
1166
+ insertAdjacentElement(position, el) {
1167
+ this.doc._assertAlive();
1168
+ if (!(el instanceof _ProxyElement)) return null;
1169
+ this._insertAt(position, [el]);
1170
+ return el;
1171
+ }
1172
+ _insertAt(position, nodes) {
1173
+ switch (position) {
1174
+ case "beforebegin": {
1175
+ if (this._parent === null) return;
1176
+ for (const n of nodes) this._parent.insertBefore(n, this);
1177
+ return;
1178
+ }
1179
+ case "afterbegin": {
1180
+ const ref = this.firstChild;
1181
+ for (const n of nodes) this.insertBefore(n, ref);
1182
+ return;
1183
+ }
1184
+ case "beforeend": {
1185
+ for (const n of nodes) this.appendChild(n);
1186
+ return;
1187
+ }
1188
+ case "afterend": {
1189
+ if (this._parent === null) return;
1190
+ const ref = this.nextSibling;
1191
+ for (const n of nodes) this._parent.insertBefore(n, ref);
1192
+ return;
1193
+ }
1194
+ default:
1195
+ throw new Error(`proxyDom.insertAdjacent*: unknown position "${String(position)}"`);
1196
+ }
1197
+ }
1198
+ /**
1199
+ * `<template>.content` — a fragment VIEW sharing the element's shadow
1200
+ * child array (same backing store) AND the element's instance id, so
1201
+ * `content.appendChild` emits `append` ops targeted at the template. The
1202
+ * driver routes template-parented ops into `el.content` — the DOM spec's
1203
+ * home for template children anyway. Non-template elements read
1204
+ * undefined.
1205
+ */
1206
+ get content() {
1207
+ if (this.instance.type !== "template") return void 0;
1208
+ let frag = templateContents.get(this);
1209
+ if (frag === void 0) {
1210
+ frag = this.doc.createDocumentFragment();
1211
+ frag._children = this._children;
1212
+ frag.instance = this.instance;
1213
+ templateContents.set(this, frag);
1214
+ }
1215
+ return frag;
1216
+ }
1217
+ /**
1218
+ * Clone as new ops — the copy gets its own instance id (it's a real new
1219
+ * element on the main thread, not a shared record). Attributes, the
1220
+ * namespace and the style-proxy properties are copied; listeners are NOT
1221
+ * (matches the DOM). `deep` clones children polymorphically — comment
1222
+ * children keep nodeType 8, texts and nested elements/fragments their
1223
+ * own kind.
1224
+ */
1225
+ cloneNode(deep) {
1226
+ this.doc._assertAlive();
1227
+ const copy = this.instance.ns === void 0 ? this.doc.createElement(this.instance.type) : this.doc.createElementNS(this.instance.ns, this.instance.type);
1228
+ for (const [name, value] of this._attrs) copy.setAttribute(name, value);
1229
+ for (const [k, v] of Object.entries(this._styleProps)) copy._writeStyle(k, v);
1230
+ if (deep === true) {
1231
+ for (const child of this._children) copy.appendChild(child.cloneNode(true));
1232
+ }
1233
+ return copy;
1234
+ }
1235
+ /* ── Geometry. Two pushed channels: the island container's setSize box
1236
+ * (root + markContainer()ed elements) and per-element setRects boxes for
1237
+ * elements the worker asked the driver to measure — via doc.observeSize,
1238
+ * a ResizeObserver shim, or lazily by any of the reads below. Until the
1239
+ * first push lands a read keeps the honest 0 and warns once per doc per
1240
+ * API; a marked element keeps reporting the CONTAINER's box — "marked"
1241
+ * still means "pretend my box is the container's" and is only honest
1242
+ * for elements that genuinely fill it. ── */
1243
+ getBoundingClientRect() {
1244
+ const rect = this.doc._rectFor(this);
1245
+ if (rect === void 0) {
1246
+ this.doc._requestMeasure(this);
1247
+ this.doc._warn("getBoundingClientRect");
1248
+ return {
1249
+ x: 0,
1250
+ y: 0,
1251
+ top: 0,
1252
+ left: 0,
1253
+ right: 0,
1254
+ bottom: 0,
1255
+ width: 0,
1256
+ height: 0,
1257
+ toJSON: () => ({})
1258
+ };
1259
+ }
1260
+ return {
1261
+ x: rect.x,
1262
+ y: rect.y,
1263
+ top: rect.y,
1264
+ left: rect.x,
1265
+ right: rect.x + rect.w,
1266
+ bottom: rect.y + rect.h,
1267
+ width: rect.w,
1268
+ height: rect.h,
1269
+ toJSON: () => ({})
1270
+ };
1271
+ }
1272
+ get clientWidth() {
1273
+ return this._geom("clientWidth", (r) => r.w);
1274
+ }
1275
+ get clientHeight() {
1276
+ return this._geom("clientHeight", (r) => r.h);
1277
+ }
1278
+ get offsetWidth() {
1279
+ return this._geom("offsetWidth", (r) => r.w);
1280
+ }
1281
+ get offsetHeight() {
1282
+ return this._geom("offsetHeight", (r) => r.h);
1283
+ }
1284
+ /** Border edge widths — honestly 0 (the pushed box doesn't measure borders). */
1285
+ get clientLeft() {
1286
+ return 0;
1287
+ }
1288
+ get clientTop() {
1289
+ return 0;
1290
+ }
1291
+ /** Position within offsetParent — not measured; 0 is honest for root-level boxes. */
1292
+ get offsetLeft() {
1293
+ return 0;
1294
+ }
1295
+ get offsetTop() {
1296
+ return 0;
1297
+ }
1298
+ get offsetParent() {
1299
+ return null;
1300
+ }
1301
+ get scrollTop() {
1302
+ this.doc._scrollWarn("scrollTop");
1303
+ return 0;
1304
+ }
1305
+ set scrollTop(_v) {
1306
+ this.doc._scrollWarn("scrollTop");
1307
+ }
1308
+ get scrollLeft() {
1309
+ this.doc._scrollWarn("scrollLeft");
1310
+ return 0;
1311
+ }
1312
+ set scrollLeft(_v) {
1313
+ this.doc._scrollWarn("scrollLeft");
1314
+ }
1315
+ get scrollHeight() {
1316
+ this.doc._scrollWarn("scrollHeight");
1317
+ return 0;
1318
+ }
1319
+ get scrollWidth() {
1320
+ this.doc._scrollWarn("scrollWidth");
1321
+ return 0;
1322
+ }
1323
+ /**
1324
+ * Reflected properties. Library code assigns `img.src = url`,
1325
+ * `el.tabIndex = 0`, `link.href = '#'` — on a bare class instance those
1326
+ * become silent expandos and nothing crosses the wire. The attribute-backed
1327
+ * accessors below (defined for the properties real libraries poke) make
1328
+ * those assignments emit ordinary `attr` ops. Everything else still falls
1329
+ * through to a plain own-property — which is also exactly what library
1330
+ * expandos like `_leaflet_pos` need, so proxy elements are deliberately
1331
+ * NOT frozen/sealed.
1332
+ */
1333
+ get src() {
1334
+ return this._attrs.get("src") ?? "";
1335
+ }
1336
+ set src(v) {
1337
+ this._setAttr("src", v);
1338
+ }
1339
+ get srcset() {
1340
+ return this._attrs.get("srcset") ?? "";
1341
+ }
1342
+ set srcset(v) {
1343
+ this._setAttr("srcset", v);
1344
+ }
1345
+ get href() {
1346
+ return this._attrs.get("href") ?? "";
1347
+ }
1348
+ set href(v) {
1349
+ this._setAttr("href", v);
1350
+ }
1351
+ get alt() {
1352
+ return this._attrs.get("alt") ?? "";
1353
+ }
1354
+ set alt(v) {
1355
+ this._setAttr("alt", v);
1356
+ }
1357
+ get title() {
1358
+ return this._attrs.get("title") ?? "";
1359
+ }
1360
+ set title(v) {
1361
+ this._setAttr("title", v);
1362
+ }
1363
+ get tabIndex() {
1364
+ return Number(this._attrs.get("tabindex") ?? -1);
1365
+ }
1366
+ set tabIndex(v) {
1367
+ this._setAttr("tabindex", String(v));
1368
+ }
1369
+ get draggable() {
1370
+ return this._attrs.get("draggable") === "true";
1371
+ }
1372
+ set draggable(v) {
1373
+ this._setAttr("draggable", String(v));
1374
+ }
1375
+ get crossOrigin() {
1376
+ return this._attrs.get("crossorigin") ?? null;
1377
+ }
1378
+ set crossOrigin(v) {
1379
+ if (v === null) this.removeAttribute("crossorigin");
1380
+ else this._setAttr("crossorigin", v);
1381
+ }
1382
+ get width() {
1383
+ return Number(this._attrs.get("width") ?? 0);
1384
+ }
1385
+ set width(v) {
1386
+ this._setAttr("width", String(v));
1387
+ }
1388
+ get height() {
1389
+ return Number(this._attrs.get("height") ?? 0);
1390
+ }
1391
+ set height(v) {
1392
+ this._setAttr("height", String(v));
1393
+ }
1394
+ get type() {
1395
+ return this._attrs.get("type") ?? "";
1396
+ }
1397
+ set type(v) {
1398
+ this._setAttr("type", v);
1399
+ }
1400
+ get loading() {
1401
+ return this._attrs.get("loading") ?? "";
1402
+ }
1403
+ set loading(v) {
1404
+ this._setAttr("loading", v);
1405
+ }
1406
+ get decoding() {
1407
+ return this._attrs.get("decoding") ?? "";
1408
+ }
1409
+ set decoding(v) {
1410
+ this._setAttr("decoding", v);
1411
+ }
1412
+ get value() {
1413
+ return this._attrs.get("value") ?? "";
1414
+ }
1415
+ set value(v) {
1416
+ this._setAttr("value", v);
1417
+ }
1418
+ get checked() {
1419
+ return this._attrs.has("checked");
1420
+ }
1421
+ set checked(v) {
1422
+ if (v) this._setAttr("checked", "");
1423
+ else this.removeAttribute("checked");
1424
+ }
1425
+ get disabled() {
1426
+ return this._attrs.has("disabled");
1427
+ }
1428
+ set disabled(v) {
1429
+ if (v) this._setAttr("disabled", "");
1430
+ else this.removeAttribute("disabled");
1431
+ }
1432
+ /* internals */
1433
+ /** Geometry read shared by the size getters above. */
1434
+ _geom(feature, pick) {
1435
+ const rect = this.doc._rectFor(this);
1436
+ if (rect === void 0) {
1437
+ this.doc._requestMeasure(this);
1438
+ this.doc._warn(feature);
1439
+ return 0;
1440
+ }
1441
+ return pick(rect);
1442
+ }
1443
+ _writeStyle(key, value) {
1444
+ this.doc._assertAlive();
1445
+ if (this._styleProps[key] === value) return;
1446
+ this._styleProps[key] = value;
1447
+ this._op({ t: "style", id: this.instance.id, props: { [key]: value } });
1448
+ }
1449
+ /** style.cssText — a FULL replace, not another key. Parse the declarations
1450
+ * and write each through the dedup path; keys absent from the new text
1451
+ * clear. Storing the text under a 'cssText' key left stale per-key values
1452
+ * that deduped real writes away (a hide's `visibility:hidden` stuck after
1453
+ * a show's cssText replay). */
1454
+ _replaceStyleText(cssText) {
1455
+ const next = /* @__PURE__ */ new Map();
1456
+ for (const decl of cssText.split(";")) {
1457
+ const sep = decl.indexOf(":");
1458
+ if (sep <= 0) continue;
1459
+ const raw = decl.slice(0, sep).trim();
1460
+ const value = decl.slice(sep + 1).trim();
1461
+ if (raw === "" || value === "") continue;
1462
+ next.set(raw.startsWith("--") ? raw : camelize(raw), value);
1463
+ }
1464
+ for (const key of Object.keys(this._styleProps)) {
1465
+ if (!next.has(key)) this._writeStyle(key, "");
1466
+ }
1467
+ for (const [key, value] of next) this._writeStyle(key, value);
1468
+ }
1469
+ _setAttr(name, value) {
1470
+ this.doc._assertAlive();
1471
+ const v = String(value);
1472
+ if (this._attrs.get(name) === v) return;
1473
+ this._attrs.set(name, v);
1474
+ this._afterAttrChange(name);
1475
+ this._op({ t: "attr", id: this.instance.id, name, value: v });
1476
+ }
1477
+ _afterAttrChange(name) {
1478
+ if (name === "class") {
1479
+ this._classes = new Set((this._attrs.get("class") ?? "").split(/\s+/).filter(Boolean));
1480
+ } else if (name === "id") {
1481
+ this.doc._trackId(this);
1482
+ }
1483
+ }
1484
+ };
1485
+
1486
+ // src/worker/dom/document.ts
1487
+ var InternalDocument = class {
1488
+ instance;
1489
+ _root;
1490
+ _wrappers = /* @__PURE__ */ new Map();
1491
+ _ids = /* @__PURE__ */ new Map();
1492
+ /** handler ids this document registered — disposed on rebuild. */
1493
+ _handlerIds = /* @__PURE__ */ new Set();
1494
+ /**
1495
+ * instance ids geometry reads answer for — id 0 (the root) plus every
1496
+ * markContainer()ed element. The pushed box is the ISLAND CONTAINER's,
1497
+ * so a marked element claims "my box is the container's box".
1498
+ */
1499
+ _containerIds = /* @__PURE__ */ new Set([0]);
1500
+ /** Handlers fired when a setSize task pushes a new container size. */
1501
+ _resizeHandlers = /* @__PURE__ */ new Set();
1502
+ /** element id → observeSize callbacks, fired on each setRects push. */
1503
+ _sizeObservers = /* @__PURE__ */ new Map();
1504
+ /** ids a `measure` op was already emitted for — dedupes observeSize and
1505
+ * the lazy auto-request a bare geometry read performs. */
1506
+ _measureRequested = /* @__PURE__ */ new Set();
1507
+ /** The window facade bundle — built lazily by windowFacadeFor, shared by
1508
+ * installDomShim and the instance dispatcher. */
1509
+ _windowBundle;
1510
+ /** what `document.defaultView` reads as (`window.getComputedStyle` etc). */
1511
+ get defaultView() {
1512
+ return this._windowBundle?.facade;
1513
+ }
1514
+ /** Document-level listeners — separate table from the root element's, so
1515
+ * doc.addEventListener and body.addEventListener don't wrongly dedupe
1516
+ * against each other. Both emit `listen` on id 0. */
1517
+ _docListeners = [];
1518
+ _warned = /* @__PURE__ */ new Set();
1519
+ _disposed = false;
1520
+ /** Set by installDomShim — restore globals when this document dies. */
1521
+ _uninstallShim = null;
1522
+ constructor(instance) {
1523
+ this.instance = instance;
1524
+ this._root = new ProxyElement(this, {
1525
+ kind: "element",
1526
+ id: 0,
1527
+ type: "#root",
1528
+ instance,
1529
+ props: {},
1530
+ listenerSlots: {}
1531
+ });
1532
+ this._wrappers.set(0, this._root);
1533
+ }
1534
+ get body() {
1535
+ return this._root;
1536
+ }
1537
+ get documentElement() {
1538
+ return this._root;
1539
+ }
1540
+ /** No hit-testing exists worker-side — honest null (a11y-layer probing). */
1541
+ elementFromPoint(_x, _y) {
1542
+ this._assertAlive();
1543
+ this._warn("elementFromPoint");
1544
+ return null;
1545
+ }
1546
+ _assertAlive() {
1547
+ if (this._disposed) {
1548
+ throw new Error(
1549
+ "proxyDom: this document was disposed by an updateProps/remount rebuild \u2014 its nodes are detached"
1550
+ );
1551
+ }
1552
+ }
1553
+ _warn(feature, message) {
1554
+ if (this._warned.has(feature)) return;
1555
+ this._warned.add(feature);
1556
+ console.warn(
1557
+ message ?? `[proxyDom] '${feature}' has no synchronous answer \u2014 a measure request was emitted and reads return real geometry once the driver pushes it; subscribe via doc.observeSize or a ResizeObserver for updates.`
1558
+ );
1559
+ }
1560
+ /** Scroll-geometry warn — the measure channel delivers border boxes, not
1561
+ * scroll offsets/overflow extents, so scroll* reads stay honest 0. */
1562
+ _scrollWarn(feature) {
1563
+ this._warn(
1564
+ feature,
1565
+ `[proxyDom] '${feature}' has no channel \u2014 the worker sees border-box geometry only (setSize/measure pushes); scroll offsets and overflow extents read 0.`
1566
+ );
1567
+ }
1568
+ markContainer(el) {
1569
+ this._assertAlive();
1570
+ this._containerIds.add(el.instance.id);
1571
+ }
1572
+ /**
1573
+ * Emit `measure` for `el` exactly once — shared by observeSize (explicit
1574
+ * subscription) and by geometry reads on unmarked connected elements (the
1575
+ * lazy path: a library reading clientWidth gets the honest 0 for now, and
1576
+ * REAL geometry on subsequent reads once the driver's push lands).
1577
+ * Disconnected/phantom nodes have no driver-side element to measure;
1578
+ * requesting is skipped until the read happens while connected.
1579
+ */
1580
+ _requestMeasure(el) {
1581
+ if (!el.isConnected) return;
1582
+ const id = el.instance.id;
1583
+ if (this._measureRequested.has(id)) return;
1584
+ this._measureRequested.add(id);
1585
+ el._op({ t: "measure", id });
1586
+ }
1587
+ observeSize(el, cb) {
1588
+ this._assertAlive();
1589
+ const id = el.instance.id;
1590
+ let set = this._sizeObservers.get(id);
1591
+ if (set === void 0) this._sizeObservers.set(id, set = /* @__PURE__ */ new Set());
1592
+ set.add(cb);
1593
+ if (!this._measureRequested.has(id)) {
1594
+ this._measureRequested.add(id);
1595
+ el._op({ t: "measure", id });
1596
+ }
1597
+ return () => {
1598
+ const observers = this._sizeObservers.get(id);
1599
+ if (observers === void 0) return;
1600
+ observers.delete(cb);
1601
+ if (observers.size === 0) {
1602
+ this._sizeObservers.delete(id);
1603
+ this._measureRequested.delete(id);
1604
+ el._op({ t: "unmeasure", id });
1605
+ }
1606
+ };
1607
+ }
1608
+ /**
1609
+ * Store pushed rects and fire per-element observers — the `setRects`
1610
+ * task calls this inside the instance's scope, so observer mutations
1611
+ * emit ops and ride back in the task's return batch.
1612
+ */
1613
+ _notifyRects(rects) {
1614
+ for (const rect of rects) {
1615
+ setInstanceRect(this.instance, rect);
1616
+ const observers = this._sizeObservers.get(rect.id);
1617
+ if (observers !== void 0) for (const cb of [...observers]) cb(rect);
1618
+ }
1619
+ }
1620
+ /**
1621
+ * The rect a geometry read on `el` may report: the element's own measured
1622
+ * box when a setRects push arrived for it, else the pushed CONTAINER box
1623
+ * when `el` is the root or was markContainer()ed (the old contract — a
1624
+ * marked element claims "my box is the container's"), undefined otherwise
1625
+ * (also undefined when nothing has been pushed yet — reads return 0).
1626
+ */
1627
+ _rectFor(el) {
1628
+ const measured = getInstanceRect(this.instance, el.instance.id);
1629
+ if (measured !== void 0) return measured;
1630
+ if (!this._containerIds.has(el.instance.id)) return void 0;
1631
+ const size = getInstanceSize(this.instance);
1632
+ return size === void 0 ? void 0 : { x: 0, y: 0, ...size };
1633
+ }
1634
+ onResize(cb) {
1635
+ this._assertAlive();
1636
+ this._resizeHandlers.add(cb);
1637
+ }
1638
+ /**
1639
+ * Fire the doc's resize handlers — the worker's `setSize` task calls this
1640
+ * inside the instance's scope, so handler mutations emit ops on the instance
1641
+ * queue and come back in the task's return batch.
1642
+ */
1643
+ _notifySize(w, h) {
1644
+ for (const cb of [...this._resizeHandlers]) cb(w, h);
1645
+ }
1646
+ /** Keep the getElementById map pointing at each element's CURRENT id. */
1647
+ _trackId(el) {
1648
+ for (const [k, v] of this._ids) if (v === el) this._ids.delete(k);
1649
+ const id = el._attrs.get("id");
1650
+ if (id !== void 0 && id !== "") this._ids.set(id, el);
1651
+ }
1652
+ /** The synthetic text child an element's `textContent` setter leaves in
1653
+ * the shadow tree — no driver-side node, so its writes retarget parent. */
1654
+ _phantomText(parent, text) {
1655
+ const t = new ProxyText(this, {
1656
+ kind: "text",
1657
+ id: allocPhantomId(),
1658
+ text,
1659
+ instance: parent.instance.instance
1660
+ });
1661
+ t._parent = parent;
1662
+ return t;
1663
+ }
1664
+ /**
1665
+ * Enrich a dispatched payload before handing it to a proxy listener:
1666
+ * - `target` = the proxy node for targetId, looked up in the shared
1667
+ * instance space and wrapped in THIS document's wrapper map (so
1668
+ * `e.target.closest`/`.dataset`/`.contains` behave like the real DOM
1669
+ * for proxy-created nodes).
1670
+ * - `currentTarget` = `self` — the node/document/facade the listener was
1671
+ * attached to (the handler is also invoked with `this` = self). For
1672
+ * document/window listeners this is the document/facade OBJECT, not
1673
+ * the id-0 root element dispatch() would otherwise stamp.
1674
+ * - `composedPath()` = synthesized from the enriched target's parent
1675
+ * chain, with `self` appended when the chain doesn't already reach it
1676
+ * (delegated handlers iterate the path until they hit their own
1677
+ * element — Svelte's handle_event_propagation terminates there).
1678
+ * - `preventDefault`/`stopPropagation`/`stopImmediatePropagation` =
1679
+ * synthesized NO-OPS. They cannot cancel anything — the real event
1680
+ * already dispatched on the main thread before this payload crossed
1681
+ * postMessage — but library code written against real DOM events calls
1682
+ * them unconditionally, so the wrapper supplies them.
1683
+ */
1684
+ _enrichEvent(payload, self) {
1685
+ const out = {
1686
+ ...payload,
1687
+ preventDefault: () => {
1688
+ },
1689
+ stopPropagation: () => {
1690
+ },
1691
+ stopImmediatePropagation: () => {
1692
+ }
1693
+ };
1694
+ if (self !== void 0) out.currentTarget = self;
1695
+ const id = payload.targetId;
1696
+ if (typeof id === "number") {
1697
+ const instance = lookupInstance(id);
1698
+ if (instance !== void 0) {
1699
+ const node = this._wrap(instance);
1700
+ if (node instanceof ProxyElement) {
1701
+ if (payload.value !== void 0) node._attrs.set("value", String(payload.value));
1702
+ if (payload.checked !== void 0) {
1703
+ if (payload.checked) node._attrs.set("checked", "");
1704
+ else node._attrs.delete("checked");
1705
+ }
1706
+ }
1707
+ out.target = node;
1708
+ }
1709
+ }
1710
+ if (typeof payload.relatedTargetId === "number") {
1711
+ const related = lookupInstance(payload.relatedTargetId);
1712
+ if (related !== void 0) out.relatedTarget = this._wrap(related);
1713
+ }
1714
+ if (out.composedPath === void 0) {
1715
+ const path = [];
1716
+ let n = out.target;
1717
+ while (n instanceof ProxyNode) {
1718
+ path.push(n);
1719
+ n = n._parent ?? void 0;
1720
+ }
1721
+ if (self !== void 0 && !path.includes(self)) path.push(self);
1722
+ out.composedPath = () => path;
1723
+ }
1724
+ return out;
1725
+ }
1726
+ createElement(tag) {
1727
+ return this._newElement(tag.toLowerCase(), void 0);
1728
+ }
1729
+ createElementNS(ns, tag) {
1730
+ return this._newElement(tag, ns);
1731
+ }
1732
+ _newElement(type, ns) {
1733
+ this._assertAlive();
1734
+ const instance = {
1735
+ kind: "element",
1736
+ id: allocId(),
1737
+ type,
1738
+ ns,
1739
+ instance: this.instance,
1740
+ props: {},
1741
+ listenerSlots: {}
1742
+ };
1743
+ instances.set(instance.id, instance);
1744
+ pushOp(this.instance, { t: "create", id: instance.id, type: instance.type, props: {}, ns: instance.ns });
1745
+ if (getActiveInstance() !== this.instance) bumpOpsVersion();
1746
+ return this._wrap(instance);
1747
+ }
1748
+ createTextNode(text) {
1749
+ this._assertAlive();
1750
+ const instance = {
1751
+ kind: "text",
1752
+ id: allocId(),
1753
+ text: String(text),
1754
+ instance: this.instance
1755
+ };
1756
+ instances.set(instance.id, instance);
1757
+ pushOp(this.instance, { t: "text", id: instance.id, text: instance.text });
1758
+ if (getActiveInstance() !== this.instance) bumpOpsVersion();
1759
+ return this._wrap(instance);
1760
+ }
1761
+ adopt(instance) {
1762
+ this._assertAlive();
1763
+ return this._wrap(instance);
1764
+ }
1765
+ createDocumentFragment() {
1766
+ this._assertAlive();
1767
+ return new ProxyFragment(this, {
1768
+ kind: "element",
1769
+ id: allocPhantomId(),
1770
+ type: "#fragment",
1771
+ instance: this.instance,
1772
+ props: {},
1773
+ listenerSlots: {}
1774
+ });
1775
+ }
1776
+ /**
1777
+ * A comment — nodeType 8 over a real EMPTY text instance. The wire has no
1778
+ * comment op: the `text` op with '' gives it a driver-side node that can
1779
+ * anchor `before:` positions, while `data` stays shadow-only (emitting it
1780
+ * would render comment text on the page).
1781
+ */
1782
+ createComment(data = "") {
1783
+ const backing = this.createTextNode("");
1784
+ const comment = new ProxyComment(this, backing.instance);
1785
+ comment.instance.text = String(data);
1786
+ this._wrappers.set(backing.instance.id, comment);
1787
+ return comment;
1788
+ }
1789
+ /**
1790
+ * DOM `importNode` — a clone through this document. Foreign (non-proxy)
1791
+ * nodes have no shadow shape to copy; they're rejected like any other
1792
+ * foreign child.
1793
+ */
1794
+ importNode(node, deep = false) {
1795
+ this._assertAlive();
1796
+ if (!(node instanceof ProxyNode)) rejectForeignChild(node);
1797
+ return node.cloneNode(deep);
1798
+ }
1799
+ /**
1800
+ * 'about:blank' — no real document URL exists worker-side. A plain field
1801
+ * (not a getter) so adapters may assign it like a normal document prop.
1802
+ */
1803
+ baseURI = "about:blank";
1804
+ /** Wrap (or return the existing wrapper for) a shared instance record. */
1805
+ _wrap(instance) {
1806
+ let node = this._wrappers.get(instance.id);
1807
+ if (node === void 0) {
1808
+ node = instance.kind === "text" ? new ProxyText(this, instance) : new ProxyElement(this, instance);
1809
+ this._wrappers.set(instance.id, node);
1810
+ }
1811
+ return node;
1812
+ }
1813
+ /**
1814
+ * Document-level listener — same currency as element addEventListener but
1815
+ * targeting id 0 (the island's container element). The driver's listen op
1816
+ * attaches a real DOM listener on the container, so events bubbling up
1817
+ * from any island child dispatch back here. Used by library code doing
1818
+ * delegated `document.addEventListener(...)`.
1819
+ */
1820
+ addEventListener(type, fn, options) {
1821
+ this._assertAlive();
1822
+ const opts = normalizeListenerOptions(options);
1823
+ if (this._docListeners.some(
1824
+ (l) => l.type === type && l.fn === fn && l.capture === opts.capture
1825
+ )) {
1826
+ return;
1827
+ }
1828
+ const hid = registerHandler(
1829
+ (p) => {
1830
+ try {
1831
+ fn.call(this, this._enrichEvent(p, this));
1832
+ } finally {
1833
+ if (opts.once) this.removeEventListener(type, fn, { capture: opts.capture });
1834
+ }
1835
+ },
1836
+ this.instance,
1837
+ 0
1838
+ // document-level listener → target id 0 is the island root
1839
+ );
1840
+ this._docListeners.push({ type, fn, hid, capture: opts.capture });
1841
+ this._handlerIds.add(hid);
1842
+ pushOp(this.instance, { t: "listen", id: 0, type, handler: hid, opts: listenerOptsForWire(options) });
1843
+ if (getActiveInstance() !== this.instance) bumpOpsVersion();
1844
+ }
1845
+ removeEventListener(type, fn, options) {
1846
+ this._assertAlive();
1847
+ const capture = normalizeListenerOptions(options).capture;
1848
+ const index = this._docListeners.findIndex(
1849
+ (l) => l.type === type && l.fn === fn && l.capture === capture
1850
+ );
1851
+ if (index === -1) return;
1852
+ const [entry] = this._docListeners.splice(index, 1);
1853
+ unregisterHandler(entry.hid);
1854
+ this._handlerIds.delete(entry.hid);
1855
+ pushOp(this.instance, {
1856
+ t: "unlisten",
1857
+ id: 0,
1858
+ type,
1859
+ handler: entry.hid,
1860
+ opts: entry.capture ? { capture: true } : void 0
1861
+ });
1862
+ if (getActiveInstance() !== this.instance) bumpOpsVersion();
1863
+ }
1864
+ getElementById(id) {
1865
+ const el = this._ids.get(id);
1866
+ return el !== void 0 && el.isConnected ? el : null;
1867
+ }
1868
+ querySelector(selector) {
1869
+ return this.querySelectorAll(selector)[0] ?? null;
1870
+ }
1871
+ querySelectorAll(selector) {
1872
+ let chain = parseSelector(selector);
1873
+ const first = chain[0];
1874
+ if ((first.tag === "body" || first.tag === "html") && first.id === void 0 && first.classes.length === 0 && first.attrs.length === 0) {
1875
+ chain = chain.slice(1);
1876
+ }
1877
+ if (chain.length === 0) return [this._root];
1878
+ const out = [];
1879
+ const walk = (node) => {
1880
+ for (const child of node._children) {
1881
+ if (child instanceof ProxyElement && matchesChain(child, chain)) out.push(child);
1882
+ walk(child);
1883
+ }
1884
+ };
1885
+ walk(this._root);
1886
+ return out;
1887
+ }
1888
+ getComputedStyle(el) {
1889
+ const rect = this._rectFor(el);
1890
+ const w = rect === void 0 ? "" : `${rect.w}px`;
1891
+ const h = rect === void 0 ? "" : `${rect.h}px`;
1892
+ if (rect === void 0) {
1893
+ this._requestMeasure(el);
1894
+ this._warn("getComputedStyle");
1895
+ }
1896
+ return new Proxy({ width: w, height: h }, {
1897
+ get: (t, prop) => prop === "getPropertyValue" ? (k) => k === "width" ? w : k === "height" ? h : "" : t[prop] ?? ""
1898
+ });
1899
+ }
1900
+ dispose() {
1901
+ this._uninstallShim?.();
1902
+ this._uninstallShim = null;
1903
+ this._disposed = true;
1904
+ if (instanceDocs.get(this.instance) === this) instanceDocs.delete(this.instance);
1905
+ this._resizeHandlers.clear();
1906
+ this._sizeObservers.clear();
1907
+ this._measureRequested.clear();
1908
+ for (const hid of this._handlerIds) unregisterHandler(hid);
1909
+ this._handlerIds.clear();
1910
+ }
1911
+ };
1912
+ var instanceDocs = /* @__PURE__ */ new Map();
1913
+ var createProxyDocument = (instance) => {
1914
+ const doc = new InternalDocument(instance);
1915
+ instanceDocs.set(instance, doc);
1916
+ return doc;
1917
+ };
1918
+ var docForInstance = (instance) => instanceDocs.get(instance) ?? createProxyDocument(instance);
1919
+ var peekInstanceDoc = (instance) => instanceDocs.get(instance);
1920
+ var disposeInstanceDoc = (instance) => {
1921
+ instanceDocs.get(instance)?.dispose();
1922
+ };
1923
+ var activeInstanceDoc = () => instanceDocs.get(getActiveInstance());
1924
+ var ambientDoc = () => instanceDocs.size === 1 ? instanceDocs.values().next().value : instanceDocs.get(getLastActiveInstance());
1925
+
1926
+ // src/worker/dom/resizeObserver.ts
1927
+ var ProxyResizeObserver = class {
1928
+ _callback;
1929
+ /** proxy el → the observeSize detacher (unobserve/disconnect paths). */
1930
+ _targets = /* @__PURE__ */ new Map();
1931
+ constructor(callback) {
1932
+ this._callback = callback;
1933
+ }
1934
+ observe(target, _options) {
1935
+ const doc = activeInstanceDoc() ?? ambientDoc();
1936
+ if (doc === void 0) return;
1937
+ const el = toProxyElement(doc, target);
1938
+ if (el === null || this._targets.has(el)) return;
1939
+ const unobserve = doc.observeSize(el, (rect) => {
1940
+ this._callback(
1941
+ [entryFor(el, rect)],
1942
+ this
1943
+ );
1944
+ });
1945
+ this._targets.set(el, unobserve);
1946
+ }
1947
+ unobserve(target) {
1948
+ const doc = activeInstanceDoc() ?? ambientDoc();
1949
+ if (doc === void 0) return;
1950
+ const el = toProxyElement(doc, target);
1951
+ if (el === null) return;
1952
+ this._targets.get(el)?.();
1953
+ this._targets.delete(el);
1954
+ }
1955
+ disconnect() {
1956
+ for (const unobserve of this._targets.values()) unobserve();
1957
+ this._targets.clear();
1958
+ }
1959
+ };
1960
+ var toProxyElement = (doc, target) => {
1961
+ if (target instanceof ProxyElement) return target;
1962
+ if (target !== null && typeof target === "object" && target.kind === "element") {
1963
+ const adopted = doc.adopt(target);
1964
+ return adopted instanceof ProxyElement ? adopted : null;
1965
+ }
1966
+ return null;
1967
+ };
1968
+ var entryFor = (el, rect) => ({
1969
+ target: el,
1970
+ contentRect: {
1971
+ x: rect.x,
1972
+ y: rect.y,
1973
+ top: rect.y,
1974
+ left: rect.x,
1975
+ right: rect.x + rect.w,
1976
+ bottom: rect.y + rect.h,
1977
+ width: rect.w,
1978
+ height: rect.h,
1979
+ toJSON: () => ({})
1980
+ },
1981
+ borderBoxSize: [{ inlineSize: rect.w, blockSize: rect.h }],
1982
+ contentBoxSize: [{ inlineSize: rect.w, blockSize: rect.h }],
1983
+ devicePixelContentBoxSize: [{ inlineSize: rect.w, blockSize: rect.h }]
1984
+ });
1985
+
1986
+ // src/worker/dom/window.ts
1987
+ var activeShimUninstall = null;
1988
+ var buildWindowFacade = (internal) => {
1989
+ const g = globalThis;
1990
+ const windowListeners = /* @__PURE__ */ new Map();
1991
+ const pushDocOp = (op) => {
1992
+ pushOp(internal.instance, op);
1993
+ if (getActiveInstance() !== internal.instance) bumpOpsVersion();
1994
+ };
1995
+ const windowRemoveEventListener = (type, fn, options) => {
1996
+ internal._assertAlive();
1997
+ const capture = normalizeListenerOptions(options).capture;
1998
+ const table = windowListeners.get(type);
1999
+ const index = table?.findIndex((l) => l.fn === fn && l.capture === capture) ?? -1;
2000
+ if (index === -1 || table === void 0) return;
2001
+ const [entry] = table.splice(index, 1);
2002
+ unregisterHandler(entry.hid);
2003
+ internal._handlerIds.delete(entry.hid);
2004
+ pushDocOp({
2005
+ t: "unlisten",
2006
+ id: 0,
2007
+ type,
2008
+ handler: entry.hid,
2009
+ opts: entry.capture ? { capture: true } : void 0
2010
+ });
2011
+ };
2012
+ const windowAddEventListener = (type, fn, options) => {
2013
+ internal._assertAlive();
2014
+ const opts = normalizeListenerOptions(options);
2015
+ let table = windowListeners.get(type);
2016
+ if (table === void 0) windowListeners.set(type, table = []);
2017
+ if (table.some((l) => l.fn === fn && l.capture === opts.capture)) return;
2018
+ const hid = registerHandler(
2019
+ (p) => {
2020
+ try {
2021
+ fn.call(facade, internal._enrichEvent(p, facade));
2022
+ } finally {
2023
+ if (opts.once) windowRemoveEventListener(type, fn, { capture: opts.capture });
2024
+ }
2025
+ },
2026
+ internal.instance,
2027
+ 0
2028
+ // window-level listener → target id 0 is the island root
2029
+ );
2030
+ table.push({ fn, hid, capture: opts.capture });
2031
+ internal._handlerIds.add(hid);
2032
+ pushDocOp({ t: "listen", id: 0, type, handler: hid, opts: listenerOptsForWire(options) });
2033
+ };
2034
+ const raf = g.requestAnimationFrame;
2035
+ const caf = g.cancelAnimationFrame;
2036
+ const globalSetTimeout = g.setTimeout;
2037
+ const globalSetInterval = g.setInterval;
2038
+ const globalClearTimeout = g.clearTimeout;
2039
+ const globalClearInterval = g.clearInterval;
2040
+ const timeouts = /* @__PURE__ */ new Set();
2041
+ const intervals = /* @__PURE__ */ new Set();
2042
+ const rafs = /* @__PURE__ */ new Set();
2043
+ const facade = {
2044
+ document: internal,
2045
+ navigator: { userAgent: "atoll-islands" },
2046
+ location: {
2047
+ href: "about:blank",
2048
+ reload() {
2049
+ },
2050
+ assign(_url) {
2051
+ },
2052
+ replace(_url) {
2053
+ }
2054
+ },
2055
+ get innerWidth() {
2056
+ return getInstanceSize(internal.instance)?.w ?? 0;
2057
+ },
2058
+ get innerHeight() {
2059
+ return getInstanceSize(internal.instance)?.h ?? 0;
2060
+ },
2061
+ devicePixelRatio: 1,
2062
+ // Workers normally have no rAF — pass through when one exists (in-process
2063
+ // test), degrade to a 16ms timer otherwise. Schedule through the host
2064
+ // timers but keep track of the handles so teardown can cancel pending
2065
+ // callbacks (Leaflet's drag/zoom animations, etc.).
2066
+ requestAnimationFrame(cb) {
2067
+ let id;
2068
+ const wrapped = (time) => {
2069
+ rafs.delete(id);
2070
+ cb(time);
2071
+ };
2072
+ id = typeof raf === "function" ? raf.call(globalThis, wrapped) : globalSetTimeout((stamp) => wrapped(stamp), 16, Date.now());
2073
+ rafs.add(id);
2074
+ return id;
2075
+ },
2076
+ cancelAnimationFrame(id) {
2077
+ if (id === void 0) return;
2078
+ rafs.delete(id);
2079
+ if (typeof caf === "function") caf.call(globalThis, id);
2080
+ else globalClearTimeout(id);
2081
+ },
2082
+ setTimeout(...args) {
2083
+ const [cb, delay, ...rest] = args;
2084
+ let id;
2085
+ const wrapped = (...a) => {
2086
+ timeouts.delete(id);
2087
+ cb(...a);
2088
+ };
2089
+ id = globalSetTimeout(wrapped, delay, ...rest);
2090
+ timeouts.add(id);
2091
+ return id;
2092
+ },
2093
+ clearTimeout(id) {
2094
+ if (id === void 0) return;
2095
+ timeouts.delete(id);
2096
+ globalClearTimeout(id);
2097
+ },
2098
+ setInterval(...args) {
2099
+ const [cb, delay, ...rest] = args;
2100
+ const id = globalSetInterval(cb, delay, ...rest);
2101
+ intervals.add(id);
2102
+ return id;
2103
+ },
2104
+ clearInterval(id) {
2105
+ if (id === void 0) return;
2106
+ intervals.delete(id);
2107
+ globalClearInterval(id);
2108
+ },
2109
+ // Delegates to doc.getComputedStyle — measured elements report real
2110
+ // width/height ('NNpx'), the unmeasured path warns once doc-side.
2111
+ getComputedStyle: (el) => el instanceof ProxyElement ? internal.getComputedStyle(el) : {},
2112
+ // The measure channel as a DOM-standard observer — chart/map libraries
2113
+ // that size off ResizeObserver (echarts, recharts ResponsiveContainer)
2114
+ // get real pushed boxes unmodified.
2115
+ ResizeObserver: ProxyResizeObserver,
2116
+ // No preference/quiz surface exists worker-side — media queries never
2117
+ // match. The shape is complete enough for feature-detection code.
2118
+ matchMedia: (query) => ({
2119
+ matches: false,
2120
+ media: query,
2121
+ onchange: null,
2122
+ addListener: () => {
2123
+ },
2124
+ removeListener: () => {
2125
+ },
2126
+ addEventListener: () => {
2127
+ },
2128
+ removeEventListener: () => {
2129
+ },
2130
+ dispatchEvent: () => false
2131
+ }),
2132
+ addEventListener: windowAddEventListener,
2133
+ removeEventListener: windowRemoveEventListener,
2134
+ // No scroll surface exists worker-side — libraries call these during
2135
+ // gesture handlers (Leaflet's Keyboard._onMouseDown scrolls the page
2136
+ // back); honest no-ops.
2137
+ scrollTo: () => {
2138
+ },
2139
+ scrollBy: () => {
2140
+ },
2141
+ scroll: () => {
2142
+ }
2143
+ };
2144
+ const teardown = () => {
2145
+ for (const id of rafs) {
2146
+ if (typeof caf === "function") caf.call(globalThis, id);
2147
+ else globalClearTimeout(id);
2148
+ }
2149
+ rafs.clear();
2150
+ for (const id of timeouts) globalClearTimeout(id);
2151
+ timeouts.clear();
2152
+ for (const id of intervals) globalClearInterval(id);
2153
+ intervals.clear();
2154
+ for (const [type, table] of windowListeners) {
2155
+ for (const entry of table) {
2156
+ unregisterHandler(entry.hid);
2157
+ internal._handlerIds.delete(entry.hid);
2158
+ if (!internal._disposed) {
2159
+ pushOp(internal.instance, { t: "unlisten", id: 0, type, handler: entry.hid });
2160
+ }
2161
+ }
2162
+ table.length = 0;
2163
+ }
2164
+ };
2165
+ return { facade, teardown };
2166
+ };
2167
+ var windowFacadeFor = (internal) => {
2168
+ if (internal._windowBundle === void 0) {
2169
+ internal._windowBundle = buildWindowFacade(internal);
2170
+ }
2171
+ return internal._windowBundle;
2172
+ };
2173
+ var instanceDispatcherInstalled = false;
2174
+ var HTML_MEDIA_STUB = class HTMLMediaElement {
2175
+ };
2176
+ var CUSTOM_ELEMENTS_STUB = {
2177
+ get: () => void 0,
2178
+ define: () => {
2179
+ },
2180
+ upgrade: () => {
2181
+ },
2182
+ // Honest answer for "will this element ever upgrade" — it won't.
2183
+ whenDefined: () => new Promise(() => {
2184
+ })
2185
+ };
2186
+ function installInstanceDispatcher() {
2187
+ if (instanceDispatcherInstalled) return;
2188
+ instanceDispatcherInstalled = true;
2189
+ const g = globalThis;
2190
+ const prevDocument = g.document;
2191
+ const prevWindow = g.window;
2192
+ const prevElement = g.Element;
2193
+ const prevNode = g.Node;
2194
+ const prevText = g.Text;
2195
+ const prevComment = g.Comment;
2196
+ const prevMedia = g.HTMLMediaElement;
2197
+ const prevCustomElements = g.customElements;
2198
+ const prevResizeObserver = g.ResizeObserver;
2199
+ let docOverride;
2200
+ let winOverride;
2201
+ let elOverride;
2202
+ Object.defineProperty(g, "document", {
2203
+ configurable: true,
2204
+ enumerable: true,
2205
+ get: () => activeInstanceDoc() ?? docOverride ?? ambientDoc() ?? prevDocument,
2206
+ set: (v) => {
2207
+ docOverride = v;
2208
+ }
2209
+ });
2210
+ Object.defineProperty(g, "window", {
2211
+ configurable: true,
2212
+ enumerable: true,
2213
+ get: () => {
2214
+ const doc = activeInstanceDoc();
2215
+ if (doc !== void 0) return windowFacadeFor(doc).facade;
2216
+ if (winOverride !== void 0) return winOverride;
2217
+ const ambient = ambientDoc();
2218
+ return ambient !== void 0 ? windowFacadeFor(ambient).facade : prevWindow;
2219
+ },
2220
+ set: (v) => {
2221
+ winOverride = v;
2222
+ }
2223
+ });
2224
+ Object.defineProperty(g, "Element", {
2225
+ configurable: true,
2226
+ enumerable: true,
2227
+ get: () => {
2228
+ if (activeInstanceDoc() !== void 0) return ProxyElement;
2229
+ if (elOverride !== void 0) return elOverride;
2230
+ return ambientDoc() !== void 0 ? ProxyElement : prevElement;
2231
+ },
2232
+ set: (v) => {
2233
+ elOverride = v;
2234
+ }
2235
+ });
2236
+ const installClassGlobal = (name, instanceValue, prev) => {
2237
+ let override;
2238
+ Object.defineProperty(g, name, {
2239
+ configurable: true,
2240
+ enumerable: true,
2241
+ get: () => {
2242
+ if (activeInstanceDoc() !== void 0) return instanceValue;
2243
+ if (override !== void 0) return override;
2244
+ return ambientDoc() !== void 0 ? instanceValue : prev;
2245
+ },
2246
+ set: (v) => {
2247
+ override = v;
2248
+ }
2249
+ });
2250
+ };
2251
+ installClassGlobal("Node", ProxyNode, prevNode);
2252
+ installClassGlobal("Text", ProxyText, prevText);
2253
+ installClassGlobal("Comment", ProxyComment, prevComment);
2254
+ installClassGlobal(
2255
+ "HTMLMediaElement",
2256
+ typeof prevMedia === "function" ? prevMedia : HTML_MEDIA_STUB,
2257
+ prevMedia
2258
+ );
2259
+ installClassGlobal(
2260
+ "customElements",
2261
+ prevCustomElements !== void 0 ? prevCustomElements : CUSTOM_ELEMENTS_STUB,
2262
+ prevCustomElements
2263
+ );
2264
+ installClassGlobal("ResizeObserver", ProxyResizeObserver, prevResizeObserver);
2265
+ }
2266
+ function installDomShim(doc) {
2267
+ const internal = doc;
2268
+ installInstanceDispatcher();
2269
+ const g = globalThis;
2270
+ activeShimUninstall?.();
2271
+ activeShimUninstall = null;
2272
+ const prevInstance = getLastActiveInstance();
2273
+ const prevDocument = g.document;
2274
+ const prevWindow = g.window;
2275
+ const prevElement = g.Element;
2276
+ const prevNode = g.Node;
2277
+ const prevText = g.Text;
2278
+ const prevComment = g.Comment;
2279
+ const prevMedia = g.HTMLMediaElement;
2280
+ const prevCustomElements = g.customElements;
2281
+ const prevResizeObserver = g.ResizeObserver;
2282
+ markInstanceActive(internal.instance);
2283
+ const bundle = windowFacadeFor(internal);
2284
+ g.document = doc;
2285
+ g.window = bundle.facade;
2286
+ g.Element = ProxyElement;
2287
+ g.Node = ProxyNode;
2288
+ g.Text = ProxyText;
2289
+ g.Comment = ProxyComment;
2290
+ if (typeof g.HTMLMediaElement !== "function") g.HTMLMediaElement = HTML_MEDIA_STUB;
2291
+ if (g.customElements === void 0) g.customElements = CUSTOM_ELEMENTS_STUB;
2292
+ if (g.ResizeObserver === void 0) g.ResizeObserver = ProxyResizeObserver;
2293
+ const uninstall = () => {
2294
+ bundle.teardown();
2295
+ internal._windowBundle = void 0;
2296
+ g.document = prevDocument;
2297
+ g.window = prevWindow;
2298
+ g.Element = prevElement;
2299
+ g.Node = prevNode;
2300
+ g.Text = prevText;
2301
+ g.Comment = prevComment;
2302
+ g.HTMLMediaElement = prevMedia;
2303
+ g.customElements = prevCustomElements;
2304
+ g.ResizeObserver = prevResizeObserver;
2305
+ markInstanceActive(prevInstance);
2306
+ if (activeShimUninstall === uninstall) activeShimUninstall = null;
2307
+ };
2308
+ internal._uninstallShim = uninstall;
2309
+ activeShimUninstall = uninstall;
2310
+ return uninstall;
2311
+ }
2312
+
2313
+ // src/worker/defineWorkers.ts
2314
+ var isImperative = (app) => typeof app === "object" && app !== null && "imperative" in app;
2315
+ var isRendered = (app) => typeof app === "object" && app !== null && "mount" in app;
2316
+ var callbackFactory = (instance) => (id) => (...args) => {
2317
+ pushOp(instance, { t: "emit", name: CALLBACK_EVENT, payload: { id, args } });
2318
+ bumpOpsVersion();
2319
+ };
2320
+ var isImperativeInstance = (r) => r.imperative !== void 0;
2321
+ var isRenderedInstance = (r) => r.rendered !== void 0;
2322
+ var mounts = /* @__PURE__ */ new Map();
2323
+ var APP_REGISTRY = /* @__PURE__ */ new Map();
2324
+ var registerSingleApp = (name, app) => {
2325
+ APP_REGISTRY.set(name, app);
2326
+ };
2327
+ var resolveApp = (name) => APP_REGISTRY.get(name) ?? (APP_REGISTRY.size === 1 ? APP_REGISTRY.values().next().value : void 0);
2328
+ var newPid = () => `w-${Math.random().toString(36).slice(2, 8)}`;
2329
+ var appNameOf = (instance) => {
2330
+ const at = instance.lastIndexOf("@");
2331
+ const name = at === -1 ? instance : instance.slice(0, at);
2332
+ const tilde = name.lastIndexOf("~");
2333
+ return tilde === -1 ? name : name.slice(tilde + 1);
2334
+ };
2335
+ function createIslandRuntime(sharedMemory = renderMemory) {
2336
+ setDoorbellContract(sharedMemory);
2337
+ installInstanceDispatcher();
2338
+ const parseContractProps = (contract, key, props) => {
2339
+ if (contract?.props === void 0) return props;
2340
+ try {
2341
+ return contract.props.parse(props);
2342
+ } catch (err) {
2343
+ throw new Error(
2344
+ `[island "${contract.app}"] props for "${key}" rejected by contract: ` + (err instanceof Error ? err.message : String(err))
2345
+ );
2346
+ }
2347
+ };
2348
+ function createInstance(key, pid) {
2349
+ const app = resolveApp(appNameOf(key));
2350
+ const contract = contractOf(app);
2351
+ setInstanceContract(key, contract);
2352
+ if (isRendered(app)) {
2353
+ return {
2354
+ key,
2355
+ app: appNameOf(key),
2356
+ pid,
2357
+ contract,
2358
+ rendered: { app, handle: void 0, props: {} }
2359
+ };
2360
+ }
2361
+ if (isImperative(app)) {
2362
+ return {
2363
+ key,
2364
+ app: appNameOf(key),
2365
+ pid,
2366
+ contract,
2367
+ imperative: {
2368
+ build: app.imperative,
2369
+ dispose: app.dispose,
2370
+ doc: createProxyDocument(key),
2371
+ props: {}
2372
+ }
2373
+ };
2374
+ }
2375
+ throw new Error(
2376
+ `createInstance: "${appNameOf(key)}" is a bare component \u2014 registry values must be { imperative } or a framework adapter's RenderedIslandApp (reactIslandApp/vueIslandApp/\u2026, or the package's define*PolyWorker which wraps for you)`
2377
+ );
2378
+ }
2379
+ function rebuildImperative(instance, props) {
2380
+ return runInInstance(instance.key, () => {
2381
+ const imp = instance.imperative;
2382
+ imp.dispose?.(imp.doc);
2383
+ imp.doc.dispose();
2384
+ pushOp(instance.key, { t: "clear" });
2385
+ imp.doc = createProxyDocument(instance.key);
2386
+ imp.props = props;
2387
+ imp.build(imp.doc, props);
2388
+ return takeOps(instance.key);
2389
+ });
2390
+ }
2391
+ function renderCtx(key, props) {
2392
+ return {
2393
+ instance: key,
2394
+ get doc() {
2395
+ return docForInstance(key);
2396
+ },
2397
+ props
2398
+ };
2399
+ }
2400
+ function rebuildRendered(instance, props) {
2401
+ return runInInstance(instance.key, () => {
2402
+ const r = instance.rendered;
2403
+ r.handle?.dispose?.();
2404
+ disposeInstanceDoc(instance.key);
2405
+ pushOp(instance.key, { t: "clear" });
2406
+ r.props = props;
2407
+ r.handle = r.app.mount(renderCtx(instance.key, props));
2408
+ return takeOps(instance.key);
2409
+ });
2410
+ }
2411
+ return defineWorker({
2412
+ sharedMemory,
2413
+ methods: {
2414
+ /**
2415
+ * Mount apps[appNameOf(instance)] into its own root; returns the
2416
+ * initial op batch. The instance key may carry an instance suffix
2417
+ * ('data-table@3') so the same app can mount multiple times.
2418
+ *
2419
+ * Mounting a instance key that is already mounted is a REMOUNT: the old
2420
+ * tree is unmounted first (a `clear` op + GC of its instance records),
2421
+ * then a fresh container renders the new tree — the returned batch
2422
+ * replays cleanly onto an emptied root. The pid is kept across remounts.
2423
+ */
2424
+ async mount(instance, props = {}) {
2425
+ props = unmarshalCallbackProps(props, callbackFactory(instance));
2426
+ const App = resolveApp(appNameOf(instance));
2427
+ if (App === void 0) {
2428
+ throw new Error(
2429
+ `mount: unknown app "${instance}" \u2014 registry has: ${[...APP_REGISTRY.keys()].join(", ")}`
2430
+ );
2431
+ }
2432
+ props = parseContractProps(contractOf(App), instance, props);
2433
+ const mounted = mounts.get(instance);
2434
+ if (mounted !== void 0 && isImperativeInstance(mounted)) {
2435
+ return rebuildImperative(mounted, props);
2436
+ }
2437
+ if (mounted !== void 0) {
2438
+ return rebuildRendered(mounted, props);
2439
+ }
2440
+ const created = createInstance(instance, newPid());
2441
+ mounts.set(instance, created);
2442
+ if (isImperativeInstance(created)) {
2443
+ const imp = created.imperative;
2444
+ imp.props = props;
2445
+ return runInInstance(instance, () => {
2446
+ imp.build(imp.doc, props);
2447
+ return takeOps(instance);
2448
+ });
2449
+ }
2450
+ const r = created.rendered;
2451
+ r.props = props;
2452
+ return runInInstance(instance, () => {
2453
+ r.handle = r.app.mount(renderCtx(instance, props));
2454
+ return takeOps(instance);
2455
+ });
2456
+ },
2457
+ /**
2458
+ * Re-render the instance's root with new props — the shell→island channel.
2459
+ * `updateProps` is how the shell mediates between islands (controls
2460
+ * emits filterChanged → shell → table.updateProps({filter})).
2461
+ */
2462
+ updateProps(instance, props) {
2463
+ props = unmarshalCallbackProps(props, callbackFactory(instance));
2464
+ const mounted = mounts.get(instance);
2465
+ if (mounted === void 0) {
2466
+ throw new Error(`updateProps: "${instance}" is not mounted in this worker \u2014 mount() first`);
2467
+ }
2468
+ props = parseContractProps(mounted.contract, instance, props);
2469
+ if (isImperativeInstance(mounted)) return rebuildImperative(mounted, props);
2470
+ const r = mounted.rendered;
2471
+ if (r.handle?.update === void 0) return rebuildRendered(mounted, props);
2472
+ return runInInstance(instance, () => {
2473
+ r.handle.update(props);
2474
+ r.props = props;
2475
+ return takeOps(instance);
2476
+ });
2477
+ },
2478
+ /**
2479
+ * Run the prop function the main thread identified by handlerId —
2480
+ * `__evt` refs are handles into the worker's handler table. The entry
2481
+ * records which instance registered it, so the re-render's ops land on the
2482
+ * right island's queue.
2483
+ */
2484
+ dispatch(handlerId, payload) {
2485
+ const entry = getHandler(handlerId);
2486
+ if (entry === void 0) return [];
2487
+ const instance = mounts.get(entry.instance);
2488
+ if (instance === void 0) return [];
2489
+ const p = payload;
2490
+ if (p.target === void 0 || p.currentTarget === void 0) {
2491
+ const doc = docForInstance(instance.key);
2492
+ if (p.target === void 0 && typeof p.targetId === "number") {
2493
+ const t = instances.get(p.targetId);
2494
+ if (t !== void 0) p.target = doc.adopt(t);
2495
+ }
2496
+ if (p.currentTarget === void 0 && entry.instanceId !== void 0) {
2497
+ p.currentTarget = entry.instanceId === 0 ? doc._root : instances.get(entry.instanceId) !== void 0 ? doc.adopt(instances.get(entry.instanceId)) : void 0;
2498
+ }
2499
+ }
2500
+ return runInInstance(instance.key, () => {
2501
+ const sync = isRenderedInstance(instance) ? instance.rendered.handle?.sync : void 0;
2502
+ if (sync !== void 0) sync(() => entry.fn(payload));
2503
+ else entry.fn(payload);
2504
+ return takeOps(instance.key);
2505
+ });
2506
+ },
2507
+ /**
2508
+ * The pushed-size channel: the driver measured the island's container
2509
+ * and stores it for the instance (see instanceSizes). Mounts
2510
+ * additionally fire their proxy document's onResize handlers —
2511
+ * inside the instance's scope so their mutations emit ops, which ride
2512
+ * back in this task's return batch.
2513
+ */
2514
+ setSize(instance, width, height) {
2515
+ setInstanceSize(instance, width, height);
2516
+ const mounted = mounts.get(instance);
2517
+ if (mounted === void 0) return takeOps(instance);
2518
+ const doc = isImperativeInstance(mounted) ? mounted.imperative.doc : peekInstanceDoc(instance);
2519
+ if (doc === void 0) return takeOps(instance);
2520
+ return runInInstance(mounted.key, () => {
2521
+ doc._notifySize(width, height);
2522
+ return takeOps(instance);
2523
+ });
2524
+ },
2525
+ /**
2526
+ * The measured-element channel: rects the driver pushed in answer to
2527
+ * `measure` ops. Stored in the instance-level map FIRST so geometry
2528
+ * reads answer even for mounts without a materialized proxy document
2529
+ * (a React element adopted later sees its rect immediately); then
2530
+ * per-element observeSize callbacks fire inside the instance's scope,
2531
+ * their ops riding back in the batch like onResize handlers.
2532
+ */
2533
+ setRects(instance, rects) {
2534
+ const mounted = mounts.get(instance);
2535
+ const doc = mounted !== void 0 && isImperativeInstance(mounted) ? mounted.imperative.doc : peekInstanceDoc(instance);
2536
+ if (doc === void 0) {
2537
+ for (const rect of rects) setInstanceRect(instance, rect);
2538
+ return takeOps(instance);
2539
+ }
2540
+ return runInInstance(mounted?.key ?? instance, () => {
2541
+ doc._notifyRects(rects);
2542
+ return takeOps(instance);
2543
+ });
2544
+ },
2545
+ /**
2546
+ * Tear down ONE instance while the worker keeps serving its others —
2547
+ * the multi-island-per-worker counterpart to process death: disposes
2548
+ * the renderer handle (or the imperative instance's proxy document),
2549
+ * drops the instance entry, and returns the clear op. Stale
2550
+ * handler dispatches then no-op via the mounts.get() guard.
2551
+ */
2552
+ unmount(instance) {
2553
+ const mounted = mounts.get(instance);
2554
+ if (mounted === void 0) return [];
2555
+ mounts.delete(instance);
2556
+ setInstanceContract(instance, void 0);
2557
+ clearInstanceRects(instance);
2558
+ return runInInstance(instance, () => {
2559
+ if (isImperativeInstance(mounted)) {
2560
+ const imp = mounted.imperative;
2561
+ imp.dispose?.(imp.doc);
2562
+ imp.doc.dispose();
2563
+ } else {
2564
+ mounted.rendered.handle?.dispose?.();
2565
+ disposeInstanceDoc(instance);
2566
+ }
2567
+ takeOps(instance);
2568
+ return [{ t: "clear" }];
2569
+ });
2570
+ },
2571
+ /**
2572
+ * Drain one instance's ops committed outside a sync task — passive effects
2573
+ * (useEffect), timers, async setState. The pool protocol has no push
2574
+ * channel, so the main thread polls this (or the doorbell pushes it).
2575
+ */
2576
+ flush(instance) {
2577
+ const mounted = mounts.get(instance);
2578
+ if (mounted === void 0) return [];
2579
+ if (isImperativeInstance(mounted)) return takeOps(instance);
2580
+ const r = mounted.rendered;
2581
+ return runInInstance(mounted.key, () => {
2582
+ r.handle?.flush?.();
2583
+ return takeOps(instance);
2584
+ });
2585
+ },
2586
+ /** The mounted instance's random id — the island's "worker pid" badge. */
2587
+ whoami(instance) {
2588
+ return mounts.get(instance)?.pid ?? "unmounted";
2589
+ },
2590
+ /** The instance's renderer tag (an adapter's `renderer`); null for imperative or unknown. */
2591
+ renderer(instance) {
2592
+ const mounted = mounts.get(instance);
2593
+ return mounted && isRenderedInstance(mounted) && mounted.rendered.app.renderer || null;
2594
+ }
2595
+ }
2596
+ });
2597
+ }
2598
+ function definePolyWorker(registry, options) {
2599
+ for (const [key, app] of Object.entries(registry.apps)) {
2600
+ const stamped = islandAppNameOf(app);
2601
+ if (stamped !== void 0 && stamped !== key) {
2602
+ console.warn(
2603
+ `[definePolyWorker] app registered as "${key}" but stamped "${stamped}" \u2014 component-reference mounts resolve "${stamped}" and will fail. Fix the key or the stamp.`
2604
+ );
2605
+ }
2606
+ registerSingleApp(key, app);
2607
+ }
2608
+ return createIslandRuntime(registry.sharedMemory ?? options?.sharedMemory);
2609
+ }
2610
+ function defineMonoWorker(app, options) {
2611
+ const stamp = app.islandAppName;
2612
+ registerSingleApp(typeof stamp === "string" && stamp !== "" ? stamp : "main", app);
2613
+ return createIslandRuntime(options?.sharedMemory);
2614
+ }
2615
+
2616
+ // src/worker/subIsland.ts
2617
+ import { devtoolsEnabled, emitDevtools, estimateCloneBytes, observe, previewValue } from "@atolljs/core";
2618
+ var subSeq = 0;
2619
+ var clientMounts = /* @__PURE__ */ new WeakMap();
2620
+ var ENRICHED_KEYS = /* @__PURE__ */ new Set([
2621
+ "target",
2622
+ "relatedTarget",
2623
+ "currentTarget",
2624
+ "composedPath",
2625
+ "preventDefault",
2626
+ "stopPropagation",
2627
+ "stopImmediatePropagation"
2628
+ ]);
2629
+ async function mountSubIsland(opts) {
2630
+ const {
2631
+ el,
2632
+ onEvent,
2633
+ onActivity,
2634
+ onOps,
2635
+ slots,
2636
+ client: givenClient,
2637
+ worker,
2638
+ app: givenApp,
2639
+ props: givenProps,
2640
+ mode: initialMode,
2641
+ framework,
2642
+ mountTimeout,
2643
+ ...workerConfig
2644
+ } = opts;
2645
+ if (!(el instanceof ProxyElement)) {
2646
+ throw new Error(
2647
+ "mountSubIsland: `el` must be a ProxyElement of a mounted parent island \u2014 a real element mounts through the main-thread driver (mountIsland handles both)."
2648
+ );
2649
+ }
2650
+ const doc = el.doc;
2651
+ const parentInstance = doc.instance;
2652
+ const app = givenApp ?? "main";
2653
+ const instance = `${parentInstance}~${app}@${++subSeq}`;
2654
+ if (givenClient === void 0 && worker === void 0) {
2655
+ throw new Error(
2656
+ "mountSubIsland: pass `worker` (a `() => new Worker(...)`/URL entry \u2014 this call builds the island-owned sub-client) or `client` (a shared connectIslandWorker \u2014 several sub-islands, one sub-worker)"
2657
+ );
2658
+ }
2659
+ const cbTable = /* @__PURE__ */ new Map();
2660
+ let cbSeq = 0;
2661
+ const registerCb = (fn) => {
2662
+ cbTable.set(++cbSeq, fn);
2663
+ return cbSeq;
2664
+ };
2665
+ const props = marshalCallbackProps(givenProps ?? {}, registerCb);
2666
+ assertCloneableProps(props, `mountIsland(${givenApp ?? "main"})`);
2667
+ const client = givenClient ?? connectIslandWorker({
2668
+ name: givenApp,
2669
+ ...workerConfig,
2670
+ worker,
2671
+ doorbell: initialMode !== "poll"
2672
+ });
2673
+ clientMounts.set(client, (clientMounts.get(client) ?? 0) + 1);
2674
+ const nodes = /* @__PURE__ */ new Map([[0, el]]);
2675
+ const proxyIdToSub = /* @__PURE__ */ new Map([[el.instance.id, 0]]);
2676
+ const prevProps = /* @__PURE__ */ new Map();
2677
+ const nodeListeners = /* @__PURE__ */ new Map();
2678
+ const measureCleanups = /* @__PURE__ */ new Map();
2679
+ const slotNodes = /* @__PURE__ */ new Map();
2680
+ const claims = (name) => slots !== void 0 && name in slots;
2681
+ function mountSlot(node, subId, name) {
2682
+ node.setAttribute("data-atoll-sub-slot", name);
2683
+ const prev = slotNodes.get(subId);
2684
+ if (prev !== void 0 && prev !== name) slots?.[prev]?.(null);
2685
+ slotNodes.set(subId, name);
2686
+ slots?.[name]?.(node);
2687
+ }
2688
+ function unmountSlot(subId) {
2689
+ const name = slotNodes.get(subId);
2690
+ if (name === void 0) return;
2691
+ slotNodes.delete(subId);
2692
+ slots?.[name]?.(null);
2693
+ }
2694
+ function releaseDetachedSlots() {
2695
+ for (const subId of [...slotNodes.keys()]) {
2696
+ const node = nodes.get(subId);
2697
+ if (node === void 0 || !el.contains(node)) unmountSlot(subId);
2698
+ }
2699
+ }
2700
+ let unsubscribe = null;
2701
+ let pollTimer = null;
2702
+ let destroyed = false;
2703
+ let opsApplied = 0;
2704
+ let flushCalls = 0;
2705
+ let mode = initialMode ?? "push";
2706
+ function translatePayload(p) {
2707
+ const out = {};
2708
+ for (const [k, v] of Object.entries(p)) {
2709
+ if (!ENRICHED_KEYS.has(k)) out[k] = v;
2710
+ }
2711
+ if (typeof p.targetId === "number") {
2712
+ const subId = proxyIdToSub.get(p.targetId);
2713
+ if (subId !== void 0) out.targetId = subId;
2714
+ else delete out.targetId;
2715
+ }
2716
+ return out;
2717
+ }
2718
+ function dispatchListener(subHid) {
2719
+ return (payload) => {
2720
+ void client.dispatch(subHid, translatePayload(payload)).then((ops) => applyOps(ops, "dispatch")).catch((err) => console.error(`[sub-island ${instance}] dispatch failed`, err));
2721
+ };
2722
+ }
2723
+ function trackListener(subId, key, fn, capture) {
2724
+ let table = nodeListeners.get(subId);
2725
+ if (!table) nodeListeners.set(subId, table = /* @__PURE__ */ new Map());
2726
+ if (!table.has(key)) table.set(key, { fn, capture });
2727
+ }
2728
+ function setProp(node, subId, name, value) {
2729
+ if (name === "data-atoll-slot" && claims(String(value))) {
2730
+ mountSlot(node, subId, String(value));
2731
+ return;
2732
+ }
2733
+ if (isEventRef(value)) {
2734
+ const eventName = name.slice(2).toLowerCase();
2735
+ const key = `${eventName}#${value.__evt}`;
2736
+ if (!nodeListeners.get(subId)?.has(key)) {
2737
+ const fn = dispatchListener(value.__evt);
2738
+ trackListener(subId, key, fn);
2739
+ node.addEventListener(eventName, fn);
2740
+ }
2741
+ return;
2742
+ }
2743
+ if (name === "style" && typeof value === "object" && value !== null) {
2744
+ const next = value;
2745
+ const prev = prevProps.get(subId)?.style ?? {};
2746
+ const style = node.style;
2747
+ for (const k of Object.keys(prev)) if (!(k in next)) style[k] = "";
2748
+ for (const [k, v] of Object.entries(next)) {
2749
+ if (prev[k] !== v) style[k] = cssStyleValue(k, v);
2750
+ }
2751
+ return;
2752
+ }
2753
+ if (name === "className") {
2754
+ node.className = String(value);
2755
+ return;
2756
+ }
2757
+ if (value === true) {
2758
+ node.setAttribute(name, "");
2759
+ return;
2760
+ }
2761
+ if (value === false) {
2762
+ node.removeAttribute(name);
2763
+ return;
2764
+ }
2765
+ if (name in node) {
2766
+ try {
2767
+ node[name] = value;
2768
+ return;
2769
+ } catch {
2770
+ }
2771
+ }
2772
+ node.setAttribute(name, String(value));
2773
+ }
2774
+ function removeProp(node, subId, name, oldValue) {
2775
+ if (name === "data-atoll-slot" && slotNodes.has(subId)) {
2776
+ unmountSlot(subId);
2777
+ node.removeAttribute("data-atoll-sub-slot");
2778
+ return;
2779
+ }
2780
+ if (isEventRef(oldValue)) {
2781
+ const eventName = name.slice(2).toLowerCase();
2782
+ const key = `${eventName}#${oldValue.__evt}`;
2783
+ const entry = nodeListeners.get(subId)?.get(key);
2784
+ if (entry) {
2785
+ node.removeEventListener(eventName, entry.fn, { capture: entry.capture });
2786
+ nodeListeners.get(subId)?.delete(key);
2787
+ }
2788
+ return;
2789
+ }
2790
+ if (name === "style") {
2791
+ for (const k of Object.keys(oldValue ?? {})) {
2792
+ node.style[k] = "";
2793
+ }
2794
+ return;
2795
+ }
2796
+ if (name === "className") {
2797
+ node.className = "";
2798
+ return;
2799
+ }
2800
+ node.removeAttribute(name);
2801
+ }
2802
+ function applyProps(node, subId, prev, next) {
2803
+ for (const name of Object.keys(prev)) {
2804
+ if (!(name in next)) removeProp(node, subId, name, prev[name]);
2805
+ }
2806
+ for (const [name, value] of Object.entries(next)) {
2807
+ if (prev[name] !== value || isEventRef(value)) setProp(node, subId, name, value);
2808
+ }
2809
+ }
2810
+ function applyOp(op) {
2811
+ switch (op.t) {
2812
+ case "create": {
2813
+ const node = op.ns !== void 0 ? doc.createElementNS(op.ns, op.type) : doc.createElement(op.type);
2814
+ nodes.set(op.id, node);
2815
+ proxyIdToSub.set(node.instance.id, op.id);
2816
+ for (const [name, value] of Object.entries(op.props)) setProp(node, op.id, name, value);
2817
+ prevProps.set(op.id, op.props);
2818
+ break;
2819
+ }
2820
+ case "text": {
2821
+ const node = doc.createTextNode(op.text);
2822
+ nodes.set(op.id, node);
2823
+ proxyIdToSub.set(node.instance.id, op.id);
2824
+ break;
2825
+ }
2826
+ case "append": {
2827
+ const parent = nodes.get(op.parent);
2828
+ const child = nodes.get(op.child);
2829
+ if (!parent || !child) {
2830
+ console.error(
2831
+ `[sub-island] append skipped: parent=${op.parent}\u2192${String(parent)} child=${op.child}\u2192${String(child)}`
2832
+ );
2833
+ break;
2834
+ }
2835
+ const before = op.before !== void 0 ? nodes.get(op.before) ?? null : null;
2836
+ parent.insertBefore(child, before);
2837
+ break;
2838
+ }
2839
+ case "remove": {
2840
+ nodes.get(op.child)?.remove();
2841
+ measureCleanups.get(op.child)?.();
2842
+ measureCleanups.delete(op.child);
2843
+ releaseDetachedSlots();
2844
+ break;
2845
+ }
2846
+ case "update": {
2847
+ const node = nodes.get(op.id);
2848
+ if (!(node instanceof ProxyElement)) break;
2849
+ const prev = prevProps.get(op.id) ?? {};
2850
+ applyProps(node, op.id, prev, op.props);
2851
+ prevProps.set(op.id, op.props);
2852
+ break;
2853
+ }
2854
+ case "utext": {
2855
+ const node = nodes.get(op.id);
2856
+ if (node) node.textContent = op.text;
2857
+ break;
2858
+ }
2859
+ case "attr": {
2860
+ const node = nodes.get(op.id);
2861
+ if (!(node instanceof ProxyElement)) break;
2862
+ if (op.name === "data-atoll-slot") {
2863
+ if (op.value === null && slotNodes.has(op.id)) {
2864
+ unmountSlot(op.id);
2865
+ node.removeAttribute("data-atoll-sub-slot");
2866
+ break;
2867
+ }
2868
+ if (op.value !== null && claims(op.value)) {
2869
+ mountSlot(node, op.id, op.value);
2870
+ break;
2871
+ }
2872
+ }
2873
+ if (op.value === null) node.removeAttribute(op.name);
2874
+ else node.setAttribute(op.name, op.value);
2875
+ break;
2876
+ }
2877
+ case "style": {
2878
+ const node = nodes.get(op.id);
2879
+ if (!(node instanceof ProxyElement)) break;
2880
+ const style = node.style;
2881
+ for (const [k, v] of Object.entries(op.props)) style[k] = v;
2882
+ const prev = prevProps.get(op.id) ?? {};
2883
+ prev.style = { ...prev.style ?? {}, ...op.props };
2884
+ prevProps.set(op.id, prev);
2885
+ break;
2886
+ }
2887
+ case "listen": {
2888
+ const node = nodes.get(op.id);
2889
+ if (!(node instanceof ProxyElement) && op.id !== 0) break;
2890
+ const target = node ?? el;
2891
+ const key = `${op.type}#${op.handler}`;
2892
+ if (!nodeListeners.get(op.id)?.has(key)) {
2893
+ const fn = dispatchListener(op.handler);
2894
+ trackListener(op.id, key, fn, op.opts?.capture);
2895
+ target.addEventListener(op.type, fn, op.opts);
2896
+ }
2897
+ break;
2898
+ }
2899
+ case "unlisten": {
2900
+ const node = nodes.get(op.id);
2901
+ if (!(node instanceof ProxyElement)) break;
2902
+ const key = `${op.type}#${op.handler}`;
2903
+ const entry = nodeListeners.get(op.id)?.get(key);
2904
+ if (entry) {
2905
+ node.removeEventListener(op.type, entry.fn, { capture: op.opts?.capture });
2906
+ nodeListeners.get(op.id)?.delete(key);
2907
+ }
2908
+ break;
2909
+ }
2910
+ case "clear": {
2911
+ el.replaceChildren();
2912
+ for (const cleanup of measureCleanups.values()) cleanup();
2913
+ measureCleanups.clear();
2914
+ releaseDetachedSlots();
2915
+ break;
2916
+ }
2917
+ case "measure": {
2918
+ const node = nodes.get(op.id);
2919
+ if (!(node instanceof ProxyElement) || measureCleanups.has(op.id)) break;
2920
+ measureCleanups.set(
2921
+ op.id,
2922
+ doc.observeSize(node, (rect) => {
2923
+ void islandCall(
2924
+ "setRects",
2925
+ client.setRects(instance, [{ id: op.id, x: rect.x, y: rect.y, w: rect.w, h: rect.h }])
2926
+ ).then((ops) => applyOps(ops, "setRects"));
2927
+ })
2928
+ );
2929
+ break;
2930
+ }
2931
+ case "unmeasure": {
2932
+ measureCleanups.get(op.id)?.();
2933
+ measureCleanups.delete(op.id);
2934
+ break;
2935
+ }
2936
+ case "emit": {
2937
+ if (op.name === CALLBACK_EVENT) {
2938
+ const p = op.payload;
2939
+ if (p !== null && typeof p === "object" && typeof p.id === "number") {
2940
+ cbTable.get(p.id)?.(...p.args ?? []);
2941
+ }
2942
+ break;
2943
+ }
2944
+ if (devtoolsEnabled()) {
2945
+ emitDevtools({ type: "island:event", instance, name: op.name, payload: previewValue(op.payload, 1024) });
2946
+ }
2947
+ runInInstance(parentInstance, () => onEvent?.(op.name, op.payload));
2948
+ break;
2949
+ }
2950
+ }
2951
+ }
2952
+ function applyOps(ops, via) {
2953
+ opsApplied += ops.length;
2954
+ onActivity?.();
2955
+ const t0 = performance.now();
2956
+ for (const op of ops) applyOp(op);
2957
+ const replayMs = performance.now() - t0;
2958
+ if (devtoolsEnabled()) {
2959
+ emitDevtools({
2960
+ type: "island:ops",
2961
+ instance,
2962
+ via,
2963
+ count: ops.length,
2964
+ replayMs,
2965
+ bytes: estimateCloneBytes(ops)
2966
+ });
2967
+ }
2968
+ onOps?.(ops, replayMs);
2969
+ }
2970
+ const islandCall = (method, p) => {
2971
+ const t0 = performance.now();
2972
+ return p.then(
2973
+ (r) => {
2974
+ emitDevtools({
2975
+ type: "island:task",
2976
+ instance,
2977
+ method,
2978
+ ms: performance.now() - t0,
2979
+ ops: Array.isArray(r) ? r.length : void 0
2980
+ });
2981
+ return r;
2982
+ },
2983
+ (err) => {
2984
+ emitDevtools({
2985
+ type: "island:task",
2986
+ instance,
2987
+ method,
2988
+ ms: performance.now() - t0,
2989
+ error: err instanceof Error ? err.message : String(err)
2990
+ });
2991
+ throw err;
2992
+ }
2993
+ );
2994
+ };
2995
+ const doFlush = () => {
2996
+ if (destroyed) return;
2997
+ flushCalls++;
2998
+ void islandCall("flush", client.flush(instance)).then((ops) => applyOps(ops, "flush"));
2999
+ };
3000
+ const doorbell2 = client.sharedMemory ? observe(client.sharedMemory, "opsVersion") : null;
3001
+ function setMode(next) {
3002
+ mode = next;
3003
+ unsubscribe?.();
3004
+ unsubscribe = null;
3005
+ if (pollTimer !== null) {
3006
+ clearInterval(pollTimer);
3007
+ pollTimer = null;
3008
+ }
3009
+ if (mode === "push" && doorbell2 !== null) unsubscribe = doorbell2.subscribe(doFlush);
3010
+ else pollTimer = setInterval(doFlush, 50);
3011
+ onActivity?.();
3012
+ }
3013
+ const mountMs = mountTimeout ?? 15e3;
3014
+ const raced = (p) => {
3015
+ if (mountMs <= 0) return p;
3016
+ let timer;
3017
+ return Promise.race([
3018
+ p,
3019
+ new Promise((_, reject) => {
3020
+ timer = setTimeout(
3021
+ () => reject(
3022
+ new Error(
3023
+ `mountIsland(${app}): the sub-worker never answered within ${mountMs}ms \u2014 check that the entry module loads and calls definePolyWorker/defineMonoWorker.`
3024
+ )
3025
+ ),
3026
+ mountMs
3027
+ );
3028
+ })
3029
+ ]).finally(() => clearTimeout(timer));
3030
+ };
3031
+ let pid;
3032
+ try {
3033
+ applyOps(await raced(islandCall("mount", client.mount(instance, props))), "mount");
3034
+ pid = await raced(islandCall("whoami", client.whoami(instance)));
3035
+ if (devtoolsEnabled()) {
3036
+ const fw = framework ?? await client.renderer(instance).catch(() => null) ?? void 0;
3037
+ emitDevtools({
3038
+ type: "island:mount",
3039
+ instance,
3040
+ app,
3041
+ pid,
3042
+ poolId: client.pool?.poolId,
3043
+ framework: fw
3044
+ });
3045
+ emitDevtools({ type: "island:props", instance, props: propsPreview(givenProps ?? {}) });
3046
+ }
3047
+ } catch (err) {
3048
+ clientMounts.set(client, (clientMounts.get(client) ?? 1) - 1);
3049
+ if (givenClient === void 0) client.terminate();
3050
+ else void islandCall("unmount", client.unmount(instance)).catch(() => {
3051
+ });
3052
+ throw err;
3053
+ }
3054
+ setMode(mode);
3055
+ let measuredEl = false;
3056
+ const pushSize = (w, h) => {
3057
+ if (destroyed) return;
3058
+ void islandCall("setSize", client.setSize(instance, w, h)).then((ops) => applyOps(ops, "setSize"));
3059
+ };
3060
+ const unobserveEl = doc.observeSize(el, (rect) => {
3061
+ if (rect.w === 0 && rect.h === 0) return;
3062
+ measuredEl = true;
3063
+ pushSize(rect.w, rect.h);
3064
+ });
3065
+ doc.onResize((w, h) => {
3066
+ if (!measuredEl) pushSize(w, h);
3067
+ });
3068
+ const initial = getInstanceSize(parentInstance);
3069
+ if (initial !== void 0) pushSize(initial.w, initial.h);
3070
+ const handle = {
3071
+ app,
3072
+ pid,
3073
+ instance,
3074
+ get mode() {
3075
+ return mode;
3076
+ },
3077
+ get opsApplied() {
3078
+ return opsApplied;
3079
+ },
3080
+ get flushCalls() {
3081
+ return flushCalls;
3082
+ },
3083
+ setMode,
3084
+ updateProps: async (next) => {
3085
+ const marshalled = marshalCallbackProps(next, registerCb);
3086
+ applyOps(await islandCall("updateProps", client.updateProps(instance, marshalled)), "updateProps");
3087
+ if (devtoolsEnabled()) emitDevtools({ type: "island:props", instance, props: propsPreview(next) });
3088
+ },
3089
+ flush: async () => {
3090
+ applyOps(await islandCall("flush", client.flush(instance)), "flush");
3091
+ },
3092
+ destroy: () => {
3093
+ if (destroyed) return;
3094
+ destroyed = true;
3095
+ emitDevtools({ type: "island:unmount", instance });
3096
+ for (const subId of [...slotNodes.keys()]) unmountSlot(subId);
3097
+ unobserveEl();
3098
+ for (const cleanup of measureCleanups.values()) cleanup();
3099
+ measureCleanups.clear();
3100
+ unsubscribe?.();
3101
+ if (pollTimer !== null) clearInterval(pollTimer);
3102
+ const remaining = (clientMounts.get(client) ?? 1) - 1;
3103
+ clientMounts.set(client, remaining);
3104
+ const warm = opts.keepAlive === true || isKeepAliveClient(client);
3105
+ if (!warm && (givenClient === void 0 || remaining <= 0)) client.terminate();
3106
+ else void islandCall("unmount", client.unmount(instance)).catch(() => {
3107
+ });
3108
+ }
3109
+ };
3110
+ return handle;
3111
+ }
3112
+ registerSubIslandMounter(mountSubIsland);
3113
+ export {
3114
+ ProxyComment,
3115
+ ProxyElement,
3116
+ ProxyFragment,
3117
+ ProxyNode,
3118
+ ProxyResizeObserver,
3119
+ ProxyText,
3120
+ ROOT_CONTAINER,
3121
+ allocId,
3122
+ bumpOpsVersion,
3123
+ connectIslandWorker,
3124
+ contractOf,
3125
+ createProxyDocument,
3126
+ defineIslandContract,
3127
+ defineMonoWorker,
3128
+ definePolyWorker,
3129
+ docForInstance,
3130
+ emit,
3131
+ getActiveInstance,
3132
+ getHandler,
3133
+ getInstanceSize,
3134
+ getLastActiveInstance,
3135
+ getLastTouchedInstance,
3136
+ installDomShim,
3137
+ installInstanceDispatcher,
3138
+ instances,
3139
+ isEventRef,
3140
+ isIslandContract,
3141
+ islandApp,
3142
+ islandAppNameOf,
3143
+ mountIsland,
3144
+ mountSubIsland,
3145
+ newElement,
3146
+ newText,
3147
+ proxyInstanceStats,
3148
+ pushOp,
3149
+ registerHandler,
3150
+ renderMemory,
3151
+ runInInstance,
3152
+ serializeProps,
3153
+ setActiveInstance,
3154
+ setDoorbellContract,
3155
+ setInstanceSize,
3156
+ takeOps,
3157
+ unregisterHandler,
3158
+ withContract
3159
+ };