marko 6.1.11 → 6.1.13

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.
@@ -20,6 +20,7 @@ function* attrTagIterator() {
20
20
  }
21
21
  //#endregion
22
22
  //#region src/common/errors.ts
23
+ const lowercaseEventHandlerReg = /^on[a-z]/;
23
24
  function _el_read_error() {
24
25
  throw new Error("Element references can only be read in scripts and event handlers.");
25
26
  }
@@ -29,10 +30,6 @@ function _hoist_read_error() {
29
30
  function _assert_hoist(value) {
30
31
  if (typeof value !== "function") throw new Error(`Hoisted values must be functions, received type "${typeof value}".`);
31
32
  }
32
- function _assert_init(scope, accessor) {
33
- if (scope["#Creating"] || !(accessor in scope)) throw new ReferenceError(`Cannot access '${accessor}' before initialization`);
34
- return scope[accessor];
35
- }
36
33
  function assertExclusiveAttrs(attrs, onError = throwErr) {
37
34
  if (attrs) {
38
35
  let exclusiveAttrs;
@@ -48,6 +45,12 @@ function assertExclusiveAttrs(attrs, onError = throwErr) {
48
45
  if (exclusiveAttrs && exclusiveAttrs.length > 1) onError(`The attributes ${joinWithAnd(exclusiveAttrs)} are mutually exclusive.`);
49
46
  }
50
47
  }
48
+ function assertValidEventHandlerAttr(name, value) {
49
+ if (value && typeof value !== "string" && lowercaseEventHandlerReg.test(name)) throw new Error(`The \`${name}\` attribute must be a string or a falsey value (\`null\`, \`undefined\`, \`false\`, \`0\`, …), but received type "${typeof value}". To attach an event listener, use the \`on${name[2].toUpperCase()}${name.slice(3)}\` event handler instead.`);
50
+ }
51
+ function assertHandlerIsFunction(name, value) {
52
+ if (value && typeof value !== "function") throw new Error(`The \`${name}\` handler must be a function or a falsey value (\`null\`, \`undefined\`, \`false\`, \`0\`, …), but received type "${typeof value}".`);
53
+ }
51
54
  function assertValidTagName(tagName) {
52
55
  if (!/^[a-z][a-z0-9._-]*$/i.test(tagName)) throw new Error(`Invalid tag name: "${tagName}". Tag names must start with a letter and contain only letters, numbers, periods, hyphens, and underscores.`);
53
56
  }
@@ -148,6 +151,7 @@ function push(opt, item) {
148
151
  //#region src/dom/event.ts
149
152
  const defaultDelegator = /* @__PURE__ */ createDelegator();
150
153
  function _on(element, type, handler) {
154
+ assertHandlerIsFunction("on" + type[0].toUpperCase() + type.slice(1), handler);
151
155
  if (element["$" + type] === void 0) defaultDelegator(element, type, handleDelegated);
152
156
  element["$" + type] = handler || null;
153
157
  }
@@ -200,16 +204,34 @@ function parseHTML(html, ns) {
200
204
  //#endregion
201
205
  //#region src/dom/scope.ts
202
206
  let nextScopeId = 1e6;
207
+ let collectingScopes;
203
208
  function createScope($global, closestBranch) {
204
209
  const scope = {
205
210
  ["#Id"]: nextScopeId++,
206
- ["#Creating"]: 1,
211
+ ["#Gen"]: runId,
207
212
  ["#ClosestBranch"]: closestBranch,
208
213
  ["$global"]: $global
209
214
  };
210
- pendingScopes.push(scope);
215
+ collectingScopes?.push(scope);
211
216
  return scope;
212
217
  }
218
+ function syncGen(scope) {
219
+ scope["#Gen"] = runId;
220
+ }
221
+ function _assert_init(scope, accessor) {
222
+ if (scope["#Gen"] === runId || !(accessor in scope)) throw new ReferenceError(`Cannot access '${accessor}' before initialization`);
223
+ return scope[accessor];
224
+ }
225
+ function collectScopes(fn) {
226
+ const prev = collectingScopes;
227
+ collectingScopes = [];
228
+ try {
229
+ fn();
230
+ return collectingScopes;
231
+ } finally {
232
+ collectingScopes = prev;
233
+ }
234
+ }
213
235
  function skipScope() {
214
236
  return nextScopeId++;
215
237
  }
@@ -223,13 +245,13 @@ function destroyBranch(branch) {
223
245
  destroyNestedScopes(branch);
224
246
  }
225
247
  function destroyScope(scope) {
226
- if (!scope["#Destroyed"]) {
248
+ if (scope["#Gen"]) {
227
249
  destroyNestedScopes(scope);
228
250
  resetControllers(scope);
229
251
  }
230
252
  }
231
253
  const destroyNestedScopes = function destroyNestedScopes(scope) {
232
- scope["#Destroyed"] = 1;
254
+ scope["#Gen"] = 0;
233
255
  scope["#BranchScopes"]?.forEach(destroyNestedScopes);
234
256
  scope["#AbortScopes"]?.forEach(resetControllers);
235
257
  };
@@ -288,7 +310,7 @@ function _let(id, fn) {
288
310
  id = +id.slice(id.lastIndexOf("/") + 1);
289
311
  return (scope, value) => {
290
312
  if (rendering) {
291
- if (scope["#Creating"]) {
313
+ if (scope["#Gen"] === runId) {
292
314
  scope[valueAccessor] = value;
293
315
  fn?.(scope);
294
316
  }
@@ -323,7 +345,7 @@ function _const(valueAccessor, fn) {
323
345
  }
324
346
  function _or(id, fn, defaultPending = 1, scopeIdAccessor = "#Id") {
325
347
  return (scope) => {
326
- if (scope["#Creating"]) if (id in scope) {
348
+ if (scope["#Gen"] === runId) if (id in scope) {
327
349
  if (!--scope[id]) fn(scope);
328
350
  } else scope[id] = defaultPending;
329
351
  else queueRender(scope, fn, id, 0, scope[scopeIdAccessor]);
@@ -334,7 +356,7 @@ function _for_closure(ownerLoopNodeAccessor, fn) {
334
356
  const ownerSignal = (ownerScope) => {
335
357
  const scopes = toArray(ownerScope[scopeAccessor]);
336
358
  if (scopes.length) queueRender(ownerScope, () => {
337
- for (const scope of scopes) if (!scope["#Creating"] && !scope["#Destroyed"]) fn(scope);
359
+ for (const scope of scopes) if (scope["#Gen"] > 0 && scope["#Gen"] < runId) fn(scope);
338
360
  }, -1, 0, scopes[0]["#Id"]);
339
361
  };
340
362
  ownerSignal._ = fn;
@@ -345,7 +367,7 @@ function _if_closure(ownerConditionalNodeAccessor, branch, fn) {
345
367
  const branchAccessor = "ConditionalRenderer:" + ownerConditionalNodeAccessor;
346
368
  const ownerSignal = (scope) => {
347
369
  const ifScope = scope[scopeAccessor];
348
- if (ifScope && !ifScope["#Creating"] && (scope[branchAccessor] || 0) === branch) queueRender(ifScope, fn, -1);
370
+ if (ifScope && ifScope["#Gen"] > 0 && ifScope["#Gen"] < runId && (scope[branchAccessor] || 0) === branch) queueRender(ifScope, fn, -1);
349
371
  };
350
372
  ownerSignal._ = fn;
351
373
  return ownerSignal;
@@ -364,7 +386,7 @@ function _closure(...closureSignals) {
364
386
  for (let i = closureSignals.length; i--;) closureSignals[i]["index"] = i;
365
387
  return (scope) => {
366
388
  if (scope[scopeInstances]) {
367
- for (const childScope of scope[scopeInstances]) if (!childScope["#Creating"]) queueRender(childScope, closureSignals[childScope[signalIndex] || 0], -1);
389
+ for (const childScope of scope[scopeInstances]) if (childScope["#Gen"] > 0 && childScope["#Gen"] < runId) queueRender(childScope, closureSignals[childScope[signalIndex] || 0], -1);
368
390
  }
369
391
  };
370
392
  }
@@ -604,6 +626,7 @@ function init(runtimeId = "M") {
604
626
  renderId
605
627
  };
606
628
  const initScope = (scope) => {
629
+ scope["#Gen"] = 1;
607
630
  scope["$global"] = initGlobal();
608
631
  if (branchesEnabled && scope["#ClosestBranchId"]) scope["#ClosestBranch"] = getScope(scope["#ClosestBranchId"]);
609
632
  return scope;
@@ -768,7 +791,7 @@ function _attr_input_checked_default(scope, nodeAccessor, checked) {
768
791
  const el = scope[nodeAccessor];
769
792
  const normalizedChecked = isNotVoid(checked);
770
793
  if (el.defaultChecked !== normalizedChecked) {
771
- const restoreValue = scope["#Creating"] ? normalizedChecked : el.checked;
794
+ const restoreValue = scope["#Gen"] < runId ? el.checked : normalizedChecked;
772
795
  el.defaultChecked = normalizedChecked;
773
796
  if (restoreValue !== normalizedChecked) el.checked = restoreValue;
774
797
  }
@@ -776,9 +799,10 @@ function _attr_input_checked_default(scope, nodeAccessor, checked) {
776
799
  function _attr_input_checked(scope, nodeAccessor, checked, checkedChange) {
777
800
  const el = scope[nodeAccessor];
778
801
  const normalizedChecked = isNotVoid(checked);
802
+ assertHandlerIsFunction("checkedChange", checkedChange);
779
803
  scope["ControlledHandler:" + nodeAccessor] = checkedChange;
780
804
  scope["ControlledType:" + nodeAccessor] = checkedChange ? 0 : 5;
781
- if (checkedChange && !scope["#Creating"]) el.checked = normalizedChecked;
805
+ if (checkedChange && scope["#Gen"] < runId) el.checked = normalizedChecked;
782
806
  else _attr_input_checked_default(scope, nodeAccessor, normalizedChecked);
783
807
  }
784
808
  function _attr_input_checked_script(scope, nodeAccessor) {
@@ -804,9 +828,10 @@ function _attr_input_checkedValue(scope, nodeAccessor, checkedValue, checkedValu
804
828
  const el = scope[nodeAccessor];
805
829
  const multiple = Array.isArray(checkedValue);
806
830
  const normalizedCheckedValue = scope["ControlledValue:" + nodeAccessor] = multiple ? checkedValue.map(normalizeStrProp) : normalizeStrProp(checkedValue);
831
+ assertHandlerIsFunction("checkedValueChange", checkedValueChange);
807
832
  scope["ControlledHandler:" + nodeAccessor] = checkedValueChange;
808
833
  scope["ControlledType:" + nodeAccessor] = checkedValueChange ? 1 : 5;
809
- if (checkedValueChange && !scope["#Creating"]) {
834
+ if (checkedValueChange && scope["#Gen"] < runId) {
810
835
  el.checked = multiple ? normalizedCheckedValue.includes(normalizeStrProp(value)) : normalizeStrProp(value) === normalizedCheckedValue;
811
836
  _attr(el, "value", value);
812
837
  } else _attr_input_checkedValue_default(scope, nodeAccessor, checkedValue, value);
@@ -832,7 +857,7 @@ function _attr_input_value_default(scope, nodeAccessor, value) {
832
857
  const el = scope[nodeAccessor];
833
858
  const normalizedValue = normalizeAttrValue(value) || "";
834
859
  if (el.defaultValue !== normalizedValue) {
835
- const restoreValue = scope["#Creating"] ? normalizedValue : el.value;
860
+ const restoreValue = scope["#Gen"] < runId ? el.value : normalizedValue;
836
861
  el.defaultValue = normalizedValue;
837
862
  setInputValue(el, restoreValue);
838
863
  }
@@ -840,10 +865,11 @@ function _attr_input_value_default(scope, nodeAccessor, value) {
840
865
  function _attr_input_value(scope, nodeAccessor, value, valueChange) {
841
866
  const el = scope[nodeAccessor];
842
867
  const normalizedValue = normalizeAttrValue(value) || "";
868
+ assertHandlerIsFunction("valueChange", valueChange);
843
869
  scope["ControlledHandler:" + nodeAccessor] = valueChange;
844
870
  scope["ControlledValue:" + nodeAccessor] = normalizedValue;
845
871
  scope["ControlledType:" + nodeAccessor] = valueChange ? 2 : 5;
846
- if (valueChange && !scope["#Creating"]) setInputValue(el, normalizedValue);
872
+ if (valueChange && scope["#Gen"] < runId) setInputValue(el, normalizedValue);
847
873
  else _attr_input_value_default(scope, nodeAccessor, normalizedValue);
848
874
  }
849
875
  function _attr_input_value_script(scope, nodeAccessor) {
@@ -869,14 +895,14 @@ function setInputValue(el, value) {
869
895
  function _attr_select_value_default(scope, nodeAccessor, value) {
870
896
  let restoreValue;
871
897
  const el = scope[nodeAccessor];
872
- const existing = !scope["#Creating"];
898
+ const live = scope["#Gen"] < runId;
873
899
  const multiple = Array.isArray(value);
874
900
  const normalizedValue = multiple ? value.map(normalizeStrProp) : normalizeStrProp(value);
875
901
  pendingEffects.unshift(() => {
876
902
  for (const opt of el.options) {
877
903
  const selected = multiple ? normalizedValue.includes(opt.value) : opt.value === normalizedValue;
878
904
  if (opt.defaultSelected !== selected) {
879
- if (existing) restoreValue ??= getSelectValue(el, multiple);
905
+ if (live) restoreValue ??= getSelectValue(el, multiple);
880
906
  opt.defaultSelected = selected;
881
907
  }
882
908
  }
@@ -885,9 +911,10 @@ function _attr_select_value_default(scope, nodeAccessor, value) {
885
911
  }
886
912
  function _attr_select_value(scope, nodeAccessor, value, valueChange) {
887
913
  const el = scope[nodeAccessor];
888
- const existing = !scope["#Creating"];
914
+ const existing = scope["#Gen"] < runId;
889
915
  const multiple = Array.isArray(value);
890
916
  const normalizedValue = scope["ControlledValue:" + nodeAccessor] = multiple ? value.map(normalizeStrProp) : normalizeStrProp(value);
917
+ assertHandlerIsFunction("valueChange", valueChange);
891
918
  scope["ControlledHandler:" + nodeAccessor] = valueChange;
892
919
  scope["ControlledType:" + nodeAccessor] = valueChange ? 3 : 5;
893
920
  if (valueChange && existing) pendingEffects.unshift(() => setSelectValue(el, normalizedValue, multiple), scope);
@@ -933,13 +960,14 @@ function getSelectValue(el, multiple) {
933
960
  return multiple ? Array.from(el.selectedOptions, (opt) => opt.value) : el.value;
934
961
  }
935
962
  function _attr_details_or_dialog_open_default(scope, nodeAccessor, open) {
936
- if (scope["#Creating"]) scope[nodeAccessor].open = isNotVoid(open);
963
+ if (scope["#Gen"] === runId) scope[nodeAccessor].open = isNotVoid(open);
937
964
  }
938
965
  function _attr_details_or_dialog_open(scope, nodeAccessor, open, openChange) {
939
966
  const normalizedOpen = scope["ControlledValue:" + nodeAccessor] = isNotVoid(open);
967
+ assertHandlerIsFunction("openChange", openChange);
940
968
  scope["ControlledHandler:" + nodeAccessor] = openChange;
941
969
  scope["ControlledType:" + nodeAccessor] = openChange ? 4 : 5;
942
- if (openChange && !scope["#Creating"]) scope[nodeAccessor].open = normalizedOpen;
970
+ if (openChange && scope["#Gen"] < runId) scope[nodeAccessor].open = normalizedOpen;
943
971
  else _attr_details_or_dialog_open_default(scope, nodeAccessor, normalizedOpen);
944
972
  }
945
973
  function _attr_details_or_dialog_open_script(scope, nodeAccessor) {
@@ -998,6 +1026,7 @@ function _to_text(value) {
998
1026
  return value || value === 0 ? value + "" : "";
999
1027
  }
1000
1028
  function _attr(element, name, value) {
1029
+ assertValidEventHandlerAttr(name, value);
1001
1030
  setAttribute(element, name, normalizeAttrValue(value));
1002
1031
  }
1003
1032
  function setAttribute(element, name, value) {
@@ -1072,8 +1101,9 @@ function _attrs_partial_content(scope, nodeAccessor, nextAttrs, skip) {
1072
1101
  }
1073
1102
  function attrsInternal(scope, nodeAccessor, nextAttrs) {
1074
1103
  const el = scope[nodeAccessor];
1075
- let events;
1104
+ let events = scope["EventAttributes:" + nodeAccessor];
1076
1105
  let skip;
1106
+ for (const name in events) events[name] = 0;
1077
1107
  switch (el.tagName) {
1078
1108
  case "INPUT":
1079
1109
  if ("checked" in nextAttrs || "checkedChange" in nextAttrs) _attr_input_checked(scope, nodeAccessor, nextAttrs.checked, nextAttrs.checkedChange);
@@ -1243,7 +1273,7 @@ function _await_promise(nodeAccessor, params) {
1243
1273
  scope[promiseAccessor] = 0;
1244
1274
  queueAsyncRender(scope, () => {
1245
1275
  if ((awaitBranch = scope[branchAccessor])["#DetachedAwait"]) {
1246
- pendingScopes.push(awaitBranch);
1276
+ awaitBranch["#PendingScopes"] = awaitBranch["#PendingScopes"]?.forEach(syncGen);
1247
1277
  setupBranch(awaitBranch["#DetachedAwait"], awaitBranch);
1248
1278
  awaitBranch["#DetachedAwait"] = 0;
1249
1279
  insertBranchBefore(awaitBranch, scope[nodeAccessor].parentNode, scope[nodeAccessor]);
@@ -1284,8 +1314,8 @@ function _await_content(nodeAccessor, template, walks, setup) {
1284
1314
  const branchAccessor = "BranchScopes:" + nodeAccessor;
1285
1315
  const renderer = _content("", template, walks, setup)();
1286
1316
  return (scope) => {
1287
- (scope[branchAccessor] = createBranch(scope["$global"], renderer, scope, scope[nodeAccessor].parentNode))["#DetachedAwait"] = renderer;
1288
- pendingScopes.pop();
1317
+ const pendingScopes = collectScopes(() => (scope[branchAccessor] = createBranch(scope["$global"], renderer, scope, scope[nodeAccessor].parentNode))["#DetachedAwait"] = renderer);
1318
+ scope[branchAccessor]["#PendingScopes"] = pendingScopes;
1289
1319
  };
1290
1320
  }
1291
1321
  function addAwaitCounter(scope, tryBranch = findBranchWithKey(scope, "#PlaceholderContent")) {
@@ -1357,7 +1387,7 @@ function _if(nodeAccessor, ...branchesArgs) {
1357
1387
  while (i < branchesArgs.length) branches.push(_content("", branchesArgs[i++], branchesArgs[i++], branchesArgs[i++])());
1358
1388
  enableBranches();
1359
1389
  return (scope, newBranch) => {
1360
- if (newBranch !== scope[branchAccessor]) setConditionalRenderer(scope, nodeAccessor, branches[scope[branchAccessor] = newBranch], createAndSetupBranch);
1390
+ if (newBranch !== (scope[branchAccessor] ?? (scope["BranchScopes:" + nodeAccessor] && 0))) setConditionalRenderer(scope, nodeAccessor, branches[scope[branchAccessor] = newBranch], createAndSetupBranch);
1361
1391
  };
1362
1392
  }
1363
1393
  function patchDynamicTag(fn) {
@@ -1449,7 +1479,8 @@ function loop(forEach) {
1449
1479
  let hasPotentialMoves;
1450
1480
  var seenKeys = /* @__PURE__ */ new Set();
1451
1481
  forEach(value, (key, args) => {
1452
- if (seenKeys.has(key)) console.error(`A <for> tag's \`by\` attribute must return a unique value for each item, but a duplicate was found matching:`, key);
1482
+ if (typeof key !== "string" && typeof key !== "number") console.error(`A <for> tag's \`by\` attribute must return a string or number, but it returned:`, key);
1483
+ else if (seenKeys.has(key)) console.error(`A <for> tag's \`by\` attribute must return a unique value for each item, but a duplicate was found matching:`, key);
1453
1484
  else seenKeys.add(key);
1454
1485
  let branch = oldLen && (oldScopesByKey ||= oldScopes.reduce((map, scope, i) => map.set(scope["#LoopKey"] ?? i, scope), /* @__PURE__ */ new Map())).get(key);
1455
1486
  if (branch) hasPotentialMoves = oldScopesByKey.delete(key);
@@ -1548,13 +1579,12 @@ function byFirstArg(name) {
1548
1579
  }
1549
1580
  //#endregion
1550
1581
  //#region src/dom/queue.ts
1551
- let runId = 1;
1552
- let pendingRenders = [];
1582
+ let rendering;
1583
+ let runId = 2;
1553
1584
  const caughtError = /* @__PURE__ */ new WeakSet();
1554
1585
  const placeholderShown = /* @__PURE__ */ new WeakSet();
1555
1586
  let pendingEffects = [];
1556
- let pendingScopes = [];
1557
- let rendering;
1587
+ let pendingRenders = [];
1558
1588
  const scopeKeyOffset = 1e3;
1559
1589
  function queueRender(scope, signal, signalKey, value, scopeKey = scope["#Id"]) {
1560
1590
  let render;
@@ -1647,13 +1677,11 @@ function runRenders() {
1647
1677
  }
1648
1678
  runRender(render);
1649
1679
  }
1650
- for (const scope of pendingScopes) scope["#Creating"] = 0;
1651
- pendingScopes = [];
1652
1680
  }
1653
1681
  let runRender = (render) => render["signal"](render["scope"], render["value"]);
1654
1682
  function skipDestroyedRenders() {
1655
1683
  runRender = ((runRender) => (render) => {
1656
- if (!render["scope"]["#ClosestBranch"]?.["#Destroyed"]) runRender(render);
1684
+ if (render["scope"]["#ClosestBranch"]?.["#Gen"] !== 0) runRender(render);
1657
1685
  })(runRender);
1658
1686
  }
1659
1687
  let catchEnabled;
@@ -1676,8 +1704,7 @@ function _enable_catch() {
1676
1704
  for (; i < effects.length;) {
1677
1705
  fn = effects[i++];
1678
1706
  scope = effects[i++];
1679
- branch = scope["#ClosestBranch"];
1680
- if (!branch?.["#Destroyed"] && !(checkPending && handlePendingTry(fn, scope, branch))) fn(scope);
1707
+ if ((branch = scope["#ClosestBranch"])?.["#Gen"] !== 0 && !(checkPending && handlePendingTry(fn, scope, branch))) fn(scope);
1681
1708
  }
1682
1709
  } else runEffects(effects);
1683
1710
  })(runEffects);
@@ -1901,13 +1928,12 @@ function _load_setup(nodeAccessor, childScopeAccessor, load) {
1901
1928
  }
1902
1929
  function insertLoaded(renderer, branch, marker, awaitCounter) {
1903
1930
  const parent = marker.parentNode;
1904
- branch["#Creating"] = 1;
1931
+ syncGen(branch);
1905
1932
  renderer["clone"](branch, parent.namespaceURI);
1906
1933
  setupBranch(renderer, branch);
1907
1934
  applyLoad(branch, () => {
1908
1935
  insertBranchBefore(branch, parent, marker);
1909
1936
  marker.remove();
1910
- pendingScopes.push(branch);
1911
1937
  awaitCounter?.c();
1912
1938
  });
1913
1939
  }
@@ -1923,14 +1949,11 @@ function applyLoad(scope, insert) {
1923
1949
  scope["#Load"] = 0;
1924
1950
  if (remaining = values?.size) for (const [promise, entry] of values) promise.then((signal) => {
1925
1951
  entry["signal"] = signal;
1926
- if (!--remaining) {
1927
- scope["#Creating"] = 1;
1928
- pendingScopes.push(scope);
1929
- queueAsyncRender(scope, (scope) => {
1930
- values.forEach((e) => e["signal"]._(scope, e["value"]));
1931
- insert();
1932
- });
1933
- }
1952
+ if (!--remaining) queueAsyncRender(scope, (scope) => {
1953
+ syncGen(scope);
1954
+ values.forEach((e) => e["signal"]._(scope, e["value"]));
1955
+ insert();
1956
+ });
1934
1957
  }, () => 0);
1935
1958
  else insert();
1936
1959
  }
@@ -1939,7 +1962,7 @@ function _load_signal(load) {
1939
1962
  let signal;
1940
1963
  return (scope, value) => {
1941
1964
  pending ||= load();
1942
- if (scope["#Load"] || !("#Load" in scope) && scope["#Creating"]) (scope["#Load"] ||= /* @__PURE__ */ new Map()).set(pending, { ["value"]: value });
1965
+ if (scope["#Load"] || !("#Load" in scope) && scope["#Gen"] === runId) (scope["#Load"] ||= /* @__PURE__ */ new Map()).set(pending, { ["value"]: value });
1943
1966
  else if (signal) signal(scope, value);
1944
1967
  else pending.then((mod) => queueAsyncRender(scope, signal = mod._, value), () => 0);
1945
1968
  };
@@ -22,6 +22,7 @@ function* attrTagIterator() {
22
22
  }
23
23
  //#endregion
24
24
  //#region src/common/errors.ts
25
+ const lowercaseEventHandlerReg = /^on[a-z]/;
25
26
  function _el_read_error() {
26
27
  throw new Error("Element references can only be read in scripts and event handlers.");
27
28
  }
@@ -46,6 +47,12 @@ function assertExclusiveAttrs(attrs, onError = throwErr) {
46
47
  if (exclusiveAttrs && exclusiveAttrs.length > 1) onError(`The attributes ${joinWithAnd(exclusiveAttrs)} are mutually exclusive.`);
47
48
  }
48
49
  }
50
+ function assertValidEventHandlerAttr(name, value) {
51
+ if (value && typeof value !== "string" && lowercaseEventHandlerReg.test(name)) throw new Error(`The \`${name}\` attribute must be a string or a falsey value (\`null\`, \`undefined\`, \`false\`, \`0\`, …), but received type "${typeof value}". To attach an event listener, use the \`on${name[2].toUpperCase()}${name.slice(3)}\` event handler instead.`);
52
+ }
53
+ function assertHandlerIsFunction(name, value) {
54
+ if (value && typeof value !== "function") throw new Error(`The \`${name}\` handler must be a function or a falsey value (\`null\`, \`undefined\`, \`false\`, \`0\`, …), but received type "${typeof value}".`);
55
+ }
49
56
  function assertValidTagName(tagName) {
50
57
  if (!/^[a-z][a-z0-9._-]*$/i.test(tagName)) throw new Error(`Invalid tag name: "${tagName}". Tag names must start with a letter and contain only letters, numbers, periods, hyphens, and underscores.`);
51
58
  }
@@ -1398,17 +1405,26 @@ function concat(opt, other) {
1398
1405
  //#region src/html/for.ts
1399
1406
  function forOfBy(by, item, index) {
1400
1407
  if (by) {
1401
- if (typeof by === "string") return item[by];
1402
- return by(item, index);
1408
+ const key = typeof by === "string" ? item[by] : by(item, index);
1409
+ if (typeof key !== "string" && typeof key !== "number") console.error(`A <for> tag's \`by\` attribute must return a string or number, but it returned:`, key);
1410
+ return key;
1403
1411
  }
1404
1412
  return index;
1405
1413
  }
1406
1414
  function forInBy(by, name, value) {
1407
- if (by) return by(name, value);
1415
+ if (by) {
1416
+ const key = by(name, value);
1417
+ if (typeof key !== "string" && typeof key !== "number") console.error(`A <for> tag's \`by\` attribute must return a string or number, but it returned:`, key);
1418
+ return key;
1419
+ }
1408
1420
  return name;
1409
1421
  }
1410
1422
  function forStepBy(by, index) {
1411
- if (by) return by(index);
1423
+ if (by) {
1424
+ const key = by(index);
1425
+ if (typeof key !== "string" && typeof key !== "number") console.error(`A <for> tag's \`by\` attribute must return a string or number, but it returned:`, key);
1426
+ return key;
1427
+ }
1412
1428
  return index;
1413
1429
  }
1414
1430
  //#endregion
@@ -2414,6 +2430,7 @@ function _attr_nonce() {
2414
2430
  return getChunk().boundary.state.nonceAttr;
2415
2431
  }
2416
2432
  function _attr(name, value) {
2433
+ assertValidEventHandlerAttr(name, value);
2417
2434
  return isVoid(value) ? "" : nonVoidAttr(name, value);
2418
2435
  }
2419
2436
  function _attrs(data, nodeAccessor, scopeId, tagName) {
@@ -2489,6 +2506,7 @@ function _attrs_partial_content(data, skip, nodeAccessor, scopeId, tagName, seri
2489
2506
  _attr_content(nodeAccessor, scopeId, data?.content, serializeReason);
2490
2507
  }
2491
2508
  function writeControlledScope(type, scopeId, nodeAccessor, value, valueChange) {
2509
+ assertHandlerIsFunction("valueChange", valueChange);
2492
2510
  writeScope(scopeId, {
2493
2511
  ["ControlledType:" + nodeAccessor]: type,
2494
2512
  ["ControlledValue:" + nodeAccessor]: value,
@@ -20,6 +20,7 @@ function* attrTagIterator() {
20
20
  }
21
21
  //#endregion
22
22
  //#region src/common/errors.ts
23
+ const lowercaseEventHandlerReg = /^on[a-z]/;
23
24
  function _el_read_error() {
24
25
  throw new Error("Element references can only be read in scripts and event handlers.");
25
26
  }
@@ -44,6 +45,12 @@ function assertExclusiveAttrs(attrs, onError = throwErr) {
44
45
  if (exclusiveAttrs && exclusiveAttrs.length > 1) onError(`The attributes ${joinWithAnd(exclusiveAttrs)} are mutually exclusive.`);
45
46
  }
46
47
  }
48
+ function assertValidEventHandlerAttr(name, value) {
49
+ if (value && typeof value !== "string" && lowercaseEventHandlerReg.test(name)) throw new Error(`The \`${name}\` attribute must be a string or a falsey value (\`null\`, \`undefined\`, \`false\`, \`0\`, …), but received type "${typeof value}". To attach an event listener, use the \`on${name[2].toUpperCase()}${name.slice(3)}\` event handler instead.`);
50
+ }
51
+ function assertHandlerIsFunction(name, value) {
52
+ if (value && typeof value !== "function") throw new Error(`The \`${name}\` handler must be a function or a falsey value (\`null\`, \`undefined\`, \`false\`, \`0\`, …), but received type "${typeof value}".`);
53
+ }
47
54
  function assertValidTagName(tagName) {
48
55
  if (!/^[a-z][a-z0-9._-]*$/i.test(tagName)) throw new Error(`Invalid tag name: "${tagName}". Tag names must start with a letter and contain only letters, numbers, periods, hyphens, and underscores.`);
49
56
  }
@@ -1396,17 +1403,26 @@ function concat(opt, other) {
1396
1403
  //#region src/html/for.ts
1397
1404
  function forOfBy(by, item, index) {
1398
1405
  if (by) {
1399
- if (typeof by === "string") return item[by];
1400
- return by(item, index);
1406
+ const key = typeof by === "string" ? item[by] : by(item, index);
1407
+ if (typeof key !== "string" && typeof key !== "number") console.error(`A <for> tag's \`by\` attribute must return a string or number, but it returned:`, key);
1408
+ return key;
1401
1409
  }
1402
1410
  return index;
1403
1411
  }
1404
1412
  function forInBy(by, name, value) {
1405
- if (by) return by(name, value);
1413
+ if (by) {
1414
+ const key = by(name, value);
1415
+ if (typeof key !== "string" && typeof key !== "number") console.error(`A <for> tag's \`by\` attribute must return a string or number, but it returned:`, key);
1416
+ return key;
1417
+ }
1406
1418
  return name;
1407
1419
  }
1408
1420
  function forStepBy(by, index) {
1409
- if (by) return by(index);
1421
+ if (by) {
1422
+ const key = by(index);
1423
+ if (typeof key !== "string" && typeof key !== "number") console.error(`A <for> tag's \`by\` attribute must return a string or number, but it returned:`, key);
1424
+ return key;
1425
+ }
1410
1426
  return index;
1411
1427
  }
1412
1428
  //#endregion
@@ -2412,6 +2428,7 @@ function _attr_nonce() {
2412
2428
  return getChunk().boundary.state.nonceAttr;
2413
2429
  }
2414
2430
  function _attr(name, value) {
2431
+ assertValidEventHandlerAttr(name, value);
2415
2432
  return isVoid(value) ? "" : nonVoidAttr(name, value);
2416
2433
  }
2417
2434
  function _attrs(data, nodeAccessor, scopeId, tagName) {
@@ -2487,6 +2504,7 @@ function _attrs_partial_content(data, skip, nodeAccessor, scopeId, tagName, seri
2487
2504
  _attr_content(nodeAccessor, scopeId, data?.content, serializeReason);
2488
2505
  }
2489
2506
  function writeControlledScope(type, scopeId, nodeAccessor, value, valueChange) {
2507
+ assertHandlerIsFunction("valueChange", valueChange);
2490
2508
  writeScope(scopeId, {
2491
2509
  ["ControlledType:" + nodeAccessor]: type,
2492
2510
  ["ControlledValue:" + nodeAccessor]: value,
@@ -9,11 +9,11 @@ export type PendingRender = {
9
9
  [PendingRenderProp.Gen]: number;
10
10
  [PendingRenderProp.Pending]?: 0 | 1;
11
11
  };
12
+ export declare let rendering: undefined | 0 | 1;
13
+ export declare let runId: number;
12
14
  export declare const caughtError: WeakSet<unknown[]>;
13
15
  export declare const placeholderShown: WeakSet<unknown[]>;
14
16
  export declare let pendingEffects: unknown[];
15
- export declare let pendingScopes: Scope[];
16
- export declare let rendering: undefined | 0 | 1;
17
17
  export declare function queueRender<T, U extends Scope = Scope>(scope: U, signal: Signal<T, U>, signalKey: number, value?: T, scopeKey?: number): void;
18
18
  export declare function queuePendingRender(render: PendingRender): void;
19
19
  export declare function queueEffect<S extends Scope, T extends ExecFn<S>>(scope: S, fn: T): void;
@@ -1,5 +1,8 @@
1
1
  import { AccessorProp, type BranchScope, type Scope } from "../common/types";
2
2
  export declare function createScope($global: Scope[AccessorProp.Global], closestBranch?: BranchScope): Scope;
3
+ export declare function syncGen(scope: Scope): void;
4
+ export declare function _assert_init(scope: Scope, accessor: string): any;
5
+ export declare function collectScopes(fn: () => void): Scope[];
3
6
  export declare function skipScope(): number;
4
7
  export declare function findBranchWithKey(scope: Scope, key: string): BranchScope | undefined;
5
8
  export declare function destroyBranch(branch: BranchScope): void;
package/dist/dom.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { attrTag, attrTags } from "./common/attr-tag";
2
- export { _assert_hoist, _assert_init } from "./common/errors";
2
+ export { _assert_hoist } from "./common/errors";
3
3
  export { forIn, forOf, forTo, forUntil } from "./common/for";
4
4
  export { _call } from "./common/helpers";
5
5
  export { $signal, $signalReset } from "./dom/abort-signal";
@@ -12,5 +12,6 @@ export { _load_event_trigger, _load_idle_trigger, _load_media_trigger, _load_rac
12
12
  export { _enable_catch, run } from "./dom/queue";
13
13
  export { _content, _content_closures, _content_resume } from "./dom/renderer";
14
14
  export { _el, _resume, _var_resume, init, initEmbedded, ready, } from "./dom/resume";
15
+ export { _assert_init } from "./dom/scope";
15
16
  export { _child_setup, _closure, _closure_get, _const, _el_read, _for_closure, _hoist, _hoist_resume, _id, _if_closure, _let, _let_change, _or, _return, _return_change, _script, _var, _var_change, } from "./dom/signals";
16
17
  export { _template } from "./dom/template";