ilha 0.7.1 → 0.8.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/dist/index-Dy3wGh4z.d.ts +297 -0
- package/dist/index.d.ts +2 -308
- package/dist/index.js +6 -1888
- package/dist/jsx-dev-runtime.d.ts +1 -2
- package/dist/jsx-dev-runtime.js +1 -2
- package/dist/jsx-runtime-BSfybS-R.d.ts +37 -0
- package/dist/jsx-runtime.d.ts +2 -3
- package/dist/jsx-runtime.js +1 -2
- package/package.json +23 -1
- package/dist/jsx-types-DSrM2NdI.d.ts +0 -26
package/dist/index.js
CHANGED
|
@@ -1,1888 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
if (__DEV__) console.warn(`[ilha] ${msg}`);
|
|
6
|
-
}
|
|
7
|
-
function shallowEqualInput(a, b) {
|
|
8
|
-
if (Object.is(a, b)) return true;
|
|
9
|
-
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
|
|
10
|
-
const ak = Object.keys(a);
|
|
11
|
-
const bk = Object.keys(b);
|
|
12
|
-
if (ak.length !== bk.length) return false;
|
|
13
|
-
for (const k of ak) {
|
|
14
|
-
if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
|
|
15
|
-
if (!Object.is(a[k], b[k])) return false;
|
|
16
|
-
}
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
function extractDirectChildSlotIdsInOrder(html) {
|
|
20
|
-
if (typeof document === "undefined") return [];
|
|
21
|
-
const tpl = document.createElement("template");
|
|
22
|
-
tpl.innerHTML = html;
|
|
23
|
-
const root = tpl.content;
|
|
24
|
-
const ids = [];
|
|
25
|
-
for (const el of root.querySelectorAll("[data-ilha-slot]")) {
|
|
26
|
-
if (!(el instanceof Element)) continue;
|
|
27
|
-
let parent = el.parentElement ?? el.parentNode;
|
|
28
|
-
let nested = false;
|
|
29
|
-
while (parent && parent !== root) {
|
|
30
|
-
if (parent instanceof Element && parent.hasAttribute("data-ilha-slot")) {
|
|
31
|
-
nested = true;
|
|
32
|
-
break;
|
|
33
|
-
}
|
|
34
|
-
parent = parent instanceof Element ? parent.parentElement ?? parent.parentNode : parent.parentNode;
|
|
35
|
-
}
|
|
36
|
-
if (!nested) ids.push(el.getAttribute("data-ilha-slot"));
|
|
37
|
-
}
|
|
38
|
-
return ids;
|
|
39
|
-
}
|
|
40
|
-
function extractSlotPropsAttr(html, slotId) {
|
|
41
|
-
const escaped = slotId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
42
|
-
const withProps = new RegExp(`<div\\s[^>]*data-ilha-slot="${escaped}"[^>]*data-ilha-props='([^']*)'`);
|
|
43
|
-
const m = html.match(withProps);
|
|
44
|
-
if (m) return m[1];
|
|
45
|
-
const withoutProps = new RegExp(`<div\\s[^>]*data-ilha-slot="${escaped}"`);
|
|
46
|
-
return html.match(withoutProps) ? "" : null;
|
|
47
|
-
}
|
|
48
|
-
function isStableInlineSlotMount(initialHtml, renderedHtml, slotIds) {
|
|
49
|
-
const initialOrder = extractDirectChildSlotIdsInOrder(initialHtml);
|
|
50
|
-
const renderedOrder = extractDirectChildSlotIdsInOrder(renderedHtml);
|
|
51
|
-
if (initialOrder.length !== renderedOrder.length || initialOrder.some((id, i) => id !== renderedOrder[i])) return false;
|
|
52
|
-
for (const id of slotIds) {
|
|
53
|
-
const initialProps = extractSlotPropsAttr(initialHtml, id);
|
|
54
|
-
const renderedProps = extractSlotPropsAttr(renderedHtml, id);
|
|
55
|
-
if (initialProps === null || renderedProps === null) return false;
|
|
56
|
-
if (initialProps !== renderedProps) return false;
|
|
57
|
-
}
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
function serializeSlotProps(props) {
|
|
61
|
-
return props === void 0 ? "" : JSON.stringify(props);
|
|
62
|
-
}
|
|
63
|
-
const MORPH_CONTROLLER_SLOT_SELECTOR = "[data-slot]";
|
|
64
|
-
const MORPH_CONTROLLER_ATTRS = new Set([
|
|
65
|
-
"data-checked",
|
|
66
|
-
"data-unchecked",
|
|
67
|
-
"data-indeterminate",
|
|
68
|
-
"aria-checked",
|
|
69
|
-
"data-open",
|
|
70
|
-
"data-closed",
|
|
71
|
-
"data-state",
|
|
72
|
-
"aria-expanded",
|
|
73
|
-
"aria-hidden",
|
|
74
|
-
"data-selected",
|
|
75
|
-
"data-panel-open"
|
|
76
|
-
]);
|
|
77
|
-
function shouldPreserveMorphAttr(el, name) {
|
|
78
|
-
return el.matches(MORPH_CONTROLLER_SLOT_SELECTOR) && MORPH_CONTROLLER_ATTRS.has(name);
|
|
79
|
-
}
|
|
80
|
-
function syncAttributes(from, to) {
|
|
81
|
-
for (const { name, value } of to.attributes) {
|
|
82
|
-
if (shouldPreserveMorphAttr(from, name)) continue;
|
|
83
|
-
if (from.getAttribute(name) !== value) from.setAttribute(name, value);
|
|
84
|
-
}
|
|
85
|
-
for (const { name } of Array.from(from.attributes)) {
|
|
86
|
-
if (shouldPreserveMorphAttr(from, name)) continue;
|
|
87
|
-
if (!to.hasAttribute(name)) from.removeAttribute(name);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
function morphChildren(fromParent, toParent) {
|
|
91
|
-
const fromNodes = Array.from(fromParent.childNodes);
|
|
92
|
-
const toNodes = Array.from(toParent.childNodes);
|
|
93
|
-
for (let i = fromNodes.length - 1; i >= toNodes.length; i--) fromNodes[i].remove();
|
|
94
|
-
for (let i = 0; i < toNodes.length; i++) {
|
|
95
|
-
const toNode = toNodes[i];
|
|
96
|
-
const fromNode = fromNodes[i];
|
|
97
|
-
if (!fromNode) {
|
|
98
|
-
fromParent.appendChild(toNode.cloneNode(true));
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (fromNode.nodeType !== toNode.nodeType) {
|
|
102
|
-
fromParent.replaceChild(toNode.cloneNode(true), fromNode);
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (fromNode.nodeType === 3 || fromNode.nodeType === 8) {
|
|
106
|
-
if (fromNode.nodeValue !== toNode.nodeValue) fromNode.nodeValue = toNode.nodeValue;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
if (fromNode.nodeType === 1) {
|
|
110
|
-
const fromEl = fromNode;
|
|
111
|
-
const toEl = toNode;
|
|
112
|
-
if (fromEl.localName !== toEl.localName || fromEl.namespaceURI !== toEl.namespaceURI) {
|
|
113
|
-
fromParent.replaceChild(toEl.cloneNode(true), fromEl);
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
if (fromEl.localName === "input" && fromEl.type !== toEl.type) {
|
|
117
|
-
fromParent.replaceChild(toEl.cloneNode(true), fromEl);
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
syncAttributes(fromEl, toEl);
|
|
121
|
-
if (fromEl.localName === "textarea") {
|
|
122
|
-
const newText = toEl.textContent ?? "";
|
|
123
|
-
if (fromEl.textContent !== newText) fromEl.textContent = newText;
|
|
124
|
-
fromEl.value = fromEl.defaultValue;
|
|
125
|
-
} else morphChildren(fromEl, toEl);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
function morphInner(from, to) {
|
|
130
|
-
if (from.localName !== to.localName || from.namespaceURI !== to.namespaceURI) throw new Error("[ilha] morph: elements must match");
|
|
131
|
-
morphChildren(from, to);
|
|
132
|
-
}
|
|
133
|
-
function validateSchema(schema, value) {
|
|
134
|
-
const result = schema["~standard"].validate(value);
|
|
135
|
-
if (result instanceof Promise) throw new Error("[ilha] Async schemas are not supported.");
|
|
136
|
-
if (result.issues) throw new Error(`[ilha] Validation failed:\n${result.issues.map((i) => ` - ${i.message}`).join("\n")}`);
|
|
137
|
-
return result.value;
|
|
138
|
-
}
|
|
139
|
-
const ESC = {
|
|
140
|
-
"&": "&",
|
|
141
|
-
"<": "<",
|
|
142
|
-
">": ">",
|
|
143
|
-
"\"": """,
|
|
144
|
-
"'": "'"
|
|
145
|
-
};
|
|
146
|
-
function escapeHtml(value) {
|
|
147
|
-
return String(value).replace(/[&<>"']/g, (c) => ESC[c]);
|
|
148
|
-
}
|
|
149
|
-
function dedentString(str) {
|
|
150
|
-
if (str.length === 0 || str[0] !== "\n") return str;
|
|
151
|
-
const lines = str.split("\n");
|
|
152
|
-
while (lines.length && lines[0].trim() === "") lines.shift();
|
|
153
|
-
while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
|
|
154
|
-
if (!lines.length) return "";
|
|
155
|
-
const indent = Math.min(...lines.filter((l) => l.trim() !== "").map((l) => l.match(/^(\s*)/)[1].length));
|
|
156
|
-
return lines.map((l) => l.slice(indent)).join("\n");
|
|
157
|
-
}
|
|
158
|
-
const RAW = Symbol.for("ilha.raw");
|
|
159
|
-
const SIGNAL_ACCESSOR = Symbol.for("ilha.signalAccessor");
|
|
160
|
-
const ISLAND = Symbol.for("ilha.island");
|
|
161
|
-
const ISLAND_CALL = Symbol.for("ilha.islandCall");
|
|
162
|
-
/** @internal Internal hook used by a parent's mountSlots to mount a child island and
|
|
163
|
-
* retain a handle to push updated props into it on subsequent parent
|
|
164
|
-
* re-renders. Not part of the public surface. */
|
|
165
|
-
const ISLAND_MOUNT_INTERNAL = Symbol.for("ilha.islandMountInternal");
|
|
166
|
-
const SLOT_ATTR = "data-ilha-slot";
|
|
167
|
-
const PROPS_ATTR = "data-ilha-props";
|
|
168
|
-
const STATE_ATTR = "data-ilha-state";
|
|
169
|
-
const CSS_ATTR = "data-ilha-css";
|
|
170
|
-
function buildScopedStyle(css) {
|
|
171
|
-
return `<style ${CSS_ATTR}>@scope (:scope) to ([data-ilha]){${css}}</style>`;
|
|
172
|
-
}
|
|
173
|
-
const renderCtxStack = [];
|
|
174
|
-
function pushRenderCtx(liveHost, asyncChildren) {
|
|
175
|
-
const ctx = {
|
|
176
|
-
slots: /* @__PURE__ */ new Map(),
|
|
177
|
-
positional: 0,
|
|
178
|
-
liveHost,
|
|
179
|
-
pending: asyncChildren ? /* @__PURE__ */ new Map() : void 0,
|
|
180
|
-
binds: []
|
|
181
|
-
};
|
|
182
|
-
renderCtxStack.push(ctx);
|
|
183
|
-
return ctx;
|
|
184
|
-
}
|
|
185
|
-
function popRenderCtx() {
|
|
186
|
-
renderCtxStack.pop();
|
|
187
|
-
}
|
|
188
|
-
function currentRenderCtx() {
|
|
189
|
-
return renderCtxStack[renderCtxStack.length - 1];
|
|
190
|
-
}
|
|
191
|
-
function isIsland(v) {
|
|
192
|
-
if (typeof v !== "function") return false;
|
|
193
|
-
if (ISLAND in v) return true;
|
|
194
|
-
return Object.getOwnPropertySymbols(v).some((s) => s.description === "ilha.island");
|
|
195
|
-
}
|
|
196
|
-
function isIslandCall(v) {
|
|
197
|
-
if (v == null || typeof v !== "object" && typeof v !== "function") return false;
|
|
198
|
-
if (ISLAND_CALL in v) return true;
|
|
199
|
-
if (Object.getOwnPropertySymbols(v).some((s) => s.description === "ilha.islandCall")) return true;
|
|
200
|
-
if (typeof v === "object" && "island" in v) {
|
|
201
|
-
const island = v.island;
|
|
202
|
-
return typeof island === "function" && (ISLAND in island || Object.getOwnPropertySymbols(island).some((s) => s.description === "ilha.island"));
|
|
203
|
-
}
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
function emitIslandSlot(island, props, key) {
|
|
207
|
-
const ctx = currentRenderCtx();
|
|
208
|
-
let id;
|
|
209
|
-
if (key !== void 0) {
|
|
210
|
-
id = `k:${key}`;
|
|
211
|
-
if (ctx && __DEV__ && ctx.slots.has(id)) warn(`Duplicate slot key "${key}" — two children with the same key in a single render will collide. Each .key() call must be unique.`);
|
|
212
|
-
} else id = ctx ? `p:${ctx.positional++}` : "p:0";
|
|
213
|
-
if (ctx) ctx.slots.set(id, {
|
|
214
|
-
island,
|
|
215
|
-
props
|
|
216
|
-
});
|
|
217
|
-
const propsAttr = props ? ` ${PROPS_ATTR}='${escapeHtml(JSON.stringify(props))}'` : "";
|
|
218
|
-
if (ctx?.liveHost) return `<div ${SLOT_ATTR}="${escapeHtml(id)}"${propsAttr}></div>`;
|
|
219
|
-
if (ctx?.pending) {
|
|
220
|
-
popRenderCtx();
|
|
221
|
-
try {
|
|
222
|
-
const result = island(props);
|
|
223
|
-
if (result instanceof Promise) {
|
|
224
|
-
ctx.pending.set(id, result.then(String));
|
|
225
|
-
return `<div ${SLOT_ATTR}="${escapeHtml(id)}"${propsAttr}></div>`;
|
|
226
|
-
}
|
|
227
|
-
return `<div ${SLOT_ATTR}="${escapeHtml(id)}"${propsAttr}>${result}</div>`;
|
|
228
|
-
} finally {
|
|
229
|
-
renderCtxStack.push(ctx);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const inner = island.toString(props);
|
|
233
|
-
return `<div ${SLOT_ATTR}="${escapeHtml(id)}"${propsAttr}>${inner}</div>`;
|
|
234
|
-
}
|
|
235
|
-
async function resolveAsyncChildren(html, ctx) {
|
|
236
|
-
for (const [id, promise] of ctx.pending) {
|
|
237
|
-
const inner = await promise;
|
|
238
|
-
const escaped = escapeHtml(id);
|
|
239
|
-
if (escaped.length > 500) throw new Error(`Slot id exceeds safe length for regex replacement (${escaped.length} > 500).`);
|
|
240
|
-
const attrPattern = escaped.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
241
|
-
const placeholder = new RegExp(`<div\\s[^>]*${SLOT_ATTR}="${attrPattern}"[^>]*></div>`, "g");
|
|
242
|
-
html = html.replace(placeholder, (match) => {
|
|
243
|
-
return match.slice(0, -6) + `>${inner}</div>`;
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
return html;
|
|
247
|
-
}
|
|
248
|
-
function markSignalAccessor(fn) {
|
|
249
|
-
fn[SIGNAL_ACCESSOR] = true;
|
|
250
|
-
const accessor = fn;
|
|
251
|
-
accessor.select = (selector) => createSelectAccessor(accessor, selector);
|
|
252
|
-
return accessor;
|
|
253
|
-
}
|
|
254
|
-
function isSignalAccessor(v) {
|
|
255
|
-
if (typeof v !== "function") return false;
|
|
256
|
-
if (SIGNAL_ACCESSOR in v) return true;
|
|
257
|
-
return Object.getOwnPropertySymbols(v).some((s) => s.description === "ilha.signalAccessor");
|
|
258
|
-
}
|
|
259
|
-
function getAtPath(obj, path) {
|
|
260
|
-
let cur = obj;
|
|
261
|
-
for (const seg of path) {
|
|
262
|
-
if (cur == null) return void 0;
|
|
263
|
-
cur = cur[seg];
|
|
264
|
-
}
|
|
265
|
-
return cur;
|
|
266
|
-
}
|
|
267
|
-
function setAtPath(obj, path, value) {
|
|
268
|
-
if (path.length === 0) return value;
|
|
269
|
-
const [head, ...rest] = path;
|
|
270
|
-
if (Array.isArray(obj)) {
|
|
271
|
-
const idx = head;
|
|
272
|
-
if (idx < 0 || idx >= obj.length) return obj;
|
|
273
|
-
const next = obj[idx];
|
|
274
|
-
const updated = rest.length === 0 ? value : setAtPath(next, rest, value);
|
|
275
|
-
if (Object.is(next, updated)) return obj;
|
|
276
|
-
const copy = obj.slice();
|
|
277
|
-
copy[idx] = updated;
|
|
278
|
-
return copy;
|
|
279
|
-
}
|
|
280
|
-
if (obj !== null && typeof obj === "object") {
|
|
281
|
-
const record = obj;
|
|
282
|
-
const key = String(head);
|
|
283
|
-
const next = record[key];
|
|
284
|
-
const updated = rest.length === 0 ? value : setAtPath(next, rest, value);
|
|
285
|
-
if (rest.length === 0) {
|
|
286
|
-
if (Object.is(next, value)) return obj;
|
|
287
|
-
} else if (Object.is(next, updated)) return obj;
|
|
288
|
-
return {
|
|
289
|
-
...record,
|
|
290
|
-
[key]: updated
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
return rest.length === 0 ? value : setAtPath(void 0, rest, value);
|
|
294
|
-
}
|
|
295
|
-
function toPathSegment(prop) {
|
|
296
|
-
if (typeof prop === "symbol") return null;
|
|
297
|
-
if (prop === "length") return null;
|
|
298
|
-
if (/^\d+$/.test(prop)) return Number(prop);
|
|
299
|
-
return prop;
|
|
300
|
-
}
|
|
301
|
-
function trackSelectPath(rootState, selector) {
|
|
302
|
-
const path = [];
|
|
303
|
-
const track = (value) => {
|
|
304
|
-
if (value === null || typeof value !== "object" && typeof value !== "function") return value;
|
|
305
|
-
return new Proxy(value, { get(target, prop, receiver) {
|
|
306
|
-
const seg = toPathSegment(prop);
|
|
307
|
-
if (seg != null) path.push(seg);
|
|
308
|
-
const next = Reflect.get(target, prop, receiver);
|
|
309
|
-
return seg != null && next !== null && typeof next === "object" ? track(next) : next;
|
|
310
|
-
} });
|
|
311
|
-
};
|
|
312
|
-
selector(track(rootState));
|
|
313
|
-
return path;
|
|
314
|
-
}
|
|
315
|
-
function createSelectAccessor(root, selector) {
|
|
316
|
-
const path = trackSelectPath(root(), selector);
|
|
317
|
-
const selected = selector(root());
|
|
318
|
-
const resolved = path.length === 0 ? root() : getAtPath(root(), path);
|
|
319
|
-
if (!Object.is(selected, resolved)) {
|
|
320
|
-
const msg = "select(): selector must only traverse nested properties or array indexes — derived or transformed values are not supported.";
|
|
321
|
-
if (__DEV__) warn(msg);
|
|
322
|
-
throw new Error(msg);
|
|
323
|
-
}
|
|
324
|
-
if (__DEV__ && path.length === 0) warn("select(): selector did not traverse nested state — bind writes may replace the entire root value.");
|
|
325
|
-
return markSignalAccessor((...args) => {
|
|
326
|
-
if (args.length === 0) return path.length === 0 ? selector(root()) : getAtPath(root(), path);
|
|
327
|
-
const next = path.length === 0 ? args[0] : setAtPath(root(), path, args[0]);
|
|
328
|
-
if (!Object.is(root(), next)) root(next);
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
function ilhaRaw(value) {
|
|
332
|
-
return {
|
|
333
|
-
[RAW]: true,
|
|
334
|
-
value
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
function ilhaCss(strings, ...values) {
|
|
338
|
-
if (typeof strings === "string") return strings;
|
|
339
|
-
let result = "";
|
|
340
|
-
for (let i = 0; i < strings.length; i++) {
|
|
341
|
-
result += strings[i];
|
|
342
|
-
if (i < values.length) result += String(values[i]);
|
|
343
|
-
}
|
|
344
|
-
return result;
|
|
345
|
-
}
|
|
346
|
-
const SAFE_NAME_RE = /^[A-Za-z_:][A-Za-z0-9:._-]*$/;
|
|
347
|
-
const SAFE_BIND_LOCAL_RE = /^[A-Za-z][A-Za-z0-9]*$/;
|
|
348
|
-
const VOID_ELEMENTS = new Set([
|
|
349
|
-
"area",
|
|
350
|
-
"base",
|
|
351
|
-
"br",
|
|
352
|
-
"col",
|
|
353
|
-
"embed",
|
|
354
|
-
"hr",
|
|
355
|
-
"img",
|
|
356
|
-
"input",
|
|
357
|
-
"link",
|
|
358
|
-
"meta",
|
|
359
|
-
"param",
|
|
360
|
-
"source",
|
|
361
|
-
"track",
|
|
362
|
-
"wbr"
|
|
363
|
-
]);
|
|
364
|
-
const SAFE_CSS_PROP_RE = /^(-{2}[a-zA-Z][a-zA-Z0-9-]*|-?[a-zA-Z][a-zA-Z0-9-]*)$/;
|
|
365
|
-
const URL_ATTRS = new Set([
|
|
366
|
-
"href",
|
|
367
|
-
"src",
|
|
368
|
-
"action",
|
|
369
|
-
"formaction",
|
|
370
|
-
"cite",
|
|
371
|
-
"data",
|
|
372
|
-
"poster"
|
|
373
|
-
]);
|
|
374
|
-
const SAFE_URL_RE = /^(?!javascript:|data:text\/html|data:text\/xml|data:application\/xhtml\+xml|data:image\/svg|vbscript:)/i;
|
|
375
|
-
function normalizeClass(value) {
|
|
376
|
-
if (Array.isArray(value)) return value.filter(Boolean).join(" ");
|
|
377
|
-
if (value && typeof value === "object") return Object.entries(value).filter(([, enabled]) => Boolean(enabled)).map(([name]) => name).join(" ");
|
|
378
|
-
return String(value);
|
|
379
|
-
}
|
|
380
|
-
function normalizeJsxChildren(props, children) {
|
|
381
|
-
const propChildren = props && "children" in props ? props.children : void 0;
|
|
382
|
-
return (children.length > 0 ? children : propChildren === void 0 ? [] : [propChildren]).flat(1);
|
|
383
|
-
}
|
|
384
|
-
function normalizeJsxSlotKey(raw) {
|
|
385
|
-
const key = String(raw);
|
|
386
|
-
if (key.trim().length === 0) throw new Error("jsx key requires a non-empty string.");
|
|
387
|
-
if (key.includes(":")) throw new Error(`jsx key cannot contain the slot separator ":" (got "${key}").`);
|
|
388
|
-
return key;
|
|
389
|
-
}
|
|
390
|
-
/** React-style `key` (props or 3rd jsx arg) on island components → `k:{key}` slot ids. */
|
|
391
|
-
function extractJsxSlotKey(props, keyArg) {
|
|
392
|
-
const fromProps = props?.key;
|
|
393
|
-
const raw = keyArg ?? (typeof fromProps === "string" || typeof fromProps === "number" ? fromProps : void 0);
|
|
394
|
-
if (raw == null) return void 0;
|
|
395
|
-
return normalizeJsxSlotKey(raw);
|
|
396
|
-
}
|
|
397
|
-
function serializeStyle(value) {
|
|
398
|
-
return Object.entries(value).map(([k, v]) => {
|
|
399
|
-
if (!SAFE_CSS_PROP_RE.test(k)) return "";
|
|
400
|
-
const prop = k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
401
|
-
let safeV = String(v).replace(/["<>{};]/g, "");
|
|
402
|
-
if (/expression\(/i.test(safeV) || /^javascript:/i.test(safeV)) return "";
|
|
403
|
-
return `${prop}:${safeV}`;
|
|
404
|
-
}).filter(Boolean).join(";");
|
|
405
|
-
}
|
|
406
|
-
function pushJsxAttr(chunks, values, name, value) {
|
|
407
|
-
if (value == null || value === false || name === "children" || name === "key") return;
|
|
408
|
-
if (name === "__proto__" || name === "constructor" || name === "prototype") return;
|
|
409
|
-
if (name.startsWith("bind:")) {
|
|
410
|
-
const [prefix, localName, ...rest] = name.split(":");
|
|
411
|
-
if (prefix !== "bind" || rest.length > 0 || !localName || !SAFE_BIND_LOCAL_RE.test(localName)) return;
|
|
412
|
-
if (!isSignalAccessor(value)) return;
|
|
413
|
-
const safeName = `${prefix}:${localName}`;
|
|
414
|
-
chunks[chunks.length - 1] += ` ${safeName}=`;
|
|
415
|
-
values.push(value);
|
|
416
|
-
chunks.push("");
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
if (!SAFE_NAME_RE.test(name)) return;
|
|
420
|
-
let safeName = name;
|
|
421
|
-
if (safeName === "className") safeName = "class";
|
|
422
|
-
if (safeName === "htmlFor") safeName = "for";
|
|
423
|
-
if (safeName.startsWith("on")) return;
|
|
424
|
-
if (safeName === "class") value = normalizeClass(value);
|
|
425
|
-
if (safeName === "style" && value && typeof value === "object") value = serializeStyle(value);
|
|
426
|
-
if ((URL_ATTRS.has(safeName) || /:(href|src|action|formaction|cite|data|poster)$/.test(safeName)) && typeof value === "string" && !SAFE_URL_RE.test(value.trim())) return;
|
|
427
|
-
if (value === true) {
|
|
428
|
-
chunks[chunks.length - 1] += ` ${safeName}`;
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
chunks[chunks.length - 1] += ` ${safeName}="`;
|
|
432
|
-
values.push(value);
|
|
433
|
-
chunks.push("\"");
|
|
434
|
-
}
|
|
435
|
-
function renderJsxElement(type, props, children) {
|
|
436
|
-
const chunks = [`<${type}`];
|
|
437
|
-
const values = [];
|
|
438
|
-
if (props) for (const [name, value] of Object.entries(props)) pushJsxAttr(chunks, values, name, value);
|
|
439
|
-
chunks[chunks.length - 1] += ">";
|
|
440
|
-
if (!VOID_ELEMENTS.has(type)) {
|
|
441
|
-
for (const child of children) {
|
|
442
|
-
values.push(child);
|
|
443
|
-
chunks.push("");
|
|
444
|
-
}
|
|
445
|
-
chunks[chunks.length - 1] += `</${type}>`;
|
|
446
|
-
} else if (children.length > 0 && __DEV__) warn(`Void element <${type}> should not have children. They will be ignored.`);
|
|
447
|
-
return ilhaHtml(chunks, ...values);
|
|
448
|
-
}
|
|
449
|
-
function ilhaJsx(type, props, maybeKey, ...restChildren) {
|
|
450
|
-
const hasKeyArg = typeof maybeKey === "string" || typeof maybeKey === "number";
|
|
451
|
-
const keyFromArg = hasKeyArg ? maybeKey : void 0;
|
|
452
|
-
const normalizedChildren = normalizeJsxChildren(props, hasKeyArg || maybeKey === void 0 ? restChildren : [maybeKey, ...restChildren]);
|
|
453
|
-
if (typeof type === "function") {
|
|
454
|
-
const slotKey = extractJsxSlotKey(props, typeof keyFromArg === "string" || typeof keyFromArg === "number" ? keyFromArg : void 0);
|
|
455
|
-
const componentProps = {
|
|
456
|
-
...props ?? {},
|
|
457
|
-
...normalizedChildren.length > 0 ? { children: normalizedChildren } : {}
|
|
458
|
-
};
|
|
459
|
-
delete componentProps["key"];
|
|
460
|
-
const out = type(Object.keys(componentProps).length ? componentProps : {});
|
|
461
|
-
if (isIslandCall(out)) {
|
|
462
|
-
if (slotKey !== void 0) out.key = slotKey;
|
|
463
|
-
return ilhaHtml`${out}`;
|
|
464
|
-
}
|
|
465
|
-
if (isRawHtml(out)) return out;
|
|
466
|
-
if (typeof out === "string" && isIsland(type)) return ilhaRaw(emitIslandSlot(type, componentProps, slotKey));
|
|
467
|
-
if (typeof out === "string") return ilhaHtml`${out}`;
|
|
468
|
-
if (typeof out === "object" && out !== null && Object.getPrototypeOf(out) === Object.prototype && out[Symbol.for("ilha.renderPart")] === true && typeof out.toString === "function") return ilhaRaw(out.toString());
|
|
469
|
-
return ilhaHtml`${out}`;
|
|
470
|
-
}
|
|
471
|
-
return renderJsxElement(type, props, normalizedChildren);
|
|
472
|
-
}
|
|
473
|
-
function ilhaFragment(props, ...children) {
|
|
474
|
-
const normalizedChildren = normalizeJsxChildren(props, children);
|
|
475
|
-
return ilhaHtml(["", ...normalizedChildren.map(() => "")], ...normalizedChildren);
|
|
476
|
-
}
|
|
477
|
-
function ilhaJsxDEV(type, props, maybeKey, _source, _self) {
|
|
478
|
-
return ilhaJsx(type, props, maybeKey);
|
|
479
|
-
}
|
|
480
|
-
function interpolateValue(v) {
|
|
481
|
-
if (v == null || v === true || v === false) return "";
|
|
482
|
-
if (Array.isArray(v)) return v.map(interpolateValue).join("");
|
|
483
|
-
if (isRawHtml(v)) return v.value;
|
|
484
|
-
if (isIslandCall(v)) {
|
|
485
|
-
const call = typeof v === "function" ? v() : v;
|
|
486
|
-
return emitIslandSlot(call.island, call.props, call.key);
|
|
487
|
-
}
|
|
488
|
-
if (isIsland(v)) return emitIslandSlot(v, void 0, void 0);
|
|
489
|
-
if (isSignalAccessor(v)) return escapeHtml(v());
|
|
490
|
-
if (typeof v === "function") return escapeHtml(v());
|
|
491
|
-
return escapeHtml(v);
|
|
492
|
-
}
|
|
493
|
-
const BIND_VALID_KINDS = new Set([
|
|
494
|
-
"value",
|
|
495
|
-
"checked",
|
|
496
|
-
"valueAsNumber",
|
|
497
|
-
"valueAsDate",
|
|
498
|
-
"files",
|
|
499
|
-
"open",
|
|
500
|
-
"group",
|
|
501
|
-
"this"
|
|
502
|
-
]);
|
|
503
|
-
const BIND_PREFIX_RE = /\bbind:([a-zA-Z]+)\s*=\s*("|')?$/;
|
|
504
|
-
function formatDateForInput(d) {
|
|
505
|
-
if (d instanceof Date) {
|
|
506
|
-
if (isNaN(d.getTime())) {
|
|
507
|
-
if (__DEV__) warn("bind:valueAsDate received an invalid Date object — value attribute will be empty.");
|
|
508
|
-
return "";
|
|
509
|
-
}
|
|
510
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
511
|
-
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
512
|
-
}
|
|
513
|
-
return "";
|
|
514
|
-
}
|
|
515
|
-
function extractElementStaticValue(prefix) {
|
|
516
|
-
const lastOpen = prefix.lastIndexOf("<");
|
|
517
|
-
if (lastOpen === -1) return null;
|
|
518
|
-
const tagPrefix = prefix.slice(lastOpen);
|
|
519
|
-
if (tagPrefix.includes(">")) return null;
|
|
520
|
-
const m = tagPrefix.match(/\bvalue\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/);
|
|
521
|
-
if (!m) return null;
|
|
522
|
-
return m[2] ?? m[3] ?? m[4] ?? null;
|
|
523
|
-
}
|
|
524
|
-
function emitBindSSR(kind, index, accessor, prefixForStaticPeek) {
|
|
525
|
-
const spec = `${kind}:${index}`;
|
|
526
|
-
let v;
|
|
527
|
-
try {
|
|
528
|
-
v = accessor();
|
|
529
|
-
} catch {
|
|
530
|
-
v = void 0;
|
|
531
|
-
}
|
|
532
|
-
switch (kind) {
|
|
533
|
-
case "value": return [` value="${escapeHtml(v ?? "")}"`, spec];
|
|
534
|
-
case "valueAsNumber": return [` value="${escapeHtml(v == null ? "" : String(v))}"`, spec];
|
|
535
|
-
case "valueAsDate": return [` value="${escapeHtml(formatDateForInput(v))}"`, spec];
|
|
536
|
-
case "checked": return [v ? ` checked` : ``, spec];
|
|
537
|
-
case "open": return [v ? ` open` : ``, spec];
|
|
538
|
-
case "files": return [``, spec];
|
|
539
|
-
case "this": return [``, spec];
|
|
540
|
-
case "group": {
|
|
541
|
-
const optionValue = extractElementStaticValue(prefixForStaticPeek);
|
|
542
|
-
if (optionValue == null) return [``, spec];
|
|
543
|
-
return [(Array.isArray(v) ? v.map(String).includes(optionValue) : v != null && String(v) === optionValue) ? ` checked` : ``, spec];
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
function ilhaHtml(strings, ...values) {
|
|
548
|
-
let result = "";
|
|
549
|
-
let stripLeadingQuote = null;
|
|
550
|
-
let pendingBindSpecs = "";
|
|
551
|
-
const ctx = currentRenderCtx();
|
|
552
|
-
for (let i = 0; i < strings.length; i++) {
|
|
553
|
-
let chunk = strings[i];
|
|
554
|
-
if (stripLeadingQuote !== null) {
|
|
555
|
-
if (chunk.startsWith(stripLeadingQuote)) chunk = chunk.slice(1);
|
|
556
|
-
stripLeadingQuote = null;
|
|
557
|
-
}
|
|
558
|
-
if (pendingBindSpecs !== "") {
|
|
559
|
-
const gtIdx = chunk.indexOf(">");
|
|
560
|
-
if (gtIdx !== -1) {
|
|
561
|
-
chunk = chunk.slice(0, gtIdx) + ` data-ilha-bind="${pendingBindSpecs}">` + chunk.slice(gtIdx + 1);
|
|
562
|
-
pendingBindSpecs = "";
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
if (i >= values.length) {
|
|
566
|
-
result += chunk;
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
const value = values[i];
|
|
570
|
-
const m = chunk.match(BIND_PREFIX_RE);
|
|
571
|
-
if (m && isSignalAccessor(value)) {
|
|
572
|
-
const name = m[1];
|
|
573
|
-
const openQuote = m[2] ?? null;
|
|
574
|
-
if (!BIND_VALID_KINDS.has(name)) {
|
|
575
|
-
if (__DEV__) warn(`Unknown bind:${name} — supported bindings are ${[...BIND_VALID_KINDS].map((k) => `bind:${k}`).join(", ")}.`);
|
|
576
|
-
result += chunk + interpolateValue(value);
|
|
577
|
-
continue;
|
|
578
|
-
}
|
|
579
|
-
if (!ctx) {
|
|
580
|
-
if (__DEV__) warn(`bind:${name} used outside an island render — bindings only work in .render(). The value is reflected once but not wired.`);
|
|
581
|
-
const stripped = chunk.slice(0, chunk.length - m[0].length);
|
|
582
|
-
const quote = openQuote ?? "\"";
|
|
583
|
-
result += stripped + name + "=" + quote + interpolateValue(value) + quote;
|
|
584
|
-
if (openQuote) stripLeadingQuote = openQuote;
|
|
585
|
-
continue;
|
|
586
|
-
}
|
|
587
|
-
const matchStart = chunk.length - m[0].length;
|
|
588
|
-
const cleanPrefix = chunk.slice(0, matchStart).replace(/\s+$/, "");
|
|
589
|
-
const index = ctx.binds.length;
|
|
590
|
-
ctx.binds.push({
|
|
591
|
-
kind: name,
|
|
592
|
-
accessor: value
|
|
593
|
-
});
|
|
594
|
-
const [valueAttrs, spec] = emitBindSSR(name, index, value, cleanPrefix);
|
|
595
|
-
result += cleanPrefix + valueAttrs;
|
|
596
|
-
pendingBindSpecs += (pendingBindSpecs ? "," : "") + spec;
|
|
597
|
-
if (openQuote) stripLeadingQuote = openQuote;
|
|
598
|
-
continue;
|
|
599
|
-
}
|
|
600
|
-
if (m && __DEV__) warn(`bind:${m[1]} requires a signal accessor — got ${typeof value}. Use ilha.signal() or a .state() accessor.`);
|
|
601
|
-
result += chunk + interpolateValue(value);
|
|
602
|
-
}
|
|
603
|
-
if (pendingBindSpecs !== "") result += ` data-ilha-bind="${pendingBindSpecs}"`;
|
|
604
|
-
return {
|
|
605
|
-
[RAW]: true,
|
|
606
|
-
value: dedentString(result)
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
/** Duck-type RawHtml — works across duplicate `ilha` bundles (distinct RAW symbols). */
|
|
610
|
-
function isRawHtml(v) {
|
|
611
|
-
if (typeof v !== "object" || v === null || !("value" in v)) return false;
|
|
612
|
-
if (typeof v.value !== "string") return false;
|
|
613
|
-
if (RAW in v) return true;
|
|
614
|
-
return Object.getOwnPropertySymbols(v).some((s) => s.description === "ilha.raw");
|
|
615
|
-
}
|
|
616
|
-
function unwrapHtml(v) {
|
|
617
|
-
return isRawHtml(v) ? v.value : v;
|
|
618
|
-
}
|
|
619
|
-
const contextRegistry = /* @__PURE__ */ new Map();
|
|
620
|
-
function ilhaContext(key, initial) {
|
|
621
|
-
if (contextRegistry.has(key)) return contextRegistry.get(key);
|
|
622
|
-
const s = signal(initial);
|
|
623
|
-
const accessor = (...args) => {
|
|
624
|
-
if (args.length === 0) return s();
|
|
625
|
-
s(args[0]);
|
|
626
|
-
};
|
|
627
|
-
contextRegistry.set(key, accessor);
|
|
628
|
-
return accessor;
|
|
629
|
-
}
|
|
630
|
-
/**
|
|
631
|
-
* Create a free-standing reactive signal that lives outside any island.
|
|
632
|
-
* Useful for sharing state across islands without prop drilling, or for
|
|
633
|
-
* binding form inputs to module-level state via the `bind:value=${signal}`
|
|
634
|
-
* template syntax.
|
|
635
|
-
*
|
|
636
|
-
* The returned accessor is a getter when called with no arguments and a
|
|
637
|
-
* setter when called with one. Reading it inside a `.derived()`, `.effect()`,
|
|
638
|
-
* or `.render()` automatically subscribes the surrounding reactive scope —
|
|
639
|
-
* so when the signal changes, dependents re-run as if it were local state.
|
|
640
|
-
*/
|
|
641
|
-
function ilhaSignal(initial) {
|
|
642
|
-
const s = signal(initial);
|
|
643
|
-
return markSignalAccessor((...args) => {
|
|
644
|
-
if (args.length === 0) return s();
|
|
645
|
-
s(args[0]);
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* Run `fn` with reactive tracking suspended. Reading signals inside `fn`
|
|
650
|
-
* returns their current value without subscribing the surrounding scope.
|
|
651
|
-
* Use this in effects/deriveds when you want to peek at state without
|
|
652
|
-
* causing a re-run on its changes.
|
|
653
|
-
*/
|
|
654
|
-
function untrack(fn) {
|
|
655
|
-
const prev = setActiveSub(void 0);
|
|
656
|
-
try {
|
|
657
|
-
return fn();
|
|
658
|
-
} finally {
|
|
659
|
-
setActiveSub(prev);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Run `fn` as an atomic batch — multiple signal writes inside the callback
|
|
664
|
-
* produce a single propagation pass, so dependents (effects, deriveds,
|
|
665
|
-
* island re-renders) see the final state and run once instead of once per
|
|
666
|
-
* write. Returns whatever `fn` returns.
|
|
667
|
-
*
|
|
668
|
-
* Note: `.on()` handlers and `.effect()` runs are batched implicitly, so
|
|
669
|
-
* you only need this when triggering multiple writes from outside an
|
|
670
|
-
* island (e.g. from a top-level event listener or async callback).
|
|
671
|
-
*/
|
|
672
|
-
function batch(fn) {
|
|
673
|
-
startBatch();
|
|
674
|
-
try {
|
|
675
|
-
return fn();
|
|
676
|
-
} finally {
|
|
677
|
-
endBatch();
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
function createDerivedAccessor(read, write) {
|
|
681
|
-
const accessor = markSignalAccessor((...args) => {
|
|
682
|
-
if (args.length > 0) {
|
|
683
|
-
if (write) write(args[0]);
|
|
684
|
-
else if (__DEV__) warn("derived values are read-only");
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
return read().value;
|
|
688
|
-
});
|
|
689
|
-
return new Proxy(accessor, { get(target, prop, receiver) {
|
|
690
|
-
if (prop === "loading" || prop === "value" || prop === "error") return read()[prop];
|
|
691
|
-
return Reflect.get(target, prop, receiver);
|
|
692
|
-
} });
|
|
693
|
-
}
|
|
694
|
-
function defaultDerivedAccessor() {
|
|
695
|
-
return createDerivedAccessor(() => ({
|
|
696
|
-
loading: false,
|
|
697
|
-
value: void 0,
|
|
698
|
-
error: void 0
|
|
699
|
-
}));
|
|
700
|
-
}
|
|
701
|
-
function buildDerivedAccessors(envelopes) {
|
|
702
|
-
const accessors = /* @__PURE__ */ new Map();
|
|
703
|
-
for (const [key, envelope] of Object.entries(envelopes)) accessors.set(key, createDerivedAccessor(() => envelope, (value) => {
|
|
704
|
-
envelope.loading = false;
|
|
705
|
-
envelope.value = value;
|
|
706
|
-
envelope.error = void 0;
|
|
707
|
-
}));
|
|
708
|
-
return new Proxy({}, { get(_, key) {
|
|
709
|
-
return accessors.get(key) ?? defaultDerivedAccessor();
|
|
710
|
-
} });
|
|
711
|
-
}
|
|
712
|
-
function createDerivedProxy(entries, state, input, derivedSnapshot) {
|
|
713
|
-
const envelopes = /* @__PURE__ */ new Map();
|
|
714
|
-
for (const entry of entries) {
|
|
715
|
-
let initialEnvelope;
|
|
716
|
-
if (derivedSnapshot != null && entry.key in derivedSnapshot) initialEnvelope = { ...derivedSnapshot[entry.key] };
|
|
717
|
-
else if (!(entry.fn.constructor.name === "AsyncFunction" || entry.fn.constructor.name === "AsyncGeneratorFunction")) {
|
|
718
|
-
const ac = new AbortController();
|
|
719
|
-
try {
|
|
720
|
-
const result = entry.fn({
|
|
721
|
-
state,
|
|
722
|
-
input,
|
|
723
|
-
signal: ac.signal
|
|
724
|
-
});
|
|
725
|
-
if (!(result instanceof Promise)) initialEnvelope = {
|
|
726
|
-
loading: false,
|
|
727
|
-
value: result,
|
|
728
|
-
error: void 0
|
|
729
|
-
};
|
|
730
|
-
else {
|
|
731
|
-
initialEnvelope = {
|
|
732
|
-
loading: true,
|
|
733
|
-
value: void 0,
|
|
734
|
-
error: void 0
|
|
735
|
-
};
|
|
736
|
-
result.catch(() => {});
|
|
737
|
-
}
|
|
738
|
-
} catch (err) {
|
|
739
|
-
initialEnvelope = {
|
|
740
|
-
loading: false,
|
|
741
|
-
value: void 0,
|
|
742
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
743
|
-
};
|
|
744
|
-
} finally {
|
|
745
|
-
ac.abort();
|
|
746
|
-
}
|
|
747
|
-
} else initialEnvelope = {
|
|
748
|
-
loading: true,
|
|
749
|
-
value: void 0,
|
|
750
|
-
error: void 0
|
|
751
|
-
};
|
|
752
|
-
const env = signal(initialEnvelope);
|
|
753
|
-
envelopes.set(entry.key, env);
|
|
754
|
-
}
|
|
755
|
-
const accessors = /* @__PURE__ */ new Map();
|
|
756
|
-
for (const entry of entries) {
|
|
757
|
-
const env = envelopes.get(entry.key);
|
|
758
|
-
accessors.set(entry.key, createDerivedAccessor(() => env(), (value) => {
|
|
759
|
-
const prevSub = setActiveSub(void 0);
|
|
760
|
-
env({
|
|
761
|
-
loading: false,
|
|
762
|
-
value,
|
|
763
|
-
error: void 0
|
|
764
|
-
});
|
|
765
|
-
setActiveSub(prevSub);
|
|
766
|
-
}));
|
|
767
|
-
}
|
|
768
|
-
const proxy = new Proxy({}, { get(_, key) {
|
|
769
|
-
return accessors.get(key) ?? defaultDerivedAccessor();
|
|
770
|
-
} });
|
|
771
|
-
const setup = () => {
|
|
772
|
-
const stops = [];
|
|
773
|
-
for (const entry of entries) {
|
|
774
|
-
const env = envelopes.get(entry.key);
|
|
775
|
-
let ac = new AbortController();
|
|
776
|
-
let skipFirst = derivedSnapshot != null && entry.key in derivedSnapshot;
|
|
777
|
-
const stopEffect = effect(() => {
|
|
778
|
-
ac.abort();
|
|
779
|
-
ac = new AbortController();
|
|
780
|
-
const currentAc = ac;
|
|
781
|
-
let result;
|
|
782
|
-
try {
|
|
783
|
-
result = entry.fn({
|
|
784
|
-
state,
|
|
785
|
-
input,
|
|
786
|
-
signal: currentAc.signal
|
|
787
|
-
});
|
|
788
|
-
} catch (err) {
|
|
789
|
-
if (skipFirst) {
|
|
790
|
-
skipFirst = false;
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
const prevSub = setActiveSub(void 0);
|
|
794
|
-
env({
|
|
795
|
-
loading: false,
|
|
796
|
-
value: void 0,
|
|
797
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
798
|
-
});
|
|
799
|
-
setActiveSub(prevSub);
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
if (skipFirst) {
|
|
803
|
-
skipFirst = false;
|
|
804
|
-
if (result instanceof Promise) result.catch(() => {});
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
if (!(result instanceof Promise)) {
|
|
808
|
-
const prevSub = setActiveSub(void 0);
|
|
809
|
-
env({
|
|
810
|
-
loading: false,
|
|
811
|
-
value: result,
|
|
812
|
-
error: void 0
|
|
813
|
-
});
|
|
814
|
-
setActiveSub(prevSub);
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
const prevSub = setActiveSub(void 0);
|
|
818
|
-
env({
|
|
819
|
-
loading: true,
|
|
820
|
-
value: env().value,
|
|
821
|
-
error: void 0
|
|
822
|
-
});
|
|
823
|
-
setActiveSub(prevSub);
|
|
824
|
-
result.then((value) => {
|
|
825
|
-
if (currentAc.signal.aborted) return;
|
|
826
|
-
env({
|
|
827
|
-
loading: false,
|
|
828
|
-
value,
|
|
829
|
-
error: void 0
|
|
830
|
-
});
|
|
831
|
-
}).catch((err) => {
|
|
832
|
-
if (currentAc.signal.aborted) return;
|
|
833
|
-
env({
|
|
834
|
-
loading: false,
|
|
835
|
-
value: void 0,
|
|
836
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
837
|
-
});
|
|
838
|
-
});
|
|
839
|
-
});
|
|
840
|
-
stops.push(() => {
|
|
841
|
-
stopEffect();
|
|
842
|
-
ac.abort();
|
|
843
|
-
});
|
|
844
|
-
}
|
|
845
|
-
return () => stops.forEach((s) => s());
|
|
846
|
-
};
|
|
847
|
-
return {
|
|
848
|
-
proxy,
|
|
849
|
-
setup
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
const BIND_SENTINEL_ATTR = "data-ilha-bind";
|
|
853
|
-
function parseBindSentinel(value) {
|
|
854
|
-
const out = [];
|
|
855
|
-
for (const part of value.split(",")) {
|
|
856
|
-
const [kind, idx] = part.split(":");
|
|
857
|
-
if (!kind || !idx) continue;
|
|
858
|
-
const i = Number(idx);
|
|
859
|
-
if (!Number.isInteger(i) || i < 0) continue;
|
|
860
|
-
out.push({
|
|
861
|
-
kind,
|
|
862
|
-
index: i
|
|
863
|
-
});
|
|
864
|
-
}
|
|
865
|
-
return out;
|
|
866
|
-
}
|
|
867
|
-
function resolveBindOps(el, kind) {
|
|
868
|
-
const input = el;
|
|
869
|
-
switch (kind) {
|
|
870
|
-
case "value": return {
|
|
871
|
-
event: el.tagName === "SELECT" ? "change" : "input",
|
|
872
|
-
read: (el) => el.value,
|
|
873
|
-
write: (el, v) => el.value = v == null ? "" : String(v)
|
|
874
|
-
};
|
|
875
|
-
case "valueAsNumber": return {
|
|
876
|
-
event: "input",
|
|
877
|
-
read: (el) => {
|
|
878
|
-
const n = el.valueAsNumber;
|
|
879
|
-
return Number.isNaN(n) ? null : n;
|
|
880
|
-
},
|
|
881
|
-
write: (el, v) => el.value = v == null || Number.isNaN(v) ? "" : String(v)
|
|
882
|
-
};
|
|
883
|
-
case "valueAsDate": return {
|
|
884
|
-
event: "input",
|
|
885
|
-
read: (el) => el.valueAsDate,
|
|
886
|
-
write: (el, v) => {
|
|
887
|
-
el.value = formatDateForInput(v);
|
|
888
|
-
}
|
|
889
|
-
};
|
|
890
|
-
case "checked": return {
|
|
891
|
-
event: "change",
|
|
892
|
-
read: (el) => el.checked,
|
|
893
|
-
write: (el, v) => el.checked = Boolean(v)
|
|
894
|
-
};
|
|
895
|
-
case "files": return {
|
|
896
|
-
event: "change",
|
|
897
|
-
read: (el) => el.files,
|
|
898
|
-
write: () => {}
|
|
899
|
-
};
|
|
900
|
-
case "open": return {
|
|
901
|
-
event: "toggle",
|
|
902
|
-
read: (el) => el.open,
|
|
903
|
-
write: (el, v) => el.open = Boolean(v)
|
|
904
|
-
};
|
|
905
|
-
case "this": return {
|
|
906
|
-
event: null,
|
|
907
|
-
read: () => void 0,
|
|
908
|
-
write: () => {}
|
|
909
|
-
};
|
|
910
|
-
case "group": {
|
|
911
|
-
const isCheckbox = input.type === "checkbox";
|
|
912
|
-
return {
|
|
913
|
-
event: "change",
|
|
914
|
-
read: (el) => {
|
|
915
|
-
const i = el;
|
|
916
|
-
if (isCheckbox) return {
|
|
917
|
-
__ilhaGroup: true,
|
|
918
|
-
value: i.value,
|
|
919
|
-
checked: i.checked
|
|
920
|
-
};
|
|
921
|
-
return i.checked ? i.value : void 0;
|
|
922
|
-
},
|
|
923
|
-
write: (el, v) => {
|
|
924
|
-
const i = el;
|
|
925
|
-
if (isCheckbox) i.checked = (Array.isArray(v) ? v.map(String) : []).includes(i.value);
|
|
926
|
-
else i.checked = v != null && String(v) === i.value;
|
|
927
|
-
}
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
function applyTemplateBindings(host, binds) {
|
|
933
|
-
if (binds.length === 0) return () => {};
|
|
934
|
-
const cleanups = [];
|
|
935
|
-
const elements = [];
|
|
936
|
-
if (host.hasAttribute(BIND_SENTINEL_ATTR)) elements.push(host);
|
|
937
|
-
for (const el of host.querySelectorAll(`[${BIND_SENTINEL_ATTR}]`)) elements.push(el);
|
|
938
|
-
for (const el of elements) {
|
|
939
|
-
const specs = parseBindSentinel(el.getAttribute(BIND_SENTINEL_ATTR));
|
|
940
|
-
for (const spec of specs) {
|
|
941
|
-
const record = binds[spec.index];
|
|
942
|
-
if (!record) {
|
|
943
|
-
if (__DEV__) warn(`bind:${spec.kind} index ${spec.index} not found in render — the data-ilha-bind sentinel may have been hand-edited or survived a stale render.`);
|
|
944
|
-
continue;
|
|
945
|
-
}
|
|
946
|
-
if (record.kind !== spec.kind) {
|
|
947
|
-
if (__DEV__) warn(`bind:${spec.kind} sentinel points at a binding registered as bind:${record.kind}. Sentinel may be stale.`);
|
|
948
|
-
continue;
|
|
949
|
-
}
|
|
950
|
-
const { event, read, write } = resolveBindOps(el, spec.kind);
|
|
951
|
-
const accessor = record.accessor;
|
|
952
|
-
if (spec.kind === "this") {
|
|
953
|
-
accessor(el);
|
|
954
|
-
cleanups.push(() => accessor(null));
|
|
955
|
-
continue;
|
|
956
|
-
}
|
|
957
|
-
try {
|
|
958
|
-
write(el, accessor());
|
|
959
|
-
} catch (err) {
|
|
960
|
-
if (__DEV__) console.error(`[ilha] bind:${spec.kind} write failed:`, err);
|
|
961
|
-
}
|
|
962
|
-
if (event === null) continue;
|
|
963
|
-
const listener = () => {
|
|
964
|
-
const raw = read(el);
|
|
965
|
-
if (spec.kind === "group") {
|
|
966
|
-
const groupRead = raw;
|
|
967
|
-
if (groupRead === void 0) return;
|
|
968
|
-
if (typeof groupRead === "object" && groupRead.__ilhaGroup) {
|
|
969
|
-
const currentArr = accessor();
|
|
970
|
-
const arr = Array.isArray(currentArr) ? [...currentArr] : [];
|
|
971
|
-
const idx = arr.findIndex((x) => String(x) === groupRead.value);
|
|
972
|
-
if (groupRead.checked && idx === -1) {
|
|
973
|
-
let coercedVal = groupRead.value;
|
|
974
|
-
const templateVal = Array.isArray(currentArr) && currentArr.length > 0 ? currentArr[0] : void 0;
|
|
975
|
-
if (templateVal !== void 0) {
|
|
976
|
-
if (typeof templateVal === "number") {
|
|
977
|
-
const n = Number(coercedVal);
|
|
978
|
-
coercedVal = Number.isNaN(n) ? coercedVal : n;
|
|
979
|
-
} else if (typeof templateVal === "boolean") coercedVal = Boolean(coercedVal);
|
|
980
|
-
}
|
|
981
|
-
arr.push(coercedVal);
|
|
982
|
-
} else if (!groupRead.checked && idx !== -1) arr.splice(idx, 1);
|
|
983
|
-
accessor(arr);
|
|
984
|
-
return;
|
|
985
|
-
}
|
|
986
|
-
const currentVal = accessor();
|
|
987
|
-
let coerced = groupRead;
|
|
988
|
-
if (typeof currentVal === "number" && typeof groupRead === "string") {
|
|
989
|
-
const n = Number(groupRead);
|
|
990
|
-
coerced = Number.isNaN(n) ? 0 : n;
|
|
991
|
-
} else if (typeof currentVal === "boolean") coerced = Boolean(groupRead);
|
|
992
|
-
accessor(coerced);
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
|
-
const currentVal = accessor();
|
|
996
|
-
let value = raw;
|
|
997
|
-
if (typeof currentVal === "number" && typeof raw === "string") {
|
|
998
|
-
const n = Number(raw);
|
|
999
|
-
value = Number.isNaN(n) ? 0 : n;
|
|
1000
|
-
} else if (typeof currentVal === "boolean") value = Boolean(raw);
|
|
1001
|
-
accessor(value);
|
|
1002
|
-
};
|
|
1003
|
-
el.addEventListener(event, listener);
|
|
1004
|
-
cleanups.push(() => el.removeEventListener(event, listener));
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
return () => cleanups.forEach((c) => c());
|
|
1008
|
-
}
|
|
1009
|
-
function parseOnArgs(selectorOrCombined) {
|
|
1010
|
-
const atIdx = selectorOrCombined.lastIndexOf("@");
|
|
1011
|
-
const selector = atIdx === -1 ? "" : selectorOrCombined.slice(0, atIdx);
|
|
1012
|
-
const parts = (atIdx === -1 ? selectorOrCombined : selectorOrCombined.slice(atIdx + 1)).split(":");
|
|
1013
|
-
const eventType = parts[0];
|
|
1014
|
-
const modifiers = new Set(parts.slice(1));
|
|
1015
|
-
return {
|
|
1016
|
-
selector,
|
|
1017
|
-
eventType,
|
|
1018
|
-
options: {
|
|
1019
|
-
once: modifiers.has("once"),
|
|
1020
|
-
capture: modifiers.has("capture"),
|
|
1021
|
-
passive: modifiers.has("passive")
|
|
1022
|
-
},
|
|
1023
|
-
abortable: modifiers.has("abortable")
|
|
1024
|
-
};
|
|
1025
|
-
}
|
|
1026
|
-
/**
|
|
1027
|
-
* Combine multiple AbortSignals into one that aborts when any input aborts.
|
|
1028
|
-
* Uses native AbortSignal.any when available, falls back to a manual chain
|
|
1029
|
-
* otherwise (for older runtimes).
|
|
1030
|
-
*/
|
|
1031
|
-
function anySignal(signals) {
|
|
1032
|
-
if (typeof AbortSignal.any === "function") return AbortSignal.any(signals);
|
|
1033
|
-
const controller = new AbortController();
|
|
1034
|
-
const cleanups = [];
|
|
1035
|
-
for (const s of signals) {
|
|
1036
|
-
if (s.aborted) {
|
|
1037
|
-
controller.abort(s.reason);
|
|
1038
|
-
cleanups.forEach((c) => c());
|
|
1039
|
-
return controller.signal;
|
|
1040
|
-
}
|
|
1041
|
-
const handler = () => controller.abort(s.reason);
|
|
1042
|
-
s.addEventListener("abort", handler, { once: true });
|
|
1043
|
-
cleanups.push(() => s.removeEventListener("abort", handler));
|
|
1044
|
-
}
|
|
1045
|
-
if (!controller.signal.aborted) controller.signal.addEventListener("abort", () => {
|
|
1046
|
-
cleanups.forEach((c) => c());
|
|
1047
|
-
});
|
|
1048
|
-
return controller.signal;
|
|
1049
|
-
}
|
|
1050
|
-
const _mountedHosts = __DEV__ ? /* @__PURE__ */ new WeakSet() : null;
|
|
1051
|
-
var IlhaBuilder = class IlhaBuilder {
|
|
1052
|
-
_cfg;
|
|
1053
|
-
constructor(cfg) {
|
|
1054
|
-
this._cfg = cfg;
|
|
1055
|
-
}
|
|
1056
|
-
input(schema) {
|
|
1057
|
-
return new IlhaBuilder({
|
|
1058
|
-
schema: schema ?? null,
|
|
1059
|
-
states: [],
|
|
1060
|
-
deriveds: [],
|
|
1061
|
-
ons: [],
|
|
1062
|
-
effects: [],
|
|
1063
|
-
onMounts: [],
|
|
1064
|
-
onErrors: [],
|
|
1065
|
-
transition: null,
|
|
1066
|
-
css: null
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
state(key, init) {
|
|
1070
|
-
const cfg = this._cfg;
|
|
1071
|
-
return new IlhaBuilder({
|
|
1072
|
-
...cfg,
|
|
1073
|
-
states: [...cfg.states, {
|
|
1074
|
-
key,
|
|
1075
|
-
init
|
|
1076
|
-
}]
|
|
1077
|
-
});
|
|
1078
|
-
}
|
|
1079
|
-
derived(key, fn) {
|
|
1080
|
-
const cfg = this._cfg;
|
|
1081
|
-
return new IlhaBuilder({
|
|
1082
|
-
...cfg,
|
|
1083
|
-
deriveds: [...cfg.deriveds, {
|
|
1084
|
-
key,
|
|
1085
|
-
fn
|
|
1086
|
-
}]
|
|
1087
|
-
});
|
|
1088
|
-
}
|
|
1089
|
-
on(selectorOrCombined, handler) {
|
|
1090
|
-
const parsed = parseOnArgs(selectorOrCombined);
|
|
1091
|
-
return new IlhaBuilder({
|
|
1092
|
-
...this._cfg,
|
|
1093
|
-
ons: [...this._cfg.ons, {
|
|
1094
|
-
selector: parsed.selector,
|
|
1095
|
-
event: parsed.eventType,
|
|
1096
|
-
options: parsed.options,
|
|
1097
|
-
abortable: parsed.abortable,
|
|
1098
|
-
handler
|
|
1099
|
-
}]
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
effect(fn) {
|
|
1103
|
-
return new IlhaBuilder({
|
|
1104
|
-
...this._cfg,
|
|
1105
|
-
effects: [...this._cfg.effects, { fn }]
|
|
1106
|
-
});
|
|
1107
|
-
}
|
|
1108
|
-
onMount(fn) {
|
|
1109
|
-
return new IlhaBuilder({
|
|
1110
|
-
...this._cfg,
|
|
1111
|
-
onMounts: [...this._cfg.onMounts, { fn }]
|
|
1112
|
-
});
|
|
1113
|
-
}
|
|
1114
|
-
onError(fn) {
|
|
1115
|
-
return new IlhaBuilder({
|
|
1116
|
-
...this._cfg,
|
|
1117
|
-
onErrors: [...this._cfg.onErrors, { fn }]
|
|
1118
|
-
});
|
|
1119
|
-
}
|
|
1120
|
-
transition(opts) {
|
|
1121
|
-
return new IlhaBuilder({
|
|
1122
|
-
...this._cfg,
|
|
1123
|
-
transition: opts
|
|
1124
|
-
});
|
|
1125
|
-
}
|
|
1126
|
-
css(strings, ...values) {
|
|
1127
|
-
let source;
|
|
1128
|
-
if (typeof strings === "string") source = strings;
|
|
1129
|
-
else {
|
|
1130
|
-
let result = "";
|
|
1131
|
-
for (let i = 0; i < strings.length; i++) {
|
|
1132
|
-
result += strings[i];
|
|
1133
|
-
if (i < values.length) result += String(values[i]);
|
|
1134
|
-
}
|
|
1135
|
-
source = result;
|
|
1136
|
-
}
|
|
1137
|
-
if (__DEV__ && this._cfg.css !== null) warn("css(): called more than once on the same builder chain. The previous stylesheet has been discarded. Compose styles into a single .css() call instead.");
|
|
1138
|
-
return new IlhaBuilder({
|
|
1139
|
-
...this._cfg,
|
|
1140
|
-
css: source
|
|
1141
|
-
});
|
|
1142
|
-
}
|
|
1143
|
-
render(fn) {
|
|
1144
|
-
const { schema, states, deriveds, ons, effects, onMounts, onErrors, transition, css: cssSource } = this._cfg;
|
|
1145
|
-
const stylePrefix = cssSource != null ? buildScopedStyle(cssSource) : "";
|
|
1146
|
-
function resolveInput(props) {
|
|
1147
|
-
const value = props ?? {};
|
|
1148
|
-
if (!schema) return value;
|
|
1149
|
-
return validateSchema(schema, value);
|
|
1150
|
-
}
|
|
1151
|
-
function renderWithCtx(fn, liveHost, asyncChildren) {
|
|
1152
|
-
const ctx = pushRenderCtx(liveHost, asyncChildren);
|
|
1153
|
-
let isAsync = false;
|
|
1154
|
-
try {
|
|
1155
|
-
const html = unwrapHtml(fn());
|
|
1156
|
-
if (ctx.pending && ctx.pending.size > 0) {
|
|
1157
|
-
isAsync = true;
|
|
1158
|
-
return (async () => {
|
|
1159
|
-
try {
|
|
1160
|
-
return {
|
|
1161
|
-
html: await resolveAsyncChildren(html, ctx),
|
|
1162
|
-
slots: ctx.slots,
|
|
1163
|
-
binds: ctx.binds
|
|
1164
|
-
};
|
|
1165
|
-
} finally {
|
|
1166
|
-
popRenderCtx();
|
|
1167
|
-
}
|
|
1168
|
-
})();
|
|
1169
|
-
}
|
|
1170
|
-
return {
|
|
1171
|
-
html,
|
|
1172
|
-
slots: ctx.slots,
|
|
1173
|
-
binds: ctx.binds
|
|
1174
|
-
};
|
|
1175
|
-
} finally {
|
|
1176
|
-
if (!isAsync) popRenderCtx();
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
function buildPlainState(input) {
|
|
1180
|
-
const state = {};
|
|
1181
|
-
for (const entry of states) {
|
|
1182
|
-
const value = typeof entry.init === "function" ? entry.init(input) : entry.init;
|
|
1183
|
-
const accessor = markSignalAccessor((...args) => {
|
|
1184
|
-
if (args.length === 0) return value;
|
|
1185
|
-
});
|
|
1186
|
-
state[entry.key] = accessor;
|
|
1187
|
-
}
|
|
1188
|
-
return state;
|
|
1189
|
-
}
|
|
1190
|
-
function buildSignalState(input, snapshot) {
|
|
1191
|
-
const state = {};
|
|
1192
|
-
for (const entry of states) {
|
|
1193
|
-
const s = signal(snapshot && entry.key in snapshot ? snapshot[entry.key] : typeof entry.init === "function" ? entry.init(input) : entry.init);
|
|
1194
|
-
const accessor = markSignalAccessor((...args) => {
|
|
1195
|
-
if (args.length === 0) return s();
|
|
1196
|
-
s(args[0]);
|
|
1197
|
-
});
|
|
1198
|
-
state[entry.key] = accessor;
|
|
1199
|
-
}
|
|
1200
|
-
return state;
|
|
1201
|
-
}
|
|
1202
|
-
function renderToString(props, sync = false) {
|
|
1203
|
-
const input = resolveInput(props);
|
|
1204
|
-
const state = buildPlainState(input);
|
|
1205
|
-
const results = deriveds.map((entry) => {
|
|
1206
|
-
const prevSub = setActiveSub(void 0);
|
|
1207
|
-
try {
|
|
1208
|
-
return {
|
|
1209
|
-
key: entry.key,
|
|
1210
|
-
result: entry.fn({
|
|
1211
|
-
state,
|
|
1212
|
-
input,
|
|
1213
|
-
signal: new AbortController().signal
|
|
1214
|
-
})
|
|
1215
|
-
};
|
|
1216
|
-
} catch (err) {
|
|
1217
|
-
return {
|
|
1218
|
-
key: entry.key,
|
|
1219
|
-
result: Promise.reject(err)
|
|
1220
|
-
};
|
|
1221
|
-
} finally {
|
|
1222
|
-
setActiveSub(prevSub);
|
|
1223
|
-
}
|
|
1224
|
-
});
|
|
1225
|
-
if (!results.some((r) => r.result instanceof Promise) || sync) {
|
|
1226
|
-
const envelopes = {};
|
|
1227
|
-
for (const r of results) if (r.result instanceof Promise) envelopes[r.key] = {
|
|
1228
|
-
loading: true,
|
|
1229
|
-
value: void 0,
|
|
1230
|
-
error: void 0
|
|
1231
|
-
};
|
|
1232
|
-
else envelopes[r.key] = {
|
|
1233
|
-
loading: false,
|
|
1234
|
-
value: r.result,
|
|
1235
|
-
error: void 0
|
|
1236
|
-
};
|
|
1237
|
-
const derived = buildDerivedAccessors(envelopes);
|
|
1238
|
-
const prevSub = setActiveSub(void 0);
|
|
1239
|
-
try {
|
|
1240
|
-
const { html } = renderWithCtx(() => fn({
|
|
1241
|
-
state,
|
|
1242
|
-
derived,
|
|
1243
|
-
input
|
|
1244
|
-
}));
|
|
1245
|
-
return stylePrefix + html;
|
|
1246
|
-
} finally {
|
|
1247
|
-
setActiveSub(prevSub);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
return Promise.all(results.map(async (r) => {
|
|
1251
|
-
try {
|
|
1252
|
-
return {
|
|
1253
|
-
key: r.key,
|
|
1254
|
-
envelope: {
|
|
1255
|
-
loading: false,
|
|
1256
|
-
value: await Promise.resolve(r.result),
|
|
1257
|
-
error: void 0
|
|
1258
|
-
}
|
|
1259
|
-
};
|
|
1260
|
-
} catch (err) {
|
|
1261
|
-
return {
|
|
1262
|
-
key: r.key,
|
|
1263
|
-
envelope: {
|
|
1264
|
-
loading: false,
|
|
1265
|
-
value: void 0,
|
|
1266
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
1267
|
-
}
|
|
1268
|
-
};
|
|
1269
|
-
}
|
|
1270
|
-
})).then(async (resolved) => {
|
|
1271
|
-
const envelopes = {};
|
|
1272
|
-
for (const r of resolved) envelopes[r.key] = r.envelope;
|
|
1273
|
-
const derived = buildDerivedAccessors(envelopes);
|
|
1274
|
-
const prevSub = setActiveSub(void 0);
|
|
1275
|
-
try {
|
|
1276
|
-
const { html } = await renderWithCtx(() => fn({
|
|
1277
|
-
state,
|
|
1278
|
-
derived,
|
|
1279
|
-
input
|
|
1280
|
-
}), void 0, true);
|
|
1281
|
-
return stylePrefix + html;
|
|
1282
|
-
} finally {
|
|
1283
|
-
setActiveSub(prevSub);
|
|
1284
|
-
}
|
|
1285
|
-
});
|
|
1286
|
-
}
|
|
1287
|
-
function mountIsland(host, props) {
|
|
1288
|
-
return mountIslandInternal(host, props).unmount;
|
|
1289
|
-
}
|
|
1290
|
-
function mountIslandInternal(host, props) {
|
|
1291
|
-
const noop = {
|
|
1292
|
-
unmount: () => {},
|
|
1293
|
-
updateProps: () => {}
|
|
1294
|
-
};
|
|
1295
|
-
if (__DEV__ && _mountedHosts) {
|
|
1296
|
-
if (_mountedHosts.has(host)) {
|
|
1297
|
-
warn(`mount(): this element is already mounted. Call the previous unmount() first to avoid memory leaks and duplicate event listeners.
|
|
1298
|
-
Element: ${host.outerHTML.slice(0, 120)}`);
|
|
1299
|
-
return noop;
|
|
1300
|
-
}
|
|
1301
|
-
_mountedHosts.add(host);
|
|
1302
|
-
}
|
|
1303
|
-
if (props === void 0) {
|
|
1304
|
-
const rawProps = host.getAttribute(PROPS_ATTR);
|
|
1305
|
-
if (rawProps) try {
|
|
1306
|
-
props = JSON.parse(rawProps);
|
|
1307
|
-
} catch {
|
|
1308
|
-
warn("Failed to parse data-ilha-props — invalid JSON, falling back to empty props.");
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
const inputSignal = signal(resolveInput(props));
|
|
1312
|
-
const input = new Proxy({}, {
|
|
1313
|
-
get(_t, key) {
|
|
1314
|
-
return inputSignal()[key];
|
|
1315
|
-
},
|
|
1316
|
-
has(_t, key) {
|
|
1317
|
-
return key in inputSignal();
|
|
1318
|
-
},
|
|
1319
|
-
ownKeys() {
|
|
1320
|
-
const prevSub = setActiveSub(void 0);
|
|
1321
|
-
try {
|
|
1322
|
-
return Reflect.ownKeys(inputSignal());
|
|
1323
|
-
} finally {
|
|
1324
|
-
setActiveSub(prevSub);
|
|
1325
|
-
}
|
|
1326
|
-
},
|
|
1327
|
-
getOwnPropertyDescriptor(_t, key) {
|
|
1328
|
-
const prevSub = setActiveSub(void 0);
|
|
1329
|
-
try {
|
|
1330
|
-
return Reflect.getOwnPropertyDescriptor(inputSignal(), key);
|
|
1331
|
-
} finally {
|
|
1332
|
-
setActiveSub(prevSub);
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
});
|
|
1336
|
-
let snapshotRaw;
|
|
1337
|
-
const rawState = host.getAttribute(STATE_ATTR);
|
|
1338
|
-
if (rawState) try {
|
|
1339
|
-
snapshotRaw = JSON.parse(rawState);
|
|
1340
|
-
} catch {
|
|
1341
|
-
warn("Failed to parse data-ilha-state — invalid JSON, snapshot ignored.");
|
|
1342
|
-
}
|
|
1343
|
-
const stateSnapshot = snapshotRaw ? Object.fromEntries(Object.entries(snapshotRaw).filter(([k]) => k !== "_derived" && k !== "_skipOnMount")) : void 0;
|
|
1344
|
-
const derivedSnapshotRaw = snapshotRaw?._derived;
|
|
1345
|
-
let derivedSnapshot;
|
|
1346
|
-
if (derivedSnapshotRaw) {
|
|
1347
|
-
derivedSnapshot = {};
|
|
1348
|
-
for (const [k, v] of Object.entries(derivedSnapshotRaw)) if (v.error && !(v.error instanceof Error)) derivedSnapshot[k] = {
|
|
1349
|
-
...v,
|
|
1350
|
-
error: new Error(String(v.error))
|
|
1351
|
-
};
|
|
1352
|
-
else derivedSnapshot[k] = v;
|
|
1353
|
-
}
|
|
1354
|
-
const hydrated = snapshotRaw != null;
|
|
1355
|
-
const shouldSkipOnMount = hydrated && snapshotRaw?.["_skipOnMount"] === true;
|
|
1356
|
-
const state = buildSignalState(input, stateSnapshot);
|
|
1357
|
-
const cleanups = [];
|
|
1358
|
-
const unmountController = new AbortController();
|
|
1359
|
-
cleanups.push(() => unmountController.abort());
|
|
1360
|
-
if (transition?.enter) {
|
|
1361
|
-
const result = transition.enter(host);
|
|
1362
|
-
if (result instanceof Promise) result.catch(console.error);
|
|
1363
|
-
}
|
|
1364
|
-
const { proxy: derived, setup: setupDerived } = createDerivedProxy(deriveds, state, input, derivedSnapshot);
|
|
1365
|
-
let stopDerived = () => {};
|
|
1366
|
-
function reportError(err, source) {
|
|
1367
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
1368
|
-
if (onErrors.length === 0) {
|
|
1369
|
-
console.error(error);
|
|
1370
|
-
return;
|
|
1371
|
-
}
|
|
1372
|
-
for (const entry of onErrors) try {
|
|
1373
|
-
entry.fn({
|
|
1374
|
-
error,
|
|
1375
|
-
source,
|
|
1376
|
-
state,
|
|
1377
|
-
derived,
|
|
1378
|
-
input,
|
|
1379
|
-
host
|
|
1380
|
-
});
|
|
1381
|
-
} catch (handlerErr) {
|
|
1382
|
-
console.error(handlerErr);
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
const mountedSlots = /* @__PURE__ */ new Map();
|
|
1386
|
-
const leavingSlots = /* @__PURE__ */ new Map();
|
|
1387
|
-
function teardownMountedSlot(id, entry) {
|
|
1388
|
-
mountedSlots.delete(id);
|
|
1389
|
-
const token = Symbol();
|
|
1390
|
-
const result = entry.unmount();
|
|
1391
|
-
const remove = (stub) => {
|
|
1392
|
-
const leaving = leavingSlots.get(id);
|
|
1393
|
-
if (!leaving || leaving.token !== token) return;
|
|
1394
|
-
leaving.el.remove();
|
|
1395
|
-
stub?.remove();
|
|
1396
|
-
leavingSlots.delete(id);
|
|
1397
|
-
};
|
|
1398
|
-
if (result instanceof Promise) {
|
|
1399
|
-
let stub;
|
|
1400
|
-
if (entry.el.isConnected) {
|
|
1401
|
-
stub = document.createElement("div");
|
|
1402
|
-
stub.setAttribute(SLOT_ATTR, id);
|
|
1403
|
-
entry.el.replaceWith(stub);
|
|
1404
|
-
}
|
|
1405
|
-
leavingSlots.set(id, {
|
|
1406
|
-
el: entry.el,
|
|
1407
|
-
token
|
|
1408
|
-
});
|
|
1409
|
-
result.finally(() => remove(stub));
|
|
1410
|
-
} else entry.el.remove();
|
|
1411
|
-
}
|
|
1412
|
-
function findSlot(id) {
|
|
1413
|
-
const leavingEls = new Set([...leavingSlots.values()].map((l) => l.el));
|
|
1414
|
-
const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/["\\]/g, "\\$&");
|
|
1415
|
-
const candidates = host.querySelectorAll(`[${SLOT_ATTR}="${escaped}"]`);
|
|
1416
|
-
for (const candidate of candidates) {
|
|
1417
|
-
if (leavingEls.has(candidate)) continue;
|
|
1418
|
-
let el = candidate;
|
|
1419
|
-
while (el && el !== host) {
|
|
1420
|
-
if (el.hasAttribute("data-ilha")) break;
|
|
1421
|
-
el = el.parentElement;
|
|
1422
|
-
}
|
|
1423
|
-
if (el === host) return candidate;
|
|
1424
|
-
}
|
|
1425
|
-
return null;
|
|
1426
|
-
}
|
|
1427
|
-
function mountSlots(slotMap) {
|
|
1428
|
-
for (const [id, entry] of mountedSlots) if (!slotMap.has(id)) teardownMountedSlot(id, entry);
|
|
1429
|
-
for (const [id, { island: childIsland, props }] of slotMap) {
|
|
1430
|
-
const existing = mountedSlots.get(id);
|
|
1431
|
-
if (existing) {
|
|
1432
|
-
existing.updateProps(props);
|
|
1433
|
-
continue;
|
|
1434
|
-
}
|
|
1435
|
-
const slotEl = findSlot(id);
|
|
1436
|
-
if (!slotEl) continue;
|
|
1437
|
-
let slotProps = props;
|
|
1438
|
-
if (slotProps === void 0) {
|
|
1439
|
-
const rawProps = slotEl.getAttribute(PROPS_ATTR) ?? slotEl.getAttribute("data-props");
|
|
1440
|
-
if (rawProps) try {
|
|
1441
|
-
slotProps = JSON.parse(rawProps);
|
|
1442
|
-
} catch {
|
|
1443
|
-
warn(`Failed to parse props on [${SLOT_ATTR}="${id}"] — invalid JSON ignored.`);
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
const prevSub = setActiveSub(void 0);
|
|
1447
|
-
let handle;
|
|
1448
|
-
try {
|
|
1449
|
-
const internal = childIsland[ISLAND_MOUNT_INTERNAL];
|
|
1450
|
-
handle = internal ? internal(slotEl, slotProps) : {
|
|
1451
|
-
unmount: childIsland.mount(slotEl, slotProps),
|
|
1452
|
-
updateProps: () => {}
|
|
1453
|
-
};
|
|
1454
|
-
} finally {
|
|
1455
|
-
setActiveSub(prevSub);
|
|
1456
|
-
}
|
|
1457
|
-
mountedSlots.set(id, {
|
|
1458
|
-
el: slotEl,
|
|
1459
|
-
...handle
|
|
1460
|
-
});
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
const listeners = [];
|
|
1464
|
-
const firedOnce = /* @__PURE__ */ new Set();
|
|
1465
|
-
const invocationControllers = /* @__PURE__ */ new WeakMap();
|
|
1466
|
-
function attachListeners() {
|
|
1467
|
-
for (const entry of ons) {
|
|
1468
|
-
if (entry.options.once && firedOnce.has(entry)) continue;
|
|
1469
|
-
const targets = entry.selector === "" ? [host] : Array.from(host.querySelectorAll(entry.selector));
|
|
1470
|
-
if (__DEV__ && entry.selector !== "" && targets.length === 0) warn(`on(): selector "${entry.selector}" matched no elements at mount time. If the element is rendered later, this is expected — otherwise check your selector.`);
|
|
1471
|
-
targets.forEach((listenerTarget) => {
|
|
1472
|
-
const listener = (event) => {
|
|
1473
|
-
if (entry.options.once) {
|
|
1474
|
-
firedOnce.add(entry);
|
|
1475
|
-
for (const l of listeners.filter((l) => l.entry === entry)) l.target.removeEventListener(l.type, l.fn, l.options);
|
|
1476
|
-
listeners.splice(0, listeners.length, ...listeners.filter((l) => l.entry !== entry));
|
|
1477
|
-
}
|
|
1478
|
-
const eventTarget = event.target instanceof Element ? event.target : listenerTarget;
|
|
1479
|
-
const handlerTarget = entry.selector === "" ? eventTarget : listenerTarget;
|
|
1480
|
-
let handlerSignal;
|
|
1481
|
-
if (entry.abortable) {
|
|
1482
|
-
let entryMap = invocationControllers.get(entry);
|
|
1483
|
-
if (!entryMap) {
|
|
1484
|
-
entryMap = /* @__PURE__ */ new WeakMap();
|
|
1485
|
-
invocationControllers.set(entry, entryMap);
|
|
1486
|
-
}
|
|
1487
|
-
const prev = entryMap.get(listenerTarget);
|
|
1488
|
-
if (prev) prev.abort();
|
|
1489
|
-
const invocationController = new AbortController();
|
|
1490
|
-
entryMap.set(listenerTarget, invocationController);
|
|
1491
|
-
handlerSignal = anySignal([unmountController.signal, invocationController.signal]);
|
|
1492
|
-
} else handlerSignal = unmountController.signal;
|
|
1493
|
-
let result;
|
|
1494
|
-
startBatch();
|
|
1495
|
-
try {
|
|
1496
|
-
result = entry.handler({
|
|
1497
|
-
state,
|
|
1498
|
-
derived,
|
|
1499
|
-
input,
|
|
1500
|
-
host,
|
|
1501
|
-
target: handlerTarget,
|
|
1502
|
-
event,
|
|
1503
|
-
signal: handlerSignal
|
|
1504
|
-
});
|
|
1505
|
-
} catch (err) {
|
|
1506
|
-
reportError(err, "on");
|
|
1507
|
-
endBatch();
|
|
1508
|
-
return;
|
|
1509
|
-
}
|
|
1510
|
-
endBatch();
|
|
1511
|
-
if (result instanceof Promise) result.catch((err) => {
|
|
1512
|
-
if (err && err.name === "AbortError") return;
|
|
1513
|
-
reportError(err, "on");
|
|
1514
|
-
});
|
|
1515
|
-
};
|
|
1516
|
-
const opts = {
|
|
1517
|
-
...entry.options,
|
|
1518
|
-
once: false
|
|
1519
|
-
};
|
|
1520
|
-
listenerTarget.addEventListener(entry.event, listener, opts);
|
|
1521
|
-
listeners.push({
|
|
1522
|
-
target: listenerTarget,
|
|
1523
|
-
type: entry.event,
|
|
1524
|
-
fn: listener,
|
|
1525
|
-
options: opts,
|
|
1526
|
-
entry
|
|
1527
|
-
});
|
|
1528
|
-
});
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
function detachListeners() {
|
|
1532
|
-
for (const l of listeners) l.target.removeEventListener(l.type, l.fn, l.options);
|
|
1533
|
-
listeners.length = 0;
|
|
1534
|
-
}
|
|
1535
|
-
const hasExistingContent = hydrated && host.childNodes.length > 0;
|
|
1536
|
-
const initial = renderWithCtx(() => fn({
|
|
1537
|
-
state,
|
|
1538
|
-
derived,
|
|
1539
|
-
input
|
|
1540
|
-
}), hasExistingContent ? host : void 0);
|
|
1541
|
-
if (!hasExistingContent) host.innerHTML = stylePrefix + initial.html;
|
|
1542
|
-
let stopBindings = applyTemplateBindings(host, initial.binds);
|
|
1543
|
-
cleanups.push(() => stopBindings());
|
|
1544
|
-
mountSlots(initial.slots);
|
|
1545
|
-
cleanups.push(() => mountedSlots.forEach((entry) => entry.unmount()));
|
|
1546
|
-
attachListeners();
|
|
1547
|
-
if (!shouldSkipOnMount) for (const entry of onMounts) {
|
|
1548
|
-
const prevSub = setActiveSub(void 0);
|
|
1549
|
-
let userCleanup;
|
|
1550
|
-
try {
|
|
1551
|
-
userCleanup = entry.fn({
|
|
1552
|
-
state,
|
|
1553
|
-
derived,
|
|
1554
|
-
input,
|
|
1555
|
-
host,
|
|
1556
|
-
hydrated
|
|
1557
|
-
});
|
|
1558
|
-
} finally {
|
|
1559
|
-
setActiveSub(prevSub);
|
|
1560
|
-
}
|
|
1561
|
-
if (userCleanup) cleanups.push(userCleanup);
|
|
1562
|
-
}
|
|
1563
|
-
for (const entry of effects) {
|
|
1564
|
-
let userCleanup;
|
|
1565
|
-
let runController = null;
|
|
1566
|
-
const stopEffect = effect(() => {
|
|
1567
|
-
if (userCleanup) {
|
|
1568
|
-
try {
|
|
1569
|
-
userCleanup();
|
|
1570
|
-
} catch (err) {
|
|
1571
|
-
reportError(err, "effect");
|
|
1572
|
-
}
|
|
1573
|
-
userCleanup = void 0;
|
|
1574
|
-
}
|
|
1575
|
-
if (runController) runController.abort();
|
|
1576
|
-
runController = new AbortController();
|
|
1577
|
-
const runSignal = anySignal([unmountController.signal, runController.signal]);
|
|
1578
|
-
startBatch();
|
|
1579
|
-
try {
|
|
1580
|
-
userCleanup = entry.fn({
|
|
1581
|
-
state,
|
|
1582
|
-
derived,
|
|
1583
|
-
input,
|
|
1584
|
-
host,
|
|
1585
|
-
signal: runSignal
|
|
1586
|
-
});
|
|
1587
|
-
} catch (err) {
|
|
1588
|
-
reportError(err, "effect");
|
|
1589
|
-
} finally {
|
|
1590
|
-
endBatch();
|
|
1591
|
-
}
|
|
1592
|
-
});
|
|
1593
|
-
cleanups.push(() => {
|
|
1594
|
-
stopEffect();
|
|
1595
|
-
if (userCleanup) try {
|
|
1596
|
-
userCleanup();
|
|
1597
|
-
} catch (err) {
|
|
1598
|
-
reportError(err, "effect");
|
|
1599
|
-
}
|
|
1600
|
-
if (runController) runController.abort();
|
|
1601
|
-
});
|
|
1602
|
-
}
|
|
1603
|
-
stopDerived = setupDerived();
|
|
1604
|
-
cleanups.push(stopDerived);
|
|
1605
|
-
let initialized = false;
|
|
1606
|
-
const initialRenderedHtml = initial.html;
|
|
1607
|
-
const stopRender = effect(() => {
|
|
1608
|
-
const { html: rendered, slots: newSlotMap, binds: newBinds } = renderWithCtx(() => fn({
|
|
1609
|
-
state,
|
|
1610
|
-
derived,
|
|
1611
|
-
input
|
|
1612
|
-
}), host);
|
|
1613
|
-
const html = stylePrefix + rendered;
|
|
1614
|
-
if (!initialized) {
|
|
1615
|
-
initialized = true;
|
|
1616
|
-
if (rendered === initialRenderedHtml) return;
|
|
1617
|
-
if (mountedSlots.size > 0 && mountedSlots.size === newSlotMap.size && [...newSlotMap.keys()].every((id) => mountedSlots.has(id)) && isStableInlineSlotMount(initialRenderedHtml, rendered, newSlotMap.keys())) return;
|
|
1618
|
-
}
|
|
1619
|
-
detachListeners();
|
|
1620
|
-
stopBindings();
|
|
1621
|
-
const prevMountedCount = mountedSlots.size;
|
|
1622
|
-
const removedSlotIds = [];
|
|
1623
|
-
for (const [id, entry] of mountedSlots) if (!newSlotMap.has(id)) {
|
|
1624
|
-
teardownMountedSlot(id, entry);
|
|
1625
|
-
removedSlotIds.push(id);
|
|
1626
|
-
}
|
|
1627
|
-
for (const id of removedSlotIds) {
|
|
1628
|
-
if (leavingSlots.has(id)) continue;
|
|
1629
|
-
const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/["\\]/g, "\\$&");
|
|
1630
|
-
for (const el of host.querySelectorAll(`[${SLOT_ATTR}="${escaped}"]`)) el.remove();
|
|
1631
|
-
}
|
|
1632
|
-
const preserved = /* @__PURE__ */ new Map();
|
|
1633
|
-
const listShrunk = newSlotMap.size < prevMountedCount;
|
|
1634
|
-
for (const [id, entry] of mountedSlots) {
|
|
1635
|
-
if (!newSlotMap.has(id)) continue;
|
|
1636
|
-
if (!entry.el.isConnected) continue;
|
|
1637
|
-
if (listShrunk && id.startsWith("p:")) {
|
|
1638
|
-
teardownMountedSlot(id, entry);
|
|
1639
|
-
continue;
|
|
1640
|
-
}
|
|
1641
|
-
const stub = document.createElement("div");
|
|
1642
|
-
stub.setAttribute(SLOT_ATTR, id);
|
|
1643
|
-
const incomingProps = serializeSlotProps(newSlotMap.get(id)?.props);
|
|
1644
|
-
if (incomingProps !== "") stub.setAttribute(PROPS_ATTR, incomingProps);
|
|
1645
|
-
entry.el.replaceWith(stub);
|
|
1646
|
-
preserved.set(id, entry.el);
|
|
1647
|
-
}
|
|
1648
|
-
const tpl = document.createElement("template");
|
|
1649
|
-
tpl.innerHTML = `<div>${html}</div>`;
|
|
1650
|
-
morphInner(host, tpl.content.firstElementChild);
|
|
1651
|
-
for (const [id, liveEl] of preserved) {
|
|
1652
|
-
const stub = findSlot(id);
|
|
1653
|
-
if (!stub) continue;
|
|
1654
|
-
stub.replaceWith(liveEl);
|
|
1655
|
-
}
|
|
1656
|
-
attachListeners();
|
|
1657
|
-
stopBindings = applyTemplateBindings(host, newBinds);
|
|
1658
|
-
mountSlots(newSlotMap);
|
|
1659
|
-
});
|
|
1660
|
-
let tornDown = false;
|
|
1661
|
-
const unmount = () => {
|
|
1662
|
-
if (tornDown) return;
|
|
1663
|
-
tornDown = true;
|
|
1664
|
-
if (__DEV__ && _mountedHosts) _mountedHosts.delete(host);
|
|
1665
|
-
stopRender();
|
|
1666
|
-
detachListeners();
|
|
1667
|
-
const pending = [];
|
|
1668
|
-
for (const [, entry] of mountedSlots) {
|
|
1669
|
-
const result = entry.unmount();
|
|
1670
|
-
if (result instanceof Promise) pending.push(result);
|
|
1671
|
-
}
|
|
1672
|
-
if (transition?.leave) {
|
|
1673
|
-
const result = transition.leave(host);
|
|
1674
|
-
if (result instanceof Promise) pending.push(result);
|
|
1675
|
-
}
|
|
1676
|
-
const finish = () => {
|
|
1677
|
-
for (const c of cleanups) c();
|
|
1678
|
-
};
|
|
1679
|
-
if (pending.length > 0) return Promise.all(pending).then(finish).catch((err) => {
|
|
1680
|
-
console.error(err);
|
|
1681
|
-
finish();
|
|
1682
|
-
});
|
|
1683
|
-
finish();
|
|
1684
|
-
};
|
|
1685
|
-
const updateProps = (nextProps) => {
|
|
1686
|
-
if (tornDown) return;
|
|
1687
|
-
const next = resolveInput(nextProps);
|
|
1688
|
-
if (shallowEqualInput(inputSignal(), next)) return;
|
|
1689
|
-
inputSignal(next);
|
|
1690
|
-
};
|
|
1691
|
-
return {
|
|
1692
|
-
unmount,
|
|
1693
|
-
updateProps
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
/**
|
|
1697
|
-
* Dual-mode island invocation:
|
|
1698
|
-
* - When called inside an `html` interpolation (i.e. {@link currentRenderCtx}
|
|
1699
|
-
* is active), returns an {@link IslandCall} carrying props to be emitted
|
|
1700
|
-
* as a child slot marker; {@link interpolateValue} consumes this marker.
|
|
1701
|
-
* - When called at the top level with no active render context, returns
|
|
1702
|
-
* SSR HTML via {@link renderToString} (backward-compatible public API).
|
|
1703
|
-
*
|
|
1704
|
-
* The declared return type omits {@link IslandCall} because TypeScript
|
|
1705
|
-
* cannot narrow based on runtime stack state.
|
|
1706
|
-
*/
|
|
1707
|
-
const island = function(props) {
|
|
1708
|
-
if (currentRenderCtx()) return {
|
|
1709
|
-
[ISLAND_CALL]: true,
|
|
1710
|
-
island,
|
|
1711
|
-
props,
|
|
1712
|
-
key: void 0
|
|
1713
|
-
};
|
|
1714
|
-
return renderToString(props);
|
|
1715
|
-
};
|
|
1716
|
-
island.toString = (props) => renderToString(props, true);
|
|
1717
|
-
island.mount = (host, props) => mountIsland(host, props);
|
|
1718
|
-
island[ISLAND_MOUNT_INTERNAL] = (host, props) => mountIslandInternal(host, props);
|
|
1719
|
-
island.key = (key) => {
|
|
1720
|
-
if (typeof key !== "string" || key.trim().length === 0) throw new Error("island.key() requires a non-empty string.");
|
|
1721
|
-
if (key.includes(":")) throw new Error(`island.key() key cannot contain the slot separator ":" (got "${key}").`);
|
|
1722
|
-
const keyed = ((props) => ({
|
|
1723
|
-
[ISLAND_CALL]: true,
|
|
1724
|
-
island,
|
|
1725
|
-
props,
|
|
1726
|
-
key
|
|
1727
|
-
}));
|
|
1728
|
-
keyed[ISLAND_CALL] = true;
|
|
1729
|
-
return keyed;
|
|
1730
|
-
};
|
|
1731
|
-
island[ISLAND] = true;
|
|
1732
|
-
island.hydratable = async (props, opts) => {
|
|
1733
|
-
const { name, as: tag = "div", snapshot = false, skipOnMount: explicitSkipOnMount } = opts;
|
|
1734
|
-
const resolvedProps = props ?? {};
|
|
1735
|
-
const inner = await renderToString(resolvedProps);
|
|
1736
|
-
const encodedProps = escapeHtml(JSON.stringify(resolvedProps));
|
|
1737
|
-
let stateAttr = "";
|
|
1738
|
-
if (snapshot !== false) {
|
|
1739
|
-
const doState = snapshot === true || snapshot.state !== false;
|
|
1740
|
-
const doDerived = snapshot === true || snapshot.derived !== false;
|
|
1741
|
-
const doSkipOnMount = explicitSkipOnMount ?? (doState || doDerived);
|
|
1742
|
-
const snapshotData = {};
|
|
1743
|
-
const input = resolveInput(resolvedProps);
|
|
1744
|
-
const plainState = buildPlainState(input);
|
|
1745
|
-
if (doState) for (const entry of states) snapshotData[entry.key] = plainState[entry.key]();
|
|
1746
|
-
if (doDerived) {
|
|
1747
|
-
const derivedResults = {};
|
|
1748
|
-
for (const entry of deriveds) {
|
|
1749
|
-
const prevSub = setActiveSub(void 0);
|
|
1750
|
-
let resultPromise;
|
|
1751
|
-
let syncError;
|
|
1752
|
-
let threw = false;
|
|
1753
|
-
try {
|
|
1754
|
-
resultPromise = entry.fn({
|
|
1755
|
-
state: plainState,
|
|
1756
|
-
input,
|
|
1757
|
-
signal: new AbortController().signal
|
|
1758
|
-
});
|
|
1759
|
-
} catch (err) {
|
|
1760
|
-
threw = true;
|
|
1761
|
-
syncError = err;
|
|
1762
|
-
} finally {
|
|
1763
|
-
setActiveSub(prevSub);
|
|
1764
|
-
}
|
|
1765
|
-
if (threw) {
|
|
1766
|
-
derivedResults[entry.key] = {
|
|
1767
|
-
loading: false,
|
|
1768
|
-
value: void 0,
|
|
1769
|
-
error: syncError instanceof Error ? syncError.message : String(syncError)
|
|
1770
|
-
};
|
|
1771
|
-
continue;
|
|
1772
|
-
}
|
|
1773
|
-
try {
|
|
1774
|
-
const result = await Promise.resolve(resultPromise);
|
|
1775
|
-
derivedResults[entry.key] = {
|
|
1776
|
-
loading: false,
|
|
1777
|
-
value: result,
|
|
1778
|
-
error: void 0
|
|
1779
|
-
};
|
|
1780
|
-
} catch (err) {
|
|
1781
|
-
derivedResults[entry.key] = {
|
|
1782
|
-
loading: false,
|
|
1783
|
-
value: void 0,
|
|
1784
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1785
|
-
};
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
snapshotData["_derived"] = derivedResults;
|
|
1789
|
-
}
|
|
1790
|
-
if (doSkipOnMount) snapshotData["_skipOnMount"] = true;
|
|
1791
|
-
stateAttr = ` ${STATE_ATTR}='${escapeHtml(JSON.stringify(snapshotData))}'`;
|
|
1792
|
-
}
|
|
1793
|
-
return `<${tag} data-ilha="${escapeHtml(name)}" ${PROPS_ATTR}='${encodedProps}'${stateAttr}>${inner}</${tag}>`;
|
|
1794
|
-
};
|
|
1795
|
-
return island;
|
|
1796
|
-
}
|
|
1797
|
-
};
|
|
1798
|
-
function ilhaFrom(selector, island, props) {
|
|
1799
|
-
const host = typeof selector === "string" ? document.querySelector(selector) : selector;
|
|
1800
|
-
if (!host) {
|
|
1801
|
-
console.warn(`[ilha] from(): element not found: ${selector}`);
|
|
1802
|
-
return null;
|
|
1803
|
-
}
|
|
1804
|
-
return island.mount(host, props);
|
|
1805
|
-
}
|
|
1806
|
-
function mountAll(registry, options = {}) {
|
|
1807
|
-
const root = options.root ?? document.body;
|
|
1808
|
-
const lazy = options.lazy ?? false;
|
|
1809
|
-
const unmounts = [];
|
|
1810
|
-
function activateEl(host) {
|
|
1811
|
-
const name = host.getAttribute("data-ilha");
|
|
1812
|
-
if (!name) return;
|
|
1813
|
-
const island = registry[name];
|
|
1814
|
-
if (!island) {
|
|
1815
|
-
warn(`mount(): no island registered under the name "${name}". Available names: [${Object.keys(registry).join(", ")}]. Check the data-ilha attribute on the element.`);
|
|
1816
|
-
return;
|
|
1817
|
-
}
|
|
1818
|
-
let props = {};
|
|
1819
|
-
const rawProps = host.getAttribute(PROPS_ATTR);
|
|
1820
|
-
if (rawProps) try {
|
|
1821
|
-
props = JSON.parse(rawProps);
|
|
1822
|
-
} catch {
|
|
1823
|
-
warn(`Failed to parse ${PROPS_ATTR} on [data-ilha="${name}"] — invalid JSON ignored.`);
|
|
1824
|
-
}
|
|
1825
|
-
unmounts.push(island.mount(host, props));
|
|
1826
|
-
}
|
|
1827
|
-
const els = Array.from(root.querySelectorAll("[data-ilha]"));
|
|
1828
|
-
if (lazy && typeof IntersectionObserver !== "undefined") {
|
|
1829
|
-
let disposed = false;
|
|
1830
|
-
const io = new IntersectionObserver((entries) => {
|
|
1831
|
-
if (disposed) return;
|
|
1832
|
-
for (const entry of entries) if (entry.isIntersecting) {
|
|
1833
|
-
activateEl(entry.target);
|
|
1834
|
-
io.unobserve(entry.target);
|
|
1835
|
-
}
|
|
1836
|
-
});
|
|
1837
|
-
els.forEach((el) => io.observe(el));
|
|
1838
|
-
unmounts.push(() => {
|
|
1839
|
-
disposed = true;
|
|
1840
|
-
io.disconnect();
|
|
1841
|
-
});
|
|
1842
|
-
} else els.forEach(activateEl);
|
|
1843
|
-
return { unmount: () => {
|
|
1844
|
-
const pending = [];
|
|
1845
|
-
for (const u of unmounts) {
|
|
1846
|
-
const result = u();
|
|
1847
|
-
if (result instanceof Promise) pending.push(result);
|
|
1848
|
-
}
|
|
1849
|
-
if (pending.length > 0) return Promise.all(pending).then(() => {});
|
|
1850
|
-
} };
|
|
1851
|
-
}
|
|
1852
|
-
const rootBuilder = new IlhaBuilder({
|
|
1853
|
-
schema: null,
|
|
1854
|
-
states: [],
|
|
1855
|
-
deriveds: [],
|
|
1856
|
-
ons: [],
|
|
1857
|
-
effects: [],
|
|
1858
|
-
onMounts: [],
|
|
1859
|
-
onErrors: [],
|
|
1860
|
-
transition: null,
|
|
1861
|
-
css: null
|
|
1862
|
-
});
|
|
1863
|
-
const ilha = Object.assign(rootBuilder, {
|
|
1864
|
-
html: ilhaHtml,
|
|
1865
|
-
raw: ilhaRaw,
|
|
1866
|
-
jsx: ilhaJsx,
|
|
1867
|
-
jsxs: ilhaJsx,
|
|
1868
|
-
jsxDEV: ilhaJsxDEV,
|
|
1869
|
-
Fragment: ilhaFragment,
|
|
1870
|
-
mount: mountAll,
|
|
1871
|
-
from: ilhaFrom,
|
|
1872
|
-
context: ilhaContext,
|
|
1873
|
-
signal: ilhaSignal,
|
|
1874
|
-
batch,
|
|
1875
|
-
untrack
|
|
1876
|
-
});
|
|
1877
|
-
const html = ilhaHtml;
|
|
1878
|
-
const raw = ilhaRaw;
|
|
1879
|
-
const jsx = ilhaJsx;
|
|
1880
|
-
const jsxs = ilhaJsx;
|
|
1881
|
-
const jsxDEV = ilhaJsxDEV;
|
|
1882
|
-
const Fragment = ilhaFragment;
|
|
1883
|
-
const css = ilhaCss;
|
|
1884
|
-
const mount = mountAll;
|
|
1885
|
-
const from = ilhaFrom;
|
|
1886
|
-
const context = ilhaContext;
|
|
1887
|
-
//#endregion
|
|
1888
|
-
export { Fragment, ISLAND_MOUNT_INTERNAL, batch, context, css, ilha as default, from, html, ilhaSignal, ilhaSignal as signal, jsx, jsxDEV, jsxs, mount, raw, untrack };
|
|
1
|
+
import{effect as e,endBatch as t,setActiveSub as n,signal as r,startBatch as i}from"alien-signals";const a=typeof process<`u`?process.env?.NODE_ENV!==`production`:!0;function o(e){a&&console.warn(`[ilha] ${e}`)}function s(e,t){if(Object.is(e,t))return!0;if(typeof e!=`object`||!e||typeof t!=`object`||!t)return!1;let n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(let r of n)if(!Object.prototype.hasOwnProperty.call(t,r)||!Object.is(e[r],t[r]))return!1;return!0}function c(e){if(typeof document>`u`)return[];let t=document.createElement(`template`);t.innerHTML=e;let n=t.content,r=[];for(let e of n.querySelectorAll(`[data-ilha-slot]`)){if(!(e instanceof Element))continue;let t=e.parentElement??e.parentNode,i=!1;for(;t&&t!==n;){if(t instanceof Element&&t.hasAttribute(`data-ilha-slot`)){i=!0;break}t=t instanceof Element?t.parentElement??t.parentNode:t.parentNode}i||r.push(e.getAttribute(`data-ilha-slot`))}return r}function l(e,t){let n=t.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),r=RegExp(`<[a-z][a-z0-9-]*\\s[^>]*data-ilha-slot="${n}"[^>]*data-ilha-props='([^']*)'`,`i`),i=e.match(r);if(i)return i[1];let a=RegExp(`<[a-z][a-z0-9-]*\\s[^>]*data-ilha-slot="${n}"`,`i`);return e.match(a)?``:null}const u=/^[a-z][a-z0-9-]*$/i;function d(e){let t=e.trim();if(t.length===0)throw Error(`island.as() requires a non-empty HTML tag name.`);if(!u.test(t))throw Error(`island.as() tag must be a valid HTML element name (got "${e}"). Use names like "span", "div", or "li".`);return t.toLowerCase()}function f(e){let t=e[E];return typeof t==`string`&&t.length>0?t:`div`}function p(e,t,n,r){return`<${e} ${D}="${b(t)}"${n}>${r}</${e}>`}function m(e,t,n){let r=c(e),i=c(t);if(r.length!==i.length||r.some((e,t)=>e!==i[t]))return!1;for(let r of n){let n=l(e,r),i=l(t,r);if(n===null||i===null||n!==i)return!1}return!0}function ee(e){return e===void 0?``:JSON.stringify(e)}const te=new Set([`data-checked`,`data-unchecked`,`data-indeterminate`,`aria-checked`,`data-open`,`data-closed`,`data-state`,`aria-expanded`,`aria-hidden`,`data-selected`,`data-panel-open`]);function h(e,t){return e.matches(`[data-slot]`)&&te.has(t)}function g(e,t){for(let{name:n,value:r}of t.attributes)h(e,n)||e.getAttribute(n)!==r&&e.setAttribute(n,r);for(let{name:n}of Array.from(e.attributes))h(e,n)||t.hasAttribute(n)||e.removeAttribute(n)}function _(e,t){let n=Array.from(e.childNodes),r=Array.from(t.childNodes);for(let e=n.length-1;e>=r.length;e--)n[e].remove();for(let t=0;t<r.length;t++){let i=r[t],a=n[t];if(!a){e.appendChild(i.cloneNode(!0));continue}if(a.nodeType!==i.nodeType){e.replaceChild(i.cloneNode(!0),a);continue}if(a.nodeType===3||a.nodeType===8){a.nodeValue!==i.nodeValue&&(a.nodeValue=i.nodeValue);continue}if(a.nodeType===1){let t=a,n=i;if(t.localName!==n.localName||t.namespaceURI!==n.namespaceURI){e.replaceChild(n.cloneNode(!0),t);continue}if(t.localName===`input`&&t.type!==n.type){e.replaceChild(n.cloneNode(!0),t);continue}if(g(t,n),t.localName===`textarea`){let e=n.textContent??``;t.textContent!==e&&(t.textContent=e),t.value=t.defaultValue}else _(t,n)}}}function ne(e,t){if(e.localName!==t.localName||e.namespaceURI!==t.namespaceURI)throw Error(`[ilha] morph: elements must match`);_(e,t)}function v(e,t){let n=e[`~standard`].validate(t);if(n instanceof Promise)throw Error(`[ilha] Async schemas are not supported.`);if(n.issues)throw Error(`[ilha] Validation failed:\n${n.issues.map(e=>` - ${e.message}`).join(`
|
|
2
|
+
`)}`);return n.value}const y={"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`};function b(e){return String(e).replace(/[&<>"']/g,e=>y[e])}function x(e){if(e.length===0||e[0]!==`
|
|
3
|
+
`)return e;let t=e.split(`
|
|
4
|
+
`);for(;t.length&&t[0].trim()===``;)t.shift();for(;t.length&&t[t.length-1].trim()===``;)t.pop();if(!t.length)return``;let n=Math.min(...t.filter(e=>e.trim()!==``).map(e=>e.match(/^(\s*)/)[1].length));return t.map(e=>e.slice(n)).join(`
|
|
5
|
+
`)}const S=Symbol.for(`ilha.raw`),C=Symbol.for(`ilha.signalAccessor`),w=Symbol.for(`ilha.island`),T=Symbol.for(`ilha.islandCall`),re=Symbol.for(`ilha.islandMountInternal`),E=Symbol.for(`ilha.islandSlotTag`),D=`data-ilha-slot`,O=`data-ilha-props`,ie=`data-ilha-state`;function ae(e){return`<style data-ilha-css>@scope (:scope) to ([data-ilha]){${e}}</style>`}const k=[];function A(e,t){let n={slots:new Map,positional:0,liveHost:e,pending:t?new Map:void 0,binds:[],slotTags:new Map};return k.push(n),n}function j(){k.pop()}function M(){return k[k.length-1]}function N(e){return typeof e==`function`?w in e?!0:Object.getOwnPropertySymbols(e).some(e=>e.description===`ilha.island`):!1}function P(e){if(e==null||typeof e!=`object`&&typeof e!=`function`)return!1;if(T in e||Object.getOwnPropertySymbols(e).some(e=>e.description===`ilha.islandCall`))return!0;if(typeof e==`object`&&`island`in e){let t=e.island;return typeof t==`function`&&(w in t||Object.getOwnPropertySymbols(t).some(e=>e.description===`ilha.island`))}return!1}function F(e,t,n){let r=M(),i;n===void 0?i=r?`p:${r.positional++}`:`p:0`:(i=`k:${n}`,r&&a&&r.slots.has(i)&&o(`Duplicate slot key "${n}" — two children with the same key in a single render will collide. Each .key() call must be unique.`)),r&&r.slots.set(i,{island:e,props:t});let s=f(e);r&&r.slotTags.set(i,s);let c=t?` ${O}='${b(JSON.stringify(t))}'`:``;if(r?.liveHost)return p(s,i,c,``);if(r?.pending){j();try{let n=e(t);return n instanceof Promise?(r.pending.set(i,n.then(String)),p(s,i,c,``)):p(s,i,c,String(n))}finally{k.push(r)}}let l=e.toString(t);return p(s,i,c,l)}async function oe(e,t){for(let[n,r]of t.pending){let i=await r,a=t.slotTags.get(n)??`div`,o=b(n);if(o.length>500)throw Error(`Slot id exceeds safe length for regex replacement (${o.length} > 500).`);let s=o.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),c=a.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),l=RegExp(`<${c}\\s[^>]*${D}="${s}"[^>]*></${c}>`,`g`);e=e.replace(l,e=>{let t=`></${a}>`;return e.slice(0,-t.length)+`>${i}</${a}>`})}return e}function I(e){e[C]=!0;let t=e;return t.select=e=>V(t,e),t}function L(e){return typeof e==`function`?C in e?!0:Object.getOwnPropertySymbols(e).some(e=>e.description===`ilha.signalAccessor`):!1}function R(e,t){let n=e;for(let e of t){if(n==null)return;n=n[e]}return n}function z(e,t,n){if(t.length===0)return n;let[r,...i]=t;if(Array.isArray(e)){let t=r;if(t<0||t>=e.length)return e;let a=e[t],o=i.length===0?n:z(a,i,n);if(Object.is(a,o))return e;let s=e.slice();return s[t]=o,s}if(typeof e==`object`&&e){let t=e,a=String(r),o=t[a],s=i.length===0?n:z(o,i,n);if(i.length===0){if(Object.is(o,n))return e}else if(Object.is(o,s))return e;return{...t,[a]:s}}return i.length===0?n:z(void 0,i,n)}function B(e){return typeof e==`symbol`||e===`length`?null:/^\d+$/.test(e)?Number(e):e}function se(e,t){let n=[],r=e=>e===null||typeof e!=`object`&&typeof e!=`function`?e:new Proxy(e,{get(e,t,i){let a=B(t);a!=null&&n.push(a);let o=Reflect.get(e,t,i);return a!=null&&typeof o==`object`&&o?r(o):o}});return t(r(e)),n}function V(e,t){let n=se(e(),t),r=t(e()),i=n.length===0?e():R(e(),n);if(!Object.is(r,i)){let e=`select(): selector must only traverse nested properties or array indexes — derived or transformed values are not supported.`;throw a&&o(e),Error(e)}return a&&n.length===0&&o(`select(): selector did not traverse nested state — bind writes may replace the entire root value.`),I((...r)=>{if(r.length===0)return n.length===0?t(e()):R(e(),n);let i=n.length===0?r[0]:z(e(),n,r[0]);Object.is(e(),i)||e(i)})}function H(e){return{[S]:!0,value:e}}function U(e,...t){if(typeof e==`string`)return e;let n=``;for(let r=0;r<e.length;r++)n+=e[r],r<t.length&&(n+=String(t[r]));return n}function W(e){if(e==null||e===!0||e===!1)return``;if(Array.isArray(e))return e.map(W).join(``);if(Z(e))return e.value;if(P(e)){let t=typeof e==`function`?e():e;return F(t.island,t.props,t.key)}return N(e)?F(e,void 0,void 0):L(e)||typeof e==`function`?b(e()):b(e)}const G=new Set([`value`,`checked`,`valueAsNumber`,`valueAsDate`,`files`,`open`,`group`,`this`]),K=/\bbind:([a-zA-Z]+)\s*=\s*("|')?$/;function q(e){if(e instanceof Date){if(isNaN(e.getTime()))return a&&o(`bind:valueAsDate received an invalid Date object — value attribute will be empty.`),``;let t=e=>String(e).padStart(2,`0`);return`${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())}`}return``}function J(e){let t=e.lastIndexOf(`<`);if(t===-1)return null;let n=e.slice(t);if(n.includes(`>`))return null;let r=n.match(/\bvalue\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/);return r?r[2]??r[3]??r[4]??null:null}function Y(e,t,n,r){let i=`${e}:${t}`,a;try{a=n()}catch{a=void 0}switch(e){case`value`:return[` value="${b(a??``)}"`,i];case`valueAsNumber`:return[` value="${b(a==null?``:String(a))}"`,i];case`valueAsDate`:return[` value="${b(q(a))}"`,i];case`checked`:return[a?` checked`:``,i];case`open`:return[a?` open`:``,i];case`files`:return[``,i];case`this`:return[``,i];case`group`:{let e=J(r);return e==null?[``,i]:[(Array.isArray(a)?a.map(String).includes(e):a!=null&&String(a)===e)?` checked`:``,i]}}}function X(e,...t){let n=``,r=null,i=``,s=M();for(let c=0;c<e.length;c++){let l=e[c];if(r!==null&&(l.startsWith(r)&&(l=l.slice(1)),r=null),i!==``){let e=l.indexOf(`>`);e!==-1&&(l=l.slice(0,e)+` data-ilha-bind="${i}">`+l.slice(e+1),i=``)}if(c>=t.length){n+=l;continue}let u=t[c],d=l.match(K);if(d&&L(u)){let e=d[1],t=d[2]??null;if(!G.has(e)){a&&o(`Unknown bind:${e} — supported bindings are ${[...G].map(e=>`bind:${e}`).join(`, `)}.`),n+=l+W(u);continue}if(!s){a&&o(`bind:${e} used outside an island render — bindings only work in .render(). The value is reflected once but not wired.`);let i=l.slice(0,l.length-d[0].length),s=t??`"`;n+=i+e+`=`+s+W(u)+s,t&&(r=t);continue}let c=l.length-d[0].length,f=l.slice(0,c).replace(/\s+$/,``),p=s.binds.length;s.binds.push({kind:e,accessor:u});let[m,ee]=Y(e,p,u,f);n+=f+m,i+=(i?`,`:``)+ee,t&&(r=t);continue}d&&a&&o(`bind:${d[1]} requires a signal accessor — got ${typeof u}. Use ilha.signal() or a .state() accessor.`),n+=l+W(u)}return i!==``&&(n+=` data-ilha-bind="${i}"`),{[S]:!0,value:x(n)}}function Z(e){return typeof e!=`object`||!e||!(`value`in e)||typeof e.value!=`string`?!1:S in e?!0:Object.getOwnPropertySymbols(e).some(e=>e.description===`ilha.raw`)}function ce(e){return Z(e)?e.value:e}const Q=new Map;function le(e,t){if(Q.has(e))return Q.get(e);let n=r(t),i=(...e)=>{if(e.length===0)return n();n(e[0])};return Q.set(e,i),i}function ue(e){let t=r(e);return I((...e)=>{if(e.length===0)return t();t(e[0])})}function de(e){let t=n(void 0);try{return e()}finally{n(t)}}function fe(e){i();try{return e()}finally{t()}}function pe(e,t){let n=I((...n)=>{if(n.length>0){t?t(n[0]):a&&o(`derived values are read-only`);return}return e().value});return new Proxy(n,{get(t,n,r){return n===`loading`||n===`value`||n===`error`?e()[n]:Reflect.get(t,n,r)}})}function me(){return pe(()=>({loading:!1,value:void 0,error:void 0}))}function he(e){let t=new Map;for(let[n,r]of Object.entries(e))t.set(n,pe(()=>r,e=>{r.loading=!1,r.value=e,r.error=void 0}));return new Proxy({},{get(e,n){return t.get(n)??me()}})}function ge(t,i,a,o){let s=new Map;for(let e of t){let t;if(o!=null&&e.key in o)t={...o[e.key]};else if(e.fn.constructor.name===`AsyncFunction`||e.fn.constructor.name===`AsyncGeneratorFunction`)t={loading:!0,value:void 0,error:void 0};else{let n=new AbortController;try{let r=e.fn({state:i,input:a,signal:n.signal});r instanceof Promise?(t={loading:!0,value:void 0,error:void 0},r.catch(()=>{})):t={loading:!1,value:r,error:void 0}}catch(e){t={loading:!1,value:void 0,error:e instanceof Error?e:Error(String(e))}}finally{n.abort()}}let n=r(t);s.set(e.key,n)}let c=new Map;for(let e of t){let t=s.get(e.key);c.set(e.key,pe(()=>t(),e=>{let r=n(void 0);t({loading:!1,value:e,error:void 0}),n(r)}))}return{proxy:new Proxy({},{get(e,t){return c.get(t)??me()}}),setup:()=>{let r=[];for(let c of t){let t=s.get(c.key),l=new AbortController,u=o!=null&&c.key in o,d=e(()=>{l.abort(),l=new AbortController;let e=l,r;try{r=c.fn({state:i,input:a,signal:e.signal})}catch(e){if(u){u=!1;return}let r=n(void 0);t({loading:!1,value:void 0,error:e instanceof Error?e:Error(String(e))}),n(r);return}if(u){u=!1,r instanceof Promise&&r.catch(()=>{});return}if(!(r instanceof Promise)){let e=n(void 0);t({loading:!1,value:r,error:void 0}),n(e);return}let o=n(void 0);t({loading:!0,value:t().value,error:void 0}),n(o),r.then(n=>{e.signal.aborted||t({loading:!1,value:n,error:void 0})}).catch(n=>{e.signal.aborted||t({loading:!1,value:void 0,error:n instanceof Error?n:Error(String(n))})})});r.push(()=>{d(),l.abort()})}return()=>r.forEach(e=>e())}}}const _e=`data-ilha-bind`;function ve(e){let t=[];for(let n of e.split(`,`)){let[e,r]=n.split(`:`);if(!e||!r)continue;let i=Number(r);!Number.isInteger(i)||i<0||t.push({kind:e,index:i})}return t}function ye(e,t){let n=e;switch(t){case`value`:return{event:e.tagName===`SELECT`?`change`:`input`,read:e=>e.value,write:(e,t)=>e.value=t==null?``:String(t)};case`valueAsNumber`:return{event:`input`,read:e=>{let t=e.valueAsNumber;return Number.isNaN(t)?null:t},write:(e,t)=>e.value=t==null||Number.isNaN(t)?``:String(t)};case`valueAsDate`:return{event:`input`,read:e=>e.valueAsDate,write:(e,t)=>{e.value=q(t)}};case`checked`:return{event:`change`,read:e=>e.checked,write:(e,t)=>e.checked=!!t};case`files`:return{event:`change`,read:e=>e.files,write:()=>{}};case`open`:return{event:`toggle`,read:e=>e.open,write:(e,t)=>e.open=!!t};case`this`:return{event:null,read:()=>void 0,write:()=>{}};case`group`:{let e=n.type===`checkbox`;return{event:`change`,read:t=>{let n=t;return e?{__ilhaGroup:!0,value:n.value,checked:n.checked}:n.checked?n.value:void 0},write:(t,n)=>{let r=t;e?r.checked=(Array.isArray(n)?n.map(String):[]).includes(r.value):r.checked=n!=null&&String(n)===r.value}}}}}function be(e,t){if(t.length===0)return()=>{};let n=[],r=[];e.hasAttribute(_e)&&r.push(e);for(let t of e.querySelectorAll(`[${_e}]`))r.push(t);for(let e of r){let r=ve(e.getAttribute(_e));for(let i of r){let r=t[i.index];if(!r){a&&o(`bind:${i.kind} index ${i.index} not found in render — the data-ilha-bind sentinel may have been hand-edited or survived a stale render.`);continue}if(r.kind!==i.kind){a&&o(`bind:${i.kind} sentinel points at a binding registered as bind:${r.kind}. Sentinel may be stale.`);continue}let{event:s,read:c,write:l}=ye(e,i.kind),u=r.accessor;if(i.kind===`this`){u(e),n.push(()=>u(null));continue}try{l(e,u())}catch(e){a&&console.error(`[ilha] bind:${i.kind} write failed:`,e)}if(s===null)continue;let d=()=>{let t=c(e);if(i.kind===`group`){let e=t;if(e===void 0)return;if(typeof e==`object`&&e.__ilhaGroup){let t=u(),n=Array.isArray(t)?[...t]:[],r=n.findIndex(t=>String(t)===e.value);if(e.checked&&r===-1){let r=e.value,i=Array.isArray(t)&&t.length>0?t[0]:void 0;if(i!==void 0)if(typeof i==`number`){let e=Number(r);r=Number.isNaN(e)?r:e}else typeof i==`boolean`&&(r=!!r);n.push(r)}else !e.checked&&r!==-1&&n.splice(r,1);u(n);return}let n=u(),r=e;if(typeof n==`number`&&typeof e==`string`){let t=Number(e);r=Number.isNaN(t)?0:t}else typeof n==`boolean`&&(r=!!e);u(r);return}let n=u(),r=t;if(typeof n==`number`&&typeof t==`string`){let e=Number(t);r=Number.isNaN(e)?0:e}else typeof n==`boolean`&&(r=!!t);u(r)};e.addEventListener(s,d),n.push(()=>e.removeEventListener(s,d))}}return()=>n.forEach(e=>e())}function xe(e){let t=e.lastIndexOf(`@`),n=t===-1?``:e.slice(0,t),r=(t===-1?e:e.slice(t+1)).split(`:`),i=r[0],a=new Set(r.slice(1));return{selector:n,eventType:i,options:{once:a.has(`once`),capture:a.has(`capture`),passive:a.has(`passive`)},abortable:a.has(`abortable`)}}function Se(e){if(typeof AbortSignal.any==`function`)return AbortSignal.any(e);let t=new AbortController,n=[];for(let r of e){if(r.aborted)return t.abort(r.reason),n.forEach(e=>e()),t.signal;let e=()=>t.abort(r.reason);r.addEventListener(`abort`,e,{once:!0}),n.push(()=>r.removeEventListener(`abort`,e))}return t.signal.aborted||t.signal.addEventListener(`abort`,()=>{n.forEach(e=>e())}),t.signal}const $=a?new WeakSet:null;var Ce=class c{_cfg;constructor(e){this._cfg=e}input(e){return new c({schema:e??null,states:[],deriveds:[],ons:[],effects:[],onMounts:[],onErrors:[],transition:null,css:null,as:null})}as(e){return new c({...this._cfg,as:d(e)})}state(e,t){let n=this._cfg;return new c({...n,states:[...n.states,{key:e,init:t}]})}derived(e,t){let n=this._cfg;return new c({...n,deriveds:[...n.deriveds,{key:e,fn:t}]})}on(e,t){let n=xe(e);return new c({...this._cfg,ons:[...this._cfg.ons,{selector:n.selector,event:n.eventType,options:n.options,abortable:n.abortable,handler:t}]})}effect(e){return new c({...this._cfg,effects:[...this._cfg.effects,{fn:e}]})}onMount(e){return new c({...this._cfg,onMounts:[...this._cfg.onMounts,{fn:e}]})}onError(e){return new c({...this._cfg,onErrors:[...this._cfg.onErrors,{fn:e}]})}transition(e){return new c({...this._cfg,transition:e})}css(e,...t){let n;if(typeof e==`string`)n=e;else{let r=``;for(let n=0;n<e.length;n++)r+=e[n],n<t.length&&(r+=String(t[n]));n=r}return a&&this._cfg.css!==null&&o(`css(): called more than once on the same builder chain. The previous stylesheet has been discarded. Compose styles into a single .css() call instead.`),new c({...this._cfg,css:n})}render(c){let{schema:l,states:u,deriveds:f,ons:p,effects:te,onMounts:h,onErrors:g,transition:_,css:y,as:x}=this._cfg,S=y==null?``:ae(y),C=x??`div`;function k(e){let t=e??{};return l?v(l,t):t}function N(e,t,n){let r=A(t,n),i=!1;try{let t=ce(e());return r.pending&&r.pending.size>0?(i=!0,(async()=>{try{return{html:await oe(t,r),slots:r.slots,binds:r.binds}}finally{j()}})()):{html:t,slots:r.slots,binds:r.binds}}finally{i||j()}}function P(e){let t={};for(let n of u){let r=typeof n.init==`function`?n.init(e):n.init,i=I((...e)=>{if(e.length===0)return r});t[n.key]=i}return t}function F(e,t){let n={};for(let i of u){let a=r(t&&i.key in t?t[i.key]:typeof i.init==`function`?i.init(e):i.init),o=I((...e)=>{if(e.length===0)return a();a(e[0])});n[i.key]=o}return n}function L(e,t=!1){let r=k(e),i=P(r),a=f.map(e=>{let t=n(void 0);try{return{key:e.key,result:e.fn({state:i,input:r,signal:new AbortController().signal})}}catch(t){return{key:e.key,result:Promise.reject(t)}}finally{n(t)}});if(!a.some(e=>e.result instanceof Promise)||t){let e={};for(let t of a)t.result instanceof Promise?e[t.key]={loading:!0,value:void 0,error:void 0}:e[t.key]={loading:!1,value:t.result,error:void 0};let t=he(e),o=n(void 0);try{let{html:e}=N(()=>c({state:i,derived:t,input:r}));return S+e}finally{n(o)}}return Promise.all(a.map(async e=>{try{return{key:e.key,envelope:{loading:!1,value:await Promise.resolve(e.result),error:void 0}}}catch(t){return{key:e.key,envelope:{loading:!1,value:void 0,error:t instanceof Error?t:Error(String(t))}}}})).then(async e=>{let t={};for(let n of e)t[n.key]=n.envelope;let a=he(t),o=n(void 0);try{let{html:e}=await N(()=>c({state:i,derived:a,input:r}),void 0,!0);return S+e}finally{n(o)}})}function R(e,t){return z(e,t).unmount}function z(l,u){let d={unmount:()=>{},updateProps:()=>{}};if(a&&$){if($.has(l))return o(`mount(): this element is already mounted. Call the previous unmount() first to avoid memory leaks and duplicate event listeners.
|
|
6
|
+
Element: ${l.outerHTML.slice(0,120)}`),d;$.add(l)}if(u===void 0){let e=l.getAttribute(O);if(e)try{u=JSON.parse(e)}catch{o(`Failed to parse data-ilha-props — invalid JSON, falling back to empty props.`)}}let v=r(k(u)),y=new Proxy({},{get(e,t){return v()[t]},has(e,t){return t in v()},ownKeys(){let e=n(void 0);try{return Reflect.ownKeys(v())}finally{n(e)}},getOwnPropertyDescriptor(e,t){let r=n(void 0);try{return Reflect.getOwnPropertyDescriptor(v(),t)}finally{n(r)}}}),b,x=l.getAttribute(ie);if(x)try{b=JSON.parse(x)}catch{o(`Failed to parse data-ilha-state — invalid JSON, snapshot ignored.`)}let C=b?Object.fromEntries(Object.entries(b).filter(([e])=>e!==`_derived`&&e!==`_skipOnMount`)):void 0,w=b?._derived,T;if(w){T={};for(let[e,t]of Object.entries(w))t.error&&!(t.error instanceof Error)?T[e]={...t,error:Error(String(t.error))}:T[e]=t}let E=b!=null,ae=E&&b?._skipOnMount===!0,A=F(y,C),j=[],M=new AbortController;if(j.push(()=>M.abort()),_?.enter){let e=_.enter(l);e instanceof Promise&&e.catch(console.error)}let{proxy:P,setup:oe}=ge(f,A,y,T),I=()=>{};function L(e,t){let n=e instanceof Error?e:Error(String(e));if(g.length===0){console.error(n);return}for(let e of g)try{e.fn({error:n,source:t,state:A,derived:P,input:y,host:l})}catch(e){console.error(e)}}let R=new Map,z=new Map;function B(e,t){R.delete(e);let n=Symbol(),r=t.unmount(),i=t=>{let r=z.get(e);!r||r.token!==n||(r.el.remove(),t?.remove(),z.delete(e))};if(r instanceof Promise){let a;t.el.isConnected&&(a=document.createElement(t.el.tagName.toLowerCase()),a.setAttribute(D,e),t.el.replaceWith(a)),z.set(e,{el:t.el,token:n}),r.finally(()=>i(a))}else t.el.remove()}function se(e){let t=new Set([...z.values()].map(e=>e.el)),n=typeof CSS<`u`&&CSS.escape?CSS.escape(e):e.replace(/["\\]/g,`\\$&`),r=l.querySelectorAll(`[${D}="${n}"]`);for(let e of r){if(t.has(e))continue;let n=e;for(;n&&n!==l&&!n.hasAttribute(`data-ilha`);)n=n.parentElement;if(n===l)return e}return null}function V(e){for(let[t,n]of R)e.has(t)||B(t,n);for(let[t,{island:r,props:i}]of e){let e=R.get(t);if(e){e.updateProps(i);continue}let a=se(t);if(!a)continue;let s=i;if(s===void 0){let e=a.getAttribute(O)??a.getAttribute(`data-props`);if(e)try{s=JSON.parse(e)}catch{o(`Failed to parse props on [${D}="${t}"] — invalid JSON ignored.`)}}let c=n(void 0),l;try{let e=r[re];l=e?e(a,s):{unmount:r.mount(a,s),updateProps:()=>{}}}finally{n(c)}R.set(t,{el:a,...l})}}let H=[],U=new Set,W=new WeakMap;function G(){for(let e of p){if(e.options.once&&U.has(e))continue;let n=e.selector===``?[l]:Array.from(l.querySelectorAll(e.selector));a&&e.selector!==``&&n.length===0&&o(`on(): selector "${e.selector}" matched no elements at mount time. If the element is rendered later, this is expected — otherwise check your selector.`),n.forEach(n=>{let r=r=>{if(e.options.once){U.add(e);for(let t of H.filter(t=>t.entry===e))t.target.removeEventListener(t.type,t.fn,t.options);H.splice(0,H.length,...H.filter(t=>t.entry!==e))}let a=r.target instanceof Element?r.target:n,o=e.selector===``?a:n,s;if(e.abortable){let t=W.get(e);t||(t=new WeakMap,W.set(e,t));let r=t.get(n);r&&r.abort();let i=new AbortController;t.set(n,i),s=Se([M.signal,i.signal])}else s=M.signal;let c;i();try{c=e.handler({state:A,derived:P,input:y,host:l,target:o,event:r,signal:s})}catch(e){L(e,`on`),t();return}t(),c instanceof Promise&&c.catch(e=>{e&&e.name===`AbortError`||L(e,`on`)})},a={...e.options,once:!1};n.addEventListener(e.event,r,a),H.push({target:n,type:e.event,fn:r,options:a,entry:e})})}}function K(){for(let e of H)e.target.removeEventListener(e.type,e.fn,e.options);H.length=0}let q=E&&l.childNodes.length>0,J=N(()=>c({state:A,derived:P,input:y}),q?l:void 0);q||(l.innerHTML=S+J.html);let Y=be(l,J.binds);if(j.push(()=>Y()),V(J.slots),j.push(()=>R.forEach(e=>e.unmount())),G(),!ae)for(let e of h){let t=n(void 0),r;try{r=e.fn({state:A,derived:P,input:y,host:l,hydrated:E})}finally{n(t)}r&&j.push(r)}for(let n of te){let r,a=null,o=e(()=>{if(r){try{r()}catch(e){L(e,`effect`)}r=void 0}a&&a.abort(),a=new AbortController;let e=Se([M.signal,a.signal]);i();try{r=n.fn({state:A,derived:P,input:y,host:l,signal:e})}catch(e){L(e,`effect`)}finally{t()}});j.push(()=>{if(o(),r)try{r()}catch(e){L(e,`effect`)}a&&a.abort()})}I=oe(),j.push(I);let X=!1,Z=J.html,ce=e(()=>{let{html:e,slots:t,binds:n}=N(()=>c({state:A,derived:P,input:y}),l),r=S+e;if(!X&&(X=!0,e===Z||R.size>0&&R.size===t.size&&[...t.keys()].every(e=>R.has(e))&&m(Z,e,t.keys())))return;K(),Y();let i=R.size,a=[];for(let[e,n]of R)t.has(e)||(B(e,n),a.push(e));for(let e of a){if(z.has(e))continue;let t=typeof CSS<`u`&&CSS.escape?CSS.escape(e):e.replace(/["\\]/g,`\\$&`);for(let e of l.querySelectorAll(`[${D}="${t}"]`))e.remove()}let o=new Map,s=t.size<i;for(let[e,n]of R){if(!t.has(e)||!n.el.isConnected)continue;if(s&&e.startsWith(`p:`)){B(e,n);continue}let r=n.el.tagName.toLowerCase(),i=document.createElement(r);i.setAttribute(D,e);let a=ee(t.get(e)?.props);a!==``&&i.setAttribute(O,a),n.el.replaceWith(i),o.set(e,n.el)}let u=document.createElement(`template`),d=l.tagName.toLowerCase();u.innerHTML=`<${d}>${r}</${d}>`,ne(l,u.content.firstElementChild);for(let[e,t]of o){let n=se(e);n&&n.replaceWith(t)}G(),Y=be(l,n),V(t)}),Q=!1;return{unmount:()=>{if(Q)return;Q=!0,a&&$&&$.delete(l),ce(),K();let e=[];for(let[,t]of R){let n=t.unmount();n instanceof Promise&&e.push(n)}if(_?.leave){let t=_.leave(l);t instanceof Promise&&e.push(t)}let t=()=>{for(let e of j)e()};if(e.length>0)return Promise.all(e).then(t).catch(e=>{console.error(e),t()});t()},updateProps:e=>{if(Q)return;let t=k(e);s(v(),t)||v(t)}}}let B=function(e){return M()?{[T]:!0,island:B,props:e,key:void 0}:L(e)};return B.toString=e=>L(e,!0),B.mount=(e,t)=>R(e,t),B[re]=(e,t)=>z(e,t),B[E]=C,B.key=e=>{if(typeof e!=`string`||e.trim().length===0)throw Error(`island.key() requires a non-empty string.`);if(e.includes(`:`))throw Error(`island.key() key cannot contain the slot separator ":" (got "${e}").`);let t=(t=>({[T]:!0,island:B,props:t,key:e}));return t[T]=!0,t},B[w]=!0,B.hydratable=async(e,t)=>{let{name:r,as:i=`div`,snapshot:a=!1,skipOnMount:o}=t,s=d(i),c=e??{},l=await L(c),p=b(JSON.stringify(c)),m=``;if(a!==!1){let e=a===!0||a.state!==!1,t=a===!0||a.derived!==!1,r=o??(e||t),i={},s=k(c),l=P(s);if(e)for(let e of u)i[e.key]=l[e.key]();if(t){let e={};for(let t of f){let r=n(void 0),i,a,o=!1;try{i=t.fn({state:l,input:s,signal:new AbortController().signal})}catch(e){o=!0,a=e}finally{n(r)}if(o){e[t.key]={loading:!1,value:void 0,error:a instanceof Error?a.message:String(a)};continue}try{let n=await Promise.resolve(i);e[t.key]={loading:!1,value:n,error:void 0}}catch(n){e[t.key]={loading:!1,value:void 0,error:n instanceof Error?n.message:String(n)}}}i._derived=e}r&&(i._skipOnMount=!0),m=` ${ie}='${b(JSON.stringify(i))}'`}return`<${s} data-ilha="${b(r)}" ${O}='${p}'${m}>${l}</${s}>`},B}};function we(e,t,n){let r=typeof e==`string`?document.querySelector(e):e;return r?t.mount(r,n):(console.warn(`[ilha] from(): element not found: ${e}`),null)}function Te(e,t={}){let n=t.root??document.body,r=t.lazy??!1,i=[];function a(t){let n=t.getAttribute(`data-ilha`);if(!n)return;let r=e[n];if(!r){o(`mount(): no island registered under the name "${n}". Available names: [${Object.keys(e).join(`, `)}]. Check the data-ilha attribute on the element.`);return}let a={},s=t.getAttribute(O);if(s)try{a=JSON.parse(s)}catch{o(`Failed to parse ${O} on [data-ilha="${n}"] — invalid JSON ignored.`)}i.push(r.mount(t,a))}let s=Array.from(n.querySelectorAll(`[data-ilha]`));if(r&&typeof IntersectionObserver<`u`){let e=!1,t=new IntersectionObserver(n=>{if(!e)for(let e of n)e.isIntersecting&&(a(e.target),t.unobserve(e.target))});s.forEach(e=>t.observe(e)),i.push(()=>{e=!0,t.disconnect()})}else s.forEach(a);return{unmount:()=>{let e=[];for(let t of i){let n=t();n instanceof Promise&&e.push(n)}if(e.length>0)return Promise.all(e).then(()=>{})}}}const Ee=new Ce({schema:null,states:[],deriveds:[],ons:[],effects:[],onMounts:[],onErrors:[],transition:null,css:null,as:null}),De=Object.assign(Ee,{html:X,raw:H,mount:Te,from:we,context:le,signal:ue,batch:fe,untrack:de});function Oe(e,t,n){return H(F(e,t,n))}const ke=X,Ae=H,je=U,Me=Te,Ne=we,Pe=le;export{re as ISLAND_MOUNT_INTERNAL,Oe as __ilhaJsxSlot,fe as batch,Pe as context,je as css,De as default,Ne as from,ke as html,ue as ilhaSignal,ue as signal,Me as mount,Ae as raw,de as untrack};
|