ilha 0.9.4 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -41
- package/dist/{index-Dx_q_SGo.d.ts → index-Bi_I3qEL.d.ts} +106 -46
- package/dist/index.d.ts +2 -2
- package/dist/index.js +609 -83
- package/dist/jsx-dev-runtime.d.ts +1 -1
- package/dist/jsx-runtime-DJ2B4fvA.d.ts +633 -0
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +94 -16
- package/package.json +1 -1
- package/dist/jsx-runtime-DysRyz9R.d.ts +0 -37
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ function exceedsMaxDepth(value, depth) {
|
|
|
11
11
|
return false;
|
|
12
12
|
}
|
|
13
13
|
for (const key in value) {
|
|
14
|
-
if (!Object.
|
|
14
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
15
15
|
if (exceedsMaxDepth(value[key], depth + 1)) return true;
|
|
16
16
|
}
|
|
17
17
|
return false;
|
|
@@ -60,7 +60,7 @@ function shallowEqualInput(a, b) {
|
|
|
60
60
|
const bk = Object.keys(b);
|
|
61
61
|
if (ak.length !== bk.length) return false;
|
|
62
62
|
for (const k of ak) {
|
|
63
|
-
if (!Object.
|
|
63
|
+
if (!Object.hasOwn(b, k)) return false;
|
|
64
64
|
if (!Object.is(a[k], b[k])) return false;
|
|
65
65
|
}
|
|
66
66
|
return true;
|
|
@@ -103,10 +103,10 @@ function getIslandSlotTag(island) {
|
|
|
103
103
|
if (typeof tag === "string" && tag.length > 0) return tag;
|
|
104
104
|
return "div";
|
|
105
105
|
}
|
|
106
|
-
function wrapIslandSlotHtml(tag, id, propsAttr, inner) {
|
|
106
|
+
function wrapIslandSlotHtml({ tag, id, propsAttr, inner }) {
|
|
107
107
|
return `<${tag} ${SLOT_ATTR}="${escapeHtml(id)}"${propsAttr}>${inner}</${tag}>`;
|
|
108
108
|
}
|
|
109
|
-
function isStableInlineSlotMount(initialHtml, renderedHtml, slotIds) {
|
|
109
|
+
function isStableInlineSlotMount({ initialHtml, renderedHtml, slotIds }) {
|
|
110
110
|
if (typeof document === "undefined") return false;
|
|
111
111
|
const initialRoot = parseHtmlFragment(initialHtml);
|
|
112
112
|
const renderedRoot = parseHtmlFragment(renderedHtml);
|
|
@@ -121,9 +121,96 @@ function isStableInlineSlotMount(initialHtml, renderedHtml, slotIds) {
|
|
|
121
121
|
}
|
|
122
122
|
return true;
|
|
123
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Props safe to embed in `data-ilha-props`.
|
|
126
|
+
* Functions cannot round-trip through JSON; children are owned by the live slot
|
|
127
|
+
* map (client) and/or the inlined SSR subtree — never depend on the attr for them.
|
|
128
|
+
* RawHtml is tagged so a rare attr-only mount can revive `Symbol.for("ilha.raw")`.
|
|
129
|
+
*/
|
|
130
|
+
function slotPropsForAttr(props) {
|
|
131
|
+
if (props === void 0) return void 0;
|
|
132
|
+
let out;
|
|
133
|
+
for (const key of Object.keys(props)) {
|
|
134
|
+
if (key === "children") continue;
|
|
135
|
+
const value = props[key];
|
|
136
|
+
if (typeof value === "function" || typeof value === "symbol") continue;
|
|
137
|
+
const encoded = encodeSlotPropValue(value);
|
|
138
|
+
if (encoded === void 0 && value !== void 0 && value !== null) continue;
|
|
139
|
+
(out ??= {})[key] = encoded;
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
const SLOT_RAW = Symbol.for("ilha.raw");
|
|
144
|
+
function isRawHtmlValue(v) {
|
|
145
|
+
return !!(v && typeof v === "object" && SLOT_RAW in v);
|
|
146
|
+
}
|
|
147
|
+
function encodeSlotPropValue(value, seen) {
|
|
148
|
+
if (value == null) return value;
|
|
149
|
+
if (typeof value === "function" || typeof value === "symbol") return void 0;
|
|
150
|
+
if (typeof value !== "object") return value;
|
|
151
|
+
if (isRawHtmlValue(value)) return {
|
|
152
|
+
__ilha: "raw",
|
|
153
|
+
value: value.value
|
|
154
|
+
};
|
|
155
|
+
const visited = seen ?? /* @__PURE__ */ new WeakSet();
|
|
156
|
+
if (visited.has(value)) throw new TypeError("encodeSlotPropValue: circular reference in slot props");
|
|
157
|
+
visited.add(value);
|
|
158
|
+
if (Array.isArray(value)) return value.map((item) => encodeSlotPropValue(item, visited)).filter((item) => item !== void 0);
|
|
159
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) return void 0;
|
|
160
|
+
const obj = value;
|
|
161
|
+
const encoded = {};
|
|
162
|
+
for (const key of Object.keys(obj)) {
|
|
163
|
+
const next = encodeSlotPropValue(obj[key], visited);
|
|
164
|
+
if (next !== void 0 || obj[key] === null) encoded[key] = next;
|
|
165
|
+
}
|
|
166
|
+
return encoded;
|
|
167
|
+
}
|
|
168
|
+
function makeRawHtml(value) {
|
|
169
|
+
return {
|
|
170
|
+
[SLOT_RAW]: true,
|
|
171
|
+
value
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function reviveSlotPropValue(value) {
|
|
175
|
+
if (value == null || typeof value !== "object") return value;
|
|
176
|
+
if (Array.isArray(value)) return value.map(reviveSlotPropValue);
|
|
177
|
+
const obj = value;
|
|
178
|
+
if (obj.__ilha === "raw" && typeof obj.value === "string") return makeRawHtml(obj.value);
|
|
179
|
+
const out = {};
|
|
180
|
+
for (const key of Object.keys(obj)) out[key] = reviveSlotPropValue(obj[key]);
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Exact legacy shape produced when RawHtml lost its brand under JSON.stringify:
|
|
185
|
+
* a plain object with a single own key `value` that is a string.
|
|
186
|
+
*/
|
|
187
|
+
function isLegacyRawHtmlBlob(value) {
|
|
188
|
+
if (!value || typeof value !== "object" || isRawHtmlValue(value)) return false;
|
|
189
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) return false;
|
|
190
|
+
const keys = Object.keys(value);
|
|
191
|
+
return keys.length === 1 && keys[0] === "value" && typeof value.value === "string";
|
|
192
|
+
}
|
|
193
|
+
/** Revive attr-parsed props (RawHtml tags). Children stay as plain data if present. */
|
|
194
|
+
function reviveSlotProps(props) {
|
|
195
|
+
if (typeof props !== "object" || props === null || Array.isArray(props)) return {};
|
|
196
|
+
const out = reviveSlotPropValue(props);
|
|
197
|
+
if ("children" in out) out.children = reviveLegacyChildren(out.children);
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
function reviveLegacyChildren(children) {
|
|
201
|
+
if (!Array.isArray(children)) {
|
|
202
|
+
if (isLegacyRawHtmlBlob(children)) return makeRawHtml(children.value);
|
|
203
|
+
return reviveSlotPropValue(children);
|
|
204
|
+
}
|
|
205
|
+
return children.map((child) => {
|
|
206
|
+
if (isLegacyRawHtmlBlob(child)) return makeRawHtml(child.value);
|
|
207
|
+
return reviveSlotPropValue(child);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
124
210
|
/** Serialized form of slot props, matching the decoded `data-ilha-props` attr. */
|
|
125
211
|
function serializeSlotProps(props) {
|
|
126
|
-
|
|
212
|
+
const safe = slotPropsForAttr(props);
|
|
213
|
+
return safe === void 0 ? "" : JSON.stringify(safe);
|
|
127
214
|
}
|
|
128
215
|
const MORPH_CONTROLLER_SLOT_SELECTOR = "[data-slot]";
|
|
129
216
|
const MORPH_CONTROLLER_ATTRS = /* @__PURE__ */ new Set([
|
|
@@ -398,25 +485,32 @@ const PROPS_ATTR = "data-ilha-props";
|
|
|
398
485
|
const STATE_ATTR = "data-ilha-state";
|
|
399
486
|
const CSS_ATTR = "data-ilha-css";
|
|
400
487
|
function buildScopedStyle(css) {
|
|
401
|
-
|
|
488
|
+
const safeCss = css.replace(/<\/style/gi, "<\\/style");
|
|
489
|
+
return `<style ${CSS_ATTR}>@scope (:scope) to ([data-ilha]){${safeCss}}</style>`;
|
|
490
|
+
}
|
|
491
|
+
const RENDER_CTX_STACK = Symbol.for("ilha.renderCtxStack");
|
|
492
|
+
function renderCtxStack() {
|
|
493
|
+
const g = globalThis;
|
|
494
|
+
return g[RENDER_CTX_STACK] ??= [];
|
|
402
495
|
}
|
|
403
|
-
const renderCtxStack = [];
|
|
404
496
|
function pushRenderCtx(liveHost, asyncChildren) {
|
|
405
497
|
const ctx = {
|
|
406
498
|
slots: /* @__PURE__ */ new Map(),
|
|
407
499
|
positional: 0,
|
|
408
500
|
liveHost,
|
|
409
501
|
pending: asyncChildren ? /* @__PURE__ */ new Map() : void 0,
|
|
410
|
-
binds: []
|
|
502
|
+
binds: [],
|
|
503
|
+
events: []
|
|
411
504
|
};
|
|
412
|
-
renderCtxStack.push(ctx);
|
|
505
|
+
renderCtxStack().push(ctx);
|
|
413
506
|
return ctx;
|
|
414
507
|
}
|
|
415
508
|
function popRenderCtx() {
|
|
416
|
-
renderCtxStack.pop();
|
|
509
|
+
renderCtxStack().pop();
|
|
417
510
|
}
|
|
418
511
|
function currentRenderCtx() {
|
|
419
|
-
|
|
512
|
+
const stack = renderCtxStack();
|
|
513
|
+
return stack[stack.length - 1];
|
|
420
514
|
}
|
|
421
515
|
function isIsland(v) {
|
|
422
516
|
return typeof v === "function" && ISLAND in v;
|
|
@@ -426,7 +520,7 @@ function isIslandCall(v) {
|
|
|
426
520
|
if (ISLAND_CALL in v) return true;
|
|
427
521
|
return typeof v === "object" && "island" in v && isIsland(v.island);
|
|
428
522
|
}
|
|
429
|
-
function emitIslandSlot(island, props, key) {
|
|
523
|
+
function emitIslandSlot({ island, props, key }) {
|
|
430
524
|
const ctx = currentRenderCtx();
|
|
431
525
|
let id;
|
|
432
526
|
if (key !== void 0) id = `k:${key}`;
|
|
@@ -436,23 +530,44 @@ function emitIslandSlot(island, props, key) {
|
|
|
436
530
|
props
|
|
437
531
|
});
|
|
438
532
|
const slotTag = getIslandSlotTag(island);
|
|
439
|
-
const
|
|
440
|
-
|
|
533
|
+
const attrProps = slotPropsForAttr(props);
|
|
534
|
+
const propsAttr = attrProps ? ` ${PROPS_ATTR}='${escapeHtml(JSON.stringify(attrProps))}'` : "";
|
|
535
|
+
if (ctx?.liveHost) return wrapIslandSlotHtml({
|
|
536
|
+
tag: slotTag,
|
|
537
|
+
id,
|
|
538
|
+
propsAttr,
|
|
539
|
+
inner: ""
|
|
540
|
+
});
|
|
441
541
|
if (ctx?.pending) {
|
|
442
542
|
popRenderCtx();
|
|
443
543
|
try {
|
|
444
544
|
const result = island(props);
|
|
445
545
|
if (result instanceof Promise) {
|
|
446
546
|
ctx.pending.set(id, result.then(String));
|
|
447
|
-
return wrapIslandSlotHtml(
|
|
547
|
+
return wrapIslandSlotHtml({
|
|
548
|
+
tag: slotTag,
|
|
549
|
+
id,
|
|
550
|
+
propsAttr,
|
|
551
|
+
inner: asyncSlotMarker(id)
|
|
552
|
+
});
|
|
448
553
|
}
|
|
449
|
-
return wrapIslandSlotHtml(
|
|
554
|
+
return wrapIslandSlotHtml({
|
|
555
|
+
tag: slotTag,
|
|
556
|
+
id,
|
|
557
|
+
propsAttr,
|
|
558
|
+
inner: String(result)
|
|
559
|
+
});
|
|
450
560
|
} finally {
|
|
451
|
-
renderCtxStack.push(ctx);
|
|
561
|
+
renderCtxStack().push(ctx);
|
|
452
562
|
}
|
|
453
563
|
}
|
|
454
564
|
const inner = island.toString(props);
|
|
455
|
-
return wrapIslandSlotHtml(
|
|
565
|
+
return wrapIslandSlotHtml({
|
|
566
|
+
tag: slotTag,
|
|
567
|
+
id,
|
|
568
|
+
propsAttr,
|
|
569
|
+
inner
|
|
570
|
+
});
|
|
456
571
|
}
|
|
457
572
|
function asyncSlotMarker(id) {
|
|
458
573
|
return `<!--ilha-async:${escapeHtml(id)}-->`;
|
|
@@ -464,6 +579,15 @@ async function resolveAsyncChildren(html, pending) {
|
|
|
464
579
|
}
|
|
465
580
|
return html;
|
|
466
581
|
}
|
|
582
|
+
function resolveSignalSetter(read, value) {
|
|
583
|
+
if (typeof value !== "function") return value;
|
|
584
|
+
const prevSub = setActiveSub(void 0);
|
|
585
|
+
try {
|
|
586
|
+
return value(read());
|
|
587
|
+
} finally {
|
|
588
|
+
setActiveSub(prevSub);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
467
591
|
function markSignalAccessor(fn) {
|
|
468
592
|
fn[SIGNAL_ACCESSOR] = true;
|
|
469
593
|
const accessor = fn;
|
|
@@ -481,14 +605,19 @@ function getAtPath(obj, path) {
|
|
|
481
605
|
}
|
|
482
606
|
return cur;
|
|
483
607
|
}
|
|
484
|
-
function setAtPath(
|
|
608
|
+
function setAtPath({ object, path, value }) {
|
|
609
|
+
const obj = object;
|
|
485
610
|
if (path.length === 0) return value;
|
|
486
611
|
const [head, ...rest] = path;
|
|
487
612
|
if (Array.isArray(obj)) {
|
|
488
613
|
const idx = head;
|
|
489
614
|
if (idx < 0 || idx >= obj.length) return obj;
|
|
490
615
|
const next = obj[idx];
|
|
491
|
-
const updated = rest.length === 0 ? value : setAtPath(
|
|
616
|
+
const updated = rest.length === 0 ? value : setAtPath({
|
|
617
|
+
object: next,
|
|
618
|
+
path: rest,
|
|
619
|
+
value
|
|
620
|
+
});
|
|
492
621
|
if (Object.is(next, updated)) return obj;
|
|
493
622
|
const copy = obj.slice();
|
|
494
623
|
copy[idx] = updated;
|
|
@@ -498,7 +627,11 @@ function setAtPath(obj, path, value) {
|
|
|
498
627
|
const record = obj;
|
|
499
628
|
const key = String(head);
|
|
500
629
|
const next = record[key];
|
|
501
|
-
const updated = rest.length === 0 ? value : setAtPath(
|
|
630
|
+
const updated = rest.length === 0 ? value : setAtPath({
|
|
631
|
+
object: next,
|
|
632
|
+
path: rest,
|
|
633
|
+
value
|
|
634
|
+
});
|
|
502
635
|
if (rest.length === 0) {
|
|
503
636
|
if (Object.is(next, value)) return obj;
|
|
504
637
|
} else if (Object.is(next, updated)) return obj;
|
|
@@ -507,7 +640,11 @@ function setAtPath(obj, path, value) {
|
|
|
507
640
|
[key]: updated
|
|
508
641
|
};
|
|
509
642
|
}
|
|
510
|
-
return rest.length === 0 ? value : setAtPath(
|
|
643
|
+
return rest.length === 0 ? value : setAtPath({
|
|
644
|
+
object: void 0,
|
|
645
|
+
path: rest,
|
|
646
|
+
value
|
|
647
|
+
});
|
|
511
648
|
}
|
|
512
649
|
function toPathSegment(prop) {
|
|
513
650
|
if (typeof prop === "symbol") return null;
|
|
@@ -536,8 +673,15 @@ function createSelectAccessor(root, selector) {
|
|
|
536
673
|
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.");
|
|
537
674
|
return markSignalAccessor((...args) => {
|
|
538
675
|
if (args.length === 0) return path.length === 0 ? selector(root()) : getAtPath(root(), path);
|
|
539
|
-
const
|
|
540
|
-
|
|
676
|
+
const previousRoot = root();
|
|
677
|
+
const previousSelected = path.length === 0 ? selector(previousRoot) : getAtPath(previousRoot, path);
|
|
678
|
+
const value = resolveSignalSetter(() => previousSelected, args[0]);
|
|
679
|
+
const next = path.length === 0 ? value : setAtPath({
|
|
680
|
+
object: previousRoot,
|
|
681
|
+
path,
|
|
682
|
+
value
|
|
683
|
+
});
|
|
684
|
+
if (!Object.is(previousRoot, next)) root((() => next));
|
|
541
685
|
});
|
|
542
686
|
}
|
|
543
687
|
function ilhaRaw(value) {
|
|
@@ -561,9 +705,17 @@ function interpolateValue(v) {
|
|
|
561
705
|
if (isRawHtml(v)) return v.value;
|
|
562
706
|
if (isIslandCall(v)) {
|
|
563
707
|
const call = typeof v === "function" ? v() : v;
|
|
564
|
-
return emitIslandSlot(
|
|
708
|
+
return emitIslandSlot({
|
|
709
|
+
island: call.island,
|
|
710
|
+
props: call.props,
|
|
711
|
+
key: call.key
|
|
712
|
+
});
|
|
565
713
|
}
|
|
566
|
-
if (isIsland(v)) return emitIslandSlot(
|
|
714
|
+
if (isIsland(v)) return emitIslandSlot({
|
|
715
|
+
island: v,
|
|
716
|
+
props: void 0,
|
|
717
|
+
key: void 0
|
|
718
|
+
});
|
|
567
719
|
if (isSignalAccessor(v)) return escapeHtml(v());
|
|
568
720
|
if (typeof v === "function") return escapeHtml(v());
|
|
569
721
|
return escapeHtml(v);
|
|
@@ -579,6 +731,30 @@ const BIND_VALID_KINDS = /* @__PURE__ */ new Set([
|
|
|
579
731
|
"this"
|
|
580
732
|
]);
|
|
581
733
|
const BIND_PREFIX_RE = /\bbind:([a-zA-Z]+)\s*=\s*("|')?$/;
|
|
734
|
+
const EVENT_PREFIX_RE = /(?:^|\s)on([a-z][a-z0-9-]*)(?::([a-z][a-z0-9-]*))?\s*=\s*("|')?$/;
|
|
735
|
+
const NATIVE_EVENT_MODIFIERS = /* @__PURE__ */ new Set([
|
|
736
|
+
"abortable",
|
|
737
|
+
"once",
|
|
738
|
+
"capture",
|
|
739
|
+
"passive"
|
|
740
|
+
]);
|
|
741
|
+
function isEventAttributePosition(source, index) {
|
|
742
|
+
let open = -1;
|
|
743
|
+
let quote = null;
|
|
744
|
+
for (let i = 0; i < index; i++) {
|
|
745
|
+
const char = source[i];
|
|
746
|
+
if (open === -1) {
|
|
747
|
+
if (char === "<") open = i;
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if (quote) {
|
|
751
|
+
if (char === quote) quote = null;
|
|
752
|
+
} else if (char === "\"" || char === "'") quote = char;
|
|
753
|
+
else if (char === ">") open = -1;
|
|
754
|
+
}
|
|
755
|
+
if (open === -1 || quote !== null) return false;
|
|
756
|
+
return !/^\s*[!/?]/.test(source.slice(open + 1, index));
|
|
757
|
+
}
|
|
582
758
|
function findTagCloseIndex(chunk, initialQuote) {
|
|
583
759
|
let quote = initialQuote;
|
|
584
760
|
for (let i = 0; i < chunk.length; i++) {
|
|
@@ -613,7 +789,7 @@ function extractElementStaticValue(prefix) {
|
|
|
613
789
|
if (!m) return null;
|
|
614
790
|
return m[2] ?? m[3] ?? m[4] ?? null;
|
|
615
791
|
}
|
|
616
|
-
function emitBindSSR(kind, index, accessor, prefixForStaticPeek) {
|
|
792
|
+
function emitBindSSR({ kind, index, accessor, prefixForStaticPeek }) {
|
|
617
793
|
const spec = `${kind}:${index}`;
|
|
618
794
|
let v;
|
|
619
795
|
try {
|
|
@@ -640,6 +816,7 @@ function ilhaHtml(strings, ...values) {
|
|
|
640
816
|
let result = "";
|
|
641
817
|
let stripLeadingQuote = null;
|
|
642
818
|
let pendingBindSpecs = "";
|
|
819
|
+
let pendingEventSpecs = "";
|
|
643
820
|
let pendingQuote = null;
|
|
644
821
|
const ctx = currentRenderCtx();
|
|
645
822
|
for (let i = 0; i < strings.length; i++) {
|
|
@@ -648,11 +825,15 @@ function ilhaHtml(strings, ...values) {
|
|
|
648
825
|
if (chunk.startsWith(stripLeadingQuote)) chunk = chunk.slice(1);
|
|
649
826
|
stripLeadingQuote = null;
|
|
650
827
|
}
|
|
651
|
-
if (pendingBindSpecs !== "") {
|
|
828
|
+
if (pendingBindSpecs !== "" || pendingEventSpecs !== "") {
|
|
652
829
|
const { index: gtIdx, quote } = findTagCloseIndex(chunk, pendingQuote);
|
|
653
830
|
if (gtIdx !== -1) {
|
|
654
|
-
|
|
831
|
+
const head = chunk.slice(0, gtIdx).replace(/\s*\/\s*$/, "");
|
|
832
|
+
const bindAttr = pendingBindSpecs ? ` data-ilha-bind="${pendingBindSpecs}"` : "";
|
|
833
|
+
const eventAttr = pendingEventSpecs ? ` data-ilha-on="${pendingEventSpecs}"` : "";
|
|
834
|
+
chunk = head + bindAttr + eventAttr + ">" + chunk.slice(gtIdx + 1);
|
|
655
835
|
pendingBindSpecs = "";
|
|
836
|
+
pendingEventSpecs = "";
|
|
656
837
|
pendingQuote = null;
|
|
657
838
|
} else pendingQuote = quote;
|
|
658
839
|
}
|
|
@@ -661,6 +842,29 @@ function ilhaHtml(strings, ...values) {
|
|
|
661
842
|
continue;
|
|
662
843
|
}
|
|
663
844
|
const value = values[i];
|
|
845
|
+
const eventMatch = chunk.match(EVENT_PREFIX_RE);
|
|
846
|
+
if (eventMatch && isEventAttributePosition(result + chunk, result.length + chunk.length - eventMatch[0].length)) {
|
|
847
|
+
const eventName = eventMatch[1];
|
|
848
|
+
const rawModifier = eventMatch[2];
|
|
849
|
+
const modifier = NATIVE_EVENT_MODIFIERS.has(rawModifier) ? rawModifier : void 0;
|
|
850
|
+
const openQuote = eventMatch[3] ?? null;
|
|
851
|
+
const matchStart = chunk.length - eventMatch[0].length;
|
|
852
|
+
const cleanPrefix = chunk.slice(0, matchStart).replace(/\s+$/, "");
|
|
853
|
+
result += cleanPrefix;
|
|
854
|
+
if (ctx && typeof value === "function") {
|
|
855
|
+
const index = ctx.events.length;
|
|
856
|
+
if (rawModifier && !modifier) {} else {
|
|
857
|
+
ctx.events.push({
|
|
858
|
+
type: eventName,
|
|
859
|
+
handler: value,
|
|
860
|
+
modifier
|
|
861
|
+
});
|
|
862
|
+
pendingEventSpecs += (pendingEventSpecs ? "," : "") + `${eventName}:${index}`;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (openQuote) stripLeadingQuote = openQuote;
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
664
868
|
const m = chunk.match(BIND_PREFIX_RE);
|
|
665
869
|
if (m && isSignalAccessor(value)) {
|
|
666
870
|
const name = m[1];
|
|
@@ -683,7 +887,12 @@ function ilhaHtml(strings, ...values) {
|
|
|
683
887
|
kind: name,
|
|
684
888
|
accessor: value
|
|
685
889
|
});
|
|
686
|
-
const [valueAttrs, spec] = emitBindSSR(
|
|
890
|
+
const [valueAttrs, spec] = emitBindSSR({
|
|
891
|
+
kind: name,
|
|
892
|
+
index,
|
|
893
|
+
accessor: value,
|
|
894
|
+
prefixForStaticPeek: cleanPrefix
|
|
895
|
+
});
|
|
687
896
|
result += cleanPrefix + valueAttrs;
|
|
688
897
|
pendingBindSpecs += (pendingBindSpecs ? "," : "") + spec;
|
|
689
898
|
if (openQuote) stripLeadingQuote = openQuote;
|
|
@@ -692,6 +901,7 @@ function ilhaHtml(strings, ...values) {
|
|
|
692
901
|
result += chunk + interpolateValue(value);
|
|
693
902
|
}
|
|
694
903
|
if (pendingBindSpecs !== "") result += ` data-ilha-bind="${pendingBindSpecs}"`;
|
|
904
|
+
if (pendingEventSpecs !== "") result += ` data-ilha-on="${pendingEventSpecs}"`;
|
|
695
905
|
return {
|
|
696
906
|
[RAW]: true,
|
|
697
907
|
value: dedentString(result)
|
|
@@ -710,7 +920,7 @@ function ilhaContextFn(key, initial) {
|
|
|
710
920
|
const s = signal(initial);
|
|
711
921
|
const accessor = (...args) => {
|
|
712
922
|
if (args.length === 0) return s();
|
|
713
|
-
s(args[0]);
|
|
923
|
+
s(resolveSignalSetter(() => s(), args[0]));
|
|
714
924
|
};
|
|
715
925
|
contextRegistry.set(key, accessor);
|
|
716
926
|
return accessor;
|
|
@@ -740,7 +950,7 @@ function ilhaSignal(initial) {
|
|
|
740
950
|
const s = signal(initial);
|
|
741
951
|
return markSignalAccessor((...args) => {
|
|
742
952
|
if (args.length === 0) return s();
|
|
743
|
-
s(args[0]);
|
|
953
|
+
s(resolveSignalSetter(() => s(), args[0]));
|
|
744
954
|
});
|
|
745
955
|
}
|
|
746
956
|
/**
|
|
@@ -867,7 +1077,7 @@ function buildDerivedAccessors(envelopes) {
|
|
|
867
1077
|
return accessors.get(key) ?? defaultDerivedAccessor();
|
|
868
1078
|
} });
|
|
869
1079
|
}
|
|
870
|
-
function createDerivedProxy(entries, state, input, derivedSnapshot) {
|
|
1080
|
+
function createDerivedProxy({ entries, state, input, derivedSnapshot }) {
|
|
871
1081
|
const envelopes = /* @__PURE__ */ new Map();
|
|
872
1082
|
for (const entry of entries) {
|
|
873
1083
|
let initialEnvelope;
|
|
@@ -974,9 +1184,10 @@ function createDerivedProxy(entries, state, input, derivedSnapshot) {
|
|
|
974
1184
|
return;
|
|
975
1185
|
}
|
|
976
1186
|
const prevSub = setActiveSub(void 0);
|
|
1187
|
+
const prevVal = env();
|
|
977
1188
|
env({
|
|
978
1189
|
loading: true,
|
|
979
|
-
value:
|
|
1190
|
+
value: prevVal.value,
|
|
980
1191
|
error: void 0
|
|
981
1192
|
});
|
|
982
1193
|
setActiveSub(prevSub);
|
|
@@ -1009,6 +1220,7 @@ function createDerivedProxy(entries, state, input, derivedSnapshot) {
|
|
|
1009
1220
|
};
|
|
1010
1221
|
}
|
|
1011
1222
|
const BIND_SENTINEL_ATTR = "data-ilha-bind";
|
|
1223
|
+
const EVENT_SENTINEL_ATTR = "data-ilha-on";
|
|
1012
1224
|
function parseBindSentinel(value) {
|
|
1013
1225
|
const out = [];
|
|
1014
1226
|
for (const part of value.split(",")) {
|
|
@@ -1088,12 +1300,86 @@ function resolveBindOps(el, kind) {
|
|
|
1088
1300
|
}
|
|
1089
1301
|
}
|
|
1090
1302
|
}
|
|
1303
|
+
function templateElementBelongsToHost(host, candidate) {
|
|
1304
|
+
if (candidate === host) return true;
|
|
1305
|
+
let current = candidate;
|
|
1306
|
+
while (current && current !== host) {
|
|
1307
|
+
if (current.hasAttribute(SLOT_ATTR) || current.hasAttribute("data-ilha")) return false;
|
|
1308
|
+
current = current.parentElement;
|
|
1309
|
+
}
|
|
1310
|
+
return current === host;
|
|
1311
|
+
}
|
|
1312
|
+
function applyJsxEvents({ host, records, reportError, unmountSignal, onceFired }) {
|
|
1313
|
+
if (records.length === 0) return () => {};
|
|
1314
|
+
const cleanups = [];
|
|
1315
|
+
const elements = [];
|
|
1316
|
+
if (host.hasAttribute(EVENT_SENTINEL_ATTR)) elements.push(host);
|
|
1317
|
+
elements.push(...Array.from(host.querySelectorAll(`[${EVENT_SENTINEL_ATTR}]`)).filter((element) => templateElementBelongsToHost(host, element)));
|
|
1318
|
+
for (const el of elements) {
|
|
1319
|
+
const sentinel = el.getAttribute(EVENT_SENTINEL_ATTR) ?? "";
|
|
1320
|
+
for (const part of sentinel.split(",")) {
|
|
1321
|
+
const separator = part.lastIndexOf(":");
|
|
1322
|
+
if (separator < 1) continue;
|
|
1323
|
+
const type = part.slice(0, separator);
|
|
1324
|
+
const index = Number(part.slice(separator + 1));
|
|
1325
|
+
if (!Number.isInteger(index) || index < 0) continue;
|
|
1326
|
+
const record = records[index];
|
|
1327
|
+
if (!record || record.type !== type) continue;
|
|
1328
|
+
if (record.modifier === "once" && onceFired.get(el)?.has(type)) continue;
|
|
1329
|
+
const listenerController = new AbortController();
|
|
1330
|
+
let invocationController;
|
|
1331
|
+
const options = {
|
|
1332
|
+
once: record.modifier === "once",
|
|
1333
|
+
capture: record.modifier === "capture",
|
|
1334
|
+
passive: record.modifier === "passive"
|
|
1335
|
+
};
|
|
1336
|
+
const reportHandlerError = (error) => {
|
|
1337
|
+
if (error?.name !== "AbortError") reportError(error);
|
|
1338
|
+
};
|
|
1339
|
+
const listener = (event) => {
|
|
1340
|
+
if (record.modifier === "once") {
|
|
1341
|
+
let fired = onceFired.get(el);
|
|
1342
|
+
if (!fired) onceFired.set(el, fired = /* @__PURE__ */ new Set());
|
|
1343
|
+
fired.add(type);
|
|
1344
|
+
}
|
|
1345
|
+
if (record.modifier === "abortable") {
|
|
1346
|
+
invocationController?.abort();
|
|
1347
|
+
invocationController = new AbortController();
|
|
1348
|
+
}
|
|
1349
|
+
const signal = invocationController ? anySignal([listenerController.signal, invocationController.signal]) : listenerController.signal;
|
|
1350
|
+
let result;
|
|
1351
|
+
startBatch();
|
|
1352
|
+
try {
|
|
1353
|
+
result = record.handler(event, { signal });
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
reportHandlerError(error);
|
|
1356
|
+
return;
|
|
1357
|
+
} finally {
|
|
1358
|
+
endBatch();
|
|
1359
|
+
}
|
|
1360
|
+
if (result != null && typeof result.then === "function") Promise.resolve(result).catch(reportHandlerError);
|
|
1361
|
+
};
|
|
1362
|
+
const stop = () => {
|
|
1363
|
+
listenerController.abort();
|
|
1364
|
+
invocationController?.abort();
|
|
1365
|
+
el.removeEventListener(type, listener, options);
|
|
1366
|
+
};
|
|
1367
|
+
el.addEventListener(type, listener, options);
|
|
1368
|
+
unmountSignal.addEventListener("abort", stop, { once: true });
|
|
1369
|
+
cleanups.push(() => {
|
|
1370
|
+
unmountSignal.removeEventListener("abort", stop);
|
|
1371
|
+
stop();
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
return () => cleanups.forEach((cleanup) => cleanup());
|
|
1376
|
+
}
|
|
1091
1377
|
function applyTemplateBindings(host, binds) {
|
|
1092
1378
|
if (binds.length === 0) return () => {};
|
|
1093
1379
|
const cleanups = [];
|
|
1094
1380
|
const elements = [];
|
|
1095
1381
|
if (host.hasAttribute(BIND_SENTINEL_ATTR)) elements.push(host);
|
|
1096
|
-
for (const el of host.querySelectorAll(`[${BIND_SENTINEL_ATTR}]`)) elements.push(el);
|
|
1382
|
+
for (const el of host.querySelectorAll(`[${BIND_SENTINEL_ATTR}]`)) if (templateElementBelongsToHost(host, el)) elements.push(el);
|
|
1097
1383
|
for (const el of elements) {
|
|
1098
1384
|
const specs = parseBindSentinel(el.getAttribute(BIND_SENTINEL_ATTR));
|
|
1099
1385
|
for (const spec of specs) {
|
|
@@ -1235,6 +1521,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1235
1521
|
defaultInput,
|
|
1236
1522
|
states: [],
|
|
1237
1523
|
deriveds: [],
|
|
1524
|
+
actions: [],
|
|
1238
1525
|
ons: [],
|
|
1239
1526
|
effects: [],
|
|
1240
1527
|
onMounts: [],
|
|
@@ -1270,6 +1557,15 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1270
1557
|
}]
|
|
1271
1558
|
});
|
|
1272
1559
|
}
|
|
1560
|
+
action(key, fn) {
|
|
1561
|
+
return new IlhaBuilder({
|
|
1562
|
+
...this._cfg,
|
|
1563
|
+
actions: [...this._cfg.actions, {
|
|
1564
|
+
key,
|
|
1565
|
+
fn
|
|
1566
|
+
}]
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1273
1569
|
on(selectorOrCombined, handler) {
|
|
1274
1570
|
const parsed = parseOnArgs(selectorOrCombined);
|
|
1275
1571
|
return new IlhaBuilder({
|
|
@@ -1324,7 +1620,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1324
1620
|
});
|
|
1325
1621
|
}
|
|
1326
1622
|
render(fn) {
|
|
1327
|
-
const { schema, defaultInput, states, deriveds, ons, effects, onMounts, onErrors, transition, css: cssSource, as: slotAs } = this._cfg;
|
|
1623
|
+
const { schema, defaultInput, states, deriveds, actions, ons, effects, onMounts, onErrors, transition, css: cssSource, as: slotAs } = this._cfg;
|
|
1328
1624
|
const stylePrefix = cssSource != null ? buildScopedStyle(cssSource) : "";
|
|
1329
1625
|
const configuredSlotTag = slotAs ?? "div";
|
|
1330
1626
|
function resolveInput(props) {
|
|
@@ -1335,26 +1631,29 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1335
1631
|
if (!schema) return merged;
|
|
1336
1632
|
return validateSchema(schema, merged);
|
|
1337
1633
|
}
|
|
1338
|
-
function renderWithCtx(
|
|
1634
|
+
function renderWithCtx({ render, liveHost, asyncChildren }) {
|
|
1339
1635
|
const ctx = pushRenderCtx(liveHost, asyncChildren);
|
|
1340
1636
|
try {
|
|
1341
|
-
const html = unwrapHtml(
|
|
1637
|
+
const html = unwrapHtml(render());
|
|
1342
1638
|
const slots = ctx.slots;
|
|
1343
1639
|
const binds = ctx.binds;
|
|
1640
|
+
const events = ctx.events;
|
|
1344
1641
|
if (ctx.pending && ctx.pending.size > 0) {
|
|
1345
1642
|
const pending = ctx.pending;
|
|
1346
1643
|
return (async () => {
|
|
1347
1644
|
return {
|
|
1348
1645
|
html: await resolveAsyncChildren(html, pending),
|
|
1349
1646
|
slots,
|
|
1350
|
-
binds
|
|
1647
|
+
binds,
|
|
1648
|
+
events
|
|
1351
1649
|
};
|
|
1352
1650
|
})();
|
|
1353
1651
|
}
|
|
1354
1652
|
return {
|
|
1355
1653
|
html,
|
|
1356
1654
|
slots,
|
|
1357
|
-
binds
|
|
1655
|
+
binds,
|
|
1656
|
+
events
|
|
1358
1657
|
};
|
|
1359
1658
|
} finally {
|
|
1360
1659
|
popRenderCtx();
|
|
@@ -1377,12 +1676,34 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1377
1676
|
const s = signal(snapshot && entry.key in snapshot ? snapshot[entry.key] : typeof entry.init === "function" ? entry.init(input) : entry.init);
|
|
1378
1677
|
const accessor = markSignalAccessor((...args) => {
|
|
1379
1678
|
if (args.length === 0) return s();
|
|
1380
|
-
s(args[0]);
|
|
1679
|
+
s(resolveSignalSetter(() => s(), args[0]));
|
|
1381
1680
|
});
|
|
1382
1681
|
state[entry.key] = accessor;
|
|
1383
1682
|
}
|
|
1384
1683
|
return state;
|
|
1385
1684
|
}
|
|
1685
|
+
function buildSsrActions() {
|
|
1686
|
+
const out = {};
|
|
1687
|
+
for (const entry of actions) {
|
|
1688
|
+
const invoke = () => {};
|
|
1689
|
+
Object.defineProperties(invoke, {
|
|
1690
|
+
pending: {
|
|
1691
|
+
get: () => false,
|
|
1692
|
+
enumerable: true
|
|
1693
|
+
},
|
|
1694
|
+
data: {
|
|
1695
|
+
get: () => void 0,
|
|
1696
|
+
enumerable: true
|
|
1697
|
+
},
|
|
1698
|
+
error: {
|
|
1699
|
+
get: () => void 0,
|
|
1700
|
+
enumerable: true
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
out[entry.key] = invoke;
|
|
1704
|
+
}
|
|
1705
|
+
return out;
|
|
1706
|
+
}
|
|
1386
1707
|
/** Detached host for SSR setup hooks; must not be `{}` (Areia/components call host.matches). */
|
|
1387
1708
|
function createSsrOnMountHost() {
|
|
1388
1709
|
if (typeof document !== "undefined") return document.createElement("div");
|
|
@@ -1398,18 +1719,19 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1398
1719
|
return stub;
|
|
1399
1720
|
}
|
|
1400
1721
|
/** Run .onMount() before top-level SSR only (seed module/external state from `input`). */
|
|
1401
|
-
function runOnMountForRender(input, state, derived) {
|
|
1722
|
+
function runOnMountForRender({ input, state, derived, action }) {
|
|
1402
1723
|
if (onMounts.length === 0) return;
|
|
1403
1724
|
if (currentRenderCtx()) return;
|
|
1404
1725
|
const host = createSsrOnMountHost();
|
|
1405
1726
|
const ssrCleanups = [];
|
|
1406
1727
|
for (const entry of onMounts) {
|
|
1407
1728
|
const prevSub = setActiveSub(void 0);
|
|
1408
|
-
let userCleanup
|
|
1729
|
+
let userCleanup;
|
|
1409
1730
|
try {
|
|
1410
1731
|
userCleanup = entry.fn({
|
|
1411
1732
|
state,
|
|
1412
1733
|
derived,
|
|
1734
|
+
action,
|
|
1413
1735
|
input,
|
|
1414
1736
|
host,
|
|
1415
1737
|
hydrated: false
|
|
@@ -1424,6 +1746,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1424
1746
|
source: "mount",
|
|
1425
1747
|
state,
|
|
1426
1748
|
derived,
|
|
1749
|
+
action,
|
|
1427
1750
|
input,
|
|
1428
1751
|
host
|
|
1429
1752
|
});
|
|
@@ -1447,6 +1770,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1447
1770
|
function renderToString(props, sync = false) {
|
|
1448
1771
|
const input = resolveInput(props);
|
|
1449
1772
|
const state = buildSignalState(input);
|
|
1773
|
+
const action = buildSsrActions();
|
|
1450
1774
|
const results = deriveds.map((entry) => {
|
|
1451
1775
|
const prevSub = setActiveSub(void 0);
|
|
1452
1776
|
try {
|
|
@@ -1480,14 +1804,20 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1480
1804
|
error: void 0
|
|
1481
1805
|
};
|
|
1482
1806
|
const derived = buildDerivedAccessors(envelopes);
|
|
1483
|
-
if (!currentRenderCtx()) runOnMountForRender(
|
|
1807
|
+
if (!currentRenderCtx()) runOnMountForRender({
|
|
1808
|
+
input,
|
|
1809
|
+
state,
|
|
1810
|
+
derived,
|
|
1811
|
+
action
|
|
1812
|
+
});
|
|
1484
1813
|
const prevSub = setActiveSub(void 0);
|
|
1485
1814
|
try {
|
|
1486
|
-
const { html } = renderWithCtx(() => fn({
|
|
1815
|
+
const { html } = renderWithCtx({ render: () => fn({
|
|
1487
1816
|
state,
|
|
1488
1817
|
derived,
|
|
1818
|
+
action,
|
|
1489
1819
|
input
|
|
1490
|
-
}));
|
|
1820
|
+
}) });
|
|
1491
1821
|
return stylePrefix + html;
|
|
1492
1822
|
} finally {
|
|
1493
1823
|
setActiveSub(prevSub);
|
|
@@ -1517,14 +1847,23 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1517
1847
|
const envelopes = {};
|
|
1518
1848
|
for (const r of resolved) envelopes[r.key] = r.envelope;
|
|
1519
1849
|
const derived = buildDerivedAccessors(envelopes);
|
|
1520
|
-
if (!currentRenderCtx()) runOnMountForRender(
|
|
1850
|
+
if (!currentRenderCtx()) runOnMountForRender({
|
|
1851
|
+
input,
|
|
1852
|
+
state,
|
|
1853
|
+
derived,
|
|
1854
|
+
action
|
|
1855
|
+
});
|
|
1521
1856
|
const prevSub = setActiveSub(void 0);
|
|
1522
1857
|
try {
|
|
1523
|
-
const { html } = await renderWithCtx(
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1858
|
+
const { html } = await renderWithCtx({
|
|
1859
|
+
render: () => fn({
|
|
1860
|
+
state,
|
|
1861
|
+
derived,
|
|
1862
|
+
action,
|
|
1863
|
+
input
|
|
1864
|
+
}),
|
|
1865
|
+
asyncChildren: true
|
|
1866
|
+
});
|
|
1528
1867
|
return stylePrefix + html;
|
|
1529
1868
|
} finally {
|
|
1530
1869
|
setActiveSub(prevSub);
|
|
@@ -1539,7 +1878,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1539
1878
|
const rawProps = host.getAttribute(PROPS_ATTR);
|
|
1540
1879
|
if (rawProps) {
|
|
1541
1880
|
const parsed = safeParseSnapshot(rawProps, PROPS_ATTR);
|
|
1542
|
-
if (parsed !== void 0) props = parsed;
|
|
1881
|
+
if (parsed !== void 0) props = reviveSlotProps(parsed);
|
|
1543
1882
|
}
|
|
1544
1883
|
}
|
|
1545
1884
|
const inputSignal = signal(resolveInput(props));
|
|
@@ -1591,8 +1930,14 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1591
1930
|
const cleanups = [];
|
|
1592
1931
|
const unmountController = new AbortController();
|
|
1593
1932
|
cleanups.push(() => unmountController.abort());
|
|
1594
|
-
const { proxy: derived, setup: setupDerived } = createDerivedProxy(
|
|
1933
|
+
const { proxy: derived, setup: setupDerived } = createDerivedProxy({
|
|
1934
|
+
entries: deriveds,
|
|
1935
|
+
state,
|
|
1936
|
+
input,
|
|
1937
|
+
derivedSnapshot
|
|
1938
|
+
});
|
|
1595
1939
|
let stopDerived = () => {};
|
|
1940
|
+
let action = {};
|
|
1596
1941
|
function reportError(err, source) {
|
|
1597
1942
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1598
1943
|
if (onErrors.length === 0) {
|
|
@@ -1605,6 +1950,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1605
1950
|
source,
|
|
1606
1951
|
state,
|
|
1607
1952
|
derived,
|
|
1953
|
+
action,
|
|
1608
1954
|
input,
|
|
1609
1955
|
host
|
|
1610
1956
|
});
|
|
@@ -1612,6 +1958,107 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1612
1958
|
console.error(handlerErr);
|
|
1613
1959
|
}
|
|
1614
1960
|
}
|
|
1961
|
+
const actionEntries = actions;
|
|
1962
|
+
const actionObject = {};
|
|
1963
|
+
let disposed = false;
|
|
1964
|
+
cleanups.push(() => {
|
|
1965
|
+
disposed = true;
|
|
1966
|
+
});
|
|
1967
|
+
for (const entry of actionEntries) {
|
|
1968
|
+
const envelope = signal({
|
|
1969
|
+
pending: 0,
|
|
1970
|
+
data: void 0,
|
|
1971
|
+
error: void 0
|
|
1972
|
+
});
|
|
1973
|
+
const readEnvelope = () => untrack(() => envelope());
|
|
1974
|
+
let latestRun = 0;
|
|
1975
|
+
const invoke = (props) => {
|
|
1976
|
+
if (currentRenderCtx()) return;
|
|
1977
|
+
if (disposed) return;
|
|
1978
|
+
const run = ++latestRun;
|
|
1979
|
+
const runSignal = unmountController.signal;
|
|
1980
|
+
const ctx = {
|
|
1981
|
+
state,
|
|
1982
|
+
derived,
|
|
1983
|
+
input,
|
|
1984
|
+
host,
|
|
1985
|
+
signal: runSignal
|
|
1986
|
+
};
|
|
1987
|
+
let result;
|
|
1988
|
+
let isAsync = false;
|
|
1989
|
+
startBatch();
|
|
1990
|
+
try {
|
|
1991
|
+
result = entry.fn(props, ctx);
|
|
1992
|
+
isAsync = result != null && typeof result.then === "function";
|
|
1993
|
+
const current = readEnvelope();
|
|
1994
|
+
envelope(isAsync ? {
|
|
1995
|
+
pending: current.pending + 1,
|
|
1996
|
+
data: current.data,
|
|
1997
|
+
error: void 0
|
|
1998
|
+
} : {
|
|
1999
|
+
pending: current.pending,
|
|
2000
|
+
data: result,
|
|
2001
|
+
error: void 0
|
|
2002
|
+
});
|
|
2003
|
+
} catch (error) {
|
|
2004
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
2005
|
+
envelope({
|
|
2006
|
+
pending: readEnvelope().pending,
|
|
2007
|
+
data: void 0,
|
|
2008
|
+
error: normalized
|
|
2009
|
+
});
|
|
2010
|
+
reportError(normalized, "action");
|
|
2011
|
+
return;
|
|
2012
|
+
} finally {
|
|
2013
|
+
endBatch();
|
|
2014
|
+
}
|
|
2015
|
+
if (!isAsync) return;
|
|
2016
|
+
Promise.resolve(result).then((data) => {
|
|
2017
|
+
if (disposed || runSignal.aborted || run !== latestRun) return;
|
|
2018
|
+
const value = readEnvelope();
|
|
2019
|
+
envelope({
|
|
2020
|
+
pending: value.pending,
|
|
2021
|
+
data,
|
|
2022
|
+
error: void 0
|
|
2023
|
+
});
|
|
2024
|
+
}).catch((error) => {
|
|
2025
|
+
if (disposed || runSignal.aborted || error?.name === "AbortError") return;
|
|
2026
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
2027
|
+
if (run === latestRun) {
|
|
2028
|
+
const value = readEnvelope();
|
|
2029
|
+
envelope({
|
|
2030
|
+
pending: value.pending,
|
|
2031
|
+
data: void 0,
|
|
2032
|
+
error: normalized
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
reportError(normalized, "action");
|
|
2036
|
+
}).finally(() => {
|
|
2037
|
+
if (disposed) return;
|
|
2038
|
+
const value = readEnvelope();
|
|
2039
|
+
envelope({
|
|
2040
|
+
...value,
|
|
2041
|
+
pending: Math.max(0, value.pending - 1)
|
|
2042
|
+
});
|
|
2043
|
+
});
|
|
2044
|
+
};
|
|
2045
|
+
Object.defineProperties(invoke, {
|
|
2046
|
+
pending: {
|
|
2047
|
+
get: () => envelope().pending > 0,
|
|
2048
|
+
enumerable: true
|
|
2049
|
+
},
|
|
2050
|
+
data: {
|
|
2051
|
+
get: () => envelope().data,
|
|
2052
|
+
enumerable: true
|
|
2053
|
+
},
|
|
2054
|
+
error: {
|
|
2055
|
+
get: () => envelope().error,
|
|
2056
|
+
enumerable: true
|
|
2057
|
+
}
|
|
2058
|
+
});
|
|
2059
|
+
actionObject[entry.key] = invoke;
|
|
2060
|
+
}
|
|
2061
|
+
action = actionObject;
|
|
1615
2062
|
if (transition?.enter) try {
|
|
1616
2063
|
const result = transition.enter(host);
|
|
1617
2064
|
if (result instanceof Promise) result.catch((err) => reportError(err, "transition"));
|
|
@@ -1661,6 +2108,13 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1661
2108
|
}
|
|
1662
2109
|
return index;
|
|
1663
2110
|
}
|
|
2111
|
+
/** Push live props from a fresh slot map into already-mounted children. */
|
|
2112
|
+
function pushUpdatedProps(nextSlots) {
|
|
2113
|
+
for (const [id, entry] of mountedSlots) {
|
|
2114
|
+
const next = nextSlots.get(id);
|
|
2115
|
+
if (next) entry.updateProps(next.props);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
1664
2118
|
function mountSlots(slotMap) {
|
|
1665
2119
|
for (const [id, entry] of mountedSlots) if (!slotMap.has(id)) teardownMountedSlot(id, entry);
|
|
1666
2120
|
for (const [id, entry] of mountedSlots) {
|
|
@@ -1682,7 +2136,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1682
2136
|
const rawProps = slotEl.getAttribute(PROPS_ATTR) ?? slotEl.getAttribute("data-props");
|
|
1683
2137
|
if (rawProps) {
|
|
1684
2138
|
const parsed = safeParseSnapshot(rawProps, `props on [${SLOT_ATTR}="${id}"]`);
|
|
1685
|
-
if (parsed !== void 0) slotProps = parsed;
|
|
2139
|
+
if (parsed !== void 0) slotProps = reviveSlotProps(parsed);
|
|
1686
2140
|
}
|
|
1687
2141
|
}
|
|
1688
2142
|
const prevSub = setActiveSub(void 0);
|
|
@@ -1709,7 +2163,13 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1709
2163
|
function attachListeners() {
|
|
1710
2164
|
for (const entry of ons) {
|
|
1711
2165
|
if (entry.options.once && firedOnce.has(entry)) continue;
|
|
1712
|
-
|
|
2166
|
+
let targets;
|
|
2167
|
+
try {
|
|
2168
|
+
targets = entry.selector === "" ? [host] : Array.from(host.querySelectorAll(entry.selector)).filter((element) => templateElementBelongsToHost(host, element));
|
|
2169
|
+
} catch {
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
targets.forEach((listenerTarget) => {
|
|
1713
2173
|
const listener = (event) => {
|
|
1714
2174
|
if (entry.options.once) {
|
|
1715
2175
|
firedOnce.add(entry);
|
|
@@ -1737,6 +2197,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1737
2197
|
result = entry.handler({
|
|
1738
2198
|
state,
|
|
1739
2199
|
derived,
|
|
2200
|
+
action,
|
|
1740
2201
|
input,
|
|
1741
2202
|
host,
|
|
1742
2203
|
target: handlerTarget,
|
|
@@ -1776,11 +2237,12 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1776
2237
|
function invokeOnMounts() {
|
|
1777
2238
|
for (const entry of onMounts) {
|
|
1778
2239
|
const prevSub = setActiveSub(void 0);
|
|
1779
|
-
let userCleanup
|
|
2240
|
+
let userCleanup;
|
|
1780
2241
|
try {
|
|
1781
2242
|
userCleanup = entry.fn({
|
|
1782
2243
|
state,
|
|
1783
2244
|
derived,
|
|
2245
|
+
action,
|
|
1784
2246
|
input,
|
|
1785
2247
|
host,
|
|
1786
2248
|
hydrated
|
|
@@ -1804,14 +2266,27 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1804
2266
|
}
|
|
1805
2267
|
const preserveSSRDom = hydrated && host.childNodes.length > 0;
|
|
1806
2268
|
if (hydrated && onMounts.length > 0 && !shouldSkipOnMount) invokeOnMounts();
|
|
1807
|
-
const
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
2269
|
+
const nativeOnceFired = /* @__PURE__ */ new WeakMap();
|
|
2270
|
+
const initial = renderWithCtx({
|
|
2271
|
+
render: () => fn({
|
|
2272
|
+
state,
|
|
2273
|
+
derived,
|
|
2274
|
+
action,
|
|
2275
|
+
input
|
|
2276
|
+
}),
|
|
2277
|
+
liveHost: preserveSSRDom ? host : void 0
|
|
2278
|
+
});
|
|
1812
2279
|
if (!preserveSSRDom) host.innerHTML = stylePrefix + initial.html;
|
|
1813
2280
|
let stopBindings = applyTemplateBindings(host, initial.binds);
|
|
1814
2281
|
cleanups.push(() => stopBindings());
|
|
2282
|
+
let stopJsxEvents = applyJsxEvents({
|
|
2283
|
+
host,
|
|
2284
|
+
records: initial.events,
|
|
2285
|
+
reportError: (error) => reportError(error, "on"),
|
|
2286
|
+
unmountSignal: unmountController.signal,
|
|
2287
|
+
onceFired: nativeOnceFired
|
|
2288
|
+
});
|
|
2289
|
+
cleanups.push(() => stopJsxEvents());
|
|
1815
2290
|
mountSlots(initial.slots);
|
|
1816
2291
|
cleanups.push(() => mountedSlots.forEach((entry) => entry.unmount()));
|
|
1817
2292
|
attachListeners();
|
|
@@ -1836,6 +2311,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1836
2311
|
userCleanup = entry.fn({
|
|
1837
2312
|
state,
|
|
1838
2313
|
derived,
|
|
2314
|
+
action,
|
|
1839
2315
|
input,
|
|
1840
2316
|
host,
|
|
1841
2317
|
signal: runSignal
|
|
@@ -1864,19 +2340,28 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1864
2340
|
let renderEpoch = 0;
|
|
1865
2341
|
const stopRender = effect(() => {
|
|
1866
2342
|
const epoch = ++renderEpoch;
|
|
1867
|
-
const { html: rendered, slots: newSlotMap, binds: newBinds } = renderWithCtx(
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
2343
|
+
const { html: rendered, slots: newSlotMap, binds: newBinds, events: newEvents } = renderWithCtx({
|
|
2344
|
+
render: () => fn({
|
|
2345
|
+
state,
|
|
2346
|
+
derived,
|
|
2347
|
+
action,
|
|
2348
|
+
input
|
|
2349
|
+
}),
|
|
2350
|
+
liveHost: host
|
|
2351
|
+
});
|
|
2352
|
+
stopJsxEvents();
|
|
2353
|
+
stopJsxEvents = applyJsxEvents({
|
|
2354
|
+
host,
|
|
2355
|
+
records: newEvents,
|
|
2356
|
+
reportError: (error) => reportError(error, "on"),
|
|
2357
|
+
unmountSignal: unmountController.signal,
|
|
2358
|
+
onceFired: nativeOnceFired
|
|
2359
|
+
});
|
|
1872
2360
|
const html = stylePrefix + rendered;
|
|
1873
2361
|
if (!initialized) {
|
|
1874
2362
|
initialized = true;
|
|
1875
2363
|
if (preserveSSRDom) {
|
|
1876
|
-
|
|
1877
|
-
const next = newSlotMap.get(id);
|
|
1878
|
-
if (next) entry.updateProps(next.props);
|
|
1879
|
-
}
|
|
2364
|
+
pushUpdatedProps(newSlotMap);
|
|
1880
2365
|
if (rendered === initialRenderedHtml) {
|
|
1881
2366
|
lastRendered = rendered;
|
|
1882
2367
|
return;
|
|
@@ -1886,17 +2371,22 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1886
2371
|
lastRendered = rendered;
|
|
1887
2372
|
return;
|
|
1888
2373
|
}
|
|
1889
|
-
if (mountedSlots.size > 0 && mountedSlots.size === newSlotMap.size && [...newSlotMap.keys()].every((id) => mountedSlots.has(id)) && isStableInlineSlotMount(
|
|
2374
|
+
if (mountedSlots.size > 0 && mountedSlots.size === newSlotMap.size && [...newSlotMap.keys()].every((id) => mountedSlots.has(id)) && isStableInlineSlotMount({
|
|
2375
|
+
initialHtml: initialRenderedHtml,
|
|
2376
|
+
renderedHtml: rendered,
|
|
2377
|
+
slotIds: newSlotMap.keys()
|
|
2378
|
+
})) {
|
|
2379
|
+
pushUpdatedProps(newSlotMap);
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
1890
2382
|
}
|
|
1891
2383
|
if (rendered === lastRendered && mountedSlots.size === newSlotMap.size && [...newSlotMap].every(([id, next]) => mountedSlots.get(id)?.island === next.island)) {
|
|
1892
|
-
|
|
1893
|
-
const next = newSlotMap.get(id);
|
|
1894
|
-
if (next) entry.updateProps(next.props);
|
|
1895
|
-
}
|
|
2384
|
+
pushUpdatedProps(newSlotMap);
|
|
1896
2385
|
return;
|
|
1897
2386
|
}
|
|
1898
2387
|
detachListeners();
|
|
1899
2388
|
stopBindings();
|
|
2389
|
+
stopJsxEvents();
|
|
1900
2390
|
const prevMountedCount = mountedSlots.size;
|
|
1901
2391
|
const leavingPromises = [];
|
|
1902
2392
|
for (const [id, entry] of mountedSlots) if (!newSlotMap.has(id)) {
|
|
@@ -1942,6 +2432,13 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1942
2432
|
}
|
|
1943
2433
|
attachListeners();
|
|
1944
2434
|
stopBindings = applyTemplateBindings(host, newBinds);
|
|
2435
|
+
stopJsxEvents = applyJsxEvents({
|
|
2436
|
+
host,
|
|
2437
|
+
records: newEvents,
|
|
2438
|
+
reportError: (error) => reportError(error, "on"),
|
|
2439
|
+
unmountSignal: unmountController.signal,
|
|
2440
|
+
onceFired: nativeOnceFired
|
|
2441
|
+
});
|
|
1945
2442
|
mountSlots(newSlotMap);
|
|
1946
2443
|
};
|
|
1947
2444
|
if (leavingPromises.length > 0) Promise.allSettled(leavingPromises).then((results) => {
|
|
@@ -1954,6 +2451,8 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1954
2451
|
const unmount = () => {
|
|
1955
2452
|
if (tornDown) return;
|
|
1956
2453
|
tornDown = true;
|
|
2454
|
+
disposed = true;
|
|
2455
|
+
unmountController.abort();
|
|
1957
2456
|
ISLAND_MOUNT_HANDLES.delete(host);
|
|
1958
2457
|
stopRender();
|
|
1959
2458
|
detachListeners();
|
|
@@ -2001,7 +2500,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
2001
2500
|
* The declared return type omits {@link IslandCall} because TypeScript
|
|
2002
2501
|
* cannot narrow based on runtime stack state.
|
|
2003
2502
|
*/
|
|
2004
|
-
const island =
|
|
2503
|
+
const island = ((props) => {
|
|
2005
2504
|
if (currentRenderCtx()) return {
|
|
2006
2505
|
[ISLAND_CALL]: true,
|
|
2007
2506
|
island,
|
|
@@ -2009,7 +2508,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
2009
2508
|
key: void 0
|
|
2010
2509
|
};
|
|
2011
2510
|
return renderToString(props);
|
|
2012
|
-
};
|
|
2511
|
+
});
|
|
2013
2512
|
island.toString = (props) => renderToString(props, true);
|
|
2014
2513
|
island.mount = (host, props) => mountIsland(host, props);
|
|
2015
2514
|
island[ISLAND_MOUNT_INTERNAL] = (host, props) => mountIslandInternal(host, props);
|
|
@@ -2198,7 +2697,7 @@ function mountAll(registry, options = {}) {
|
|
|
2198
2697
|
const rawProps = host.getAttribute(PROPS_ATTR);
|
|
2199
2698
|
if (rawProps) {
|
|
2200
2699
|
const parsed = safeParseSnapshot(rawProps, `${PROPS_ATTR} on [data-ilha="${name}"]`);
|
|
2201
|
-
if (parsed !== void 0) props = parsed;
|
|
2700
|
+
if (parsed !== void 0) props = reviveSlotProps(parsed);
|
|
2202
2701
|
}
|
|
2203
2702
|
unmounts.push(island.mount(host, props));
|
|
2204
2703
|
}
|
|
@@ -2232,6 +2731,7 @@ const rootBuilder = new IlhaBuilder({
|
|
|
2232
2731
|
defaultInput: null,
|
|
2233
2732
|
states: [],
|
|
2234
2733
|
deriveds: [],
|
|
2734
|
+
actions: [],
|
|
2235
2735
|
ons: [],
|
|
2236
2736
|
effects: [],
|
|
2237
2737
|
onMounts: [],
|
|
@@ -2240,7 +2740,17 @@ const rootBuilder = new IlhaBuilder({
|
|
|
2240
2740
|
css: null,
|
|
2241
2741
|
as: null
|
|
2242
2742
|
});
|
|
2243
|
-
const
|
|
2743
|
+
const callableRoot = ((fn) => rootBuilder.input().render(fn));
|
|
2744
|
+
Object.defineProperty(callableRoot, "_cfg", {
|
|
2745
|
+
value: rootBuilder._cfg,
|
|
2746
|
+
enumerable: false
|
|
2747
|
+
});
|
|
2748
|
+
for (const key of Object.getOwnPropertyNames(IlhaBuilder.prototype)) {
|
|
2749
|
+
if (key === "constructor") continue;
|
|
2750
|
+
const descriptor = Object.getOwnPropertyDescriptor(IlhaBuilder.prototype, key);
|
|
2751
|
+
if (descriptor) Object.defineProperty(callableRoot, key, descriptor);
|
|
2752
|
+
}
|
|
2753
|
+
const ilha = Object.assign(callableRoot, {
|
|
2244
2754
|
html: ilhaHtml,
|
|
2245
2755
|
raw: ilhaRaw,
|
|
2246
2756
|
mount: mountAll,
|
|
@@ -2252,9 +2762,25 @@ const ilha = Object.assign(rootBuilder, {
|
|
|
2252
2762
|
untrack,
|
|
2253
2763
|
onUncaughtError
|
|
2254
2764
|
});
|
|
2765
|
+
/** @internal Used by the separate JSX runtime to register a native event handler. */
|
|
2766
|
+
function __ilhaJsxEvent({ type, handler, modifier }) {
|
|
2767
|
+
const ctx = currentRenderCtx();
|
|
2768
|
+
if (!ctx) return void 0;
|
|
2769
|
+
const index = ctx.events.length;
|
|
2770
|
+
ctx.events.push({
|
|
2771
|
+
type,
|
|
2772
|
+
handler,
|
|
2773
|
+
modifier
|
|
2774
|
+
});
|
|
2775
|
+
return index;
|
|
2776
|
+
}
|
|
2255
2777
|
/** @internal Used by the separate JSX runtime entry to preserve island slot composition. */
|
|
2256
|
-
function __ilhaJsxSlot(island, props, key) {
|
|
2257
|
-
return ilhaRaw(emitIslandSlot(
|
|
2778
|
+
function __ilhaJsxSlot({ island, props, key }) {
|
|
2779
|
+
return ilhaRaw(emitIslandSlot({
|
|
2780
|
+
island,
|
|
2781
|
+
props,
|
|
2782
|
+
key
|
|
2783
|
+
}));
|
|
2258
2784
|
}
|
|
2259
2785
|
const html = ilhaHtml;
|
|
2260
2786
|
const raw = ilhaRaw;
|
|
@@ -2264,4 +2790,4 @@ const from = ilhaFrom;
|
|
|
2264
2790
|
const context = ilhaContext;
|
|
2265
2791
|
|
|
2266
2792
|
//#endregion
|
|
2267
|
-
export { ISLAND_MOUNT_HANDLES, ISLAND_MOUNT_INTERNAL, __ilhaJsxSlot, batch, ilhaComputed as computed, context, css, ilha as default, ilhaEffect as effect, from, html, ilhaSignal, ilhaSignal as signal, mount, onUncaughtError, raw, untrack };
|
|
2793
|
+
export { ISLAND_MOUNT_HANDLES, ISLAND_MOUNT_INTERNAL, __ilhaJsxEvent, __ilhaJsxSlot, batch, ilhaComputed as computed, context, css, ilha as default, ilhaEffect as effect, from, html, ilhaSignal, ilhaSignal as signal, mount, onUncaughtError, raw, untrack };
|