ilha 0.15.2 → 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.
@@ -1,513 +0,0 @@
1
- import { _ as isString, c as KEEP, d as defer, g as isObject, m as isFunction, r as isVNode, u as SLOT_ATTR, y as unwrap } from "./vnode-BHUY61Rm.js";
2
- import { E as makeRuntime, S as closeFiber, T as makeRootFiber, a as markControlled, c as standInFor, d as runSetup, i as insertRendered, l as bindEvents, o as morphInner, r as committedElement, s as placeChildren, t as createPainter, u as eventTypeFromProp } from "./paint-core-B7jENZwS.js";
3
-
4
- //#region src/paint.ts
5
- const noopDisconnect = () => {};
6
- const core$1 = createPainter({
7
- appendRoot: (root, node) => insertRendered(root, node, null),
8
- asElement: (el) => el,
9
- asNode: (host) => host,
10
- asRoot: (el) => el,
11
- bindEvents,
12
- committed: committedElement,
13
- createElement: (tag, ns) => ns ? document.createElementNS(ns, tag) : document.createElement(tag),
14
- createRaw: (html, parent) => {
15
- const range = document.createRange();
16
- range.selectNodeContents(parent);
17
- return [...range.createContextualFragment(html).childNodes];
18
- },
19
- createSlotHost: (slotId) => {
20
- const host = document.createElement("span");
21
- if (slotId !== null) host.setAttribute(SLOT_ATTR, slotId);
22
- host.style.display = "contents";
23
- return host;
24
- },
25
- createText: (text) => document.createTextNode(text),
26
- disconnect: noopDisconnect,
27
- placeChildren: (root, nodes) => {
28
- if (root instanceof Element) placeChildren(root, nodes);
29
- },
30
- reuseNode: (host) => host instanceof Element && host.isConnected ? standInFor(host) : host,
31
- setFormControl: (el, key, v) => {
32
- const node = el;
33
- if (v !== null && v !== void 0) markControlled(el, key);
34
- if (key === "value") {
35
- const text = v === null || v === void 0 ? "" : String(v);
36
- if (el.localName === "textarea") {
37
- node.defaultValue = text;
38
- node.value = text;
39
- } else if (el.localName === "select") node.value = text;
40
- else if (v === null || v === void 0) {
41
- el.removeAttribute("value");
42
- node.value = "";
43
- } else {
44
- el.setAttribute("value", text);
45
- node.value = text;
46
- }
47
- } else if (key === "checked") {
48
- node.checked = Boolean(v);
49
- if (v) el.setAttribute("checked", "");
50
- else el.removeAttribute("checked");
51
- } else {
52
- node.selected = Boolean(v);
53
- if (v) el.setAttribute("selected", "");
54
- else el.removeAttribute("selected");
55
- }
56
- },
57
- setStyle: (el, css) => {
58
- el.style.cssText = css;
59
- }
60
- });
61
- const refreshAtomHost = (fiber, atom, host, hole) => {
62
- if (hole.closed || !host.isConnected) return;
63
- const next = unwrap(fiber.registry.get(atom));
64
- if (next === KEEP) return;
65
- core$1.paintHole(hole, next);
66
- };
67
- const refreshAllAtomHosts = (fiber) => {
68
- for (const h of fiber.holes) if (h.atom && h.host && h.holeFiber) refreshAtomHost(fiber, h.atom, h.host, h.holeFiber);
69
- };
70
- const pruneDisconnectedAtomHoles = (fiber) => {
71
- fiber.holes = fiber.holes.filter((h) => {
72
- if (!h.atom || !h.host || !h.holeFiber) return true;
73
- if (h.host.isConnected) return true;
74
- h.dispose();
75
- return false;
76
- });
77
- };
78
- const canHydrate = (fiber, view) => {
79
- if (!isVNode(view) || !isString(view.type)) return false;
80
- const el = fiber.root.firstElementChild;
81
- return !!el && el.tagName === view.type.toUpperCase();
82
- };
83
- const paintHydrate = (fiber, view) => {
84
- const el = fiber.root.firstElementChild;
85
- if (!el) return;
86
- fiber.hydrate = false;
87
- fiber.liveEl = el;
88
- core$1.applyProps(el, view.props, fiber);
89
- const childFiber = {
90
- ...fiber,
91
- holes: fiber.holes,
92
- root: el
93
- };
94
- el.replaceChildren();
95
- for (const c of view.children) for (const n of core$1.materialize(c, childFiber)) insertRendered(el, n, null);
96
- };
97
- const paintMorphLive = (fiber, view) => {
98
- if (!fiber.liveEl) return;
99
- core$1.disposeHoles(fiber, { morph: true });
100
- core$1.applyProps(fiber.liveEl, view.props, fiber);
101
- const liveNs = fiber.liveEl.namespaceURI;
102
- const tmp = liveNs && liveNs !== "http://www.w3.org/1999/xhtml" ? document.createElementNS(liveNs, fiber.liveEl.localName) : document.createElement(fiber.liveEl.localName);
103
- const childFiber = {
104
- ...fiber,
105
- holes: fiber.holes,
106
- root: tmp
107
- };
108
- for (const c of view.children) for (const n of core$1.materialize(c, childFiber)) tmp.append(n);
109
- morphInner(fiber.liveEl, tmp);
110
- refreshAllAtomHosts(fiber);
111
- pruneDisconnectedAtomHoles(fiber);
112
- };
113
- const paintRoot = (fiber, view) => {
114
- fiber.islandFrame ??= {
115
- i: 0,
116
- slots: []
117
- };
118
- if (fiber.hydrate) {
119
- if (canHydrate(fiber, view)) {
120
- paintHydrate(fiber, view);
121
- return;
122
- }
123
- console.warn("[ilha] hydrate mismatch, full mount");
124
- fiber.hydrate = false;
125
- if (fiber.root instanceof Element) fiber.root.replaceChildren();
126
- }
127
- if (fiber.liveEl && isVNode(view) && isString(view.type) && fiber.liveEl.localName === view.type) {
128
- paintMorphLive(fiber, view);
129
- return;
130
- }
131
- core$1.disposeHoles(fiber, { morph: true });
132
- if (fiber.root instanceof Element && fiber.root.childNodes.length > 0) {
133
- const tmp = document.createElement("div");
134
- for (const n of core$1.materialize(view, fiber)) tmp.append(n);
135
- morphInner(fiber.root, tmp);
136
- const first = fiber.root.firstElementChild;
137
- if (first) fiber.liveEl = first;
138
- return;
139
- }
140
- core$1.insert(fiber, core$1.materialize(view, fiber));
141
- const first = fiber.root instanceof Element ? fiber.root.firstElementChild : null;
142
- if (first) fiber.liveEl = first;
143
- };
144
- const paint$1 = (fiber, view) => {
145
- core$1.commit(() => {
146
- core$1.pass(fiber, () => {
147
- paintRoot(fiber, view);
148
- });
149
- });
150
- };
151
-
152
- //#endregion
153
- //#region src/snapshot.ts
154
- const MAX_SNAPSHOT_CHARS = 262144;
155
- const MAX_SNAPSHOT_DEPTH = 32;
156
- const exceedsMaxDepth = (value, depth) => {
157
- if (depth > MAX_SNAPSHOT_DEPTH) return true;
158
- if (value === null || !isObject(value)) {
159
- if (Array.isArray(value)) {
160
- for (const item of value) if (exceedsMaxDepth(item, depth + 1)) return true;
161
- }
162
- return false;
163
- }
164
- const obj = value;
165
- for (const key of Object.keys(obj)) if (exceedsMaxDepth(obj[key], depth + 1)) return true;
166
- return false;
167
- };
168
- const deleteUnsafeKey = (obj, key) => {
169
- if (Object.hasOwn(obj, key)) Reflect.deleteProperty(obj, key);
170
- };
171
- const stripUnsafeKeys = (value) => {
172
- if (Array.isArray(value)) {
173
- for (const item of value) stripUnsafeKeys(item);
174
- return;
175
- }
176
- if (value === null || !isObject(value)) return;
177
- const obj = value;
178
- deleteUnsafeKey(obj, "__proto__");
179
- deleteUnsafeKey(obj, "constructor");
180
- deleteUnsafeKey(obj, "prototype");
181
- for (const key of Object.getOwnPropertyNames(obj)) stripUnsafeKeys(obj[key]);
182
- };
183
- const safeParseSnapshot = (raw) => {
184
- if (raw.length > MAX_SNAPSHOT_CHARS) return;
185
- let parsed;
186
- try {
187
- parsed = JSON.parse(raw);
188
- } catch {
189
- return;
190
- }
191
- if (exceedsMaxDepth(parsed, 1)) return;
192
- if (!isObject(parsed) || Array.isArray(parsed)) return;
193
- stripUnsafeKeys(parsed);
194
- return parsed;
195
- };
196
- const encodeSnapshot = (values) => JSON.stringify({ v: values });
197
- const decodeSnapshot = (raw) => {
198
- const values = safeParseSnapshot(raw)?.v;
199
- return Array.isArray(values) ? values : void 0;
200
- };
201
-
202
- //#endregion
203
- //#region src/ssr-dom.ts
204
- const VOID = /* @__PURE__ */ new Set([
205
- "area",
206
- "base",
207
- "br",
208
- "col",
209
- "embed",
210
- "hr",
211
- "img",
212
- "input",
213
- "link",
214
- "meta",
215
- "param",
216
- "source",
217
- "track",
218
- "wbr"
219
- ]);
220
- /** Tags whose children are raw text — do not HTML-escape content. */
221
- const RAW_TEXT = /* @__PURE__ */ new Set(["script", "style"]);
222
- const escapeText = (s) => s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
223
- const escapeAttr = (s) => escapeText(s).replaceAll("\"", "&quot;");
224
- const createSsrRaw = (html) => ({
225
- html,
226
- kind: "raw"
227
- });
228
- const createSsrText = (text) => ({
229
- kind: "text",
230
- text
231
- });
232
- const serializeRawText = (tag, text) => {
233
- if (new RegExp(`</${tag}\\b`, "iu").test(text)) throw new Error(`ilha: unsafe raw text in <${tag}> (contains "</${tag}")`);
234
- return text;
235
- };
236
- const serializeNode = (node) => {
237
- if (node.kind === "text") return escapeText(node.text);
238
- if (node.kind === "raw") return node.html;
239
- const parts = [];
240
- for (const [name, value] of node.attrs) parts.push(`${name}="${escapeAttr(value)}"`);
241
- const attr = parts.length > 0 ? ` ${parts.join(" ")}` : "";
242
- const { tag } = node;
243
- if (VOID.has(tag)) return `<${tag}${attr}>`;
244
- if (RAW_TEXT.has(tag)) return `<${tag}${attr}>${serializeRawText(tag, node.textContent)}</${tag}>`;
245
- return `<${tag}${attr}>${node.children.map(serializeNode).join("")}</${tag}>`;
246
- };
247
- const toDataAttr = (key) => `data-${key.replaceAll(/[A-Z]/gu, (ch) => `-${ch.toLowerCase()}`)}`;
248
- const isSymbolKey = (value) => Object.prototype.toString.call(value) === "[object Symbol]";
249
- const createSsrElement = (tag) => {
250
- const lower = tag.toLowerCase();
251
- const children = [];
252
- const el = {
253
- append: (...nodes) => {
254
- children.push(...nodes);
255
- },
256
- appendChild: (node) => {
257
- children.push(node);
258
- },
259
- attrs: /* @__PURE__ */ new Map(),
260
- children,
261
- dataset: new Proxy({}, {
262
- get: (_t, prop) => {
263
- if (prop === "undefined" || isSymbolKey(prop)) return;
264
- return el.getAttribute(toDataAttr(prop)) ?? void 0;
265
- },
266
- set: (_t, prop, value) => {
267
- if (isSymbolKey(prop)) return false;
268
- const attr = toDataAttr(prop);
269
- if (value === void 0 || value === null) el.removeAttribute(attr);
270
- else el.setAttribute(attr, String(value));
271
- return true;
272
- }
273
- }),
274
- getAttribute: (name) => {
275
- const value = el.attrs.get(name);
276
- return value === void 0 ? null : value;
277
- },
278
- hasAttribute: (name) => el.attrs.has(name),
279
- isConnected: true,
280
- kind: "element",
281
- localName: lower,
282
- removeAttribute: (name) => {
283
- el.attrs.delete(name);
284
- },
285
- replaceChildren: (...nodes) => {
286
- children.length = 0;
287
- children.push(...nodes);
288
- },
289
- setAttribute: (name, value) => {
290
- el.attrs.set(name, value);
291
- },
292
- tag: lower,
293
- tagName: lower.toUpperCase(),
294
- get textContent() {
295
- return children.map((n) => {
296
- if (n.kind === "text") return n.text;
297
- if (n.kind === "raw") return n.html;
298
- return n.textContent;
299
- }).join("");
300
- },
301
- set textContent(value) {
302
- children.length = 0;
303
- children.push(createSsrText(value));
304
- }
305
- };
306
- return el;
307
- };
308
- const createSsrRoot = () => {
309
- const children = [];
310
- return {
311
- append: (...nodes) => {
312
- children.push(...nodes);
313
- },
314
- appendChild: (node) => {
315
- children.push(node);
316
- },
317
- children,
318
- get innerHTML() {
319
- return children.map(serializeNode).join("");
320
- },
321
- kind: "root",
322
- replaceChildren: () => {
323
- children.length = 0;
324
- }
325
- };
326
- };
327
-
328
- //#endregion
329
- //#region src/ssr-paint.ts
330
- const ssrAs = (n) => n;
331
- const ACTION_CALL = Symbol.for("ilha.actionCall");
332
- const bindSsrEvents = (el, props, fiber) => {
333
- const seen = /* @__PURE__ */ new Set();
334
- for (const key of Object.keys(props)) {
335
- const type = eventTypeFromProp(key);
336
- if (!type || seen.has(type)) continue;
337
- seen.add(type);
338
- const fn = props[key];
339
- if (!isFunction(fn)) continue;
340
- const branded = fn[ACTION_CALL];
341
- const rec = branded?.k ? {
342
- a: branded.a ?? [],
343
- k: branded.k
344
- } : void 0;
345
- if (!rec) continue;
346
- const id = `${type}:${fiber.runtime.ssrEventI}`;
347
- fiber.runtime.ssrEventI += 1;
348
- fiber.runtime.ssrActions[id] = rec;
349
- const existing = el.dataset.ilhaOn;
350
- el.dataset.ilhaOn = existing ? `${existing},${id}` : id;
351
- }
352
- };
353
- const WHITESPACE_RUN = /\s+/gu;
354
- /**
355
- * Mark the first option whose value is `value` selected and clear the rest,
356
- * as setting a live select's `.value` does. Returns whether one matched.
357
- */
358
- const selectOption = (parent, value, found) => {
359
- let matched = found;
360
- for (const child of parent.children) {
361
- if (child.kind !== "element") continue;
362
- if (child.localName === "optgroup") matched = selectOption(child, value, matched);
363
- else if (child.localName === "option") {
364
- const optionValue = child.getAttribute("value") ?? child.textContent.trim().replaceAll(WHITESPACE_RUN, " ");
365
- if (!matched && optionValue === value) {
366
- child.setAttribute("selected", "");
367
- matched = true;
368
- } else child.removeAttribute("selected");
369
- }
370
- }
371
- return matched;
372
- };
373
- const core = createPainter({
374
- appendRoot: (root, node) => ssrAs(root).append(node),
375
- asElement: (el) => ssrAs(el),
376
- asNode: (host) => ssrAs(host),
377
- asRoot: (el) => ssrAs(el),
378
- bindEvents: bindSsrEvents,
379
- committed: (el) => el,
380
- createElement: createSsrElement,
381
- createRaw: (html, _parent) => [createSsrRaw(html)],
382
- createSlotHost: (slotId) => {
383
- const host = createSsrElement("span");
384
- if (slotId !== null) host.setAttribute(SLOT_ATTR, slotId);
385
- host.setAttribute("style", "display:contents");
386
- return host;
387
- },
388
- createText: createSsrText,
389
- disconnect: (el) => {
390
- el.isConnected = false;
391
- },
392
- placeChildren: (root, nodes) => {
393
- const host = ssrAs(root);
394
- host.replaceChildren();
395
- for (const n of nodes) host.append(n);
396
- },
397
- reuseNode: (host) => ssrAs(host),
398
- setFormControl: (el, key, v) => {
399
- if (key === "value") {
400
- const text = String(v ?? "");
401
- if (el.localName === "textarea") el.textContent = text;
402
- else if (el.localName === "select") {
403
- if (v !== null && v !== void 0) selectOption(el, text, false);
404
- } else el.setAttribute("value", text);
405
- } else if (v) el.setAttribute(key, "");
406
- else el.removeAttribute(key);
407
- },
408
- setStyle: (el, css) => el.setAttribute("style", css)
409
- });
410
- const paint = (fiber, view) => {
411
- fiber.islandFrame ??= {
412
- i: 0,
413
- slots: []
414
- };
415
- core.pass(fiber, () => {
416
- core.disposeHoles(fiber);
417
- ssrAs(fiber.root).replaceChildren();
418
- core.insert(fiber, core.materialize(view, fiber));
419
- });
420
- };
421
- const attachSsr = (fn, opts) => {
422
- const root = createSsrRoot();
423
- const runtime = makeRuntime({
424
- onError: opts?.onError,
425
- ssr: true,
426
- ssrCapture: opts?.ssrCapture === true
427
- });
428
- const { promise: ready, resolve } = defer();
429
- const fiber = makeRootFiber({
430
- paint,
431
- root: ssrAs(root),
432
- runtime,
433
- settle: resolve
434
- });
435
- runtime.begin();
436
- runtime.setIdle(resolve);
437
- runSetup(fiber, fn);
438
- runtime.end();
439
- return {
440
- ready,
441
- root,
442
- runtime,
443
- unmount: () => {
444
- closeFiber(fiber);
445
- runtime.close();
446
- root.replaceChildren();
447
- }
448
- };
449
- };
450
-
451
- //#endregion
452
- //#region src/mount.ts
453
- const readHydrate = (el) => {
454
- const raw = (el.matches("[data-ilha]") ? el : el.querySelector("[data-ilha]"))?.dataset.ilhaState;
455
- if (raw) return decodeSnapshot(raw);
456
- const first = el.firstElementChild;
457
- if (first?.tagName === "TEMPLATE" && first.matches("[data-ilha-state]")) {
458
- const tplRaw = first.dataset.ilhaState;
459
- first.remove();
460
- return tplRaw ? decodeSnapshot(tplRaw) : void 0;
461
- }
462
- };
463
- const delay = (ms) => {
464
- const { promise, resolve } = defer();
465
- setTimeout(resolve, ms);
466
- return promise;
467
- };
468
- const attach = (el, fn, opts) => {
469
- if (!opts?.hydrate) el.innerHTML = "";
470
- const runtime = makeRuntime({
471
- hydrate: opts?.hydrate ? readHydrate(el) : void 0,
472
- onError: opts?.onError
473
- });
474
- const { promise: ready, resolve } = defer();
475
- const fiber = makeRootFiber({
476
- paint: paint$1,
477
- root: el,
478
- runtime,
479
- settle: resolve
480
- });
481
- if (opts?.hydrate) fiber.hydrate = true;
482
- runtime.begin();
483
- runtime.setIdle(resolve);
484
- runSetup(fiber, fn);
485
- runtime.end();
486
- return {
487
- ready,
488
- runtime,
489
- unmount: () => {
490
- closeFiber(fiber);
491
- runtime.close();
492
- if (!opts?.hydrate) el.innerHTML = "";
493
- }
494
- };
495
- };
496
- const mount = (el, fn, opts) => attach(el, fn, opts).unmount;
497
- const renderToString = async (fn, opts) => {
498
- const snapshot = opts?.snapshot !== false;
499
- const markers = opts?.markers !== false;
500
- const { unmount, ready, runtime, root } = attachSsr(fn, { ssrCapture: opts?.captureActions === true });
501
- const timeout = opts?.timeout;
502
- await (timeout === void 0 ? ready : Promise.race([ready, delay(timeout)]));
503
- let inner = root.innerHTML;
504
- const actions = runtime.ssrActions;
505
- if (Object.keys(actions).length > 0) inner = `<template data-ilha-actions="${escapeAttr(JSON.stringify(actions))}"></template>${inner}`;
506
- const snap = snapshot ? encodeSnapshot(runtime.ssrValues) : void 0;
507
- unmount();
508
- if (!markers) return snap ? `<template data-ilha-state="${escapeAttr(snap)}"></template>${inner}` : inner;
509
- return `<div data-ilha${snap ? ` data-ilha-state="${escapeAttr(snap)}"` : ""}>${inner}</div>`;
510
- };
511
-
512
- //#endregion
513
- export { renderToString as n, mount as t };