ilha 0.14.1 → 0.14.3

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.
@@ -163,6 +163,22 @@ function beginPrimitiveFrame(fiber) {
163
163
  if (used > 0 && used !== slots) console.warn(`ilha: atom/watch slot count changed between renders (${slots} → ${used}); keep primitive order stable`);
164
164
  fiber.primitiveI = 0;
165
165
  fiber.watchI = 0;
166
+ const islandFrame = fiber.islandFrame ??= {
167
+ i: 0,
168
+ slots: []
169
+ };
170
+ const usedIslands = islandFrame.i;
171
+ if (usedIslands < islandFrame.slots.length) {
172
+ for (let j = usedIslands; j < islandFrame.slots.length; j++) {
173
+ const slot = islandFrame.slots[j];
174
+ if (slot) {
175
+ slot.unmount?.();
176
+ islandFrame.slots[j] = void 0;
177
+ }
178
+ }
179
+ islandFrame.slots.length = usedIslands;
180
+ }
181
+ islandFrame.i = 0;
166
182
  }
167
183
  function readTracked(atom, registry) {
168
184
  const frame = trackStack.at(-1);
@@ -417,12 +433,12 @@ function bindEvents(node, props, fiber) {
417
433
  //#endregion
418
434
  //#region src/morph.ts
419
435
  const PRESERVE_ATTR = "data-morph-preserve";
420
- const KEY_ATTR = "data-ilha-key";
421
- const SLOT_ATTR = "data-ilha-slot";
436
+ const KEY_ATTR$1 = "data-ilha-key";
437
+ const SLOT_ATTR$1 = "data-ilha-slot";
422
438
  function morphKeyOf(el) {
423
- const k = el.getAttribute(KEY_ATTR);
439
+ const k = el.getAttribute(KEY_ATTR$1);
424
440
  if (k !== null) return `k:${k}`;
425
- const s = el.getAttribute(SLOT_ATTR);
441
+ const s = el.getAttribute(SLOT_ATTR$1);
426
442
  return s === null ? null : `s:${s}`;
427
443
  }
428
444
  function shouldPreserveMorphAttr(el, name) {
@@ -529,7 +545,7 @@ function morphChildren(fromParent, toParent) {
529
545
  fromParent.replaceChild(toEl, fromEl);
530
546
  continue;
531
547
  }
532
- const slotId = toEl.getAttribute(SLOT_ATTR);
548
+ const slotId = toEl.getAttribute(SLOT_ATTR$1);
533
549
  if (slotId !== null && fromEl.getAttribute("data-ilha-slot") === slotId) continue;
534
550
  if (fromEl.localName === "input") {
535
551
  const hadChecked = fromEl.hasAttribute("checked");
@@ -701,13 +717,13 @@ function runSetup(fiber, fn) {
701
717
 
702
718
  //#endregion
703
719
  //#region src/paint.ts
704
- const ISLAND = Symbol.for("ilha.island");
705
- const ISLAND_MOUNT_INTERNAL = Symbol.for("ilha.islandMountInternal");
706
- const ISLAND_SLOT_TAG = Symbol.for("ilha.islandSlotTag");
707
- function insert(fiber, nodes) {
720
+ const ISLAND$1 = Symbol.for("ilha.island");
721
+ const ISLAND_MOUNT_INTERNAL$1 = Symbol.for("ilha.islandMountInternal");
722
+ const ISLAND_SLOT_TAG$1 = Symbol.for("ilha.islandSlotTag");
723
+ function insert$1(fiber, nodes) {
708
724
  for (const n of nodes) fiber.root.appendChild(n);
709
725
  }
710
- function disposeHoles(fiber, opts) {
726
+ function disposeHoles$1(fiber, opts) {
711
727
  const keep = [];
712
728
  for (const h of fiber.holes) {
713
729
  if (opts?.morph && h.keepOnMorph) {
@@ -718,10 +734,10 @@ function disposeHoles(fiber, opts) {
718
734
  }
719
735
  fiber.holes = keep;
720
736
  }
721
- function skip(x) {
737
+ function skip$1(x) {
722
738
  return x === null || x === void 0 || typeof x === "boolean";
723
739
  }
724
- function applyProps(el, props, fiber) {
740
+ function applyProps$1(el, props, fiber) {
725
741
  for (const [k, v] of Object.entries(props)) {
726
742
  if (k === "children" || k === "key" || k === "ref") continue;
727
743
  if (k === "className") {
@@ -776,10 +792,10 @@ function applyProps(el, props, fiber) {
776
792
  fiber.holes.push({ dispose: () => ref(null) });
777
793
  }
778
794
  }
779
- const KEEP = Symbol("keep");
780
- function unwrap(value, keep) {
795
+ const KEEP$1 = Symbol("keep");
796
+ function unwrap$1(value, keep) {
781
797
  if (AsyncResult.isAsyncResult(value)) {
782
- if (AsyncResult.isWaiting(value) && keep !== void 0) return KEEP;
798
+ if (AsyncResult.isWaiting(value) && keep !== void 0) return KEEP$1;
783
799
  if (AsyncResult.isFailure(value) && !AsyncResult.isWaiting(value)) return String(AsyncResult.error(value) ?? "error");
784
800
  const v = AsyncResult.value(value);
785
801
  if (v._tag === "Some") return v.value;
@@ -787,18 +803,23 @@ function unwrap(value, keep) {
787
803
  }
788
804
  return value;
789
805
  }
790
- function paintHole(fiber, view) {
791
- if (view === KEEP) return;
806
+ function paintHole$1(fiber, view) {
807
+ if (view === KEEP$1) return;
808
+ const frame = fiber.islandFrame ??= {
809
+ i: 0,
810
+ slots: []
811
+ };
812
+ frame.i = 0;
792
813
  const list = Array.isArray(view) ? view : null;
793
814
  if (list && list.length > 0 && list.every((v) => isVNode(v) && v.key != null)) {
794
- keyedPaintHole(fiber, list);
815
+ keyedPaintHole$1(fiber, list);
795
816
  return;
796
817
  }
797
- disposeHoles(fiber);
818
+ disposeHoles$1(fiber);
798
819
  if (fiber.root instanceof Element) fiber.root.replaceChildren();
799
- insert(fiber, materialize(view, fiber));
820
+ insert$1(fiber, materialize$1(view, fiber));
800
821
  }
801
- function keyedPaintHole(fiber, views) {
822
+ function keyedPaintHole$1(fiber, views) {
802
823
  const prev = fiber.keyedHoles ?? /* @__PURE__ */ new Map();
803
824
  const keep = new Set(views.map((v) => String(v.key)));
804
825
  for (const [k, h] of prev) if (!keep.has(k)) closeFiber(h);
@@ -812,34 +833,34 @@ function keyedPaintHole(fiber, views) {
812
833
  nodes.push(reuse.root);
813
834
  continue;
814
835
  }
815
- const made = materialize(v, fiber);
836
+ const made = materialize$1(v, fiber);
816
837
  const hole = fiber.keyedHoles?.get(k);
817
838
  if (hole) next.set(k, hole);
818
839
  nodes.push(...made);
819
840
  }
820
841
  fiber.keyedHoles = next;
821
842
  if (fiber.root instanceof Element) fiber.root.replaceChildren();
822
- insert(fiber, nodes);
843
+ insert$1(fiber, nodes);
823
844
  }
824
- function openHole(parent) {
845
+ function openHole$1(parent) {
825
846
  const id = parent.runtime.nextHole();
826
847
  const host = document.createElement("span");
827
- host.setAttribute(SLOT_ATTR, String(id));
848
+ host.setAttribute(SLOT_ATTR$1, String(id));
828
849
  host.style.display = "contents";
829
850
  const child = makeFiber(parent.runtime, host, (f, v) => {
830
- disposeHoles(f);
851
+ disposeHoles$1(f);
831
852
  if (f.root instanceof Element) f.root.replaceChildren();
832
- insert(f, materialize(v, f));
853
+ insert$1(f, materialize$1(v, f));
833
854
  }, { onFail: (e) => {
834
855
  console.error(e);
835
- paintHole(child, errorView(e));
856
+ paintHole$1(child, errorView$1(e));
836
857
  } });
837
858
  return {
838
859
  fiber: child,
839
860
  nodes: [host]
840
861
  };
841
862
  }
842
- function errorView(e) {
863
+ function errorView$1(e) {
843
864
  return {
844
865
  $$ilha: 1,
845
866
  type: "pre",
@@ -847,7 +868,7 @@ function errorView(e) {
847
868
  children: [failureMessage(e)]
848
869
  };
849
870
  }
850
- function findReusableAtomHost(fiber, atom) {
871
+ function findReusableAtomHost$1(fiber, atom) {
851
872
  for (const h of fiber.holes) if (h.atom === atom && h.host?.isConnected && h.holeFiber && !h.holeFiber.closed) return {
852
873
  host: h.host,
853
874
  hole: h.holeFiber
@@ -855,9 +876,9 @@ function findReusableAtomHost(fiber, atom) {
855
876
  }
856
877
  function refreshAtomHost(fiber, atom, host, hole) {
857
878
  if (hole.closed || !host.isConnected) return;
858
- const next = unwrap(fiber.registry.get(atom), void 0);
859
- if (next === KEEP) return;
860
- paintHole(hole, next);
879
+ const next = unwrap$1(fiber.registry.get(atom), void 0);
880
+ if (next === KEEP$1) return;
881
+ paintHole$1(hole, next);
861
882
  }
862
883
  function refreshAllAtomHosts(fiber) {
863
884
  for (const h of fiber.holes) if (h.atom && h.host && h.holeFiber) refreshAtomHost(fiber, h.atom, h.host, h.holeFiber);
@@ -870,14 +891,14 @@ function pruneDisconnectedAtomHoles(fiber) {
870
891
  return false;
871
892
  });
872
893
  }
873
- function atomHostPlaceholder(host) {
894
+ function atomHostPlaceholder$1(host) {
874
895
  const placeholder = document.createElement("span");
875
- const slot = host.getAttribute(SLOT_ATTR);
876
- if (slot !== null) placeholder.setAttribute(SLOT_ATTR, slot);
896
+ const slot = host.getAttribute(SLOT_ATTR$1);
897
+ if (slot !== null) placeholder.setAttribute(SLOT_ATTR$1, slot);
877
898
  placeholder.style.display = "contents";
878
899
  return placeholder;
879
900
  }
880
- function trackHole(parent, hole, extra, meta) {
901
+ function trackHole$1(parent, hole, extra, meta) {
881
902
  parent.holes.push({
882
903
  keepOnMorph: !!(meta?.atom && meta?.host) || !!meta?.keyed,
883
904
  atom: meta?.atom,
@@ -889,13 +910,13 @@ function trackHole(parent, hole, extra, meta) {
889
910
  }
890
911
  });
891
912
  }
892
- function materialize(view, fiber) {
893
- if (skip(view)) return [];
913
+ function materialize$1(view, fiber) {
914
+ if (skip$1(view)) return [];
894
915
  if (typeof view === "string" || typeof view === "number" || typeof view === "bigint") return [document.createTextNode(String(view))];
895
916
  if (isAtomHandle(view)) {
896
- const reused = findReusableAtomHost(fiber, view.atom);
897
- if (reused) return [atomHostPlaceholder(reused.host)];
898
- const { fiber: hole, nodes } = openHole(fiber);
917
+ const reused = findReusableAtomHost$1(fiber, view.atom);
918
+ if (reused) return [atomHostPlaceholder$1(reused.host)];
919
+ const { fiber: hole, nodes } = openHole$1(fiber);
899
920
  const host = nodes[0];
900
921
  let unsub = () => {};
901
922
  fiber.runtime.later(() => {
@@ -903,53 +924,77 @@ function materialize(view, fiber) {
903
924
  let keep;
904
925
  unsub = fiber.registry.subscribe(view.atom, (v) => {
905
926
  if (hole.closed) return;
906
- const next = unwrap(v, keep);
907
- if (next === KEEP) return;
927
+ const next = unwrap$1(v, keep);
928
+ if (next === KEEP$1) return;
908
929
  keep = next;
909
- paintHole(hole, next);
930
+ paintHole$1(hole, next);
910
931
  }, { immediate: true });
911
932
  });
912
- trackHole(fiber, hole, () => unsub(), {
933
+ trackHole$1(fiber, hole, () => unsub(), {
913
934
  atom: view.atom,
914
935
  host
915
936
  });
916
937
  return nodes;
917
938
  }
918
939
  if (Stream.isStream(view)) {
919
- const { fiber: hole, nodes } = openHole(fiber);
940
+ const { fiber: hole, nodes } = openHole$1(fiber);
920
941
  const src = fiber.runtime.ssr ? view.pipe(Stream.take(1)) : view;
921
942
  hole.run(Stream.runForEach(src, (v) => Effect.sync(() => {
922
- if (!hole.closed) paintHole(hole, v);
943
+ if (!hole.closed) paintHole$1(hole, v);
923
944
  })).pipe(Effect.catch((e) => Effect.sync(() => {
924
- if (!hole.closed) paintHole(hole, String(e));
945
+ if (!hole.closed) paintHole$1(hole, String(e));
925
946
  }))), fiber.runtime.ssr ? () => {} : void 0);
926
- trackHole(fiber, hole);
947
+ trackHole$1(fiber, hole);
927
948
  return nodes;
928
949
  }
929
950
  if (isSetupFn(view)) {
930
- const { fiber: hole, nodes } = openHole(fiber);
951
+ const { fiber: hole, nodes } = openHole$1(fiber);
931
952
  fiber.runtime.later(() => {
932
953
  if (!hole.closed) runSetup(hole, view);
933
954
  });
934
- trackHole(fiber, hole);
955
+ trackHole$1(fiber, hole);
935
956
  return nodes;
936
957
  }
937
958
  if (isVNode(view)) {
938
- if (view.type === Fragment) return view.children.flatMap((c) => materialize(c, fiber));
959
+ if (view.type === Fragment) return view.children.flatMap((c) => materialize$1(c, fiber));
939
960
  if (typeof view.type === "function") {
940
961
  const props = {
941
962
  ...view.props,
942
963
  children: view.children
943
964
  };
944
965
  const type = view.type;
945
- if (type[ISLAND] === true) {
946
- const tag = typeof type[ISLAND_SLOT_TAG] === "string" ? type[ISLAND_SLOT_TAG] : "div";
966
+ if (type[ISLAND$1] === true) {
967
+ const frame = fiber.islandFrame ??= {
968
+ i: 0,
969
+ slots: []
970
+ };
971
+ const i = frame.i++;
972
+ const existing = frame.slots[i];
973
+ if (existing && existing.type === type && existing.host.isConnected && typeof existing.updateProps === "function") {
974
+ existing.updateProps(props);
975
+ return [existing.host];
976
+ }
977
+ existing?.unmount?.();
978
+ const tag = typeof type[ISLAND_SLOT_TAG$1] === "string" ? type[ISLAND_SLOT_TAG$1] : "div";
947
979
  const el = document.createElement(tag);
948
980
  el.setAttribute("data-ilha", "");
949
- const mountFn = type[ISLAND_MOUNT_INTERNAL];
981
+ const mountFn = type[ISLAND_MOUNT_INTERNAL$1];
950
982
  if (typeof mountFn === "function") {
951
983
  const handle = mountFn(el, props);
952
- fiber.holes.push({ dispose: () => handle?.unmount?.() });
984
+ const slot = {
985
+ type,
986
+ host: el,
987
+ updateProps: handle?.updateProps,
988
+ unmount: handle?.unmount
989
+ };
990
+ frame.slots[i] = slot;
991
+ fiber.holes.push({
992
+ keepOnMorph: true,
993
+ dispose: () => {
994
+ handle?.unmount?.();
995
+ if (frame.slots[i] === slot) frame.slots[i] = void 0;
996
+ }
997
+ });
953
998
  }
954
999
  return [el];
955
1000
  }
@@ -961,7 +1006,7 @@ function materialize(view, fiber) {
961
1006
  if (next && typeof next.then !== "function" && !isSetupFn(next)) reuse.paint(next);
962
1007
  return [reuse.root];
963
1008
  }
964
- const { fiber: hole, nodes } = openHole(fiber);
1009
+ const { fiber: hole, nodes } = openHole$1(fiber);
965
1010
  hole.propsBox = { current: props };
966
1011
  if (k) {
967
1012
  fiber.keyedHoles ??= /* @__PURE__ */ new Map();
@@ -970,12 +1015,12 @@ function materialize(view, fiber) {
970
1015
  fiber.runtime.later(() => {
971
1016
  if (!hole.closed) runSetup(hole, () => type(hole.propsBox.current));
972
1017
  });
973
- trackHole(fiber, hole, void 0, k ? { keyed: true } : void 0);
1018
+ trackHole$1(fiber, hole, void 0, k ? { keyed: true } : void 0);
974
1019
  return nodes;
975
1020
  }
976
1021
  const el = document.createElement(view.type);
977
- if (view.key != null) el.setAttribute(KEY_ATTR, String(view.key));
978
- applyProps(el, view.props, fiber);
1022
+ if (view.key != null) el.setAttribute(KEY_ATTR$1, String(view.key));
1023
+ applyProps$1(el, view.props, fiber);
979
1024
  fiber.keyedHoles ??= /* @__PURE__ */ new Map();
980
1025
  const childFiber = {
981
1026
  ...fiber,
@@ -983,10 +1028,10 @@ function materialize(view, fiber) {
983
1028
  holes: fiber.holes,
984
1029
  keyedHoles: fiber.keyedHoles
985
1030
  };
986
- for (const c of view.children) for (const n of materialize(c, childFiber)) el.appendChild(n);
1031
+ for (const c of view.children) for (const n of materialize$1(c, childFiber)) el.appendChild(n);
987
1032
  return [el];
988
1033
  }
989
- if (Array.isArray(view) || view && typeof view === "object" && Symbol.iterator in Object(view)) return [...view].flatMap((c) => materialize(c, fiber));
1034
+ if (Array.isArray(view) || view && typeof view === "object" && Symbol.iterator in Object(view)) return [...view].flatMap((c) => materialize$1(c, fiber));
990
1035
  return [document.createTextNode(String(view))];
991
1036
  }
992
1037
  function canHydrate(fiber, view) {
@@ -994,20 +1039,24 @@ function canHydrate(fiber, view) {
994
1039
  const el = fiber.root.firstElementChild;
995
1040
  return !!el && el.tagName === view.type.toUpperCase();
996
1041
  }
997
- function paint(fiber, view) {
1042
+ function paint$1(fiber, view) {
1043
+ fiber.islandFrame ??= {
1044
+ i: 0,
1045
+ slots: []
1046
+ };
998
1047
  if (fiber.hydrate) {
999
1048
  if (canHydrate(fiber, view)) {
1000
1049
  const el = fiber.root.firstElementChild;
1001
1050
  fiber.hydrate = false;
1002
1051
  fiber.liveEl = el;
1003
- applyProps(el, view.props, fiber);
1052
+ applyProps$1(el, view.props, fiber);
1004
1053
  const childFiber = {
1005
1054
  ...fiber,
1006
1055
  root: el,
1007
1056
  holes: fiber.holes
1008
1057
  };
1009
1058
  el.replaceChildren();
1010
- for (const c of view.children) for (const n of materialize(c, childFiber)) el.appendChild(n);
1059
+ for (const c of view.children) for (const n of materialize$1(c, childFiber)) el.appendChild(n);
1011
1060
  return;
1012
1061
  } else {
1013
1062
  console.warn("[ilha] hydrate mismatch, full mount");
@@ -1016,31 +1065,31 @@ function paint(fiber, view) {
1016
1065
  }
1017
1066
  }
1018
1067
  if (fiber.liveEl && isVNode(view) && typeof view.type === "string" && fiber.liveEl.localName === view.type) {
1019
- disposeHoles(fiber, { morph: true });
1020
- applyProps(fiber.liveEl, view.props, fiber);
1068
+ disposeHoles$1(fiber, { morph: true });
1069
+ applyProps$1(fiber.liveEl, view.props, fiber);
1021
1070
  const tmp = document.createElement(fiber.liveEl.localName);
1022
- for (const c of view.children) for (const n of materialize(c, fiber)) tmp.appendChild(n);
1071
+ for (const c of view.children) for (const n of materialize$1(c, fiber)) tmp.appendChild(n);
1023
1072
  morphInner(fiber.liveEl, tmp);
1024
1073
  refreshAllAtomHosts(fiber);
1025
1074
  pruneDisconnectedAtomHoles(fiber);
1026
1075
  return;
1027
1076
  }
1028
- disposeHoles(fiber);
1077
+ disposeHoles$1(fiber);
1029
1078
  if (fiber.root instanceof Element && fiber.root.childNodes.length > 0) {
1030
1079
  const tmp = document.createElement("div");
1031
- for (const n of materialize(view, fiber)) tmp.appendChild(n);
1080
+ for (const n of materialize$1(view, fiber)) tmp.appendChild(n);
1032
1081
  morphInner(fiber.root, tmp);
1033
1082
  const first = fiber.root.firstElementChild;
1034
1083
  if (first) fiber.liveEl = first;
1035
1084
  return;
1036
1085
  }
1037
- insert(fiber, materialize(view, fiber));
1086
+ insert$1(fiber, materialize$1(view, fiber));
1038
1087
  const first = fiber.root instanceof Element ? fiber.root.firstElementChild : null;
1039
1088
  if (first) fiber.liveEl = first;
1040
1089
  }
1041
- function paintError(fiber, e) {
1090
+ function paintError$1(fiber, e) {
1042
1091
  console.error(e);
1043
- paint(fiber, errorView(e));
1092
+ paint$1(fiber, errorView$1(e));
1044
1093
  }
1045
1094
 
1046
1095
  //#endregion
@@ -1093,10 +1142,466 @@ function decodeSnapshot(raw) {
1093
1142
  }
1094
1143
 
1095
1144
  //#endregion
1096
- //#region src/mount.ts
1145
+ //#region src/ssr-dom.ts
1146
+ const VOID = /* @__PURE__ */ new Set([
1147
+ "area",
1148
+ "base",
1149
+ "br",
1150
+ "col",
1151
+ "embed",
1152
+ "hr",
1153
+ "img",
1154
+ "input",
1155
+ "link",
1156
+ "meta",
1157
+ "param",
1158
+ "source",
1159
+ "track",
1160
+ "wbr"
1161
+ ]);
1162
+ /** Tags whose children are raw text — do not HTML-escape content. */
1163
+ const RAW_TEXT = /* @__PURE__ */ new Set(["script", "style"]);
1164
+ function escapeText(s) {
1165
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1166
+ }
1097
1167
  function escapeAttr(s) {
1098
- return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1168
+ return escapeText(s).replace(/"/g, "&quot;");
1169
+ }
1170
+ function createSsrText(text) {
1171
+ return {
1172
+ kind: "text",
1173
+ text
1174
+ };
1175
+ }
1176
+ function createSsrElement(tag) {
1177
+ const lower = tag.toLowerCase();
1178
+ const children = [];
1179
+ const el = {
1180
+ kind: "element",
1181
+ tag: lower,
1182
+ attrs: /* @__PURE__ */ new Map(),
1183
+ children,
1184
+ isConnected: true,
1185
+ tagName: lower.toUpperCase(),
1186
+ localName: lower,
1187
+ setAttribute(name, value) {
1188
+ el.attrs.set(name, value);
1189
+ },
1190
+ getAttribute(name) {
1191
+ return el.attrs.has(name) ? el.attrs.get(name) : null;
1192
+ },
1193
+ removeAttribute(name) {
1194
+ el.attrs.delete(name);
1195
+ },
1196
+ hasAttribute(name) {
1197
+ return el.attrs.has(name);
1198
+ },
1199
+ replaceChildren(...nodes) {
1200
+ children.length = 0;
1201
+ children.push(...nodes);
1202
+ },
1203
+ appendChild(node) {
1204
+ children.push(node);
1205
+ },
1206
+ get textContent() {
1207
+ return children.map((n) => n.kind === "text" ? n.text : n.textContent).join("");
1208
+ },
1209
+ set textContent(value) {
1210
+ children.length = 0;
1211
+ children.push(createSsrText(value));
1212
+ }
1213
+ };
1214
+ return el;
1215
+ }
1216
+ function createSsrRoot() {
1217
+ const children = [];
1218
+ return {
1219
+ kind: "root",
1220
+ children,
1221
+ appendChild(node) {
1222
+ children.push(node);
1223
+ },
1224
+ replaceChildren() {
1225
+ children.length = 0;
1226
+ },
1227
+ get innerHTML() {
1228
+ return children.map(serializeNode).join("");
1229
+ }
1230
+ };
1231
+ }
1232
+ function serializeRawText(tag, text) {
1233
+ if (new RegExp(`</${tag}\\b`, "i").test(text)) throw new Error(`ilha: unsafe raw text in <${tag}> (contains "</${tag}")`);
1234
+ return text;
1235
+ }
1236
+ function serializeNode(node) {
1237
+ if (node.kind === "text") return escapeText(node.text);
1238
+ const parts = [];
1239
+ for (const [name, value] of node.attrs) parts.push(`${name}="${escapeAttr(value)}"`);
1240
+ const attr = parts.length > 0 ? ` ${parts.join(" ")}` : "";
1241
+ const tag = node.tag;
1242
+ if (VOID.has(tag)) return `<${tag}${attr}>`;
1243
+ if (RAW_TEXT.has(tag)) return `<${tag}${attr}>${serializeRawText(tag, node.textContent)}</${tag}>`;
1244
+ return `<${tag}${attr}>${node.children.map(serializeNode).join("")}</${tag}>`;
1245
+ }
1246
+
1247
+ //#endregion
1248
+ //#region src/ssr-paint.ts
1249
+ const KEY_ATTR = "data-ilha-key";
1250
+ const SLOT_ATTR = "data-ilha-slot";
1251
+ const ISLAND = Symbol.for("ilha.island");
1252
+ const ISLAND_MOUNT_INTERNAL = Symbol.for("ilha.islandMountInternal");
1253
+ const ISLAND_SLOT_TAG = Symbol.for("ilha.islandSlotTag");
1254
+ function insert(fiber, nodes) {
1255
+ const root = fiber.root;
1256
+ for (const n of nodes) root.appendChild(n);
1257
+ }
1258
+ function disposeHoles(fiber) {
1259
+ for (const h of fiber.holes) h.dispose();
1260
+ fiber.holes = [];
1261
+ }
1262
+ function skip(x) {
1263
+ return x === null || x === void 0 || typeof x === "boolean";
1264
+ }
1265
+ function bindSsrEvents(el, props, fiber) {
1266
+ const seen = /* @__PURE__ */ new Set();
1267
+ for (const key of Object.keys(props)) {
1268
+ const type = eventTypeFromProp(key);
1269
+ if (!type || seen.has(type)) continue;
1270
+ seen.add(type);
1271
+ const fn = props[key];
1272
+ if (typeof fn !== "function") continue;
1273
+ const branded = fn[Symbol.for("ilha.actionCall")];
1274
+ const rec = branded?.k ? {
1275
+ k: branded.k,
1276
+ a: branded.a ?? []
1277
+ } : void 0;
1278
+ if (!rec) continue;
1279
+ const id = `${type}:${fiber.runtime.ssrEventI++}`;
1280
+ fiber.runtime.ssrActions[id] = rec;
1281
+ const existing = el.getAttribute("data-ilha-on");
1282
+ el.setAttribute("data-ilha-on", existing ? `${existing},${id}` : id);
1283
+ }
1284
+ }
1285
+ function applyProps(el, props, fiber) {
1286
+ for (const [k, v] of Object.entries(props)) {
1287
+ if (k === "children" || k === "key" || k === "ref") continue;
1288
+ if (k === "className") {
1289
+ el.setAttribute("class", String(v ?? ""));
1290
+ continue;
1291
+ }
1292
+ if (k === "htmlFor") {
1293
+ el.setAttribute("for", String(v ?? ""));
1294
+ continue;
1295
+ }
1296
+ if (k.toLowerCase() === "srcdoc") continue;
1297
+ if (k === "style" && v != null && v !== false) {
1298
+ const css = typeof v === "string" || typeof v === "object" ? serializeStyleAttr(v) : "";
1299
+ if (css) el.setAttribute("style", css);
1300
+ continue;
1301
+ }
1302
+ if (isEventProp(k)) continue;
1303
+ if (/^on[A-Z]/.test(k)) continue;
1304
+ if (k === "value" || k === "checked" || k === "selected") {
1305
+ const setProp = (x) => {
1306
+ if (k === "value") el.setAttribute("value", String(x ?? ""));
1307
+ else if (k === "checked") {
1308
+ if (x) el.setAttribute("checked", "");
1309
+ else el.removeAttribute("checked");
1310
+ } else if (x) el.setAttribute("selected", "");
1311
+ else el.removeAttribute("selected");
1312
+ };
1313
+ if (isAtomHandle(v)) {
1314
+ const unsub = fiber.registry.subscribe(v.atom, setProp, { immediate: true });
1315
+ fiber.holes.push({ dispose: unsub });
1316
+ } else setProp(v);
1317
+ continue;
1318
+ }
1319
+ if (typeof v === "function") continue;
1320
+ if (v === false || v === null || v === void 0) el.removeAttribute(k);
1321
+ else {
1322
+ const str = v === true ? "" : String(v);
1323
+ if (isUrlAttributeName(k) && !isSafeUrlAttrValue(el.tagName, k, str)) continue;
1324
+ el.setAttribute(k, str);
1325
+ }
1326
+ }
1327
+ bindSsrEvents(el, props, fiber);
1328
+ const ref = props.ref;
1329
+ if (typeof ref === "function") {
1330
+ ref(el);
1331
+ fiber.holes.push({ dispose: () => ref(null) });
1332
+ }
1333
+ }
1334
+ const KEEP = Symbol("keep");
1335
+ function unwrap(value, keep) {
1336
+ if (AsyncResult.isAsyncResult(value)) {
1337
+ if (AsyncResult.isWaiting(value) && keep !== void 0) return KEEP;
1338
+ if (AsyncResult.isFailure(value) && !AsyncResult.isWaiting(value)) return String(AsyncResult.error(value) ?? "error");
1339
+ const v = AsyncResult.value(value);
1340
+ if (v._tag === "Some") return v.value;
1341
+ return "";
1342
+ }
1343
+ return value;
1344
+ }
1345
+ function paintHole(fiber, view) {
1346
+ if (view === KEEP) return;
1347
+ const frame = fiber.islandFrame ??= {
1348
+ i: 0,
1349
+ slots: []
1350
+ };
1351
+ frame.i = 0;
1352
+ const list = Array.isArray(view) ? view : null;
1353
+ if (list && list.length > 0 && list.every((v) => isVNode(v) && v.key != null)) {
1354
+ keyedPaintHole(fiber, list);
1355
+ return;
1356
+ }
1357
+ disposeHoles(fiber);
1358
+ fiber.root.replaceChildren();
1359
+ insert(fiber, materialize(view, fiber));
1360
+ }
1361
+ function keyedPaintHole(fiber, views) {
1362
+ const prev = fiber.keyedHoles ?? /* @__PURE__ */ new Map();
1363
+ const keep = new Set(views.map((v) => String(v.key)));
1364
+ for (const [k, h] of prev) if (!keep.has(k)) closeFiber(h);
1365
+ const next = /* @__PURE__ */ new Map();
1366
+ const nodes = [];
1367
+ for (const v of views) {
1368
+ const k = String(v.key);
1369
+ const reuse = prev.get(k);
1370
+ if (reuse && !reuse.closed) {
1371
+ next.set(k, reuse);
1372
+ nodes.push(reuse.root);
1373
+ continue;
1374
+ }
1375
+ const made = materialize(v, fiber);
1376
+ const hole = fiber.keyedHoles?.get(k);
1377
+ if (hole) next.set(k, hole);
1378
+ nodes.push(...made);
1379
+ }
1380
+ fiber.keyedHoles = next;
1381
+ fiber.root.replaceChildren();
1382
+ insert(fiber, nodes);
1383
+ }
1384
+ function openHole(parent) {
1385
+ const id = parent.runtime.nextHole();
1386
+ const host = createSsrElement("span");
1387
+ host.setAttribute(SLOT_ATTR, String(id));
1388
+ host.setAttribute("style", "display:contents");
1389
+ const child = makeFiber(parent.runtime, host, paint, { onFail: (e) => {
1390
+ console.error(e);
1391
+ paintHole(child, errorView(e));
1392
+ } });
1393
+ return {
1394
+ fiber: child,
1395
+ nodes: [host]
1396
+ };
1397
+ }
1398
+ function errorView(e) {
1399
+ return {
1400
+ $$ilha: 1,
1401
+ type: "pre",
1402
+ props: { "data-ilha-error": "" },
1403
+ children: [failureMessage(e)]
1404
+ };
1405
+ }
1406
+ function findReusableAtomHost(fiber, atom) {
1407
+ for (const h of fiber.holes) {
1408
+ const host = h.host;
1409
+ if (h.atom === atom && host?.isConnected && h.holeFiber && !h.holeFiber.closed) return {
1410
+ host,
1411
+ hole: h.holeFiber
1412
+ };
1413
+ }
1414
+ }
1415
+ function atomHostPlaceholder(host) {
1416
+ const placeholder = createSsrElement("span");
1417
+ const slot = host.getAttribute(SLOT_ATTR);
1418
+ if (slot !== null) placeholder.setAttribute(SLOT_ATTR, slot);
1419
+ placeholder.setAttribute("style", "display:contents");
1420
+ return placeholder;
1421
+ }
1422
+ function trackHole(parent, hole, extra, meta) {
1423
+ parent.holes.push({
1424
+ keepOnMorph: !!(meta?.atom && meta?.host) || !!meta?.keyed,
1425
+ atom: meta?.atom,
1426
+ host: meta?.host,
1427
+ holeFiber: hole,
1428
+ dispose() {
1429
+ extra?.();
1430
+ if (meta?.host) meta.host.isConnected = false;
1431
+ closeFiber(hole);
1432
+ }
1433
+ });
1434
+ }
1435
+ function materialize(view, fiber) {
1436
+ if (skip(view)) return [];
1437
+ if (typeof view === "string" || typeof view === "number" || typeof view === "bigint") return [createSsrText(String(view))];
1438
+ if (isAtomHandle(view)) {
1439
+ const reused = findReusableAtomHost(fiber, view.atom);
1440
+ if (reused) return [atomHostPlaceholder(reused.host)];
1441
+ const { fiber: hole, nodes } = openHole(fiber);
1442
+ const host = nodes[0];
1443
+ let unsub = () => {};
1444
+ fiber.runtime.later(() => {
1445
+ if (hole.closed) return;
1446
+ let keep;
1447
+ unsub = fiber.registry.subscribe(view.atom, (v) => {
1448
+ if (hole.closed) return;
1449
+ const next = unwrap(v, keep);
1450
+ if (next === KEEP) return;
1451
+ keep = next;
1452
+ paintHole(hole, next);
1453
+ }, { immediate: true });
1454
+ });
1455
+ trackHole(fiber, hole, () => unsub(), {
1456
+ atom: view.atom,
1457
+ host
1458
+ });
1459
+ return nodes;
1460
+ }
1461
+ if (Stream.isStream(view)) {
1462
+ const { fiber: hole, nodes } = openHole(fiber);
1463
+ hole.run(Stream.runForEach(view.pipe(Stream.take(1)), (v) => Effect.sync(() => {
1464
+ if (!hole.closed) paintHole(hole, v);
1465
+ })).pipe(Effect.catch((e) => Effect.sync(() => {
1466
+ if (!hole.closed) paintHole(hole, String(e));
1467
+ }))), () => {});
1468
+ trackHole(fiber, hole);
1469
+ return nodes;
1470
+ }
1471
+ if (isSetupFn(view)) {
1472
+ const { fiber: hole, nodes } = openHole(fiber);
1473
+ fiber.runtime.later(() => {
1474
+ if (!hole.closed) runSetup(hole, view);
1475
+ });
1476
+ trackHole(fiber, hole);
1477
+ return nodes;
1478
+ }
1479
+ if (isVNode(view)) {
1480
+ if (view.type === Fragment) return view.children.flatMap((c) => materialize(c, fiber));
1481
+ if (typeof view.type === "function") {
1482
+ const props = {
1483
+ ...view.props,
1484
+ children: view.children
1485
+ };
1486
+ const type = view.type;
1487
+ if (type[ISLAND] === true) {
1488
+ const frame = fiber.islandFrame ??= {
1489
+ i: 0,
1490
+ slots: []
1491
+ };
1492
+ const i = frame.i++;
1493
+ const existing = frame.slots[i];
1494
+ if (existing && existing.type === type && existing.host.isConnected && typeof existing.updateProps === "function") {
1495
+ existing.updateProps(props);
1496
+ return [existing.host];
1497
+ }
1498
+ existing?.unmount?.();
1499
+ const tag = typeof type[ISLAND_SLOT_TAG] === "string" ? type[ISLAND_SLOT_TAG] : "div";
1500
+ const el = createSsrElement(tag);
1501
+ el.setAttribute("data-ilha", "");
1502
+ const mountFn = type[ISLAND_MOUNT_INTERNAL];
1503
+ if (typeof mountFn === "function") {
1504
+ const handle = mountFn(el, props);
1505
+ const slot = {
1506
+ type,
1507
+ host: el,
1508
+ updateProps: handle?.updateProps,
1509
+ unmount: handle?.unmount
1510
+ };
1511
+ frame.slots[i] = slot;
1512
+ fiber.holes.push({
1513
+ keepOnMorph: true,
1514
+ dispose: () => {
1515
+ el.isConnected = false;
1516
+ handle?.unmount?.();
1517
+ if (frame.slots[i] === slot) frame.slots[i] = void 0;
1518
+ }
1519
+ });
1520
+ }
1521
+ return [el];
1522
+ }
1523
+ const k = view.key == null ? void 0 : String(view.key);
1524
+ const reuse = k ? fiber.keyedHoles?.get(k) : void 0;
1525
+ if (reuse && !reuse.closed) {
1526
+ if (reuse.propsBox) reuse.propsBox.current = props;
1527
+ const next = withFiber(fiber, () => type(reuse.propsBox?.current ?? props));
1528
+ if (next && typeof next.then !== "function" && !isSetupFn(next)) reuse.paint(next);
1529
+ return [reuse.root];
1530
+ }
1531
+ const { fiber: hole, nodes } = openHole(fiber);
1532
+ hole.propsBox = { current: props };
1533
+ if (k) {
1534
+ fiber.keyedHoles ??= /* @__PURE__ */ new Map();
1535
+ fiber.keyedHoles.set(k, hole);
1536
+ }
1537
+ fiber.runtime.later(() => {
1538
+ if (!hole.closed) runSetup(hole, () => type(hole.propsBox.current));
1539
+ });
1540
+ trackHole(fiber, hole, void 0, k ? { keyed: true } : void 0);
1541
+ return nodes;
1542
+ }
1543
+ const el = createSsrElement(view.type);
1544
+ if (view.key != null) el.setAttribute(KEY_ATTR, String(view.key));
1545
+ applyProps(el, view.props, fiber);
1546
+ fiber.keyedHoles ??= /* @__PURE__ */ new Map();
1547
+ const childFiber = {
1548
+ ...fiber,
1549
+ root: el,
1550
+ holes: fiber.holes,
1551
+ keyedHoles: fiber.keyedHoles
1552
+ };
1553
+ for (const c of view.children) for (const n of materialize(c, childFiber)) el.appendChild(n);
1554
+ return [el];
1555
+ }
1556
+ if (Array.isArray(view) || view && typeof view === "object" && Symbol.iterator in Object(view)) return [...view].flatMap((c) => materialize(c, fiber));
1557
+ return [createSsrText(String(view))];
1558
+ }
1559
+ function paint(fiber, view) {
1560
+ fiber.islandFrame ??= {
1561
+ i: 0,
1562
+ slots: []
1563
+ };
1564
+ disposeHoles(fiber);
1565
+ fiber.root.replaceChildren();
1566
+ insert(fiber, materialize(view, fiber));
1567
+ }
1568
+ function paintError(fiber, e) {
1569
+ console.error(e);
1570
+ paint(fiber, errorView(e));
1571
+ }
1572
+ function attachSsr(fn, opts) {
1573
+ const root = createSsrRoot();
1574
+ const runtime = makeRuntime({
1575
+ ssr: true,
1576
+ ssrCapture: opts?.ssrCapture === true
1577
+ });
1578
+ let resolve;
1579
+ const ready = new Promise((r) => {
1580
+ resolve = r;
1581
+ });
1582
+ const fiber = makeFiber(runtime, root, paint, { onFail: (e) => {
1583
+ opts?.onError?.(e);
1584
+ paintError(fiber, e);
1585
+ resolve();
1586
+ } });
1587
+ runtime.begin();
1588
+ runtime.setIdle(resolve);
1589
+ runSetup(fiber, fn);
1590
+ runtime.end();
1591
+ return {
1592
+ root,
1593
+ ready,
1594
+ runtime,
1595
+ unmount() {
1596
+ closeFiber(fiber);
1597
+ runtime.close();
1598
+ root.replaceChildren();
1599
+ }
1600
+ };
1099
1601
  }
1602
+
1603
+ //#endregion
1604
+ //#region src/mount.ts
1100
1605
  function readHydrate(el) {
1101
1606
  const raw = (el.hasAttribute("data-ilha") ? el : el.querySelector("[data-ilha]"))?.getAttribute("data-ilha-state");
1102
1607
  if (raw) return decodeSnapshot(raw);
@@ -1109,18 +1614,14 @@ function readHydrate(el) {
1109
1614
  }
1110
1615
  function attach(el, fn, opts) {
1111
1616
  if (!opts?.hydrate) el.innerHTML = "";
1112
- const runtime = makeRuntime({
1113
- ssr: opts?.ssr,
1114
- ssrCapture: opts?.ssrCapture,
1115
- hydrate: opts?.hydrate ? readHydrate(el) : void 0
1116
- });
1617
+ const runtime = makeRuntime({ hydrate: opts?.hydrate ? readHydrate(el) : void 0 });
1117
1618
  let resolve;
1118
1619
  const ready = new Promise((r) => {
1119
1620
  resolve = r;
1120
1621
  });
1121
- const fiber = makeFiber(runtime, el, paint, { onFail: (e) => {
1622
+ const fiber = makeFiber(runtime, el, paint$1, { onFail: (e) => {
1122
1623
  opts?.onError?.(e);
1123
- paintError(fiber, e);
1624
+ paintError$1(fiber, e);
1124
1625
  resolve();
1125
1626
  } });
1126
1627
  if (opts?.hydrate) fiber.hydrate = true;
@@ -1141,26 +1642,13 @@ function attach(el, fn, opts) {
1141
1642
  function mount(el, fn, opts) {
1142
1643
  return attach(el, fn, opts).unmount;
1143
1644
  }
1144
- let domReady;
1145
- function ensureDocument() {
1146
- if (typeof globalThis.document !== "undefined") return Promise.resolve();
1147
- domReady ??= import("@happy-dom/global-registrator").then(({ GlobalRegistrator }) => {
1148
- if (typeof globalThis.document === "undefined") GlobalRegistrator.register();
1149
- });
1150
- return domReady;
1151
- }
1152
1645
  async function renderToString(fn, opts) {
1153
- await ensureDocument();
1154
1646
  const snapshot = opts?.snapshot !== false;
1155
1647
  const markers = opts?.markers !== false;
1156
- const el = document.createElement("div");
1157
- const { unmount, ready, runtime } = attach(el, fn, {
1158
- ssr: true,
1159
- ssrCapture: opts?.captureActions === true
1160
- });
1648
+ const { unmount, ready, runtime, root } = attachSsr(fn, { ssrCapture: opts?.captureActions === true });
1161
1649
  if (opts?.timeout == null) await ready;
1162
1650
  else await Promise.race([ready, new Promise((r) => setTimeout(r, opts.timeout))]);
1163
- let inner = el.innerHTML;
1651
+ let inner = root.innerHTML;
1164
1652
  const actions = runtime.ssrActions;
1165
1653
  if (Object.keys(actions).length > 0) inner = `<template data-ilha-actions="${escapeAttr(JSON.stringify(actions))}"></template>${inner}`;
1166
1654
  const snap = snapshot ? encodeSnapshot(runtime.ssrValues) : void 0;
@@ -1170,4 +1658,4 @@ async function renderToString(fn, opts) {
1170
1658
  }
1171
1659
 
1172
1660
  //#endregion
1173
- export { atom as a, instr as c, getFiber as d, makeFiber as f, interpret as i, isAtomHandle as l, renderToString as n, batch as o, paint as r, handleOwner as s, mount as t, closeFiber as u };
1661
+ export { atom as a, instr as c, getFiber as d, makeFiber as f, interpret as i, isAtomHandle as l, renderToString as n, batch as o, paint$1 as r, handleOwner as s, mount as t, closeFiber as u };