ilha 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.js +116 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -420,6 +420,8 @@ const Card = ilha.render(
|
|
|
420
420
|
);
|
|
421
421
|
```
|
|
422
422
|
|
|
423
|
+
Props (including JSX `children` and function callbacks) stay on the **live slot map** during parent render/mount. `data-ilha-props` only carries JSON-safe scalars for hydration hints — never rely on it for children or handlers. Nested islands under layout shells (`defineLayout` / `wrapLayout`) use the same path as page-level composition.
|
|
424
|
+
|
|
423
425
|
**Keyed children** — use `.key()` when a child may reorder or appear conditionally. Keys must be unique within a parent render:
|
|
424
426
|
|
|
425
427
|
```ts
|
package/dist/index.js
CHANGED
|
@@ -121,9 +121,96 @@ function isStableInlineSlotMount(initialHtml, renderedHtml, slotIds) {
|
|
|
121
121
|
}
|
|
122
122
|
return true;
|
|
123
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Props safe to embed in `data-ilha-props`.
|
|
126
|
+
* Functions cannot round-trip through JSON; children are owned by the live slot
|
|
127
|
+
* map (client) and/or the inlined SSR subtree — never depend on the attr for them.
|
|
128
|
+
* RawHtml is tagged so a rare attr-only mount can revive `Symbol.for("ilha.raw")`.
|
|
129
|
+
*/
|
|
130
|
+
function slotPropsForAttr(props) {
|
|
131
|
+
if (props === void 0) return void 0;
|
|
132
|
+
let out;
|
|
133
|
+
for (const key of Object.keys(props)) {
|
|
134
|
+
if (key === "children") continue;
|
|
135
|
+
const value = props[key];
|
|
136
|
+
if (typeof value === "function" || typeof value === "symbol") continue;
|
|
137
|
+
const encoded = encodeSlotPropValue(value);
|
|
138
|
+
if (encoded === void 0 && value !== void 0 && value !== null) continue;
|
|
139
|
+
(out ??= {})[key] = encoded;
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
const SLOT_RAW = Symbol.for("ilha.raw");
|
|
144
|
+
function isRawHtmlValue(v) {
|
|
145
|
+
return !!(v && typeof v === "object" && SLOT_RAW in v);
|
|
146
|
+
}
|
|
147
|
+
function encodeSlotPropValue(value, seen) {
|
|
148
|
+
if (value == null) return value;
|
|
149
|
+
if (typeof value === "function" || typeof value === "symbol") return void 0;
|
|
150
|
+
if (typeof value !== "object") return value;
|
|
151
|
+
if (isRawHtmlValue(value)) return {
|
|
152
|
+
__ilha: "raw",
|
|
153
|
+
value: value.value
|
|
154
|
+
};
|
|
155
|
+
const visited = seen ?? /* @__PURE__ */ new WeakSet();
|
|
156
|
+
if (visited.has(value)) throw new TypeError("encodeSlotPropValue: circular reference in slot props");
|
|
157
|
+
visited.add(value);
|
|
158
|
+
if (Array.isArray(value)) return value.map((item) => encodeSlotPropValue(item, visited)).filter((item) => item !== void 0);
|
|
159
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) return void 0;
|
|
160
|
+
const obj = value;
|
|
161
|
+
const encoded = {};
|
|
162
|
+
for (const key of Object.keys(obj)) {
|
|
163
|
+
const next = encodeSlotPropValue(obj[key], visited);
|
|
164
|
+
if (next !== void 0 || obj[key] === null) encoded[key] = next;
|
|
165
|
+
}
|
|
166
|
+
return encoded;
|
|
167
|
+
}
|
|
168
|
+
function makeRawHtml(value) {
|
|
169
|
+
return {
|
|
170
|
+
[SLOT_RAW]: true,
|
|
171
|
+
value
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function reviveSlotPropValue(value) {
|
|
175
|
+
if (value == null || typeof value !== "object") return value;
|
|
176
|
+
if (Array.isArray(value)) return value.map(reviveSlotPropValue);
|
|
177
|
+
const obj = value;
|
|
178
|
+
if (obj.__ilha === "raw" && typeof obj.value === "string") return makeRawHtml(obj.value);
|
|
179
|
+
const out = {};
|
|
180
|
+
for (const key of Object.keys(obj)) out[key] = reviveSlotPropValue(obj[key]);
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Exact legacy shape produced when RawHtml lost its brand under JSON.stringify:
|
|
185
|
+
* a plain object with a single own key `value` that is a string.
|
|
186
|
+
*/
|
|
187
|
+
function isLegacyRawHtmlBlob(value) {
|
|
188
|
+
if (!value || typeof value !== "object" || isRawHtmlValue(value)) return false;
|
|
189
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) return false;
|
|
190
|
+
const keys = Object.keys(value);
|
|
191
|
+
return keys.length === 1 && keys[0] === "value" && typeof value.value === "string";
|
|
192
|
+
}
|
|
193
|
+
/** Revive attr-parsed props (RawHtml tags). Children stay as plain data if present. */
|
|
194
|
+
function reviveSlotProps(props) {
|
|
195
|
+
if (typeof props !== "object" || props === null || Array.isArray(props)) return {};
|
|
196
|
+
const out = reviveSlotPropValue(props);
|
|
197
|
+
if ("children" in out) out.children = reviveLegacyChildren(out.children);
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
function reviveLegacyChildren(children) {
|
|
201
|
+
if (!Array.isArray(children)) {
|
|
202
|
+
if (isLegacyRawHtmlBlob(children)) return makeRawHtml(children.value);
|
|
203
|
+
return reviveSlotPropValue(children);
|
|
204
|
+
}
|
|
205
|
+
return children.map((child) => {
|
|
206
|
+
if (isLegacyRawHtmlBlob(child)) return makeRawHtml(child.value);
|
|
207
|
+
return reviveSlotPropValue(child);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
124
210
|
/** Serialized form of slot props, matching the decoded `data-ilha-props` attr. */
|
|
125
211
|
function serializeSlotProps(props) {
|
|
126
|
-
|
|
212
|
+
const safe = slotPropsForAttr(props);
|
|
213
|
+
return safe === void 0 ? "" : JSON.stringify(safe);
|
|
127
214
|
}
|
|
128
215
|
const MORPH_CONTROLLER_SLOT_SELECTOR = "[data-slot]";
|
|
129
216
|
const MORPH_CONTROLLER_ATTRS = /* @__PURE__ */ new Set([
|
|
@@ -400,7 +487,11 @@ const CSS_ATTR = "data-ilha-css";
|
|
|
400
487
|
function buildScopedStyle(css) {
|
|
401
488
|
return `<style ${CSS_ATTR}>@scope (:scope) to ([data-ilha]){${css.replace(/<\/style/gi, "<\\/style")}}</style>`;
|
|
402
489
|
}
|
|
403
|
-
const
|
|
490
|
+
const RENDER_CTX_STACK = Symbol.for("ilha.renderCtxStack");
|
|
491
|
+
function renderCtxStack() {
|
|
492
|
+
const g = globalThis;
|
|
493
|
+
return g[RENDER_CTX_STACK] ??= [];
|
|
494
|
+
}
|
|
404
495
|
function pushRenderCtx(liveHost, asyncChildren) {
|
|
405
496
|
const ctx = {
|
|
406
497
|
slots: /* @__PURE__ */ new Map(),
|
|
@@ -409,14 +500,15 @@ function pushRenderCtx(liveHost, asyncChildren) {
|
|
|
409
500
|
pending: asyncChildren ? /* @__PURE__ */ new Map() : void 0,
|
|
410
501
|
binds: []
|
|
411
502
|
};
|
|
412
|
-
renderCtxStack.push(ctx);
|
|
503
|
+
renderCtxStack().push(ctx);
|
|
413
504
|
return ctx;
|
|
414
505
|
}
|
|
415
506
|
function popRenderCtx() {
|
|
416
|
-
renderCtxStack.pop();
|
|
507
|
+
renderCtxStack().pop();
|
|
417
508
|
}
|
|
418
509
|
function currentRenderCtx() {
|
|
419
|
-
|
|
510
|
+
const stack = renderCtxStack();
|
|
511
|
+
return stack[stack.length - 1];
|
|
420
512
|
}
|
|
421
513
|
function isIsland(v) {
|
|
422
514
|
return typeof v === "function" && ISLAND in v;
|
|
@@ -436,7 +528,8 @@ function emitIslandSlot(island, props, key) {
|
|
|
436
528
|
props
|
|
437
529
|
});
|
|
438
530
|
const slotTag = getIslandSlotTag(island);
|
|
439
|
-
const
|
|
531
|
+
const attrProps = slotPropsForAttr(props);
|
|
532
|
+
const propsAttr = attrProps ? ` ${PROPS_ATTR}='${escapeHtml(JSON.stringify(attrProps))}'` : "";
|
|
440
533
|
if (ctx?.liveHost) return wrapIslandSlotHtml(slotTag, id, propsAttr, "");
|
|
441
534
|
if (ctx?.pending) {
|
|
442
535
|
popRenderCtx();
|
|
@@ -448,7 +541,7 @@ function emitIslandSlot(island, props, key) {
|
|
|
448
541
|
}
|
|
449
542
|
return wrapIslandSlotHtml(slotTag, id, propsAttr, String(result));
|
|
450
543
|
} finally {
|
|
451
|
-
renderCtxStack.push(ctx);
|
|
544
|
+
renderCtxStack().push(ctx);
|
|
452
545
|
}
|
|
453
546
|
}
|
|
454
547
|
const inner = island.toString(props);
|
|
@@ -1539,7 +1632,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1539
1632
|
const rawProps = host.getAttribute(PROPS_ATTR);
|
|
1540
1633
|
if (rawProps) {
|
|
1541
1634
|
const parsed = safeParseSnapshot(rawProps, PROPS_ATTR);
|
|
1542
|
-
if (parsed !== void 0) props = parsed;
|
|
1635
|
+
if (parsed !== void 0) props = reviveSlotProps(parsed);
|
|
1543
1636
|
}
|
|
1544
1637
|
}
|
|
1545
1638
|
const inputSignal = signal(resolveInput(props));
|
|
@@ -1661,6 +1754,13 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1661
1754
|
}
|
|
1662
1755
|
return index;
|
|
1663
1756
|
}
|
|
1757
|
+
/** Push live props from a fresh slot map into already-mounted children. */
|
|
1758
|
+
function pushUpdatedProps(nextSlots) {
|
|
1759
|
+
for (const [id, entry] of mountedSlots) {
|
|
1760
|
+
const next = nextSlots.get(id);
|
|
1761
|
+
if (next) entry.updateProps(next.props);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1664
1764
|
function mountSlots(slotMap) {
|
|
1665
1765
|
for (const [id, entry] of mountedSlots) if (!slotMap.has(id)) teardownMountedSlot(id, entry);
|
|
1666
1766
|
for (const [id, entry] of mountedSlots) {
|
|
@@ -1682,7 +1782,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1682
1782
|
const rawProps = slotEl.getAttribute(PROPS_ATTR) ?? slotEl.getAttribute("data-props");
|
|
1683
1783
|
if (rawProps) {
|
|
1684
1784
|
const parsed = safeParseSnapshot(rawProps, `props on [${SLOT_ATTR}="${id}"]`);
|
|
1685
|
-
if (parsed !== void 0) slotProps = parsed;
|
|
1785
|
+
if (parsed !== void 0) slotProps = reviveSlotProps(parsed);
|
|
1686
1786
|
}
|
|
1687
1787
|
}
|
|
1688
1788
|
const prevSub = setActiveSub(void 0);
|
|
@@ -1873,10 +1973,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1873
1973
|
if (!initialized) {
|
|
1874
1974
|
initialized = true;
|
|
1875
1975
|
if (preserveSSRDom) {
|
|
1876
|
-
|
|
1877
|
-
const next = newSlotMap.get(id);
|
|
1878
|
-
if (next) entry.updateProps(next.props);
|
|
1879
|
-
}
|
|
1976
|
+
pushUpdatedProps(newSlotMap);
|
|
1880
1977
|
if (rendered === initialRenderedHtml) {
|
|
1881
1978
|
lastRendered = rendered;
|
|
1882
1979
|
return;
|
|
@@ -1886,13 +1983,13 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1886
1983
|
lastRendered = rendered;
|
|
1887
1984
|
return;
|
|
1888
1985
|
}
|
|
1889
|
-
if (mountedSlots.size > 0 && mountedSlots.size === newSlotMap.size && [...newSlotMap.keys()].every((id) => mountedSlots.has(id)) && isStableInlineSlotMount(initialRenderedHtml, rendered, newSlotMap.keys()))
|
|
1986
|
+
if (mountedSlots.size > 0 && mountedSlots.size === newSlotMap.size && [...newSlotMap.keys()].every((id) => mountedSlots.has(id)) && isStableInlineSlotMount(initialRenderedHtml, rendered, newSlotMap.keys())) {
|
|
1987
|
+
pushUpdatedProps(newSlotMap);
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1890
1990
|
}
|
|
1891
1991
|
if (rendered === lastRendered && mountedSlots.size === newSlotMap.size && [...newSlotMap].every(([id, next]) => mountedSlots.get(id)?.island === next.island)) {
|
|
1892
|
-
|
|
1893
|
-
const next = newSlotMap.get(id);
|
|
1894
|
-
if (next) entry.updateProps(next.props);
|
|
1895
|
-
}
|
|
1992
|
+
pushUpdatedProps(newSlotMap);
|
|
1896
1993
|
return;
|
|
1897
1994
|
}
|
|
1898
1995
|
detachListeners();
|
|
@@ -2198,7 +2295,7 @@ function mountAll(registry, options = {}) {
|
|
|
2198
2295
|
const rawProps = host.getAttribute(PROPS_ATTR);
|
|
2199
2296
|
if (rawProps) {
|
|
2200
2297
|
const parsed = safeParseSnapshot(rawProps, `${PROPS_ATTR} on [data-ilha="${name}"]`);
|
|
2201
|
-
if (parsed !== void 0) props = parsed;
|
|
2298
|
+
if (parsed !== void 0) props = reviveSlotProps(parsed);
|
|
2202
2299
|
}
|
|
2203
2300
|
unmounts.push(island.mount(host, props));
|
|
2204
2301
|
}
|