@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.
- package/dist/common/errors.d.ts +4 -1
- package/dist/common/helpers.d.ts +1 -0
- package/dist/debug/dom.js +139 -58
- package/dist/debug/dom.mjs +139 -58
- package/dist/debug/html.js +171 -89
- package/dist/debug/html.mjs +171 -89
- package/dist/dom.js +21 -21
- package/dist/dom.mjs +21 -21
- package/dist/html/writer.d.ts +1 -1
- package/dist/html.js +51 -44
- package/dist/html.mjs +51 -44
- package/dist/translator/index.js +63 -4
- package/dist/translator/util/normalize-string-expression.d.ts +1 -0
- package/package.json +1 -1
package/dist/common/errors.d.ts
CHANGED
|
@@ -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;
|
package/dist/common/helpers.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
package/dist/debug/dom.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|