ilha 0.11.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +181 -787
- package/dist/index-BETRqz-0.d.ts +377 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1663 -1731
- package/dist/internal.d.ts +69 -0
- package/dist/internal.js +49 -0
- package/dist/jsx-dev-runtime.d.ts +1 -1
- package/dist/{jsx-runtime-DMkplizR.d.ts → jsx-runtime-qnxoD2YJ.d.ts} +1 -3
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +11 -28
- package/package.json +7 -2
- package/dist/index-DnoM0M8S.d.ts +0 -452
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ISLAND_MOUNT_HANDLES, ISLAND_MOUNT_INTERNAL, serializeServerManifest, setJsxRuntimeBridge, setServerActionBinder } from "./internal.js";
|
|
2
|
+
import { effect as effect$1, endBatch, setActiveSub, signal, startBatch } from "alien-signals";
|
|
2
3
|
|
|
3
4
|
//#region src/index.ts
|
|
5
|
+
const __DEV__ = false;
|
|
4
6
|
const MAX_SNAPSHOT_CHARS = 262144;
|
|
5
7
|
const MAX_SNAPSHOT_DEPTH = 32;
|
|
6
8
|
function exceedsMaxDepth(value, depth) {
|
|
@@ -94,8 +96,8 @@ function extractSlotPropsAttr(root, slotId) {
|
|
|
94
96
|
const SLOT_TAG_NAME_RE = /^[a-z][a-z0-9-]*$/i;
|
|
95
97
|
function assertValidSlotTagName(tag) {
|
|
96
98
|
const trimmed = tag.trim();
|
|
97
|
-
if (trimmed.length === 0) throw new Error("
|
|
98
|
-
if (!SLOT_TAG_NAME_RE.test(trimmed)) throw new Error(`
|
|
99
|
+
if (trimmed.length === 0) throw new Error("The { as } option requires a non-empty HTML tag name.");
|
|
100
|
+
if (!SLOT_TAG_NAME_RE.test(trimmed)) throw new Error(`The { as } option tag must be a valid HTML element name (got "${tag}"). Use names like "span", "div", or "li".`);
|
|
99
101
|
return trimmed.toLowerCase();
|
|
100
102
|
}
|
|
101
103
|
function getIslandSlotTag(island) {
|
|
@@ -134,11 +136,18 @@ function slotPropsForAttr(props, captureActions = false) {
|
|
|
134
136
|
if (key === "children") continue;
|
|
135
137
|
const value = props[key];
|
|
136
138
|
if (typeof value === "function") {
|
|
137
|
-
const
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
const id = captureActions && !isRawHtmlValue(value) ? actionManifestId(value) : void 0;
|
|
140
|
+
if (id) {
|
|
141
|
+
const a = actionBoundArgs(value);
|
|
142
|
+
(out ??= {})[key] = Array.isArray(a) ? {
|
|
143
|
+
__ilha: "action",
|
|
144
|
+
k: id,
|
|
145
|
+
a
|
|
146
|
+
} : {
|
|
147
|
+
__ilha: "action",
|
|
148
|
+
k: id
|
|
149
|
+
};
|
|
150
|
+
}
|
|
142
151
|
continue;
|
|
143
152
|
}
|
|
144
153
|
if (typeof value === "symbol") continue;
|
|
@@ -188,32 +197,10 @@ function reviveSlotPropValue(value) {
|
|
|
188
197
|
for (const key of Object.keys(obj)) out[key] = reviveSlotPropValue(obj[key]);
|
|
189
198
|
return out;
|
|
190
199
|
}
|
|
191
|
-
/**
|
|
192
|
-
* Exact legacy shape produced when RawHtml lost its brand under JSON.stringify:
|
|
193
|
-
* a plain object with a single own key `value` that is a string.
|
|
194
|
-
*/
|
|
195
|
-
function isLegacyRawHtmlBlob(value) {
|
|
196
|
-
if (!value || typeof value !== "object" || isRawHtmlValue(value)) return false;
|
|
197
|
-
if (Object.getPrototypeOf(value) !== Object.prototype) return false;
|
|
198
|
-
const keys = Object.keys(value);
|
|
199
|
-
return keys.length === 1 && keys[0] === "value" && typeof value.value === "string";
|
|
200
|
-
}
|
|
201
|
-
/** Revive attr-parsed props (RawHtml tags). Children stay as plain data if present. */
|
|
200
|
+
/** Revive attr-parsed props, restoring tagged `__ilha:raw` RawHtml values. */
|
|
202
201
|
function reviveSlotProps(props) {
|
|
203
202
|
if (typeof props !== "object" || props === null || Array.isArray(props)) return {};
|
|
204
|
-
|
|
205
|
-
if ("children" in out) out.children = reviveLegacyChildren(out.children);
|
|
206
|
-
return out;
|
|
207
|
-
}
|
|
208
|
-
function reviveLegacyChildren(children) {
|
|
209
|
-
if (!Array.isArray(children)) {
|
|
210
|
-
if (isLegacyRawHtmlBlob(children)) return makeRawHtml(children.value);
|
|
211
|
-
return reviveSlotPropValue(children);
|
|
212
|
-
}
|
|
213
|
-
return children.map((child) => {
|
|
214
|
-
if (isLegacyRawHtmlBlob(child)) return makeRawHtml(child.value);
|
|
215
|
-
return reviveSlotPropValue(child);
|
|
216
|
-
});
|
|
203
|
+
return reviveSlotPropValue(props);
|
|
217
204
|
}
|
|
218
205
|
/** Serialized form of slot props, matching the decoded `data-ilha-props` attr. */
|
|
219
206
|
function serializeSlotProps(props) {
|
|
@@ -328,7 +315,7 @@ function morphChildren(fromParent, toParent) {
|
|
|
328
315
|
fromParent.replaceChild(toEl.cloneNode(true), fromEl);
|
|
329
316
|
continue;
|
|
330
317
|
}
|
|
331
|
-
if (fromEl.hasAttribute(
|
|
318
|
+
if (fromEl.hasAttribute(SLOT_ATTR) && !fromEl.isConnected) {
|
|
332
319
|
fromParent.replaceChild(toEl.cloneNode(true), fromEl);
|
|
333
320
|
continue;
|
|
334
321
|
}
|
|
@@ -460,6 +447,70 @@ const ESC = {
|
|
|
460
447
|
function escapeHtml(value) {
|
|
461
448
|
return String(value).replace(/[&<>"']/g, (c) => ESC[c]);
|
|
462
449
|
}
|
|
450
|
+
const SAFE_CSS_PROP_RE = /^(-{2}[a-zA-Z][a-zA-Z0-9-]*|-?[a-zA-Z][a-zA-Z0-9-]*)$/;
|
|
451
|
+
const URL_ATTRS = /* @__PURE__ */ new Set([
|
|
452
|
+
"href",
|
|
453
|
+
"src",
|
|
454
|
+
"srcset",
|
|
455
|
+
"imagesrcset",
|
|
456
|
+
"action",
|
|
457
|
+
"formaction",
|
|
458
|
+
"cite",
|
|
459
|
+
"data",
|
|
460
|
+
"poster"
|
|
461
|
+
]);
|
|
462
|
+
const SAFE_DATA_IMAGE_RE = /^data:image\/(png|jpe?g|gif|webp|avif)[;,]/i;
|
|
463
|
+
const UNSAFE_SCHEME_RE = /^(?:javascript|vbscript):/i;
|
|
464
|
+
const DATA_IMAGE_CONTEXTS = /* @__PURE__ */ new Set([
|
|
465
|
+
"img:src",
|
|
466
|
+
"source:srcset",
|
|
467
|
+
"link:imagesrcset"
|
|
468
|
+
]);
|
|
469
|
+
function normalizeUrl(value) {
|
|
470
|
+
return value.replace(/[\u0000-\u0020]/g, "");
|
|
471
|
+
}
|
|
472
|
+
function checkUrl(normalized, allowDataImage) {
|
|
473
|
+
if (UNSAFE_SCHEME_RE.test(normalized)) return false;
|
|
474
|
+
if (/^data:/i.test(normalized)) return allowDataImage && SAFE_DATA_IMAGE_RE.test(normalized);
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
/** Strict single-URL check — all data: URLs rejected. */
|
|
478
|
+
function isSafeUrl(value) {
|
|
479
|
+
return checkUrl(normalizeUrl(value), false);
|
|
480
|
+
}
|
|
481
|
+
/** Element- and attribute-aware URL policy for rendered attributes.
|
|
482
|
+
* `srcset`/`imagesrcset` validate each comma-separated candidate URL. */
|
|
483
|
+
function isSafeUrlAttrValue(tagName, attrName, value) {
|
|
484
|
+
const attr = attrName.toLowerCase();
|
|
485
|
+
const allowDataImage = DATA_IMAGE_CONTEXTS.has(`${tagName.toLowerCase()}:${attr}`) && !attr.startsWith("srcset") && attr !== "imagesrcset";
|
|
486
|
+
if (/^(?:srcset|imagesrcset)$/i.test(attr)) return value.split(",").every((candidate) => {
|
|
487
|
+
const url = candidate.trim().split(/\s+/)[0] ?? "";
|
|
488
|
+
return url === "" || checkUrl(normalizeUrl(url), false);
|
|
489
|
+
});
|
|
490
|
+
return checkUrl(normalizeUrl(value), allowDataImage);
|
|
491
|
+
}
|
|
492
|
+
function isUrlAttributeName(name) {
|
|
493
|
+
const lower = name.toLowerCase();
|
|
494
|
+
return URL_ATTRS.has(lower) || /:(href|src|srcset|imagesrcset|action|formaction|cite|data|poster)$/.test(lower);
|
|
495
|
+
}
|
|
496
|
+
function isPlainObject(v) {
|
|
497
|
+
return v !== null && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype;
|
|
498
|
+
}
|
|
499
|
+
function serializeStyle(value) {
|
|
500
|
+
return Object.entries(value).map(([k, v]) => {
|
|
501
|
+
if (!SAFE_CSS_PROP_RE.test(k)) return "";
|
|
502
|
+
const prop = k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
503
|
+
const str = String(v);
|
|
504
|
+
if (/[<>{};]/.test(str) || /expression\(/i.test(str) || /javascript:/i.test(str)) return "";
|
|
505
|
+
return `${prop}:${str}`;
|
|
506
|
+
}).filter(Boolean).join(";");
|
|
507
|
+
}
|
|
508
|
+
function serializeScriptJson(value) {
|
|
509
|
+
return JSON.stringify(value).replace(/</g, "\\u003C");
|
|
510
|
+
}
|
|
511
|
+
function escapeStyleText(text) {
|
|
512
|
+
return text.replace(/</g, "\\3C ");
|
|
513
|
+
}
|
|
463
514
|
function dedentString(str) {
|
|
464
515
|
if (str.length === 0 || str[0] !== "\n") return str;
|
|
465
516
|
const lines = str.split("\n");
|
|
@@ -474,17 +525,8 @@ const SIGNAL_ACCESSOR = Symbol.for("ilha.signalAccessor");
|
|
|
474
525
|
const ISLAND = Symbol.for("ilha.island");
|
|
475
526
|
const ISLAND_CALL = Symbol.for("ilha.islandCall");
|
|
476
527
|
const ASTRO_RENDERER_GLOBAL = Symbol.for("ilha.astroRenderer");
|
|
477
|
-
/** @internal Internal hook used by a parent's mountSlots to mount a child island and
|
|
478
|
-
* retain a handle to push updated props into it on subsequent parent
|
|
479
|
-
* re-renders. Not part of the public surface. */
|
|
480
|
-
const ISLAND_MOUNT_INTERNAL = Symbol.for("ilha.islandMountInternal");
|
|
481
528
|
const ISLAND_SLOT_TAG = Symbol.for("ilha.islandSlotTag");
|
|
482
529
|
const ISLAND_CLIENT_REF = Symbol.for("ilha.clientRef");
|
|
483
|
-
/** @internal Live mount handles keyed by host element. Lets @ilha/router adopt
|
|
484
|
-
* islands hydrated by `ilha.mount()` (whose handles it never saw) and push new
|
|
485
|
-
* loader props into them in place instead of remounting. Entries are removed
|
|
486
|
-
* on unmount. Not part of the public surface. */
|
|
487
|
-
const ISLAND_MOUNT_HANDLES = /* @__PURE__ */ new WeakMap();
|
|
488
530
|
/** @internal Sync re-render of an island from a state snapshot. Attached by
|
|
489
531
|
* render(); used by server-frame endpoints to produce HTML for streamed
|
|
490
532
|
* updates without a live mount. Not part of the public surface. */
|
|
@@ -493,61 +535,148 @@ const ISLAND_RENDER_STATE = Symbol.for("ilha.renderState");
|
|
|
493
535
|
* provided map. Used by emitIslandSlot so nested server islands ship their
|
|
494
536
|
* interactive surface alongside their inlined HTML. */
|
|
495
537
|
const ISLAND_SSR_MANIFEST = Symbol.for("ilha.ssrManifest");
|
|
496
|
-
function manifestTemplatePrefix(manifest) {
|
|
497
|
-
if (manifest.size === 0) return "";
|
|
498
|
-
const json = escapeHtml(JSON.stringify(Object.fromEntries(manifest)));
|
|
499
|
-
return `<template ${ACTIONS_ATTR}='${json}'></template>`;
|
|
500
|
-
}
|
|
501
538
|
const SLOT_ATTR = "data-ilha-slot";
|
|
502
|
-
/** Marks a slot host whose child island is mid leave-transition: it has been
|
|
503
|
-
* unmounted (and its slot id stripped) but stays connected so the leave
|
|
504
|
-
* animation can paint. The morph replaces such elements instead of matching
|
|
505
|
-
* or mutating them. */
|
|
506
|
-
const LEAVING_ATTR = "data-ilha-leaving";
|
|
507
539
|
const PROPS_ATTR = "data-ilha-props";
|
|
508
540
|
const CLIENT_REF_ATTR = "data-ilha-client-ref";
|
|
509
541
|
const STATE_ATTR = "data-ilha-state";
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
542
|
+
function normalizeElseOutput(value) {
|
|
543
|
+
if (value == null || value === false) return value;
|
|
544
|
+
return Array.isArray(value) ? value : [value];
|
|
545
|
+
}
|
|
546
|
+
function createEachResult(items, mapFn) {
|
|
547
|
+
const mapped = items.length === 0 ? [] : items.map(mapFn);
|
|
548
|
+
const result = mapped;
|
|
549
|
+
Object.defineProperty(result, "else", {
|
|
550
|
+
value(fallback) {
|
|
551
|
+
if (items.length === 0) return normalizeElseOutput(typeof fallback === "function" ? fallback(items) : fallback);
|
|
552
|
+
return mapped;
|
|
553
|
+
},
|
|
554
|
+
enumerable: false
|
|
555
|
+
});
|
|
556
|
+
return result;
|
|
557
|
+
}
|
|
558
|
+
function createEachBuilder(items) {
|
|
559
|
+
return {
|
|
560
|
+
key(keyFn) {
|
|
561
|
+
return { as(mapFn) {
|
|
562
|
+
return createEachResult(items, (item, index) => mapFn(item, index, keyFn(item, index)));
|
|
563
|
+
} };
|
|
564
|
+
},
|
|
565
|
+
as(mapFn) {
|
|
566
|
+
return createEachResult(items, mapFn);
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
}
|
|
524
570
|
/**
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
571
|
+
* Svelte-style `{#each}` helper for mapping collections to rendered output
|
|
572
|
+
* with an optional empty fallback.
|
|
573
|
+
*
|
|
574
|
+
* @example — plain list (empty → [])
|
|
575
|
+
* each(items).as((item) => html`<li>${item.name}</li>`)
|
|
528
576
|
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
577
|
+
* @example — keyed list feeding island.key(), with empty fallback
|
|
578
|
+
* each(items)
|
|
579
|
+
* .key((item) => item.id)
|
|
580
|
+
* .as((item, _i, id) => Row.key(id)({ item }))
|
|
581
|
+
* .else(() => html`<EmptyState />`)
|
|
534
582
|
*/
|
|
535
|
-
function
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
583
|
+
function each(items) {
|
|
584
|
+
if (typeof items === "function") throw new TypeError("[ilha] each() expected an array but received a function. Call accessors first (each(state.items())) or pass a snapshot from a reactive render.");
|
|
585
|
+
return createEachBuilder(Array.isArray(items) ? items : []);
|
|
586
|
+
}
|
|
587
|
+
/** Marks SSR action stubs so event registration can recognize action
|
|
588
|
+
* references without ever invoking them (executing a handler body during
|
|
589
|
+
* render would run its side effects server-side). Symbol.for brands keep
|
|
590
|
+
* this recognizable across duplicate ilha copies and external action
|
|
591
|
+
* implementations (e.g. oxidejs) in the same realm. */
|
|
592
|
+
const SSR_ACTION_STUB = Symbol.for("ilha.ssrActionStub");
|
|
593
|
+
const ACTION_MANIFEST_ID = Symbol.for("ilha.actionManifestId");
|
|
594
|
+
const ACTION_BOUND_ARGS = Symbol.for("ilha.actionBoundArgs");
|
|
595
|
+
/** Serialized size ceiling for `.with()` payloads (JSON chars). */
|
|
596
|
+
const MAX_BOUND_ARGS_CHARS = 8192;
|
|
597
|
+
/** Manifest id for a brand-recognized action reference, else undefined.
|
|
598
|
+
* Anything that isn't a branded action stub is rejected outright — event
|
|
599
|
+
* handlers are never executed during SSR to discover what they call. */
|
|
600
|
+
function actionManifestId(fn) {
|
|
601
|
+
if (typeof fn !== "function" || !(SSR_ACTION_STUB in fn)) return void 0;
|
|
602
|
+
return fn[ACTION_MANIFEST_ID];
|
|
603
|
+
}
|
|
604
|
+
/** Bound args recorded by `.with()` on an action reference, else undefined. */
|
|
605
|
+
function actionBoundArgs(fn) {
|
|
606
|
+
if (typeof fn !== "function" || !(ACTION_BOUND_ARGS in fn)) return void 0;
|
|
607
|
+
const args = fn[ACTION_BOUND_ARGS];
|
|
608
|
+
return Array.isArray(args) ? args : void 0;
|
|
609
|
+
}
|
|
610
|
+
/** Attach `.with(...args)` to an action accessor. Returns an explicit,
|
|
611
|
+
* serializable action REFERENCE — never a closure to inspect or probe.
|
|
612
|
+
* Arguments are validated JSON-safe at bind time; the hydration manifest
|
|
613
|
+
* stores `{ actionId, args }` directly and the client RPC path sends them
|
|
614
|
+
* only after a real user event. On the client, calling the bound reference
|
|
615
|
+
* invokes the accessor with the bound payload. */
|
|
616
|
+
/** Keys that must never appear in a bound payload — they become dangerous
|
|
617
|
+
* if an RPC handler or downstream library merges the object into another. */
|
|
618
|
+
const UNSAFE_KEYS = /* @__PURE__ */ new Set([
|
|
619
|
+
"__proto__",
|
|
620
|
+
"constructor",
|
|
621
|
+
"prototype"
|
|
622
|
+
]);
|
|
623
|
+
/** Strict JSON-safety validator: accept, or throw — never transform.
|
|
624
|
+
* Rejects undefined, functions, symbols, bigint, non-finite numbers,
|
|
625
|
+
* Dates/custom prototypes/Map/Set, circular values, and unsafe keys so a
|
|
626
|
+
* bound payload is byte-identical to what the author wrote. */
|
|
627
|
+
function assertJsonSafe(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
628
|
+
if (value === null) return;
|
|
629
|
+
switch (typeof value) {
|
|
630
|
+
case "string":
|
|
631
|
+
case "boolean": return;
|
|
632
|
+
case "number":
|
|
633
|
+
if (Number.isFinite(value)) return;
|
|
634
|
+
throw new TypeError("with() rejects non-finite numbers.");
|
|
635
|
+
case "object": break;
|
|
636
|
+
default: throw new TypeError(`with() rejects ${typeof value} values.`);
|
|
637
|
+
}
|
|
638
|
+
if (seen.has(value)) throw new TypeError("with() rejects circular values.");
|
|
639
|
+
seen.add(value);
|
|
640
|
+
if (Array.isArray(value)) {
|
|
641
|
+
for (const item of value) assertJsonSafe(item, seen);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) throw new TypeError("with() accepts plain objects only.");
|
|
645
|
+
for (const [key, child] of Object.entries(value)) {
|
|
646
|
+
if (UNSAFE_KEYS.has(key)) throw new TypeError(`with() rejects unsafe key "${key}".`);
|
|
647
|
+
assertJsonSafe(child, seen);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
/** Deep-clone an already-validated JSON-safe value. */
|
|
651
|
+
function cloneJsonSafe(value) {
|
|
652
|
+
return structuredClone(value);
|
|
653
|
+
}
|
|
654
|
+
function attachWith(accessor) {
|
|
655
|
+
Object.defineProperty(accessor, "with", {
|
|
656
|
+
enumerable: false,
|
|
657
|
+
value: (...args) => {
|
|
658
|
+
for (const arg of args) assertJsonSafe(arg);
|
|
659
|
+
const serialized = cloneJsonSafe(args);
|
|
660
|
+
if (JSON.stringify(serialized).length > MAX_BOUND_ARGS_CHARS) throw new RangeError(`with() payload exceeds ${MAX_BOUND_ARGS_CHARS} JSON characters.`);
|
|
661
|
+
const manifestId = actionManifestId(accessor);
|
|
662
|
+
const bound = (...runtimeArgs) => {
|
|
663
|
+
return accessor(serialized.length > 0 ? serialized.length === 1 ? serialized[0] : serialized : runtimeArgs.length === 1 ? runtimeArgs[0] : runtimeArgs);
|
|
664
|
+
};
|
|
665
|
+
const brand = bound;
|
|
666
|
+
brand[SSR_ACTION_STUB] = true;
|
|
667
|
+
brand[ACTION_MANIFEST_ID] = manifestId;
|
|
668
|
+
brand[ACTION_BOUND_ARGS] = serialized;
|
|
669
|
+
return bound;
|
|
670
|
+
}
|
|
671
|
+
});
|
|
550
672
|
}
|
|
673
|
+
setServerActionBinder(function bindExternalAction(fn, manifestId) {
|
|
674
|
+
const action = fn;
|
|
675
|
+
action[SSR_ACTION_STUB] = true;
|
|
676
|
+
action[ACTION_MANIFEST_ID] = manifestId;
|
|
677
|
+
attachWith(action);
|
|
678
|
+
return action;
|
|
679
|
+
});
|
|
551
680
|
const RENDER_CTX_STACK = Symbol.for("ilha.renderCtxStack");
|
|
552
681
|
function renderCtxStack() {
|
|
553
682
|
const g = globalThis;
|
|
@@ -605,8 +734,8 @@ function emitIslandSlot({ island, props, key }) {
|
|
|
605
734
|
try {
|
|
606
735
|
const manifest = /* @__PURE__ */ new Map();
|
|
607
736
|
const ssrWithManifest = island[ISLAND_SSR_MANIFEST];
|
|
608
|
-
const result = ssrWithManifest ? ssrWithManifest(props, manifest) : island(props);
|
|
609
|
-
const prefix = () =>
|
|
737
|
+
const result = ssrWithManifest ? ssrWithManifest(props, manifest) : island.toStringAsync(props);
|
|
738
|
+
const prefix = () => serializeServerManifest(manifest);
|
|
610
739
|
if (result instanceof Promise) {
|
|
611
740
|
ctx.pending.set(id, result.then(String).then((html) => prefix() + html));
|
|
612
741
|
return wrapIslandSlotHtml({
|
|
@@ -656,7 +785,7 @@ function resolveSignalSetter(read, value) {
|
|
|
656
785
|
function markSignalAccessor(fn) {
|
|
657
786
|
fn[SIGNAL_ACCESSOR] = true;
|
|
658
787
|
const accessor = fn;
|
|
659
|
-
accessor.select = (
|
|
788
|
+
accessor.select = ((...args) => createSelectAccessor(accessor, args));
|
|
660
789
|
return accessor;
|
|
661
790
|
}
|
|
662
791
|
function isSignalAccessor(v) {
|
|
@@ -731,22 +860,27 @@ function trackSelectPath(rootState, selector) {
|
|
|
731
860
|
selector(track(rootState));
|
|
732
861
|
return path;
|
|
733
862
|
}
|
|
734
|
-
function createSelectAccessor(root,
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
863
|
+
function createSelectAccessor(root, input) {
|
|
864
|
+
const selector = typeof input[0] === "function" ? input[0] : void 0;
|
|
865
|
+
let path;
|
|
866
|
+
if (selector) {
|
|
867
|
+
path = trackSelectPath(root(), selector);
|
|
868
|
+
const selected = selector(root());
|
|
869
|
+
const resolved = path.length === 0 ? root() : getAtPath(root(), path);
|
|
870
|
+
if (!Object.is(selected, resolved)) throw new Error("select(): selector must only traverse nested properties or array indexes — derived or transformed values are not supported.");
|
|
871
|
+
} else path = input;
|
|
872
|
+
const read = (from) => selector ? path.length === 0 ? selector(from) : getAtPath(from, path) : getAtPath(from, path);
|
|
739
873
|
return markSignalAccessor((...args) => {
|
|
740
|
-
if (args.length === 0) return
|
|
874
|
+
if (args.length === 0) return read(root());
|
|
741
875
|
const previousRoot = root();
|
|
742
|
-
const previousSelected =
|
|
876
|
+
const previousSelected = read(previousRoot);
|
|
743
877
|
const value = resolveSignalSetter(() => previousSelected, args[0]);
|
|
744
|
-
const next = path.length === 0 ? value : setAtPath({
|
|
878
|
+
const next = path.length === 0 && selector ? value : setAtPath({
|
|
745
879
|
object: previousRoot,
|
|
746
880
|
path,
|
|
747
881
|
value
|
|
748
882
|
});
|
|
749
|
-
if (!Object.is(previousRoot, next)) root((
|
|
883
|
+
if (!Object.is(previousRoot, next)) root(() => next);
|
|
750
884
|
});
|
|
751
885
|
}
|
|
752
886
|
function ilhaRaw(value) {
|
|
@@ -755,14 +889,37 @@ function ilhaRaw(value) {
|
|
|
755
889
|
value
|
|
756
890
|
};
|
|
757
891
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
892
|
+
/**
|
|
893
|
+
* Serialize a JSON-safe value into executable `<script>` content safely. `<`
|
|
894
|
+
* is escaped as `\u003C`, so a value can never close the script block or open
|
|
895
|
+
* the `<!--` escape hatch. Use it for script-element data:
|
|
896
|
+
*
|
|
897
|
+
* ```ts
|
|
898
|
+
* html`<script>const d = ${json(payload)};<\/script>`
|
|
899
|
+
* ```
|
|
900
|
+
*
|
|
901
|
+
* Like `raw()`, `json()` is for element *content* only — interpolating it into
|
|
902
|
+
* an attribute is author error. It does not protect against values that are
|
|
903
|
+
* not valid JSON (functions, symbols, undefined, cyclic objects are dropped
|
|
904
|
+
* or throw, matching `JSON.stringify`).
|
|
905
|
+
*/
|
|
906
|
+
function json(value) {
|
|
907
|
+
return ilhaRaw(serializeScriptJson(value));
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Escape text for a `<style>` element body. `<` becomes the CSS hex escape
|
|
911
|
+
* `\3C `, so the value can never close the style block or open the `<!--`
|
|
912
|
+
* escape hatch while surviving CSS parsing:
|
|
913
|
+
*
|
|
914
|
+
* ```ts
|
|
915
|
+
* html`<style>${css(cssSource)}</style>`
|
|
916
|
+
* ```
|
|
917
|
+
*
|
|
918
|
+
* Like `raw()`, `css()` is for element *content* only — interpolating it into
|
|
919
|
+
* an attribute is author error.
|
|
920
|
+
*/
|
|
921
|
+
function css(value) {
|
|
922
|
+
return ilhaRaw(escapeStyleText(value));
|
|
766
923
|
}
|
|
767
924
|
function interpolateValue(v) {
|
|
768
925
|
if (v == null || v === true || v === false) return "";
|
|
@@ -788,21 +945,31 @@ function interpolateValue(v) {
|
|
|
788
945
|
const BIND_VALID_KINDS = /* @__PURE__ */ new Set([
|
|
789
946
|
"value",
|
|
790
947
|
"checked",
|
|
791
|
-
"valueAsNumber",
|
|
792
|
-
"valueAsDate",
|
|
793
948
|
"files",
|
|
794
949
|
"open",
|
|
795
950
|
"group",
|
|
796
951
|
"this"
|
|
797
952
|
]);
|
|
798
953
|
const BIND_PREFIX_RE = /\bbind:([a-zA-Z]+)\s*=\s*("|')?$/;
|
|
799
|
-
const EVENT_PREFIX_RE = /(?:^|\s)on([a-
|
|
954
|
+
const EVENT_PREFIX_RE = /(?:^|\s)on([a-zA-Z][a-zA-Z0-9-]*)(?::([a-zA-Z][a-zA-Z0-9-]*))?\s*=\s*("|')?$/;
|
|
800
955
|
const NATIVE_EVENT_MODIFIERS = /* @__PURE__ */ new Set([
|
|
801
956
|
"abortable",
|
|
802
957
|
"once",
|
|
803
958
|
"capture",
|
|
804
959
|
"passive"
|
|
805
960
|
]);
|
|
961
|
+
const FULL_VALUE_ATTR_RE = /(?:^|\s)([a-zA-Z_:][a-zA-Z0-9:._-]*)\s*=\s*("|')?$/;
|
|
962
|
+
const UNQUOTED_HAZARD_RE = /[\s=]|`/;
|
|
963
|
+
function resolveTemplateValue(v) {
|
|
964
|
+
if (isSignalAccessor(v)) return v();
|
|
965
|
+
if (typeof v === "function") return v();
|
|
966
|
+
return v;
|
|
967
|
+
}
|
|
968
|
+
function openTagName(source, index) {
|
|
969
|
+
const open = source.lastIndexOf("<", index);
|
|
970
|
+
if (open === -1) return "element";
|
|
971
|
+
return (source.slice(open).match(/^<\s*([a-zA-Z][a-zA-Z0-9:._-]*)/)?.[1] ?? "element").toLowerCase();
|
|
972
|
+
}
|
|
806
973
|
function isEventAttributePosition(source, index) {
|
|
807
974
|
let open = -1;
|
|
808
975
|
let quote = null;
|
|
@@ -837,14 +1004,6 @@ function findTagCloseIndex(chunk, initialQuote) {
|
|
|
837
1004
|
quote
|
|
838
1005
|
};
|
|
839
1006
|
}
|
|
840
|
-
function formatDateForInput(d) {
|
|
841
|
-
if (d instanceof Date) {
|
|
842
|
-
if (isNaN(d.getTime())) return "";
|
|
843
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
844
|
-
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
845
|
-
}
|
|
846
|
-
return "";
|
|
847
|
-
}
|
|
848
1007
|
function extractElementStaticValue(prefix) {
|
|
849
1008
|
const lastOpen = prefix.lastIndexOf("<");
|
|
850
1009
|
if (lastOpen === -1) return null;
|
|
@@ -864,8 +1023,6 @@ function emitBindSSR({ kind, index, accessor, prefixForStaticPeek }) {
|
|
|
864
1023
|
}
|
|
865
1024
|
switch (kind) {
|
|
866
1025
|
case "value": return [` value="${escapeHtml(v ?? "")}"`, spec];
|
|
867
|
-
case "valueAsNumber": return [` value="${escapeHtml(v == null ? "" : String(v))}"`, spec];
|
|
868
|
-
case "valueAsDate": return [` value="${escapeHtml(formatDateForInput(v))}"`, spec];
|
|
869
1026
|
case "checked": return [v ? ` checked` : ``, spec];
|
|
870
1027
|
case "open": return [v ? ` open` : ``, spec];
|
|
871
1028
|
case "files": return [``, spec];
|
|
@@ -910,24 +1067,29 @@ function ilhaHtml(strings, ...values) {
|
|
|
910
1067
|
const value = values[i];
|
|
911
1068
|
const eventMatch = chunk.match(EVENT_PREFIX_RE);
|
|
912
1069
|
const eventMatchStart = eventMatch ? chunk.length - eventMatch[0].length : -1;
|
|
1070
|
+
if (!eventMatch && typeof value === "function") {
|
|
1071
|
+
const ambiguous = chunk.match(/(?:^|\s)on([a-zA-Z][a-zA-Z0-9-]*)(?::[a-zA-Z][a-zA-Z0-9-]*)?\s*=\s*(?:(?:"[^"]*)|(?:'[^']*)|(?:[^\s"'=<>`]+))$/);
|
|
1072
|
+
const start = ambiguous ? chunk.length - ambiguous[0].length : -1;
|
|
1073
|
+
if (ambiguous && isEventAttributePosition(result + chunk, result.length + start)) throw new Error(`[ilha] on${ambiguous[1]} handler must occupy the entire attribute value. Write on${ambiguous[1]}=\${handler}.`);
|
|
1074
|
+
}
|
|
913
1075
|
const eventAttributeFragment = eventMatch !== null && chunk.slice(0, eventMatchStart).trim() === "";
|
|
914
1076
|
if (eventMatch && (eventAttributeFragment || isEventAttributePosition(result + chunk, result.length + eventMatchStart))) {
|
|
915
|
-
const eventName = eventMatch[1];
|
|
916
|
-
const rawModifier = eventMatch[2];
|
|
917
|
-
const modifier = NATIVE_EVENT_MODIFIERS.has(rawModifier) ? rawModifier : void 0;
|
|
1077
|
+
const eventName = eventMatch[1].toLowerCase();
|
|
1078
|
+
const rawModifier = eventMatch[2]?.toLowerCase();
|
|
1079
|
+
const modifier = rawModifier !== void 0 && NATIVE_EVENT_MODIFIERS.has(rawModifier) ? rawModifier : void 0;
|
|
918
1080
|
const openQuote = eventMatch[3] ?? null;
|
|
1081
|
+
const nextChunk = strings[i + 1] ?? "";
|
|
1082
|
+
if (!(openQuote ? nextChunk.startsWith(openQuote) : /^(?:\s|\/?>|$)/.test(nextChunk))) throw new Error(`[ilha] on${eventName} handler must occupy the entire attribute value. Write on${eventName}=\${handler}.`);
|
|
919
1083
|
const cleanPrefix = chunk.slice(0, eventMatchStart).replace(/\s+$/, "");
|
|
920
|
-
const isActionStub = typeof value === "function" && SSR_ACTION_STUB in value;
|
|
921
|
-
const capture = ctx?.manifest && typeof value === "function" && !isActionStub ? captureHandlerActions(value) : void 0;
|
|
922
1084
|
result += cleanPrefix;
|
|
1085
|
+
typeof value === "function" && SSR_ACTION_STUB in value;
|
|
923
1086
|
if (ctx && typeof value === "function") {
|
|
924
1087
|
const index = ctx.events.length;
|
|
925
1088
|
if (rawModifier && !modifier) {} else {
|
|
926
1089
|
ctx.events.push({
|
|
927
1090
|
type: eventName,
|
|
928
1091
|
handler: value,
|
|
929
|
-
modifier
|
|
930
|
-
capture
|
|
1092
|
+
modifier
|
|
931
1093
|
});
|
|
932
1094
|
pendingEventSpecs += (pendingEventSpecs ? "," : "") + `${eventName}:${index}`;
|
|
933
1095
|
}
|
|
@@ -968,6 +1130,72 @@ function ilhaHtml(strings, ...values) {
|
|
|
968
1130
|
if (openQuote) stripLeadingQuote = openQuote;
|
|
969
1131
|
continue;
|
|
970
1132
|
}
|
|
1133
|
+
const attrMatch = chunk.match(FULL_VALUE_ATTR_RE);
|
|
1134
|
+
if (attrMatch && isEventAttributePosition(result + chunk, result.length + attrMatch.index)) {
|
|
1135
|
+
const attrName = attrMatch[1];
|
|
1136
|
+
const openQuote = attrMatch[2] ?? null;
|
|
1137
|
+
if (openQuote) stripLeadingQuote = openQuote;
|
|
1138
|
+
const cleanPrefix = chunk.slice(0, attrMatch.index).replace(/\s+$/, "");
|
|
1139
|
+
if (attrName.length > 2 && /^on/i.test(attrName)) {
|
|
1140
|
+
result += cleanPrefix;
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
if (attrName.toLowerCase() === "srcdoc") {
|
|
1144
|
+
result += cleanPrefix;
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
if (isIsland(value) || isIslandCall(value)) {
|
|
1148
|
+
stripLeadingQuote = null;
|
|
1149
|
+
result += chunk + interpolateValue(value);
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
if (isUrlAttributeName(attrName)) {
|
|
1153
|
+
if (isRawHtml(value)) {
|
|
1154
|
+
result += cleanPrefix + ` ${attrName}="${value.value}"`;
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
const resolved = resolveTemplateValue(value);
|
|
1158
|
+
if (resolved == null || resolved === false) {
|
|
1159
|
+
result += cleanPrefix;
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
const url = String(resolved);
|
|
1163
|
+
if (!isSafeUrlAttrValue(openTagName(result + chunk, result.length + attrMatch.index), attrName, url)) {
|
|
1164
|
+
result += cleanPrefix;
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
const quotedUrl = openQuote !== null || UNQUOTED_HAZARD_RE.test(url);
|
|
1168
|
+
result += cleanPrefix + ` ${attrName}=` + (quotedUrl ? `"${escapeHtml(url)}"` : escapeHtml(url));
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
if (attrName.toLowerCase() === "style") {
|
|
1172
|
+
if (!isRawHtml(value)) {
|
|
1173
|
+
const resolved = resolveTemplateValue(value);
|
|
1174
|
+
if (isPlainObject(resolved)) {
|
|
1175
|
+
result += cleanPrefix + ` style="${escapeHtml(serializeStyle(resolved))}"`;
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (isRawHtml(value)) {
|
|
1181
|
+
stripLeadingQuote = null;
|
|
1182
|
+
result += chunk + interpolateValue(value);
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
const resolved = resolveTemplateValue(value);
|
|
1186
|
+
if (resolved == null || resolved === false) {
|
|
1187
|
+
result += cleanPrefix;
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
if (resolved === true) {
|
|
1191
|
+
result += cleanPrefix + ` ${attrName}`;
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
const generic = String(resolved);
|
|
1195
|
+
if (openQuote !== null || UNQUOTED_HAZARD_RE.test(generic)) result += cleanPrefix + ` ${attrName}="${escapeHtml(generic)}"`;
|
|
1196
|
+
else result += cleanPrefix + ` ${attrName}=${escapeHtml(generic)}`;
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
971
1199
|
result += chunk + interpolateValue(value);
|
|
972
1200
|
}
|
|
973
1201
|
if (pendingBindSpecs !== "") result += ` data-ilha-bind="${pendingBindSpecs}"`;
|
|
@@ -988,10 +1216,10 @@ const contextRegistry = /* @__PURE__ */ new Map();
|
|
|
988
1216
|
function ilhaContextFn(key, initial) {
|
|
989
1217
|
if (contextRegistry.has(key)) return contextRegistry.get(key);
|
|
990
1218
|
const s = signal(initial);
|
|
991
|
-
const accessor = (...args) => {
|
|
1219
|
+
const accessor = markSignalAccessor((...args) => {
|
|
992
1220
|
if (args.length === 0) return s();
|
|
993
1221
|
s(resolveSignalSetter(() => s(), args[0]));
|
|
994
|
-
};
|
|
1222
|
+
});
|
|
995
1223
|
contextRegistry.set(key, accessor);
|
|
996
1224
|
return accessor;
|
|
997
1225
|
}
|
|
@@ -1007,16 +1235,11 @@ const ilhaContext = Object.assign(ilhaContextFn, {
|
|
|
1007
1235
|
});
|
|
1008
1236
|
/**
|
|
1009
1237
|
* Create a free-standing reactive signal that lives outside any island.
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1012
|
-
* template syntax.
|
|
1013
|
-
*
|
|
1014
|
-
* The returned accessor is a getter when called with no arguments and a
|
|
1015
|
-
* setter when called with one. Reading it inside a `.derived()`, `.effect()`,
|
|
1016
|
-
* or `.render()` automatically subscribes the surrounding reactive scope —
|
|
1017
|
-
* so when the signal changes, dependents re-run as if it were local state.
|
|
1238
|
+
* Internal: used by state()'s slot-drift fallback. The public way to create
|
|
1239
|
+
* free-standing shared state is `context(key, initial)`.
|
|
1018
1240
|
*/
|
|
1019
1241
|
function ilhaSignal(initial) {
|
|
1242
|
+
if (currentFrame()?.creating && __DEV__);
|
|
1020
1243
|
const s = signal(initial);
|
|
1021
1244
|
return markSignalAccessor((...args) => {
|
|
1022
1245
|
if (args.length === 0) return s();
|
|
@@ -1024,25 +1247,6 @@ function ilhaSignal(initial) {
|
|
|
1024
1247
|
});
|
|
1025
1248
|
}
|
|
1026
1249
|
/**
|
|
1027
|
-
* Create a free-standing read-only reactive value derived from other signals.
|
|
1028
|
-
* The computation is lazy and cached: `fn` re-runs only when a signal it read
|
|
1029
|
-
* changed and the computed is read again. Reading it inside a `.derived()`,
|
|
1030
|
-
* `.effect()`, `.render()`, or top-level `effect()` subscribes that scope —
|
|
1031
|
-
* dependents re-run when the computed's value changes.
|
|
1032
|
-
*
|
|
1033
|
-
* ```ts
|
|
1034
|
-
* const items = ilha.signal([1, 2, 3]);
|
|
1035
|
-
* const total = ilha.computed(() => items().reduce((a, b) => a + b, 0));
|
|
1036
|
-
* ```
|
|
1037
|
-
*/
|
|
1038
|
-
function ilhaComputed(fn) {
|
|
1039
|
-
const c = computed(fn);
|
|
1040
|
-
return markSignalAccessor((...args) => {
|
|
1041
|
-
if (args.length > 0) return;
|
|
1042
|
-
return c();
|
|
1043
|
-
});
|
|
1044
|
-
}
|
|
1045
|
-
/**
|
|
1046
1250
|
* Run a free-standing reactive effect outside any island. `fn` runs once
|
|
1047
1251
|
* immediately and again whenever a signal it read changes. It may return a
|
|
1048
1252
|
* cleanup function, invoked before each re-run and on stop. Signal writes
|
|
@@ -1067,7 +1271,7 @@ function ilhaEffect(fn) {
|
|
|
1067
1271
|
cleanup = void 0;
|
|
1068
1272
|
}
|
|
1069
1273
|
};
|
|
1070
|
-
const stop = effect(() => {
|
|
1274
|
+
const stop = effect$1(() => {
|
|
1071
1275
|
runCleanup();
|
|
1072
1276
|
startBatch();
|
|
1073
1277
|
try {
|
|
@@ -1115,6 +1319,74 @@ function batch(fn) {
|
|
|
1115
1319
|
endBatch();
|
|
1116
1320
|
}
|
|
1117
1321
|
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Keep a standalone signal in sync with persistent storage:
|
|
1324
|
+
*
|
|
1325
|
+
* 1. On call, reads `key` and writes the stored value into the signal.
|
|
1326
|
+
* 2. Subscribes to the signal and writes its value back on every change.
|
|
1327
|
+
* 3. Optionally mirrors writes from other tabs (`storage` events).
|
|
1328
|
+
*
|
|
1329
|
+
* No-op on the server (returns an inert unsubscribe). Call the returned
|
|
1330
|
+
* unsubscribe to stop syncing.
|
|
1331
|
+
*
|
|
1332
|
+
* ```ts
|
|
1333
|
+
* const cart = signal([] as string[]);
|
|
1334
|
+
* persist(cart, "cart");
|
|
1335
|
+
* ```
|
|
1336
|
+
*/
|
|
1337
|
+
function persist(accessor, key, options = {}) {
|
|
1338
|
+
if (typeof window === "undefined") return () => {};
|
|
1339
|
+
const storage = options.storage ?? window.localStorage;
|
|
1340
|
+
const serialize = options.serialize ?? JSON.stringify;
|
|
1341
|
+
const deserialize = options.deserialize ?? ((raw) => {
|
|
1342
|
+
try {
|
|
1343
|
+
return JSON.parse(raw);
|
|
1344
|
+
} catch {
|
|
1345
|
+
return null;
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
const label = `[ilha] persist("${key}")`;
|
|
1349
|
+
const applyRaw = (raw) => {
|
|
1350
|
+
if (raw == null) return;
|
|
1351
|
+
try {
|
|
1352
|
+
const value = deserialize(raw);
|
|
1353
|
+
if (value !== null) {
|
|
1354
|
+
const prevSub = setActiveSub(void 0);
|
|
1355
|
+
try {
|
|
1356
|
+
accessor(() => value);
|
|
1357
|
+
} finally {
|
|
1358
|
+
setActiveSub(prevSub);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
} catch (err) {
|
|
1362
|
+
console.error(`${label}: failed to restore`, err);
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
try {
|
|
1366
|
+
applyRaw(storage.getItem(key));
|
|
1367
|
+
} catch (err) {
|
|
1368
|
+
console.error(`${label}: failed to read storage`, err);
|
|
1369
|
+
}
|
|
1370
|
+
const stopEffect = ilhaEffect(() => {
|
|
1371
|
+
try {
|
|
1372
|
+
storage.setItem(key, serialize(accessor()));
|
|
1373
|
+
} catch (err) {
|
|
1374
|
+
console.error(`${label}: failed to write storage`, err);
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
let onStorage = null;
|
|
1378
|
+
if (options.crossTab !== false && storage === window.localStorage) {
|
|
1379
|
+
onStorage = (e) => {
|
|
1380
|
+
if (e.key !== key) return;
|
|
1381
|
+
applyRaw(e.newValue);
|
|
1382
|
+
};
|
|
1383
|
+
window.addEventListener("storage", onStorage);
|
|
1384
|
+
}
|
|
1385
|
+
return () => {
|
|
1386
|
+
stopEffect();
|
|
1387
|
+
if (onStorage) window.removeEventListener("storage", onStorage);
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1118
1390
|
function createDerivedAccessor(read, write) {
|
|
1119
1391
|
const accessor = markSignalAccessor((...args) => {
|
|
1120
1392
|
if (args.length > 0) {
|
|
@@ -1135,333 +1407,60 @@ function defaultDerivedAccessor() {
|
|
|
1135
1407
|
error: void 0
|
|
1136
1408
|
}));
|
|
1137
1409
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
if (
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
const ILHA_QUERY = Symbol.for("ilha.store.query");
|
|
1151
|
-
const ILHA_QUERY_RUN = Symbol.for("ilha.store.query.run");
|
|
1152
|
-
const ILHA_QUERY_PENDING = Symbol.for("ilha.store.query.pending");
|
|
1153
|
-
const ILHA_QUERY_STACK = Symbol.for("ilha.store.query.stack");
|
|
1154
|
-
function queryCaptureStack() {
|
|
1155
|
-
const g = globalThis;
|
|
1156
|
-
let stack = g[ILHA_QUERY_STACK];
|
|
1157
|
-
if (!stack) {
|
|
1158
|
-
stack = [];
|
|
1159
|
-
g[ILHA_QUERY_STACK] = stack;
|
|
1160
|
-
}
|
|
1161
|
-
return stack;
|
|
1162
|
-
}
|
|
1163
|
-
function isQueryCall(value) {
|
|
1164
|
-
return value !== null && typeof value === "object" && ILHA_QUERY in value;
|
|
1165
|
-
}
|
|
1166
|
-
function resolveQueryCall(result, captureSlot) {
|
|
1167
|
-
const pending = globalThis[ILHA_QUERY_PENDING];
|
|
1168
|
-
globalThis[ILHA_QUERY_PENDING] = void 0;
|
|
1169
|
-
return captureSlot.queryCall ?? pending ?? (isQueryCall(result) ? result : void 0);
|
|
1170
|
-
}
|
|
1171
|
-
function runBrandedQuery(queryCall, abortSignal, env) {
|
|
1172
|
-
const writeEnv = (next) => {
|
|
1173
|
-
if (abortSignal.aborted) return;
|
|
1174
|
-
const prevSub = setActiveSub(void 0);
|
|
1175
|
-
env(next);
|
|
1176
|
-
setActiveSub(prevSub);
|
|
1177
|
-
};
|
|
1178
|
-
const run = queryCall[ILHA_QUERY_RUN];
|
|
1179
|
-
if (typeof run !== "function") {
|
|
1180
|
-
writeEnv({
|
|
1181
|
-
loading: false,
|
|
1182
|
-
value: void 0,
|
|
1183
|
-
error: /* @__PURE__ */ new Error("[@ilha] branded query is missing a callable runner")
|
|
1184
|
-
});
|
|
1185
|
-
return;
|
|
1186
|
-
}
|
|
1187
|
-
try {
|
|
1188
|
-
run.call(queryCall, abortSignal, {
|
|
1189
|
-
onResult: (value) => {
|
|
1190
|
-
writeEnv({
|
|
1191
|
-
loading: false,
|
|
1192
|
-
value,
|
|
1193
|
-
error: void 0
|
|
1194
|
-
});
|
|
1195
|
-
},
|
|
1196
|
-
onError: (err) => {
|
|
1197
|
-
writeEnv({
|
|
1198
|
-
loading: false,
|
|
1199
|
-
value: void 0,
|
|
1200
|
-
error: err
|
|
1201
|
-
});
|
|
1202
|
-
},
|
|
1203
|
-
onFetchStart: () => {
|
|
1204
|
-
const prevSub = setActiveSub(void 0);
|
|
1205
|
-
env({
|
|
1206
|
-
loading: true,
|
|
1207
|
-
value: env().value,
|
|
1208
|
-
error: void 0
|
|
1209
|
-
});
|
|
1210
|
-
setActiveSub(prevSub);
|
|
1211
|
-
}
|
|
1212
|
-
});
|
|
1213
|
-
} catch (err) {
|
|
1214
|
-
writeEnv({
|
|
1215
|
-
loading: false,
|
|
1216
|
-
value: void 0,
|
|
1217
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
1410
|
+
const BIND_SENTINEL_ATTR = "data-ilha-bind";
|
|
1411
|
+
const EVENT_SENTINEL_ATTR = "data-ilha-on";
|
|
1412
|
+
function parseBindSentinel(value) {
|
|
1413
|
+
const out = [];
|
|
1414
|
+
for (const part of value.split(",")) {
|
|
1415
|
+
const [kind, idx] = part.split(":");
|
|
1416
|
+
if (!kind || !idx) continue;
|
|
1417
|
+
const i = Number(idx);
|
|
1418
|
+
if (!Number.isInteger(i) || i < 0) continue;
|
|
1419
|
+
out.push({
|
|
1420
|
+
kind,
|
|
1421
|
+
index: i
|
|
1218
1422
|
});
|
|
1219
1423
|
}
|
|
1424
|
+
return out;
|
|
1220
1425
|
}
|
|
1221
|
-
function
|
|
1222
|
-
const
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
value: void 0,
|
|
1229
|
-
error: void 0
|
|
1426
|
+
function resolveBindOps(el, kind) {
|
|
1427
|
+
const input = el;
|
|
1428
|
+
switch (kind) {
|
|
1429
|
+
case "value": return {
|
|
1430
|
+
event: el.tagName === "SELECT" ? "change" : "input",
|
|
1431
|
+
read: (el) => el.value,
|
|
1432
|
+
write: (el, v) => el.value = v == null ? "" : String(v)
|
|
1230
1433
|
};
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
globalThis[ILHA_QUERY_PENDING] = void 0;
|
|
1262
|
-
initialEnvelope = {
|
|
1263
|
-
loading: false,
|
|
1264
|
-
value: void 0,
|
|
1265
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
1266
|
-
};
|
|
1267
|
-
} finally {
|
|
1268
|
-
stack.pop();
|
|
1269
|
-
ac.abort();
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
const env = signal(initialEnvelope);
|
|
1273
|
-
envelopes.set(entry.key, env);
|
|
1274
|
-
}
|
|
1275
|
-
const accessors = /* @__PURE__ */ new Map();
|
|
1276
|
-
for (const entry of entries) {
|
|
1277
|
-
const env = envelopes.get(entry.key);
|
|
1278
|
-
accessors.set(entry.key, createDerivedAccessor(() => env(), (value) => {
|
|
1279
|
-
const prevSub = setActiveSub(void 0);
|
|
1280
|
-
env({
|
|
1281
|
-
loading: false,
|
|
1282
|
-
value,
|
|
1283
|
-
error: void 0
|
|
1284
|
-
});
|
|
1285
|
-
setActiveSub(prevSub);
|
|
1286
|
-
}));
|
|
1287
|
-
}
|
|
1288
|
-
const proxy = new Proxy({}, { get(_, key) {
|
|
1289
|
-
if (typeof key !== "string") return void 0;
|
|
1290
|
-
return accessors.get(key) ?? defaultDerivedAccessor();
|
|
1291
|
-
} });
|
|
1292
|
-
const setup = () => {
|
|
1293
|
-
const stops = [];
|
|
1294
|
-
for (const entry of entries) {
|
|
1295
|
-
const env = envelopes.get(entry.key);
|
|
1296
|
-
let ac = new AbortController();
|
|
1297
|
-
let skipFirst = derivedSnapshot != null && entry.key in derivedSnapshot;
|
|
1298
|
-
const stopEffect = effect(() => {
|
|
1299
|
-
ac.abort();
|
|
1300
|
-
ac = new AbortController();
|
|
1301
|
-
const currentAc = ac;
|
|
1302
|
-
let result;
|
|
1303
|
-
const captureSlot = {};
|
|
1304
|
-
const stack = queryCaptureStack();
|
|
1305
|
-
globalThis[ILHA_QUERY_PENDING] = void 0;
|
|
1306
|
-
stack.push(captureSlot);
|
|
1307
|
-
try {
|
|
1308
|
-
result = entry.fn({
|
|
1309
|
-
state,
|
|
1310
|
-
input,
|
|
1311
|
-
signal: currentAc.signal
|
|
1312
|
-
});
|
|
1313
|
-
} catch (err) {
|
|
1314
|
-
globalThis[ILHA_QUERY_PENDING] = void 0;
|
|
1315
|
-
if (skipFirst) {
|
|
1316
|
-
skipFirst = false;
|
|
1317
|
-
return;
|
|
1318
|
-
}
|
|
1319
|
-
const prevSub = setActiveSub(void 0);
|
|
1320
|
-
env({
|
|
1321
|
-
loading: false,
|
|
1322
|
-
value: void 0,
|
|
1323
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
1324
|
-
});
|
|
1325
|
-
setActiveSub(prevSub);
|
|
1326
|
-
return;
|
|
1327
|
-
} finally {
|
|
1328
|
-
stack.pop();
|
|
1329
|
-
}
|
|
1330
|
-
const queryCall = resolveQueryCall(result, captureSlot);
|
|
1331
|
-
if (skipFirst) {
|
|
1332
|
-
skipFirst = false;
|
|
1333
|
-
if (result instanceof Promise) result.catch(() => {});
|
|
1334
|
-
return;
|
|
1335
|
-
}
|
|
1336
|
-
if (queryCall) {
|
|
1337
|
-
runBrandedQuery(queryCall, currentAc.signal, env);
|
|
1338
|
-
return;
|
|
1339
|
-
}
|
|
1340
|
-
if (!(result instanceof Promise)) {
|
|
1341
|
-
const prevSub = setActiveSub(void 0);
|
|
1342
|
-
env({
|
|
1343
|
-
loading: false,
|
|
1344
|
-
value: result,
|
|
1345
|
-
error: void 0
|
|
1346
|
-
});
|
|
1347
|
-
setActiveSub(prevSub);
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1350
|
-
const prevSub = setActiveSub(void 0);
|
|
1351
|
-
const prevVal = env();
|
|
1352
|
-
env({
|
|
1353
|
-
loading: true,
|
|
1354
|
-
value: prevVal.value,
|
|
1355
|
-
error: void 0
|
|
1356
|
-
});
|
|
1357
|
-
setActiveSub(prevSub);
|
|
1358
|
-
result.then((value) => {
|
|
1359
|
-
if (currentAc.signal.aborted) return;
|
|
1360
|
-
if (isQueryCall(value)) {
|
|
1361
|
-
runBrandedQuery(value, currentAc.signal, env);
|
|
1362
|
-
return;
|
|
1363
|
-
}
|
|
1364
|
-
const latePending = globalThis[ILHA_QUERY_PENDING];
|
|
1365
|
-
globalThis[ILHA_QUERY_PENDING] = void 0;
|
|
1366
|
-
if (latePending) {
|
|
1367
|
-
runBrandedQuery(latePending, currentAc.signal, env);
|
|
1368
|
-
return;
|
|
1369
|
-
}
|
|
1370
|
-
env({
|
|
1371
|
-
loading: false,
|
|
1372
|
-
value,
|
|
1373
|
-
error: void 0
|
|
1374
|
-
});
|
|
1375
|
-
}).catch((err) => {
|
|
1376
|
-
if (currentAc.signal.aborted) return;
|
|
1377
|
-
env({
|
|
1378
|
-
loading: false,
|
|
1379
|
-
value: void 0,
|
|
1380
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
1381
|
-
});
|
|
1382
|
-
});
|
|
1383
|
-
});
|
|
1384
|
-
stops.push(() => {
|
|
1385
|
-
stopEffect();
|
|
1386
|
-
ac.abort();
|
|
1387
|
-
});
|
|
1388
|
-
}
|
|
1389
|
-
return () => stops.forEach((s) => s());
|
|
1390
|
-
};
|
|
1391
|
-
return {
|
|
1392
|
-
proxy,
|
|
1393
|
-
setup
|
|
1394
|
-
};
|
|
1395
|
-
}
|
|
1396
|
-
const BIND_SENTINEL_ATTR = "data-ilha-bind";
|
|
1397
|
-
const EVENT_SENTINEL_ATTR = "data-ilha-on";
|
|
1398
|
-
function parseBindSentinel(value) {
|
|
1399
|
-
const out = [];
|
|
1400
|
-
for (const part of value.split(",")) {
|
|
1401
|
-
const [kind, idx] = part.split(":");
|
|
1402
|
-
if (!kind || !idx) continue;
|
|
1403
|
-
const i = Number(idx);
|
|
1404
|
-
if (!Number.isInteger(i) || i < 0) continue;
|
|
1405
|
-
out.push({
|
|
1406
|
-
kind,
|
|
1407
|
-
index: i
|
|
1408
|
-
});
|
|
1409
|
-
}
|
|
1410
|
-
return out;
|
|
1411
|
-
}
|
|
1412
|
-
function resolveBindOps(el, kind) {
|
|
1413
|
-
const input = el;
|
|
1414
|
-
switch (kind) {
|
|
1415
|
-
case "value": return {
|
|
1416
|
-
event: el.tagName === "SELECT" ? "change" : "input",
|
|
1417
|
-
read: (el) => el.value,
|
|
1418
|
-
write: (el, v) => el.value = v == null ? "" : String(v)
|
|
1419
|
-
};
|
|
1420
|
-
case "valueAsNumber": return {
|
|
1421
|
-
event: "input",
|
|
1422
|
-
read: (el) => {
|
|
1423
|
-
const n = el.valueAsNumber;
|
|
1424
|
-
return Number.isNaN(n) ? null : n;
|
|
1425
|
-
},
|
|
1426
|
-
write: (el, v) => el.value = v == null || Number.isNaN(v) ? "" : String(v)
|
|
1427
|
-
};
|
|
1428
|
-
case "valueAsDate": return {
|
|
1429
|
-
event: "input",
|
|
1430
|
-
read: (el) => el.valueAsDate,
|
|
1431
|
-
write: (el, v) => {
|
|
1432
|
-
el.value = formatDateForInput(v);
|
|
1433
|
-
}
|
|
1434
|
-
};
|
|
1435
|
-
case "checked": return {
|
|
1436
|
-
event: "change",
|
|
1437
|
-
read: (el) => el.checked,
|
|
1438
|
-
write: (el, v) => el.checked = Boolean(v)
|
|
1439
|
-
};
|
|
1440
|
-
case "files": return {
|
|
1441
|
-
event: "change",
|
|
1442
|
-
read: (el) => el.files,
|
|
1443
|
-
write: () => {}
|
|
1444
|
-
};
|
|
1445
|
-
case "open": return {
|
|
1446
|
-
event: "toggle",
|
|
1447
|
-
read: (el) => el.open,
|
|
1448
|
-
write: (el, v) => el.open = Boolean(v)
|
|
1449
|
-
};
|
|
1450
|
-
case "this": return {
|
|
1451
|
-
event: null,
|
|
1452
|
-
read: () => void 0,
|
|
1453
|
-
write: () => {}
|
|
1454
|
-
};
|
|
1455
|
-
case "group": {
|
|
1456
|
-
const isCheckbox = input.type === "checkbox";
|
|
1457
|
-
return {
|
|
1458
|
-
event: "change",
|
|
1459
|
-
read: (el) => {
|
|
1460
|
-
const i = el;
|
|
1461
|
-
if (isCheckbox) return {
|
|
1462
|
-
__ilhaGroup: true,
|
|
1463
|
-
value: i.value,
|
|
1464
|
-
checked: i.checked
|
|
1434
|
+
case "checked": return {
|
|
1435
|
+
event: "change",
|
|
1436
|
+
read: (el) => el.checked,
|
|
1437
|
+
write: (el, v) => el.checked = Boolean(v)
|
|
1438
|
+
};
|
|
1439
|
+
case "files": return {
|
|
1440
|
+
event: "change",
|
|
1441
|
+
read: (el) => el.files,
|
|
1442
|
+
write: () => {}
|
|
1443
|
+
};
|
|
1444
|
+
case "open": return {
|
|
1445
|
+
event: "toggle",
|
|
1446
|
+
read: (el) => el.open,
|
|
1447
|
+
write: (el, v) => el.open = Boolean(v)
|
|
1448
|
+
};
|
|
1449
|
+
case "this": return {
|
|
1450
|
+
event: null,
|
|
1451
|
+
read: () => void 0,
|
|
1452
|
+
write: () => {}
|
|
1453
|
+
};
|
|
1454
|
+
case "group": {
|
|
1455
|
+
const isCheckbox = input.type === "checkbox";
|
|
1456
|
+
return {
|
|
1457
|
+
event: "change",
|
|
1458
|
+
read: (el) => {
|
|
1459
|
+
const i = el;
|
|
1460
|
+
if (isCheckbox) return {
|
|
1461
|
+
__ilhaGroup: true,
|
|
1462
|
+
value: i.value,
|
|
1463
|
+
checked: i.checked
|
|
1465
1464
|
};
|
|
1466
1465
|
return i.checked ? i.value : void 0;
|
|
1467
1466
|
},
|
|
@@ -1520,7 +1519,7 @@ function applyJsxEvents({ host, records, reportError, unmountSignal, onceFired }
|
|
|
1520
1519
|
invocationController?.abort();
|
|
1521
1520
|
invocationController = new AbortController();
|
|
1522
1521
|
}
|
|
1523
|
-
const signal = invocationController ?
|
|
1522
|
+
const signal = invocationController ? AbortSignal.any([listenerController.signal, invocationController.signal]) : listenerController.signal;
|
|
1524
1523
|
let result;
|
|
1525
1524
|
startBatch();
|
|
1526
1525
|
try {
|
|
@@ -1619,47 +1618,6 @@ function applyTemplateBindings(host, binds, phase = "all") {
|
|
|
1619
1618
|
}
|
|
1620
1619
|
return () => cleanups.forEach((c) => c());
|
|
1621
1620
|
}
|
|
1622
|
-
function parseOnArgs(selectorOrCombined) {
|
|
1623
|
-
const atIdx = selectorOrCombined.lastIndexOf("@");
|
|
1624
|
-
const selector = atIdx === -1 ? "" : selectorOrCombined.slice(0, atIdx);
|
|
1625
|
-
const parts = (atIdx === -1 ? selectorOrCombined : selectorOrCombined.slice(atIdx + 1)).split(":");
|
|
1626
|
-
const eventType = parts[0];
|
|
1627
|
-
const modifiers = new Set(parts.slice(1));
|
|
1628
|
-
return {
|
|
1629
|
-
selector,
|
|
1630
|
-
eventType,
|
|
1631
|
-
options: {
|
|
1632
|
-
once: modifiers.has("once"),
|
|
1633
|
-
capture: modifiers.has("capture"),
|
|
1634
|
-
passive: modifiers.has("passive")
|
|
1635
|
-
},
|
|
1636
|
-
abortable: modifiers.has("abortable")
|
|
1637
|
-
};
|
|
1638
|
-
}
|
|
1639
|
-
/**
|
|
1640
|
-
* Combine multiple AbortSignals into one that aborts when any input aborts.
|
|
1641
|
-
* Uses native AbortSignal.any when available, falls back to a manual chain
|
|
1642
|
-
* otherwise (for older runtimes).
|
|
1643
|
-
*/
|
|
1644
|
-
function anySignal(signals) {
|
|
1645
|
-
if (typeof AbortSignal.any === "function") return AbortSignal.any(signals);
|
|
1646
|
-
const controller = new AbortController();
|
|
1647
|
-
const cleanups = [];
|
|
1648
|
-
for (const s of signals) {
|
|
1649
|
-
if (s.aborted) {
|
|
1650
|
-
controller.abort(s.reason);
|
|
1651
|
-
cleanups.forEach((c) => c());
|
|
1652
|
-
return controller.signal;
|
|
1653
|
-
}
|
|
1654
|
-
const handler = () => controller.abort(s.reason);
|
|
1655
|
-
s.addEventListener("abort", handler, { once: true });
|
|
1656
|
-
cleanups.push(() => s.removeEventListener("abort", handler));
|
|
1657
|
-
}
|
|
1658
|
-
if (!controller.signal.aborted) controller.signal.addEventListener("abort", () => {
|
|
1659
|
-
cleanups.forEach((c) => c());
|
|
1660
|
-
});
|
|
1661
|
-
return controller.signal;
|
|
1662
|
-
}
|
|
1663
1621
|
const globalErrorHandlers = /* @__PURE__ */ new Set();
|
|
1664
1622
|
function reportToGlobal(error, source) {
|
|
1665
1623
|
if (globalErrorHandlers.size === 0) return false;
|
|
@@ -1672,9 +1630,7 @@ function reportToGlobal(error, source) {
|
|
|
1672
1630
|
}
|
|
1673
1631
|
/**
|
|
1674
1632
|
* Register a global error handler invoked when any island reports an error
|
|
1675
|
-
*
|
|
1676
|
-
* handler. Returns an unsubscribe function. Islands with their own .onError()
|
|
1677
|
-
* are handled locally and do not reach the global sink.
|
|
1633
|
+
* and has no local onError() handler. Returns an unsubscribe function.
|
|
1678
1634
|
*/
|
|
1679
1635
|
function onUncaughtError(fn) {
|
|
1680
1636
|
globalErrorHandlers.add(fn);
|
|
@@ -1682,1238 +1638,1256 @@ function onUncaughtError(fn) {
|
|
|
1682
1638
|
globalErrorHandlers.delete(fn);
|
|
1683
1639
|
};
|
|
1684
1640
|
}
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
return
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
states: [],
|
|
1701
|
-
streams: [],
|
|
1702
|
-
deriveds: [],
|
|
1703
|
-
actions: [],
|
|
1704
|
-
ons: [],
|
|
1705
|
-
effects: [],
|
|
1706
|
-
onMounts: [],
|
|
1707
|
-
onErrors: [],
|
|
1708
|
-
transition: null,
|
|
1709
|
-
css: null,
|
|
1710
|
-
as: null
|
|
1711
|
-
});
|
|
1712
|
-
}
|
|
1713
|
-
as(tag) {
|
|
1714
|
-
return new IlhaBuilder({
|
|
1715
|
-
...this._cfg,
|
|
1716
|
-
as: assertValidSlotTagName(tag)
|
|
1717
|
-
});
|
|
1718
|
-
}
|
|
1719
|
-
state(key, init) {
|
|
1720
|
-
const cfg = this._cfg;
|
|
1721
|
-
return new IlhaBuilder({
|
|
1722
|
-
...cfg,
|
|
1723
|
-
states: [...cfg.states, {
|
|
1724
|
-
key,
|
|
1725
|
-
init
|
|
1726
|
-
}]
|
|
1727
|
-
});
|
|
1728
|
-
}
|
|
1729
|
-
stream(key, fn) {
|
|
1730
|
-
const cfg = this._cfg;
|
|
1731
|
-
const states = cfg.states.some((entry) => entry.key === key) ? cfg.states : [...cfg.states, {
|
|
1732
|
-
key,
|
|
1733
|
-
init: void 0
|
|
1734
|
-
}];
|
|
1735
|
-
return new IlhaBuilder({
|
|
1736
|
-
...cfg,
|
|
1737
|
-
states,
|
|
1738
|
-
streams: [...cfg.streams.filter((entry) => entry.key !== key), {
|
|
1739
|
-
key,
|
|
1740
|
-
fn
|
|
1741
|
-
}]
|
|
1742
|
-
});
|
|
1743
|
-
}
|
|
1744
|
-
derived(key, fn) {
|
|
1745
|
-
const cfg = this._cfg;
|
|
1746
|
-
return new IlhaBuilder({
|
|
1747
|
-
...cfg,
|
|
1748
|
-
deriveds: [...cfg.deriveds, {
|
|
1749
|
-
key,
|
|
1750
|
-
fn
|
|
1751
|
-
}]
|
|
1752
|
-
});
|
|
1641
|
+
const FRAME_STACK = Symbol.for("ilha.primitiveFrameStack");
|
|
1642
|
+
function frameStack() {
|
|
1643
|
+
const g = globalThis;
|
|
1644
|
+
return g[FRAME_STACK] ??= [];
|
|
1645
|
+
}
|
|
1646
|
+
function currentFrame() {
|
|
1647
|
+
const stack = frameStack();
|
|
1648
|
+
return stack[stack.length - 1];
|
|
1649
|
+
}
|
|
1650
|
+
function withFrame(frame, fn) {
|
|
1651
|
+
frameStack().push(frame);
|
|
1652
|
+
try {
|
|
1653
|
+
return fn();
|
|
1654
|
+
} finally {
|
|
1655
|
+
frameStack().pop();
|
|
1753
1656
|
}
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1657
|
+
}
|
|
1658
|
+
function freshFrame(options) {
|
|
1659
|
+
return {
|
|
1660
|
+
slots: [],
|
|
1661
|
+
cursor: 0,
|
|
1662
|
+
creating: true,
|
|
1663
|
+
ssr: options?.ssr ?? false,
|
|
1664
|
+
instance: options?.instance ?? null,
|
|
1665
|
+
stateIndex: 0,
|
|
1666
|
+
derivedIndex: 0,
|
|
1667
|
+
actionIndex: 0,
|
|
1668
|
+
pendingDerived: options?.ssr ? [] : null,
|
|
1669
|
+
actionManifest: options?.ssr ? /* @__PURE__ */ new Map() : null
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
const PRIMITIVE_NAMES = {
|
|
1673
|
+
state: "state",
|
|
1674
|
+
derived: "derived",
|
|
1675
|
+
action: "action",
|
|
1676
|
+
effect: "effect",
|
|
1677
|
+
once: "effect.once",
|
|
1678
|
+
error: "onError"
|
|
1679
|
+
};
|
|
1680
|
+
/** Reserve the next slot for a primitive call. Throws outside an island
|
|
1681
|
+
* render; warns in dev on hook count/kind drift during re-renders. */
|
|
1682
|
+
function acquireSlot(kind) {
|
|
1683
|
+
const frame = currentFrame();
|
|
1684
|
+
if (!frame) throw new Error(`[ilha] ${PRIMITIVE_NAMES[kind]}() called outside an island render. Primitives are only valid while an island component (or a plain component owned by one) is rendering.`);
|
|
1685
|
+
const index = frame.cursor++;
|
|
1686
|
+
if (frame.creating) return void 0;
|
|
1687
|
+
const existing = frame.slots[index];
|
|
1688
|
+
return existing && existing.kind === kind ? existing : void 0;
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Declare island-local reactive state at this call position. The initializer
|
|
1692
|
+
* applies only when the instance is created — later renders reuse the same
|
|
1693
|
+
* underlying signal, so prop-driven initializers never reset user state.
|
|
1694
|
+
*
|
|
1695
|
+
* A function argument is treated as a lazy initializer:
|
|
1696
|
+
* const count = state(() => expensiveInitialValue());
|
|
1697
|
+
* To store a function VALUE, return it from the updater wrapper on write:
|
|
1698
|
+
* setCallback(() => nextCallback);
|
|
1699
|
+
*/
|
|
1700
|
+
function state(init) {
|
|
1701
|
+
const frame = currentFrame();
|
|
1702
|
+
const existing = acquireSlot("state");
|
|
1703
|
+
if (existing) return existing.acc;
|
|
1704
|
+
const f = frame;
|
|
1705
|
+
if (!f.creating) return ilhaSignal(void 0);
|
|
1706
|
+
const index = f.stateIndex++;
|
|
1707
|
+
const snap = f.ssr ? void 0 : f.instance?.stateSnapshot?.s?.[index];
|
|
1708
|
+
const initial = snap === void 0 ? typeof init === "function" ? init() : init : snap;
|
|
1709
|
+
const s = signal(initial);
|
|
1710
|
+
const acc = markSignalAccessor((...args) => {
|
|
1711
|
+
if (args.length === 0) return s();
|
|
1712
|
+
const next = resolveSignalSetter(() => s(), args[0]);
|
|
1713
|
+
s(next);
|
|
1714
|
+
});
|
|
1715
|
+
f.slots[f.cursor - 1] = {
|
|
1716
|
+
kind: "state",
|
|
1717
|
+
acc
|
|
1718
|
+
};
|
|
1719
|
+
return acc;
|
|
1720
|
+
}
|
|
1721
|
+
/** True for async iterables (incl. async generators). */
|
|
1722
|
+
function isAsyncIterable(value) {
|
|
1723
|
+
return value !== null && typeof value[Symbol.asyncIterator] === "function";
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Declare derived state at this call position: synchronous computations,
|
|
1727
|
+
* Promises, or async generators. Reads track their dependencies; async work
|
|
1728
|
+
* races latest-run-wins and aborts stale runs through `ctx.signal`.
|
|
1729
|
+
*/
|
|
1730
|
+
function derived(fn) {
|
|
1731
|
+
const frame = currentFrame();
|
|
1732
|
+
const existing = acquireSlot("derived");
|
|
1733
|
+
const f = frame;
|
|
1734
|
+
if (existing) {
|
|
1735
|
+
const slot = existing;
|
|
1736
|
+
slot.fn = fn;
|
|
1737
|
+
if (f.ssr && !f.creating && !slot.ssrAsync) {
|
|
1738
|
+
const ac = new AbortController();
|
|
1739
|
+
const prevSub = setActiveSub(void 0);
|
|
1740
|
+
try {
|
|
1741
|
+
const result = slot.fn({ signal: ac.signal });
|
|
1742
|
+
if (!(result instanceof Promise) && !isAsyncIterable(result)) slot.env({
|
|
1743
|
+
loading: false,
|
|
1744
|
+
value: result,
|
|
1745
|
+
error: void 0
|
|
1746
|
+
});
|
|
1747
|
+
} catch (err) {
|
|
1748
|
+
slot.env({
|
|
1749
|
+
loading: false,
|
|
1750
|
+
value: void 0,
|
|
1751
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
1752
|
+
});
|
|
1753
|
+
} finally {
|
|
1754
|
+
setActiveSub(prevSub);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
return slot.acc;
|
|
1762
1758
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1759
|
+
if (!f.creating) return defaultDerivedAccessor();
|
|
1760
|
+
const index = f.derivedIndex++;
|
|
1761
|
+
const env = signal({
|
|
1762
|
+
loading: false,
|
|
1763
|
+
value: void 0,
|
|
1764
|
+
error: void 0
|
|
1765
|
+
});
|
|
1766
|
+
let fromSnapshot = false;
|
|
1767
|
+
if (!f.ssr) {
|
|
1768
|
+
const snap = f.instance?.stateSnapshot?.d?.[index];
|
|
1769
|
+
if (snap) {
|
|
1770
|
+
env({ ...snap });
|
|
1771
|
+
fromSnapshot = true;
|
|
1772
|
+
}
|
|
1775
1773
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1774
|
+
const accessor = createDerivedAccessor(() => env(), (value) => {
|
|
1775
|
+
const prevSub = setActiveSub(void 0);
|
|
1776
|
+
try {
|
|
1777
|
+
env({
|
|
1778
|
+
loading: false,
|
|
1779
|
+
value,
|
|
1780
|
+
error: void 0
|
|
1781
|
+
});
|
|
1782
|
+
} finally {
|
|
1783
|
+
setActiveSub(prevSub);
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
const slot = {
|
|
1787
|
+
kind: "derived",
|
|
1788
|
+
env,
|
|
1789
|
+
acc: accessor,
|
|
1790
|
+
fn,
|
|
1791
|
+
ssrAsync: false,
|
|
1792
|
+
fromSnapshot
|
|
1793
|
+
};
|
|
1794
|
+
if (f.ssr) {
|
|
1795
|
+
const ac = new AbortController();
|
|
1796
|
+
const prevSub = setActiveSub(void 0);
|
|
1797
|
+
let result;
|
|
1798
|
+
let threw = false;
|
|
1799
|
+
let syncError;
|
|
1800
|
+
try {
|
|
1801
|
+
result = slot.fn({ signal: ac.signal });
|
|
1802
|
+
} catch (err) {
|
|
1803
|
+
threw = true;
|
|
1804
|
+
syncError = err;
|
|
1805
|
+
} finally {
|
|
1806
|
+
setActiveSub(prevSub);
|
|
1807
|
+
}
|
|
1808
|
+
let pull = null;
|
|
1809
|
+
if (threw) env({
|
|
1810
|
+
loading: false,
|
|
1811
|
+
value: void 0,
|
|
1812
|
+
error: syncError instanceof Error ? syncError : new Error(String(syncError))
|
|
1780
1813
|
});
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1814
|
+
else if (result instanceof Promise) {
|
|
1815
|
+
env({
|
|
1816
|
+
loading: true,
|
|
1817
|
+
value: void 0,
|
|
1818
|
+
error: void 0
|
|
1819
|
+
});
|
|
1820
|
+
pull = result.then((value) => env({
|
|
1821
|
+
loading: false,
|
|
1822
|
+
value,
|
|
1823
|
+
error: void 0
|
|
1824
|
+
}), (err) => env({
|
|
1825
|
+
loading: false,
|
|
1826
|
+
value: void 0,
|
|
1827
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
1828
|
+
}));
|
|
1829
|
+
result.catch(() => {});
|
|
1830
|
+
} else if (isAsyncIterable(result)) {
|
|
1831
|
+
env({
|
|
1832
|
+
loading: true,
|
|
1833
|
+
value: void 0,
|
|
1834
|
+
error: void 0
|
|
1835
|
+
});
|
|
1836
|
+
const iterable = result;
|
|
1837
|
+
pull = (async () => {
|
|
1838
|
+
const it = iterable[Symbol.asyncIterator]();
|
|
1839
|
+
try {
|
|
1840
|
+
const first = await it.next();
|
|
1841
|
+
env({
|
|
1842
|
+
loading: false,
|
|
1843
|
+
value: first.done ? void 0 : first.value,
|
|
1844
|
+
error: void 0
|
|
1845
|
+
});
|
|
1846
|
+
} finally {
|
|
1847
|
+
await it.return?.(void 0);
|
|
1848
|
+
}
|
|
1849
|
+
})();
|
|
1850
|
+
} else env({
|
|
1851
|
+
loading: false,
|
|
1852
|
+
value: result,
|
|
1853
|
+
error: void 0
|
|
1786
1854
|
});
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
onErrors: [...this._cfg.onErrors, { fn }]
|
|
1855
|
+
slot.ssrAsync = pull !== null;
|
|
1856
|
+
if (pull) f.pendingDerived.push({
|
|
1857
|
+
index,
|
|
1858
|
+
promise: pull.catch(() => {})
|
|
1792
1859
|
});
|
|
1793
1860
|
}
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1861
|
+
f.slots[f.cursor - 1] = slot;
|
|
1862
|
+
return accessor;
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Declare a reactive operation at this call position. Use plain functions
|
|
1866
|
+
* for ordinary operations; action() adds pending/data/error tracking,
|
|
1867
|
+
* concurrent-invocation bookkeeping, and lifecycle cancellation.
|
|
1868
|
+
*/
|
|
1869
|
+
function action(fn) {
|
|
1870
|
+
const frame = currentFrame();
|
|
1871
|
+
const existing = acquireSlot("action");
|
|
1872
|
+
const f = frame;
|
|
1873
|
+
if (existing) return existing.acc;
|
|
1874
|
+
if (!f.creating) {
|
|
1875
|
+
const noop = (() => {});
|
|
1876
|
+
Object.defineProperties(noop, {
|
|
1877
|
+
pending: { get: () => false },
|
|
1878
|
+
data: { get: () => void 0 },
|
|
1879
|
+
error: { get: () => void 0 }
|
|
1798
1880
|
});
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1881
|
+
attachWith(noop);
|
|
1882
|
+
return noop;
|
|
1883
|
+
}
|
|
1884
|
+
const manifestId = `a${f.actionIndex++}`;
|
|
1885
|
+
if (f.ssr) {
|
|
1886
|
+
const invoke = (..._callArgs) => {};
|
|
1887
|
+
invoke[SSR_ACTION_STUB] = true;
|
|
1888
|
+
invoke[ACTION_MANIFEST_ID] = manifestId;
|
|
1889
|
+
Object.defineProperties(invoke, {
|
|
1890
|
+
pending: {
|
|
1891
|
+
get: () => false,
|
|
1892
|
+
enumerable: true
|
|
1893
|
+
},
|
|
1894
|
+
data: {
|
|
1895
|
+
get: () => void 0,
|
|
1896
|
+
enumerable: true
|
|
1897
|
+
},
|
|
1898
|
+
error: {
|
|
1899
|
+
get: () => void 0,
|
|
1900
|
+
enumerable: true
|
|
1808
1901
|
}
|
|
1809
|
-
source = result;
|
|
1810
|
-
}
|
|
1811
|
-
return new IlhaBuilder({
|
|
1812
|
-
...this._cfg,
|
|
1813
|
-
css: source
|
|
1814
1902
|
});
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1903
|
+
f.actionManifest.set(invoke, manifestId);
|
|
1904
|
+
f.slots[f.cursor - 1] = {
|
|
1905
|
+
kind: "action",
|
|
1906
|
+
acc: invoke,
|
|
1907
|
+
fn
|
|
1908
|
+
};
|
|
1909
|
+
const ssrAccessor = invoke;
|
|
1910
|
+
attachWith(ssrAccessor);
|
|
1911
|
+
return ssrAccessor;
|
|
1912
|
+
}
|
|
1913
|
+
const instance = f.instance;
|
|
1914
|
+
const envelope = signal({
|
|
1915
|
+
pending: 0,
|
|
1916
|
+
data: void 0,
|
|
1917
|
+
error: void 0
|
|
1918
|
+
});
|
|
1919
|
+
const readEnvelope = () => untrack(() => envelope());
|
|
1920
|
+
let latestRun = 0;
|
|
1921
|
+
const invoke = (payload) => {
|
|
1922
|
+
if (currentRenderCtx()) return;
|
|
1923
|
+
if (instance.disposed) return;
|
|
1924
|
+
const run = ++latestRun;
|
|
1925
|
+
const runSignal = instance.unmountController.signal;
|
|
1926
|
+
let result;
|
|
1927
|
+
let isAsync = false;
|
|
1928
|
+
startBatch();
|
|
1929
|
+
try {
|
|
1930
|
+
result = slot.fn(payload, { signal: runSignal });
|
|
1931
|
+
isAsync = result != null && typeof result.then === "function";
|
|
1932
|
+
const current = readEnvelope();
|
|
1933
|
+
envelope(isAsync ? {
|
|
1934
|
+
pending: current.pending + 1,
|
|
1935
|
+
data: current.data,
|
|
1936
|
+
error: void 0
|
|
1937
|
+
} : {
|
|
1938
|
+
pending: current.pending,
|
|
1939
|
+
data: result,
|
|
1940
|
+
error: void 0
|
|
1941
|
+
});
|
|
1942
|
+
} catch (error) {
|
|
1943
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
1944
|
+
envelope({
|
|
1945
|
+
pending: readEnvelope().pending,
|
|
1946
|
+
data: void 0,
|
|
1947
|
+
error: normalized
|
|
1948
|
+
});
|
|
1949
|
+
instance.reportError(normalized, "action");
|
|
1950
|
+
return;
|
|
1951
|
+
} finally {
|
|
1952
|
+
endBatch();
|
|
1855
1953
|
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1954
|
+
if (!isAsync) return;
|
|
1955
|
+
Promise.resolve(result).then((data) => {
|
|
1956
|
+
if (instance.disposed || runSignal.aborted || run !== latestRun) return;
|
|
1957
|
+
const value = readEnvelope();
|
|
1958
|
+
envelope({
|
|
1959
|
+
pending: value.pending,
|
|
1960
|
+
data,
|
|
1961
|
+
error: void 0
|
|
1962
|
+
});
|
|
1963
|
+
}).catch((error) => {
|
|
1964
|
+
if (instance.disposed || runSignal.aborted || error?.name === "AbortError") return;
|
|
1965
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
1966
|
+
if (run === latestRun) {
|
|
1967
|
+
const value = readEnvelope();
|
|
1968
|
+
envelope({
|
|
1969
|
+
pending: value.pending,
|
|
1970
|
+
data: void 0,
|
|
1971
|
+
error: normalized
|
|
1862
1972
|
});
|
|
1863
|
-
state[entry.key] = accessor;
|
|
1864
1973
|
}
|
|
1865
|
-
|
|
1974
|
+
instance.reportError(normalized, "action");
|
|
1975
|
+
}).finally(() => {
|
|
1976
|
+
if (instance.disposed) return;
|
|
1977
|
+
const value = readEnvelope();
|
|
1978
|
+
envelope({
|
|
1979
|
+
...value,
|
|
1980
|
+
pending: Math.max(0, value.pending - 1)
|
|
1981
|
+
});
|
|
1982
|
+
});
|
|
1983
|
+
};
|
|
1984
|
+
const slot = {
|
|
1985
|
+
kind: "action",
|
|
1986
|
+
acc: invoke,
|
|
1987
|
+
fn
|
|
1988
|
+
};
|
|
1989
|
+
Object.defineProperties(invoke, {
|
|
1990
|
+
pending: {
|
|
1991
|
+
get: () => envelope().pending > 0,
|
|
1992
|
+
enumerable: true
|
|
1993
|
+
},
|
|
1994
|
+
data: {
|
|
1995
|
+
get: () => envelope().data,
|
|
1996
|
+
enumerable: true
|
|
1997
|
+
},
|
|
1998
|
+
error: {
|
|
1999
|
+
get: () => envelope().error,
|
|
2000
|
+
enumerable: true
|
|
1866
2001
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
2002
|
+
});
|
|
2003
|
+
f.slots[f.cursor - 1] = slot;
|
|
2004
|
+
instance.actionIds.set(slot.acc, manifestId);
|
|
2005
|
+
const clientAccessor = invoke;
|
|
2006
|
+
clientAccessor[ACTION_MANIFEST_ID] = manifestId;
|
|
2007
|
+
attachWith(clientAccessor);
|
|
2008
|
+
return clientAccessor;
|
|
2009
|
+
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Reactive side effect. Inside an island render, registers an ordered effect
|
|
2012
|
+
* slot that tracks signals read in the body, reruns on change, cleans up
|
|
2013
|
+
* before rerun and on unmount, and is client-only. Outside an island render,
|
|
2014
|
+
* behaves as the standalone reactive effect and returns a stop function.
|
|
2015
|
+
*
|
|
2016
|
+
* @returns Inside an island render, `void` (an effect slot is registered and
|
|
2017
|
+
* cleaned up automatically). At module scope, a stop function that disposes
|
|
2018
|
+
* the effect and runs its final cleanup. The union return type is deliberate:
|
|
2019
|
+
* the same call shape cannot be discriminated at the call site.
|
|
2020
|
+
*/
|
|
2021
|
+
const effect = Object.assign((fn) => {
|
|
2022
|
+
const frame = currentFrame();
|
|
2023
|
+
if (!frame) {
|
|
2024
|
+
let cleanup;
|
|
2025
|
+
let controller = null;
|
|
2026
|
+
const runCleanup = () => {
|
|
2027
|
+
if (typeof cleanup === "function") {
|
|
2028
|
+
try {
|
|
2029
|
+
cleanup();
|
|
2030
|
+
} catch (err) {
|
|
2031
|
+
console.error(err);
|
|
2032
|
+
}
|
|
2033
|
+
cleanup = void 0;
|
|
1877
2034
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
2035
|
+
};
|
|
2036
|
+
const stop = ilhaEffect(() => {
|
|
2037
|
+
runCleanup();
|
|
2038
|
+
if (controller) controller.abort();
|
|
2039
|
+
controller = new AbortController();
|
|
2040
|
+
startBatch();
|
|
2041
|
+
try {
|
|
2042
|
+
cleanup = fn({ signal: controller.signal });
|
|
2043
|
+
} catch (err) {
|
|
2044
|
+
console.error(err);
|
|
2045
|
+
} finally {
|
|
2046
|
+
endBatch();
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
return () => {
|
|
2050
|
+
stop();
|
|
2051
|
+
runCleanup();
|
|
2052
|
+
controller?.abort();
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
const existing = acquireSlot("effect");
|
|
2056
|
+
if (existing) {
|
|
2057
|
+
existing.fn = fn;
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
if (!frame.creating) return;
|
|
2061
|
+
frame.slots[frame.cursor - 1] = {
|
|
2062
|
+
kind: "effect",
|
|
2063
|
+
fn
|
|
2064
|
+
};
|
|
2065
|
+
}, { once(fn) {
|
|
2066
|
+
if (acquireSlot("once")) return;
|
|
2067
|
+
const f = currentFrame();
|
|
2068
|
+
if (!f) throw new Error("[ilha] effect.once() called outside an island render. It registers per-instance setup and is only valid while an island component is rendering.");
|
|
2069
|
+
if (!f.creating) return;
|
|
2070
|
+
f.slots[f.cursor - 1] = {
|
|
2071
|
+
kind: "once",
|
|
2072
|
+
fn
|
|
2073
|
+
};
|
|
2074
|
+
} });
|
|
2075
|
+
/**
|
|
2076
|
+
* Register an error handler slot for this island. Handlers run in
|
|
2077
|
+
* declaration order; state, derived values, actions, and props should be
|
|
2078
|
+
* accessed through lexical closure rather than the context object.
|
|
2079
|
+
*/
|
|
2080
|
+
function onError(fn) {
|
|
2081
|
+
if (!currentFrame()) throw new Error("[ilha] onError() called outside an island render. Register it while an island component is rendering.");
|
|
2082
|
+
const existing = acquireSlot("error");
|
|
2083
|
+
if (existing) {
|
|
2084
|
+
existing.fn = fn;
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
const f = currentFrame();
|
|
2088
|
+
if (!f || !f.creating) return;
|
|
2089
|
+
f.slots[f.cursor - 1] = {
|
|
2090
|
+
kind: "error",
|
|
2091
|
+
fn
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
function parseStateSnapshot(raw) {
|
|
2095
|
+
const parsed = safeParseSnapshot(raw, STATE_ATTR);
|
|
2096
|
+
if (parsed === void 0) return void 0;
|
|
2097
|
+
const rec = parsed;
|
|
2098
|
+
if (rec["v"] !== 2) return;
|
|
2099
|
+
const out = { skipOnce: rec["_skipOnMount"] === true };
|
|
2100
|
+
if (Array.isArray(rec["s"])) {
|
|
2101
|
+
stripUnsafeKeys(rec["s"]);
|
|
2102
|
+
out.s = rec["s"];
|
|
2103
|
+
}
|
|
2104
|
+
if (Array.isArray(rec["d"])) {
|
|
2105
|
+
stripUnsafeKeys(rec["d"]);
|
|
2106
|
+
out.d = rec["d"].map((entry) => {
|
|
2107
|
+
const e = entry ?? {};
|
|
2108
|
+
const errorValue = e.error;
|
|
2109
|
+
return {
|
|
2110
|
+
loading: e.loading === true,
|
|
2111
|
+
value: e.value,
|
|
2112
|
+
error: errorValue == null ? void 0 : errorValue instanceof Error ? errorValue : new Error(String(errorValue))
|
|
2113
|
+
};
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
return out;
|
|
2117
|
+
}
|
|
2118
|
+
const ilha = ((...args) => {
|
|
2119
|
+
let schema = null;
|
|
2120
|
+
let component;
|
|
2121
|
+
let options;
|
|
2122
|
+
const first = args[0];
|
|
2123
|
+
const second = args[1];
|
|
2124
|
+
if (isStandardSchema(first)) {
|
|
2125
|
+
schema = first;
|
|
2126
|
+
component = second;
|
|
2127
|
+
options = args[2];
|
|
2128
|
+
} else {
|
|
2129
|
+
component = first;
|
|
2130
|
+
options = second;
|
|
2131
|
+
}
|
|
2132
|
+
if (typeof component !== "function") throw new Error("[ilha] ilha() requires a component function.");
|
|
2133
|
+
const configuredSlotTag = options?.as === void 0 ? "div" : assertValidSlotTagName(options.as);
|
|
2134
|
+
function resolveInput(props) {
|
|
2135
|
+
const merged = { ...props };
|
|
2136
|
+
if (!schema) return merged;
|
|
2137
|
+
return validateSchema(schema, merged);
|
|
2138
|
+
}
|
|
2139
|
+
function renderWithCtx({ render, liveHost, asyncChildren, manifest }) {
|
|
2140
|
+
const ctx = pushRenderCtx(liveHost, asyncChildren, manifest);
|
|
2141
|
+
try {
|
|
2142
|
+
const html = unwrapHtml(render());
|
|
2143
|
+
const slots = ctx.slots;
|
|
2144
|
+
const binds = ctx.binds;
|
|
2145
|
+
const events = ctx.events;
|
|
2146
|
+
if (ctx.pending && ctx.pending.size > 0) {
|
|
2147
|
+
const pending = ctx.pending;
|
|
2148
|
+
return (async () => {
|
|
2149
|
+
return {
|
|
2150
|
+
html: await resolveAsyncChildren(html, pending),
|
|
2151
|
+
slots,
|
|
2152
|
+
binds,
|
|
2153
|
+
events
|
|
2154
|
+
};
|
|
2155
|
+
})();
|
|
1911
2156
|
}
|
|
1912
2157
|
return {
|
|
1913
|
-
|
|
1914
|
-
|
|
2158
|
+
html,
|
|
2159
|
+
slots,
|
|
2160
|
+
binds,
|
|
2161
|
+
events
|
|
1915
2162
|
};
|
|
2163
|
+
} finally {
|
|
2164
|
+
popRenderCtx();
|
|
1916
2165
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
2166
|
+
}
|
|
2167
|
+
/** Invoke the component inside the given frame + render context. */
|
|
2168
|
+
function runInFrame(frame, props) {
|
|
2169
|
+
return () => withFrame(frame, () => component(props));
|
|
2170
|
+
}
|
|
2171
|
+
function recordEventsManifest(eventsOut, events, frame) {
|
|
2172
|
+
if (!eventsOut || events.length === 0) return;
|
|
2173
|
+
events.forEach((record, index) => {
|
|
2174
|
+
const id = frame.actionManifest?.get(record.handler) ?? actionManifestId(record.handler);
|
|
2175
|
+
if (!id) return;
|
|
2176
|
+
const args = actionBoundArgs(record.handler);
|
|
2177
|
+
eventsOut.set(`${record.type}:${index}`, Array.isArray(args) ? {
|
|
2178
|
+
k: id,
|
|
2179
|
+
a: args
|
|
2180
|
+
} : id);
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
function renderToString(props, sync = false, eventsOut, forceAsyncChildren = false) {
|
|
2184
|
+
const input = resolveInput(props);
|
|
2185
|
+
const frame = freshFrame({ ssr: true });
|
|
2186
|
+
const first = forceAsyncChildren ? renderWithCtx({
|
|
2187
|
+
render: runInFrame(frame, input),
|
|
2188
|
+
asyncChildren: true,
|
|
2189
|
+
manifest: eventsOut !== void 0
|
|
2190
|
+
}) : renderWithCtx({
|
|
2191
|
+
render: runInFrame(frame, input),
|
|
2192
|
+
manifest: eventsOut !== void 0
|
|
2193
|
+
});
|
|
2194
|
+
const finish = (firstHtml) => firstHtml.html;
|
|
2195
|
+
const needsAsync = forceAsyncChildren || (frame.pendingDerived?.length ?? 0) > 0;
|
|
2196
|
+
if (first instanceof Promise) return first.then((out) => {
|
|
2197
|
+
recordEventsManifest(eventsOut, out.events, frame);
|
|
2198
|
+
return needsAsync ? awaitPending(out) : out.html;
|
|
2199
|
+
});
|
|
2200
|
+
recordEventsManifest(eventsOut, first.events, frame);
|
|
2201
|
+
if (!needsAsync || sync) {
|
|
2202
|
+
if (sync && (frame.pendingDerived?.length ?? 0) > 0 && __DEV__);
|
|
2203
|
+
return finish(first);
|
|
2204
|
+
}
|
|
2205
|
+
return awaitPending(first);
|
|
2206
|
+
function awaitPending(out) {
|
|
2207
|
+
const pendings = frame.pendingDerived ?? [];
|
|
2208
|
+
if (pendings.length === 0) return Promise.resolve(out.html);
|
|
2209
|
+
return Promise.all(pendings.map((p) => p.promise)).then(() => {
|
|
2210
|
+
frame.creating = false;
|
|
2211
|
+
frame.cursor = 0;
|
|
2212
|
+
const second = forceAsyncChildren ? renderWithCtx({
|
|
2213
|
+
render: runInFrame(frame, input),
|
|
2214
|
+
asyncChildren: true,
|
|
2215
|
+
manifest: eventsOut !== void 0
|
|
2216
|
+
}) : renderWithCtx({
|
|
2217
|
+
render: runInFrame(frame, input),
|
|
2218
|
+
manifest: eventsOut !== void 0
|
|
1930
2219
|
});
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2220
|
+
const done = (o) => {
|
|
2221
|
+
recordEventsManifest(eventsOut, o.events, frame);
|
|
2222
|
+
return o.html;
|
|
2223
|
+
};
|
|
2224
|
+
return second instanceof Promise ? second.then(done) : done(second);
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
function mountIsland(host, props) {
|
|
2229
|
+
return mountIslandInternal(host, props).unmount;
|
|
2230
|
+
}
|
|
2231
|
+
function mountIslandInternal(host, props) {
|
|
2232
|
+
if (props === void 0) {
|
|
2233
|
+
const rawProps = host.getAttribute(PROPS_ATTR);
|
|
2234
|
+
if (rawProps) {
|
|
2235
|
+
const parsed = safeParseSnapshot(rawProps, PROPS_ATTR);
|
|
2236
|
+
if (parsed !== void 0) props = reviveSlotProps(parsed);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
const inputSignal = signal(resolveInput(props));
|
|
2240
|
+
const derivedPropsVersion = signal(0);
|
|
2241
|
+
const input = new Proxy({}, {
|
|
2242
|
+
get(_t, key) {
|
|
2243
|
+
return inputSignal()[key];
|
|
2244
|
+
},
|
|
2245
|
+
has(_t, key) {
|
|
2246
|
+
return key in inputSignal();
|
|
2247
|
+
},
|
|
2248
|
+
ownKeys() {
|
|
1956
2249
|
const prevSub = setActiveSub(void 0);
|
|
1957
2250
|
try {
|
|
1958
|
-
return
|
|
1959
|
-
key: entry.key,
|
|
1960
|
-
result: entry.fn({
|
|
1961
|
-
state,
|
|
1962
|
-
input,
|
|
1963
|
-
signal: new AbortController().signal
|
|
1964
|
-
})
|
|
1965
|
-
};
|
|
1966
|
-
} catch (err) {
|
|
1967
|
-
return {
|
|
1968
|
-
key: entry.key,
|
|
1969
|
-
result: Promise.reject(err)
|
|
1970
|
-
};
|
|
2251
|
+
return Reflect.ownKeys(inputSignal());
|
|
1971
2252
|
} finally {
|
|
1972
2253
|
setActiveSub(prevSub);
|
|
1973
2254
|
}
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
for (const r of results) if (r.result instanceof Promise) envelopes[r.key] = {
|
|
1982
|
-
loading: true,
|
|
1983
|
-
value: void 0,
|
|
1984
|
-
error: void 0
|
|
1985
|
-
};
|
|
1986
|
-
else envelopes[r.key] = {
|
|
1987
|
-
loading: false,
|
|
1988
|
-
value: r.result,
|
|
1989
|
-
error: void 0
|
|
1990
|
-
};
|
|
1991
|
-
const derived = buildDerivedAccessors(envelopes);
|
|
1992
|
-
const prevSub = setActiveSub(void 0);
|
|
1993
|
-
try {
|
|
1994
|
-
const { html, events } = renderWithCtx({
|
|
1995
|
-
render: () => fn({
|
|
1996
|
-
state,
|
|
1997
|
-
derived,
|
|
1998
|
-
action,
|
|
1999
|
-
input
|
|
2000
|
-
}),
|
|
2001
|
-
manifest: eventsOut !== void 0
|
|
2002
|
-
});
|
|
2003
|
-
recordEventsManifest(events);
|
|
2004
|
-
return stylePrefix + html;
|
|
2005
|
-
} finally {
|
|
2006
|
-
setActiveSub(prevSub);
|
|
2007
|
-
}
|
|
2255
|
+
},
|
|
2256
|
+
getOwnPropertyDescriptor(_t, key) {
|
|
2257
|
+
const prevSub = setActiveSub(void 0);
|
|
2258
|
+
try {
|
|
2259
|
+
return Reflect.getOwnPropertyDescriptor(inputSignal(), key);
|
|
2260
|
+
} finally {
|
|
2261
|
+
setActiveSub(prevSub);
|
|
2008
2262
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
const envelopes = {};
|
|
2031
|
-
for (const r of resolved) envelopes[r.key] = r.envelope;
|
|
2032
|
-
const derived = buildDerivedAccessors(envelopes);
|
|
2033
|
-
const prevSub = setActiveSub(void 0);
|
|
2034
|
-
try {
|
|
2035
|
-
const { html, events } = await renderWithCtx({
|
|
2036
|
-
render: () => fn({
|
|
2037
|
-
state,
|
|
2038
|
-
derived,
|
|
2039
|
-
action,
|
|
2040
|
-
input
|
|
2041
|
-
}),
|
|
2042
|
-
asyncChildren: true,
|
|
2043
|
-
manifest: eventsOut !== void 0
|
|
2044
|
-
});
|
|
2045
|
-
recordEventsManifest(events);
|
|
2046
|
-
return stylePrefix + html;
|
|
2047
|
-
} finally {
|
|
2048
|
-
setActiveSub(prevSub);
|
|
2049
|
-
}
|
|
2263
|
+
}
|
|
2264
|
+
});
|
|
2265
|
+
const snapshotRaw = host.getAttribute(STATE_ATTR);
|
|
2266
|
+
const snapshot = snapshotRaw ? parseStateSnapshot(snapshotRaw) : void 0;
|
|
2267
|
+
const hydrated = snapshot != null;
|
|
2268
|
+
const shouldSkipOnce = snapshot?.skipOnce === true;
|
|
2269
|
+
const cleanups = [];
|
|
2270
|
+
const unmountController = new AbortController();
|
|
2271
|
+
cleanups.push(() => unmountController.abort());
|
|
2272
|
+
const errorSlots = [];
|
|
2273
|
+
const reportError = (err, source) => {
|
|
2274
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
2275
|
+
if (errorSlots.length === 0) {
|
|
2276
|
+
if (!reportToGlobal(error, source)) console.error(error);
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
for (const slot of errorSlots) try {
|
|
2280
|
+
slot.fn({
|
|
2281
|
+
error,
|
|
2282
|
+
source,
|
|
2283
|
+
host
|
|
2050
2284
|
});
|
|
2285
|
+
} catch (handlerErr) {
|
|
2286
|
+
console.error(handlerErr);
|
|
2051
2287
|
}
|
|
2288
|
+
};
|
|
2289
|
+
const instance = {
|
|
2290
|
+
host,
|
|
2291
|
+
inputSignal,
|
|
2292
|
+
input,
|
|
2293
|
+
unmountController,
|
|
2294
|
+
cleanups,
|
|
2295
|
+
reportError,
|
|
2296
|
+
actionIds: /* @__PURE__ */ new Map(),
|
|
2297
|
+
stateSnapshot: snapshot ? {
|
|
2298
|
+
s: snapshot.s,
|
|
2299
|
+
d: snapshot.d
|
|
2300
|
+
} : void 0,
|
|
2301
|
+
hydrated,
|
|
2302
|
+
disposed: false,
|
|
2303
|
+
frame: null
|
|
2304
|
+
};
|
|
2305
|
+
cleanups.push(() => {
|
|
2306
|
+
instance.disposed = true;
|
|
2307
|
+
});
|
|
2308
|
+
const frame = freshFrame({
|
|
2309
|
+
ssr: false,
|
|
2310
|
+
instance
|
|
2311
|
+
});
|
|
2312
|
+
instance.frame = frame;
|
|
2313
|
+
const mountedSlots = /* @__PURE__ */ new Map();
|
|
2314
|
+
function teardownMountedSlot(id, entry) {
|
|
2315
|
+
mountedSlots.delete(id);
|
|
2316
|
+
entry.el.remove();
|
|
2317
|
+
entry.unmount();
|
|
2052
2318
|
}
|
|
2053
|
-
function
|
|
2054
|
-
|
|
2319
|
+
function slotBelongsToHost(candidate) {
|
|
2320
|
+
let el = candidate.parentElement;
|
|
2321
|
+
while (el && el !== host) {
|
|
2322
|
+
if (el.hasAttribute(SLOT_ATTR) || el.hasAttribute("data-ilha")) return false;
|
|
2323
|
+
el = el.parentElement;
|
|
2324
|
+
}
|
|
2325
|
+
return el === host;
|
|
2055
2326
|
}
|
|
2056
|
-
function
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
}
|
|
2327
|
+
function buildSlotIndex() {
|
|
2328
|
+
const index = /* @__PURE__ */ new Map();
|
|
2329
|
+
for (const candidate of host.querySelectorAll(`[${SLOT_ATTR}]`)) {
|
|
2330
|
+
const id = candidate.getAttribute(SLOT_ATTR);
|
|
2331
|
+
if (id === null || index.has(id)) continue;
|
|
2332
|
+
if (slotBelongsToHost(candidate)) index.set(id, candidate);
|
|
2063
2333
|
}
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
return key in inputSignal();
|
|
2071
|
-
},
|
|
2072
|
-
ownKeys() {
|
|
2073
|
-
const prevSub = setActiveSub(void 0);
|
|
2074
|
-
try {
|
|
2075
|
-
return Reflect.ownKeys(inputSignal());
|
|
2076
|
-
} finally {
|
|
2077
|
-
setActiveSub(prevSub);
|
|
2078
|
-
}
|
|
2079
|
-
},
|
|
2080
|
-
getOwnPropertyDescriptor(_t, key) {
|
|
2081
|
-
const prevSub = setActiveSub(void 0);
|
|
2082
|
-
try {
|
|
2083
|
-
return Reflect.getOwnPropertyDescriptor(inputSignal(), key);
|
|
2084
|
-
} finally {
|
|
2085
|
-
setActiveSub(prevSub);
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
|
-
});
|
|
2089
|
-
let snapshotRaw;
|
|
2090
|
-
const rawState = host.getAttribute(STATE_ATTR);
|
|
2091
|
-
if (rawState) {
|
|
2092
|
-
const parsed = safeParseSnapshot(rawState, STATE_ATTR);
|
|
2093
|
-
if (parsed !== void 0) snapshotRaw = parsed;
|
|
2334
|
+
return index;
|
|
2335
|
+
}
|
|
2336
|
+
function pushUpdatedProps(nextSlots) {
|
|
2337
|
+
for (const [id, entry] of mountedSlots) {
|
|
2338
|
+
const next = nextSlots.get(id);
|
|
2339
|
+
if (next) entry.updateProps(next.props);
|
|
2094
2340
|
}
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
...v,
|
|
2102
|
-
error: new Error(String(v.error))
|
|
2103
|
-
};
|
|
2104
|
-
else derivedSnapshot[k] = v;
|
|
2341
|
+
}
|
|
2342
|
+
function mountSlots(slotMap) {
|
|
2343
|
+
for (const [id, entry] of mountedSlots) if (!slotMap.has(id)) teardownMountedSlot(id, entry);
|
|
2344
|
+
for (const [id, entry] of mountedSlots) {
|
|
2345
|
+
const next = slotMap.get(id);
|
|
2346
|
+
if (next && next.island !== entry.island) teardownMountedSlot(id, entry);
|
|
2105
2347
|
}
|
|
2106
|
-
|
|
2107
|
-
const
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
cleanups.push(() => unmountController.abort());
|
|
2113
|
-
const { proxy: derived, setup: setupDerived } = createDerivedProxy({
|
|
2114
|
-
entries: deriveds,
|
|
2115
|
-
state,
|
|
2116
|
-
input,
|
|
2117
|
-
derivedSnapshot
|
|
2118
|
-
});
|
|
2119
|
-
let stopDerived = () => {};
|
|
2120
|
-
let action = {};
|
|
2121
|
-
function reportError(err, source) {
|
|
2122
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
2123
|
-
if (onErrors.length === 0) {
|
|
2124
|
-
if (!reportToGlobal(error, source)) console.error(error);
|
|
2125
|
-
return;
|
|
2126
|
-
}
|
|
2127
|
-
for (const entry of onErrors) try {
|
|
2128
|
-
entry.fn({
|
|
2129
|
-
error,
|
|
2130
|
-
source,
|
|
2131
|
-
state,
|
|
2132
|
-
derived,
|
|
2133
|
-
action,
|
|
2134
|
-
input,
|
|
2135
|
-
host
|
|
2136
|
-
});
|
|
2137
|
-
} catch (handlerErr) {
|
|
2138
|
-
console.error(handlerErr);
|
|
2348
|
+
let slotIndex = null;
|
|
2349
|
+
for (const [id, { island: childIsland, props: childProps }] of slotMap) {
|
|
2350
|
+
const existing = mountedSlots.get(id);
|
|
2351
|
+
if (existing) {
|
|
2352
|
+
existing.updateProps(childProps);
|
|
2353
|
+
continue;
|
|
2139
2354
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
pending: 0,
|
|
2150
|
-
data: void 0,
|
|
2151
|
-
error: void 0
|
|
2152
|
-
});
|
|
2153
|
-
const readEnvelope = () => untrack(() => envelope());
|
|
2154
|
-
let latestRun = 0;
|
|
2155
|
-
const invoke = (props) => {
|
|
2156
|
-
if (currentRenderCtx()) return;
|
|
2157
|
-
if (disposed) return;
|
|
2158
|
-
const run = ++latestRun;
|
|
2159
|
-
const runSignal = unmountController.signal;
|
|
2160
|
-
const ctx = {
|
|
2161
|
-
state,
|
|
2162
|
-
derived,
|
|
2163
|
-
input,
|
|
2164
|
-
host,
|
|
2165
|
-
signal: runSignal
|
|
2166
|
-
};
|
|
2167
|
-
let result;
|
|
2168
|
-
let isAsync = false;
|
|
2169
|
-
startBatch();
|
|
2170
|
-
try {
|
|
2171
|
-
result = entry.fn(props, ctx);
|
|
2172
|
-
isAsync = result != null && typeof result.then === "function";
|
|
2173
|
-
const current = readEnvelope();
|
|
2174
|
-
envelope(isAsync ? {
|
|
2175
|
-
pending: current.pending + 1,
|
|
2176
|
-
data: current.data,
|
|
2177
|
-
error: void 0
|
|
2178
|
-
} : {
|
|
2179
|
-
pending: current.pending,
|
|
2180
|
-
data: result,
|
|
2181
|
-
error: void 0
|
|
2182
|
-
});
|
|
2183
|
-
} catch (error) {
|
|
2184
|
-
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
2185
|
-
envelope({
|
|
2186
|
-
pending: readEnvelope().pending,
|
|
2187
|
-
data: void 0,
|
|
2188
|
-
error: normalized
|
|
2189
|
-
});
|
|
2190
|
-
reportError(normalized, "action");
|
|
2191
|
-
return;
|
|
2192
|
-
} finally {
|
|
2193
|
-
endBatch();
|
|
2194
|
-
}
|
|
2195
|
-
if (!isAsync) return;
|
|
2196
|
-
Promise.resolve(result).then((data) => {
|
|
2197
|
-
if (disposed || runSignal.aborted || run !== latestRun) return;
|
|
2198
|
-
const value = readEnvelope();
|
|
2199
|
-
envelope({
|
|
2200
|
-
pending: value.pending,
|
|
2201
|
-
data,
|
|
2202
|
-
error: void 0
|
|
2203
|
-
});
|
|
2204
|
-
}).catch((error) => {
|
|
2205
|
-
if (disposed || runSignal.aborted || error?.name === "AbortError") return;
|
|
2206
|
-
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
2207
|
-
if (run === latestRun) {
|
|
2208
|
-
const value = readEnvelope();
|
|
2209
|
-
envelope({
|
|
2210
|
-
pending: value.pending,
|
|
2211
|
-
data: void 0,
|
|
2212
|
-
error: normalized
|
|
2213
|
-
});
|
|
2214
|
-
}
|
|
2215
|
-
reportError(normalized, "action");
|
|
2216
|
-
}).finally(() => {
|
|
2217
|
-
if (disposed) return;
|
|
2218
|
-
const value = readEnvelope();
|
|
2219
|
-
envelope({
|
|
2220
|
-
...value,
|
|
2221
|
-
pending: Math.max(0, value.pending - 1)
|
|
2222
|
-
});
|
|
2223
|
-
});
|
|
2224
|
-
};
|
|
2225
|
-
Object.defineProperties(invoke, {
|
|
2226
|
-
pending: {
|
|
2227
|
-
get: () => envelope().pending > 0,
|
|
2228
|
-
enumerable: true
|
|
2229
|
-
},
|
|
2230
|
-
data: {
|
|
2231
|
-
get: () => envelope().data,
|
|
2232
|
-
enumerable: true
|
|
2233
|
-
},
|
|
2234
|
-
error: {
|
|
2235
|
-
get: () => envelope().error,
|
|
2236
|
-
enumerable: true
|
|
2355
|
+
if (slotIndex === null) slotIndex = buildSlotIndex();
|
|
2356
|
+
const slotEl = slotIndex.get(id) ?? null;
|
|
2357
|
+
if (!slotEl) continue;
|
|
2358
|
+
let slotProps = childProps;
|
|
2359
|
+
if (slotProps === void 0) {
|
|
2360
|
+
const rawProps = slotEl.getAttribute(PROPS_ATTR) ?? slotEl.getAttribute("data-props");
|
|
2361
|
+
if (rawProps) {
|
|
2362
|
+
const parsed = safeParseSnapshot(rawProps, `props on [${SLOT_ATTR}="${id}"]`);
|
|
2363
|
+
if (parsed !== void 0) slotProps = reviveSlotProps(parsed);
|
|
2237
2364
|
}
|
|
2238
|
-
});
|
|
2239
|
-
actionObject[entry.key] = invoke;
|
|
2240
|
-
}
|
|
2241
|
-
action = actionObject;
|
|
2242
|
-
if (transition?.enter) try {
|
|
2243
|
-
const result = transition.enter(host);
|
|
2244
|
-
if (result instanceof Promise) result.catch((err) => reportError(err, "transition"));
|
|
2245
|
-
} catch (err) {
|
|
2246
|
-
reportError(err, "transition");
|
|
2247
|
-
}
|
|
2248
|
-
const mountedSlots = /* @__PURE__ */ new Map();
|
|
2249
|
-
const leavingSlots = /* @__PURE__ */ new Map();
|
|
2250
|
-
function teardownMountedSlot(id, entry) {
|
|
2251
|
-
mountedSlots.delete(id);
|
|
2252
|
-
const token = Symbol();
|
|
2253
|
-
const result = entry.unmount();
|
|
2254
|
-
const remove = (stub) => {
|
|
2255
|
-
const leaving = leavingSlots.get(id);
|
|
2256
|
-
if (!leaving || leaving.token !== token) return;
|
|
2257
|
-
leaving.el.remove();
|
|
2258
|
-
stub?.remove();
|
|
2259
|
-
leavingSlots.delete(id);
|
|
2260
|
-
};
|
|
2261
|
-
if (result instanceof Promise) {
|
|
2262
|
-
entry.el.removeAttribute(SLOT_ATTR);
|
|
2263
|
-
entry.el.setAttribute(LEAVING_ATTR, "");
|
|
2264
|
-
leavingSlots.set(id, {
|
|
2265
|
-
el: entry.el,
|
|
2266
|
-
token
|
|
2267
|
-
});
|
|
2268
|
-
return result.finally(() => remove());
|
|
2269
|
-
}
|
|
2270
|
-
entry.el.remove();
|
|
2271
|
-
}
|
|
2272
|
-
function slotBelongsToHost(candidate) {
|
|
2273
|
-
let el = candidate.parentElement;
|
|
2274
|
-
while (el && el !== host) {
|
|
2275
|
-
if (el.hasAttribute(SLOT_ATTR) || el.hasAttribute("data-ilha")) return false;
|
|
2276
|
-
el = el.parentElement;
|
|
2277
|
-
}
|
|
2278
|
-
return el === host;
|
|
2279
|
-
}
|
|
2280
|
-
function buildSlotIndex() {
|
|
2281
|
-
const leavingEls = new Set([...leavingSlots.values()].map((l) => l.el));
|
|
2282
|
-
const index = /* @__PURE__ */ new Map();
|
|
2283
|
-
for (const candidate of host.querySelectorAll(`[${SLOT_ATTR}]`)) {
|
|
2284
|
-
if (leavingEls.has(candidate)) continue;
|
|
2285
|
-
const id = candidate.getAttribute(SLOT_ATTR);
|
|
2286
|
-
if (id === null || index.has(id)) continue;
|
|
2287
|
-
if (slotBelongsToHost(candidate)) index.set(id, candidate);
|
|
2288
2365
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2366
|
+
const prevSub = setActiveSub(void 0);
|
|
2367
|
+
let handle;
|
|
2368
|
+
try {
|
|
2369
|
+
const internal = childIsland[ISLAND_MOUNT_INTERNAL];
|
|
2370
|
+
handle = internal ? internal(slotEl, slotProps) : {
|
|
2371
|
+
unmount: childIsland.mount(slotEl, slotProps),
|
|
2372
|
+
updateProps: () => {}
|
|
2373
|
+
};
|
|
2374
|
+
} finally {
|
|
2375
|
+
setActiveSub(prevSub);
|
|
2296
2376
|
}
|
|
2377
|
+
mountedSlots.set(id, {
|
|
2378
|
+
el: slotEl,
|
|
2379
|
+
island: childIsland,
|
|
2380
|
+
...handle
|
|
2381
|
+
});
|
|
2297
2382
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
};
|
|
2330
|
-
} finally {
|
|
2331
|
-
setActiveSub(prevSub);
|
|
2383
|
+
}
|
|
2384
|
+
const nativeOnceFired = /* @__PURE__ */ new WeakMap();
|
|
2385
|
+
let stopJsxEvents = () => {};
|
|
2386
|
+
let stopBindings = () => {};
|
|
2387
|
+
/** Register derived scopes before the render effect so source updates
|
|
2388
|
+
* settle derived envelopes before a render can consume them. */
|
|
2389
|
+
function startDerivedScopes() {
|
|
2390
|
+
for (const slot of frame.slots) if (slot.kind === "derived") startDerivedScope(slot);
|
|
2391
|
+
}
|
|
2392
|
+
/** Register side-effecting scopes after the render effect so prop-change
|
|
2393
|
+
* renders refresh their closures before those scopes execute. */
|
|
2394
|
+
function startEffectScopes() {
|
|
2395
|
+
for (const slot of frame.slots) if (slot.kind === "effect") startEffectScope(slot);
|
|
2396
|
+
else if (slot.kind === "once") startOnceScope(slot);
|
|
2397
|
+
else if (slot.kind === "error") errorSlots.push(slot);
|
|
2398
|
+
}
|
|
2399
|
+
function startDerivedScope(slot) {
|
|
2400
|
+
let ac = new AbortController();
|
|
2401
|
+
let skipFirst = slot.fromSnapshot;
|
|
2402
|
+
const stopScope = effect$1(() => {
|
|
2403
|
+
ac.abort();
|
|
2404
|
+
ac = new AbortController();
|
|
2405
|
+
const currentAc = ac;
|
|
2406
|
+
derivedPropsVersion();
|
|
2407
|
+
let result;
|
|
2408
|
+
try {
|
|
2409
|
+
result = slot.fn({ signal: currentAc.signal });
|
|
2410
|
+
} catch (err) {
|
|
2411
|
+
if (skipFirst) {
|
|
2412
|
+
skipFirst = false;
|
|
2413
|
+
return;
|
|
2332
2414
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2415
|
+
writeEnvelope(currentAc, {
|
|
2416
|
+
loading: false,
|
|
2417
|
+
value: void 0,
|
|
2418
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
2337
2419
|
});
|
|
2420
|
+
return;
|
|
2338
2421
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
function attachListeners() {
|
|
2344
|
-
for (const entry of ons) {
|
|
2345
|
-
if (entry.options.once && firedOnce.has(entry)) continue;
|
|
2346
|
-
let targets;
|
|
2347
|
-
try {
|
|
2348
|
-
targets = entry.selector === "" ? [host] : Array.from(host.querySelectorAll(entry.selector)).filter((element) => templateElementBelongsToHost(host, element));
|
|
2349
|
-
} catch {
|
|
2350
|
-
continue;
|
|
2351
|
-
}
|
|
2352
|
-
targets.forEach((listenerTarget) => {
|
|
2353
|
-
const listener = (event) => {
|
|
2354
|
-
if (entry.options.once) {
|
|
2355
|
-
firedOnce.add(entry);
|
|
2356
|
-
for (const l of listeners.filter((l) => l.entry === entry)) l.target.removeEventListener(l.type, l.fn, l.options);
|
|
2357
|
-
listeners.splice(0, listeners.length, ...listeners.filter((l) => l.entry !== entry));
|
|
2358
|
-
}
|
|
2359
|
-
const eventTarget = event.target instanceof Element ? event.target : listenerTarget;
|
|
2360
|
-
const handlerTarget = entry.selector === "" ? eventTarget : listenerTarget;
|
|
2361
|
-
let handlerSignal;
|
|
2362
|
-
if (entry.abortable) {
|
|
2363
|
-
let entryMap = invocationControllers.get(entry);
|
|
2364
|
-
if (!entryMap) {
|
|
2365
|
-
entryMap = /* @__PURE__ */ new WeakMap();
|
|
2366
|
-
invocationControllers.set(entry, entryMap);
|
|
2367
|
-
}
|
|
2368
|
-
const prev = entryMap.get(listenerTarget);
|
|
2369
|
-
if (prev) prev.abort();
|
|
2370
|
-
const invocationController = new AbortController();
|
|
2371
|
-
entryMap.set(listenerTarget, invocationController);
|
|
2372
|
-
handlerSignal = anySignal([unmountController.signal, invocationController.signal]);
|
|
2373
|
-
} else handlerSignal = unmountController.signal;
|
|
2374
|
-
let result;
|
|
2375
|
-
startBatch();
|
|
2376
|
-
try {
|
|
2377
|
-
result = entry.handler({
|
|
2378
|
-
state,
|
|
2379
|
-
derived,
|
|
2380
|
-
action,
|
|
2381
|
-
input,
|
|
2382
|
-
host,
|
|
2383
|
-
target: handlerTarget,
|
|
2384
|
-
event,
|
|
2385
|
-
signal: handlerSignal
|
|
2386
|
-
});
|
|
2387
|
-
} catch (err) {
|
|
2388
|
-
reportError(err, "on");
|
|
2389
|
-
endBatch();
|
|
2390
|
-
return;
|
|
2391
|
-
}
|
|
2392
|
-
endBatch();
|
|
2393
|
-
if (result instanceof Promise) result.catch((err) => {
|
|
2394
|
-
if (err && err.name === "AbortError") return;
|
|
2395
|
-
reportError(err, "on");
|
|
2396
|
-
});
|
|
2397
|
-
};
|
|
2398
|
-
const opts = {
|
|
2399
|
-
...entry.options,
|
|
2400
|
-
once: false
|
|
2401
|
-
};
|
|
2402
|
-
listenerTarget.addEventListener(entry.event, listener, opts);
|
|
2403
|
-
listeners.push({
|
|
2404
|
-
target: listenerTarget,
|
|
2405
|
-
type: entry.event,
|
|
2406
|
-
fn: listener,
|
|
2407
|
-
options: opts,
|
|
2408
|
-
entry
|
|
2409
|
-
});
|
|
2410
|
-
});
|
|
2422
|
+
if (skipFirst) {
|
|
2423
|
+
skipFirst = false;
|
|
2424
|
+
if (result instanceof Promise) result.catch(() => {});
|
|
2425
|
+
return;
|
|
2411
2426
|
}
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
const prevSub = setActiveSub(void 0);
|
|
2420
|
-
let userCleanup;
|
|
2421
|
-
try {
|
|
2422
|
-
userCleanup = entry.fn({
|
|
2423
|
-
state,
|
|
2424
|
-
derived,
|
|
2425
|
-
action,
|
|
2426
|
-
input,
|
|
2427
|
-
host,
|
|
2428
|
-
hydrated,
|
|
2429
|
-
signal: unmountController.signal
|
|
2427
|
+
if (!(result instanceof Promise)) {
|
|
2428
|
+
if (isAsyncIterable(result)) {
|
|
2429
|
+
const iterable = result;
|
|
2430
|
+
writeEnvelope(currentAc, {
|
|
2431
|
+
loading: true,
|
|
2432
|
+
value: readEnv().value,
|
|
2433
|
+
error: void 0
|
|
2430
2434
|
});
|
|
2431
|
-
|
|
2432
|
-
reportError(err, "mount");
|
|
2433
|
-
} finally {
|
|
2434
|
-
setActiveSub(prevSub);
|
|
2435
|
-
}
|
|
2436
|
-
if (userCleanup) {
|
|
2437
|
-
const teardown = userCleanup;
|
|
2438
|
-
cleanups.push(() => {
|
|
2435
|
+
(async () => {
|
|
2439
2436
|
try {
|
|
2440
|
-
|
|
2437
|
+
for await (const value of iterable) if (writeEnvelope(currentAc, {
|
|
2438
|
+
loading: false,
|
|
2439
|
+
value,
|
|
2440
|
+
error: void 0
|
|
2441
|
+
})) break;
|
|
2441
2442
|
} catch (err) {
|
|
2442
|
-
|
|
2443
|
+
writeEnvelope(currentAc, {
|
|
2444
|
+
loading: false,
|
|
2445
|
+
value: void 0,
|
|
2446
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
2447
|
+
});
|
|
2443
2448
|
}
|
|
2444
|
-
});
|
|
2449
|
+
})();
|
|
2450
|
+
return;
|
|
2445
2451
|
}
|
|
2452
|
+
writeEnvelope(currentAc, {
|
|
2453
|
+
loading: false,
|
|
2454
|
+
value: result,
|
|
2455
|
+
error: void 0
|
|
2456
|
+
});
|
|
2457
|
+
return;
|
|
2446
2458
|
}
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
+
writeEnvelope(currentAc, {
|
|
2460
|
+
loading: true,
|
|
2461
|
+
value: readEnv().value,
|
|
2462
|
+
error: void 0
|
|
2463
|
+
});
|
|
2464
|
+
result.then((value) => {
|
|
2465
|
+
writeEnvelope(currentAc, {
|
|
2466
|
+
loading: false,
|
|
2467
|
+
value,
|
|
2468
|
+
error: void 0
|
|
2469
|
+
});
|
|
2470
|
+
}).catch((err) => {
|
|
2471
|
+
writeEnvelope(currentAc, {
|
|
2472
|
+
loading: false,
|
|
2473
|
+
value: void 0,
|
|
2474
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
2475
|
+
});
|
|
2476
|
+
});
|
|
2459
2477
|
});
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
cleanups.push(() => stopBindings());
|
|
2464
|
-
let stopJsxEvents = applyJsxEvents({
|
|
2465
|
-
host,
|
|
2466
|
-
records: initial.events,
|
|
2467
|
-
reportError: (error) => reportError(error, "on"),
|
|
2468
|
-
unmountSignal: unmountController.signal,
|
|
2469
|
-
onceFired: nativeOnceFired
|
|
2478
|
+
cleanups.push(() => {
|
|
2479
|
+
stopScope();
|
|
2480
|
+
ac.abort();
|
|
2470
2481
|
});
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
const runSignal = anySignal([unmountController.signal, runController.signal]);
|
|
2496
|
-
startBatch();
|
|
2482
|
+
function readEnv() {
|
|
2483
|
+
const prevSub = setActiveSub(void 0);
|
|
2484
|
+
try {
|
|
2485
|
+
return slot.env();
|
|
2486
|
+
} finally {
|
|
2487
|
+
setActiveSub(prevSub);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
function writeEnvelope(ac, patch) {
|
|
2491
|
+
if (ac.signal.aborted) return true;
|
|
2492
|
+
const prevSub = setActiveSub(void 0);
|
|
2493
|
+
try {
|
|
2494
|
+
slot.env(patch);
|
|
2495
|
+
} finally {
|
|
2496
|
+
setActiveSub(prevSub);
|
|
2497
|
+
}
|
|
2498
|
+
return false;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
function startEffectScope(slot) {
|
|
2502
|
+
let userCleanup;
|
|
2503
|
+
let runController = null;
|
|
2504
|
+
const stopScope = effect$1(() => {
|
|
2505
|
+
if (userCleanup) {
|
|
2497
2506
|
try {
|
|
2498
|
-
userCleanup = entry.fn({
|
|
2499
|
-
state,
|
|
2500
|
-
derived,
|
|
2501
|
-
action,
|
|
2502
|
-
input,
|
|
2503
|
-
host,
|
|
2504
|
-
signal: runSignal
|
|
2505
|
-
});
|
|
2506
|
-
} catch (err) {
|
|
2507
|
-
reportError(err, "effect");
|
|
2508
|
-
} finally {
|
|
2509
|
-
endBatch();
|
|
2510
|
-
}
|
|
2511
|
-
});
|
|
2512
|
-
cleanups.push(() => {
|
|
2513
|
-
stopEffect();
|
|
2514
|
-
if (userCleanup) try {
|
|
2515
2507
|
userCleanup();
|
|
2516
2508
|
} catch (err) {
|
|
2517
2509
|
reportError(err, "effect");
|
|
2518
2510
|
}
|
|
2519
|
-
|
|
2511
|
+
userCleanup = void 0;
|
|
2512
|
+
}
|
|
2513
|
+
if (runController) runController.abort();
|
|
2514
|
+
runController = new AbortController();
|
|
2515
|
+
const runSignal = AbortSignal.any([unmountController.signal, runController.signal]);
|
|
2516
|
+
inputSignal();
|
|
2517
|
+
startBatch();
|
|
2518
|
+
try {
|
|
2519
|
+
userCleanup = slot.fn({ signal: runSignal });
|
|
2520
|
+
} catch (err) {
|
|
2521
|
+
reportError(err, "effect");
|
|
2522
|
+
} finally {
|
|
2523
|
+
endBatch();
|
|
2524
|
+
}
|
|
2525
|
+
});
|
|
2526
|
+
cleanups.push(() => {
|
|
2527
|
+
stopScope();
|
|
2528
|
+
if (userCleanup) try {
|
|
2529
|
+
userCleanup();
|
|
2530
|
+
} catch (err) {
|
|
2531
|
+
reportError(err, "effect");
|
|
2532
|
+
}
|
|
2533
|
+
if (runController) runController.abort();
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
2536
|
+
function startOnceScope(slot) {
|
|
2537
|
+
if (shouldSkipOnce) return;
|
|
2538
|
+
const prevSub = setActiveSub(void 0);
|
|
2539
|
+
let userCleanup;
|
|
2540
|
+
try {
|
|
2541
|
+
userCleanup = slot.fn({
|
|
2542
|
+
host,
|
|
2543
|
+
hydrated,
|
|
2544
|
+
signal: unmountController.signal
|
|
2520
2545
|
});
|
|
2546
|
+
} catch (err) {
|
|
2547
|
+
reportError(err, "once");
|
|
2548
|
+
} finally {
|
|
2549
|
+
setActiveSub(prevSub);
|
|
2521
2550
|
}
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
let streamAborted = false;
|
|
2526
|
-
const streamController = new AbortController();
|
|
2527
|
-
const runStream = async () => {
|
|
2551
|
+
if (userCleanup) {
|
|
2552
|
+
const teardown = userCleanup;
|
|
2553
|
+
cleanups.push(() => {
|
|
2528
2554
|
try {
|
|
2529
|
-
|
|
2530
|
-
input,
|
|
2531
|
-
signal: streamController.signal
|
|
2532
|
-
});
|
|
2533
|
-
while (!streamAborted && !streamController.signal.aborted) {
|
|
2534
|
-
const { done, value } = await gen.next();
|
|
2535
|
-
if (streamAborted || streamController.signal.aborted || done) break;
|
|
2536
|
-
const prevSub = setActiveSub(void 0);
|
|
2537
|
-
try {
|
|
2538
|
-
const setter = state[entry.key];
|
|
2539
|
-
setter?.(value);
|
|
2540
|
-
} finally {
|
|
2541
|
-
setActiveSub(prevSub);
|
|
2542
|
-
}
|
|
2543
|
-
}
|
|
2555
|
+
teardown();
|
|
2544
2556
|
} catch (err) {
|
|
2545
|
-
|
|
2557
|
+
reportError(err, "once");
|
|
2546
2558
|
}
|
|
2547
|
-
};
|
|
2548
|
-
runStream();
|
|
2549
|
-
cleanups.push(() => {
|
|
2550
|
-
streamAborted = true;
|
|
2551
|
-
streamController.abort();
|
|
2552
2559
|
});
|
|
2553
2560
|
}
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2561
|
+
}
|
|
2562
|
+
const preserveSSRDom = hydrated && host.childNodes.length > 0;
|
|
2563
|
+
const initial = renderWithCtx({
|
|
2564
|
+
render: runInFrame(frame, input),
|
|
2565
|
+
liveHost: preserveSSRDom ? host : void 0
|
|
2566
|
+
});
|
|
2567
|
+
if (!preserveSSRDom) host.innerHTML = initial.html;
|
|
2568
|
+
const stopInitialBindingReflection = applyTemplateBindings(host, initial.binds, "reflect");
|
|
2569
|
+
let stopInitialBindingListeners = null;
|
|
2570
|
+
stopBindings = stopInitialBindingReflection;
|
|
2571
|
+
cleanups.push(() => stopBindings());
|
|
2572
|
+
stopJsxEvents = applyJsxEvents({
|
|
2573
|
+
host,
|
|
2574
|
+
records: initial.events,
|
|
2575
|
+
reportError: (error) => reportError(error, "event"),
|
|
2576
|
+
unmountSignal: unmountController.signal,
|
|
2577
|
+
onceFired: nativeOnceFired
|
|
2578
|
+
});
|
|
2579
|
+
cleanups.push(() => stopJsxEvents());
|
|
2580
|
+
mountSlots(initial.slots);
|
|
2581
|
+
cleanups.push(() => mountedSlots.forEach((entry) => entry.unmount()));
|
|
2582
|
+
stopInitialBindingListeners = applyTemplateBindings(host, initial.binds, "listen");
|
|
2583
|
+
stopBindings = () => {
|
|
2584
|
+
stopInitialBindingListeners();
|
|
2585
|
+
stopInitialBindingReflection();
|
|
2586
|
+
};
|
|
2587
|
+
let initialized = false;
|
|
2588
|
+
const initialRenderedHtml = initial.html;
|
|
2589
|
+
let lastRendered = null;
|
|
2590
|
+
let renderEpoch = 0;
|
|
2591
|
+
const renderReactive = () => {
|
|
2592
|
+
const epoch = ++renderEpoch;
|
|
2593
|
+
frame.creating = false;
|
|
2594
|
+
frame.cursor = 0;
|
|
2595
|
+
const { html: rendered, slots: newSlotMap, binds: newBinds, events: newEvents } = renderWithCtx({
|
|
2596
|
+
render: runInFrame(frame, input),
|
|
2597
|
+
liveHost: host
|
|
2598
|
+
});
|
|
2599
|
+
stopJsxEvents();
|
|
2600
|
+
stopJsxEvents = applyJsxEvents({
|
|
2601
|
+
host,
|
|
2602
|
+
records: newEvents,
|
|
2603
|
+
reportError: (error) => reportError(error, "event"),
|
|
2604
|
+
unmountSignal: unmountController.signal,
|
|
2605
|
+
onceFired: nativeOnceFired
|
|
2606
|
+
});
|
|
2607
|
+
if (!initialized) {
|
|
2608
|
+
initialized = true;
|
|
2609
|
+
if (preserveSSRDom) {
|
|
2610
|
+
pushUpdatedProps(newSlotMap);
|
|
2587
2611
|
if (rendered === initialRenderedHtml) {
|
|
2588
2612
|
lastRendered = rendered;
|
|
2589
2613
|
return;
|
|
2590
2614
|
}
|
|
2591
|
-
if (mountedSlots.size > 0 && mountedSlots.size === newSlotMap.size && [...newSlotMap.keys()].every((id) => mountedSlots.has(id)) && isStableInlineSlotMount({
|
|
2592
|
-
initialHtml: initialRenderedHtml,
|
|
2593
|
-
renderedHtml: rendered,
|
|
2594
|
-
slotIds: newSlotMap.keys()
|
|
2595
|
-
})) {
|
|
2596
|
-
pushUpdatedProps(newSlotMap);
|
|
2597
|
-
return;
|
|
2598
|
-
}
|
|
2599
2615
|
}
|
|
2600
|
-
if (rendered ===
|
|
2601
|
-
|
|
2616
|
+
if (rendered === initialRenderedHtml) {
|
|
2617
|
+
lastRendered = rendered;
|
|
2602
2618
|
return;
|
|
2603
2619
|
}
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
if (r instanceof Promise) leavingPromises.push(r);
|
|
2612
|
-
}
|
|
2613
|
-
for (const [id, entry] of mountedSlots) {
|
|
2614
|
-
const next = newSlotMap.get(id);
|
|
2615
|
-
if (next && next.island !== entry.island) {
|
|
2616
|
-
const r = teardownMountedSlot(id, entry);
|
|
2617
|
-
if (r instanceof Promise) leavingPromises.push(r);
|
|
2618
|
-
}
|
|
2620
|
+
if (mountedSlots.size > 0 && mountedSlots.size === newSlotMap.size && [...newSlotMap.keys()].every((id) => mountedSlots.has(id)) && isStableInlineSlotMount({
|
|
2621
|
+
initialHtml: initialRenderedHtml,
|
|
2622
|
+
renderedHtml: rendered,
|
|
2623
|
+
slotIds: newSlotMap.keys()
|
|
2624
|
+
})) {
|
|
2625
|
+
pushUpdatedProps(newSlotMap);
|
|
2626
|
+
return;
|
|
2619
2627
|
}
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2628
|
+
}
|
|
2629
|
+
if (rendered === lastRendered && mountedSlots.size === newSlotMap.size && [...newSlotMap].every(([id, next]) => mountedSlots.get(id)?.island === next.island)) {
|
|
2630
|
+
pushUpdatedProps(newSlotMap);
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
stopBindings();
|
|
2634
|
+
stopJsxEvents();
|
|
2635
|
+
const prevMountedCount = mountedSlots.size;
|
|
2636
|
+
for (const [id, entry] of mountedSlots) if (!newSlotMap.has(id)) teardownMountedSlot(id, entry);
|
|
2637
|
+
for (const [id, entry] of mountedSlots) {
|
|
2638
|
+
const next = newSlotMap.get(id);
|
|
2639
|
+
if (next && next.island !== entry.island) teardownMountedSlot(id, entry);
|
|
2640
|
+
}
|
|
2641
|
+
const applyMorph = () => {
|
|
2642
|
+
if (epoch !== renderEpoch) return;
|
|
2643
|
+
if (newSlotMap.size < prevMountedCount) {
|
|
2644
|
+
let divergeFrom = Infinity;
|
|
2645
|
+
for (const [id, entry] of mountedSlots) {
|
|
2646
|
+
if (!id.startsWith("p:")) continue;
|
|
2647
|
+
const next = newSlotMap.get(id);
|
|
2648
|
+
if (!next) continue;
|
|
2649
|
+
const index = Number(id.slice(2));
|
|
2650
|
+
if (Number.isNaN(index) || index >= divergeFrom) continue;
|
|
2651
|
+
if (serializeSlotProps(next.props) !== (entry.el.getAttribute(PROPS_ATTR) ?? "")) divergeFrom = index;
|
|
2637
2652
|
}
|
|
2638
|
-
const tpl = document.createElement("template");
|
|
2639
|
-
const morphRootTag = host.tagName.toLowerCase();
|
|
2640
|
-
tpl.innerHTML = `<${morphRootTag}>${html}</${morphRootTag}>`;
|
|
2641
|
-
morphInner(host, tpl.content.firstElementChild);
|
|
2642
|
-
lastRendered = rendered;
|
|
2643
|
-
let rehomeIndex = null;
|
|
2644
2653
|
for (const [id, entry] of mountedSlots) {
|
|
2645
|
-
if (!newSlotMap.has(id)
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
if (stub && stub !== entry.el) stub.replaceWith(entry.el);
|
|
2654
|
+
if (!newSlotMap.has(id)) continue;
|
|
2655
|
+
if (!entry.el.isConnected) continue;
|
|
2656
|
+
if (id.startsWith("p:") && Number(id.slice(2)) >= divergeFrom) teardownMountedSlot(id, entry);
|
|
2649
2657
|
}
|
|
2650
|
-
attachListeners();
|
|
2651
|
-
stopBindings = applyTemplateBindings(host, newBinds);
|
|
2652
|
-
stopJsxEvents = applyJsxEvents({
|
|
2653
|
-
host,
|
|
2654
|
-
records: newEvents,
|
|
2655
|
-
reportError: (error) => reportError(error, "on"),
|
|
2656
|
-
unmountSignal: unmountController.signal,
|
|
2657
|
-
onceFired: nativeOnceFired
|
|
2658
|
-
});
|
|
2659
|
-
mountSlots(newSlotMap);
|
|
2660
|
-
};
|
|
2661
|
-
if (leavingPromises.length > 0) Promise.allSettled(leavingPromises).then((results) => {
|
|
2662
|
-
for (const r of results) if (r.status === "rejected") console.error(r.reason);
|
|
2663
|
-
applyMorph();
|
|
2664
|
-
});
|
|
2665
|
-
else applyMorph();
|
|
2666
|
-
});
|
|
2667
|
-
let tornDown = false;
|
|
2668
|
-
const unmount = () => {
|
|
2669
|
-
if (tornDown) return;
|
|
2670
|
-
tornDown = true;
|
|
2671
|
-
disposed = true;
|
|
2672
|
-
unmountController.abort();
|
|
2673
|
-
ISLAND_MOUNT_HANDLES.delete(host);
|
|
2674
|
-
stopRender();
|
|
2675
|
-
detachListeners();
|
|
2676
|
-
const pending = [];
|
|
2677
|
-
for (const [, entry] of mountedSlots) {
|
|
2678
|
-
const result = entry.unmount();
|
|
2679
|
-
if (result instanceof Promise) pending.push(result);
|
|
2680
2658
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2659
|
+
const tpl = document.createElement("template");
|
|
2660
|
+
const morphRootTag = host.tagName.toLowerCase();
|
|
2661
|
+
tpl.innerHTML = `<${morphRootTag}>${rendered}</${morphRootTag}>`;
|
|
2662
|
+
morphInner(host, tpl.content.firstElementChild);
|
|
2663
|
+
lastRendered = rendered;
|
|
2664
|
+
let rehomeIndex = null;
|
|
2665
|
+
for (const [id, entry] of mountedSlots) {
|
|
2666
|
+
if (!newSlotMap.has(id) || entry.el.isConnected) continue;
|
|
2667
|
+
rehomeIndex ??= buildSlotIndex();
|
|
2668
|
+
const stub = rehomeIndex.get(id);
|
|
2669
|
+
if (stub && stub !== entry.el) stub.replaceWith(entry.el);
|
|
2686
2670
|
}
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
reportError(
|
|
2692
|
-
|
|
2671
|
+
stopBindings = applyTemplateBindings(host, newBinds);
|
|
2672
|
+
stopJsxEvents = applyJsxEvents({
|
|
2673
|
+
host,
|
|
2674
|
+
records: newEvents,
|
|
2675
|
+
reportError: (error) => reportError(error, "event"),
|
|
2676
|
+
unmountSignal: unmountController.signal,
|
|
2677
|
+
onceFired: nativeOnceFired
|
|
2693
2678
|
});
|
|
2694
|
-
|
|
2695
|
-
};
|
|
2696
|
-
const updateProps = (nextProps) => {
|
|
2697
|
-
if (tornDown) return;
|
|
2698
|
-
const next = resolveInput(nextProps);
|
|
2699
|
-
if (shallowEqualInput(inputSignal(), next)) return;
|
|
2700
|
-
inputSignal(next);
|
|
2701
|
-
};
|
|
2702
|
-
const handle = {
|
|
2703
|
-
unmount,
|
|
2704
|
-
updateProps
|
|
2679
|
+
mountSlots(newSlotMap);
|
|
2705
2680
|
};
|
|
2706
|
-
|
|
2707
|
-
return handle;
|
|
2708
|
-
}
|
|
2709
|
-
/**
|
|
2710
|
-
* Dual-mode island invocation:
|
|
2711
|
-
* - When called inside an `html` interpolation (i.e. {@link currentRenderCtx}
|
|
2712
|
-
* is active), returns an {@link IslandCall} carrying props to be emitted
|
|
2713
|
-
* as a child slot marker; {@link interpolateValue} consumes this marker.
|
|
2714
|
-
* - When called at the top level with no active render context, returns
|
|
2715
|
-
* SSR HTML via {@link renderToString} (backward-compatible public API).
|
|
2716
|
-
*
|
|
2717
|
-
* The declared return type omits {@link IslandCall} because TypeScript
|
|
2718
|
-
* cannot narrow based on runtime stack state.
|
|
2719
|
-
*/
|
|
2720
|
-
const island = ((props) => {
|
|
2721
|
-
if (currentRenderCtx()) return {
|
|
2722
|
-
[ISLAND_CALL]: true,
|
|
2723
|
-
island,
|
|
2724
|
-
props,
|
|
2725
|
-
key: void 0
|
|
2726
|
-
};
|
|
2727
|
-
return renderToString(props);
|
|
2728
|
-
});
|
|
2729
|
-
island.toString = (props) => renderToString(props, true);
|
|
2730
|
-
island.toStringAsync = (props) => Promise.resolve(renderToString(props, false));
|
|
2731
|
-
island.mount = (host, props) => mountIsland(host, props);
|
|
2732
|
-
island[ISLAND_MOUNT_INTERNAL] = (host, props) => mountIslandInternal(host, props);
|
|
2733
|
-
island[ISLAND_SLOT_TAG] = configuredSlotTag;
|
|
2734
|
-
island[ISLAND_RENDER_STATE] = async (props) => {
|
|
2735
|
-
const manifest = /* @__PURE__ */ new Map();
|
|
2736
|
-
const html = await renderToString(props ?? void 0, false, void 0, manifest, void 0, true);
|
|
2737
|
-
return manifestTemplatePrefix(manifest) + html;
|
|
2681
|
+
applyMorph();
|
|
2738
2682
|
};
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2683
|
+
let stopRender = effect$1(renderReactive);
|
|
2684
|
+
startDerivedScopes();
|
|
2685
|
+
stopRender();
|
|
2686
|
+
stopRender = effect$1(renderReactive);
|
|
2687
|
+
startEffectScopes();
|
|
2688
|
+
let tornDown = false;
|
|
2689
|
+
const unmount = () => {
|
|
2690
|
+
if (tornDown) return;
|
|
2691
|
+
tornDown = true;
|
|
2692
|
+
instance.disposed = true;
|
|
2693
|
+
unmountController.abort();
|
|
2694
|
+
ISLAND_MOUNT_HANDLES.delete(host);
|
|
2695
|
+
stopRender();
|
|
2696
|
+
for (const [, entry] of mountedSlots) entry.unmount();
|
|
2697
|
+
for (const c of cleanups) c();
|
|
2751
2698
|
};
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2699
|
+
const updateProps = (nextProps) => {
|
|
2700
|
+
if (tornDown) return;
|
|
2701
|
+
const next = resolveInput(nextProps);
|
|
2702
|
+
if (shallowEqualInput(inputSignal(), next)) return;
|
|
2703
|
+
inputSignal(next);
|
|
2704
|
+
derivedPropsVersion(derivedPropsVersion() + 1);
|
|
2705
|
+
};
|
|
2706
|
+
const handle = {
|
|
2707
|
+
unmount,
|
|
2708
|
+
updateProps
|
|
2709
|
+
};
|
|
2710
|
+
ISLAND_MOUNT_HANDLES.set(host, handle);
|
|
2711
|
+
return handle;
|
|
2712
|
+
}
|
|
2713
|
+
const island = ((props) => ({
|
|
2714
|
+
[ISLAND_CALL]: true,
|
|
2715
|
+
island,
|
|
2716
|
+
props,
|
|
2717
|
+
key: void 0
|
|
2718
|
+
}));
|
|
2719
|
+
island.toString = (props) => renderToString(props, true);
|
|
2720
|
+
island.toStringAsync = (props) => Promise.resolve(renderToString(props, false));
|
|
2721
|
+
island.mount = (host, props) => mountIsland(host, props);
|
|
2722
|
+
island[ISLAND_MOUNT_INTERNAL] = (host, props) => mountIslandInternal(host, props);
|
|
2723
|
+
island[ISLAND_SLOT_TAG] = configuredSlotTag;
|
|
2724
|
+
island[ISLAND_RENDER_STATE] = async (props) => {
|
|
2725
|
+
const manifest = /* @__PURE__ */ new Map();
|
|
2726
|
+
const html = await renderToString(props ?? void 0, false, manifest, true);
|
|
2727
|
+
return serializeServerManifest(manifest) + html;
|
|
2728
|
+
};
|
|
2729
|
+
island[ISLAND_SSR_MANIFEST] = (props, eventsOut) => renderToString(props, false, eventsOut, true);
|
|
2730
|
+
island.key = (key) => {
|
|
2731
|
+
if (typeof key !== "string" || key.trim().length === 0) throw new Error("island.key() requires a non-empty string.");
|
|
2732
|
+
if (key.includes(":")) throw new Error(`island.key() key cannot contain the slot separator ":" (got "${key}").`);
|
|
2733
|
+
const keyed = ((props) => ({
|
|
2734
|
+
[ISLAND_CALL]: true,
|
|
2735
|
+
island,
|
|
2736
|
+
props,
|
|
2737
|
+
key
|
|
2738
|
+
}));
|
|
2739
|
+
keyed[ISLAND_CALL] = true;
|
|
2740
|
+
return keyed;
|
|
2741
|
+
};
|
|
2742
|
+
island[ISLAND] = true;
|
|
2743
|
+
const astroRendererName = globalThis[ASTRO_RENDERER_GLOBAL];
|
|
2744
|
+
if (typeof astroRendererName === "string") island[Symbol.for("astro:renderer")] = astroRendererName;
|
|
2745
|
+
island.define = (tagName, options) => {
|
|
2746
|
+
if (typeof customElements === "undefined" || typeof HTMLElement === "undefined") {
|
|
2747
|
+
`${tagName}`;
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
const CE_RESERVED = /* @__PURE__ */ new Set([
|
|
2751
|
+
"annotation-xml",
|
|
2752
|
+
"color-profile",
|
|
2753
|
+
"font-face",
|
|
2754
|
+
"font-face-src",
|
|
2755
|
+
"font-face-uri",
|
|
2756
|
+
"font-face-format",
|
|
2757
|
+
"font-face-name",
|
|
2758
|
+
"missing-glyph"
|
|
2759
|
+
]);
|
|
2760
|
+
if (typeof tagName !== "string" || !/^[a-z][a-z0-9._-]*-[a-z0-9._-]*$/.test(tagName) || CE_RESERVED.has(tagName)) {
|
|
2761
|
+
`${tagName}`;
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
if (customElements.get(tagName)) {
|
|
2765
|
+
`${tagName}`;
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
const observe = options?.observe ?? [];
|
|
2769
|
+
class IlhaIslandElement extends HTMLElement {
|
|
2770
|
+
static observedAttributes = observe;
|
|
2771
|
+
_handle = null;
|
|
2772
|
+
_props;
|
|
2773
|
+
_unmounting = false;
|
|
2774
|
+
_reconnect = false;
|
|
2775
|
+
get props() {
|
|
2776
|
+
return this._props;
|
|
2773
2777
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2778
|
+
set props(p) {
|
|
2779
|
+
this._props = p;
|
|
2780
|
+
if (this._handle && !this._unmounting) this._handle.updateProps(this._mergedProps());
|
|
2777
2781
|
}
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2782
|
+
_mergedProps() {
|
|
2783
|
+
const attrProps = {};
|
|
2784
|
+
let hasAttrs = false;
|
|
2785
|
+
for (const name of observe) {
|
|
2786
|
+
const v = this.getAttribute(name);
|
|
2787
|
+
if (v !== null) {
|
|
2788
|
+
attrProps[name] = v;
|
|
2789
|
+
hasAttrs = true;
|
|
2790
|
+
}
|
|
2787
2791
|
}
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2792
|
+
if (!hasAttrs && this._props === void 0) return void 0;
|
|
2793
|
+
return {
|
|
2794
|
+
...attrProps,
|
|
2795
|
+
...this._props
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
connectedCallback() {
|
|
2799
|
+
if (this._unmounting) {
|
|
2800
|
+
this._reconnect = true;
|
|
2801
|
+
return;
|
|
2791
2802
|
}
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2803
|
+
if (this._handle) return;
|
|
2804
|
+
this._handle = mountIslandInternal(this, this._mergedProps());
|
|
2805
|
+
}
|
|
2806
|
+
disconnectedCallback() {
|
|
2807
|
+
if (!this._handle || this._unmounting) return;
|
|
2808
|
+
this._unmounting = true;
|
|
2809
|
+
this._reconnect = false;
|
|
2810
|
+
Promise.resolve(this._handle.unmount()).finally(() => {
|
|
2811
|
+
this._handle = null;
|
|
2812
|
+
this._unmounting = false;
|
|
2813
|
+
if (this._reconnect) {
|
|
2814
|
+
this._reconnect = false;
|
|
2815
|
+
if (this.isConnected) this._handle = mountIslandInternal(this, this._mergedProps());
|
|
2801
2816
|
}
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
attributeChangedCallback() {
|
|
2820
|
+
if (this._handle && !this._unmounting) this._handle.updateProps(this._mergedProps());
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
customElements.define(tagName, IlhaIslandElement);
|
|
2824
|
+
};
|
|
2825
|
+
island.hydratable = async (props, opts) => {
|
|
2826
|
+
const { name, as: rawTag = "div", snapshot = false, skipOnMount: explicitSkipOnMount } = opts;
|
|
2827
|
+
const tag = assertValidSlotTagName(rawTag);
|
|
2828
|
+
const resolvedProps = props ?? {};
|
|
2829
|
+
const innerPromise = renderToString(resolvedProps, false, void 0, true);
|
|
2830
|
+
let stateAttr = "";
|
|
2831
|
+
if (snapshot !== false) {
|
|
2832
|
+
const doState = snapshot === true || snapshot.state !== false;
|
|
2833
|
+
const doDerived = snapshot === true || snapshot.derived !== false;
|
|
2834
|
+
const doSkipOnMount = explicitSkipOnMount ?? false;
|
|
2835
|
+
const snapshotData = { v: 2 };
|
|
2836
|
+
const input = resolveInput(resolvedProps);
|
|
2837
|
+
const probeFrame = freshFrame({ ssr: true });
|
|
2838
|
+
withFrame(probeFrame, () => {
|
|
2839
|
+
component(input);
|
|
2840
|
+
});
|
|
2841
|
+
if (doState) {
|
|
2842
|
+
const states = [];
|
|
2843
|
+
for (const slot of probeFrame.slots) {
|
|
2844
|
+
if (slot.kind !== "state") continue;
|
|
2845
|
+
const prevSub = setActiveSub(void 0);
|
|
2846
|
+
try {
|
|
2847
|
+
states.push(slot.acc());
|
|
2848
|
+
} finally {
|
|
2849
|
+
setActiveSub(prevSub);
|
|
2812
2850
|
}
|
|
2813
|
-
if (this._handle) return;
|
|
2814
|
-
this._handle = mountIslandInternal(this, this._mergedProps());
|
|
2815
2851
|
}
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
}
|
|
2852
|
+
snapshotData["s"] = states;
|
|
2853
|
+
}
|
|
2854
|
+
if (doDerived) {
|
|
2855
|
+
const probePendings = probeFrame.pendingDerived ?? [];
|
|
2856
|
+
if (probePendings.length > 0) {
|
|
2857
|
+
await Promise.all(probePendings.map((p) => p.promise));
|
|
2858
|
+
probeFrame.creating = false;
|
|
2859
|
+
probeFrame.cursor = 0;
|
|
2860
|
+
withFrame(probeFrame, () => {
|
|
2861
|
+
component(input);
|
|
2827
2862
|
});
|
|
2828
2863
|
}
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
const resolvedProps = props ?? {};
|
|
2839
|
-
const streamValues = {};
|
|
2840
|
-
const eventsManifest = /* @__PURE__ */ new Map();
|
|
2841
|
-
const inner = await renderToString(resolvedProps, false, streamValues, eventsManifest, void 0, true);
|
|
2842
|
-
const encodedProps = escapeHtml(JSON.stringify(resolvedProps));
|
|
2843
|
-
let stateAttr = "";
|
|
2844
|
-
if (snapshot !== false) {
|
|
2845
|
-
const doState = snapshot === true || snapshot.state !== false;
|
|
2846
|
-
const doDerived = snapshot === true || snapshot.derived !== false;
|
|
2847
|
-
const doSkipOnMount = explicitSkipOnMount ?? (doState || doDerived);
|
|
2848
|
-
const snapshotData = {};
|
|
2849
|
-
const input = resolveInput(resolvedProps);
|
|
2850
|
-
const plainState = buildPlainState(input);
|
|
2851
|
-
const stateForDerived = { ...plainState };
|
|
2852
|
-
for (const [k, v] of Object.entries(streamValues)) stateForDerived[k] = () => v;
|
|
2853
|
-
if (doState) for (const entry of states) snapshotData[entry.key] = entry.key in streamValues ? streamValues[entry.key] : plainState[entry.key]();
|
|
2854
|
-
if (doDerived) {
|
|
2855
|
-
const derivedResults = {};
|
|
2856
|
-
for (const entry of deriveds) {
|
|
2857
|
-
const prevSub = setActiveSub(void 0);
|
|
2858
|
-
let resultPromise;
|
|
2859
|
-
let syncError;
|
|
2860
|
-
let threw = false;
|
|
2861
|
-
try {
|
|
2862
|
-
resultPromise = entry.fn({
|
|
2863
|
-
state: stateForDerived,
|
|
2864
|
-
input,
|
|
2865
|
-
signal: new AbortController().signal
|
|
2866
|
-
});
|
|
2867
|
-
} catch (err) {
|
|
2868
|
-
threw = true;
|
|
2869
|
-
syncError = err;
|
|
2870
|
-
} finally {
|
|
2871
|
-
setActiveSub(prevSub);
|
|
2872
|
-
}
|
|
2873
|
-
if (threw) {
|
|
2874
|
-
derivedResults[entry.key] = {
|
|
2875
|
-
loading: false,
|
|
2876
|
-
value: void 0,
|
|
2877
|
-
error: syncError instanceof Error ? syncError.message : String(syncError)
|
|
2878
|
-
};
|
|
2879
|
-
continue;
|
|
2880
|
-
}
|
|
2881
|
-
try {
|
|
2882
|
-
const result = await Promise.resolve(resultPromise);
|
|
2883
|
-
derivedResults[entry.key] = {
|
|
2884
|
-
loading: false,
|
|
2885
|
-
value: result,
|
|
2886
|
-
error: void 0
|
|
2887
|
-
};
|
|
2888
|
-
} catch (err) {
|
|
2889
|
-
derivedResults[entry.key] = {
|
|
2890
|
-
loading: false,
|
|
2891
|
-
value: void 0,
|
|
2892
|
-
error: err instanceof Error ? err.message : String(err)
|
|
2893
|
-
};
|
|
2894
|
-
}
|
|
2864
|
+
const derivedEntries = [];
|
|
2865
|
+
for (const slot of probeFrame.slots) {
|
|
2866
|
+
if (slot.kind !== "derived") continue;
|
|
2867
|
+
const prevSub = setActiveSub(void 0);
|
|
2868
|
+
let envelope;
|
|
2869
|
+
try {
|
|
2870
|
+
envelope = { ...slot.env() };
|
|
2871
|
+
} finally {
|
|
2872
|
+
setActiveSub(prevSub);
|
|
2895
2873
|
}
|
|
2896
|
-
|
|
2874
|
+
const entry = {
|
|
2875
|
+
loading: false,
|
|
2876
|
+
value: envelope.value
|
|
2877
|
+
};
|
|
2878
|
+
if (envelope.error) entry["error"] = envelope.error.message;
|
|
2879
|
+
derivedEntries.push(entry);
|
|
2897
2880
|
}
|
|
2898
|
-
|
|
2899
|
-
if (streams.length > 0) snapshotData["_streams"] = Object.fromEntries(streams.map((entry) => [entry.key, true]));
|
|
2900
|
-
stateAttr = ` ${STATE_ATTR}='${escapeHtml(JSON.stringify(snapshotData))}'`;
|
|
2881
|
+
snapshotData["d"] = derivedEntries;
|
|
2901
2882
|
}
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
return
|
|
2907
|
-
}
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
const host = typeof selector === "string" ? document.querySelector(selector) : selector;
|
|
2911
|
-
if (!host) {
|
|
2912
|
-
console.warn(`[ilha] from(): element not found: ${selector}`);
|
|
2913
|
-
return null;
|
|
2914
|
-
}
|
|
2915
|
-
return island.mount(host, props);
|
|
2916
|
-
}
|
|
2883
|
+
if (doSkipOnMount) snapshotData["_skipOnMount"] = true;
|
|
2884
|
+
stateAttr = ` ${STATE_ATTR}='${escapeHtml(JSON.stringify(snapshotData))}'`;
|
|
2885
|
+
}
|
|
2886
|
+
const inner = await innerPromise;
|
|
2887
|
+
return `<${tag} data-ilha="${escapeHtml(name)}" ${PROPS_ATTR}='${escapeHtml(JSON.stringify(resolvedProps))}'${stateAttr}>${inner}</${tag}>`;
|
|
2888
|
+
};
|
|
2889
|
+
return island;
|
|
2890
|
+
});
|
|
2917
2891
|
function mountAll(registry, options = {}) {
|
|
2918
2892
|
const root = options.root ?? document.body;
|
|
2919
2893
|
const lazy = options.lazy ?? false;
|
|
@@ -2959,67 +2933,27 @@ function mountAll(registry, options = {}) {
|
|
|
2959
2933
|
if (pending.length > 0) return Promise.all(pending).then(() => {});
|
|
2960
2934
|
} };
|
|
2961
2935
|
}
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
}
|
|
2982
|
-
for (const key of Object.getOwnPropertyNames(IlhaBuilder.prototype)) {
|
|
2983
|
-
if (key === "constructor") continue;
|
|
2984
|
-
const descriptor = Object.getOwnPropertyDescriptor(IlhaBuilder.prototype, key);
|
|
2985
|
-
if (descriptor) Object.defineProperty(callableRoot, key, descriptor);
|
|
2986
|
-
}
|
|
2987
|
-
const ilha = Object.assign(callableRoot, {
|
|
2988
|
-
html: ilhaHtml,
|
|
2989
|
-
raw: ilhaRaw,
|
|
2990
|
-
mount: mountAll,
|
|
2991
|
-
from: ilhaFrom,
|
|
2992
|
-
context: ilhaContext,
|
|
2993
|
-
signal: ilhaSignal,
|
|
2994
|
-
computed: ilhaComputed,
|
|
2995
|
-
morph,
|
|
2996
|
-
batch,
|
|
2997
|
-
untrack,
|
|
2998
|
-
onUncaughtError
|
|
2936
|
+
setJsxRuntimeBridge({
|
|
2937
|
+
registerEvent({ type, handler, modifier }) {
|
|
2938
|
+
const ctx = currentRenderCtx();
|
|
2939
|
+
if (!ctx) return void 0;
|
|
2940
|
+
SSR_ACTION_STUB in handler;
|
|
2941
|
+
const index = ctx.events.length;
|
|
2942
|
+
ctx.events.push({
|
|
2943
|
+
type,
|
|
2944
|
+
handler,
|
|
2945
|
+
modifier
|
|
2946
|
+
});
|
|
2947
|
+
return index;
|
|
2948
|
+
},
|
|
2949
|
+
slot({ island, props, key }) {
|
|
2950
|
+
return ilhaRaw(emitIslandSlot({
|
|
2951
|
+
island,
|
|
2952
|
+
props,
|
|
2953
|
+
key
|
|
2954
|
+
}));
|
|
2955
|
+
}
|
|
2999
2956
|
});
|
|
3000
|
-
/** @internal Used by the separate JSX runtime to register a native event handler. */
|
|
3001
|
-
function __ilhaJsxEvent({ type, handler, modifier }) {
|
|
3002
|
-
const ctx = currentRenderCtx();
|
|
3003
|
-
if (!ctx) return void 0;
|
|
3004
|
-
const isActionStub = SSR_ACTION_STUB in handler;
|
|
3005
|
-
const capture = ctx.manifest && !isActionStub ? captureHandlerActions(handler) : void 0;
|
|
3006
|
-
const index = ctx.events.length;
|
|
3007
|
-
ctx.events.push({
|
|
3008
|
-
type,
|
|
3009
|
-
handler,
|
|
3010
|
-
modifier,
|
|
3011
|
-
capture
|
|
3012
|
-
});
|
|
3013
|
-
return index;
|
|
3014
|
-
}
|
|
3015
|
-
/** @internal Used by the separate JSX runtime entry to preserve island slot composition. */
|
|
3016
|
-
function __ilhaJsxSlot({ island, props, key }) {
|
|
3017
|
-
return ilhaRaw(emitIslandSlot({
|
|
3018
|
-
island,
|
|
3019
|
-
props,
|
|
3020
|
-
key
|
|
3021
|
-
}));
|
|
3022
|
-
}
|
|
3023
2957
|
/**
|
|
3024
2958
|
* Morph an element's children toward `html`, patching in place: surviving
|
|
3025
2959
|
* elements keep their identity, listeners, and focus (see the morph engine).
|
|
@@ -3043,10 +2977,8 @@ function morph(host, html) {
|
|
|
3043
2977
|
}
|
|
3044
2978
|
const html = ilhaHtml;
|
|
3045
2979
|
const raw = ilhaRaw;
|
|
3046
|
-
const css = ilhaCss;
|
|
3047
2980
|
const mount = mountAll;
|
|
3048
|
-
const from = ilhaFrom;
|
|
3049
2981
|
const context = ilhaContext;
|
|
3050
2982
|
|
|
3051
2983
|
//#endregion
|
|
3052
|
-
export {
|
|
2984
|
+
export { action, batch, context, css, derived, each, effect, html, ilha, ilhaSignal, isSafeUrl, isSafeUrlAttrValue, isUrlAttributeName, json, morph, mount, onError, onUncaughtError, persist, raw, serializeStyle, state, untrack };
|