ilha 0.14.3 → 0.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1519 @@
1
+ import { _ as skip, a as ISLAND, b as failureMessage, c as KEEP, d as errorView, f as isBigInt, g as isString, h as isObject, i as Fragment, l as KEY_ATTR, m as isNumber, n as isSetupFn, o as ISLAND_MOUNT_INTERNAL, p as isFunction, r as isVNode, s as ISLAND_SLOT_TAG, u as SLOT_ATTR, v as unwrap, y as asFailure } from "./vnode-DP76Gn-b.js";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Stream from "effect/Stream";
4
+ import * as Atom from "effect/unstable/reactivity/Atom";
5
+ import * as Cause from "effect/Cause";
6
+ import * as Exit from "effect/Exit";
7
+ import * as Fiber from "effect/Fiber";
8
+ import * as Scope from "effect/Scope";
9
+ import * as Registry from "effect/unstable/reactivity/AtomRegistry";
10
+
11
+ //#region src/runtime.ts
12
+ const stack = [];
13
+ const getFiber = () => {
14
+ const f = stack.findLast((x) => !x.closed);
15
+ if (!f) throw new Error("ilha: no fiber");
16
+ return f;
17
+ };
18
+ const getActiveFiber = () => stack.findLast((x) => !x.closed);
19
+ const withFiber = (fiber, fn) => {
20
+ stack.push(fiber);
21
+ try {
22
+ return fn();
23
+ } finally {
24
+ stack.pop();
25
+ }
26
+ };
27
+ const provide = (effect, registry) => effect.pipe(Effect.provideService(Registry.AtomRegistry, registry));
28
+ const runInScope = (effect, registry, scope, onOk, onErr, onDone) => {
29
+ const provided = provide(effect, registry);
30
+ const f = Effect.runFork(onOk || onDone ? provided.pipe(Effect.onExit((ex) => Effect.sync(() => {
31
+ onDone?.();
32
+ if (Exit.isSuccess(ex)) onOk?.(ex.value);
33
+ else if (!Cause.hasInterruptsOnly(ex.cause)) onErr?.(Cause.squash(ex.cause));
34
+ }))) : provided);
35
+ Effect.runSync(Scope.addFinalizer(scope, Fiber.interrupt(f)));
36
+ };
37
+ const closeFiber = (fiber) => {
38
+ if (fiber.closed) return;
39
+ fiber.closed = true;
40
+ fiber.trackRestore?.();
41
+ fiber.trackRestore = void 0;
42
+ fiber.renderSub?.();
43
+ fiber.renderSub = void 0;
44
+ for (const slot of fiber.watchSlots ?? []) slot?.dispose();
45
+ fiber.watchSlots = [];
46
+ for (const h of fiber.holes) h.dispose();
47
+ fiber.holes = [];
48
+ Effect.runFork(Scope.close(fiber.scope, Exit.void));
49
+ };
50
+ const makeRuntime = (opts) => {
51
+ const registry = Registry.make();
52
+ const scope = Scope.makeUnsafe();
53
+ let holeSeq = 0;
54
+ let pending = 0;
55
+ let onIdle;
56
+ const runtime = {
57
+ begin: () => {
58
+ pending += 1;
59
+ },
60
+ close: () => {
61
+ onIdle = void 0;
62
+ Effect.runFork(Scope.close(scope, Exit.void));
63
+ registry.dispose();
64
+ },
65
+ end: () => {
66
+ pending -= 1;
67
+ if (pending === 0) onIdle?.();
68
+ },
69
+ hydrateI: 0,
70
+ hydrateValues: opts?.hydrate,
71
+ later: (fn) => {
72
+ runtime.begin();
73
+ queueMicrotask(() => {
74
+ try {
75
+ fn();
76
+ } finally {
77
+ runtime.end();
78
+ }
79
+ });
80
+ },
81
+ nextHole: () => {
82
+ holeSeq += 1;
83
+ return holeSeq;
84
+ },
85
+ registry,
86
+ scope,
87
+ setIdle: (onReady) => {
88
+ onIdle = onReady;
89
+ if (pending === 0) return onReady();
90
+ },
91
+ ssr: opts?.ssr === true,
92
+ ssrActions: {},
93
+ ssrCapture: opts?.ssrCapture === true,
94
+ ssrEventI: 0,
95
+ ssrValues: []
96
+ };
97
+ return runtime;
98
+ };
99
+ const makeFiber = (runtime, root, paint, opts) => {
100
+ const scope = Scope.forkUnsafe(runtime.scope);
101
+ const fiber = {
102
+ closed: false,
103
+ fail: (e) => {
104
+ opts?.onFail?.(e);
105
+ },
106
+ holes: [],
107
+ inFlight: false,
108
+ paint: (view) => paint(fiber, view),
109
+ paintFn: paint,
110
+ registry: runtime.registry,
111
+ root,
112
+ run: (effect, onOk, onErr) => {
113
+ if (fiber.closed) return;
114
+ if (onOk) fiber.runtime.begin();
115
+ runInScope(effect, fiber.registry, fiber.scope, onOk && ((a) => {
116
+ if (!fiber.closed) withFiber(fiber, () => onOk(a));
117
+ }), onErr && ((e) => {
118
+ if (!fiber.closed) withFiber(fiber, () => onErr(e));
119
+ }), onOk && (() => fiber.runtime.end()));
120
+ },
121
+ runtime,
122
+ scope
123
+ };
124
+ return fiber;
125
+ };
126
+
127
+ //#endregion
128
+ //#region src/atom.ts
129
+ const handleOwner = /* @__PURE__ */ new WeakMap();
130
+ const guardHandleFiber = (handle) => {
131
+ const owner = handleOwner.get(handle);
132
+ if (!owner) return;
133
+ const active = getActiveFiber();
134
+ if (!active || active.registry === owner.registry) return;
135
+ throw new Error("ilha: atom handle used from a different island");
136
+ };
137
+ const instr = (effect) => {
138
+ const fiber = getFiber();
139
+ const out = {
140
+ $$ilhaOp: 1,
141
+ effect,
142
+ *[Symbol.iterator]() {
143
+ return yield out;
144
+ }
145
+ };
146
+ const awaitInstruction = async (onFulfilled, onRejected) => {
147
+ const { promise, resolve, reject } = Promise.withResolvers();
148
+ fiber.run(out.effect, (a) => {
149
+ withFiber(fiber, () => resolve(a));
150
+ }, reject);
151
+ try {
152
+ const value = await promise;
153
+ onFulfilled?.(value);
154
+ } catch (error) {
155
+ onRejected?.(error);
156
+ }
157
+ };
158
+ Object.defineProperty(out, "then", { value: awaitInstruction });
159
+ return out;
160
+ };
161
+ const isAtomHandle = (x) => isFunction(x) && x.$$atom === 1;
162
+ let trackGet;
163
+ const trackStack = [];
164
+ const beginPrimitiveFrame = (fiber) => {
165
+ const used = fiber.primitiveI ?? 0;
166
+ const slots = fiber.primitives?.length ?? 0;
167
+ if (used > 0 && used !== slots) console.warn(`ilha: atom/watch slot count changed between renders (${slots} → ${used}); keep primitive order stable`);
168
+ fiber.primitiveI = 0;
169
+ fiber.watchI = 0;
170
+ if (!fiber.islandFrame) fiber.islandFrame = {
171
+ i: 0,
172
+ slots: []
173
+ };
174
+ const { islandFrame } = fiber;
175
+ const usedIslands = islandFrame.i;
176
+ if (usedIslands < islandFrame.slots.length) {
177
+ for (let j = usedIslands; j < islandFrame.slots.length; j += 1) {
178
+ const slot = islandFrame.slots[j];
179
+ if (slot) {
180
+ slot.unmount?.();
181
+ islandFrame.slots[j] = void 0;
182
+ }
183
+ }
184
+ islandFrame.slots.length = usedIslands;
185
+ }
186
+ islandFrame.i = 0;
187
+ };
188
+ const readTracked = (atom, registry) => {
189
+ const frame = trackStack.at(-1);
190
+ const active = getActiveFiber();
191
+ if (trackGet && frame && (active === frame.fiber || active === void 0)) return trackGet(atom);
192
+ return registry.get(atom);
193
+ };
194
+ const usePrimitiveSlot = (fiber, make) => {
195
+ const i = fiber.primitiveI ?? 0;
196
+ fiber.primitiveI = i + 1;
197
+ fiber.primitives ??= [];
198
+ const existing = fiber.primitives[i];
199
+ if (existing) return {
200
+ atom: existing,
201
+ fresh: false
202
+ };
203
+ const a = make();
204
+ fiber.primitives[i] = a;
205
+ return {
206
+ atom: a,
207
+ fresh: true
208
+ };
209
+ };
210
+ const withTrackGetRun = (fiber, fn, onOk, onErr) => {
211
+ const deps = /* @__PURE__ */ new Set();
212
+ const get = (a) => {
213
+ deps.add(a);
214
+ return fiber.registry.get(a);
215
+ };
216
+ const restore = () => {
217
+ if (fiber.trackRestore !== restore) return;
218
+ const top = trackStack.pop();
219
+ if (top?.get !== get) {
220
+ if (top) trackStack.push(top);
221
+ const i = trackStack.findIndex((frame) => frame.get === get);
222
+ if (i !== -1) trackStack.splice(i, 1);
223
+ }
224
+ trackGet = trackStack.at(-1)?.get;
225
+ fiber.trackRestore = void 0;
226
+ };
227
+ trackStack.push({
228
+ deps,
229
+ fiber,
230
+ get
231
+ });
232
+ trackGet = get;
233
+ fiber.trackRestore = restore;
234
+ try {
235
+ const result = withFiber(fiber, () => fn());
236
+ if (result instanceof Promise) {
237
+ const settle = async () => {
238
+ try {
239
+ const value = await result;
240
+ restore();
241
+ if (fiber.closed) return;
242
+ onOk({
243
+ deps,
244
+ value
245
+ });
246
+ } catch (error) {
247
+ restore();
248
+ if (fiber.closed) return;
249
+ onErr(error);
250
+ }
251
+ };
252
+ settle();
253
+ return;
254
+ }
255
+ restore();
256
+ onOk({
257
+ deps,
258
+ value: result
259
+ });
260
+ } catch (error) {
261
+ restore();
262
+ onErr(error);
263
+ }
264
+ };
265
+ const subscribeRenderDeps = (fiber, deps, run) => {
266
+ let scheduled = false;
267
+ const schedule = () => {
268
+ if (scheduled) return;
269
+ scheduled = true;
270
+ fiber.runtime.later(() => {
271
+ scheduled = false;
272
+ run();
273
+ });
274
+ };
275
+ const unsubs = [];
276
+ for (const dep of deps) unsubs.push(fiber.registry.subscribe(dep, schedule));
277
+ return () => {
278
+ for (const unsub of unsubs) unsub();
279
+ };
280
+ };
281
+ const wrapHandle = (atom, fiber) => {
282
+ const { registry } = fiber;
283
+ const read = (() => {
284
+ guardHandleFiber(read);
285
+ return readTracked(atom, registry);
286
+ });
287
+ handleOwner.set(read, fiber);
288
+ Object.defineProperties(read, {
289
+ $$atom: { value: 1 },
290
+ atom: { value: atom },
291
+ set: { value: (n) => {
292
+ guardHandleFiber(read);
293
+ if (Atom.isWritable(atom)) registry.set(atom, n);
294
+ } },
295
+ update: { value: (f) => {
296
+ guardHandleFiber(read);
297
+ if (Atom.isWritable(atom)) registry.update(atom, f);
298
+ } }
299
+ });
300
+ return read;
301
+ };
302
+ const makeAtom = (init) => {
303
+ const fiber = getFiber();
304
+ const snap = fiber.runtime.hydrateValues;
305
+ const { atom: a, fresh } = usePrimitiveSlot(fiber, () => {
306
+ let seed = init;
307
+ if (snap && !Atom.isAtom(init) && !Effect.isEffect(init) && !Stream.isStream(init)) {
308
+ const v = snap[fiber.runtime.hydrateI];
309
+ fiber.runtime.hydrateI += 1;
310
+ if (v !== void 0) seed = v;
311
+ }
312
+ if (Atom.isAtom(seed)) return seed;
313
+ if (Effect.isEffect(seed) || Stream.isStream(seed)) return Atom.make(seed);
314
+ return Atom.make(seed);
315
+ });
316
+ const handle = wrapHandle(a, fiber);
317
+ if (fresh && fiber.runtime.ssr) fiber.runtime.ssrValues.push(fiber.registry.get(a));
318
+ return handle;
319
+ };
320
+ const atomLazy = (init) => {
321
+ const fiber = getFiber();
322
+ const snap = fiber.runtime.hydrateValues;
323
+ const { atom: a, fresh } = usePrimitiveSlot(fiber, () => {
324
+ let seed = init();
325
+ if (snap) {
326
+ const v = snap[fiber.runtime.hydrateI];
327
+ fiber.runtime.hydrateI += 1;
328
+ if (v !== void 0) seed = v;
329
+ }
330
+ return Atom.make(seed);
331
+ });
332
+ const handle = wrapHandle(a, fiber);
333
+ if (fresh && fiber.runtime.ssr) fiber.runtime.ssrValues.push(fiber.registry.get(a));
334
+ return handle;
335
+ };
336
+ const atom = Object.assign(makeAtom, { lazy: atomLazy });
337
+ const { batch } = Atom;
338
+
339
+ //#endregion
340
+ //#region src/interpret.ts
341
+ const isInstruction = (x) => isObject(x) && x.$$ilhaOp === 1;
342
+ const interpret = (gen, fiber) => {
343
+ const iter = gen();
344
+ const resume = (send, thrown) => {
345
+ if (fiber.closed) return;
346
+ withFiber(fiber, () => {
347
+ if (fiber.inFlight) throw new Error("ilha: re-enter while Instruction in flight");
348
+ let step;
349
+ try {
350
+ step = thrown === void 0 ? iter.next(send) : iter.throw(thrown);
351
+ } catch (error) {
352
+ fiber.fail(asFailure(error));
353
+ return;
354
+ }
355
+ if (step.done) {
356
+ if (step.value !== void 0) fiber.paint(step.value);
357
+ return;
358
+ }
359
+ let effect;
360
+ if (isInstruction(step.value)) ({effect} = step.value);
361
+ else if (Effect.isEffect(step.value)) effect = step.value;
362
+ if (effect) {
363
+ fiber.inFlight = true;
364
+ fiber.run(effect, (a) => {
365
+ fiber.inFlight = false;
366
+ resume(a);
367
+ }, (e) => {
368
+ fiber.inFlight = false;
369
+ resume(void 0, new Error(failureMessage(e)));
370
+ });
371
+ return;
372
+ }
373
+ fiber.paint(step.value);
374
+ resume();
375
+ });
376
+ };
377
+ resume();
378
+ };
379
+
380
+ //#endregion
381
+ //#region src/events.ts
382
+ const ACTION_CALL$1 = Symbol.for("ilha.actionCall");
383
+ const eventTypeFromProp = (key) => {
384
+ if (key.length < 4) return;
385
+ let type;
386
+ if (/^on[A-Z]/u.test(key)) type = key.slice(2).toLowerCase();
387
+ else if (/^on[a-z]{3,}$/u.test(key)) type = key.slice(2).toLowerCase();
388
+ else return;
389
+ return type.length >= 3 ? type : void 0;
390
+ };
391
+ const isEventProp = (key) => eventTypeFromProp(key) !== void 0;
392
+ const bindEvents = (node, props, fiber) => {
393
+ const el = node;
394
+ el.__ilhaProps = props;
395
+ el.__ilhaEvents ??= /* @__PURE__ */ new Set();
396
+ for (const key of Object.keys(props)) {
397
+ const type = eventTypeFromProp(key);
398
+ if (!type) continue;
399
+ if (el.__ilhaEvents.has(type)) continue;
400
+ el.__ilhaEvents.add(type);
401
+ if (fiber.runtime.ssr) {
402
+ const fn = props[key];
403
+ if (!isFunction(fn)) continue;
404
+ const branded = fn[ACTION_CALL$1];
405
+ const rec = branded?.k ? {
406
+ a: branded.a ?? [],
407
+ k: branded.k
408
+ } : void 0;
409
+ if (!rec) continue;
410
+ const id = `${type}:${fiber.runtime.ssrEventI}`;
411
+ fiber.runtime.ssrEventI += 1;
412
+ fiber.runtime.ssrActions[id] = rec;
413
+ const existing = el.dataset.ilhaOn;
414
+ el.dataset.ilhaOn = existing ? `${existing},${id}` : id;
415
+ continue;
416
+ }
417
+ el.addEventListener(type, (ev) => {
418
+ if (fiber.closed) return;
419
+ const fn = el.__ilhaProps?.[key];
420
+ if (!isFunction(fn)) return;
421
+ const out = fn(ev);
422
+ if (Effect.isEffect(out)) fiber.run(out);
423
+ });
424
+ }
425
+ };
426
+
427
+ //#endregion
428
+ //#region src/morph.ts
429
+ const PRESERVE_ATTR = "data-morph-preserve";
430
+ const morphKeyOf = (el) => {
431
+ const k = el.getAttribute(KEY_ATTR);
432
+ if (k !== null) return `k:${k}`;
433
+ const s = el.getAttribute(SLOT_ATTR);
434
+ return s === null ? null : `s:${s}`;
435
+ };
436
+ const shouldPreserveMorphAttr = (el, name) => {
437
+ if (name === "value" || name === "checked" || name === "selected") return true;
438
+ if (name === PRESERVE_ATTR) return el.hasAttribute(PRESERVE_ATTR);
439
+ const custom = el.getAttribute(PRESERVE_ATTR);
440
+ if (custom !== null) {
441
+ for (const token of custom.split(/\s+/u)) if (token === name) return true;
442
+ }
443
+ return false;
444
+ };
445
+ const syncAttributes = (from, to) => {
446
+ for (const { name, value } of to.attributes) {
447
+ if (shouldPreserveMorphAttr(from, name)) continue;
448
+ if (from.getAttribute(name) !== value) from.setAttribute(name, value);
449
+ }
450
+ const names = [];
451
+ for (const { name } of from.attributes) names.push(name);
452
+ for (const name of names) {
453
+ if (shouldPreserveMorphAttr(from, name)) continue;
454
+ if (!to.hasAttribute(name)) from.removeAttribute(name);
455
+ }
456
+ };
457
+ const snapshotFocus = () => {
458
+ if (globalThis.document === void 0) return null;
459
+ const active = document.activeElement;
460
+ if (!(active instanceof HTMLElement) || active === document.body) return null;
461
+ let selection = null;
462
+ try {
463
+ if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement) selection = {
464
+ dir: active.selectionDirection,
465
+ end: active.selectionEnd,
466
+ start: active.selectionStart
467
+ };
468
+ } catch {}
469
+ return {
470
+ active,
471
+ selection
472
+ };
473
+ };
474
+ const restoreFocus = (snapshot) => {
475
+ if (!snapshot || !snapshot.active.isConnected) return;
476
+ try {
477
+ if (document.activeElement !== snapshot.active) snapshot.active.focus({ preventScroll: true });
478
+ const sel = snapshot.selection;
479
+ if (sel && sel.start !== null) {
480
+ const el = snapshot.active;
481
+ if (el.selectionStart !== sel.start || el.selectionEnd !== sel.end) el.setSelectionRange(sel.start, sel.end, sel.dir ?? "none");
482
+ }
483
+ } catch {}
484
+ };
485
+ const buildKeyedIndex = (parent) => {
486
+ let fromKeyed = null;
487
+ for (const child of parent.children) {
488
+ const k = morphKeyOf(child);
489
+ if (k !== null) {
490
+ fromKeyed ??= /* @__PURE__ */ new Map();
491
+ if (!fromKeyed.has(k)) fromKeyed.set(k, child);
492
+ }
493
+ }
494
+ return fromKeyed;
495
+ };
496
+ const collectKeys = (parent) => {
497
+ const keys = /* @__PURE__ */ new Set();
498
+ for (const child of parent.children) {
499
+ const k = morphKeyOf(child);
500
+ if (k !== null) keys.add(k);
501
+ }
502
+ return keys;
503
+ };
504
+ const morphInput = (fromEl, toEl) => {
505
+ const hadChecked = fromEl.hasAttribute("checked");
506
+ const hadValue = fromEl.getAttribute("value");
507
+ syncAttributes(fromEl, toEl);
508
+ const hasChecked = toEl.hasAttribute("checked");
509
+ if (hasChecked !== hadChecked) fromEl.checked = hasChecked;
510
+ const newValue = toEl.getAttribute("value");
511
+ if (newValue !== hadValue) fromEl.value = newValue ?? "";
512
+ };
513
+ const morphTextarea = (fromEl, toEl) => {
514
+ const newText = toEl.textContent ?? "";
515
+ if (fromEl.textContent !== newText) {
516
+ fromEl.textContent = newText;
517
+ fromEl.value = newText;
518
+ }
519
+ };
520
+ const morphSelect = (fromEl, toEl, api) => {
521
+ const before = /* @__PURE__ */ new Map();
522
+ const fromSelect = fromEl;
523
+ for (const o of fromSelect.options) before.set(o, {
524
+ attr: o.hasAttribute("selected"),
525
+ live: o.selected
526
+ });
527
+ syncAttributes(fromEl, toEl);
528
+ api.morphChildren(fromEl, toEl);
529
+ const options = [...fromSelect.options];
530
+ if (options.some((o) => o.hasAttribute("selected") !== (before.get(o)?.attr ?? false))) for (const o of options) o.selected = o.hasAttribute("selected");
531
+ else for (const o of options) {
532
+ const prev = before.get(o);
533
+ if (prev && o.selected !== prev.live) o.selected = prev.live;
534
+ }
535
+ };
536
+ const morphElementPair = (fromEl, toEl, api) => {
537
+ if (fromEl.localName !== toEl.localName) {
538
+ fromEl.replaceWith(toEl);
539
+ return;
540
+ }
541
+ const slotId = toEl.getAttribute(SLOT_ATTR);
542
+ if (slotId !== null && fromEl.getAttribute("data-ilha-slot") === slotId) return;
543
+ if (fromEl.localName === "input") {
544
+ morphInput(fromEl, toEl);
545
+ return;
546
+ }
547
+ if (fromEl.localName === "select") {
548
+ morphSelect(fromEl, toEl, api);
549
+ return;
550
+ }
551
+ syncAttributes(fromEl, toEl);
552
+ if (fromEl.localName === "textarea") morphTextarea(fromEl, toEl);
553
+ else api.morphChildren(fromEl, toEl);
554
+ };
555
+ const alignKeyedNode = (fromParent, fromNode, toNode, fromKeyed, toKeys) => {
556
+ const toKey = toNode.nodeType === 1 ? morphKeyOf(toNode) : null;
557
+ let current = fromNode;
558
+ if (toKey !== null) {
559
+ const match = fromKeyed.get(toKey);
560
+ if (match) {
561
+ fromKeyed.delete(toKey);
562
+ if (match !== current) {
563
+ current?.before(match);
564
+ if (!current) fromParent.append(match);
565
+ current = match;
566
+ }
567
+ }
568
+ }
569
+ if (current instanceof Element) {
570
+ const fromKey = morphKeyOf(current);
571
+ if (fromKey !== null && fromKey !== toKey && toKeys.has(fromKey)) {
572
+ current.before(toNode);
573
+ return "continue";
574
+ }
575
+ }
576
+ return current;
577
+ };
578
+ const api = { morphChildren: (fromParent, toParent) => {
579
+ const toNodes = [...toParent.childNodes];
580
+ const fromKeyed = buildKeyedIndex(fromParent);
581
+ const toKeys = fromKeyed === null ? null : collectKeys(toParent);
582
+ for (let i = 0; i < toNodes.length; i += 1) {
583
+ const toNode = toNodes[i];
584
+ if (!toNode) continue;
585
+ let fromNode = fromParent.childNodes[i];
586
+ if (fromKeyed !== null && toKeys !== null) {
587
+ const aligned = alignKeyedNode(fromParent, fromNode, toNode, fromKeyed, toKeys);
588
+ if (aligned === "continue") continue;
589
+ fromNode = aligned;
590
+ }
591
+ if (!fromNode) {
592
+ fromParent.append(toNode);
593
+ continue;
594
+ }
595
+ if (fromNode.nodeType !== toNode.nodeType) {
596
+ fromNode.replaceWith(toNode);
597
+ continue;
598
+ }
599
+ if (fromNode.nodeType === 3 || fromNode.nodeType === 8) {
600
+ if (fromNode.nodeValue !== toNode.nodeValue) fromNode.nodeValue = toNode.nodeValue;
601
+ continue;
602
+ }
603
+ if (fromNode.nodeType === 1) morphElementPair(fromNode, toNode, api);
604
+ }
605
+ while (fromParent.childNodes.length > toNodes.length) fromParent.lastChild?.remove();
606
+ } };
607
+ const morphInner = (from, to) => {
608
+ const focus = snapshotFocus();
609
+ api.morphChildren(from, to);
610
+ restoreFocus(focus);
611
+ };
612
+
613
+ //#endregion
614
+ //#region src/security.ts
615
+ const SAFE_CSS_PROP_RE = /^(?:-{2}[a-zA-Z][a-zA-Z0-9-]*|-?[a-zA-Z][a-zA-Z0-9-]*)$/u;
616
+ const URL_ATTRS = /* @__PURE__ */ new Set([
617
+ "href",
618
+ "src",
619
+ "srcset",
620
+ "imagesrcset",
621
+ "action",
622
+ "formaction",
623
+ "cite",
624
+ "data",
625
+ "poster"
626
+ ]);
627
+ const SAFE_DATA_IMAGE_RE = /^data:image\/(?<fmt>png|jpe?g|gif|webp|avif)[;,]/iu;
628
+ const UNSAFE_SCHEME_RE = /^(?:javascript|vbscript):/iu;
629
+ const DATA_IMAGE_CONTEXTS = /* @__PURE__ */ new Set([
630
+ "img:src",
631
+ "source:srcset",
632
+ "link:imagesrcset"
633
+ ]);
634
+ const isBlockedUrlChar = (code) => code <= 32 || code === 160 || code === 5760 || code >= 8192 && code <= 8205 || code === 8232 || code === 8233 || code === 8239 || code === 8287 || code === 12288 || code === 65279;
635
+ const normalizeUrl = (value) => {
636
+ let out = "";
637
+ for (const ch of value) {
638
+ const code = ch.codePointAt(0) ?? 0;
639
+ if (!isBlockedUrlChar(code)) out += ch;
640
+ }
641
+ return out;
642
+ };
643
+ const checkUrl = (normalized, allowDataImage) => {
644
+ if (UNSAFE_SCHEME_RE.test(normalized)) return false;
645
+ if (/^data:/iu.test(normalized)) return allowDataImage && SAFE_DATA_IMAGE_RE.test(normalized);
646
+ return true;
647
+ };
648
+ const isUrlAttributeName = (name) => {
649
+ const lower = name.toLowerCase();
650
+ return URL_ATTRS.has(lower) || /:(?<attr>href|src|srcset|imagesrcset|action|formaction|cite|data|poster)$/u.test(lower);
651
+ };
652
+ const isSafeUrlAttrValue = (tagName, attrName, value) => {
653
+ const attr = attrName.toLowerCase();
654
+ const allowDataImage = DATA_IMAGE_CONTEXTS.has(`${tagName.toLowerCase()}:${attr}`) && !attr.startsWith("srcset") && attr !== "imagesrcset";
655
+ if (/^(?:srcset|imagesrcset)$/iu.test(attr)) return value.split(",").every((candidate) => {
656
+ const url = candidate.trim().split(/\s+/u)[0] ?? "";
657
+ return url === "" || checkUrl(normalizeUrl(url), false);
658
+ });
659
+ return checkUrl(normalizeUrl(value), allowDataImage);
660
+ };
661
+ const isSafeStyleValue = (str) => {
662
+ if (/[<>{};]/u.test(str) || /expression\(/iu.test(str) || /javascript:/iu.test(str) || /data:/iu.test(str)) return false;
663
+ if (/\\/u.test(str)) return false;
664
+ const urlRe = /url\s*\(\s*(?<inner>[^)]+)\s*\)/giu;
665
+ let m;
666
+ while ((m = urlRe.exec(str)) !== null) {
667
+ const inner = (m.groups?.inner ?? "").trim().replaceAll(/^['"]|['"]$/gu, "");
668
+ if (!checkUrl(normalizeUrl(inner), false)) return false;
669
+ }
670
+ return true;
671
+ };
672
+ const serializeStyleEntries = (value) => Object.entries(value).map(([k, v]) => {
673
+ if (!SAFE_CSS_PROP_RE.test(k)) return "";
674
+ const prop = k.replaceAll(/[A-Z]/gu, (m) => `-${m.toLowerCase()}`);
675
+ const str = String(v);
676
+ if (!isSafeStyleValue(str)) return "";
677
+ return `${prop}:${str}`;
678
+ }).filter(Boolean).join(";");
679
+ const serializeStyleAttr = (value) => {
680
+ if (isString(value)) {
681
+ const obj = {};
682
+ for (const decl of value.split(";")) {
683
+ const colon = decl.indexOf(":");
684
+ if (colon === -1) continue;
685
+ const prop = decl.slice(0, colon).trim();
686
+ const val = decl.slice(colon + 1).trim();
687
+ if (prop) obj[prop] = val;
688
+ }
689
+ return serializeStyleEntries(obj);
690
+ }
691
+ return serializeStyleEntries(value);
692
+ };
693
+
694
+ //#endregion
695
+ //#region src/start.ts
696
+ const isGen = (x) => {
697
+ if (x === null || x === void 0) return false;
698
+ const g = x;
699
+ return isFunction(g.next) && isFunction(g.throw);
700
+ };
701
+ const finish = (fiber, view) => {
702
+ if (fiber.closed) return;
703
+ if (view !== void 0) fiber.paint(view);
704
+ };
705
+ const runSetup = (fiber, fn) => {
706
+ const disposeRenderSub = () => {
707
+ fiber.renderSub?.();
708
+ fiber.renderSub = void 0;
709
+ };
710
+ const api = {
711
+ commit: (out, deps) => {
712
+ if (isGen(out)) {
713
+ disposeRenderSub();
714
+ interpret((() => out), fiber);
715
+ return;
716
+ }
717
+ disposeRenderSub();
718
+ if (!fiber.runtime.ssr) fiber.renderSub = subscribeRenderDeps(fiber, deps, () => api.render());
719
+ withFiber(fiber, () => {
720
+ finish(fiber, out);
721
+ });
722
+ },
723
+ render: () => {
724
+ if (fiber.closed) return;
725
+ fiber.runtime.begin();
726
+ beginPrimitiveFrame(fiber);
727
+ withTrackGetRun(fiber, () => fn(), ({ value: out, deps }) => {
728
+ try {
729
+ api.commit(out, deps);
730
+ } catch (error) {
731
+ withFiber(fiber, () => fiber.fail(asFailure(error)));
732
+ } finally {
733
+ fiber.runtime.end();
734
+ }
735
+ }, (e) => {
736
+ withFiber(fiber, () => fiber.fail(asFailure(e)));
737
+ fiber.runtime.end();
738
+ });
739
+ }
740
+ };
741
+ try {
742
+ api.render();
743
+ } catch (error) {
744
+ fiber.fail(asFailure(error));
745
+ }
746
+ };
747
+
748
+ //#endregion
749
+ //#region src/paint-core.ts
750
+ const disposeHoles = (fiber, opts) => {
751
+ const keep = [];
752
+ for (const h of fiber.holes) {
753
+ if (opts?.morph && h.keepOnMorph) {
754
+ keep.push(h);
755
+ continue;
756
+ }
757
+ h.dispose();
758
+ }
759
+ fiber.holes = keep;
760
+ };
761
+ const findReusableAtomHost = (fiber, atom) => {
762
+ for (const h of fiber.holes) if (h.atom === atom && h.host?.isConnected && h.holeFiber && !h.holeFiber.closed) return {
763
+ hole: h.holeFiber,
764
+ host: h.host
765
+ };
766
+ };
767
+ const emptyUnsub = () => {};
768
+ const isTextish = (view) => isString(view) || isNumber(view) || isBigInt(view);
769
+ const isIterableView = (view) => {
770
+ if (Array.isArray(view)) return true;
771
+ if (!view || !isObject(view)) return false;
772
+ return Symbol.iterator in view;
773
+ };
774
+ const styleCss = (v) => {
775
+ if (isString(v) || isObject(v)) return serializeStyleAttr(v);
776
+ return "";
777
+ };
778
+ const isThenable = (value) => {
779
+ if (!isObject(value) && !isFunction(value)) return false;
780
+ return isFunction(value.then);
781
+ };
782
+ const writeAttr = (el, k, v) => {
783
+ if (v === false || v === null || v === void 0) {
784
+ el.removeAttribute(k);
785
+ return;
786
+ }
787
+ const str = v === true ? "" : String(v);
788
+ if (isUrlAttributeName(k) && !isSafeUrlAttrValue(el.tagName, k, str)) return;
789
+ el.setAttribute(k, str);
790
+ };
791
+ const isSkippedPropKey = (k) => k === "children" || k === "key" || k === "ref" || k.toLowerCase() === "srcdoc" || isEventProp(k) || /^on[A-Z]/u.test(k);
792
+ /**
793
+ * The painter shared by DOM mount and SSR: props, materialization, holes, and
794
+ * keyed reconciliation. Host differences live in `PaintOps`.
795
+ */
796
+ const createPainter = (ops) => {
797
+ const applyFormControl = (el, key, v, fiber) => {
798
+ const setProp = (x) => ops.setFormControl(el, key, x);
799
+ if (isAtomHandle(v)) {
800
+ const memo = el;
801
+ if (memo.__ilhaVal === v) return;
802
+ memo.__ilhaVal = v;
803
+ const unsub = fiber.registry.subscribe(v.atom, setProp, { immediate: true });
804
+ fiber.holes.push({ dispose: unsub });
805
+ } else setProp(v);
806
+ };
807
+ const trackHole = (parent, hole, extra, meta) => {
808
+ parent.holes.push({
809
+ atom: meta?.atom,
810
+ dispose() {
811
+ extra?.();
812
+ if (meta?.host) ops.disconnect(meta.host);
813
+ closeFiber(hole);
814
+ },
815
+ holeFiber: hole,
816
+ host: meta?.host ? ops.asElement(meta.host) : void 0,
817
+ keepOnMorph: !!(meta?.atom && meta?.host) || !!meta?.keyed
818
+ });
819
+ };
820
+ const insert = (fiber, nodes) => {
821
+ for (const n of nodes) ops.appendRoot(fiber.root, n);
822
+ };
823
+ const applyProps = (el, props, fiber) => {
824
+ for (const [k, v] of Object.entries(props)) {
825
+ if (isSkippedPropKey(k)) continue;
826
+ if (k === "className") {
827
+ el.setAttribute("class", String(v ?? ""));
828
+ continue;
829
+ }
830
+ if (k === "htmlFor") {
831
+ el.setAttribute("for", String(v ?? ""));
832
+ continue;
833
+ }
834
+ if (k === "style" && v !== null && v !== void 0 && v !== false) {
835
+ const css = styleCss(v);
836
+ if (css) ops.setStyle(el, css);
837
+ continue;
838
+ }
839
+ if (k === "value" || k === "checked" || k === "selected") {
840
+ applyFormControl(el, k, v, fiber);
841
+ continue;
842
+ }
843
+ if (isFunction(v)) continue;
844
+ writeAttr(el, k, v);
845
+ }
846
+ ops.bindEvents(el, props, fiber);
847
+ const { ref } = props;
848
+ if (isFunction(ref)) {
849
+ const refFn = ref;
850
+ refFn(ops.asElement(el));
851
+ fiber.holes.push({ dispose: () => refFn(null) });
852
+ }
853
+ };
854
+ const paintFns = {
855
+ keyedPaintHole: () => {},
856
+ materialize: () => []
857
+ };
858
+ const api = {
859
+ applyProps,
860
+ insert,
861
+ materialize: (view, fiber) => paintFns.materialize(view, fiber),
862
+ openHole: (parent) => {
863
+ const id = parent.runtime.nextHole();
864
+ const host = ops.createSlotHost(String(id));
865
+ const child = makeFiber(parent.runtime, ops.asRoot(host), (f, v) => api.paintHole(f, v), { onFail: (e) => {
866
+ console.error(e);
867
+ api.paintHole(child, errorView(e));
868
+ } });
869
+ return {
870
+ fiber: child,
871
+ nodes: [host]
872
+ };
873
+ },
874
+ paintHole: (fiber, view) => {
875
+ if (view === KEEP) return;
876
+ let frame = fiber.islandFrame;
877
+ if (!frame) {
878
+ frame = {
879
+ i: 0,
880
+ slots: []
881
+ };
882
+ fiber.islandFrame = frame;
883
+ }
884
+ frame.i = 0;
885
+ const list = Array.isArray(view) ? view : null;
886
+ if (list && list.length > 0 && list.every((v) => isVNode(v) && v.key !== null && v.key !== void 0)) {
887
+ paintFns.keyedPaintHole(fiber, list);
888
+ return;
889
+ }
890
+ disposeHoles(fiber);
891
+ ops.clearRoot(fiber.root);
892
+ api.insert(fiber, api.materialize(view, fiber));
893
+ }
894
+ };
895
+ const materializeAtom = (view, fiber) => {
896
+ const reused = findReusableAtomHost(fiber, view.atom);
897
+ if (reused) return [ops.createSlotHost(reused.host.getAttribute(SLOT_ATTR))];
898
+ const { fiber: hole, nodes } = api.openHole(fiber);
899
+ const [host] = nodes;
900
+ if (!host) return nodes;
901
+ let unsub = emptyUnsub;
902
+ fiber.runtime.later(() => {
903
+ if (hole.closed) return;
904
+ let keep;
905
+ unsub = fiber.registry.subscribe(view.atom, (v) => {
906
+ if (hole.closed) return;
907
+ const next = unwrap(v, keep);
908
+ if (next === KEEP) return;
909
+ keep = next;
910
+ api.paintHole(hole, next);
911
+ }, { immediate: true });
912
+ });
913
+ trackHole(fiber, hole, () => unsub(), {
914
+ atom: view.atom,
915
+ host
916
+ });
917
+ return nodes;
918
+ };
919
+ const materializeStream = (view, fiber) => {
920
+ const { fiber: hole, nodes } = api.openHole(fiber);
921
+ const src = fiber.runtime.ssr ? view.pipe(Stream.take(1)) : view;
922
+ const effect = Effect.gen(function* streamPaint() {
923
+ const result = yield* Effect.result(Stream.runForEach(src, (v) => Effect.sync(() => {
924
+ if (!hole.closed) api.paintHole(hole, v);
925
+ })));
926
+ if (result._tag === "Failure" && !hole.closed) api.paintHole(hole, String(result.failure));
927
+ });
928
+ hole.run(effect, fiber.runtime.ssr ? emptyUnsub : void 0);
929
+ trackHole(fiber, hole);
930
+ return nodes;
931
+ };
932
+ const materializeIsland = (type, props, fiber) => {
933
+ let frame = fiber.islandFrame;
934
+ if (!frame) {
935
+ frame = {
936
+ i: 0,
937
+ slots: []
938
+ };
939
+ fiber.islandFrame = frame;
940
+ }
941
+ const { i } = frame;
942
+ frame.i = i + 1;
943
+ const existing = frame.slots[i];
944
+ if (existing && existing.type === type && existing.host.isConnected && isFunction(existing.updateProps)) {
945
+ existing.updateProps(props);
946
+ return [ops.asNode(existing.host)];
947
+ }
948
+ existing?.unmount?.();
949
+ const tag = isString(type[ISLAND_SLOT_TAG]) ? type[ISLAND_SLOT_TAG] : "div";
950
+ const el = ops.createElement(tag);
951
+ el.dataset.ilha = "";
952
+ const mountFn = type[ISLAND_MOUNT_INTERNAL];
953
+ if (isFunction(mountFn)) {
954
+ const handle = mountFn(ops.asElement(el), props);
955
+ const slot = {
956
+ host: ops.asElement(el),
957
+ type,
958
+ unmount: handle?.unmount,
959
+ updateProps: handle?.updateProps
960
+ };
961
+ frame.slots[i] = slot;
962
+ fiber.holes.push({
963
+ dispose: () => {
964
+ ops.disconnect(el);
965
+ handle?.unmount?.();
966
+ if (frame.slots[i] === slot) frame.slots[i] = void 0;
967
+ },
968
+ keepOnMorph: true
969
+ });
970
+ }
971
+ return [el];
972
+ };
973
+ const materializeComponent = (view, type, fiber) => {
974
+ const props = {
975
+ ...view.props,
976
+ children: view.children
977
+ };
978
+ const branded = type;
979
+ if (branded[ISLAND] === true) return materializeIsland(branded, props, fiber);
980
+ const k = view.key === null || view.key === void 0 ? void 0 : String(view.key);
981
+ const reuse = k ? fiber.keyedHoles?.get(k) : void 0;
982
+ if (reuse && !reuse.closed) {
983
+ if (reuse.propsBox) reuse.propsBox.current = props;
984
+ const next = withFiber(fiber, () => type(reuse.propsBox?.current ?? props));
985
+ if (next && !isThenable(next) && !isSetupFn(next)) reuse.paint(next);
986
+ return [ops.asNode(reuse.root)];
987
+ }
988
+ const { fiber: hole, nodes } = api.openHole(fiber);
989
+ hole.propsBox = { current: props };
990
+ if (k) {
991
+ fiber.keyedHoles ??= /* @__PURE__ */ new Map();
992
+ fiber.keyedHoles.set(k, hole);
993
+ }
994
+ fiber.runtime.later(() => {
995
+ if (hole.closed) return;
996
+ const box = hole.propsBox;
997
+ if (!box) return;
998
+ runSetup(hole, () => type(box.current));
999
+ });
1000
+ trackHole(fiber, hole, void 0, k ? { keyed: true } : void 0);
1001
+ return nodes;
1002
+ };
1003
+ const materializeElement = (view, fiber) => {
1004
+ const el = ops.createElement(view.type);
1005
+ if (view.key !== null && view.key !== void 0) el.setAttribute(KEY_ATTR, String(view.key));
1006
+ api.applyProps(el, view.props, fiber);
1007
+ fiber.keyedHoles ??= /* @__PURE__ */ new Map();
1008
+ const childFiber = {
1009
+ ...fiber,
1010
+ holes: fiber.holes,
1011
+ keyedHoles: fiber.keyedHoles,
1012
+ root: ops.asRoot(el)
1013
+ };
1014
+ for (const c of view.children) for (const n of api.materialize(c, childFiber)) el.append(n);
1015
+ return [el];
1016
+ };
1017
+ const materializeVNode = (view, fiber) => {
1018
+ if (view.type === Fragment) return view.children.flatMap((c) => api.materialize(c, fiber));
1019
+ if (isFunction(view.type)) return materializeComponent(view, view.type, fiber);
1020
+ return materializeElement(view, fiber);
1021
+ };
1022
+ paintFns.materialize = (view, fiber) => {
1023
+ if (skip(view)) return [];
1024
+ if (isTextish(view)) return [ops.createText(String(view))];
1025
+ if (isAtomHandle(view)) return materializeAtom(view, fiber);
1026
+ if (Stream.isStream(view)) return materializeStream(view, fiber);
1027
+ if (isSetupFn(view)) {
1028
+ const { fiber: hole, nodes } = api.openHole(fiber);
1029
+ fiber.runtime.later(() => {
1030
+ if (!hole.closed) runSetup(hole, view);
1031
+ });
1032
+ trackHole(fiber, hole);
1033
+ return nodes;
1034
+ }
1035
+ if (isVNode(view)) return materializeVNode(view, fiber);
1036
+ if (isIterableView(view)) return [...view].flatMap((c) => api.materialize(c, fiber));
1037
+ return [ops.createText(String(view))];
1038
+ };
1039
+ paintFns.keyedPaintHole = (fiber, views) => {
1040
+ const prev = fiber.keyedHoles ?? /* @__PURE__ */ new Map();
1041
+ const keep = new Set(views.map((v) => String(v.key)));
1042
+ for (const [k, h] of prev) if (!keep.has(k)) closeFiber(h);
1043
+ const next = /* @__PURE__ */ new Map();
1044
+ const nodes = [];
1045
+ for (const v of views) {
1046
+ const k = String(v.key);
1047
+ const reuse = prev.get(k);
1048
+ if (reuse && !reuse.closed) {
1049
+ next.set(k, reuse);
1050
+ nodes.push(ops.asNode(reuse.root));
1051
+ continue;
1052
+ }
1053
+ const made = api.materialize(v, fiber);
1054
+ const hole = fiber.keyedHoles?.get(k);
1055
+ if (hole) next.set(k, hole);
1056
+ nodes.push(...made);
1057
+ }
1058
+ fiber.keyedHoles = next;
1059
+ ops.clearRoot(fiber.root);
1060
+ api.insert(fiber, nodes);
1061
+ };
1062
+ return {
1063
+ applyProps: api.applyProps,
1064
+ disposeHoles: (fiber, opts) => disposeHoles(fiber, opts),
1065
+ insert: api.insert,
1066
+ materialize: api.materialize,
1067
+ paintHole: api.paintHole
1068
+ };
1069
+ };
1070
+
1071
+ //#endregion
1072
+ //#region src/paint.ts
1073
+ const noopDisconnect = () => {};
1074
+ const core$1 = createPainter({
1075
+ appendRoot: (root, node) => root.append(node),
1076
+ asElement: (el) => el,
1077
+ asNode: (host) => host,
1078
+ asRoot: (el) => el,
1079
+ bindEvents,
1080
+ clearRoot: (root) => {
1081
+ if (root instanceof Element) root.replaceChildren();
1082
+ },
1083
+ createElement: (tag) => document.createElement(tag),
1084
+ createSlotHost: (slotId) => {
1085
+ const host = document.createElement("span");
1086
+ if (slotId !== null) host.setAttribute(SLOT_ATTR, slotId);
1087
+ host.style.display = "contents";
1088
+ return host;
1089
+ },
1090
+ createText: (text) => document.createTextNode(text),
1091
+ disconnect: noopDisconnect,
1092
+ setFormControl: (el, key, v) => {
1093
+ const node = el;
1094
+ if (key === "value") node.value = String(v ?? "");
1095
+ else if (key === "checked") node.checked = Boolean(v);
1096
+ else node.selected = Boolean(v);
1097
+ },
1098
+ setStyle: (el, css) => {
1099
+ el.style.cssText = css;
1100
+ }
1101
+ });
1102
+ const refreshAtomHost = (fiber, atom, host, hole) => {
1103
+ if (hole.closed || !host.isConnected) return;
1104
+ const next = unwrap(fiber.registry.get(atom));
1105
+ if (next === KEEP) return;
1106
+ core$1.paintHole(hole, next);
1107
+ };
1108
+ const refreshAllAtomHosts = (fiber) => {
1109
+ for (const h of fiber.holes) if (h.atom && h.host && h.holeFiber) refreshAtomHost(fiber, h.atom, h.host, h.holeFiber);
1110
+ };
1111
+ const pruneDisconnectedAtomHoles = (fiber) => {
1112
+ fiber.holes = fiber.holes.filter((h) => {
1113
+ if (!h.atom || !h.host || !h.holeFiber) return true;
1114
+ if (h.host.isConnected) return true;
1115
+ h.dispose();
1116
+ return false;
1117
+ });
1118
+ };
1119
+ const canHydrate = (fiber, view) => {
1120
+ if (!isVNode(view) || !isString(view.type)) return false;
1121
+ const el = fiber.root.firstElementChild;
1122
+ return !!el && el.tagName === view.type.toUpperCase();
1123
+ };
1124
+ const paintHydrate = (fiber, view) => {
1125
+ const el = fiber.root.firstElementChild;
1126
+ if (!el) return;
1127
+ fiber.hydrate = false;
1128
+ fiber.liveEl = el;
1129
+ core$1.applyProps(el, view.props, fiber);
1130
+ const childFiber = {
1131
+ ...fiber,
1132
+ holes: fiber.holes,
1133
+ root: el
1134
+ };
1135
+ el.replaceChildren();
1136
+ for (const c of view.children) for (const n of core$1.materialize(c, childFiber)) el.append(n);
1137
+ };
1138
+ const paintMorphLive = (fiber, view) => {
1139
+ if (!fiber.liveEl) return;
1140
+ core$1.disposeHoles(fiber, { morph: true });
1141
+ core$1.applyProps(fiber.liveEl, view.props, fiber);
1142
+ const tmp = document.createElement(fiber.liveEl.localName);
1143
+ for (const c of view.children) for (const n of core$1.materialize(c, fiber)) tmp.append(n);
1144
+ morphInner(fiber.liveEl, tmp);
1145
+ refreshAllAtomHosts(fiber);
1146
+ pruneDisconnectedAtomHoles(fiber);
1147
+ };
1148
+ const paint$1 = (fiber, view) => {
1149
+ fiber.islandFrame ??= {
1150
+ i: 0,
1151
+ slots: []
1152
+ };
1153
+ if (fiber.hydrate) {
1154
+ if (canHydrate(fiber, view)) {
1155
+ paintHydrate(fiber, view);
1156
+ return;
1157
+ }
1158
+ console.warn("[ilha] hydrate mismatch, full mount");
1159
+ fiber.hydrate = false;
1160
+ if (fiber.root instanceof Element) fiber.root.replaceChildren();
1161
+ }
1162
+ if (fiber.liveEl && isVNode(view) && isString(view.type) && fiber.liveEl.localName === view.type) {
1163
+ paintMorphLive(fiber, view);
1164
+ return;
1165
+ }
1166
+ core$1.disposeHoles(fiber);
1167
+ if (fiber.root instanceof Element && fiber.root.childNodes.length > 0) {
1168
+ const tmp = document.createElement("div");
1169
+ for (const n of core$1.materialize(view, fiber)) tmp.append(n);
1170
+ morphInner(fiber.root, tmp);
1171
+ const first = fiber.root.firstElementChild;
1172
+ if (first) fiber.liveEl = first;
1173
+ return;
1174
+ }
1175
+ core$1.insert(fiber, core$1.materialize(view, fiber));
1176
+ const first = fiber.root instanceof Element ? fiber.root.firstElementChild : null;
1177
+ if (first) fiber.liveEl = first;
1178
+ };
1179
+ const paintError$1 = (fiber, e) => {
1180
+ console.error(e);
1181
+ paint$1(fiber, errorView(e));
1182
+ };
1183
+
1184
+ //#endregion
1185
+ //#region src/snapshot.ts
1186
+ const MAX_SNAPSHOT_CHARS = 262144;
1187
+ const MAX_SNAPSHOT_DEPTH = 32;
1188
+ const exceedsMaxDepth = (value, depth) => {
1189
+ if (depth > MAX_SNAPSHOT_DEPTH) return true;
1190
+ if (value === null || !isObject(value)) {
1191
+ if (Array.isArray(value)) {
1192
+ for (const item of value) if (exceedsMaxDepth(item, depth + 1)) return true;
1193
+ }
1194
+ return false;
1195
+ }
1196
+ const obj = value;
1197
+ for (const key of Object.keys(obj)) if (exceedsMaxDepth(obj[key], depth + 1)) return true;
1198
+ return false;
1199
+ };
1200
+ const deleteUnsafeKey = (obj, key) => {
1201
+ if (Object.hasOwn(obj, key)) Reflect.deleteProperty(obj, key);
1202
+ };
1203
+ const stripUnsafeKeys = (value) => {
1204
+ if (Array.isArray(value)) {
1205
+ for (const item of value) stripUnsafeKeys(item);
1206
+ return;
1207
+ }
1208
+ if (value === null || !isObject(value)) return;
1209
+ const obj = value;
1210
+ deleteUnsafeKey(obj, "__proto__");
1211
+ deleteUnsafeKey(obj, "constructor");
1212
+ deleteUnsafeKey(obj, "prototype");
1213
+ for (const key of Object.getOwnPropertyNames(obj)) stripUnsafeKeys(obj[key]);
1214
+ };
1215
+ const safeParseSnapshot = (raw) => {
1216
+ if (raw.length > MAX_SNAPSHOT_CHARS) return;
1217
+ let parsed;
1218
+ try {
1219
+ parsed = JSON.parse(raw);
1220
+ } catch {
1221
+ return;
1222
+ }
1223
+ if (exceedsMaxDepth(parsed, 1)) return;
1224
+ if (!isObject(parsed) || Array.isArray(parsed)) return;
1225
+ stripUnsafeKeys(parsed);
1226
+ return parsed;
1227
+ };
1228
+ const encodeSnapshot = (values) => JSON.stringify({ v: values });
1229
+ const decodeSnapshot = (raw) => {
1230
+ const values = safeParseSnapshot(raw)?.v;
1231
+ return Array.isArray(values) ? values : void 0;
1232
+ };
1233
+
1234
+ //#endregion
1235
+ //#region src/ssr-dom.ts
1236
+ const VOID = /* @__PURE__ */ new Set([
1237
+ "area",
1238
+ "base",
1239
+ "br",
1240
+ "col",
1241
+ "embed",
1242
+ "hr",
1243
+ "img",
1244
+ "input",
1245
+ "link",
1246
+ "meta",
1247
+ "param",
1248
+ "source",
1249
+ "track",
1250
+ "wbr"
1251
+ ]);
1252
+ /** Tags whose children are raw text — do not HTML-escape content. */
1253
+ const RAW_TEXT = /* @__PURE__ */ new Set(["script", "style"]);
1254
+ const escapeText = (s) => s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1255
+ const escapeAttr = (s) => escapeText(s).replaceAll("\"", "&quot;");
1256
+ const createSsrText = (text) => ({
1257
+ kind: "text",
1258
+ text
1259
+ });
1260
+ const serializeRawText = (tag, text) => {
1261
+ if (new RegExp(`</${tag}\\b`, "iu").test(text)) throw new Error(`ilha: unsafe raw text in <${tag}> (contains "</${tag}")`);
1262
+ return text;
1263
+ };
1264
+ const serializeNode = (node) => {
1265
+ if (node.kind === "text") return escapeText(node.text);
1266
+ const parts = [];
1267
+ for (const [name, value] of node.attrs) parts.push(`${name}="${escapeAttr(value)}"`);
1268
+ const attr = parts.length > 0 ? ` ${parts.join(" ")}` : "";
1269
+ const { tag } = node;
1270
+ if (VOID.has(tag)) return `<${tag}${attr}>`;
1271
+ if (RAW_TEXT.has(tag)) return `<${tag}${attr}>${serializeRawText(tag, node.textContent)}</${tag}>`;
1272
+ return `<${tag}${attr}>${node.children.map(serializeNode).join("")}</${tag}>`;
1273
+ };
1274
+ const toDataAttr = (key) => `data-${key.replaceAll(/[A-Z]/gu, (ch) => `-${ch.toLowerCase()}`)}`;
1275
+ const isSymbolKey = (value) => Object.prototype.toString.call(value) === "[object Symbol]";
1276
+ const createSsrElement = (tag) => {
1277
+ const lower = tag.toLowerCase();
1278
+ const children = [];
1279
+ const el = {
1280
+ append: (...nodes) => {
1281
+ children.push(...nodes);
1282
+ },
1283
+ appendChild: (node) => {
1284
+ children.push(node);
1285
+ },
1286
+ attrs: /* @__PURE__ */ new Map(),
1287
+ children,
1288
+ dataset: new Proxy({}, {
1289
+ get: (_t, prop) => {
1290
+ if (prop === "undefined" || isSymbolKey(prop)) return;
1291
+ return el.getAttribute(toDataAttr(prop)) ?? void 0;
1292
+ },
1293
+ set: (_t, prop, value) => {
1294
+ if (isSymbolKey(prop)) return false;
1295
+ const attr = toDataAttr(prop);
1296
+ if (value === void 0 || value === null) el.removeAttribute(attr);
1297
+ else el.setAttribute(attr, String(value));
1298
+ return true;
1299
+ }
1300
+ }),
1301
+ getAttribute: (name) => {
1302
+ const value = el.attrs.get(name);
1303
+ return value === void 0 ? null : value;
1304
+ },
1305
+ hasAttribute: (name) => el.attrs.has(name),
1306
+ isConnected: true,
1307
+ kind: "element",
1308
+ localName: lower,
1309
+ removeAttribute: (name) => {
1310
+ el.attrs.delete(name);
1311
+ },
1312
+ replaceChildren: (...nodes) => {
1313
+ children.length = 0;
1314
+ children.push(...nodes);
1315
+ },
1316
+ setAttribute: (name, value) => {
1317
+ el.attrs.set(name, value);
1318
+ },
1319
+ tag: lower,
1320
+ tagName: lower.toUpperCase(),
1321
+ get textContent() {
1322
+ return children.map((n) => n.kind === "text" ? n.text : n.textContent).join("");
1323
+ },
1324
+ set textContent(value) {
1325
+ children.length = 0;
1326
+ children.push(createSsrText(value));
1327
+ }
1328
+ };
1329
+ return el;
1330
+ };
1331
+ const createSsrRoot = () => {
1332
+ const children = [];
1333
+ return {
1334
+ append: (...nodes) => {
1335
+ children.push(...nodes);
1336
+ },
1337
+ appendChild: (node) => {
1338
+ children.push(node);
1339
+ },
1340
+ children,
1341
+ get innerHTML() {
1342
+ return children.map(serializeNode).join("");
1343
+ },
1344
+ kind: "root",
1345
+ replaceChildren: () => {
1346
+ children.length = 0;
1347
+ }
1348
+ };
1349
+ };
1350
+
1351
+ //#endregion
1352
+ //#region src/ssr-paint.ts
1353
+ const ssrAs = (n) => n;
1354
+ const ACTION_CALL = Symbol.for("ilha.actionCall");
1355
+ const defer$1 = () => {
1356
+ const { promise, resolve } = Promise.withResolvers();
1357
+ return {
1358
+ promise,
1359
+ resolve: () => {
1360
+ resolve(null);
1361
+ }
1362
+ };
1363
+ };
1364
+ const bindSsrEvents = (el, props, fiber) => {
1365
+ const seen = /* @__PURE__ */ new Set();
1366
+ for (const key of Object.keys(props)) {
1367
+ const type = eventTypeFromProp(key);
1368
+ if (!type || seen.has(type)) continue;
1369
+ seen.add(type);
1370
+ const fn = props[key];
1371
+ if (!isFunction(fn)) continue;
1372
+ const branded = fn[ACTION_CALL];
1373
+ const rec = branded?.k ? {
1374
+ a: branded.a ?? [],
1375
+ k: branded.k
1376
+ } : void 0;
1377
+ if (!rec) continue;
1378
+ const id = `${type}:${fiber.runtime.ssrEventI}`;
1379
+ fiber.runtime.ssrEventI += 1;
1380
+ fiber.runtime.ssrActions[id] = rec;
1381
+ const existing = el.dataset.ilhaOn;
1382
+ el.dataset.ilhaOn = existing ? `${existing},${id}` : id;
1383
+ }
1384
+ };
1385
+ const ssrOps = {
1386
+ appendRoot: (root, node) => ssrAs(root).append(node),
1387
+ asElement: (el) => ssrAs(el),
1388
+ asNode: (host) => ssrAs(host),
1389
+ asRoot: (el) => ssrAs(el),
1390
+ bindEvents: bindSsrEvents,
1391
+ clearRoot: (root) => ssrAs(root).replaceChildren(),
1392
+ createElement: createSsrElement,
1393
+ createSlotHost: (slotId) => {
1394
+ const host = createSsrElement("span");
1395
+ if (slotId !== null) host.setAttribute(SLOT_ATTR, slotId);
1396
+ host.setAttribute("style", "display:contents");
1397
+ return host;
1398
+ },
1399
+ createText: createSsrText,
1400
+ disconnect: (el) => {
1401
+ el.isConnected = false;
1402
+ },
1403
+ setFormControl: (el, key, v) => {
1404
+ if (key === "value") el.setAttribute("value", String(v ?? ""));
1405
+ else if (v) el.setAttribute(key, "");
1406
+ else el.removeAttribute(key);
1407
+ },
1408
+ setStyle: (el, css) => el.setAttribute("style", css)
1409
+ };
1410
+ const core = createPainter(ssrOps);
1411
+ const paint = (fiber, view) => {
1412
+ fiber.islandFrame ??= {
1413
+ i: 0,
1414
+ slots: []
1415
+ };
1416
+ core.disposeHoles(fiber);
1417
+ ssrOps.clearRoot(fiber.root);
1418
+ core.insert(fiber, core.materialize(view, fiber));
1419
+ };
1420
+ const paintError = (fiber, e) => {
1421
+ console.error(e);
1422
+ paint(fiber, errorView(e));
1423
+ };
1424
+ const attachSsr = (fn, opts) => {
1425
+ const root = createSsrRoot();
1426
+ const runtime = makeRuntime({
1427
+ ssr: true,
1428
+ ssrCapture: opts?.ssrCapture === true
1429
+ });
1430
+ const { promise: ready, resolve } = defer$1();
1431
+ const fiber = makeFiber(runtime, ssrAs(root), paint, { onFail: (e) => {
1432
+ opts?.onError?.(e);
1433
+ paintError(fiber, e);
1434
+ resolve();
1435
+ } });
1436
+ runtime.begin();
1437
+ runtime.setIdle(resolve);
1438
+ runSetup(fiber, fn);
1439
+ runtime.end();
1440
+ return {
1441
+ ready,
1442
+ root,
1443
+ runtime,
1444
+ unmount: () => {
1445
+ closeFiber(fiber);
1446
+ runtime.close();
1447
+ root.replaceChildren();
1448
+ }
1449
+ };
1450
+ };
1451
+
1452
+ //#endregion
1453
+ //#region src/mount.ts
1454
+ const defer = () => {
1455
+ const { promise, resolve } = Promise.withResolvers();
1456
+ return {
1457
+ promise,
1458
+ resolve: () => {
1459
+ resolve(null);
1460
+ }
1461
+ };
1462
+ };
1463
+ const readHydrate = (el) => {
1464
+ const raw = (el.matches("[data-ilha]") ? el : el.querySelector("[data-ilha]"))?.dataset.ilhaState;
1465
+ if (raw) return decodeSnapshot(raw);
1466
+ const first = el.firstElementChild;
1467
+ if (first?.tagName === "TEMPLATE" && first.matches("[data-ilha-state]")) {
1468
+ const tplRaw = first.dataset.ilhaState;
1469
+ first.remove();
1470
+ return tplRaw ? decodeSnapshot(tplRaw) : void 0;
1471
+ }
1472
+ };
1473
+ const delay = (ms) => {
1474
+ const { promise, resolve } = defer();
1475
+ setTimeout(resolve, ms);
1476
+ return promise;
1477
+ };
1478
+ const attach = (el, fn, opts) => {
1479
+ if (!opts?.hydrate) el.innerHTML = "";
1480
+ const runtime = makeRuntime({ hydrate: opts?.hydrate ? readHydrate(el) : void 0 });
1481
+ const { promise: ready, resolve } = defer();
1482
+ const fiber = makeFiber(runtime, el, paint$1, { onFail: (e) => {
1483
+ opts?.onError?.(e);
1484
+ paintError$1(fiber, e);
1485
+ resolve();
1486
+ } });
1487
+ if (opts?.hydrate) fiber.hydrate = true;
1488
+ runtime.begin();
1489
+ runtime.setIdle(resolve);
1490
+ runSetup(fiber, fn);
1491
+ runtime.end();
1492
+ return {
1493
+ ready,
1494
+ runtime,
1495
+ unmount: () => {
1496
+ closeFiber(fiber);
1497
+ runtime.close();
1498
+ if (!opts?.hydrate) el.innerHTML = "";
1499
+ }
1500
+ };
1501
+ };
1502
+ const mount = (el, fn, opts) => attach(el, fn, opts).unmount;
1503
+ const renderToString = async (fn, opts) => {
1504
+ const snapshot = opts?.snapshot !== false;
1505
+ const markers = opts?.markers !== false;
1506
+ const { unmount, ready, runtime, root } = attachSsr(fn, { ssrCapture: opts?.captureActions === true });
1507
+ const timeout = opts?.timeout;
1508
+ await (timeout === void 0 ? ready : Promise.race([ready, delay(timeout)]));
1509
+ let inner = root.innerHTML;
1510
+ const actions = runtime.ssrActions;
1511
+ if (Object.keys(actions).length > 0) inner = `<template data-ilha-actions="${escapeAttr(JSON.stringify(actions))}"></template>${inner}`;
1512
+ const snap = snapshot ? encodeSnapshot(runtime.ssrValues) : void 0;
1513
+ unmount();
1514
+ if (!markers) return snap ? `<template data-ilha-state="${escapeAttr(snap)}"></template>${inner}` : inner;
1515
+ return `<div data-ilha${snap ? ` data-ilha-state="${escapeAttr(snap)}"` : ""}>${inner}</div>`;
1516
+ };
1517
+
1518
+ //#endregion
1519
+ export { batch as a, isAtomHandle as c, makeFiber as d, atom as i, closeFiber as l, renderToString as n, handleOwner as o, interpret as r, instr as s, mount as t, getFiber as u };