ilha 0.14.10 → 0.15.1

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.
@@ -1,4 +1,4 @@
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, x as toError, y as asFailure } from "./vnode-M-oJMKcv.js";
1
+ import { S as toError, _ as isString, a as ISLAND, b as asFailure, c as KEEP, f as errorView, g as isObject, h as isNumber, i as Fragment, l as KEY_ATTR, m as isFunction, n as isSetupFn, o as ISLAND_MOUNT_INTERNAL, p as isBigInt, r as isVNode, s as ISLAND_SLOT_TAG, u as SLOT_ATTR, v as skip, x as failureMessage, y as unwrap } from "./vnode-BHUY61Rm.js";
2
2
  import * as Atom from "effect/reactivity/Atom";
3
3
  import { Equal } from "effect";
4
4
  import * as Effect$1 from "effect/Effect";
@@ -44,6 +44,8 @@ const clearFiberView = (fiber) => {
44
44
  fiber.watchI = 0;
45
45
  fiber.primitives = void 0;
46
46
  fiber.primitiveI = 0;
47
+ for (const release of fiber.primitiveHolds ?? []) release();
48
+ fiber.primitiveHolds = void 0;
47
49
  fiber.islandFrame = void 0;
48
50
  fiber.keyedHoles = void 0;
49
51
  for (const h of fiber.holes) h.dispose();
@@ -93,6 +95,7 @@ const makeRuntime = (opts) => {
93
95
  holeSeq += 1;
94
96
  return holeSeq;
95
97
  },
98
+ onError: opts?.onError,
96
99
  registry,
97
100
  scope,
98
101
  setIdle: (onReady) => {
@@ -148,6 +151,20 @@ const reportFiberError = (fiber, error) => {
148
151
  }
149
152
  return false;
150
153
  };
154
+ /**
155
+ * Open a mount's root fiber. A failing root component reports to
156
+ * `runtime.onError`, paints the error view, and settles the mount.
157
+ */
158
+ const makeRootFiber = ({ runtime, root, paint, settle }) => {
159
+ const fiber = makeFiber(runtime, root, paint, { onFail: (e) => {
160
+ const error = toError(e);
161
+ runtime.onError?.(error);
162
+ console.error(error);
163
+ fiber.paint(errorView(error));
164
+ settle();
165
+ } });
166
+ return fiber;
167
+ };
151
168
 
152
169
  //#endregion
153
170
  //#region src/atom.ts
@@ -272,6 +289,19 @@ const usePrimitiveSlot = (fiber, make) => {
272
289
  fresh: true
273
290
  };
274
291
  };
292
+ /**
293
+ * Keep a component's own state atom in the registry while its fiber lives.
294
+ *
295
+ * The registry drops an atom nothing subscribes to. State that a handler
296
+ * writes and the view never reads would otherwise reset between events.
297
+ */
298
+ const holdState = (fiber, atom) => {
299
+ if (!fiber.runtime.ssr) {
300
+ fiber.primitiveHolds ??= [];
301
+ fiber.primitiveHolds.push(fiber.registry.mount(atom));
302
+ }
303
+ return atom;
304
+ };
275
305
  const withTrackGetRun = (fiber, fn, onOk, onErr) => {
276
306
  const deps = /* @__PURE__ */ new Set();
277
307
  const get = (a) => {
@@ -332,8 +362,10 @@ const subscribeRenderDeps = (fiber, deps, run) => {
332
362
  const schedule = () => {
333
363
  if (scheduled) return;
334
364
  scheduled = true;
365
+ const seen = fiber.renderGen;
335
366
  fiber.runtime.later(() => {
336
367
  scheduled = false;
368
+ if (fiber.renderGen !== seen) return;
337
369
  run();
338
370
  });
339
371
  };
@@ -378,9 +410,9 @@ const makeAtom = (init, options) => {
378
410
  if (Effect$1.isEffect(seed) || Stream$1.isStream(seed)) return Atom.make(seed);
379
411
  const made = Atom.make(seed);
380
412
  const eq = options?.equals;
381
- if (eq === void 0) return made;
413
+ if (eq === void 0) return holdState(fiber, made);
382
414
  const equalsFn = eq === "structural" ? structuralEqual : eq;
383
- return Atom.withEquality(made, equalsFn);
415
+ return holdState(fiber, Atom.withEquality(made, equalsFn));
384
416
  });
385
417
  const handle = wrapHandle(a, fiber);
386
418
  if (fresh && fiber.runtime.ssr) fiber.runtime.ssrValues.push(fiber.registry.get(a));
@@ -396,7 +428,7 @@ const atomLazy = (init) => {
396
428
  fiber.runtime.hydrateI += 1;
397
429
  if (v !== void 0) seed = v;
398
430
  }
399
- return Atom.make(seed);
431
+ return holdState(fiber, Atom.make(seed));
400
432
  });
401
433
  const handle = wrapHandle(a, fiber);
402
434
  if (fresh && fiber.runtime.ssr) fiber.runtime.ssrValues.push(fiber.registry.get(a));
@@ -477,6 +509,7 @@ const runSetup = (fiber, fn) => {
477
509
  },
478
510
  render: () => {
479
511
  if (fiber.closed) return;
512
+ fiber.renderGen = (fiber.renderGen ?? 0) + 1;
480
513
  fiber.runtime.begin();
481
514
  beginPrimitiveFrame(fiber);
482
515
  withTrackGetRun(fiber, () => fn(), ({ value: out, deps }) => {
@@ -502,7 +535,7 @@ const runSetup = (fiber, fn) => {
502
535
 
503
536
  //#endregion
504
537
  //#region src/events.ts
505
- const ACTION_CALL$1 = Symbol.for("ilha.actionCall");
538
+ const ACTION_CALL = Symbol.for("ilha.actionCall");
506
539
  const eventTypeFromProp = (key) => {
507
540
  if (key.length < 4) return;
508
541
  let type;
@@ -537,7 +570,7 @@ const bindSsrActions = (el, props, fiber) => {
537
570
  seen.add(type);
538
571
  const fn = props[key];
539
572
  if (!isFunction(fn)) continue;
540
- const branded = fn[ACTION_CALL$1];
573
+ const branded = fn[ACTION_CALL];
541
574
  const rec = branded?.k ? {
542
575
  a: branded.a ?? [],
543
576
  k: branded.k
@@ -1103,27 +1136,107 @@ const elementNs = (lower, parentNs) => {
1103
1136
  if (parentNs === "http://www.w3.org/2000/svg" || parentNs === "http://www.w3.org/1998/Math/MathML") return parentNs;
1104
1137
  };
1105
1138
  const canMorphRoot = (root) => typeof Element !== "undefined" && typeof document !== "undefined" && root instanceof Element && root.childNodes.length > 0;
1139
+ /** Close a child hole through the parent's hole list, so it disposes once. */
1140
+ const releaseHole = (fiber, hole) => {
1141
+ const idx = fiber.holes.findIndex((h) => h.holeFiber === hole);
1142
+ if (idx === -1) closeFiber(hole);
1143
+ else {
1144
+ const [removed] = fiber.holes.splice(idx, 1);
1145
+ removed?.dispose();
1146
+ }
1147
+ };
1106
1148
  const sweepComponentSlots = (fiber) => {
1107
1149
  const cframe = fiber.componentFrame;
1108
1150
  if (!cframe) return;
1109
1151
  const { slots } = cframe;
1110
1152
  for (let j = cframe.i; j < slots.length; j += 1) {
1111
1153
  const s = slots[j];
1112
- if (s) {
1113
- const { hole } = s;
1114
- if (!hole.closed) {
1115
- const idx = fiber.holes.findIndex((h) => h.holeFiber === hole);
1116
- if (idx === -1) closeFiber(hole);
1117
- else {
1118
- const [removed] = fiber.holes.splice(idx, 1);
1119
- removed?.dispose();
1120
- }
1121
- }
1122
- }
1154
+ if (s && !s.hole.closed) releaseHole(fiber, s.hole);
1123
1155
  slots[j] = void 0;
1124
1156
  }
1125
1157
  slots.length = cframe.i;
1126
1158
  };
1159
+ /** Close the keyed components the paint that just ran did not reach. */
1160
+ const sweepKeyedHoles = (fiber) => {
1161
+ const { keyedHoles, keyedGen } = fiber;
1162
+ if (!keyedHoles) return;
1163
+ for (const [k, hole] of keyedHoles) {
1164
+ if (!hole.closed && hole.keyedSeen === keyedGen) continue;
1165
+ keyedHoles.delete(k);
1166
+ if (!hole.closed) releaseHole(fiber, hole);
1167
+ }
1168
+ };
1169
+ /**
1170
+ * Run `fn` as one paint of `fiber`: the keyed components it materializes stay
1171
+ * alive, and the ones it no longer reaches are closed afterwards.
1172
+ */
1173
+ const pass = (fiber, fn) => {
1174
+ fiber.keyedHoles ??= /* @__PURE__ */ new Map();
1175
+ fiber.keyedGen = (fiber.keyedGen ?? 0) + 1;
1176
+ fn();
1177
+ sweepKeyedHoles(fiber);
1178
+ };
1179
+ /**
1180
+ * Dispose a hole fiber's child holes before it repaints, except the component
1181
+ * and island holes the repaint can reuse: positional children, keyed
1182
+ * children, and islands. Reuse pushes new props instead of remounting, and the
1183
+ * sweeps that end the paint close whatever it did not reach.
1184
+ */
1185
+ const keepReusableHoles = (fiber, cframe, frame) => {
1186
+ const kept = /* @__PURE__ */ new Set();
1187
+ for (const s of cframe.slots) if (s && !s.hole.closed) kept.add(s.hole);
1188
+ for (const hole of fiber.keyedHoles?.values() ?? []) if (!hole.closed) kept.add(hole);
1189
+ const keptIslands = new Set(frame.slots);
1190
+ const keep = [];
1191
+ for (const h of fiber.holes) {
1192
+ if (h.holeFiber && kept.has(h.holeFiber) || h.islandSlot && keptIslands.has(h.islandSlot)) {
1193
+ keep.push(h);
1194
+ continue;
1195
+ }
1196
+ h.dispose();
1197
+ }
1198
+ fiber.holes = keep;
1199
+ };
1200
+ /** Shallow equality of two prop bags, children compared item by item. */
1201
+ const sameProps = (prev, next) => {
1202
+ if (!prev) return false;
1203
+ const keys = Object.keys(next);
1204
+ if (keys.length !== Object.keys(prev).length) return false;
1205
+ return keys.every((k) => {
1206
+ const a = prev[k];
1207
+ const b = next[k];
1208
+ if (k === "children" && Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((x, i) => Object.is(x, b[i]));
1209
+ return Object.is(a, b);
1210
+ });
1211
+ };
1212
+ /**
1213
+ * Rerun a reused component hole with new props, in its own fiber. The props
1214
+ * box is updated in place, so every render closure of the hole reads the
1215
+ * latest props, including a self-render queued while an async render ran.
1216
+ */
1217
+ const rerunHole = (hole, run, props) => {
1218
+ const box = hole.propsBox ?? { current: props };
1219
+ box.current = props;
1220
+ hole.propsBox = box;
1221
+ runSetup(hole, () => run(box.current));
1222
+ };
1223
+ /**
1224
+ * Run a new component hole's first render after the paint that opened it. A
1225
+ * repaint that reached the hole first already rendered it with newer props.
1226
+ */
1227
+ const mountHoleLater = (fiber, hole, run) => {
1228
+ fiber.runtime.later(() => {
1229
+ const box = hole.propsBox;
1230
+ if (hole.closed || hole.renderGen !== void 0 || !box) return;
1231
+ runSetup(hole, () => run(box.current));
1232
+ });
1233
+ };
1234
+ const warnedKeys = /* @__PURE__ */ new Set();
1235
+ const warnDuplicateKey = (key) => {
1236
+ if (warnedKeys.has(key)) return;
1237
+ warnedKeys.add(key);
1238
+ console.warn(`ilha: key "${key}" is used by more than one component in the same parent component; keys must be unique there. The duplicate keeps its state by position instead.`);
1239
+ };
1127
1240
  const sweepIslandSlots = (fiber) => {
1128
1241
  const frame = fiber.islandFrame;
1129
1242
  if (!frame) return;
@@ -1156,10 +1269,6 @@ const styleCss = (v) => {
1156
1269
  if (isString(v) || isObject(v)) return serializeStyleAttr(v);
1157
1270
  return "";
1158
1271
  };
1159
- const isThenable = (value) => {
1160
- if (!isObject(value) && !isFunction(value)) return false;
1161
- return isFunction(value.then);
1162
- };
1163
1272
  const writeAttr = (el, k, v) => {
1164
1273
  if (v === false || v === null || v === void 0) {
1165
1274
  el.removeAttribute(k);
@@ -1218,6 +1327,7 @@ const createPainter = (ops) => {
1218
1327
  const applyProps = (el, props, fiber) => {
1219
1328
  for (const [k, v] of Object.entries(props)) {
1220
1329
  if (isSkippedPropKey(k)) continue;
1330
+ if (ops.setProp?.(el, k, v) === true) continue;
1221
1331
  if (k === "className") {
1222
1332
  el.setAttribute("class", String(v ?? ""));
1223
1333
  continue;
@@ -1263,6 +1373,7 @@ const createPainter = (ops) => {
1263
1373
  onFail: (e) => {
1264
1374
  const error = toError(e);
1265
1375
  if (reportFiberError(child, error)) return;
1376
+ parent.runtime.onError?.(error);
1266
1377
  console.error(error);
1267
1378
  api.paintHole(child, errorView(error));
1268
1379
  },
@@ -1273,57 +1384,47 @@ const createPainter = (ops) => {
1273
1384
  nodes: [host]
1274
1385
  };
1275
1386
  },
1276
- paintHole: (fiber, view) => commit(() => {
1387
+ paintHole: (fiber, view) => {
1277
1388
  if (view === KEEP) return;
1278
- let frame = fiber.islandFrame;
1279
- if (!frame) {
1280
- frame = {
1281
- i: 0,
1282
- slots: []
1283
- };
1284
- fiber.islandFrame = frame;
1285
- }
1286
- frame.i = 0;
1287
- let cframe = fiber.componentFrame;
1288
- if (!cframe) {
1289
- cframe = {
1290
- i: 0,
1291
- slots: []
1292
- };
1293
- fiber.componentFrame = cframe;
1294
- }
1295
- cframe.i = 0;
1296
- const list = Array.isArray(view) ? view : null;
1297
- if (list && list.length > 0 && list.every((v) => isVNode(v) && isFunction(v.type) && v.key !== null && v.key !== void 0)) {
1298
- paintFns.keyedPaintHole(fiber, list);
1299
- sweepComponentSlots(fiber);
1300
- sweepIslandSlots(fiber);
1301
- return;
1302
- }
1303
- const kept = /* @__PURE__ */ new Set();
1304
- for (const s of cframe.slots) if (s && !s.hole.closed) kept.add(s.hole);
1305
- const keptIslands = new Set(frame.slots);
1306
- const keep = [];
1307
- for (const h of fiber.holes) {
1308
- if (h.holeFiber && kept.has(h.holeFiber) || h.islandSlot && keptIslands.has(h.islandSlot)) {
1309
- keep.push(h);
1310
- continue;
1311
- }
1312
- h.dispose();
1313
- }
1314
- fiber.holes = keep;
1315
- const fresh = api.materialize(view, fiber);
1316
- if (canMorphRoot(fiber.root)) {
1317
- const tmp = document.createElement("div");
1318
- for (const n of fresh) tmp.append(n);
1319
- morphInner(fiber.root, tmp);
1320
- } else {
1321
- ops.clearRoot(fiber.root);
1322
- api.insert(fiber, fresh);
1323
- }
1324
- sweepComponentSlots(fiber);
1325
- sweepIslandSlots(fiber);
1326
- })
1389
+ commit(() => {
1390
+ pass(fiber, () => {
1391
+ let frame = fiber.islandFrame;
1392
+ if (!frame) {
1393
+ frame = {
1394
+ i: 0,
1395
+ slots: []
1396
+ };
1397
+ fiber.islandFrame = frame;
1398
+ }
1399
+ frame.i = 0;
1400
+ let cframe = fiber.componentFrame;
1401
+ if (!cframe) {
1402
+ cframe = {
1403
+ i: 0,
1404
+ slots: []
1405
+ };
1406
+ fiber.componentFrame = cframe;
1407
+ }
1408
+ cframe.i = 0;
1409
+ const list = Array.isArray(view) ? view : null;
1410
+ if (list && list.length > 0 && list.every((v) => isVNode(v) && isFunction(v.type) && v.key !== null && v.key !== void 0)) {
1411
+ paintFns.keyedPaintHole(fiber, list);
1412
+ sweepComponentSlots(fiber);
1413
+ sweepIslandSlots(fiber);
1414
+ return;
1415
+ }
1416
+ keepReusableHoles(fiber, cframe, frame);
1417
+ const fresh = api.materialize(view, fiber);
1418
+ if (canMorphRoot(fiber.root)) {
1419
+ const tmp = document.createElement("div");
1420
+ for (const n of fresh) tmp.append(n);
1421
+ morphInner(fiber.root, tmp);
1422
+ } else ops.placeChildren(fiber.root, fresh);
1423
+ sweepComponentSlots(fiber);
1424
+ sweepIslandSlots(fiber);
1425
+ });
1426
+ });
1427
+ }
1327
1428
  };
1328
1429
  const materializeAtom = (view, fiber) => {
1329
1430
  const reused = findReusableAtomHost(fiber, view.atom);
@@ -1426,18 +1527,10 @@ const createPainter = (ops) => {
1426
1527
  if (existing) {
1427
1528
  const { hole } = existing;
1428
1529
  if (existing.type === type && !hole.closed) {
1429
- hole.propsBox = { current: props };
1430
- runSetup(hole, () => run(props));
1530
+ rerunHole(hole, run, props);
1431
1531
  return [ops.reuseNode(hole.root)];
1432
1532
  }
1433
- if (!hole.closed) {
1434
- const idx = fiber.holes.findIndex((h) => h.holeFiber === hole);
1435
- if (idx === -1) closeFiber(hole);
1436
- else {
1437
- const [removed] = fiber.holes.splice(idx, 1);
1438
- removed?.dispose();
1439
- }
1440
- }
1533
+ if (!hole.closed) releaseHole(fiber, hole);
1441
1534
  }
1442
1535
  const { fiber: hole, nodes } = api.openHole(fiber);
1443
1536
  hole.propsBox = { current: props };
@@ -1446,15 +1539,36 @@ const createPainter = (ops) => {
1446
1539
  hole,
1447
1540
  type
1448
1541
  };
1449
- fiber.runtime.later(() => {
1450
- if (hole.closed) return;
1451
- const box = hole.propsBox;
1452
- if (!box) return;
1453
- runSetup(hole, () => run(box.current));
1454
- });
1542
+ mountHoleLater(fiber, hole, run);
1455
1543
  trackHole(fiber, hole, void 0, { keyed: true });
1456
1544
  return nodes;
1457
1545
  };
1546
+ const materializeKeyedComponent = ({ run, type, props, key, fiber }) => {
1547
+ let reuse = fiber.keyedHoles?.get(key);
1548
+ const gen = fiber.keyedGen;
1549
+ if (reuse && !reuse.closed && reuse.keyedSeen === gen) {
1550
+ warnDuplicateKey(key);
1551
+ return materializeUnkeyedComponent(run, type, props, fiber);
1552
+ }
1553
+ if (reuse && !reuse.closed && reuse.componentType !== type) {
1554
+ fiber.keyedHoles?.delete(key);
1555
+ releaseHole(fiber, reuse);
1556
+ reuse = void 0;
1557
+ }
1558
+ if (reuse && !reuse.closed) {
1559
+ reuse.keyedSeen = gen;
1560
+ if (!sameProps(reuse.propsBox?.current, props)) rerunHole(reuse, run, props);
1561
+ return [ops.reuseNode(reuse.root)];
1562
+ }
1563
+ const { fiber: khole, nodes: knodes } = api.openHole(fiber);
1564
+ khole.propsBox = { current: props };
1565
+ khole.componentType = type;
1566
+ khole.keyedSeen = gen;
1567
+ fiber.keyedHoles?.set(key, khole);
1568
+ mountHoleLater(fiber, khole, run);
1569
+ trackHole(fiber, khole, void 0, { keyed: true });
1570
+ return knodes;
1571
+ };
1458
1572
  const materializeComponent = (view, type, fiber) => {
1459
1573
  const props = {
1460
1574
  ...view.props,
@@ -1462,28 +1576,15 @@ const createPainter = (ops) => {
1462
1576
  };
1463
1577
  const branded = type;
1464
1578
  if (branded[ISLAND] === true) return materializeIsland(branded, props, fiber);
1465
- const k = view.key === null || view.key === void 0 ? void 0 : String(view.key);
1466
- const reuse = k ? fiber.keyedHoles?.get(k) : void 0;
1467
1579
  const run = type;
1468
- if (reuse && !reuse.closed) {
1469
- if (reuse.propsBox) reuse.propsBox.current = props;
1470
- const next = withFiber(fiber, () => run(reuse.propsBox?.current ?? props));
1471
- if (next && !isThenable(next) && !isSetupFn(next)) reuse.paint(next);
1472
- return [ops.reuseNode(reuse.root)];
1473
- }
1474
- if (!k) return materializeUnkeyedComponent(run, type, props, fiber);
1475
- const { fiber: khole, nodes: knodes } = api.openHole(fiber);
1476
- khole.propsBox = { current: props };
1477
- fiber.keyedHoles ??= /* @__PURE__ */ new Map();
1478
- fiber.keyedHoles.set(k, khole);
1479
- fiber.runtime.later(() => {
1480
- if (khole.closed) return;
1481
- const box = khole.propsBox;
1482
- if (!box) return;
1483
- runSetup(khole, () => run(box.current));
1580
+ if (view.key === null || view.key === void 0) return materializeUnkeyedComponent(run, type, props, fiber);
1581
+ return materializeKeyedComponent({
1582
+ fiber,
1583
+ key: String(view.key),
1584
+ props,
1585
+ run,
1586
+ type
1484
1587
  });
1485
- trackHole(fiber, khole, void 0, { keyed: true });
1486
- return knodes;
1487
1588
  };
1488
1589
  const materializeElement = (view, fiber) => {
1489
1590
  const tag = view.type;
@@ -1492,7 +1593,6 @@ const createPainter = (ops) => {
1492
1593
  const el = ops.createElement(tag, ns);
1493
1594
  if (view.key !== null && view.key !== void 0) el.setAttribute(KEY_ATTR, String(view.key));
1494
1595
  api.applyProps(el, view.props, fiber);
1495
- fiber.keyedHoles ??= /* @__PURE__ */ new Map();
1496
1596
  const childFiber = {
1497
1597
  ...fiber,
1498
1598
  holes: fiber.holes,
@@ -1526,25 +1626,22 @@ const createPainter = (ops) => {
1526
1626
  return [ops.createText(String(view))];
1527
1627
  };
1528
1628
  paintFns.keyedPaintHole = (fiber, views) => {
1529
- const prev = fiber.keyedHoles ?? /* @__PURE__ */ new Map();
1530
- const keep = new Set(views.map((v) => String(v.key)));
1531
- for (const [k, h] of prev) if (!keep.has(k)) closeFiber(h);
1532
- const next = /* @__PURE__ */ new Map();
1533
1629
  const nodes = [];
1534
1630
  for (const v of views) {
1535
- const k = String(v.key);
1536
- const reuse = prev.get(k);
1537
- if (reuse && !reuse.closed) {
1538
- next.set(k, reuse);
1631
+ const reuse = fiber.keyedHoles?.get(String(v.key));
1632
+ const reusable = reuse && !reuse.closed && reuse.keyedSeen !== fiber.keyedGen && reuse.componentType === v.type;
1633
+ if (reuse && reusable) {
1634
+ reuse.keyedSeen = fiber.keyedGen;
1635
+ const props = {
1636
+ ...v.props,
1637
+ children: v.children
1638
+ };
1639
+ if (!sameProps(reuse.propsBox?.current, props)) rerunHole(reuse, v.type, props);
1539
1640
  nodes.push(ops.asNode(reuse.root));
1540
1641
  continue;
1541
1642
  }
1542
- const made = api.materialize(v, fiber);
1543
- const hole = fiber.keyedHoles?.get(k);
1544
- if (hole) next.set(k, hole);
1545
- nodes.push(...made);
1643
+ nodes.push(...api.materialize(v, fiber));
1546
1644
  }
1547
- fiber.keyedHoles = next;
1548
1645
  ops.placeChildren(fiber.root, nodes);
1549
1646
  };
1550
1647
  return {
@@ -1553,508 +1650,10 @@ const createPainter = (ops) => {
1553
1650
  disposeHoles: (fiber, opts) => disposeHoles(fiber, opts),
1554
1651
  insert: api.insert,
1555
1652
  materialize: api.materialize,
1556
- paintHole: api.paintHole
1653
+ paintHole: api.paintHole,
1654
+ pass
1557
1655
  };
1558
1656
  };
1559
1657
 
1560
1658
  //#endregion
1561
- //#region src/paint.ts
1562
- const noopDisconnect = () => {};
1563
- const core$1 = createPainter({
1564
- appendRoot: (root, node) => insertRendered(root, node, null),
1565
- asElement: (el) => el,
1566
- asNode: (host) => host,
1567
- asRoot: (el) => el,
1568
- bindEvents,
1569
- clearRoot: (root) => {
1570
- if (root instanceof Element) root.replaceChildren();
1571
- },
1572
- committed: committedElement,
1573
- createElement: (tag, ns) => ns ? document.createElementNS(ns, tag) : document.createElement(tag),
1574
- createRaw: (html, parent) => {
1575
- const range = document.createRange();
1576
- range.selectNodeContents(parent);
1577
- return [...range.createContextualFragment(html).childNodes];
1578
- },
1579
- createSlotHost: (slotId) => {
1580
- const host = document.createElement("span");
1581
- if (slotId !== null) host.setAttribute(SLOT_ATTR, slotId);
1582
- host.style.display = "contents";
1583
- return host;
1584
- },
1585
- createText: (text) => document.createTextNode(text),
1586
- disconnect: noopDisconnect,
1587
- placeChildren: (root, nodes) => {
1588
- if (root instanceof Element) placeChildren(root, nodes);
1589
- },
1590
- reuseNode: (host) => host instanceof Element && host.isConnected ? standInFor(host) : host,
1591
- setFormControl: (el, key, v) => {
1592
- const node = el;
1593
- if (key === "value") {
1594
- if (v === null || v === void 0) {
1595
- el.removeAttribute("value");
1596
- node.value = "";
1597
- } else {
1598
- el.setAttribute("value", String(v));
1599
- node.value = String(v);
1600
- }
1601
- } else if (key === "checked") {
1602
- node.checked = Boolean(v);
1603
- if (v) el.setAttribute("checked", "");
1604
- else el.removeAttribute("checked");
1605
- } else {
1606
- node.selected = Boolean(v);
1607
- if (v) el.setAttribute("selected", "");
1608
- else el.removeAttribute("selected");
1609
- }
1610
- },
1611
- setStyle: (el, css) => {
1612
- el.style.cssText = css;
1613
- }
1614
- });
1615
- const refreshAtomHost = (fiber, atom, host, hole) => {
1616
- if (hole.closed || !host.isConnected) return;
1617
- const next = unwrap(fiber.registry.get(atom));
1618
- if (next === KEEP) return;
1619
- core$1.paintHole(hole, next);
1620
- };
1621
- const refreshAllAtomHosts = (fiber) => {
1622
- for (const h of fiber.holes) if (h.atom && h.host && h.holeFiber) refreshAtomHost(fiber, h.atom, h.host, h.holeFiber);
1623
- };
1624
- const pruneDisconnectedAtomHoles = (fiber) => {
1625
- fiber.holes = fiber.holes.filter((h) => {
1626
- if (!h.atom || !h.host || !h.holeFiber) return true;
1627
- if (h.host.isConnected) return true;
1628
- h.dispose();
1629
- return false;
1630
- });
1631
- };
1632
- const canHydrate = (fiber, view) => {
1633
- if (!isVNode(view) || !isString(view.type)) return false;
1634
- const el = fiber.root.firstElementChild;
1635
- return !!el && el.tagName === view.type.toUpperCase();
1636
- };
1637
- const paintHydrate = (fiber, view) => {
1638
- const el = fiber.root.firstElementChild;
1639
- if (!el) return;
1640
- fiber.hydrate = false;
1641
- fiber.liveEl = el;
1642
- core$1.applyProps(el, view.props, fiber);
1643
- const childFiber = {
1644
- ...fiber,
1645
- holes: fiber.holes,
1646
- root: el
1647
- };
1648
- el.replaceChildren();
1649
- for (const c of view.children) for (const n of core$1.materialize(c, childFiber)) insertRendered(el, n, null);
1650
- };
1651
- const paintMorphLive = (fiber, view) => {
1652
- if (!fiber.liveEl) return;
1653
- core$1.disposeHoles(fiber, { morph: true });
1654
- core$1.applyProps(fiber.liveEl, view.props, fiber);
1655
- const liveNs = fiber.liveEl.namespaceURI;
1656
- const tmp = liveNs && liveNs !== "http://www.w3.org/1999/xhtml" ? document.createElementNS(liveNs, fiber.liveEl.localName) : document.createElement(fiber.liveEl.localName);
1657
- const childFiber = {
1658
- ...fiber,
1659
- holes: fiber.holes,
1660
- root: tmp
1661
- };
1662
- for (const c of view.children) for (const n of core$1.materialize(c, childFiber)) tmp.append(n);
1663
- morphInner(fiber.liveEl, tmp);
1664
- refreshAllAtomHosts(fiber);
1665
- pruneDisconnectedAtomHoles(fiber);
1666
- };
1667
- const paintRoot = (fiber, view) => {
1668
- fiber.islandFrame ??= {
1669
- i: 0,
1670
- slots: []
1671
- };
1672
- if (fiber.hydrate) {
1673
- if (canHydrate(fiber, view)) {
1674
- paintHydrate(fiber, view);
1675
- return;
1676
- }
1677
- console.warn("[ilha] hydrate mismatch, full mount");
1678
- fiber.hydrate = false;
1679
- if (fiber.root instanceof Element) fiber.root.replaceChildren();
1680
- }
1681
- if (fiber.liveEl && isVNode(view) && isString(view.type) && fiber.liveEl.localName === view.type) {
1682
- paintMorphLive(fiber, view);
1683
- return;
1684
- }
1685
- core$1.disposeHoles(fiber, { morph: true });
1686
- if (fiber.root instanceof Element && fiber.root.childNodes.length > 0) {
1687
- const tmp = document.createElement("div");
1688
- for (const n of core$1.materialize(view, fiber)) tmp.append(n);
1689
- morphInner(fiber.root, tmp);
1690
- const first = fiber.root.firstElementChild;
1691
- if (first) fiber.liveEl = first;
1692
- return;
1693
- }
1694
- core$1.insert(fiber, core$1.materialize(view, fiber));
1695
- const first = fiber.root instanceof Element ? fiber.root.firstElementChild : null;
1696
- if (first) fiber.liveEl = first;
1697
- };
1698
- const paint$1 = (fiber, view) => {
1699
- core$1.commit(() => {
1700
- paintRoot(fiber, view);
1701
- });
1702
- };
1703
- const paintError$1 = (fiber, e) => {
1704
- console.error(e);
1705
- paint$1(fiber, errorView(e));
1706
- };
1707
-
1708
- //#endregion
1709
- //#region src/snapshot.ts
1710
- const MAX_SNAPSHOT_CHARS = 262144;
1711
- const MAX_SNAPSHOT_DEPTH = 32;
1712
- const exceedsMaxDepth = (value, depth) => {
1713
- if (depth > MAX_SNAPSHOT_DEPTH) return true;
1714
- if (value === null || !isObject(value)) {
1715
- if (Array.isArray(value)) {
1716
- for (const item of value) if (exceedsMaxDepth(item, depth + 1)) return true;
1717
- }
1718
- return false;
1719
- }
1720
- const obj = value;
1721
- for (const key of Object.keys(obj)) if (exceedsMaxDepth(obj[key], depth + 1)) return true;
1722
- return false;
1723
- };
1724
- const deleteUnsafeKey = (obj, key) => {
1725
- if (Object.hasOwn(obj, key)) Reflect.deleteProperty(obj, key);
1726
- };
1727
- const stripUnsafeKeys = (value) => {
1728
- if (Array.isArray(value)) {
1729
- for (const item of value) stripUnsafeKeys(item);
1730
- return;
1731
- }
1732
- if (value === null || !isObject(value)) return;
1733
- const obj = value;
1734
- deleteUnsafeKey(obj, "__proto__");
1735
- deleteUnsafeKey(obj, "constructor");
1736
- deleteUnsafeKey(obj, "prototype");
1737
- for (const key of Object.getOwnPropertyNames(obj)) stripUnsafeKeys(obj[key]);
1738
- };
1739
- const safeParseSnapshot = (raw) => {
1740
- if (raw.length > MAX_SNAPSHOT_CHARS) return;
1741
- let parsed;
1742
- try {
1743
- parsed = JSON.parse(raw);
1744
- } catch {
1745
- return;
1746
- }
1747
- if (exceedsMaxDepth(parsed, 1)) return;
1748
- if (!isObject(parsed) || Array.isArray(parsed)) return;
1749
- stripUnsafeKeys(parsed);
1750
- return parsed;
1751
- };
1752
- const encodeSnapshot = (values) => JSON.stringify({ v: values });
1753
- const decodeSnapshot = (raw) => {
1754
- const values = safeParseSnapshot(raw)?.v;
1755
- return Array.isArray(values) ? values : void 0;
1756
- };
1757
-
1758
- //#endregion
1759
- //#region src/ssr-dom.ts
1760
- const VOID = /* @__PURE__ */ new Set([
1761
- "area",
1762
- "base",
1763
- "br",
1764
- "col",
1765
- "embed",
1766
- "hr",
1767
- "img",
1768
- "input",
1769
- "link",
1770
- "meta",
1771
- "param",
1772
- "source",
1773
- "track",
1774
- "wbr"
1775
- ]);
1776
- /** Tags whose children are raw text — do not HTML-escape content. */
1777
- const RAW_TEXT = /* @__PURE__ */ new Set(["script", "style"]);
1778
- const escapeText = (s) => s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1779
- const escapeAttr = (s) => escapeText(s).replaceAll("\"", "&quot;");
1780
- const createSsrRaw = (html) => ({
1781
- html,
1782
- kind: "raw"
1783
- });
1784
- const createSsrText = (text) => ({
1785
- kind: "text",
1786
- text
1787
- });
1788
- const serializeRawText = (tag, text) => {
1789
- if (new RegExp(`</${tag}\\b`, "iu").test(text)) throw new Error(`ilha: unsafe raw text in <${tag}> (contains "</${tag}")`);
1790
- return text;
1791
- };
1792
- const serializeNode = (node) => {
1793
- if (node.kind === "text") return escapeText(node.text);
1794
- if (node.kind === "raw") return node.html;
1795
- const parts = [];
1796
- for (const [name, value] of node.attrs) parts.push(`${name}="${escapeAttr(value)}"`);
1797
- const attr = parts.length > 0 ? ` ${parts.join(" ")}` : "";
1798
- const { tag } = node;
1799
- if (VOID.has(tag)) return `<${tag}${attr}>`;
1800
- if (RAW_TEXT.has(tag)) return `<${tag}${attr}>${serializeRawText(tag, node.textContent)}</${tag}>`;
1801
- return `<${tag}${attr}>${node.children.map(serializeNode).join("")}</${tag}>`;
1802
- };
1803
- const toDataAttr = (key) => `data-${key.replaceAll(/[A-Z]/gu, (ch) => `-${ch.toLowerCase()}`)}`;
1804
- const isSymbolKey = (value) => Object.prototype.toString.call(value) === "[object Symbol]";
1805
- const createSsrElement = (tag) => {
1806
- const lower = tag.toLowerCase();
1807
- const children = [];
1808
- const el = {
1809
- append: (...nodes) => {
1810
- children.push(...nodes);
1811
- },
1812
- appendChild: (node) => {
1813
- children.push(node);
1814
- },
1815
- attrs: /* @__PURE__ */ new Map(),
1816
- children,
1817
- dataset: new Proxy({}, {
1818
- get: (_t, prop) => {
1819
- if (prop === "undefined" || isSymbolKey(prop)) return;
1820
- return el.getAttribute(toDataAttr(prop)) ?? void 0;
1821
- },
1822
- set: (_t, prop, value) => {
1823
- if (isSymbolKey(prop)) return false;
1824
- const attr = toDataAttr(prop);
1825
- if (value === void 0 || value === null) el.removeAttribute(attr);
1826
- else el.setAttribute(attr, String(value));
1827
- return true;
1828
- }
1829
- }),
1830
- getAttribute: (name) => {
1831
- const value = el.attrs.get(name);
1832
- return value === void 0 ? null : value;
1833
- },
1834
- hasAttribute: (name) => el.attrs.has(name),
1835
- isConnected: true,
1836
- kind: "element",
1837
- localName: lower,
1838
- removeAttribute: (name) => {
1839
- el.attrs.delete(name);
1840
- },
1841
- replaceChildren: (...nodes) => {
1842
- children.length = 0;
1843
- children.push(...nodes);
1844
- },
1845
- setAttribute: (name, value) => {
1846
- el.attrs.set(name, value);
1847
- },
1848
- tag: lower,
1849
- tagName: lower.toUpperCase(),
1850
- get textContent() {
1851
- return children.map((n) => {
1852
- if (n.kind === "text") return n.text;
1853
- if (n.kind === "raw") return n.html;
1854
- return n.textContent;
1855
- }).join("");
1856
- },
1857
- set textContent(value) {
1858
- children.length = 0;
1859
- children.push(createSsrText(value));
1860
- }
1861
- };
1862
- return el;
1863
- };
1864
- const createSsrRoot = () => {
1865
- const children = [];
1866
- return {
1867
- append: (...nodes) => {
1868
- children.push(...nodes);
1869
- },
1870
- appendChild: (node) => {
1871
- children.push(node);
1872
- },
1873
- children,
1874
- get innerHTML() {
1875
- return children.map(serializeNode).join("");
1876
- },
1877
- kind: "root",
1878
- replaceChildren: () => {
1879
- children.length = 0;
1880
- }
1881
- };
1882
- };
1883
-
1884
- //#endregion
1885
- //#region src/ssr-paint.ts
1886
- const ssrAs = (n) => n;
1887
- const ACTION_CALL = Symbol.for("ilha.actionCall");
1888
- const defer$1 = () => {
1889
- const { promise, resolve } = Promise.withResolvers();
1890
- return {
1891
- promise,
1892
- resolve: () => {
1893
- resolve(null);
1894
- }
1895
- };
1896
- };
1897
- const bindSsrEvents = (el, props, fiber) => {
1898
- const seen = /* @__PURE__ */ new Set();
1899
- for (const key of Object.keys(props)) {
1900
- const type = eventTypeFromProp(key);
1901
- if (!type || seen.has(type)) continue;
1902
- seen.add(type);
1903
- const fn = props[key];
1904
- if (!isFunction(fn)) continue;
1905
- const branded = fn[ACTION_CALL];
1906
- const rec = branded?.k ? {
1907
- a: branded.a ?? [],
1908
- k: branded.k
1909
- } : void 0;
1910
- if (!rec) continue;
1911
- const id = `${type}:${fiber.runtime.ssrEventI}`;
1912
- fiber.runtime.ssrEventI += 1;
1913
- fiber.runtime.ssrActions[id] = rec;
1914
- const existing = el.dataset.ilhaOn;
1915
- el.dataset.ilhaOn = existing ? `${existing},${id}` : id;
1916
- }
1917
- };
1918
- const ssrOps = {
1919
- appendRoot: (root, node) => ssrAs(root).append(node),
1920
- asElement: (el) => ssrAs(el),
1921
- asNode: (host) => ssrAs(host),
1922
- asRoot: (el) => ssrAs(el),
1923
- bindEvents: bindSsrEvents,
1924
- clearRoot: (root) => ssrAs(root).replaceChildren(),
1925
- committed: (el) => el,
1926
- createElement: createSsrElement,
1927
- createRaw: (html, _parent) => [createSsrRaw(html)],
1928
- createSlotHost: (slotId) => {
1929
- const host = createSsrElement("span");
1930
- if (slotId !== null) host.setAttribute(SLOT_ATTR, slotId);
1931
- host.setAttribute("style", "display:contents");
1932
- return host;
1933
- },
1934
- createText: createSsrText,
1935
- disconnect: (el) => {
1936
- el.isConnected = false;
1937
- },
1938
- placeChildren: (root, nodes) => {
1939
- const host = ssrAs(root);
1940
- host.replaceChildren();
1941
- for (const n of nodes) host.append(n);
1942
- },
1943
- reuseNode: (host) => ssrAs(host),
1944
- setFormControl: (el, key, v) => {
1945
- if (key === "value") el.setAttribute("value", String(v ?? ""));
1946
- else if (v) el.setAttribute(key, "");
1947
- else el.removeAttribute(key);
1948
- },
1949
- setStyle: (el, css) => el.setAttribute("style", css)
1950
- };
1951
- const core = createPainter(ssrOps);
1952
- const paint = (fiber, view) => {
1953
- fiber.islandFrame ??= {
1954
- i: 0,
1955
- slots: []
1956
- };
1957
- core.disposeHoles(fiber);
1958
- ssrOps.clearRoot(fiber.root);
1959
- core.insert(fiber, core.materialize(view, fiber));
1960
- };
1961
- const paintError = (fiber, e) => {
1962
- console.error(e);
1963
- paint(fiber, errorView(e));
1964
- };
1965
- const attachSsr = (fn, opts) => {
1966
- const root = createSsrRoot();
1967
- const runtime = makeRuntime({
1968
- ssr: true,
1969
- ssrCapture: opts?.ssrCapture === true
1970
- });
1971
- const { promise: ready, resolve } = defer$1();
1972
- const fiber = makeFiber(runtime, ssrAs(root), paint, { onFail: (e) => {
1973
- opts?.onError?.(e);
1974
- paintError(fiber, e);
1975
- resolve();
1976
- } });
1977
- runtime.begin();
1978
- runtime.setIdle(resolve);
1979
- runSetup(fiber, fn);
1980
- runtime.end();
1981
- return {
1982
- ready,
1983
- root,
1984
- runtime,
1985
- unmount: () => {
1986
- closeFiber(fiber);
1987
- runtime.close();
1988
- root.replaceChildren();
1989
- }
1990
- };
1991
- };
1992
-
1993
- //#endregion
1994
- //#region src/mount.ts
1995
- const defer = () => {
1996
- const { promise, resolve } = Promise.withResolvers();
1997
- return {
1998
- promise,
1999
- resolve: () => {
2000
- resolve(null);
2001
- }
2002
- };
2003
- };
2004
- const readHydrate = (el) => {
2005
- const raw = (el.matches("[data-ilha]") ? el : el.querySelector("[data-ilha]"))?.dataset.ilhaState;
2006
- if (raw) return decodeSnapshot(raw);
2007
- const first = el.firstElementChild;
2008
- if (first?.tagName === "TEMPLATE" && first.matches("[data-ilha-state]")) {
2009
- const tplRaw = first.dataset.ilhaState;
2010
- first.remove();
2011
- return tplRaw ? decodeSnapshot(tplRaw) : void 0;
2012
- }
2013
- };
2014
- const delay = (ms) => {
2015
- const { promise, resolve } = defer();
2016
- setTimeout(resolve, ms);
2017
- return promise;
2018
- };
2019
- const attach = (el, fn, opts) => {
2020
- if (!opts?.hydrate) el.innerHTML = "";
2021
- const runtime = makeRuntime({ hydrate: opts?.hydrate ? readHydrate(el) : void 0 });
2022
- const { promise: ready, resolve } = defer();
2023
- const fiber = makeFiber(runtime, el, paint$1, { onFail: (e) => {
2024
- opts?.onError?.(e);
2025
- paintError$1(fiber, e);
2026
- resolve();
2027
- } });
2028
- if (opts?.hydrate) fiber.hydrate = true;
2029
- runtime.begin();
2030
- runtime.setIdle(resolve);
2031
- runSetup(fiber, fn);
2032
- runtime.end();
2033
- return {
2034
- ready,
2035
- runtime,
2036
- unmount: () => {
2037
- closeFiber(fiber);
2038
- runtime.close();
2039
- if (!opts?.hydrate) el.innerHTML = "";
2040
- }
2041
- };
2042
- };
2043
- const mount = (el, fn, opts) => attach(el, fn, opts).unmount;
2044
- const renderToString = async (fn, opts) => {
2045
- const snapshot = opts?.snapshot !== false;
2046
- const markers = opts?.markers !== false;
2047
- const { unmount, ready, runtime, root } = attachSsr(fn, { ssrCapture: opts?.captureActions === true });
2048
- const timeout = opts?.timeout;
2049
- await (timeout === void 0 ? ready : Promise.race([ready, delay(timeout)]));
2050
- let inner = root.innerHTML;
2051
- const actions = runtime.ssrActions;
2052
- if (Object.keys(actions).length > 0) inner = `<template data-ilha-actions="${escapeAttr(JSON.stringify(actions))}"></template>${inner}`;
2053
- const snap = snapshot ? encodeSnapshot(runtime.ssrValues) : void 0;
2054
- unmount();
2055
- if (!markers) return snap ? `<template data-ilha-state="${escapeAttr(snap)}"></template>${inner}` : inner;
2056
- return `<div data-ilha${snap ? ` data-ilha-state="${escapeAttr(snap)}"` : ""}>${inner}</div>`;
2057
- };
2058
-
2059
- //#endregion
2060
- export { makeFiber as _, interpret as a, handleOwner as c, structuralEqual as d, untrack as f, getFiber as g, closeFiber as h, runSetup as i, instr as l, clearFiberView as m, renderToString as n, atom as o, wrapHandle as p, unsafe as r, batch as s, mount as t, isAtomHandle as u };
1659
+ export { makeFiber as C, getFiber as S, makeRuntime as T, structuralEqual as _, morphInner as a, clearFiberView as b, bindEvents as c, interpret as d, atom as f, isAtomHandle as g, instr as h, insertRendered as i, eventTypeFromProp as l, handleOwner as m, unsafe as n, placeChildren as o, batch as p, committedElement as r, standInFor as s, createPainter as t, runSetup as u, untrack as v, makeRootFiber as w, closeFiber as x, wrapHandle as y };