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/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 `.withArgs()` payloads (JSON chars). */
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 `.withArgs()` on an action reference, else undefined. */
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 `.withArgs(...args)` to an action accessor. Returns an explicit,
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("withArgs() rejects non-finite numbers.");
625
+ throw new TypeError("with() rejects non-finite numbers.");
635
626
  case "object": break;
636
- default: throw new TypeError(`withArgs() rejects ${typeof value} values.`);
627
+ default: throw new TypeError(`with() rejects ${typeof value} values.`);
637
628
  }
638
- if (seen.has(value)) throw new TypeError("withArgs() rejects circular values.");
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("withArgs() accepts plain objects only.");
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(`withArgs() rejects unsafe key "${key}".`);
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 attachWithArgs(accessor) {
655
- Object.defineProperty(accessor, "withArgs", {
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(`withArgs() payload exceeds ${MAX_BOUND_ARGS_CHARS} JSON characters.`);
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 resolveSignalSetter(read, value) {
770
- if (typeof value !== "function") return value;
771
- const prevSub = setActiveSub(void 0);
772
- try {
773
- return value(read());
774
- } finally {
775
- setActiveSub(prevSub);
776
- }
777
- }
778
- function markSignalAccessor(fn) {
779
- fn[SIGNAL_ACCESSOR] = true;
780
- const accessor = fn;
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((...args) => {
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(() => next);
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 openTagName(source, index) {
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
- function ilhaHtml(strings, ...values) {
1031
- let result = "";
1032
- let stripLeadingQuote = null;
1033
- let pendingBindSpecs = "";
1034
- let pendingEventSpecs = "";
1035
- let pendingQuote = null;
1036
- const ctx = currentRenderCtx();
1037
- for (let i = 0; i < strings.length; i++) {
1038
- let chunk = strings[i];
1039
- if (stripLeadingQuote !== null) {
1040
- if (chunk.startsWith(stripLeadingQuote)) chunk = chunk.slice(1);
1041
- stripLeadingQuote = null;
1042
- }
1043
- if (pendingBindSpecs !== "" || pendingEventSpecs !== "") {
1044
- const { index: gtIdx, quote } = findTagCloseIndex(chunk, pendingQuote);
1045
- if (gtIdx === -1) pendingQuote = quote;
1046
- else {
1047
- const head = chunk.slice(0, gtIdx).replace(/\s*\/\s*$/, "");
1048
- const bindAttr = pendingBindSpecs ? ` data-ilha-bind="${pendingBindSpecs}"` : "";
1049
- const eventAttr = pendingEventSpecs ? ` data-ilha-on="${pendingEventSpecs}"` : "";
1050
- chunk = head + bindAttr + eventAttr + ">" + chunk.slice(gtIdx + 1);
1051
- pendingBindSpecs = "";
1052
- pendingEventSpecs = "";
1053
- pendingQuote = null;
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 >= values.length) {
1057
- result += chunk;
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
- const value = values[i];
1061
- const eventMatch = chunk.match(EVENT_PREFIX_RE);
1062
- const eventMatchStart = eventMatch ? chunk.length - eventMatch[0].length : -1;
1063
- if (!eventMatch && typeof value === "function") {
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
- const eventAttributeFragment = eventMatch !== null && chunk.slice(0, eventMatchStart).trim() === "";
1069
- if (eventMatch && (eventAttributeFragment || isEventAttributePosition(result + chunk, result.length + eventMatchStart))) {
1070
- const eventName = eventMatch[1].toLowerCase();
1071
- const rawModifier = eventMatch[2]?.toLowerCase();
1072
- const modifier = rawModifier !== void 0 && NATIVE_EVENT_MODIFIERS.has(rawModifier) ? rawModifier : void 0;
1073
- const openQuote = eventMatch[3] ?? null;
1074
- const nextChunk = strings[i + 1] ?? "";
1075
- if (!(openQuote ? nextChunk.startsWith(openQuote) : /^(?:\s|\/?>|$)/.test(nextChunk))) throw new Error(`[ilha] on${eventName} handler must occupy the entire attribute value. Write on${eventName}=\${handler}.`);
1076
- const cleanPrefix = chunk.slice(0, eventMatchStart).replace(/\s+$/, "");
1077
- result += cleanPrefix;
1078
- typeof value === "function" && SSR_ACTION_STUB in value;
1079
- if (ctx && typeof value === "function") {
1080
- const index = ctx.events.length;
1081
- if (rawModifier && !modifier) {} else {
1082
- ctx.events.push({
1083
- type: eventName,
1084
- handler: value,
1085
- modifier
1086
- });
1087
- pendingEventSpecs += (pendingEventSpecs ? "," : "") + `${eventName}:${index}`;
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 (openQuote) stripLeadingQuote = openQuote;
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 m = chunk.match(BIND_PREFIX_RE);
1094
- if (m && isSignalAccessor(value)) {
1095
- const name = m[1];
1096
- const openQuote = m[2] ?? null;
1097
- if (!BIND_VALID_KINDS.has(name)) {
1098
- result += chunk + interpolateValue(value);
1099
- continue;
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 stripped = chunk.slice(0, chunk.length - m[0].length);
1103
- const quote = openQuote ?? "\"";
1104
- result += stripped + name + "=" + quote + interpolateValue(value) + quote;
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: name,
1113
- accessor: value
1379
+ kind,
1380
+ accessor: sourceValue
1114
1381
  });
1115
- const [valueAttrs, spec] = emitBindSSR({
1116
- kind: name,
1382
+ const [reflected, spec] = emitBindSSR({
1383
+ kind,
1117
1384
  index,
1118
- accessor: value,
1119
- prefixForStaticPeek: cleanPrefix
1385
+ accessor: sourceValue,
1386
+ optionValue: resolvedAttrText(node.props.value)
1120
1387
  });
1121
- result += cleanPrefix + valueAttrs;
1122
- pendingBindSpecs += (pendingBindSpecs ? "," : "") + spec;
1123
- if (openQuote) stripLeadingQuote = openQuote;
1388
+ attrs += reflected;
1389
+ bindSpecs.push(spec);
1124
1390
  continue;
1125
1391
  }
1126
- const attrMatch = chunk.match(FULL_VALUE_ATTR_RE);
1127
- if (attrMatch && isEventAttributePosition(result + chunk, result.length + attrMatch.index)) {
1128
- const attrName = attrMatch[1];
1129
- const openQuote = attrMatch[2] ?? null;
1130
- if (openQuote) stripLeadingQuote = openQuote;
1131
- const cleanPrefix = chunk.slice(0, attrMatch.index).replace(/\s+$/, "");
1132
- if (attrName.length > 2 && /^on/i.test(attrName)) {
1133
- result += cleanPrefix;
1134
- continue;
1135
- }
1136
- if (attrName.toLowerCase() === "srcdoc") {
1137
- result += cleanPrefix;
1138
- continue;
1139
- }
1140
- if (isIsland(value) || isIslandCall(value)) {
1141
- stripLeadingQuote = null;
1142
- result += chunk + interpolateValue(value);
1143
- continue;
1144
- }
1145
- if (isUrlAttributeName(attrName)) {
1146
- if (isRawHtml(value)) {
1147
- result += cleanPrefix + ` ${attrName}="${value.value}"`;
1148
- continue;
1149
- }
1150
- const resolved = resolveTemplateValue(value);
1151
- if (resolved == null || resolved === false) {
1152
- result += cleanPrefix;
1153
- continue;
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
- result += chunk + interpolateValue(value);
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
- if (pendingBindSpecs !== "") result += ` data-ilha-bind="${pendingBindSpecs}"`;
1195
- if (pendingEventSpecs !== "") result += ` data-ilha-on="${pendingEventSpecs}"`;
1196
- return {
1197
- [RAW]: true,
1198
- value: dedentString(result)
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((...args) => {
1213
- if (args.length === 0) return s();
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((...args) => {
1238
- if (args.length === 0) return s();
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(() => value);
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((...args) => {
1385
- if (args.length > 0) {
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, return it from the updater wrapper on write:
1691
- * setCallback(() => nextCallback);
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((...args) => {
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
- attachWithArgs(noop);
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
- attachWithArgs(ssrAccessor);
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
- attachWithArgs(clientAccessor);
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 ?? (doState || doDerived);
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, ilhaSignal, isSafeUrl, isSafeUrlAttrValue, isUrlAttributeName, json, morph, mount, onError, onUncaughtError, persist, raw, serializeStyle, state, untrack };
3223
+ export { action, batch, context, css, derived, each, effect, html, ilha, isSafeUrl, isSafeUrlAttrValue, isUrlAttributeName, json, morph, mount, onError, onUncaughtError, persist, raw, serializeStyle, state, untrack };