ilha 0.15.1 → 0.16.0
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/AGENTS.md +102 -0
- package/LICENSE +21 -0
- package/MIGRATIONS.json +96 -0
- package/MIGRATIONS.md +198 -0
- package/README.md +9 -26
- package/dist/debug.d.ts +45 -0
- package/dist/debug.js +217 -0
- package/dist/index.d.ts +30 -50
- package/dist/index.js +105 -586
- package/dist/jsx-dev-runtime.d.ts +2 -2
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.d.ts +2 -2
- package/dist/jsx-runtime.js +1 -1
- package/dist/mount-3Ggg67T0.d.ts +14 -0
- package/dist/mount-BwuJsiDH.js +585 -0
- package/dist/paint-core-D1n4lbpF.js +350 -0
- package/dist/renderer.d.ts +30 -66
- package/dist/renderer.js +18 -50
- package/dist/{runtime-Baq7MtkE.d.ts → runtime-BCdKBqYp.d.ts} +29 -35
- package/dist/shared-vpgYXytd.js +36 -0
- package/dist/snapshot-BDC74QwI.d.ts +9 -0
- package/dist/start-B-e1dG5U.js +515 -0
- package/dist/test.d.ts +66 -0
- package/dist/test.js +179 -0
- package/dist/{types-DsdtZMhG.d.ts → types-NhEiBvwy.d.ts} +39 -46
- package/dist/vnode-DJ25Jn3D.js +22 -0
- package/dist/{vnode-CpSQZnLM.d.ts → vnode-DskooLPD.d.ts} +10 -2
- package/llms.txt +37 -0
- package/oxlint.cjs +596 -0
- package/package.json +20 -14
- package/dist/define.d.ts +0 -4
- package/dist/define.js +0 -18
- package/dist/mount-DCu8b0hE.js +0 -482
- package/dist/paint-core-D9VO-oUM.js +0 -1659
- package/dist/vnode-BHUY61Rm.js +0 -100
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { g as makeFiber, i as runSetup, m as closeFiber, n as notifyRoots, r as rootListeners, u as isAtomHandle, y as ilhaWarn } from "./start-B-e1dG5U.js";
|
|
2
|
+
import { a as hasBrand, c as isObject, d as skip, l as isString, n as KEY_ATTR, o as isFunction, p as unwrap, r as SLOT_ATTR, s as isIterable, t as KEEP, u as noop } from "./shared-vpgYXytd.js";
|
|
3
|
+
import { n as isVNode, r as Fragment } from "./vnode-DJ25Jn3D.js";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Stream from "effect/Stream";
|
|
6
|
+
|
|
7
|
+
//#region src/events.ts
|
|
8
|
+
const EVENT_PROP = /^on(?:[A-Z].{2,}|[a-z]{3,}$)/u;
|
|
9
|
+
/** `onClick` / `onclick` → `click`; non-event props → `undefined`. */
|
|
10
|
+
const eventTypeFromProp = (key) => EVENT_PROP.test(key) ? key.slice(2).toLowerCase() : void 0;
|
|
11
|
+
/** One native listener per type; it reads the latest handler at dispatch. */
|
|
12
|
+
const listen = (el, type) => {
|
|
13
|
+
el.__ilhaEvents ??= /* @__PURE__ */ new Set();
|
|
14
|
+
if (el.__ilhaEvents.has(type)) return;
|
|
15
|
+
el.__ilhaEvents.add(type);
|
|
16
|
+
el.addEventListener(type, (ev) => {
|
|
17
|
+
const fiber = el.__ilhaFiber;
|
|
18
|
+
const fn = el.__ilhaHandlers?.get(type);
|
|
19
|
+
if (!fiber || fiber.closed || !isFunction(fn)) return;
|
|
20
|
+
const out = fn(ev);
|
|
21
|
+
if (Effect.isEffect(out)) fiber.run(out);
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
const install = (el, handlers, fiber) => {
|
|
25
|
+
el.__ilhaHandlers = handlers;
|
|
26
|
+
el.__ilhaFiber = fiber;
|
|
27
|
+
for (const type of handlers?.keys() ?? []) listen(el, type);
|
|
28
|
+
};
|
|
29
|
+
const bindEvents = (el, props, fiber) => {
|
|
30
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
31
|
+
for (const [key, value] of Object.entries(props)) {
|
|
32
|
+
const type = eventTypeFromProp(key);
|
|
33
|
+
if (type && !handlers.has(type)) handlers.set(type, value);
|
|
34
|
+
}
|
|
35
|
+
install(el, handlers, fiber);
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The morph kept `live` in place of the freshly rendered `fresh`: hand the
|
|
39
|
+
* render's handlers to the live node, so closures never go stale and handlers
|
|
40
|
+
* added on a later render still fire.
|
|
41
|
+
*/
|
|
42
|
+
const adoptEvents = (live, fresh) => {
|
|
43
|
+
if (fresh.__ilhaFiber) install(live, fresh.__ilhaHandlers, fresh.__ilhaFiber);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/security.ts
|
|
48
|
+
const SAFE_CSS_PROP = /^-{0,2}[a-zA-Z][a-zA-Z0-9-]*$/u;
|
|
49
|
+
const URL_ATTR = /(?:^|:)(?:href|src|srcset|imagesrcset|action|formaction|cite|data|poster)$/iu;
|
|
50
|
+
const SRCSET_ATTR = /^(?:srcset|imagesrcset)$/iu;
|
|
51
|
+
const SAFE_DATA_IMAGE = /^data:image\/(?:png|jpe?g|gif|webp|avif)[;,]/iu;
|
|
52
|
+
const UNSAFE_SCHEME = /^(?:javascript|vbscript):/iu;
|
|
53
|
+
const URL_PADDING = /[\0-\u0020\u00A0\u1680\u2000-\u200D\u2028\u2029\u202F\u205F\u3000\uFEFF]/gu;
|
|
54
|
+
const UNSAFE_CSS = /[<>{};\\]|expression\(|javascript:|data:/iu;
|
|
55
|
+
const CSS_URL = /url\s*\(\s*(?<inner>[^)]+)\s*\)/giu;
|
|
56
|
+
const isSafeUrl = (value, allowDataImage = false) => {
|
|
57
|
+
const url = value.replaceAll(URL_PADDING, "");
|
|
58
|
+
if (UNSAFE_SCHEME.test(url)) return false;
|
|
59
|
+
return !/^data:/iu.test(url) || allowDataImage && SAFE_DATA_IMAGE.test(url);
|
|
60
|
+
};
|
|
61
|
+
const isUrlAttributeName = (name) => URL_ATTR.test(name);
|
|
62
|
+
const isSafeUrlAttrValue = (tagName, attrName, value) => {
|
|
63
|
+
if (SRCSET_ATTR.test(attrName)) return value.split(",").every((candidate) => isSafeUrl(candidate.trim().split(/\s+/u)[0] ?? ""));
|
|
64
|
+
return isSafeUrl(value, tagName.toLowerCase() === "img" && attrName.toLowerCase() === "src");
|
|
65
|
+
};
|
|
66
|
+
const isSafeStyleValue = (str) => !UNSAFE_CSS.test(str) && [...str.matchAll(CSS_URL)].every((m) => isSafeUrl((m.groups?.inner ?? "").trim().replaceAll(/^['"]|['"]$/gu, "")));
|
|
67
|
+
/** Serialize a style string or object, dropping unsafe properties and values. */
|
|
68
|
+
const serializeStyleAttr = (value) => {
|
|
69
|
+
return (isString(value) ? value.split(";").map((decl) => {
|
|
70
|
+
const colon = decl.indexOf(":");
|
|
71
|
+
return colon === -1 ? ["", ""] : [decl.slice(0, colon).trim(), decl.slice(colon + 1).trim()];
|
|
72
|
+
}) : Object.entries(value)).filter(([k, v]) => k && v !== null && v !== void 0 && SAFE_CSS_PROP.test(k) && isSafeStyleValue(String(v))).map(([k, v]) => `${(k ?? "").replaceAll(/[A-Z]/gu, (m) => `-${m.toLowerCase()}`)}:${String(v)}`).join(";");
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/unsafe.ts
|
|
77
|
+
/**
|
|
78
|
+
* Mark a string as pre-rendered HTML and paint it without escaping.
|
|
79
|
+
* Use it for markup you built or sanitized yourself — SVG from an external
|
|
80
|
+
* library, Markdown rendered to HTML, embed codes. Never pass untrusted
|
|
81
|
+
* user input: unlike interpolated strings, this content is not escaped and
|
|
82
|
+
* scripts inserted this way do not run, but event attributes and other
|
|
83
|
+
* markup still apply.
|
|
84
|
+
*/
|
|
85
|
+
const unsafe = (html) => ({
|
|
86
|
+
$$ilhaUnsafe: 1,
|
|
87
|
+
html
|
|
88
|
+
});
|
|
89
|
+
const isUnsafeHtml = (x) => hasBrand(x, "$$ilhaUnsafe");
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/paint-core.ts
|
|
93
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
94
|
+
const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
|
|
95
|
+
const elementNs = (lower, parent) => {
|
|
96
|
+
if (lower === "svg") return SVG_NS;
|
|
97
|
+
if (lower === "math") return MATHML_NS;
|
|
98
|
+
if (lower === "foreignobject") return "http://www.w3.org/1999/xhtml";
|
|
99
|
+
const ns = parent.namespaceURI;
|
|
100
|
+
return ns === SVG_NS || ns === MATHML_NS ? ns : void 0;
|
|
101
|
+
};
|
|
102
|
+
const hasKey = (view) => view.key !== null && view.key !== void 0;
|
|
103
|
+
/** Shallow equality of two prop bags, children compared item by item. */
|
|
104
|
+
const sameProps = (prev, next) => {
|
|
105
|
+
if (!prev) return false;
|
|
106
|
+
const keys = Object.keys(next);
|
|
107
|
+
return keys.length === Object.keys(prev).length && keys.every((k) => {
|
|
108
|
+
const a = prev[k];
|
|
109
|
+
const b = next[k];
|
|
110
|
+
return k === "children" && Array.isArray(a) && Array.isArray(b) ? a.length === b.length && a.every((x, i) => Object.is(x, b[i])) : Object.is(a, b);
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
/** Rerun a component hole with `props`; render closures read the latest box. */
|
|
114
|
+
const rerunHole = (hole, run, props) => {
|
|
115
|
+
hole.propsBox ??= { current: props };
|
|
116
|
+
const box = hole.propsBox;
|
|
117
|
+
box.current = props;
|
|
118
|
+
runSetup(hole, () => run(box.current));
|
|
119
|
+
};
|
|
120
|
+
const hostOf = (fiber) => fiber.root;
|
|
121
|
+
/** Dispose `hole` and drop it from a `filter` pass. */
|
|
122
|
+
const dispose = (hole) => {
|
|
123
|
+
hole.dispose();
|
|
124
|
+
return false;
|
|
125
|
+
};
|
|
126
|
+
const isSkippedPropKey = (k) => k === "children" || k === "key" || k === "ref" || k.toLowerCase() === "srcdoc" || eventTypeFromProp(k) !== void 0 || /^on[A-Z]/u.test(k);
|
|
127
|
+
/**
|
|
128
|
+
* The painter shared by DOM mount, SSR and custom hosts: props,
|
|
129
|
+
* materialization, holes, and keyed reconciliation.
|
|
130
|
+
*/
|
|
131
|
+
const createPainter = (ops) => {
|
|
132
|
+
const { committed = (el) => el, reuseNode = (host) => host, setStyle = (el, css) => el.setAttribute("style", css) } = ops;
|
|
133
|
+
const asRoot = (el) => el;
|
|
134
|
+
const self = {};
|
|
135
|
+
let depth = 0;
|
|
136
|
+
const pendingRefs = [];
|
|
137
|
+
const setFormControl = ops.setFormControl ?? ((el, key, v) => {
|
|
138
|
+
if (v === null || v === void 0 || v === false) el.removeAttribute(key);
|
|
139
|
+
else el.setAttribute(key, key === "value" ? String(v) : "");
|
|
140
|
+
});
|
|
141
|
+
const writeAttr = (el, k, v) => {
|
|
142
|
+
if (v === false || v === null || v === void 0) {
|
|
143
|
+
el.removeAttribute(k);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const str = v === true ? "" : String(v);
|
|
147
|
+
if (!isUrlAttributeName(k) || isSafeUrlAttrValue(el.tagName, k, str)) el.setAttribute(k, str);
|
|
148
|
+
};
|
|
149
|
+
const applyProp = (el, k, v, fiber) => {
|
|
150
|
+
if (isSkippedPropKey(k) || ops.setProp?.(el, k, v) === true) return;
|
|
151
|
+
if (k === "className" || k === "htmlFor") el.setAttribute(k === "className" ? "class" : "for", String(v ?? ""));
|
|
152
|
+
else if (k === "style") {
|
|
153
|
+
const css = isString(v) || isObject(v) ? serializeStyleAttr(v) : "";
|
|
154
|
+
if (css) setStyle(el, css);
|
|
155
|
+
} else if (k === "value" || k === "checked" || k === "selected") {
|
|
156
|
+
if (isAtomHandle(v)) {
|
|
157
|
+
const unsub = fiber.registry.subscribe(v.atom, (x) => setFormControl(committed(el), k, x), { immediate: true });
|
|
158
|
+
fiber.holes.push({ dispose: unsub });
|
|
159
|
+
} else setFormControl(el, k, v);
|
|
160
|
+
} else if (!isFunction(v)) writeAttr(el, k, v);
|
|
161
|
+
};
|
|
162
|
+
const applyProps = (el, props, fiber) => {
|
|
163
|
+
for (const [k, v] of Object.entries(props)) applyProp(el, k, v, fiber);
|
|
164
|
+
ops.bindEvents?.(el, props, fiber);
|
|
165
|
+
const { ref } = props;
|
|
166
|
+
if (isFunction(ref)) {
|
|
167
|
+
const refFn = ref;
|
|
168
|
+
const attach = () => refFn(committed(el));
|
|
169
|
+
if (depth > 0) pendingRefs.push(attach);
|
|
170
|
+
else attach();
|
|
171
|
+
fiber.holes.push({ dispose: () => refFn(null) });
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Open a child fiber rooted in a fresh `display: contents` host. `keep`
|
|
176
|
+
* holes survive repaints that reach them again; others close on repaint.
|
|
177
|
+
*/
|
|
178
|
+
const openHole = (parent, meta = {}, onDispose) => {
|
|
179
|
+
parent.runtime.holeSeq += 1;
|
|
180
|
+
const host = ops.createElement("span");
|
|
181
|
+
host.setAttribute(SLOT_ATTR, String(parent.runtime.holeSeq));
|
|
182
|
+
setStyle(host, "display:contents");
|
|
183
|
+
const hole = makeFiber(parent.runtime, asRoot(host), self.paint, { parent });
|
|
184
|
+
hole.seen = parent.gen;
|
|
185
|
+
parent.holes.push({
|
|
186
|
+
...meta,
|
|
187
|
+
dispose: () => {
|
|
188
|
+
onDispose?.();
|
|
189
|
+
closeFiber(hole);
|
|
190
|
+
},
|
|
191
|
+
fiber: hole
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
hole,
|
|
195
|
+
host
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
/** Reuse a kept hole in this paint: mark it reached, return its host. */
|
|
199
|
+
const reuse = (fiber, hole) => {
|
|
200
|
+
hole.seen = fiber.gen;
|
|
201
|
+
return reuseNode(hostOf(hole));
|
|
202
|
+
};
|
|
203
|
+
const materializeAtom = (view, fiber) => {
|
|
204
|
+
const found = fiber.holes.find((h) => h.atom === view.atom && h.fiber && !h.fiber.closed && h.fiber.seen !== fiber.gen && hostOf(h.fiber).isConnected !== false)?.fiber;
|
|
205
|
+
if (found) return [reuse(fiber, found)];
|
|
206
|
+
let unsub;
|
|
207
|
+
const { hole, host } = openHole(fiber, {
|
|
208
|
+
atom: view.atom,
|
|
209
|
+
keep: true
|
|
210
|
+
}, () => unsub?.());
|
|
211
|
+
fiber.runtime.later(() => {
|
|
212
|
+
if (hole.closed) return;
|
|
213
|
+
let last;
|
|
214
|
+
unsub = fiber.registry.subscribe(view.atom, (v) => {
|
|
215
|
+
const next = unwrap(v, last);
|
|
216
|
+
if (next !== KEEP && !hole.closed) {
|
|
217
|
+
last = next;
|
|
218
|
+
self.paint(hole, next);
|
|
219
|
+
}
|
|
220
|
+
}, { immediate: true });
|
|
221
|
+
});
|
|
222
|
+
return [host];
|
|
223
|
+
};
|
|
224
|
+
const materializeStream = (view, fiber) => {
|
|
225
|
+
const { ssr } = fiber.runtime;
|
|
226
|
+
const { hole, host } = openHole(fiber);
|
|
227
|
+
hole.run((ssr ? view.pipe(Stream.take(1)) : view).pipe(Stream.runForEach((v) => Effect.sync(() => self.paint(hole, v))), Effect.tapError((reason) => Effect.sync(() => self.paint(hole, String(reason))))), ssr ? noop : void 0);
|
|
228
|
+
return [host];
|
|
229
|
+
};
|
|
230
|
+
const mountComponent = (fiber, type, props) => {
|
|
231
|
+
const opened = openHole(fiber, { keep: true });
|
|
232
|
+
const { hole } = opened;
|
|
233
|
+
hole.propsBox = { current: props };
|
|
234
|
+
hole.componentType = type;
|
|
235
|
+
fiber.runtime.later(() => {
|
|
236
|
+
if (!hole.closed && hole.renderGen === void 0) rerunHole(hole, type, props);
|
|
237
|
+
});
|
|
238
|
+
return opened;
|
|
239
|
+
};
|
|
240
|
+
const materializeComponent = (view, fiber) => {
|
|
241
|
+
const props = {
|
|
242
|
+
...view.props,
|
|
243
|
+
children: view.children
|
|
244
|
+
};
|
|
245
|
+
const run = view.type;
|
|
246
|
+
const keyed = fiber.keyedHoles;
|
|
247
|
+
const key = String(view.key);
|
|
248
|
+
let prev = hasKey(view) ? keyed?.get(key) : void 0;
|
|
249
|
+
if (prev && prev.seen === fiber.gen && !prev.closed) {
|
|
250
|
+
ilhaWarn("ILHA005", `key "${key}"`);
|
|
251
|
+
prev = void 0;
|
|
252
|
+
} else if (hasKey(view)) {
|
|
253
|
+
if (!prev || prev.closed || prev.componentType !== run) {
|
|
254
|
+
const { hole, host } = mountComponent(fiber, run, props);
|
|
255
|
+
keyed?.set(key, hole);
|
|
256
|
+
return [host];
|
|
257
|
+
}
|
|
258
|
+
if (!sameProps(prev.propsBox?.current, props)) rerunHole(prev, run, props);
|
|
259
|
+
return [reuse(fiber, prev)];
|
|
260
|
+
}
|
|
261
|
+
fiber.componentFrame ??= {
|
|
262
|
+
i: 0,
|
|
263
|
+
slots: []
|
|
264
|
+
};
|
|
265
|
+
const frame = fiber.componentFrame;
|
|
266
|
+
const at = frame.i;
|
|
267
|
+
frame.i += 1;
|
|
268
|
+
const slot = frame.slots[at];
|
|
269
|
+
if (slot && slot.type === run && !slot.hole.closed) {
|
|
270
|
+
rerunHole(slot.hole, run, props);
|
|
271
|
+
return [reuse(fiber, slot.hole)];
|
|
272
|
+
}
|
|
273
|
+
const { hole, host } = mountComponent(fiber, run, props);
|
|
274
|
+
frame.slots[at] = {
|
|
275
|
+
hole,
|
|
276
|
+
type: run
|
|
277
|
+
};
|
|
278
|
+
return [host];
|
|
279
|
+
};
|
|
280
|
+
const materializeElement = (view, fiber) => {
|
|
281
|
+
const tag = view.type;
|
|
282
|
+
const lower = tag.toLowerCase();
|
|
283
|
+
const el = ops.createElement(tag, elementNs(lower, fiber.root));
|
|
284
|
+
if (hasKey(view)) el.setAttribute(KEY_ATTR, String(view.key));
|
|
285
|
+
const late = lower === "select";
|
|
286
|
+
if (!late) applyProps(el, view.props, fiber);
|
|
287
|
+
const inner = {
|
|
288
|
+
...fiber,
|
|
289
|
+
root: asRoot(el)
|
|
290
|
+
};
|
|
291
|
+
for (const c of view.children) el.append(...self.materialize(c, inner));
|
|
292
|
+
if (late) applyProps(el, view.props, fiber);
|
|
293
|
+
return [el];
|
|
294
|
+
};
|
|
295
|
+
self.materialize = (view, fiber) => {
|
|
296
|
+
if (skip(view)) return [ops.createText("")];
|
|
297
|
+
if (isUnsafeHtml(view)) return ops.createRaw(view.html, fiber.root);
|
|
298
|
+
if (isAtomHandle(view)) return materializeAtom(view, fiber);
|
|
299
|
+
if (Stream.isStream(view)) return materializeStream(view, fiber);
|
|
300
|
+
if (isFunction(view)) {
|
|
301
|
+
const { hole, host } = openHole(fiber);
|
|
302
|
+
fiber.runtime.later(() => {
|
|
303
|
+
if (!hole.closed) runSetup(hole, view);
|
|
304
|
+
});
|
|
305
|
+
return [host];
|
|
306
|
+
}
|
|
307
|
+
if (isVNode(view)) {
|
|
308
|
+
if (view.type === Fragment) return view.children.flatMap((c) => self.materialize(c, fiber));
|
|
309
|
+
return isFunction(view.type) ? materializeComponent(view, fiber) : materializeElement(view, fiber);
|
|
310
|
+
}
|
|
311
|
+
if (isIterable(view)) return [...view].flatMap((c) => self.materialize(c, fiber));
|
|
312
|
+
return [ops.createText(String(view))];
|
|
313
|
+
};
|
|
314
|
+
self.paint = (fiber, view) => {
|
|
315
|
+
depth += 1;
|
|
316
|
+
try {
|
|
317
|
+
const gen = (fiber.gen ?? 0) + 1;
|
|
318
|
+
fiber.gen = gen;
|
|
319
|
+
fiber.keyedHoles ??= /* @__PURE__ */ new Map();
|
|
320
|
+
fiber.componentFrame ??= {
|
|
321
|
+
i: 0,
|
|
322
|
+
slots: []
|
|
323
|
+
};
|
|
324
|
+
const frame = fiber.componentFrame;
|
|
325
|
+
frame.i = 0;
|
|
326
|
+
fiber.holes = fiber.holes.filter((h) => h.keep || dispose(h));
|
|
327
|
+
const hydrate = fiber.hydrate === true;
|
|
328
|
+
fiber.hydrate = false;
|
|
329
|
+
if (Array.isArray(view) && view.length > 0 && view.every((v) => isVNode(v) && isFunction(v.type) && hasKey(v))) ops.placeChildren(fiber.root, view.flatMap((v) => self.materialize(v, fiber)));
|
|
330
|
+
else {
|
|
331
|
+
const nodes = self.materialize(view, fiber);
|
|
332
|
+
if (ops.morph) ops.morph(fiber.root, nodes, hydrate);
|
|
333
|
+
else ops.placeChildren(fiber.root, nodes);
|
|
334
|
+
}
|
|
335
|
+
frame.slots.length = frame.i;
|
|
336
|
+
fiber.holes = fiber.holes.filter((h) => !h.keep || h.fiber?.seen === gen || dispose(h));
|
|
337
|
+
for (const [k, hole] of fiber.keyedHoles) if (hole.closed) fiber.keyedHoles.delete(k);
|
|
338
|
+
} finally {
|
|
339
|
+
depth -= 1;
|
|
340
|
+
if (depth === 0) {
|
|
341
|
+
for (const run of pendingRefs.splice(0)) run();
|
|
342
|
+
if (rootListeners.size > 0) notifyRoots();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
return self;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
//#endregion
|
|
350
|
+
export { bindEvents as i, unsafe as n, adoptEvents as r, createPainter as t };
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,82 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as getFiber, t as FiberLocal } from "./runtime-
|
|
3
|
-
//#region src/shared.d.ts
|
|
4
|
-
declare const KEEP: unique symbol;
|
|
5
|
-
//#endregion
|
|
1
|
+
import { _ as View, a as Component, c as IlhaRuntime, m as PropValue, p as PropBag } from "./types-NhEiBvwy.js";
|
|
2
|
+
import { n as getFiber, t as FiberLocal } from "./runtime-BCdKBqYp.js";
|
|
6
3
|
//#region src/paint-core.d.ts
|
|
7
|
-
/** Structural element API shared by DOM `Element
|
|
4
|
+
/** Structural element API shared by DOM `Element`, the SSR shim, and custom hosts. */
|
|
8
5
|
interface PaintEl<Node> {
|
|
9
6
|
readonly tagName: string;
|
|
10
|
-
|
|
7
|
+
readonly isConnected?: boolean;
|
|
11
8
|
setAttribute: (name: string, value: string) => void;
|
|
12
9
|
removeAttribute: (name: string) => void;
|
|
13
|
-
appendChild: (node: Node) => void;
|
|
14
10
|
append: (...nodes: Node[]) => void;
|
|
15
11
|
}
|
|
16
12
|
type FormControlKey = "value" | "checked" | "selected";
|
|
13
|
+
/**
|
|
14
|
+
* Host operations behind the shared painter. Fiber roots are the mount root
|
|
15
|
+
* or a hole host element; hosts cast them back to their own node type.
|
|
16
|
+
*/
|
|
17
17
|
interface PaintOps<Node, El extends PaintEl<Node>> {
|
|
18
18
|
createElement: (tag: string, ns?: string) => El;
|
|
19
19
|
createText: (text: string) => Node;
|
|
20
|
-
/**
|
|
21
|
-
* Parse pre-rendered HTML into nodes under `parent` (DOM:
|
|
22
|
-
* context-aware fragment parse; SSR: raw node, parent unused).
|
|
23
|
-
*/
|
|
20
|
+
/** Parse pre-rendered HTML into nodes for `parent`. */
|
|
24
21
|
createRaw: (html: string, parent: ParentNode) => Node[];
|
|
25
|
-
/** Display-contents span marking a dynamic hole; `null` omits the slot attr. */
|
|
26
|
-
createSlotHost: (slotId: string | null) => El;
|
|
27
|
-
/** Fiber root for a host element (SSR: cast SsrEl up to ParentNode). */
|
|
28
|
-
asRoot: (el: El) => ParentNode;
|
|
29
|
-
/** Insertable node for a reused fiber/island host. */
|
|
30
|
-
asNode: (host: ParentNode) => Node;
|
|
31
|
-
/**
|
|
32
|
-
* Node a render returns for a reused live host (DOM: a stand-in the morph
|
|
33
|
-
* swaps for the host in place, so the host is never detached).
|
|
34
|
-
*/
|
|
35
|
-
reuseNode: (host: ParentNode) => Node;
|
|
36
22
|
/** Make `root`'s children exactly `nodes`, moving as little as possible. */
|
|
37
23
|
placeChildren: (root: ParentNode, nodes: Node[]) => void;
|
|
38
|
-
/** Append one node to `root`. Only `Painter.insert` calls it. */
|
|
39
|
-
appendRoot: (root: ParentNode, node: Node) => void;
|
|
40
|
-
/** Element view of a host (SSR: SsrEl masquerades as Element). */
|
|
41
|
-
asElement: (el: El) => Element;
|
|
42
24
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
25
|
+
* Reconcile `root` with a fresh render, keeping live nodes (DOM morph).
|
|
26
|
+
* Defaults to `placeChildren`. `hydrate` marks a mount's first paint.
|
|
45
27
|
*/
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
|
|
28
|
+
morph?: (root: ParentNode, nodes: Node[], hydrate: boolean) => void;
|
|
29
|
+
/** Node a render returns for a reused host. Defaults to the host itself. */
|
|
30
|
+
reuseNode?: (host: El) => Node;
|
|
31
|
+
/** The element a rendered element ended up as after `morph`. */
|
|
32
|
+
committed?: (el: El) => El;
|
|
49
33
|
/**
|
|
50
34
|
* Take a prop as the component wrote it, before it is stringified. Return
|
|
51
|
-
* `true` to claim it
|
|
52
|
-
* and form-control handling below. A host that is not a DOM uses it to keep
|
|
53
|
-
* object values. Event props, `children`, `key` and `ref` never reach it.
|
|
35
|
+
* `true` to claim it. Event props, `children`, `key` and `ref` never reach it.
|
|
54
36
|
*/
|
|
55
37
|
setProp?: (el: El, key: string, value: PropValue) => boolean;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
38
|
+
/** Defaults to the `style` attribute. */
|
|
39
|
+
setStyle?: (el: El, css: string) => void;
|
|
40
|
+
/** Defaults to attribute writes. */
|
|
41
|
+
setFormControl?: (el: El, key: FormControlKey, v: PropValue) => void;
|
|
42
|
+
bindEvents?: (el: El, props: PropBag, fiber: FiberLocal) => void;
|
|
59
43
|
}
|
|
60
44
|
/** Painter surface returned by `createPainter`, shared by every host. */
|
|
61
|
-
interface Painter<Node
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
commit: (fn: () => void) => void;
|
|
65
|
-
/**
|
|
66
|
-
* Runs `fn` as one paint of `fiber`: the keyed components it materializes
|
|
67
|
-
* stay alive, and the ones it no longer reaches are closed afterwards.
|
|
68
|
-
*/
|
|
69
|
-
pass: (fiber: FiberLocal, fn: () => void) => void;
|
|
70
|
-
disposeHoles: (fiber: FiberLocal, opts?: {
|
|
71
|
-
morph?: boolean;
|
|
72
|
-
}) => void;
|
|
73
|
-
insert: (fiber: FiberLocal, nodes: Node[]) => void;
|
|
45
|
+
interface Painter<Node> {
|
|
46
|
+
/** Paint `view` into `fiber.root`, reusing the holes it still reaches. */
|
|
47
|
+
paint: (fiber: FiberLocal, view: View) => void;
|
|
74
48
|
materialize: (view: View, fiber: FiberLocal) => Node[];
|
|
75
|
-
paintHole: (fiber: FiberLocal, view: View | typeof KEEP) => void;
|
|
76
49
|
}
|
|
77
50
|
//#endregion
|
|
78
51
|
//#region src/events.d.ts
|
|
79
|
-
export declare const bindEvents: (
|
|
52
|
+
export declare const bindEvents: (el: Element, props: PropBag, fiber: FiberLocal) => void;
|
|
80
53
|
//#endregion
|
|
81
54
|
//#region src/renderer.d.ts
|
|
82
55
|
export interface RendererMountOptions {
|
|
@@ -99,21 +72,12 @@ export interface RendererHandle {
|
|
|
99
72
|
ready: Promise<null>;
|
|
100
73
|
runtime: IlhaRuntime;
|
|
101
74
|
}
|
|
102
|
-
export interface HostRenderer<Node
|
|
75
|
+
export interface HostRenderer<Node> {
|
|
103
76
|
/** The shared painter, for advanced callers that paint holes themselves. */
|
|
104
|
-
readonly painter: Painter<Node
|
|
77
|
+
readonly painter: Painter<Node>;
|
|
105
78
|
mount: (root: ParentNode, fn: Component, options?: RendererMountOptions) => RendererHandle;
|
|
106
79
|
}
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
*
|
|
110
|
-
* Unlike the DOM painter this materializes fresh host nodes on every paint and
|
|
111
|
-
* attempts no morph: the host receives the new child list through
|
|
112
|
-
* `ops.placeChildren` and decides what to keep. Holes that must survive a
|
|
113
|
-
* repaint (atoms, islands, keyed rows, positional components) stay alive across
|
|
114
|
-
* paints and hand their host node back through `ops.reuseNode`, so their state
|
|
115
|
-
* is never remounted.
|
|
116
|
-
*/
|
|
117
|
-
export declare const createRenderer: <Node, El extends PaintEl<Node> & Node>(ops: PaintOps<Node, El>) => HostRenderer<Node, El>;
|
|
80
|
+
/** Build a renderer for a host described by `ops`. */
|
|
81
|
+
export declare const createRenderer: <Node, El extends PaintEl<Node> & Node>(ops: PaintOps<Node, El>) => HostRenderer<Node>;
|
|
118
82
|
//#endregion
|
|
119
83
|
export { type Component, type FiberLocal, type FormControlKey, type IlhaRuntime, type PaintEl, type PaintOps, type Painter, type PropBag, type PropValue, type View, getFiber };
|
package/dist/renderer.js
CHANGED
|
@@ -1,68 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { a as startRoot, h as getFiber } from "./start-B-e1dG5U.js";
|
|
2
|
+
import { i as bindEvents, t as createPainter } from "./paint-core-D1n4lbpF.js";
|
|
3
3
|
|
|
4
4
|
//#region src/renderer.ts
|
|
5
5
|
/**
|
|
6
6
|
* Host renderer seam.
|
|
7
7
|
*
|
|
8
|
-
* `mount()` and `renderToString()` ship the two painters ilha uses on the web
|
|
9
|
-
* the
|
|
10
|
-
*
|
|
11
|
-
* that is not a DOM — by letting the caller supply `PaintOps`.
|
|
8
|
+
* `mount()` and `renderToString()` ship the two painters ilha uses on the web.
|
|
9
|
+
* This entry exposes the same painter for a third host — a native toolkit, a
|
|
10
|
+
* test harness, or any tree that is not a DOM — through `PaintOps`.
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* A host mounted through `createRenderer` is placed with `placeChildren` only:
|
|
18
|
-
* every paint hands over the complete child list of the root it painted.
|
|
19
|
-
* `appendRoot` is never called on this path.
|
|
20
|
-
*/
|
|
21
|
-
/**
|
|
22
|
-
* Build a renderer for a host described by `ops`.
|
|
23
|
-
*
|
|
24
|
-
* Unlike the DOM painter this materializes fresh host nodes on every paint and
|
|
25
|
-
* attempts no morph: the host receives the new child list through
|
|
26
|
-
* `ops.placeChildren` and decides what to keep. Holes that must survive a
|
|
27
|
-
* repaint (atoms, islands, keyed rows, positional components) stay alive across
|
|
28
|
-
* paints and hand their host node back through `ops.reuseNode`, so their state
|
|
29
|
-
* is never remounted.
|
|
12
|
+
* Every paint materializes fresh host nodes and hands the complete child list
|
|
13
|
+
* of the painted root to `ops.placeChildren` (or `ops.morph`). Holes that
|
|
14
|
+
* survive a repaint (atoms, keyed rows, positional components) hand their host
|
|
15
|
+
* node back through `ops.reuseNode`, so their state is never remounted.
|
|
30
16
|
*/
|
|
17
|
+
/** Build a renderer for a host described by `ops`. */
|
|
31
18
|
const createRenderer = (ops) => {
|
|
32
19
|
const painter = createPainter(ops);
|
|
33
|
-
const paint = (fiber, view) => {
|
|
34
|
-
painter.commit(() => {
|
|
35
|
-
painter.pass(fiber, () => {
|
|
36
|
-
fiber.islandFrame ??= {
|
|
37
|
-
i: 0,
|
|
38
|
-
slots: []
|
|
39
|
-
};
|
|
40
|
-
painter.disposeHoles(fiber, { morph: true });
|
|
41
|
-
ops.placeChildren(fiber.root, painter.materialize(view, fiber));
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
20
|
return {
|
|
46
|
-
mount(root, fn,
|
|
47
|
-
const runtime =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
paint,
|
|
52
|
-
root
|
|
53
|
-
runtime,
|
|
54
|
-
settle: resolve
|
|
21
|
+
mount: (root, fn, { onError, onRuntime } = {}) => {
|
|
22
|
+
const { close, ready, runtime } = startRoot({
|
|
23
|
+
fn,
|
|
24
|
+
onError,
|
|
25
|
+
onRuntime,
|
|
26
|
+
paint: painter.paint,
|
|
27
|
+
root
|
|
55
28
|
});
|
|
56
|
-
runtime.begin();
|
|
57
|
-
runtime.setIdle(resolve);
|
|
58
|
-
runSetup(fiber, fn);
|
|
59
|
-
runtime.end();
|
|
60
29
|
return {
|
|
61
30
|
ready,
|
|
62
31
|
runtime,
|
|
63
32
|
unmount: () => {
|
|
64
|
-
|
|
65
|
-
runtime.close();
|
|
33
|
+
close();
|
|
66
34
|
ops.placeChildren(root, []);
|
|
67
35
|
}
|
|
68
36
|
};
|