@marko/runtime-tags 6.1.13 → 6.1.14

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.
@@ -1,8 +1,11 @@
1
+ export declare function assertValidAttrValue(name: string, value: unknown): void;
2
+ export declare function assertValidTextValue(value: unknown): void;
3
+ export declare function assertValidLoopKey(key: unknown, seenKeys?: Set<unknown>): void;
4
+ export declare function assertValidAttrName(name: string): void;
1
5
  export declare function _el_read_error(): void;
2
6
  export declare function _hoist_read_error(): void;
3
7
  export declare function _assert_hoist(value: unknown): void;
4
8
  export declare function assertExclusiveAttrs(attrs: Record<string, unknown> | undefined, onError?: typeof throwErr): void;
5
- export declare function assertValidEventHandlerAttr(name: string, value: unknown): void;
6
9
  export declare function assertHandlerIsFunction(name: string, value: unknown): void;
7
10
  export declare function assertValidTagName(tagName: string): void;
8
11
  declare function throwErr(msg: string): void;
@@ -1,5 +1,6 @@
1
1
  export declare const htmlAttrNameReg: RegExp;
2
2
  export declare const userAttrNameReg: RegExp;
3
+ export declare function getWrongAttrSuggestion(name: string): string | undefined;
3
4
  export declare function _call<T>(fn: (v: T) => unknown, v: T): T;
4
5
  export declare function stringifyClassObject(name: string, value: unknown): string;
5
6
  export declare function stringifyStyleObject(name: string, value: unknown): string;
package/dist/debug/dom.js CHANGED
@@ -21,8 +21,133 @@ function* attrTagIterator() {
21
21
  yield* this[rest];
22
22
  }
23
23
  //#endregion
24
+ //#region src/common/helpers.ts
25
+ const htmlAttrNameReg = /^[^a-z_]|[^a-z0-9._:-]/i;
26
+ const knownWrongAttrs = {
27
+ className: "class",
28
+ classList: "class",
29
+ htmlFor: "for",
30
+ acceptCharset: "accept-charset",
31
+ httpEquiv: "http-equiv",
32
+ defaultValue: "value",
33
+ defaultChecked: "checked",
34
+ dangerouslySetInnerHTML: "$!{html}",
35
+ key: "<for by>",
36
+ ref: "<tag/ref>",
37
+ "v-if": "<if>",
38
+ "v-else": "<else>",
39
+ "v-else-if": "<else if>",
40
+ "v-for": "<for>",
41
+ "v-show": "<if>",
42
+ "v-model": "value:=state",
43
+ "v-bind": "...attrs",
44
+ "v-html": "$!{html}",
45
+ "v-text": "${text}"
46
+ };
47
+ function getWrongAttrSuggestion(name) {
48
+ const exact = knownWrongAttrs[name];
49
+ if (exact) return exact;
50
+ const colon = name.indexOf(":");
51
+ if (colon > 0) {
52
+ const rest = name.slice(colon + 1);
53
+ switch (name.slice(0, colon)) {
54
+ case "class": return `class={ ${rest}: condition }`;
55
+ case "style": return `style={ ${rest}: value }`;
56
+ case "on":
57
+ case "v-on": return `on${rest.charAt(0).toUpperCase()}${rest.slice(1)}`;
58
+ case "bind":
59
+ case "v-model": return `${rest}:=state`;
60
+ case "v-bind": return rest;
61
+ }
62
+ }
63
+ }
64
+ function _call(fn, v) {
65
+ fn(v);
66
+ return v;
67
+ }
68
+ function stringifyClassObject(name, value) {
69
+ return value ? name : "";
70
+ }
71
+ function stringifyStyleObject(name, value) {
72
+ return value || value === 0 ? name + ":" + value : "";
73
+ }
74
+ const toDelimitedString = function toDelimitedString(val, delimiter, stringify) {
75
+ let str = "";
76
+ let sep = "";
77
+ let part;
78
+ if (val) if (typeof val !== "object") str += val;
79
+ else if (Array.isArray(val)) for (const v of val) {
80
+ part = toDelimitedString(v, delimiter, stringify);
81
+ if (part) {
82
+ str += sep + part;
83
+ sep = delimiter;
84
+ }
85
+ }
86
+ else for (const name in val) {
87
+ part = stringify(name, val[name]);
88
+ if (part) {
89
+ str += sep + part;
90
+ sep = delimiter;
91
+ }
92
+ }
93
+ return str;
94
+ };
95
+ function isEventHandler(name) {
96
+ return /^on[A-Z-]/.test(name);
97
+ }
98
+ function getEventHandlerName(name) {
99
+ return name[2] === "-" ? name.slice(3) : name.slice(2).toLowerCase();
100
+ }
101
+ function isNotVoid(value) {
102
+ return value != null && value !== false;
103
+ }
104
+ function normalizeDynamicRenderer(value) {
105
+ if (value) {
106
+ if (typeof value === "string") return value;
107
+ const normalized = value.content || value.default || value;
108
+ if ("id" in normalized) return normalized;
109
+ }
110
+ }
111
+ //#endregion
24
112
  //#region src/common/errors.ts
25
113
  const lowercaseEventHandlerReg = /^on[a-z]/;
114
+ function assertValidAttrValue(name, value) {
115
+ 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.`);
116
+ if (typeof value === "function") {
117
+ if (name === "content" || /^on/i.test(name) || /Change$/.test(name)) return;
118
+ throw new Error(`The \`${name}\` attribute cannot be a function.`);
119
+ }
120
+ const unrenderable = describeUnrenderable(value);
121
+ if (unrenderable) throw new Error(`The \`${name}\` attribute cannot be ${unrenderable}.`);
122
+ }
123
+ function assertValidTextValue(value) {
124
+ const unrenderable = describeUnrenderable(value);
125
+ if (unrenderable) throw new Error(`Text content cannot be ${unrenderable}.`);
126
+ }
127
+ function describeUnrenderable(value) {
128
+ if (typeof value === "symbol") return "a symbol";
129
+ if (typeof value === "object" && value !== null) {
130
+ let stringified;
131
+ try {
132
+ stringified = `${value}`;
133
+ } catch {
134
+ stringified = "[object Object]";
135
+ }
136
+ if (/^\[object \w+\]$/.test(stringified)) return stringified === "[object Promise]" ? "a promise (use the `<await>` tag to render its resolved value)" : stringified === "[object Object]" ? "a plain object (it would render as `[object Object]`)" : `a value that renders as \`${stringified}\``;
137
+ }
138
+ }
139
+ function assertValidLoopKey(key, seenKeys) {
140
+ if (typeof key !== "string" && typeof key !== "number") throw new Error(`A \`<for>\` tag's \`by\` attribute must return a string or number for each item, but received ${key === null ? "null" : `type "${typeof key}"`}.`);
141
+ if (seenKeys) {
142
+ if (seenKeys.has(key)) throw new Error(`A \`<for>\` tag's \`by\` attribute must return a unique value for each item, but \`${key}\` was used more than once.`);
143
+ seenKeys.add(key);
144
+ }
145
+ }
146
+ function assertValidAttrName(name) {
147
+ if (htmlAttrNameReg.test(name)) throw new Error(`Invalid attribute name: ${JSON.stringify(name)}`);
148
+ const suggestion = getWrongAttrSuggestion(name);
149
+ if (suggestion) throw new Error(`\`${name}\` is not a valid attribute, did you mean \`${suggestion}\`?`);
150
+ }
26
151
  function _el_read_error() {
27
152
  throw new Error("Element references can only be read in scripts and event handlers.");
28
153
  }
@@ -47,9 +172,6 @@ function assertExclusiveAttrs(attrs, onError = throwErr) {
47
172
  if (exclusiveAttrs && exclusiveAttrs.length > 1) onError(`The attributes ${joinWithAnd(exclusiveAttrs)} are mutually exclusive.`);
48
173
  }
49
174
  }
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
175
  function assertHandlerIsFunction(name, value) {
54
176
  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
177
  }
@@ -89,56 +211,6 @@ function forUntil(until, from, step, cb) {
89
211
  for (let steps = (until - start) / delta, i = 0; i < steps; i++) cb(start + i * delta);
90
212
  }
91
213
  //#endregion
92
- //#region src/common/helpers.ts
93
- const htmlAttrNameReg = /^[^a-z_]|[^a-z0-9._:-]/i;
94
- function _call(fn, v) {
95
- fn(v);
96
- return v;
97
- }
98
- function stringifyClassObject(name, value) {
99
- return value ? name : "";
100
- }
101
- function stringifyStyleObject(name, value) {
102
- return value || value === 0 ? name + ":" + value : "";
103
- }
104
- const toDelimitedString = function toDelimitedString(val, delimiter, stringify) {
105
- let str = "";
106
- let sep = "";
107
- let part;
108
- if (val) if (typeof val !== "object") str += val;
109
- else if (Array.isArray(val)) for (const v of val) {
110
- part = toDelimitedString(v, delimiter, stringify);
111
- if (part) {
112
- str += sep + part;
113
- sep = delimiter;
114
- }
115
- }
116
- else for (const name in val) {
117
- part = stringify(name, val[name]);
118
- if (part) {
119
- str += sep + part;
120
- sep = delimiter;
121
- }
122
- }
123
- return str;
124
- };
125
- function isEventHandler(name) {
126
- return /^on[A-Z-]/.test(name);
127
- }
128
- function getEventHandlerName(name) {
129
- return name[2] === "-" ? name.slice(3) : name.slice(2).toLowerCase();
130
- }
131
- function isNotVoid(value) {
132
- return value != null && value !== false;
133
- }
134
- function normalizeDynamicRenderer(value) {
135
- if (value) {
136
- if (typeof value === "string") return value;
137
- const normalized = value.content || value.default || value;
138
- if ("id" in normalized) return normalized;
139
- }
140
- }
141
- //#endregion
142
214
  //#region src/common/meta.ts
143
215
  const DYNAMIC_TAG_SCRIPT_REGISTER_ID = "_dynamicTagScript";
144
216
  //#endregion
@@ -919,6 +991,7 @@ function _attr_select_value(scope, nodeAccessor, value, valueChange) {
919
991
  assertHandlerIsFunction("valueChange", valueChange);
920
992
  scope["ControlledHandler:" + nodeAccessor] = valueChange;
921
993
  scope["ControlledType:" + nodeAccessor] = valueChange ? 3 : 5;
994
+ if (valueChange) pendingEffects.unshift(() => assertSelectValueMatchesOption(el, normalizedValue, value), scope);
922
995
  if (valueChange && existing) pendingEffects.unshift(() => setSelectValue(el, normalizedValue, multiple), scope);
923
996
  else _attr_select_value_default(scope, nodeAccessor, normalizedValue);
924
997
  }
@@ -961,6 +1034,13 @@ function setSelectValue(el, value, multiple) {
961
1034
  function getSelectValue(el, multiple) {
962
1035
  return multiple ? Array.from(el.selectedOptions, (opt) => opt.value) : el.value;
963
1036
  }
1037
+ function assertSelectValueMatchesOption(el, normalizedValue, value) {
1038
+ const multiple = Array.isArray(normalizedValue);
1039
+ if (multiple ? normalizedValue.some(Boolean) : normalizedValue) {
1040
+ for (const opt of el.options) if (multiple ? normalizedValue.includes(opt.value) : opt.value === normalizedValue) return;
1041
+ console.error("A controlled `<select>`'s `value` has no matching `<option>`:", value);
1042
+ }
1043
+ }
964
1044
  function _attr_details_or_dialog_open_default(scope, nodeAccessor, open) {
965
1045
  if (scope["#Gen"] === runId) scope[nodeAccessor].open = isNotVoid(open);
966
1046
  }
@@ -1025,10 +1105,11 @@ function updateList(arr, val, push) {
1025
1105
  //#endregion
1026
1106
  //#region src/dom/dom.ts
1027
1107
  function _to_text(value) {
1108
+ assertValidTextValue(value);
1028
1109
  return value || value === 0 ? value + "" : "";
1029
1110
  }
1030
1111
  function _attr(element, name, value) {
1031
- assertValidEventHandlerAttr(name, value);
1112
+ assertValidAttrValue(name, value);
1032
1113
  setAttribute(element, name, normalizeAttrValue(value));
1033
1114
  }
1034
1115
  function setAttribute(element, name, value) {
@@ -1106,6 +1187,8 @@ function attrsInternal(scope, nodeAccessor, nextAttrs) {
1106
1187
  let events = scope["EventAttributes:" + nodeAccessor];
1107
1188
  let skip;
1108
1189
  for (const name in events) events[name] = 0;
1190
+ scope["ControlledType:" + nodeAccessor] = 5;
1191
+ scope["ControlledHandler:" + nodeAccessor] = 0;
1109
1192
  switch (el.tagName) {
1110
1193
  case "INPUT":
1111
1194
  if ("checked" in nextAttrs || "checkedChange" in nextAttrs) _attr_input_checked(scope, nodeAccessor, nextAttrs.checked, nextAttrs.checkedChange);
@@ -1144,7 +1227,7 @@ function attrsInternal(scope, nodeAccessor, nextAttrs) {
1144
1227
  _attr_style(el, value);
1145
1228
  break;
1146
1229
  default:
1147
- if (htmlAttrNameReg.test(name)) throw new Error(`Invalid attribute name: ${JSON.stringify(name)}`);
1230
+ assertValidAttrName(name);
1148
1231
  if (isEventHandler(name)) (events ||= scope["EventAttributes:" + nodeAccessor] = {})[getEventHandlerName(name)] = value;
1149
1232
  else if (!(skip?.test(name) || name === "content" && el.tagName !== "META")) _attr(el, name, value);
1150
1233
  break;
@@ -1481,9 +1564,7 @@ function loop(forEach) {
1481
1564
  let hasPotentialMoves;
1482
1565
  var seenKeys = /* @__PURE__ */ new Set();
1483
1566
  forEach(value, (key, args) => {
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);
1486
- else seenKeys.add(key);
1567
+ assertValidLoopKey(key, seenKeys);
1487
1568
  let branch = oldLen && (oldScopesByKey ||= oldScopes.reduce((map, scope, i) => map.set(scope["#LoopKey"] ?? i, scope), /* @__PURE__ */ new Map())).get(key);
1488
1569
  if (branch) hasPotentialMoves = oldScopesByKey.delete(key);
1489
1570
  else branch = createAndSetupBranch(scope["$global"], renderer, scope, parentNode);
@@ -19,8 +19,133 @@ function* attrTagIterator() {
19
19
  yield* this[rest];
20
20
  }
21
21
  //#endregion
22
+ //#region src/common/helpers.ts
23
+ const htmlAttrNameReg = /^[^a-z_]|[^a-z0-9._:-]/i;
24
+ const knownWrongAttrs = {
25
+ className: "class",
26
+ classList: "class",
27
+ htmlFor: "for",
28
+ acceptCharset: "accept-charset",
29
+ httpEquiv: "http-equiv",
30
+ defaultValue: "value",
31
+ defaultChecked: "checked",
32
+ dangerouslySetInnerHTML: "$!{html}",
33
+ key: "<for by>",
34
+ ref: "<tag/ref>",
35
+ "v-if": "<if>",
36
+ "v-else": "<else>",
37
+ "v-else-if": "<else if>",
38
+ "v-for": "<for>",
39
+ "v-show": "<if>",
40
+ "v-model": "value:=state",
41
+ "v-bind": "...attrs",
42
+ "v-html": "$!{html}",
43
+ "v-text": "${text}"
44
+ };
45
+ function getWrongAttrSuggestion(name) {
46
+ const exact = knownWrongAttrs[name];
47
+ if (exact) return exact;
48
+ const colon = name.indexOf(":");
49
+ if (colon > 0) {
50
+ const rest = name.slice(colon + 1);
51
+ switch (name.slice(0, colon)) {
52
+ case "class": return `class={ ${rest}: condition }`;
53
+ case "style": return `style={ ${rest}: value }`;
54
+ case "on":
55
+ case "v-on": return `on${rest.charAt(0).toUpperCase()}${rest.slice(1)}`;
56
+ case "bind":
57
+ case "v-model": return `${rest}:=state`;
58
+ case "v-bind": return rest;
59
+ }
60
+ }
61
+ }
62
+ function _call(fn, v) {
63
+ fn(v);
64
+ return v;
65
+ }
66
+ function stringifyClassObject(name, value) {
67
+ return value ? name : "";
68
+ }
69
+ function stringifyStyleObject(name, value) {
70
+ return value || value === 0 ? name + ":" + value : "";
71
+ }
72
+ const toDelimitedString = function toDelimitedString(val, delimiter, stringify) {
73
+ let str = "";
74
+ let sep = "";
75
+ let part;
76
+ if (val) if (typeof val !== "object") str += val;
77
+ else if (Array.isArray(val)) for (const v of val) {
78
+ part = toDelimitedString(v, delimiter, stringify);
79
+ if (part) {
80
+ str += sep + part;
81
+ sep = delimiter;
82
+ }
83
+ }
84
+ else for (const name in val) {
85
+ part = stringify(name, val[name]);
86
+ if (part) {
87
+ str += sep + part;
88
+ sep = delimiter;
89
+ }
90
+ }
91
+ return str;
92
+ };
93
+ function isEventHandler(name) {
94
+ return /^on[A-Z-]/.test(name);
95
+ }
96
+ function getEventHandlerName(name) {
97
+ return name[2] === "-" ? name.slice(3) : name.slice(2).toLowerCase();
98
+ }
99
+ function isNotVoid(value) {
100
+ return value != null && value !== false;
101
+ }
102
+ function normalizeDynamicRenderer(value) {
103
+ if (value) {
104
+ if (typeof value === "string") return value;
105
+ const normalized = value.content || value.default || value;
106
+ if ("id" in normalized) return normalized;
107
+ }
108
+ }
109
+ //#endregion
22
110
  //#region src/common/errors.ts
23
111
  const lowercaseEventHandlerReg = /^on[a-z]/;
112
+ function assertValidAttrValue(name, value) {
113
+ 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.`);
114
+ if (typeof value === "function") {
115
+ if (name === "content" || /^on/i.test(name) || /Change$/.test(name)) return;
116
+ throw new Error(`The \`${name}\` attribute cannot be a function.`);
117
+ }
118
+ const unrenderable = describeUnrenderable(value);
119
+ if (unrenderable) throw new Error(`The \`${name}\` attribute cannot be ${unrenderable}.`);
120
+ }
121
+ function assertValidTextValue(value) {
122
+ const unrenderable = describeUnrenderable(value);
123
+ if (unrenderable) throw new Error(`Text content cannot be ${unrenderable}.`);
124
+ }
125
+ function describeUnrenderable(value) {
126
+ if (typeof value === "symbol") return "a symbol";
127
+ if (typeof value === "object" && value !== null) {
128
+ let stringified;
129
+ try {
130
+ stringified = `${value}`;
131
+ } catch {
132
+ stringified = "[object Object]";
133
+ }
134
+ if (/^\[object \w+\]$/.test(stringified)) return stringified === "[object Promise]" ? "a promise (use the `<await>` tag to render its resolved value)" : stringified === "[object Object]" ? "a plain object (it would render as `[object Object]`)" : `a value that renders as \`${stringified}\``;
135
+ }
136
+ }
137
+ function assertValidLoopKey(key, seenKeys) {
138
+ if (typeof key !== "string" && typeof key !== "number") throw new Error(`A \`<for>\` tag's \`by\` attribute must return a string or number for each item, but received ${key === null ? "null" : `type "${typeof key}"`}.`);
139
+ if (seenKeys) {
140
+ if (seenKeys.has(key)) throw new Error(`A \`<for>\` tag's \`by\` attribute must return a unique value for each item, but \`${key}\` was used more than once.`);
141
+ seenKeys.add(key);
142
+ }
143
+ }
144
+ function assertValidAttrName(name) {
145
+ if (htmlAttrNameReg.test(name)) throw new Error(`Invalid attribute name: ${JSON.stringify(name)}`);
146
+ const suggestion = getWrongAttrSuggestion(name);
147
+ if (suggestion) throw new Error(`\`${name}\` is not a valid attribute, did you mean \`${suggestion}\`?`);
148
+ }
24
149
  function _el_read_error() {
25
150
  throw new Error("Element references can only be read in scripts and event handlers.");
26
151
  }
@@ -45,9 +170,6 @@ function assertExclusiveAttrs(attrs, onError = throwErr) {
45
170
  if (exclusiveAttrs && exclusiveAttrs.length > 1) onError(`The attributes ${joinWithAnd(exclusiveAttrs)} are mutually exclusive.`);
46
171
  }
47
172
  }
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
173
  function assertHandlerIsFunction(name, value) {
52
174
  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
175
  }
@@ -87,56 +209,6 @@ function forUntil(until, from, step, cb) {
87
209
  for (let steps = (until - start) / delta, i = 0; i < steps; i++) cb(start + i * delta);
88
210
  }
89
211
  //#endregion
90
- //#region src/common/helpers.ts
91
- const htmlAttrNameReg = /^[^a-z_]|[^a-z0-9._:-]/i;
92
- function _call(fn, v) {
93
- fn(v);
94
- return v;
95
- }
96
- function stringifyClassObject(name, value) {
97
- return value ? name : "";
98
- }
99
- function stringifyStyleObject(name, value) {
100
- return value || value === 0 ? name + ":" + value : "";
101
- }
102
- const toDelimitedString = function toDelimitedString(val, delimiter, stringify) {
103
- let str = "";
104
- let sep = "";
105
- let part;
106
- if (val) if (typeof val !== "object") str += val;
107
- else if (Array.isArray(val)) for (const v of val) {
108
- part = toDelimitedString(v, delimiter, stringify);
109
- if (part) {
110
- str += sep + part;
111
- sep = delimiter;
112
- }
113
- }
114
- else for (const name in val) {
115
- part = stringify(name, val[name]);
116
- if (part) {
117
- str += sep + part;
118
- sep = delimiter;
119
- }
120
- }
121
- return str;
122
- };
123
- function isEventHandler(name) {
124
- return /^on[A-Z-]/.test(name);
125
- }
126
- function getEventHandlerName(name) {
127
- return name[2] === "-" ? name.slice(3) : name.slice(2).toLowerCase();
128
- }
129
- function isNotVoid(value) {
130
- return value != null && value !== false;
131
- }
132
- function normalizeDynamicRenderer(value) {
133
- if (value) {
134
- if (typeof value === "string") return value;
135
- const normalized = value.content || value.default || value;
136
- if ("id" in normalized) return normalized;
137
- }
138
- }
139
- //#endregion
140
212
  //#region src/common/meta.ts
141
213
  const DYNAMIC_TAG_SCRIPT_REGISTER_ID = "_dynamicTagScript";
142
214
  //#endregion
@@ -917,6 +989,7 @@ function _attr_select_value(scope, nodeAccessor, value, valueChange) {
917
989
  assertHandlerIsFunction("valueChange", valueChange);
918
990
  scope["ControlledHandler:" + nodeAccessor] = valueChange;
919
991
  scope["ControlledType:" + nodeAccessor] = valueChange ? 3 : 5;
992
+ if (valueChange) pendingEffects.unshift(() => assertSelectValueMatchesOption(el, normalizedValue, value), scope);
920
993
  if (valueChange && existing) pendingEffects.unshift(() => setSelectValue(el, normalizedValue, multiple), scope);
921
994
  else _attr_select_value_default(scope, nodeAccessor, normalizedValue);
922
995
  }
@@ -959,6 +1032,13 @@ function setSelectValue(el, value, multiple) {
959
1032
  function getSelectValue(el, multiple) {
960
1033
  return multiple ? Array.from(el.selectedOptions, (opt) => opt.value) : el.value;
961
1034
  }
1035
+ function assertSelectValueMatchesOption(el, normalizedValue, value) {
1036
+ const multiple = Array.isArray(normalizedValue);
1037
+ if (multiple ? normalizedValue.some(Boolean) : normalizedValue) {
1038
+ for (const opt of el.options) if (multiple ? normalizedValue.includes(opt.value) : opt.value === normalizedValue) return;
1039
+ console.error("A controlled `<select>`'s `value` has no matching `<option>`:", value);
1040
+ }
1041
+ }
962
1042
  function _attr_details_or_dialog_open_default(scope, nodeAccessor, open) {
963
1043
  if (scope["#Gen"] === runId) scope[nodeAccessor].open = isNotVoid(open);
964
1044
  }
@@ -1023,10 +1103,11 @@ function updateList(arr, val, push) {
1023
1103
  //#endregion
1024
1104
  //#region src/dom/dom.ts
1025
1105
  function _to_text(value) {
1106
+ assertValidTextValue(value);
1026
1107
  return value || value === 0 ? value + "" : "";
1027
1108
  }
1028
1109
  function _attr(element, name, value) {
1029
- assertValidEventHandlerAttr(name, value);
1110
+ assertValidAttrValue(name, value);
1030
1111
  setAttribute(element, name, normalizeAttrValue(value));
1031
1112
  }
1032
1113
  function setAttribute(element, name, value) {
@@ -1104,6 +1185,8 @@ function attrsInternal(scope, nodeAccessor, nextAttrs) {
1104
1185
  let events = scope["EventAttributes:" + nodeAccessor];
1105
1186
  let skip;
1106
1187
  for (const name in events) events[name] = 0;
1188
+ scope["ControlledType:" + nodeAccessor] = 5;
1189
+ scope["ControlledHandler:" + nodeAccessor] = 0;
1107
1190
  switch (el.tagName) {
1108
1191
  case "INPUT":
1109
1192
  if ("checked" in nextAttrs || "checkedChange" in nextAttrs) _attr_input_checked(scope, nodeAccessor, nextAttrs.checked, nextAttrs.checkedChange);
@@ -1142,7 +1225,7 @@ function attrsInternal(scope, nodeAccessor, nextAttrs) {
1142
1225
  _attr_style(el, value);
1143
1226
  break;
1144
1227
  default:
1145
- if (htmlAttrNameReg.test(name)) throw new Error(`Invalid attribute name: ${JSON.stringify(name)}`);
1228
+ assertValidAttrName(name);
1146
1229
  if (isEventHandler(name)) (events ||= scope["EventAttributes:" + nodeAccessor] = {})[getEventHandlerName(name)] = value;
1147
1230
  else if (!(skip?.test(name) || name === "content" && el.tagName !== "META")) _attr(el, name, value);
1148
1231
  break;
@@ -1479,9 +1562,7 @@ function loop(forEach) {
1479
1562
  let hasPotentialMoves;
1480
1563
  var seenKeys = /* @__PURE__ */ new Set();
1481
1564
  forEach(value, (key, args) => {
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);
1484
- else seenKeys.add(key);
1565
+ assertValidLoopKey(key, seenKeys);
1485
1566
  let branch = oldLen && (oldScopesByKey ||= oldScopes.reduce((map, scope, i) => map.set(scope["#LoopKey"] ?? i, scope), /* @__PURE__ */ new Map())).get(key);
1486
1567
  if (branch) hasPotentialMoves = oldScopesByKey.delete(key);
1487
1568
  else branch = createAndSetupBranch(scope["$global"], renderer, scope, parentNode);