ilha 0.12.0 → 0.13.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 +7 -25
- package/dist/{index-DZI0fEmR.d.ts → index-CVOjC94p.d.ts} +36 -15
- package/dist/index.d.ts +2 -2
- package/dist/index.js +516 -270
- package/dist/internal.d.ts +18 -2
- package/dist/internal.js +24 -1
- package/dist/jsx-dev-runtime.d.ts +1 -1
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/{jsx-runtime-BCGuHwTg.d.ts → jsx-runtime-CzU-60tX.d.ts} +1 -1
- package/dist/jsx-runtime-DreKruAt.js +124 -0
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -235
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ISLAND_MOUNT_HANDLES, ISLAND_MOUNT_INTERNAL, serializeServerManifest, setJsxRuntimeBridge } from "./internal.js";
|
|
1
|
+
import { ISLAND_MOUNT_HANDLES, ISLAND_MOUNT_INTERNAL, __ilhaJsxEvent, serializeServerManifest, setJsxRuntimeBridge, setServerActionBinder, setTemplateRenderer } from "./internal.js";
|
|
2
2
|
import { effect as effect$1, endBatch, setActiveSub, signal, startBatch } from "alien-signals";
|
|
3
3
|
|
|
4
4
|
//#region src/index.ts
|
|
@@ -511,15 +511,6 @@ function serializeScriptJson(value) {
|
|
|
511
511
|
function escapeStyleText(text) {
|
|
512
512
|
return text.replace(/</g, "\\3C ");
|
|
513
513
|
}
|
|
514
|
-
function dedentString(str) {
|
|
515
|
-
if (str.length === 0 || str[0] !== "\n") return str;
|
|
516
|
-
const lines = str.split("\n");
|
|
517
|
-
while (lines.length && lines[0].trim() === "") lines.shift();
|
|
518
|
-
while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
|
|
519
|
-
if (!lines.length) return "";
|
|
520
|
-
const indent = Math.min(...lines.filter((l) => l.trim() !== "").map((l) => l.match(/^(\s*)/)[1].length));
|
|
521
|
-
return lines.map((l) => l.slice(indent)).join("\n");
|
|
522
|
-
}
|
|
523
514
|
const RAW = Symbol.for("ilha.raw");
|
|
524
515
|
const SIGNAL_ACCESSOR = Symbol.for("ilha.signalAccessor");
|
|
525
516
|
const ISLAND = Symbol.for("ilha.island");
|
|
@@ -592,7 +583,7 @@ function each(items) {
|
|
|
592
583
|
const SSR_ACTION_STUB = Symbol.for("ilha.ssrActionStub");
|
|
593
584
|
const ACTION_MANIFEST_ID = Symbol.for("ilha.actionManifestId");
|
|
594
585
|
const ACTION_BOUND_ARGS = Symbol.for("ilha.actionBoundArgs");
|
|
595
|
-
/** Serialized size ceiling for `.
|
|
586
|
+
/** Serialized size ceiling for `.with()` payloads (JSON chars). */
|
|
596
587
|
const MAX_BOUND_ARGS_CHARS = 8192;
|
|
597
588
|
/** Manifest id for a brand-recognized action reference, else undefined.
|
|
598
589
|
* Anything that isn't a branded action stub is rejected outright — event
|
|
@@ -601,13 +592,13 @@ function actionManifestId(fn) {
|
|
|
601
592
|
if (typeof fn !== "function" || !(SSR_ACTION_STUB in fn)) return void 0;
|
|
602
593
|
return fn[ACTION_MANIFEST_ID];
|
|
603
594
|
}
|
|
604
|
-
/** Bound args recorded by `.
|
|
595
|
+
/** Bound args recorded by `.with()` on an action reference, else undefined. */
|
|
605
596
|
function actionBoundArgs(fn) {
|
|
606
597
|
if (typeof fn !== "function" || !(ACTION_BOUND_ARGS in fn)) return void 0;
|
|
607
598
|
const args = fn[ACTION_BOUND_ARGS];
|
|
608
599
|
return Array.isArray(args) ? args : void 0;
|
|
609
600
|
}
|
|
610
|
-
/** Attach `.
|
|
601
|
+
/** Attach `.with(...args)` to an action accessor. Returns an explicit,
|
|
611
602
|
* serializable action REFERENCE — never a closure to inspect or probe.
|
|
612
603
|
* Arguments are validated JSON-safe at bind time; the hydration manifest
|
|
613
604
|
* stores `{ actionId, args }` directly and the client RPC path sends them
|
|
@@ -631,19 +622,19 @@ function assertJsonSafe(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
631
622
|
case "boolean": return;
|
|
632
623
|
case "number":
|
|
633
624
|
if (Number.isFinite(value)) return;
|
|
634
|
-
throw new TypeError("
|
|
625
|
+
throw new TypeError("with() rejects non-finite numbers.");
|
|
635
626
|
case "object": break;
|
|
636
|
-
default: throw new TypeError(`
|
|
627
|
+
default: throw new TypeError(`with() rejects ${typeof value} values.`);
|
|
637
628
|
}
|
|
638
|
-
if (seen.has(value)) throw new TypeError("
|
|
629
|
+
if (seen.has(value)) throw new TypeError("with() rejects circular values.");
|
|
639
630
|
seen.add(value);
|
|
640
631
|
if (Array.isArray(value)) {
|
|
641
632
|
for (const item of value) assertJsonSafe(item, seen);
|
|
642
633
|
return;
|
|
643
634
|
}
|
|
644
|
-
if (Object.getPrototypeOf(value) !== Object.prototype) throw new TypeError("
|
|
635
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) throw new TypeError("with() accepts plain objects only.");
|
|
645
636
|
for (const [key, child] of Object.entries(value)) {
|
|
646
|
-
if (UNSAFE_KEYS.has(key)) throw new TypeError(`
|
|
637
|
+
if (UNSAFE_KEYS.has(key)) throw new TypeError(`with() rejects unsafe key "${key}".`);
|
|
647
638
|
assertJsonSafe(child, seen);
|
|
648
639
|
}
|
|
649
640
|
}
|
|
@@ -651,13 +642,13 @@ function assertJsonSafe(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
651
642
|
function cloneJsonSafe(value) {
|
|
652
643
|
return structuredClone(value);
|
|
653
644
|
}
|
|
654
|
-
function
|
|
655
|
-
Object.defineProperty(accessor, "
|
|
645
|
+
function attachWith(accessor) {
|
|
646
|
+
Object.defineProperty(accessor, "with", {
|
|
656
647
|
enumerable: false,
|
|
657
648
|
value: (...args) => {
|
|
658
649
|
for (const arg of args) assertJsonSafe(arg);
|
|
659
650
|
const serialized = cloneJsonSafe(args);
|
|
660
|
-
if (JSON.stringify(serialized).length > MAX_BOUND_ARGS_CHARS) throw new RangeError(`
|
|
651
|
+
if (JSON.stringify(serialized).length > MAX_BOUND_ARGS_CHARS) throw new RangeError(`with() payload exceeds ${MAX_BOUND_ARGS_CHARS} JSON characters.`);
|
|
661
652
|
const manifestId = actionManifestId(accessor);
|
|
662
653
|
const bound = (...runtimeArgs) => {
|
|
663
654
|
return accessor(serialized.length > 0 ? serialized.length === 1 ? serialized[0] : serialized : runtimeArgs.length === 1 ? runtimeArgs[0] : runtimeArgs);
|
|
@@ -670,6 +661,13 @@ function attachWithArgs(accessor) {
|
|
|
670
661
|
}
|
|
671
662
|
});
|
|
672
663
|
}
|
|
664
|
+
setServerActionBinder(function bindExternalAction(fn, manifestId) {
|
|
665
|
+
const action = fn;
|
|
666
|
+
action[SSR_ACTION_STUB] = true;
|
|
667
|
+
action[ACTION_MANIFEST_ID] = manifestId;
|
|
668
|
+
attachWith(action);
|
|
669
|
+
return action;
|
|
670
|
+
});
|
|
673
671
|
const RENDER_CTX_STACK = Symbol.for("ilha.renderCtxStack");
|
|
674
672
|
function renderCtxStack() {
|
|
675
673
|
const g = globalThis;
|
|
@@ -766,18 +764,18 @@ async function resolveAsyncChildren(html, pending) {
|
|
|
766
764
|
}
|
|
767
765
|
return html;
|
|
768
766
|
}
|
|
769
|
-
function
|
|
770
|
-
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
767
|
+
function markSignalAccessor(read, write) {
|
|
768
|
+
read[SIGNAL_ACCESSOR] = true;
|
|
769
|
+
const accessor = read;
|
|
770
|
+
accessor.set = (value) => write(value);
|
|
771
|
+
accessor.update = (fn) => {
|
|
772
|
+
const prevSub = setActiveSub(void 0);
|
|
773
|
+
try {
|
|
774
|
+
write(fn(read()));
|
|
775
|
+
} finally {
|
|
776
|
+
setActiveSub(prevSub);
|
|
777
|
+
}
|
|
778
|
+
};
|
|
781
779
|
accessor.select = ((...args) => createSelectAccessor(accessor, args));
|
|
782
780
|
return accessor;
|
|
783
781
|
}
|
|
@@ -863,17 +861,14 @@ function createSelectAccessor(root, input) {
|
|
|
863
861
|
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.");
|
|
864
862
|
} else path = input;
|
|
865
863
|
const read = (from) => selector ? path.length === 0 ? selector(from) : getAtPath(from, path) : getAtPath(from, path);
|
|
866
|
-
return markSignalAccessor((
|
|
867
|
-
if (args.length === 0) return read(root());
|
|
864
|
+
return markSignalAccessor(() => read(root()), (value) => {
|
|
868
865
|
const previousRoot = root();
|
|
869
|
-
const previousSelected = read(previousRoot);
|
|
870
|
-
const value = resolveSignalSetter(() => previousSelected, args[0]);
|
|
871
866
|
const next = path.length === 0 && selector ? value : setAtPath({
|
|
872
867
|
object: previousRoot,
|
|
873
868
|
path,
|
|
874
869
|
value
|
|
875
870
|
});
|
|
876
|
-
if (!Object.is(previousRoot, next)) root(
|
|
871
|
+
if (!Object.is(previousRoot, next)) root.set(next);
|
|
877
872
|
});
|
|
878
873
|
}
|
|
879
874
|
function ilhaRaw(value) {
|
|
@@ -943,70 +938,18 @@ const BIND_VALID_KINDS = /* @__PURE__ */ new Set([
|
|
|
943
938
|
"group",
|
|
944
939
|
"this"
|
|
945
940
|
]);
|
|
946
|
-
const BIND_PREFIX_RE = /\bbind:([a-zA-Z]+)\s*=\s*("|')?$/;
|
|
947
|
-
const EVENT_PREFIX_RE = /(?:^|\s)on([a-zA-Z][a-zA-Z0-9-]*)(?::([a-zA-Z][a-zA-Z0-9-]*))?\s*=\s*("|')?$/;
|
|
948
941
|
const NATIVE_EVENT_MODIFIERS = /* @__PURE__ */ new Set([
|
|
949
942
|
"abortable",
|
|
950
943
|
"once",
|
|
951
944
|
"capture",
|
|
952
945
|
"passive"
|
|
953
946
|
]);
|
|
954
|
-
const FULL_VALUE_ATTR_RE = /(?:^|\s)([a-zA-Z_:][a-zA-Z0-9:._-]*)\s*=\s*("|')?$/;
|
|
955
|
-
const UNQUOTED_HAZARD_RE = /[\s=]|`/;
|
|
956
947
|
function resolveTemplateValue(v) {
|
|
957
948
|
if (isSignalAccessor(v)) return v();
|
|
958
949
|
if (typeof v === "function") return v();
|
|
959
950
|
return v;
|
|
960
951
|
}
|
|
961
|
-
function
|
|
962
|
-
const open = source.lastIndexOf("<", index);
|
|
963
|
-
if (open === -1) return "element";
|
|
964
|
-
return (source.slice(open).match(/^<\s*([a-zA-Z][a-zA-Z0-9:._-]*)/)?.[1] ?? "element").toLowerCase();
|
|
965
|
-
}
|
|
966
|
-
function isEventAttributePosition(source, index) {
|
|
967
|
-
let open = -1;
|
|
968
|
-
let quote = null;
|
|
969
|
-
for (let i = 0; i < index; i++) {
|
|
970
|
-
const char = source[i];
|
|
971
|
-
if (open === -1) {
|
|
972
|
-
if (char === "<") open = i;
|
|
973
|
-
continue;
|
|
974
|
-
}
|
|
975
|
-
if (quote) {
|
|
976
|
-
if (char === quote) quote = null;
|
|
977
|
-
} else if (char === "\"" || char === "'") quote = char;
|
|
978
|
-
else if (char === ">") open = -1;
|
|
979
|
-
}
|
|
980
|
-
if (open === -1 || quote !== null) return false;
|
|
981
|
-
return !/^\s*[!/?]/.test(source.slice(open + 1, index));
|
|
982
|
-
}
|
|
983
|
-
function findTagCloseIndex(chunk, initialQuote) {
|
|
984
|
-
let quote = initialQuote;
|
|
985
|
-
for (let i = 0; i < chunk.length; i++) {
|
|
986
|
-
const ch = chunk[i];
|
|
987
|
-
if (quote !== null) {
|
|
988
|
-
if (ch === quote) quote = null;
|
|
989
|
-
} else if (ch === "\"" || ch === "'") quote = ch;
|
|
990
|
-
else if (ch === ">") return {
|
|
991
|
-
index: i,
|
|
992
|
-
quote
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
return {
|
|
996
|
-
index: -1,
|
|
997
|
-
quote
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
function extractElementStaticValue(prefix) {
|
|
1001
|
-
const lastOpen = prefix.lastIndexOf("<");
|
|
1002
|
-
if (lastOpen === -1) return null;
|
|
1003
|
-
const tagPrefix = prefix.slice(lastOpen);
|
|
1004
|
-
if (tagPrefix.includes(">")) return null;
|
|
1005
|
-
const m = tagPrefix.match(/\bvalue\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/);
|
|
1006
|
-
if (!m) return null;
|
|
1007
|
-
return m[2] ?? m[3] ?? m[4] ?? null;
|
|
1008
|
-
}
|
|
1009
|
-
function emitBindSSR({ kind, index, accessor, prefixForStaticPeek }) {
|
|
952
|
+
function emitBindSSR({ kind, index, accessor, optionValue }) {
|
|
1010
953
|
const spec = `${kind}:${index}`;
|
|
1011
954
|
let v;
|
|
1012
955
|
try {
|
|
@@ -1020,183 +963,494 @@ function emitBindSSR({ kind, index, accessor, prefixForStaticPeek }) {
|
|
|
1020
963
|
case "open": return [v ? ` open` : ``, spec];
|
|
1021
964
|
case "files": return [``, spec];
|
|
1022
965
|
case "this": return [``, spec];
|
|
1023
|
-
case "group":
|
|
1024
|
-
const optionValue = extractElementStaticValue(prefixForStaticPeek);
|
|
966
|
+
case "group":
|
|
1025
967
|
if (optionValue == null) return [``, spec];
|
|
1026
968
|
return [(Array.isArray(v) ? v.map(String).includes(optionValue) : v != null && String(v) === optionValue) ? ` checked` : ``, spec];
|
|
1027
|
-
}
|
|
1028
969
|
}
|
|
1029
970
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
971
|
+
const TEMPLATE_ATTRIBUTE = Symbol.for("ilha.templateAttribute");
|
|
972
|
+
const TEMPLATE_PARTS = Symbol.for("ilha.templateParts");
|
|
973
|
+
const TEMPLATE_SLOT_START = "ilha:";
|
|
974
|
+
const TEMPLATE_SLOT_END = ":";
|
|
975
|
+
const TEMPLATE_SLOT_RE = /\uE000ilha:(\d+):\uE001/g;
|
|
976
|
+
const templateBlueprintCache = /* @__PURE__ */ new WeakMap();
|
|
977
|
+
const TEMPLATE_PARSE_VOID_TAGS = /* @__PURE__ */ new Set([
|
|
978
|
+
"area",
|
|
979
|
+
"base",
|
|
980
|
+
"br",
|
|
981
|
+
"col",
|
|
982
|
+
"embed",
|
|
983
|
+
"hr",
|
|
984
|
+
"img",
|
|
985
|
+
"input",
|
|
986
|
+
"link",
|
|
987
|
+
"meta",
|
|
988
|
+
"param",
|
|
989
|
+
"source",
|
|
990
|
+
"track",
|
|
991
|
+
"wbr"
|
|
992
|
+
]);
|
|
993
|
+
const TEMPLATE_PARSE_RAW_TEXT_TAGS = /* @__PURE__ */ new Set([
|
|
994
|
+
"script",
|
|
995
|
+
"style",
|
|
996
|
+
"textarea",
|
|
997
|
+
"title"
|
|
998
|
+
]);
|
|
999
|
+
function templateSkipWs(s, i) {
|
|
1000
|
+
while (i < s.length && /[\t\n\f\r ]/.test(s[i])) i++;
|
|
1001
|
+
return i;
|
|
1002
|
+
}
|
|
1003
|
+
function templateAsciiLower(source) {
|
|
1004
|
+
return source.replace(/[A-Z]/g, (char) => String.fromCharCode(char.charCodeAt(0) + 32));
|
|
1005
|
+
}
|
|
1006
|
+
function templateClosingTagAt(lower, index, tag) {
|
|
1007
|
+
const prefix = `</${tag}`;
|
|
1008
|
+
if (!lower.startsWith(prefix, index)) return false;
|
|
1009
|
+
const boundary = lower[index + prefix.length];
|
|
1010
|
+
return boundary === ">" || boundary === "/" || /[\t\n\f\r ]/.test(boundary ?? "");
|
|
1011
|
+
}
|
|
1012
|
+
function templateFindClosingTag(lower, tag, from) {
|
|
1013
|
+
const needle = `</${tag}`;
|
|
1014
|
+
let index = lower.indexOf(needle, from);
|
|
1015
|
+
while (index !== -1) {
|
|
1016
|
+
if (templateClosingTagAt(lower, index, tag)) return index;
|
|
1017
|
+
index = lower.indexOf(needle, index + 1);
|
|
1018
|
+
}
|
|
1019
|
+
return -1;
|
|
1020
|
+
}
|
|
1021
|
+
function templateParseFragment(source) {
|
|
1022
|
+
const children = [];
|
|
1023
|
+
templateParseNodes(source, templateAsciiLower(source), 0, null, children);
|
|
1024
|
+
return {
|
|
1025
|
+
nodeName: "#document-fragment",
|
|
1026
|
+
childNodes: children
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
function templateParseNodes(s, lower, i, stop, out) {
|
|
1030
|
+
while (i < s.length) {
|
|
1031
|
+
if (stop && templateClosingTagAt(lower, i, stop)) {
|
|
1032
|
+
const gt = s.indexOf(">", i);
|
|
1033
|
+
return gt === -1 ? s.length : gt + 1;
|
|
1055
1034
|
}
|
|
1056
|
-
if (i
|
|
1057
|
-
|
|
1035
|
+
if (s.startsWith("<!--", i)) {
|
|
1036
|
+
const end = s.indexOf("-->", i + 4);
|
|
1037
|
+
const close = end === -1 ? s.length : end + 3;
|
|
1038
|
+
out.push({
|
|
1039
|
+
nodeName: "#comment",
|
|
1040
|
+
data: s.slice(i + 4, end === -1 ? s.length : end)
|
|
1041
|
+
});
|
|
1042
|
+
i = close;
|
|
1058
1043
|
continue;
|
|
1059
1044
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
const ambiguous = chunk.match(/(?:^|\s)on([a-zA-Z][a-zA-Z0-9-]*)(?::[a-zA-Z][a-zA-Z0-9-]*)?\s*=\s*(?:(?:"[^"]*)|(?:'[^']*)|(?:[^\s"'=<>`]+))$/);
|
|
1065
|
-
const start = ambiguous ? chunk.length - ambiguous[0].length : -1;
|
|
1066
|
-
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}.`);
|
|
1045
|
+
if (stop === null && s[i] === "<" && s[i + 1] === "/") {
|
|
1046
|
+
const gt = s.indexOf(">", i);
|
|
1047
|
+
i = gt === -1 ? s.length : gt + 1;
|
|
1048
|
+
continue;
|
|
1067
1049
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1073
|
-
const
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1050
|
+
if (s[i] === "<" && /[A-Za-z]/.test(s[i + 1] ?? "")) {
|
|
1051
|
+
const start = i;
|
|
1052
|
+
i++;
|
|
1053
|
+
const tagStart = i;
|
|
1054
|
+
while (i < s.length && /[A-Za-z0-9:-]/.test(s[i])) i++;
|
|
1055
|
+
const tagOrig = s.slice(tagStart, i);
|
|
1056
|
+
const tag = tagOrig.toLowerCase();
|
|
1057
|
+
const attrs = [];
|
|
1058
|
+
const attrLocs = {};
|
|
1059
|
+
i = templateSkipWs(s, i);
|
|
1060
|
+
while (i < s.length && s[i] !== ">" && s[i] !== "/") {
|
|
1061
|
+
const attrStart = i;
|
|
1062
|
+
while (i < s.length && /[^\s=>/]/.test(s[i])) i++;
|
|
1063
|
+
const rawName = s.slice(attrStart, i);
|
|
1064
|
+
if (!rawName) break;
|
|
1065
|
+
const name = rawName;
|
|
1066
|
+
i = templateSkipWs(s, i);
|
|
1067
|
+
let value = "";
|
|
1068
|
+
if (s[i] === "=") {
|
|
1069
|
+
i++;
|
|
1070
|
+
i = templateSkipWs(s, i);
|
|
1071
|
+
if (s[i] === "\"" || s[i] === "'") {
|
|
1072
|
+
const q = s[i];
|
|
1073
|
+
i++;
|
|
1074
|
+
const vs = i;
|
|
1075
|
+
while (i < s.length && s[i] !== q) i++;
|
|
1076
|
+
value = s.slice(vs, i);
|
|
1077
|
+
if (s[i] === q) i++;
|
|
1078
|
+
} else {
|
|
1079
|
+
const vs = i;
|
|
1080
|
+
while (i < s.length && /[^\s>]/.test(s[i])) {
|
|
1081
|
+
if (s[i] === "/") {
|
|
1082
|
+
let j = i + 1;
|
|
1083
|
+
while (j < s.length && /\s/.test(s[j])) j++;
|
|
1084
|
+
if (j >= s.length || s[j] === ">") break;
|
|
1085
|
+
}
|
|
1086
|
+
i++;
|
|
1087
|
+
}
|
|
1088
|
+
value = s.slice(vs, i);
|
|
1089
|
+
}
|
|
1088
1090
|
}
|
|
1091
|
+
attrs.push({
|
|
1092
|
+
name,
|
|
1093
|
+
value
|
|
1094
|
+
});
|
|
1095
|
+
attrLocs[name] = {
|
|
1096
|
+
startOffset: attrStart,
|
|
1097
|
+
endOffset: i
|
|
1098
|
+
};
|
|
1099
|
+
i = templateSkipWs(s, i);
|
|
1100
|
+
}
|
|
1101
|
+
if (s[i] === "/") {
|
|
1102
|
+
i++;
|
|
1103
|
+
i = templateSkipWs(s, i);
|
|
1089
1104
|
}
|
|
1090
|
-
if (
|
|
1105
|
+
if (s[i] === ">") i++;
|
|
1106
|
+
const el = {
|
|
1107
|
+
nodeName: tagOrig,
|
|
1108
|
+
tagName: tagOrig,
|
|
1109
|
+
attrs,
|
|
1110
|
+
childNodes: [],
|
|
1111
|
+
sourceCodeLocation: {
|
|
1112
|
+
attrs: attrLocs,
|
|
1113
|
+
startTag: {
|
|
1114
|
+
startOffset: start,
|
|
1115
|
+
endOffset: i
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
if (!TEMPLATE_PARSE_VOID_TAGS.has(tag)) {
|
|
1120
|
+
if (TEMPLATE_PARSE_RAW_TEXT_TAGS.has(tag)) {
|
|
1121
|
+
const ci = templateFindClosingTag(lower, tag, i);
|
|
1122
|
+
const textEnd = ci === -1 ? s.length : ci;
|
|
1123
|
+
if (textEnd > i) el.childNodes.push({
|
|
1124
|
+
nodeName: "#text",
|
|
1125
|
+
value: s.slice(i, textEnd)
|
|
1126
|
+
});
|
|
1127
|
+
i = ci === -1 ? s.length : s.indexOf(">", ci) === -1 ? s.length : s.indexOf(">", ci) + 1;
|
|
1128
|
+
} else i = templateParseNodes(s, lower, i, tag, el.childNodes);
|
|
1129
|
+
}
|
|
1130
|
+
out.push(el);
|
|
1091
1131
|
continue;
|
|
1092
1132
|
}
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1133
|
+
const next = s.indexOf("<", i);
|
|
1134
|
+
const end = next === -1 ? s.length : next;
|
|
1135
|
+
if (end > i) {
|
|
1136
|
+
out.push({
|
|
1137
|
+
nodeName: "#text",
|
|
1138
|
+
value: s.slice(i, end)
|
|
1139
|
+
});
|
|
1140
|
+
i = end;
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
out.push({
|
|
1144
|
+
nodeName: "#text",
|
|
1145
|
+
value: s[i]
|
|
1146
|
+
});
|
|
1147
|
+
i++;
|
|
1148
|
+
}
|
|
1149
|
+
return i;
|
|
1150
|
+
}
|
|
1151
|
+
function templateSegments(value) {
|
|
1152
|
+
const out = [];
|
|
1153
|
+
let offset = 0;
|
|
1154
|
+
for (const match of value.matchAll(TEMPLATE_SLOT_RE)) {
|
|
1155
|
+
if (match.index > offset) out.push(value.slice(offset, match.index));
|
|
1156
|
+
out.push({ slot: Number(match[1]) });
|
|
1157
|
+
offset = match.index + match[0].length;
|
|
1158
|
+
}
|
|
1159
|
+
if (offset < value.length) out.push(value.slice(offset));
|
|
1160
|
+
return out;
|
|
1161
|
+
}
|
|
1162
|
+
function templateBlueprint(node, source) {
|
|
1163
|
+
if (node.nodeName === "#text") return {
|
|
1164
|
+
kind: "text",
|
|
1165
|
+
value: templateSegments(node.value)
|
|
1166
|
+
};
|
|
1167
|
+
if (node.nodeName === "#comment") return {
|
|
1168
|
+
kind: "comment",
|
|
1169
|
+
value: templateSegments(node.data)
|
|
1170
|
+
};
|
|
1171
|
+
if (node.nodeName === "#document-fragment") return {
|
|
1172
|
+
kind: "fragment",
|
|
1173
|
+
children: node.childNodes.map((child) => templateBlueprint(child, source)).filter(Boolean)
|
|
1174
|
+
};
|
|
1175
|
+
if (typeof node.tagName === "string") {
|
|
1176
|
+
const props = {};
|
|
1177
|
+
for (const attr of node.attrs ?? []) {
|
|
1178
|
+
const location = node.sourceCodeLocation?.attrs?.[attr.name];
|
|
1179
|
+
const sourceAttr = location ? source.slice(location.startOffset, location.endOffset) : attr.name;
|
|
1180
|
+
const parts = templateSegments(attr.value);
|
|
1181
|
+
parts.quoted = /=\s*["']/.test(sourceAttr);
|
|
1182
|
+
parts.bare = !sourceAttr.includes("=");
|
|
1183
|
+
props[attr.name] = parts;
|
|
1184
|
+
}
|
|
1185
|
+
return {
|
|
1186
|
+
kind: "element",
|
|
1187
|
+
tag: node.tagName,
|
|
1188
|
+
props,
|
|
1189
|
+
children: (node.childNodes ?? []).map((child) => templateBlueprint(child, source)).filter(Boolean),
|
|
1190
|
+
selfClosing: node.sourceCodeLocation?.startTag ? /\/\s*>$/.test(source.slice(node.sourceCodeLocation.startTag.startOffset, node.sourceCodeLocation.startTag.endOffset)) : false
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
return null;
|
|
1194
|
+
}
|
|
1195
|
+
function templateCompile(strings) {
|
|
1196
|
+
let source = strings[0] ?? "";
|
|
1197
|
+
for (let i = 0; i < strings.length - 1; i++) source += `${TEMPLATE_SLOT_START}${i}${TEMPLATE_SLOT_END}${strings[i + 1] ?? ""}`;
|
|
1198
|
+
return templateBlueprint(templateParseFragment(source), source) ?? {
|
|
1199
|
+
kind: "fragment",
|
|
1200
|
+
children: []
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
function templateBindSegments(parts, values) {
|
|
1204
|
+
let value;
|
|
1205
|
+
if (parts.length === 1 && typeof parts[0] !== "string") value = values[parts[0].slot];
|
|
1206
|
+
else if (parts.some((part) => typeof part !== "string")) value = {
|
|
1207
|
+
[TEMPLATE_PARTS]: true,
|
|
1208
|
+
values: parts.map((part) => typeof part === "string" ? part : values[part.slot])
|
|
1209
|
+
};
|
|
1210
|
+
else value = parts.join("");
|
|
1211
|
+
return {
|
|
1212
|
+
[TEMPLATE_ATTRIBUTE]: true,
|
|
1213
|
+
value,
|
|
1214
|
+
quoted: parts.quoted ?? true,
|
|
1215
|
+
bare: parts.bare ?? false
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
function templateBind(node, values) {
|
|
1219
|
+
if (node.kind === "text") {
|
|
1220
|
+
const out = [];
|
|
1221
|
+
for (const part of node.value) if (typeof part === "string") {
|
|
1222
|
+
if (part) out.push({
|
|
1223
|
+
kind: "text",
|
|
1224
|
+
value: part
|
|
1225
|
+
});
|
|
1226
|
+
} else out.push({
|
|
1227
|
+
kind: "dynamic",
|
|
1228
|
+
value: values[part.slot]
|
|
1229
|
+
});
|
|
1230
|
+
return out;
|
|
1231
|
+
}
|
|
1232
|
+
if (node.kind === "comment") {
|
|
1233
|
+
const parts = node.value;
|
|
1234
|
+
if (parts.length === 1 && typeof parts[0] === "string") return {
|
|
1235
|
+
kind: "comment",
|
|
1236
|
+
value: parts[0]
|
|
1237
|
+
};
|
|
1238
|
+
if (parts.every((part) => typeof part === "string")) return {
|
|
1239
|
+
kind: "comment",
|
|
1240
|
+
value: parts.join("")
|
|
1241
|
+
};
|
|
1242
|
+
return {
|
|
1243
|
+
kind: "comment",
|
|
1244
|
+
value: {
|
|
1245
|
+
[TEMPLATE_PARTS]: true,
|
|
1246
|
+
values: parts.map((part) => typeof part === "string" ? part : values[part.slot])
|
|
1100
1247
|
}
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
if (node.kind === "element") {
|
|
1251
|
+
const props = {};
|
|
1252
|
+
for (const [name, parts] of Object.entries(node.props)) props[name] = templateBindSegments(parts, values);
|
|
1253
|
+
return {
|
|
1254
|
+
kind: "element",
|
|
1255
|
+
tag: node.tag,
|
|
1256
|
+
props,
|
|
1257
|
+
children: node.children.flatMap((child) => templateBind(child, values)),
|
|
1258
|
+
selfClosing: node.selfClosing
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
return {
|
|
1262
|
+
kind: "fragment",
|
|
1263
|
+
children: node.children.flatMap((child) => templateBind(child, values))
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
function htmlTemplate(strings, values) {
|
|
1267
|
+
let compiled = templateBlueprintCache.get(strings);
|
|
1268
|
+
if (!compiled) {
|
|
1269
|
+
compiled = templateCompile(strings);
|
|
1270
|
+
templateBlueprintCache.set(strings, compiled);
|
|
1271
|
+
}
|
|
1272
|
+
const result = templateBind(compiled, values);
|
|
1273
|
+
return Array.isArray(result) ? {
|
|
1274
|
+
kind: "fragment",
|
|
1275
|
+
children: result
|
|
1276
|
+
} : result;
|
|
1277
|
+
}
|
|
1278
|
+
function templateAttribute(value) {
|
|
1279
|
+
return value && typeof value === "object" && TEMPLATE_ATTRIBUTE in value ? value : null;
|
|
1280
|
+
}
|
|
1281
|
+
function templateParts(value) {
|
|
1282
|
+
return value && typeof value === "object" && TEMPLATE_PARTS in value ? value.values : null;
|
|
1283
|
+
}
|
|
1284
|
+
const TEMPLATE_ATTRIBUTE_ALIASES = {
|
|
1285
|
+
className: "class",
|
|
1286
|
+
htmlFor: "for",
|
|
1287
|
+
acceptCharset: "accept-charset",
|
|
1288
|
+
httpEquiv: "http-equiv",
|
|
1289
|
+
accentHeight: "accent-height",
|
|
1290
|
+
alignmentBaseline: "alignment-baseline",
|
|
1291
|
+
baselineShift: "baseline-shift",
|
|
1292
|
+
clipPath: "clip-path",
|
|
1293
|
+
clipRule: "clip-rule",
|
|
1294
|
+
dominantBaseline: "dominant-baseline",
|
|
1295
|
+
fillOpacity: "fill-opacity",
|
|
1296
|
+
fillRule: "fill-rule",
|
|
1297
|
+
floodColor: "flood-color",
|
|
1298
|
+
floodOpacity: "flood-opacity",
|
|
1299
|
+
fontFamily: "font-family",
|
|
1300
|
+
fontSize: "font-size",
|
|
1301
|
+
fontStyle: "font-style",
|
|
1302
|
+
fontWeight: "font-weight",
|
|
1303
|
+
markerEnd: "marker-end",
|
|
1304
|
+
markerMid: "marker-mid",
|
|
1305
|
+
markerStart: "marker-start",
|
|
1306
|
+
stopColor: "stop-color",
|
|
1307
|
+
stopOpacity: "stop-opacity",
|
|
1308
|
+
strokeDasharray: "stroke-dasharray",
|
|
1309
|
+
strokeDashoffset: "stroke-dashoffset",
|
|
1310
|
+
strokeLinecap: "stroke-linecap",
|
|
1311
|
+
strokeLinejoin: "stroke-linejoin",
|
|
1312
|
+
strokeMiterlimit: "stroke-miterlimit",
|
|
1313
|
+
strokeOpacity: "stroke-opacity",
|
|
1314
|
+
strokeWidth: "stroke-width",
|
|
1315
|
+
textAnchor: "text-anchor",
|
|
1316
|
+
vectorEffect: "vector-effect",
|
|
1317
|
+
xlinkHref: "xlink:href"
|
|
1318
|
+
};
|
|
1319
|
+
const TEMPLATE_VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
1320
|
+
"area",
|
|
1321
|
+
"base",
|
|
1322
|
+
"br",
|
|
1323
|
+
"col",
|
|
1324
|
+
"embed",
|
|
1325
|
+
"hr",
|
|
1326
|
+
"img",
|
|
1327
|
+
"input",
|
|
1328
|
+
"link",
|
|
1329
|
+
"meta",
|
|
1330
|
+
"param",
|
|
1331
|
+
"source",
|
|
1332
|
+
"track",
|
|
1333
|
+
"wbr"
|
|
1334
|
+
]);
|
|
1335
|
+
const TEMPLATE_STRING_BOOLEANS = /* @__PURE__ */ new Set([
|
|
1336
|
+
"contenteditable",
|
|
1337
|
+
"draggable",
|
|
1338
|
+
"spellcheck"
|
|
1339
|
+
]);
|
|
1340
|
+
const TEMPLATE_SAFE_NAME = /^[A-Za-z_:][A-Za-z0-9:._-]*$/;
|
|
1341
|
+
const TEMPLATE_EVENT = /^on([a-z][a-z0-9-]*)(?::([a-z][a-z0-9-]*))?$/;
|
|
1342
|
+
function normalizeTemplateClass(value) {
|
|
1343
|
+
if (Array.isArray(value)) return value.filter(Boolean).join(" ");
|
|
1344
|
+
if (isPlainObject(value)) return Object.entries(value).filter(([, enabled]) => Boolean(enabled)).map(([name]) => name).join(" ");
|
|
1345
|
+
return String(value);
|
|
1346
|
+
}
|
|
1347
|
+
function resolvedAttrText(rawSourceValue) {
|
|
1348
|
+
const attribute = templateAttribute(rawSourceValue);
|
|
1349
|
+
const sourceValue = attribute ? attribute.value : rawSourceValue;
|
|
1350
|
+
if (sourceValue == null) return null;
|
|
1351
|
+
const parts = templateParts(sourceValue);
|
|
1352
|
+
const value = parts ? parts.map((part) => resolveTemplateValue(part) ?? "").join("") : resolveTemplateValue(sourceValue);
|
|
1353
|
+
if (value == null || value === false) return null;
|
|
1354
|
+
return String(value);
|
|
1355
|
+
}
|
|
1356
|
+
function renderTemplateElement(node) {
|
|
1357
|
+
const ctx = currentRenderCtx();
|
|
1358
|
+
let attrs = "";
|
|
1359
|
+
const eventSpecs = [];
|
|
1360
|
+
const bindSpecs = [];
|
|
1361
|
+
for (const [sourceName, rawSourceValue] of Object.entries(node.props)) {
|
|
1362
|
+
const attribute = templateAttribute(rawSourceValue);
|
|
1363
|
+
const sourceValue = attribute ? attribute.value : rawSourceValue;
|
|
1364
|
+
if (sourceName === "children" || sourceName === "key" || sourceValue == null) continue;
|
|
1365
|
+
if (sourceName === "__proto__" || sourceName === "constructor" || sourceName === "prototype") continue;
|
|
1366
|
+
const name = TEMPLATE_ATTRIBUTE_ALIASES[sourceName] ?? (/^on[A-Z]/.test(sourceName) ? sourceName.toLowerCase() : sourceName);
|
|
1367
|
+
if (!TEMPLATE_SAFE_NAME.test(name)) continue;
|
|
1368
|
+
if (name.startsWith("bind:")) {
|
|
1369
|
+
const kind = name.slice(5);
|
|
1370
|
+
if (!BIND_VALID_KINDS.has(kind) || !isSignalAccessor(sourceValue)) continue;
|
|
1101
1371
|
if (!ctx) {
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
if (openQuote) stripLeadingQuote = openQuote;
|
|
1372
|
+
const value = sourceValue();
|
|
1373
|
+
if (kind === "value") attrs += ` value="${escapeHtml(value ?? "")}"`;
|
|
1374
|
+
else if ((kind === "checked" || kind === "open") && value) attrs += ` ${kind}`;
|
|
1106
1375
|
continue;
|
|
1107
1376
|
}
|
|
1108
|
-
const matchStart = chunk.length - m[0].length;
|
|
1109
|
-
const cleanPrefix = chunk.slice(0, matchStart).replace(/\s+$/, "");
|
|
1110
1377
|
const index = ctx.binds.length;
|
|
1111
1378
|
ctx.binds.push({
|
|
1112
|
-
kind
|
|
1113
|
-
accessor:
|
|
1379
|
+
kind,
|
|
1380
|
+
accessor: sourceValue
|
|
1114
1381
|
});
|
|
1115
|
-
const [
|
|
1116
|
-
kind
|
|
1382
|
+
const [reflected, spec] = emitBindSSR({
|
|
1383
|
+
kind,
|
|
1117
1384
|
index,
|
|
1118
|
-
accessor:
|
|
1119
|
-
|
|
1385
|
+
accessor: sourceValue,
|
|
1386
|
+
optionValue: resolvedAttrText(node.props.value)
|
|
1120
1387
|
});
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
if (openQuote) stripLeadingQuote = openQuote;
|
|
1388
|
+
attrs += reflected;
|
|
1389
|
+
bindSpecs.push(spec);
|
|
1124
1390
|
continue;
|
|
1125
1391
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
}
|
|
1155
|
-
const url = String(resolved);
|
|
1156
|
-
if (!isSafeUrlAttrValue(openTagName(result + chunk, result.length + attrMatch.index), attrName, url)) {
|
|
1157
|
-
result += cleanPrefix;
|
|
1158
|
-
continue;
|
|
1159
|
-
}
|
|
1160
|
-
const quotedUrl = openQuote !== null || UNQUOTED_HAZARD_RE.test(url);
|
|
1161
|
-
result += cleanPrefix + ` ${attrName}=` + (quotedUrl ? `"${escapeHtml(url)}"` : escapeHtml(url));
|
|
1162
|
-
continue;
|
|
1163
|
-
}
|
|
1164
|
-
if (attrName.toLowerCase() === "style") {
|
|
1165
|
-
if (!isRawHtml(value)) {
|
|
1166
|
-
const resolved = resolveTemplateValue(value);
|
|
1167
|
-
if (isPlainObject(resolved)) {
|
|
1168
|
-
result += cleanPrefix + ` style="${escapeHtml(serializeStyle(resolved))}"`;
|
|
1169
|
-
continue;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
if (isRawHtml(value)) {
|
|
1174
|
-
stripLeadingQuote = null;
|
|
1175
|
-
result += chunk + interpolateValue(value);
|
|
1176
|
-
continue;
|
|
1177
|
-
}
|
|
1178
|
-
const resolved = resolveTemplateValue(value);
|
|
1179
|
-
if (resolved == null || resolved === false) {
|
|
1180
|
-
result += cleanPrefix;
|
|
1181
|
-
continue;
|
|
1182
|
-
}
|
|
1183
|
-
if (resolved === true) {
|
|
1184
|
-
result += cleanPrefix + ` ${attrName}`;
|
|
1185
|
-
continue;
|
|
1186
|
-
}
|
|
1187
|
-
const generic = String(resolved);
|
|
1188
|
-
if (openQuote !== null || UNQUOTED_HAZARD_RE.test(generic)) result += cleanPrefix + ` ${attrName}="${escapeHtml(generic)}"`;
|
|
1189
|
-
else result += cleanPrefix + ` ${attrName}=${escapeHtml(generic)}`;
|
|
1392
|
+
if (/^on/i.test(name)) {
|
|
1393
|
+
const match = TEMPLATE_EVENT.exec(name.toLowerCase());
|
|
1394
|
+
if (templateParts(sourceValue)?.some((part) => typeof part === "function")) throw new Error(`[ilha] ${name} handler must occupy the entire attribute value. Write ${name}=\${handler}.`);
|
|
1395
|
+
if (!match || typeof sourceValue !== "function") continue;
|
|
1396
|
+
const eventName = match[1];
|
|
1397
|
+
const rawModifier = match[2];
|
|
1398
|
+
const modifier = rawModifier !== void 0 && NATIVE_EVENT_MODIFIERS.has(rawModifier) ? rawModifier : void 0;
|
|
1399
|
+
if (rawModifier && !modifier) continue;
|
|
1400
|
+
const index = __ilhaJsxEvent({
|
|
1401
|
+
type: eventName,
|
|
1402
|
+
handler: sourceValue,
|
|
1403
|
+
modifier
|
|
1404
|
+
});
|
|
1405
|
+
if (index !== void 0) eventSpecs.push(`${eventName}:${index}`);
|
|
1406
|
+
continue;
|
|
1407
|
+
}
|
|
1408
|
+
const lower = name.toLowerCase();
|
|
1409
|
+
if (lower === "srcdoc") continue;
|
|
1410
|
+
const parts = templateParts(sourceValue);
|
|
1411
|
+
let value = parts ? parts.map((part) => resolveTemplateValue(part) ?? "").join("") : resolveTemplateValue(sourceValue);
|
|
1412
|
+
if ((lower.startsWith("aria-") || TEMPLATE_STRING_BOOLEANS.has(lower)) && typeof value === "boolean") value = String(value);
|
|
1413
|
+
if (lower === "class" && !isRawHtml(value)) value = normalizeTemplateClass(value);
|
|
1414
|
+
const structuredStyle = lower === "style" && isPlainObject(value) && !isRawHtml(value);
|
|
1415
|
+
if (structuredStyle) value = serializeStyle(value);
|
|
1416
|
+
if (isUrlAttributeName(lower) && !isRawHtml(value) && !isSafeUrlAttrValue(node.tag, lower, String(value))) continue;
|
|
1417
|
+
if (value === false || value == null) continue;
|
|
1418
|
+
if (attribute?.bare && value === "" || value === true && !TEMPLATE_STRING_BOOLEANS.has(lower) && !lower.startsWith("aria-")) {
|
|
1419
|
+
attrs += ` ${name}`;
|
|
1190
1420
|
continue;
|
|
1191
1421
|
}
|
|
1192
|
-
|
|
1422
|
+
const text = isRawHtml(value) ? value.value : String(value);
|
|
1423
|
+
const escaped = isRawHtml(value) ? text : escapeHtml(text);
|
|
1424
|
+
attrs += attribute && !attribute.quoted && !structuredStyle && !/[\s"'`=<>]/.test(text) ? ` ${name}=${escaped}` : ` ${name}="${escaped}"`;
|
|
1425
|
+
}
|
|
1426
|
+
const key = node.props.key;
|
|
1427
|
+
if (key != null && node.props["data-key"] == null) attrs += ` data-key="${escapeHtml(key)}"`;
|
|
1428
|
+
if (bindSpecs.length) attrs += ` data-ilha-bind="${bindSpecs.join(",")}"`;
|
|
1429
|
+
if (eventSpecs.length) attrs += ` data-ilha-on="${eventSpecs.join(",")}"`;
|
|
1430
|
+
if (TEMPLATE_VOID_ELEMENTS.has(node.tag.toLowerCase())) return `<${node.tag}${attrs}${node.selfClosing ? " /" : ""}>`;
|
|
1431
|
+
return `<${node.tag}${attrs}>${node.children.map((child) => renderTemplateNode(child, node.tag)).join("")}</${node.tag}>`;
|
|
1432
|
+
}
|
|
1433
|
+
function renderTemplateNode(node, parentTag) {
|
|
1434
|
+
switch (node.kind) {
|
|
1435
|
+
case "fragment": return node.children.map((child) => renderTemplateNode(child, parentTag)).join("");
|
|
1436
|
+
case "element": return renderTemplateElement(node);
|
|
1437
|
+
case "dynamic": return interpolateValue(node.value);
|
|
1438
|
+
case "comment": {
|
|
1439
|
+
const parts = templateParts(node.value);
|
|
1440
|
+
return `<!--${(parts ? parts.map((part) => resolveTemplateValue(part) ?? "").join("") : String(node.value)).replace(/--/g, "- -")}-->`;
|
|
1441
|
+
}
|
|
1442
|
+
case "text": {
|
|
1443
|
+
const parent = parentTag?.toLowerCase();
|
|
1444
|
+
return parent === "script" || parent === "style" ? node.value : escapeHtml(node.value);
|
|
1445
|
+
}
|
|
1193
1446
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
return
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1447
|
+
}
|
|
1448
|
+
function renderTemplate(node) {
|
|
1449
|
+
return ilhaRaw(renderTemplateNode(node));
|
|
1450
|
+
}
|
|
1451
|
+
setTemplateRenderer(renderTemplate);
|
|
1452
|
+
function ilhaHtml(strings, ...values) {
|
|
1453
|
+
return renderTemplate(htmlTemplate(strings, values));
|
|
1200
1454
|
}
|
|
1201
1455
|
function isRawHtml(v) {
|
|
1202
1456
|
if (typeof v !== "object" || v === null) return false;
|
|
@@ -1209,9 +1463,8 @@ const contextRegistry = /* @__PURE__ */ new Map();
|
|
|
1209
1463
|
function ilhaContextFn(key, initial) {
|
|
1210
1464
|
if (contextRegistry.has(key)) return contextRegistry.get(key);
|
|
1211
1465
|
const s = signal(initial);
|
|
1212
|
-
const accessor = markSignalAccessor((
|
|
1213
|
-
|
|
1214
|
-
s(resolveSignalSetter(() => s(), args[0]));
|
|
1466
|
+
const accessor = markSignalAccessor(() => s(), (next) => {
|
|
1467
|
+
s(next);
|
|
1215
1468
|
});
|
|
1216
1469
|
contextRegistry.set(key, accessor);
|
|
1217
1470
|
return accessor;
|
|
@@ -1234,9 +1487,8 @@ const ilhaContext = Object.assign(ilhaContextFn, {
|
|
|
1234
1487
|
function ilhaSignal(initial) {
|
|
1235
1488
|
if (currentFrame()?.creating && __DEV__);
|
|
1236
1489
|
const s = signal(initial);
|
|
1237
|
-
return markSignalAccessor((
|
|
1238
|
-
|
|
1239
|
-
s(resolveSignalSetter(() => s(), args[0]));
|
|
1490
|
+
return markSignalAccessor(() => s(), (next) => {
|
|
1491
|
+
s(next);
|
|
1240
1492
|
});
|
|
1241
1493
|
}
|
|
1242
1494
|
/**
|
|
@@ -1346,7 +1598,7 @@ function persist(accessor, key, options = {}) {
|
|
|
1346
1598
|
if (value !== null) {
|
|
1347
1599
|
const prevSub = setActiveSub(void 0);
|
|
1348
1600
|
try {
|
|
1349
|
-
accessor(
|
|
1601
|
+
accessor.set(value);
|
|
1350
1602
|
} finally {
|
|
1351
1603
|
setActiveSub(prevSub);
|
|
1352
1604
|
}
|
|
@@ -1381,12 +1633,8 @@ function persist(accessor, key, options = {}) {
|
|
|
1381
1633
|
};
|
|
1382
1634
|
}
|
|
1383
1635
|
function createDerivedAccessor(read, write) {
|
|
1384
|
-
const accessor = markSignalAccessor((
|
|
1385
|
-
if (
|
|
1386
|
-
if (write) write(args[0]);
|
|
1387
|
-
return;
|
|
1388
|
-
}
|
|
1389
|
-
return read().value;
|
|
1636
|
+
const accessor = markSignalAccessor(() => read().value, (value) => {
|
|
1637
|
+
if (write) write(value);
|
|
1390
1638
|
});
|
|
1391
1639
|
return new Proxy(accessor, { get(target, prop, receiver) {
|
|
1392
1640
|
if (prop === "loading" || prop === "value" || prop === "error") return read()[prop];
|
|
@@ -1556,8 +1804,8 @@ function applyTemplateBindings(host, binds, phase = "all") {
|
|
|
1556
1804
|
const accessor = record.accessor;
|
|
1557
1805
|
if (spec.kind === "this") {
|
|
1558
1806
|
if (phase !== "listen") {
|
|
1559
|
-
accessor(el);
|
|
1560
|
-
cleanups.push(() => accessor(null));
|
|
1807
|
+
accessor.set(el);
|
|
1808
|
+
cleanups.push(() => accessor.set(null));
|
|
1561
1809
|
}
|
|
1562
1810
|
continue;
|
|
1563
1811
|
}
|
|
@@ -1585,7 +1833,7 @@ function applyTemplateBindings(host, binds, phase = "all") {
|
|
|
1585
1833
|
}
|
|
1586
1834
|
arr.push(coercedVal);
|
|
1587
1835
|
} else if (!groupRead.checked && idx !== -1) arr.splice(idx, 1);
|
|
1588
|
-
accessor(arr);
|
|
1836
|
+
accessor.set(arr);
|
|
1589
1837
|
return;
|
|
1590
1838
|
}
|
|
1591
1839
|
const currentVal = accessor();
|
|
@@ -1594,7 +1842,7 @@ function applyTemplateBindings(host, binds, phase = "all") {
|
|
|
1594
1842
|
const n = Number(groupRead);
|
|
1595
1843
|
coerced = Number.isNaN(n) ? 0 : n;
|
|
1596
1844
|
} else if (typeof currentVal === "boolean") coerced = Boolean(groupRead);
|
|
1597
|
-
accessor(coerced);
|
|
1845
|
+
accessor.set(coerced);
|
|
1598
1846
|
return;
|
|
1599
1847
|
}
|
|
1600
1848
|
const currentVal = accessor();
|
|
@@ -1603,7 +1851,7 @@ function applyTemplateBindings(host, binds, phase = "all") {
|
|
|
1603
1851
|
const n = Number(raw);
|
|
1604
1852
|
value = Number.isNaN(n) ? 0 : n;
|
|
1605
1853
|
} else if (typeof currentVal === "boolean") value = Boolean(raw);
|
|
1606
|
-
accessor(value);
|
|
1854
|
+
accessor.set(value);
|
|
1607
1855
|
};
|
|
1608
1856
|
el.addEventListener(event, listener);
|
|
1609
1857
|
cleanups.push(() => el.removeEventListener(event, listener));
|
|
@@ -1687,8 +1935,8 @@ function acquireSlot(kind) {
|
|
|
1687
1935
|
*
|
|
1688
1936
|
* A function argument is treated as a lazy initializer:
|
|
1689
1937
|
* const count = state(() => expensiveInitialValue());
|
|
1690
|
-
* To store a function VALUE,
|
|
1691
|
-
*
|
|
1938
|
+
* To store a function VALUE, pass it to `.set`:
|
|
1939
|
+
* onSave.set(nextCallback);
|
|
1692
1940
|
*/
|
|
1693
1941
|
function state(init) {
|
|
1694
1942
|
const frame = currentFrame();
|
|
@@ -1700,9 +1948,7 @@ function state(init) {
|
|
|
1700
1948
|
const snap = f.ssr ? void 0 : f.instance?.stateSnapshot?.s?.[index];
|
|
1701
1949
|
const initial = snap === void 0 ? typeof init === "function" ? init() : init : snap;
|
|
1702
1950
|
const s = signal(initial);
|
|
1703
|
-
const acc = markSignalAccessor((
|
|
1704
|
-
if (args.length === 0) return s();
|
|
1705
|
-
const next = resolveSignalSetter(() => s(), args[0]);
|
|
1951
|
+
const acc = markSignalAccessor(() => s(), (next) => {
|
|
1706
1952
|
s(next);
|
|
1707
1953
|
});
|
|
1708
1954
|
f.slots[f.cursor - 1] = {
|
|
@@ -1871,7 +2117,7 @@ function action(fn) {
|
|
|
1871
2117
|
data: { get: () => void 0 },
|
|
1872
2118
|
error: { get: () => void 0 }
|
|
1873
2119
|
});
|
|
1874
|
-
|
|
2120
|
+
attachWith(noop);
|
|
1875
2121
|
return noop;
|
|
1876
2122
|
}
|
|
1877
2123
|
const manifestId = `a${f.actionIndex++}`;
|
|
@@ -1900,7 +2146,7 @@ function action(fn) {
|
|
|
1900
2146
|
fn
|
|
1901
2147
|
};
|
|
1902
2148
|
const ssrAccessor = invoke;
|
|
1903
|
-
|
|
2149
|
+
attachWith(ssrAccessor);
|
|
1904
2150
|
return ssrAccessor;
|
|
1905
2151
|
}
|
|
1906
2152
|
const instance = f.instance;
|
|
@@ -1997,7 +2243,7 @@ function action(fn) {
|
|
|
1997
2243
|
instance.actionIds.set(slot.acc, manifestId);
|
|
1998
2244
|
const clientAccessor = invoke;
|
|
1999
2245
|
clientAccessor[ACTION_MANIFEST_ID] = manifestId;
|
|
2000
|
-
|
|
2246
|
+
attachWith(clientAccessor);
|
|
2001
2247
|
return clientAccessor;
|
|
2002
2248
|
}
|
|
2003
2249
|
/**
|
|
@@ -2824,7 +3070,7 @@ const ilha = ((...args) => {
|
|
|
2824
3070
|
if (snapshot !== false) {
|
|
2825
3071
|
const doState = snapshot === true || snapshot.state !== false;
|
|
2826
3072
|
const doDerived = snapshot === true || snapshot.derived !== false;
|
|
2827
|
-
const doSkipOnMount = explicitSkipOnMount ??
|
|
3073
|
+
const doSkipOnMount = explicitSkipOnMount ?? false;
|
|
2828
3074
|
const snapshotData = { v: 2 };
|
|
2829
3075
|
const input = resolveInput(resolvedProps);
|
|
2830
3076
|
const probeFrame = freshFrame({ ssr: true });
|
|
@@ -2974,4 +3220,4 @@ const mount = mountAll;
|
|
|
2974
3220
|
const context = ilhaContext;
|
|
2975
3221
|
|
|
2976
3222
|
//#endregion
|
|
2977
|
-
export { action, batch, context, css, derived, each, effect, html, ilha,
|
|
3223
|
+
export { action, batch, context, css, derived, each, effect, html, ilha, isSafeUrl, isSafeUrlAttrValue, isUrlAttributeName, json, morph, mount, onError, onUncaughtError, persist, raw, serializeStyle, state, untrack };
|