@atolljs/islands 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +57 -0
- package/dist/callbackProps.d.ts +35 -0
- package/dist/chunk-TX4FAHRB.js +1187 -0
- package/dist/contract.d.ts +96 -0
- package/dist/devtoolsCommands.d.ts +21 -0
- package/dist/devtoolsPreview.d.ts +8 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +30 -0
- package/dist/island.d.ts +175 -0
- package/dist/memory.d.ts +34 -0
- package/dist/metrics.d.ts +38 -0
- package/dist/ops.d.ts +339 -0
- package/dist/worker/defineWorkers.d.ts +207 -0
- package/dist/worker/dom/css.d.ts +24 -0
- package/dist/worker/dom/document.d.ts +274 -0
- package/dist/worker/dom/element.d.ts +199 -0
- package/dist/worker/dom/html.d.ts +7 -0
- package/dist/worker/dom/node.d.ts +172 -0
- package/dist/worker/dom/resizeObserver.d.ts +29 -0
- package/dist/worker/dom/selectors.d.ts +17 -0
- package/dist/worker/dom/window.d.ts +80 -0
- package/dist/worker/index.d.ts +33 -0
- package/dist/worker/index.js +3159 -0
- package/dist/worker/instance.d.ts +187 -0
- package/dist/worker/proxyDom.d.ts +10 -0
- package/dist/worker/subIsland.d.ts +11 -0
- package/package.json +15 -5
- package/src/index.ts +1 -1
- package/src/island.ts +13 -3
- package/src/worker/subIsland.ts +5 -4
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
676
|
+
var escapeAttr = (s) => s.replace(/&/g, "&").replace(/"/g, """);
|
|
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
|
+
};
|