@marko/runtime-tags 6.1.12 → 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.
@@ -2,6 +2,8 @@ export declare function _el_read_error(): void;
2
2
  export declare function _hoist_read_error(): void;
3
3
  export declare function _assert_hoist(value: unknown): void;
4
4
  export declare function assertExclusiveAttrs(attrs: Record<string, unknown> | undefined, onError?: typeof throwErr): void;
5
+ export declare function assertValidEventHandlerAttr(name: string, value: unknown): void;
6
+ export declare function assertHandlerIsFunction(name: string, value: unknown): void;
5
7
  export declare function assertValidTagName(tagName: string): void;
6
8
  declare function throwErr(msg: string): void;
7
9
  export {};
package/dist/debug/dom.js CHANGED
@@ -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
  }
@@ -146,6 +153,7 @@ function push(opt, item) {
146
153
  //#region src/dom/event.ts
147
154
  const defaultDelegator = /* @__PURE__ */ createDelegator();
148
155
  function _on(element, type, handler) {
156
+ assertHandlerIsFunction("on" + type[0].toUpperCase() + type.slice(1), handler);
149
157
  if (element["$" + type] === void 0) defaultDelegator(element, type, handleDelegated);
150
158
  element["$" + type] = handler || null;
151
159
  }
@@ -793,6 +801,7 @@ function _attr_input_checked_default(scope, nodeAccessor, checked) {
793
801
  function _attr_input_checked(scope, nodeAccessor, checked, checkedChange) {
794
802
  const el = scope[nodeAccessor];
795
803
  const normalizedChecked = isNotVoid(checked);
804
+ assertHandlerIsFunction("checkedChange", checkedChange);
796
805
  scope["ControlledHandler:" + nodeAccessor] = checkedChange;
797
806
  scope["ControlledType:" + nodeAccessor] = checkedChange ? 0 : 5;
798
807
  if (checkedChange && scope["#Gen"] < runId) el.checked = normalizedChecked;
@@ -821,6 +830,7 @@ function _attr_input_checkedValue(scope, nodeAccessor, checkedValue, checkedValu
821
830
  const el = scope[nodeAccessor];
822
831
  const multiple = Array.isArray(checkedValue);
823
832
  const normalizedCheckedValue = scope["ControlledValue:" + nodeAccessor] = multiple ? checkedValue.map(normalizeStrProp) : normalizeStrProp(checkedValue);
833
+ assertHandlerIsFunction("checkedValueChange", checkedValueChange);
824
834
  scope["ControlledHandler:" + nodeAccessor] = checkedValueChange;
825
835
  scope["ControlledType:" + nodeAccessor] = checkedValueChange ? 1 : 5;
826
836
  if (checkedValueChange && scope["#Gen"] < runId) {
@@ -857,6 +867,7 @@ function _attr_input_value_default(scope, nodeAccessor, value) {
857
867
  function _attr_input_value(scope, nodeAccessor, value, valueChange) {
858
868
  const el = scope[nodeAccessor];
859
869
  const normalizedValue = normalizeAttrValue(value) || "";
870
+ assertHandlerIsFunction("valueChange", valueChange);
860
871
  scope["ControlledHandler:" + nodeAccessor] = valueChange;
861
872
  scope["ControlledValue:" + nodeAccessor] = normalizedValue;
862
873
  scope["ControlledType:" + nodeAccessor] = valueChange ? 2 : 5;
@@ -905,6 +916,7 @@ function _attr_select_value(scope, nodeAccessor, value, valueChange) {
905
916
  const existing = scope["#Gen"] < runId;
906
917
  const multiple = Array.isArray(value);
907
918
  const normalizedValue = scope["ControlledValue:" + nodeAccessor] = multiple ? value.map(normalizeStrProp) : normalizeStrProp(value);
919
+ assertHandlerIsFunction("valueChange", valueChange);
908
920
  scope["ControlledHandler:" + nodeAccessor] = valueChange;
909
921
  scope["ControlledType:" + nodeAccessor] = valueChange ? 3 : 5;
910
922
  if (valueChange && existing) pendingEffects.unshift(() => setSelectValue(el, normalizedValue, multiple), scope);
@@ -954,6 +966,7 @@ function _attr_details_or_dialog_open_default(scope, nodeAccessor, open) {
954
966
  }
955
967
  function _attr_details_or_dialog_open(scope, nodeAccessor, open, openChange) {
956
968
  const normalizedOpen = scope["ControlledValue:" + nodeAccessor] = isNotVoid(open);
969
+ assertHandlerIsFunction("openChange", openChange);
957
970
  scope["ControlledHandler:" + nodeAccessor] = openChange;
958
971
  scope["ControlledType:" + nodeAccessor] = openChange ? 4 : 5;
959
972
  if (openChange && scope["#Gen"] < runId) scope[nodeAccessor].open = normalizedOpen;
@@ -1015,6 +1028,7 @@ function _to_text(value) {
1015
1028
  return value || value === 0 ? value + "" : "";
1016
1029
  }
1017
1030
  function _attr(element, name, value) {
1031
+ assertValidEventHandlerAttr(name, value);
1018
1032
  setAttribute(element, name, normalizeAttrValue(value));
1019
1033
  }
1020
1034
  function setAttribute(element, name, value) {
@@ -1089,8 +1103,9 @@ function _attrs_partial_content(scope, nodeAccessor, nextAttrs, skip) {
1089
1103
  }
1090
1104
  function attrsInternal(scope, nodeAccessor, nextAttrs) {
1091
1105
  const el = scope[nodeAccessor];
1092
- let events;
1106
+ let events = scope["EventAttributes:" + nodeAccessor];
1093
1107
  let skip;
1108
+ for (const name in events) events[name] = 0;
1094
1109
  switch (el.tagName) {
1095
1110
  case "INPUT":
1096
1111
  if ("checked" in nextAttrs || "checkedChange" in nextAttrs) _attr_input_checked(scope, nodeAccessor, nextAttrs.checked, nextAttrs.checkedChange);
@@ -1374,7 +1389,7 @@ function _if(nodeAccessor, ...branchesArgs) {
1374
1389
  while (i < branchesArgs.length) branches.push(_content("", branchesArgs[i++], branchesArgs[i++], branchesArgs[i++])());
1375
1390
  enableBranches();
1376
1391
  return (scope, newBranch) => {
1377
- if (newBranch !== scope[branchAccessor]) setConditionalRenderer(scope, nodeAccessor, branches[scope[branchAccessor] = newBranch], createAndSetupBranch);
1392
+ if (newBranch !== (scope[branchAccessor] ?? (scope["BranchScopes:" + nodeAccessor] && 0))) setConditionalRenderer(scope, nodeAccessor, branches[scope[branchAccessor] = newBranch], createAndSetupBranch);
1378
1393
  };
1379
1394
  }
1380
1395
  function patchDynamicTag(fn) {
@@ -1466,7 +1481,8 @@ function loop(forEach) {
1466
1481
  let hasPotentialMoves;
1467
1482
  var seenKeys = /* @__PURE__ */ new Set();
1468
1483
  forEach(value, (key, args) => {
1469
- 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);
1484
+ 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);
1485
+ 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);
1470
1486
  else seenKeys.add(key);
1471
1487
  let branch = oldLen && (oldScopesByKey ||= oldScopes.reduce((map, scope, i) => map.set(scope["#LoopKey"] ?? i, scope), /* @__PURE__ */ new Map())).get(key);
1472
1488
  if (branch) hasPotentialMoves = oldScopesByKey.delete(key);
@@ -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
  }
@@ -144,6 +151,7 @@ function push(opt, item) {
144
151
  //#region src/dom/event.ts
145
152
  const defaultDelegator = /* @__PURE__ */ createDelegator();
146
153
  function _on(element, type, handler) {
154
+ assertHandlerIsFunction("on" + type[0].toUpperCase() + type.slice(1), handler);
147
155
  if (element["$" + type] === void 0) defaultDelegator(element, type, handleDelegated);
148
156
  element["$" + type] = handler || null;
149
157
  }
@@ -791,6 +799,7 @@ function _attr_input_checked_default(scope, nodeAccessor, checked) {
791
799
  function _attr_input_checked(scope, nodeAccessor, checked, checkedChange) {
792
800
  const el = scope[nodeAccessor];
793
801
  const normalizedChecked = isNotVoid(checked);
802
+ assertHandlerIsFunction("checkedChange", checkedChange);
794
803
  scope["ControlledHandler:" + nodeAccessor] = checkedChange;
795
804
  scope["ControlledType:" + nodeAccessor] = checkedChange ? 0 : 5;
796
805
  if (checkedChange && scope["#Gen"] < runId) el.checked = normalizedChecked;
@@ -819,6 +828,7 @@ function _attr_input_checkedValue(scope, nodeAccessor, checkedValue, checkedValu
819
828
  const el = scope[nodeAccessor];
820
829
  const multiple = Array.isArray(checkedValue);
821
830
  const normalizedCheckedValue = scope["ControlledValue:" + nodeAccessor] = multiple ? checkedValue.map(normalizeStrProp) : normalizeStrProp(checkedValue);
831
+ assertHandlerIsFunction("checkedValueChange", checkedValueChange);
822
832
  scope["ControlledHandler:" + nodeAccessor] = checkedValueChange;
823
833
  scope["ControlledType:" + nodeAccessor] = checkedValueChange ? 1 : 5;
824
834
  if (checkedValueChange && scope["#Gen"] < runId) {
@@ -855,6 +865,7 @@ function _attr_input_value_default(scope, nodeAccessor, value) {
855
865
  function _attr_input_value(scope, nodeAccessor, value, valueChange) {
856
866
  const el = scope[nodeAccessor];
857
867
  const normalizedValue = normalizeAttrValue(value) || "";
868
+ assertHandlerIsFunction("valueChange", valueChange);
858
869
  scope["ControlledHandler:" + nodeAccessor] = valueChange;
859
870
  scope["ControlledValue:" + nodeAccessor] = normalizedValue;
860
871
  scope["ControlledType:" + nodeAccessor] = valueChange ? 2 : 5;
@@ -903,6 +914,7 @@ function _attr_select_value(scope, nodeAccessor, value, valueChange) {
903
914
  const existing = scope["#Gen"] < runId;
904
915
  const multiple = Array.isArray(value);
905
916
  const normalizedValue = scope["ControlledValue:" + nodeAccessor] = multiple ? value.map(normalizeStrProp) : normalizeStrProp(value);
917
+ assertHandlerIsFunction("valueChange", valueChange);
906
918
  scope["ControlledHandler:" + nodeAccessor] = valueChange;
907
919
  scope["ControlledType:" + nodeAccessor] = valueChange ? 3 : 5;
908
920
  if (valueChange && existing) pendingEffects.unshift(() => setSelectValue(el, normalizedValue, multiple), scope);
@@ -952,6 +964,7 @@ function _attr_details_or_dialog_open_default(scope, nodeAccessor, open) {
952
964
  }
953
965
  function _attr_details_or_dialog_open(scope, nodeAccessor, open, openChange) {
954
966
  const normalizedOpen = scope["ControlledValue:" + nodeAccessor] = isNotVoid(open);
967
+ assertHandlerIsFunction("openChange", openChange);
955
968
  scope["ControlledHandler:" + nodeAccessor] = openChange;
956
969
  scope["ControlledType:" + nodeAccessor] = openChange ? 4 : 5;
957
970
  if (openChange && scope["#Gen"] < runId) scope[nodeAccessor].open = normalizedOpen;
@@ -1013,6 +1026,7 @@ function _to_text(value) {
1013
1026
  return value || value === 0 ? value + "" : "";
1014
1027
  }
1015
1028
  function _attr(element, name, value) {
1029
+ assertValidEventHandlerAttr(name, value);
1016
1030
  setAttribute(element, name, normalizeAttrValue(value));
1017
1031
  }
1018
1032
  function setAttribute(element, name, value) {
@@ -1087,8 +1101,9 @@ function _attrs_partial_content(scope, nodeAccessor, nextAttrs, skip) {
1087
1101
  }
1088
1102
  function attrsInternal(scope, nodeAccessor, nextAttrs) {
1089
1103
  const el = scope[nodeAccessor];
1090
- let events;
1104
+ let events = scope["EventAttributes:" + nodeAccessor];
1091
1105
  let skip;
1106
+ for (const name in events) events[name] = 0;
1092
1107
  switch (el.tagName) {
1093
1108
  case "INPUT":
1094
1109
  if ("checked" in nextAttrs || "checkedChange" in nextAttrs) _attr_input_checked(scope, nodeAccessor, nextAttrs.checked, nextAttrs.checkedChange);
@@ -1372,7 +1387,7 @@ function _if(nodeAccessor, ...branchesArgs) {
1372
1387
  while (i < branchesArgs.length) branches.push(_content("", branchesArgs[i++], branchesArgs[i++], branchesArgs[i++])());
1373
1388
  enableBranches();
1374
1389
  return (scope, newBranch) => {
1375
- 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);
1376
1391
  };
1377
1392
  }
1378
1393
  function patchDynamicTag(fn) {
@@ -1464,7 +1479,8 @@ function loop(forEach) {
1464
1479
  let hasPotentialMoves;
1465
1480
  var seenKeys = /* @__PURE__ */ new Set();
1466
1481
  forEach(value, (key, args) => {
1467
- 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);
1468
1484
  else seenKeys.add(key);
1469
1485
  let branch = oldLen && (oldScopesByKey ||= oldScopes.reduce((map, scope, i) => map.set(scope["#LoopKey"] ?? i, scope), /* @__PURE__ */ new Map())).get(key);
1470
1486
  if (branch) hasPotentialMoves = oldScopesByKey.delete(key);
@@ -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,
package/dist/dom.js CHANGED
@@ -770,7 +770,8 @@ function _attrs_partial_content(scope, nodeAccessor, nextAttrs, skip) {
770
770
  _attrs_partial(scope, nodeAccessor, nextAttrs, skip), _attr_content(scope, nodeAccessor, nextAttrs?.content);
771
771
  }
772
772
  function attrsInternal(scope, nodeAccessor, nextAttrs) {
773
- let el = scope[nodeAccessor], events, skip;
773
+ let el = scope[nodeAccessor], events = scope["I" + nodeAccessor], skip;
774
+ for (let name in events) events[name] = 0;
774
775
  switch (el.tagName) {
775
776
  case "INPUT":
776
777
  if ("checked" in nextAttrs || "checkedChange" in nextAttrs) _attr_input_checked(scope, nodeAccessor, nextAttrs.checked, nextAttrs.checkedChange);
@@ -955,7 +956,7 @@ function _if(nodeAccessor, ...branchesArgs) {
955
956
  let branchAccessor = "D" + nodeAccessor, branches = [], i = 0;
956
957
  for (; i < branchesArgs.length;) branches.push(_content("", branchesArgs[i++], branchesArgs[i++], branchesArgs[i++])());
957
958
  return enableBranches(), (scope, newBranch) => {
958
- newBranch !== scope[branchAccessor] && setConditionalRenderer(scope, nodeAccessor, branches[scope[branchAccessor] = newBranch], createAndSetupBranch);
959
+ newBranch !== (scope[branchAccessor] ?? (scope["A" + nodeAccessor] && 0)) && setConditionalRenderer(scope, nodeAccessor, branches[scope[branchAccessor] = newBranch], createAndSetupBranch);
959
960
  };
960
961
  }
961
962
  function patchDynamicTag(fn) {
package/dist/dom.mjs CHANGED
@@ -769,7 +769,8 @@ function _attrs_partial_content(scope, nodeAccessor, nextAttrs, skip) {
769
769
  _attrs_partial(scope, nodeAccessor, nextAttrs, skip), _attr_content(scope, nodeAccessor, nextAttrs?.content);
770
770
  }
771
771
  function attrsInternal(scope, nodeAccessor, nextAttrs) {
772
- let el = scope[nodeAccessor], events, skip;
772
+ let el = scope[nodeAccessor], events = scope["I" + nodeAccessor], skip;
773
+ for (let name in events) events[name] = 0;
773
774
  switch (el.tagName) {
774
775
  case "INPUT":
775
776
  if ("checked" in nextAttrs || "checkedChange" in nextAttrs) _attr_input_checked(scope, nodeAccessor, nextAttrs.checked, nextAttrs.checkedChange);
@@ -954,7 +955,7 @@ function _if(nodeAccessor, ...branchesArgs) {
954
955
  let branchAccessor = "D" + nodeAccessor, branches = [], i = 0;
955
956
  for (; i < branchesArgs.length;) branches.push(_content("", branchesArgs[i++], branchesArgs[i++], branchesArgs[i++])());
956
957
  return enableBranches(), (scope, newBranch) => {
957
- newBranch !== scope[branchAccessor] && setConditionalRenderer(scope, nodeAccessor, branches[scope[branchAccessor] = newBranch], createAndSetupBranch);
958
+ newBranch !== (scope[branchAccessor] ?? (scope["A" + nodeAccessor] && 0)) && setConditionalRenderer(scope, nodeAccessor, branches[scope[branchAccessor] = newBranch], createAndSetupBranch);
958
959
  };
959
960
  }
960
961
  function patchDynamicTag(fn) {
@@ -182,15 +182,15 @@ function isNullableExpr(expr) {
182
182
  case "**=": return false;
183
183
  case "||=":
184
184
  case "??=": return isNullableExpr(expr.right) || isNullableExpr(expr.left);
185
- case "&&=": return isNullableExpr(expr.left) && isNullableExpr(expr.right);
185
+ case "&&=": return isNullableExpr(expr.left) || isNullableExpr(expr.right);
186
186
  default: return true;
187
187
  }
188
188
  case "AwaitExpression": return isNullableExpr(expr.argument);
189
- case "ConditionalExpression": return isNullableExpr(expr.consequent) && isNullableExpr(expr.alternate);
189
+ case "ConditionalExpression": return isNullableExpr(expr.consequent) || isNullableExpr(expr.alternate);
190
190
  case "LogicalExpression": switch (expr.operator) {
191
191
  case "||":
192
192
  case "??": return isNullableExpr(expr.right) || isNullableExpr(expr.left);
193
- case "&&": return isNullableExpr(expr.left) && isNullableExpr(expr.right);
193
+ case "&&": return isNullableExpr(expr.left) || isNullableExpr(expr.right);
194
194
  default: return true;
195
195
  }
196
196
  case "ParenthesizedExpression": return isNullableExpr(expr.expression);
@@ -2807,7 +2807,7 @@ function simplifyFunction(fn) {
2807
2807
  case "FunctionDeclaration":
2808
2808
  case "FunctionExpression":
2809
2809
  case "ArrowFunctionExpression": return fn;
2810
- default: return _marko_compiler.types.functionExpression(null, fn.params, fn.body, fn.async, fn.generator);
2810
+ default: return _marko_compiler.types.functionExpression(null, fn.params, fn.body, fn.generator, fn.async);
2811
2811
  }
2812
2812
  }
2813
2813
  //#endregion
@@ -4181,10 +4181,14 @@ var native_tag_default = {
4181
4181
  }
4182
4182
  seen[attr.name] = attr;
4183
4183
  if (injectNonce && attr.name === "nonce") injectNonce = false;
4184
+ if (isEventOrChangeHandler(attr.name)) assertNativeHandlerAttr(tag, attr);
4184
4185
  if (isEventHandler(attr.name)) {
4185
4186
  valueExtra.isEffect = true;
4186
4187
  hasEventHandlers = true;
4187
- } else if (!evaluate(attr.value).confident) hasDynamicAttributes = true;
4188
+ } else {
4189
+ assertValidNativeEventHandlerAttr(tag, attr);
4190
+ if (!evaluate(attr.value).confident) hasDynamicAttributes = true;
4191
+ }
4188
4192
  } else if (_marko_compiler.types.isMarkoSpreadAttribute(attr)) {
4189
4193
  valueExtra.isEffect = true;
4190
4194
  hasEventHandlers = true;
@@ -4593,6 +4597,23 @@ function isInjectNonceTag(tagName) {
4593
4597
  default: return false;
4594
4598
  }
4595
4599
  }
4600
+ function assertNativeHandlerAttr(tag, attr) {
4601
+ if ((0, _marko_compiler_babel_utils.computeNode)(attr.value)?.value) throw tag.hub.buildError(attr.value, `The \`${attr.name}\` ${isEventHandler(attr.name) ? "event handler" : "change handler"} on a [native tag](https://markojs.com/docs/reference/native-tag) must be a function or a falsey value (\`null\`, \`undefined\`, \`false\`, \`0\`, …).`, Error);
4602
+ }
4603
+ const lowercaseEventHandlerReg = /^on[a-z]/;
4604
+ function assertValidNativeEventHandlerAttr(tag, attr) {
4605
+ if (!lowercaseEventHandlerReg.test(attr.name)) return;
4606
+ const { value } = attr;
4607
+ let invalid = _marko_compiler.types.isFunction(value);
4608
+ if (!invalid) {
4609
+ const { confident, computed } = evaluate(value);
4610
+ invalid = confident && !!computed && typeof computed !== "string";
4611
+ }
4612
+ if (invalid) {
4613
+ const suggestion = "on" + attr.name[2].toUpperCase() + attr.name.slice(3);
4614
+ throw tag.hub.buildError(value, `The \`${attr.name}\` attribute on a [native tag](https://markojs.com/docs/reference/native-tag) must be a string or a falsey value (\`null\`, \`undefined\`, \`false\`, \`0\`, …). To attach an event listener, use the \`${suggestion}\` [event handler attribute](https://markojs.com/docs/reference/event-handling) instead.`, Error);
4615
+ }
4616
+ }
4596
4617
  function getCanonicalTagName(tag) {
4597
4618
  const tagName = getTagName(tag);
4598
4619
  switch (tagName) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marko/runtime-tags",
3
- "version": "6.1.12",
3
+ "version": "6.1.13",
4
4
  "description": "Optimized runtime for Marko templates.",
5
5
  "keywords": [
6
6
  "api",