@marko/runtime-tags 6.1.13 → 6.1.15
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 +143 -58
- package/dist/debug/dom.mjs +143 -58
- package/dist/debug/html.js +175 -90
- package/dist/debug/html.mjs +175 -90
- 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,137 @@ 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 (!((typeof normalized === "object" || typeof normalized === "function") && "id" in normalized)) {
|
|
109
|
+
if (value.content) throw new Error(`A dynamic tag must be a string tag name (like \`"div"\`) or a Marko template/component, but received an object whose \`content\` is not a template/component.`);
|
|
110
|
+
if (typeof value !== "object" && typeof value !== "function") throw new Error(`A dynamic tag must be a string tag name (like \`"div"\`) or a Marko template/component, but received a ${typeof value}.`);
|
|
111
|
+
}
|
|
112
|
+
if ("id" in normalized) return normalized;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
24
116
|
//#region src/common/errors.ts
|
|
25
117
|
const lowercaseEventHandlerReg = /^on[a-z]/;
|
|
118
|
+
function assertValidAttrValue(name, value) {
|
|
119
|
+
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.`);
|
|
120
|
+
if (typeof value === "function") {
|
|
121
|
+
if (name === "content" || /^on/i.test(name) || /Change$/.test(name)) return;
|
|
122
|
+
throw new Error(`The \`${name}\` attribute cannot be a function.`);
|
|
123
|
+
}
|
|
124
|
+
const unrenderable = describeUnrenderable(value);
|
|
125
|
+
if (unrenderable) throw new Error(`The \`${name}\` attribute cannot be ${unrenderable}.`);
|
|
126
|
+
}
|
|
127
|
+
function assertValidTextValue(value) {
|
|
128
|
+
const unrenderable = describeUnrenderable(value);
|
|
129
|
+
if (unrenderable) throw new Error(`Text content cannot be ${unrenderable}.`);
|
|
130
|
+
}
|
|
131
|
+
function describeUnrenderable(value) {
|
|
132
|
+
if (typeof value === "symbol") return "a symbol";
|
|
133
|
+
if (typeof value === "object" && value !== null) {
|
|
134
|
+
let stringified;
|
|
135
|
+
try {
|
|
136
|
+
stringified = `${value}`;
|
|
137
|
+
} catch {
|
|
138
|
+
stringified = "[object Object]";
|
|
139
|
+
}
|
|
140
|
+
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}\``;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function assertValidLoopKey(key, seenKeys) {
|
|
144
|
+
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}"`}.`);
|
|
145
|
+
if (seenKeys) {
|
|
146
|
+
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.`);
|
|
147
|
+
seenKeys.add(key);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function assertValidAttrName(name) {
|
|
151
|
+
if (htmlAttrNameReg.test(name)) throw new Error(`Invalid attribute name: ${JSON.stringify(name)}`);
|
|
152
|
+
const suggestion = getWrongAttrSuggestion(name);
|
|
153
|
+
if (suggestion) throw new Error(`\`${name}\` is not a valid attribute, did you mean \`${suggestion}\`?`);
|
|
154
|
+
}
|
|
26
155
|
function _el_read_error() {
|
|
27
156
|
throw new Error("Element references can only be read in scripts and event handlers.");
|
|
28
157
|
}
|
|
@@ -47,9 +176,6 @@ function assertExclusiveAttrs(attrs, onError = throwErr) {
|
|
|
47
176
|
if (exclusiveAttrs && exclusiveAttrs.length > 1) onError(`The attributes ${joinWithAnd(exclusiveAttrs)} are mutually exclusive.`);
|
|
48
177
|
}
|
|
49
178
|
}
|
|
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
179
|
function assertHandlerIsFunction(name, value) {
|
|
54
180
|
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
181
|
}
|
|
@@ -89,56 +215,6 @@ function forUntil(until, from, step, cb) {
|
|
|
89
215
|
for (let steps = (until - start) / delta, i = 0; i < steps; i++) cb(start + i * delta);
|
|
90
216
|
}
|
|
91
217
|
//#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
218
|
//#region src/common/meta.ts
|
|
143
219
|
const DYNAMIC_TAG_SCRIPT_REGISTER_ID = "_dynamicTagScript";
|
|
144
220
|
//#endregion
|
|
@@ -919,6 +995,7 @@ function _attr_select_value(scope, nodeAccessor, value, valueChange) {
|
|
|
919
995
|
assertHandlerIsFunction("valueChange", valueChange);
|
|
920
996
|
scope["ControlledHandler:" + nodeAccessor] = valueChange;
|
|
921
997
|
scope["ControlledType:" + nodeAccessor] = valueChange ? 3 : 5;
|
|
998
|
+
if (valueChange) pendingEffects.unshift(() => assertSelectValueMatchesOption(el, normalizedValue, value), scope);
|
|
922
999
|
if (valueChange && existing) pendingEffects.unshift(() => setSelectValue(el, normalizedValue, multiple), scope);
|
|
923
1000
|
else _attr_select_value_default(scope, nodeAccessor, normalizedValue);
|
|
924
1001
|
}
|
|
@@ -961,6 +1038,13 @@ function setSelectValue(el, value, multiple) {
|
|
|
961
1038
|
function getSelectValue(el, multiple) {
|
|
962
1039
|
return multiple ? Array.from(el.selectedOptions, (opt) => opt.value) : el.value;
|
|
963
1040
|
}
|
|
1041
|
+
function assertSelectValueMatchesOption(el, normalizedValue, value) {
|
|
1042
|
+
const multiple = Array.isArray(normalizedValue);
|
|
1043
|
+
if (multiple ? normalizedValue.some(Boolean) : normalizedValue) {
|
|
1044
|
+
for (const opt of el.options) if (multiple ? normalizedValue.includes(opt.value) : opt.value === normalizedValue) return;
|
|
1045
|
+
console.error("A controlled `<select>`'s `value` has no matching `<option>`:", value);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
964
1048
|
function _attr_details_or_dialog_open_default(scope, nodeAccessor, open) {
|
|
965
1049
|
if (scope["#Gen"] === runId) scope[nodeAccessor].open = isNotVoid(open);
|
|
966
1050
|
}
|
|
@@ -1025,10 +1109,11 @@ function updateList(arr, val, push) {
|
|
|
1025
1109
|
//#endregion
|
|
1026
1110
|
//#region src/dom/dom.ts
|
|
1027
1111
|
function _to_text(value) {
|
|
1112
|
+
assertValidTextValue(value);
|
|
1028
1113
|
return value || value === 0 ? value + "" : "";
|
|
1029
1114
|
}
|
|
1030
1115
|
function _attr(element, name, value) {
|
|
1031
|
-
|
|
1116
|
+
assertValidAttrValue(name, value);
|
|
1032
1117
|
setAttribute(element, name, normalizeAttrValue(value));
|
|
1033
1118
|
}
|
|
1034
1119
|
function setAttribute(element, name, value) {
|
|
@@ -1106,6 +1191,8 @@ function attrsInternal(scope, nodeAccessor, nextAttrs) {
|
|
|
1106
1191
|
let events = scope["EventAttributes:" + nodeAccessor];
|
|
1107
1192
|
let skip;
|
|
1108
1193
|
for (const name in events) events[name] = 0;
|
|
1194
|
+
scope["ControlledType:" + nodeAccessor] = 5;
|
|
1195
|
+
scope["ControlledHandler:" + nodeAccessor] = 0;
|
|
1109
1196
|
switch (el.tagName) {
|
|
1110
1197
|
case "INPUT":
|
|
1111
1198
|
if ("checked" in nextAttrs || "checkedChange" in nextAttrs) _attr_input_checked(scope, nodeAccessor, nextAttrs.checked, nextAttrs.checkedChange);
|
|
@@ -1144,7 +1231,7 @@ function attrsInternal(scope, nodeAccessor, nextAttrs) {
|
|
|
1144
1231
|
_attr_style(el, value);
|
|
1145
1232
|
break;
|
|
1146
1233
|
default:
|
|
1147
|
-
|
|
1234
|
+
assertValidAttrName(name);
|
|
1148
1235
|
if (isEventHandler(name)) (events ||= scope["EventAttributes:" + nodeAccessor] = {})[getEventHandlerName(name)] = value;
|
|
1149
1236
|
else if (!(skip?.test(name) || name === "content" && el.tagName !== "META")) _attr(el, name, value);
|
|
1150
1237
|
break;
|
|
@@ -1481,9 +1568,7 @@ function loop(forEach) {
|
|
|
1481
1568
|
let hasPotentialMoves;
|
|
1482
1569
|
var seenKeys = /* @__PURE__ */ new Set();
|
|
1483
1570
|
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);
|
|
1571
|
+
assertValidLoopKey(key, seenKeys);
|
|
1487
1572
|
let branch = oldLen && (oldScopesByKey ||= oldScopes.reduce((map, scope, i) => map.set(scope["#LoopKey"] ?? i, scope), /* @__PURE__ */ new Map())).get(key);
|
|
1488
1573
|
if (branch) hasPotentialMoves = oldScopesByKey.delete(key);
|
|
1489
1574
|
else branch = createAndSetupBranch(scope["$global"], renderer, scope, parentNode);
|
package/dist/debug/dom.mjs
CHANGED
|
@@ -19,8 +19,137 @@ 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 (!((typeof normalized === "object" || typeof normalized === "function") && "id" in normalized)) {
|
|
107
|
+
if (value.content) throw new Error(`A dynamic tag must be a string tag name (like \`"div"\`) or a Marko template/component, but received an object whose \`content\` is not a template/component.`);
|
|
108
|
+
if (typeof value !== "object" && typeof value !== "function") throw new Error(`A dynamic tag must be a string tag name (like \`"div"\`) or a Marko template/component, but received a ${typeof value}.`);
|
|
109
|
+
}
|
|
110
|
+
if ("id" in normalized) return normalized;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
22
114
|
//#region src/common/errors.ts
|
|
23
115
|
const lowercaseEventHandlerReg = /^on[a-z]/;
|
|
116
|
+
function assertValidAttrValue(name, value) {
|
|
117
|
+
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.`);
|
|
118
|
+
if (typeof value === "function") {
|
|
119
|
+
if (name === "content" || /^on/i.test(name) || /Change$/.test(name)) return;
|
|
120
|
+
throw new Error(`The \`${name}\` attribute cannot be a function.`);
|
|
121
|
+
}
|
|
122
|
+
const unrenderable = describeUnrenderable(value);
|
|
123
|
+
if (unrenderable) throw new Error(`The \`${name}\` attribute cannot be ${unrenderable}.`);
|
|
124
|
+
}
|
|
125
|
+
function assertValidTextValue(value) {
|
|
126
|
+
const unrenderable = describeUnrenderable(value);
|
|
127
|
+
if (unrenderable) throw new Error(`Text content cannot be ${unrenderable}.`);
|
|
128
|
+
}
|
|
129
|
+
function describeUnrenderable(value) {
|
|
130
|
+
if (typeof value === "symbol") return "a symbol";
|
|
131
|
+
if (typeof value === "object" && value !== null) {
|
|
132
|
+
let stringified;
|
|
133
|
+
try {
|
|
134
|
+
stringified = `${value}`;
|
|
135
|
+
} catch {
|
|
136
|
+
stringified = "[object Object]";
|
|
137
|
+
}
|
|
138
|
+
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}\``;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function assertValidLoopKey(key, seenKeys) {
|
|
142
|
+
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}"`}.`);
|
|
143
|
+
if (seenKeys) {
|
|
144
|
+
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.`);
|
|
145
|
+
seenKeys.add(key);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function assertValidAttrName(name) {
|
|
149
|
+
if (htmlAttrNameReg.test(name)) throw new Error(`Invalid attribute name: ${JSON.stringify(name)}`);
|
|
150
|
+
const suggestion = getWrongAttrSuggestion(name);
|
|
151
|
+
if (suggestion) throw new Error(`\`${name}\` is not a valid attribute, did you mean \`${suggestion}\`?`);
|
|
152
|
+
}
|
|
24
153
|
function _el_read_error() {
|
|
25
154
|
throw new Error("Element references can only be read in scripts and event handlers.");
|
|
26
155
|
}
|
|
@@ -45,9 +174,6 @@ function assertExclusiveAttrs(attrs, onError = throwErr) {
|
|
|
45
174
|
if (exclusiveAttrs && exclusiveAttrs.length > 1) onError(`The attributes ${joinWithAnd(exclusiveAttrs)} are mutually exclusive.`);
|
|
46
175
|
}
|
|
47
176
|
}
|
|
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
177
|
function assertHandlerIsFunction(name, value) {
|
|
52
178
|
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
179
|
}
|
|
@@ -87,56 +213,6 @@ function forUntil(until, from, step, cb) {
|
|
|
87
213
|
for (let steps = (until - start) / delta, i = 0; i < steps; i++) cb(start + i * delta);
|
|
88
214
|
}
|
|
89
215
|
//#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
216
|
//#region src/common/meta.ts
|
|
141
217
|
const DYNAMIC_TAG_SCRIPT_REGISTER_ID = "_dynamicTagScript";
|
|
142
218
|
//#endregion
|
|
@@ -917,6 +993,7 @@ function _attr_select_value(scope, nodeAccessor, value, valueChange) {
|
|
|
917
993
|
assertHandlerIsFunction("valueChange", valueChange);
|
|
918
994
|
scope["ControlledHandler:" + nodeAccessor] = valueChange;
|
|
919
995
|
scope["ControlledType:" + nodeAccessor] = valueChange ? 3 : 5;
|
|
996
|
+
if (valueChange) pendingEffects.unshift(() => assertSelectValueMatchesOption(el, normalizedValue, value), scope);
|
|
920
997
|
if (valueChange && existing) pendingEffects.unshift(() => setSelectValue(el, normalizedValue, multiple), scope);
|
|
921
998
|
else _attr_select_value_default(scope, nodeAccessor, normalizedValue);
|
|
922
999
|
}
|
|
@@ -959,6 +1036,13 @@ function setSelectValue(el, value, multiple) {
|
|
|
959
1036
|
function getSelectValue(el, multiple) {
|
|
960
1037
|
return multiple ? Array.from(el.selectedOptions, (opt) => opt.value) : el.value;
|
|
961
1038
|
}
|
|
1039
|
+
function assertSelectValueMatchesOption(el, normalizedValue, value) {
|
|
1040
|
+
const multiple = Array.isArray(normalizedValue);
|
|
1041
|
+
if (multiple ? normalizedValue.some(Boolean) : normalizedValue) {
|
|
1042
|
+
for (const opt of el.options) if (multiple ? normalizedValue.includes(opt.value) : opt.value === normalizedValue) return;
|
|
1043
|
+
console.error("A controlled `<select>`'s `value` has no matching `<option>`:", value);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
962
1046
|
function _attr_details_or_dialog_open_default(scope, nodeAccessor, open) {
|
|
963
1047
|
if (scope["#Gen"] === runId) scope[nodeAccessor].open = isNotVoid(open);
|
|
964
1048
|
}
|
|
@@ -1023,10 +1107,11 @@ function updateList(arr, val, push) {
|
|
|
1023
1107
|
//#endregion
|
|
1024
1108
|
//#region src/dom/dom.ts
|
|
1025
1109
|
function _to_text(value) {
|
|
1110
|
+
assertValidTextValue(value);
|
|
1026
1111
|
return value || value === 0 ? value + "" : "";
|
|
1027
1112
|
}
|
|
1028
1113
|
function _attr(element, name, value) {
|
|
1029
|
-
|
|
1114
|
+
assertValidAttrValue(name, value);
|
|
1030
1115
|
setAttribute(element, name, normalizeAttrValue(value));
|
|
1031
1116
|
}
|
|
1032
1117
|
function setAttribute(element, name, value) {
|
|
@@ -1104,6 +1189,8 @@ function attrsInternal(scope, nodeAccessor, nextAttrs) {
|
|
|
1104
1189
|
let events = scope["EventAttributes:" + nodeAccessor];
|
|
1105
1190
|
let skip;
|
|
1106
1191
|
for (const name in events) events[name] = 0;
|
|
1192
|
+
scope["ControlledType:" + nodeAccessor] = 5;
|
|
1193
|
+
scope["ControlledHandler:" + nodeAccessor] = 0;
|
|
1107
1194
|
switch (el.tagName) {
|
|
1108
1195
|
case "INPUT":
|
|
1109
1196
|
if ("checked" in nextAttrs || "checkedChange" in nextAttrs) _attr_input_checked(scope, nodeAccessor, nextAttrs.checked, nextAttrs.checkedChange);
|
|
@@ -1142,7 +1229,7 @@ function attrsInternal(scope, nodeAccessor, nextAttrs) {
|
|
|
1142
1229
|
_attr_style(el, value);
|
|
1143
1230
|
break;
|
|
1144
1231
|
default:
|
|
1145
|
-
|
|
1232
|
+
assertValidAttrName(name);
|
|
1146
1233
|
if (isEventHandler(name)) (events ||= scope["EventAttributes:" + nodeAccessor] = {})[getEventHandlerName(name)] = value;
|
|
1147
1234
|
else if (!(skip?.test(name) || name === "content" && el.tagName !== "META")) _attr(el, name, value);
|
|
1148
1235
|
break;
|
|
@@ -1479,9 +1566,7 @@ function loop(forEach) {
|
|
|
1479
1566
|
let hasPotentialMoves;
|
|
1480
1567
|
var seenKeys = /* @__PURE__ */ new Set();
|
|
1481
1568
|
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);
|
|
1569
|
+
assertValidLoopKey(key, seenKeys);
|
|
1485
1570
|
let branch = oldLen && (oldScopesByKey ||= oldScopes.reduce((map, scope, i) => map.set(scope["#LoopKey"] ?? i, scope), /* @__PURE__ */ new Map())).get(key);
|
|
1486
1571
|
if (branch) hasPotentialMoves = oldScopesByKey.delete(key);
|
|
1487
1572
|
else branch = createAndSetupBranch(scope["$global"], renderer, scope, parentNode);
|