@lightspeed-js/core 0.4.5 → 0.4.7
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/compile.js +125 -3
- package/dist/index.js +158 -12
- package/dist/ssg.js +158 -12
- package/dist/types/src/runtime/dom/ForBlock.d.ts +18 -1
- package/dist/types/test/slot-fallback-adoption.test.d.ts +1 -0
- package/dist/types/test/svg-attribute-case.test.d.ts +1 -0
- package/dist/types/test/tag.test.d.ts +1 -0
- package/dist/vite.js +158 -12
- package/package.json +1 -1
package/dist/compile.js
CHANGED
|
@@ -1019,7 +1019,7 @@ class BindingEvaluator {
|
|
|
1019
1019
|
binding.el.innerHTML = value == null ? "" : String(value);
|
|
1020
1020
|
break;
|
|
1021
1021
|
case "attr": {
|
|
1022
|
-
const attr = binding.attr;
|
|
1022
|
+
const attr = svgAttributeName(binding.el, binding.attr);
|
|
1023
1023
|
const aria = attr.startsWith("aria-");
|
|
1024
1024
|
if (BOOLEAN_ATTRIBUTES.has(attr) && !aria) {
|
|
1025
1025
|
if (value)
|
|
@@ -1132,7 +1132,95 @@ function isPlain(value) {
|
|
|
1132
1132
|
const proto = Object.getPrototypeOf(value);
|
|
1133
1133
|
return proto === Object.prototype || proto === null;
|
|
1134
1134
|
}
|
|
1135
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
1136
|
+
var SVG_ATTRIBUTES = new Map([
|
|
1137
|
+
"attributeName",
|
|
1138
|
+
"attributeType",
|
|
1139
|
+
"baseFrequency",
|
|
1140
|
+
"baseProfile",
|
|
1141
|
+
"calcMode",
|
|
1142
|
+
"clipPathUnits",
|
|
1143
|
+
"diffuseConstant",
|
|
1144
|
+
"edgeMode",
|
|
1145
|
+
"filterUnits",
|
|
1146
|
+
"glyphRef",
|
|
1147
|
+
"gradientTransform",
|
|
1148
|
+
"gradientUnits",
|
|
1149
|
+
"kernelMatrix",
|
|
1150
|
+
"kernelUnitLength",
|
|
1151
|
+
"keyPoints",
|
|
1152
|
+
"keySplines",
|
|
1153
|
+
"keyTimes",
|
|
1154
|
+
"lengthAdjust",
|
|
1155
|
+
"limitingConeAngle",
|
|
1156
|
+
"markerHeight",
|
|
1157
|
+
"markerUnits",
|
|
1158
|
+
"markerWidth",
|
|
1159
|
+
"maskContentUnits",
|
|
1160
|
+
"maskUnits",
|
|
1161
|
+
"numOctaves",
|
|
1162
|
+
"pathLength",
|
|
1163
|
+
"patternContentUnits",
|
|
1164
|
+
"patternTransform",
|
|
1165
|
+
"patternUnits",
|
|
1166
|
+
"pointsAtX",
|
|
1167
|
+
"pointsAtY",
|
|
1168
|
+
"pointsAtZ",
|
|
1169
|
+
"preserveAlpha",
|
|
1170
|
+
"preserveAspectRatio",
|
|
1171
|
+
"primitiveUnits",
|
|
1172
|
+
"refX",
|
|
1173
|
+
"refY",
|
|
1174
|
+
"repeatCount",
|
|
1175
|
+
"repeatDur",
|
|
1176
|
+
"requiredExtensions",
|
|
1177
|
+
"requiredFeatures",
|
|
1178
|
+
"specularConstant",
|
|
1179
|
+
"specularExponent",
|
|
1180
|
+
"spreadMethod",
|
|
1181
|
+
"startOffset",
|
|
1182
|
+
"stdDeviation",
|
|
1183
|
+
"stitchTiles",
|
|
1184
|
+
"surfaceScale",
|
|
1185
|
+
"systemLanguage",
|
|
1186
|
+
"tableValues",
|
|
1187
|
+
"targetX",
|
|
1188
|
+
"targetY",
|
|
1189
|
+
"textLength",
|
|
1190
|
+
"viewBox",
|
|
1191
|
+
"viewTarget",
|
|
1192
|
+
"xChannelSelector",
|
|
1193
|
+
"yChannelSelector",
|
|
1194
|
+
"zoomAndPan"
|
|
1195
|
+
].map((name) => [name.toLowerCase(), name]));
|
|
1196
|
+
function svgAttributeName(el, attr) {
|
|
1197
|
+
return el.namespaceURI === SVG_NS ? SVG_ATTRIBUTES.get(attr) ?? attr : attr;
|
|
1198
|
+
}
|
|
1135
1199
|
// src/runtime/dom/ForBlock.ts
|
|
1200
|
+
var TAG_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
1201
|
+
var REFUSED_TAGS = new Set([
|
|
1202
|
+
"script",
|
|
1203
|
+
"style",
|
|
1204
|
+
"template",
|
|
1205
|
+
"slot",
|
|
1206
|
+
"iframe",
|
|
1207
|
+
"object",
|
|
1208
|
+
"embed"
|
|
1209
|
+
]);
|
|
1210
|
+
function retag(source, name) {
|
|
1211
|
+
const doc = source.ownerDocument;
|
|
1212
|
+
const el = source.namespaceURI ? doc.createElementNS(source.namespaceURI, name) : doc.createElement(name);
|
|
1213
|
+
for (const attr of Array.from(source.attributes)) {
|
|
1214
|
+
if (attr.namespaceURI)
|
|
1215
|
+
el.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
|
|
1216
|
+
else
|
|
1217
|
+
el.setAttribute(attr.name, attr.value);
|
|
1218
|
+
}
|
|
1219
|
+
for (const child of Array.from(source.childNodes)) {
|
|
1220
|
+
el.appendChild(child.cloneNode(true));
|
|
1221
|
+
}
|
|
1222
|
+
return el;
|
|
1223
|
+
}
|
|
1136
1224
|
function toIterable(value) {
|
|
1137
1225
|
if (value == null)
|
|
1138
1226
|
return [];
|
|
@@ -1174,12 +1262,13 @@ class ForBlock {
|
|
|
1174
1262
|
renderer;
|
|
1175
1263
|
conditional;
|
|
1176
1264
|
itemsExpr;
|
|
1265
|
+
tagged;
|
|
1177
1266
|
rows = new Map;
|
|
1178
1267
|
order = [];
|
|
1179
1268
|
pendingItems = null;
|
|
1180
1269
|
running = false;
|
|
1181
1270
|
warnedDuplicates = false;
|
|
1182
|
-
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "") {
|
|
1271
|
+
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "", tagged = false) {
|
|
1183
1272
|
this.owner = owner;
|
|
1184
1273
|
this.anchor = anchor;
|
|
1185
1274
|
this.templates = templates;
|
|
@@ -1190,22 +1279,55 @@ class ForBlock {
|
|
|
1190
1279
|
this.renderer = renderer;
|
|
1191
1280
|
this.conditional = conditional;
|
|
1192
1281
|
this.itemsExpr = itemsExpr;
|
|
1282
|
+
this.tagged = tagged;
|
|
1193
1283
|
}
|
|
1284
|
+
retagged = new Map;
|
|
1285
|
+
warnedTag = false;
|
|
1194
1286
|
toItems(value) {
|
|
1195
1287
|
if (this.conditional) {
|
|
1196
1288
|
return Number.isInteger(value) && value >= 0 ? [value] : [];
|
|
1197
1289
|
}
|
|
1290
|
+
if (this.tagged)
|
|
1291
|
+
return [this.tagFor(value)];
|
|
1198
1292
|
return toIterable(value);
|
|
1199
1293
|
}
|
|
1200
1294
|
templateFor(item) {
|
|
1295
|
+
if (this.tagged) {
|
|
1296
|
+
const source = this.templates[0];
|
|
1297
|
+
if (item === source.localName)
|
|
1298
|
+
return source;
|
|
1299
|
+
let made = this.retagged.get(item);
|
|
1300
|
+
if (!made) {
|
|
1301
|
+
made = retag(source, item);
|
|
1302
|
+
this.retagged.set(item, made);
|
|
1303
|
+
}
|
|
1304
|
+
return made;
|
|
1305
|
+
}
|
|
1201
1306
|
return this.conditional ? this.templates[item] : this.templates[0];
|
|
1202
1307
|
}
|
|
1308
|
+
tagFor(value) {
|
|
1309
|
+
const written = this.templates[0].localName;
|
|
1310
|
+
if (value == null || value === "")
|
|
1311
|
+
return written;
|
|
1312
|
+
const name = typeof value === "string" ? value.trim().toLowerCase() : null;
|
|
1313
|
+
if (name && TAG_NAME.test(name) && !REFUSED_TAGS.has(name))
|
|
1314
|
+
return name;
|
|
1315
|
+
if (!this.warnedTag) {
|
|
1316
|
+
this.warnedTag = true;
|
|
1317
|
+
warn(`$tag="${this.itemsExpr}" gave ${JSON.stringify(value)}, which is not an element it can make - rendering <${written}>.`);
|
|
1318
|
+
}
|
|
1319
|
+
return written;
|
|
1320
|
+
}
|
|
1203
1321
|
schedule(items) {
|
|
1204
1322
|
this.pendingItems = items;
|
|
1205
1323
|
if (this.running)
|
|
1206
1324
|
return;
|
|
1207
1325
|
this.running = true;
|
|
1208
|
-
this.drain();
|
|
1326
|
+
this.draining = this.drain();
|
|
1327
|
+
}
|
|
1328
|
+
draining = null;
|
|
1329
|
+
settled() {
|
|
1330
|
+
return this.draining ?? Promise.resolve();
|
|
1209
1331
|
}
|
|
1210
1332
|
async drain() {
|
|
1211
1333
|
try {
|
package/dist/index.js
CHANGED
|
@@ -591,7 +591,7 @@ class BindingEvaluator {
|
|
|
591
591
|
binding.el.innerHTML = value == null ? "" : String(value);
|
|
592
592
|
break;
|
|
593
593
|
case "attr": {
|
|
594
|
-
const attr = binding.attr;
|
|
594
|
+
const attr = svgAttributeName(binding.el, binding.attr);
|
|
595
595
|
const aria = attr.startsWith("aria-");
|
|
596
596
|
if (BOOLEAN_ATTRIBUTES.has(attr) && !aria) {
|
|
597
597
|
if (value)
|
|
@@ -704,6 +704,70 @@ function isPlain(value) {
|
|
|
704
704
|
const proto = Object.getPrototypeOf(value);
|
|
705
705
|
return proto === Object.prototype || proto === null;
|
|
706
706
|
}
|
|
707
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
708
|
+
var SVG_ATTRIBUTES = new Map([
|
|
709
|
+
"attributeName",
|
|
710
|
+
"attributeType",
|
|
711
|
+
"baseFrequency",
|
|
712
|
+
"baseProfile",
|
|
713
|
+
"calcMode",
|
|
714
|
+
"clipPathUnits",
|
|
715
|
+
"diffuseConstant",
|
|
716
|
+
"edgeMode",
|
|
717
|
+
"filterUnits",
|
|
718
|
+
"glyphRef",
|
|
719
|
+
"gradientTransform",
|
|
720
|
+
"gradientUnits",
|
|
721
|
+
"kernelMatrix",
|
|
722
|
+
"kernelUnitLength",
|
|
723
|
+
"keyPoints",
|
|
724
|
+
"keySplines",
|
|
725
|
+
"keyTimes",
|
|
726
|
+
"lengthAdjust",
|
|
727
|
+
"limitingConeAngle",
|
|
728
|
+
"markerHeight",
|
|
729
|
+
"markerUnits",
|
|
730
|
+
"markerWidth",
|
|
731
|
+
"maskContentUnits",
|
|
732
|
+
"maskUnits",
|
|
733
|
+
"numOctaves",
|
|
734
|
+
"pathLength",
|
|
735
|
+
"patternContentUnits",
|
|
736
|
+
"patternTransform",
|
|
737
|
+
"patternUnits",
|
|
738
|
+
"pointsAtX",
|
|
739
|
+
"pointsAtY",
|
|
740
|
+
"pointsAtZ",
|
|
741
|
+
"preserveAlpha",
|
|
742
|
+
"preserveAspectRatio",
|
|
743
|
+
"primitiveUnits",
|
|
744
|
+
"refX",
|
|
745
|
+
"refY",
|
|
746
|
+
"repeatCount",
|
|
747
|
+
"repeatDur",
|
|
748
|
+
"requiredExtensions",
|
|
749
|
+
"requiredFeatures",
|
|
750
|
+
"specularConstant",
|
|
751
|
+
"specularExponent",
|
|
752
|
+
"spreadMethod",
|
|
753
|
+
"startOffset",
|
|
754
|
+
"stdDeviation",
|
|
755
|
+
"stitchTiles",
|
|
756
|
+
"surfaceScale",
|
|
757
|
+
"systemLanguage",
|
|
758
|
+
"tableValues",
|
|
759
|
+
"targetX",
|
|
760
|
+
"targetY",
|
|
761
|
+
"textLength",
|
|
762
|
+
"viewBox",
|
|
763
|
+
"viewTarget",
|
|
764
|
+
"xChannelSelector",
|
|
765
|
+
"yChannelSelector",
|
|
766
|
+
"zoomAndPan"
|
|
767
|
+
].map((name) => [name.toLowerCase(), name]));
|
|
768
|
+
function svgAttributeName(el, attr) {
|
|
769
|
+
return el.namespaceURI === SVG_NS ? SVG_ATTRIBUTES.get(attr) ?? attr : attr;
|
|
770
|
+
}
|
|
707
771
|
// src/runtime/events/EventBinder.ts
|
|
708
772
|
class EventBinder {
|
|
709
773
|
bind(el, eventType, handlerName, owner, cleanupTarget = owner) {
|
|
@@ -1040,6 +1104,30 @@ class Row {
|
|
|
1040
1104
|
this.locals[this.locals.length - 1] = value;
|
|
1041
1105
|
}
|
|
1042
1106
|
}
|
|
1107
|
+
var TAG_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
1108
|
+
var REFUSED_TAGS = new Set([
|
|
1109
|
+
"script",
|
|
1110
|
+
"style",
|
|
1111
|
+
"template",
|
|
1112
|
+
"slot",
|
|
1113
|
+
"iframe",
|
|
1114
|
+
"object",
|
|
1115
|
+
"embed"
|
|
1116
|
+
]);
|
|
1117
|
+
function retag(source, name) {
|
|
1118
|
+
const doc = source.ownerDocument;
|
|
1119
|
+
const el = source.namespaceURI ? doc.createElementNS(source.namespaceURI, name) : doc.createElement(name);
|
|
1120
|
+
for (const attr of Array.from(source.attributes)) {
|
|
1121
|
+
if (attr.namespaceURI)
|
|
1122
|
+
el.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
|
|
1123
|
+
else
|
|
1124
|
+
el.setAttribute(attr.name, attr.value);
|
|
1125
|
+
}
|
|
1126
|
+
for (const child of Array.from(source.childNodes)) {
|
|
1127
|
+
el.appendChild(child.cloneNode(true));
|
|
1128
|
+
}
|
|
1129
|
+
return el;
|
|
1130
|
+
}
|
|
1043
1131
|
function toIterable(value) {
|
|
1044
1132
|
if (value == null)
|
|
1045
1133
|
return [];
|
|
@@ -1081,12 +1169,13 @@ class ForBlock {
|
|
|
1081
1169
|
renderer;
|
|
1082
1170
|
conditional;
|
|
1083
1171
|
itemsExpr;
|
|
1172
|
+
tagged;
|
|
1084
1173
|
rows = new Map;
|
|
1085
1174
|
order = [];
|
|
1086
1175
|
pendingItems = null;
|
|
1087
1176
|
running = false;
|
|
1088
1177
|
warnedDuplicates = false;
|
|
1089
|
-
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "") {
|
|
1178
|
+
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "", tagged = false) {
|
|
1090
1179
|
this.owner = owner;
|
|
1091
1180
|
this.anchor = anchor;
|
|
1092
1181
|
this.templates = templates;
|
|
@@ -1097,22 +1186,55 @@ class ForBlock {
|
|
|
1097
1186
|
this.renderer = renderer;
|
|
1098
1187
|
this.conditional = conditional;
|
|
1099
1188
|
this.itemsExpr = itemsExpr;
|
|
1189
|
+
this.tagged = tagged;
|
|
1100
1190
|
}
|
|
1191
|
+
retagged = new Map;
|
|
1192
|
+
warnedTag = false;
|
|
1101
1193
|
toItems(value) {
|
|
1102
1194
|
if (this.conditional) {
|
|
1103
1195
|
return Number.isInteger(value) && value >= 0 ? [value] : [];
|
|
1104
1196
|
}
|
|
1197
|
+
if (this.tagged)
|
|
1198
|
+
return [this.tagFor(value)];
|
|
1105
1199
|
return toIterable(value);
|
|
1106
1200
|
}
|
|
1107
1201
|
templateFor(item) {
|
|
1202
|
+
if (this.tagged) {
|
|
1203
|
+
const source = this.templates[0];
|
|
1204
|
+
if (item === source.localName)
|
|
1205
|
+
return source;
|
|
1206
|
+
let made = this.retagged.get(item);
|
|
1207
|
+
if (!made) {
|
|
1208
|
+
made = retag(source, item);
|
|
1209
|
+
this.retagged.set(item, made);
|
|
1210
|
+
}
|
|
1211
|
+
return made;
|
|
1212
|
+
}
|
|
1108
1213
|
return this.conditional ? this.templates[item] : this.templates[0];
|
|
1109
1214
|
}
|
|
1215
|
+
tagFor(value) {
|
|
1216
|
+
const written = this.templates[0].localName;
|
|
1217
|
+
if (value == null || value === "")
|
|
1218
|
+
return written;
|
|
1219
|
+
const name = typeof value === "string" ? value.trim().toLowerCase() : null;
|
|
1220
|
+
if (name && TAG_NAME.test(name) && !REFUSED_TAGS.has(name))
|
|
1221
|
+
return name;
|
|
1222
|
+
if (!this.warnedTag) {
|
|
1223
|
+
this.warnedTag = true;
|
|
1224
|
+
warn(`$tag="${this.itemsExpr}" gave ${JSON.stringify(value)}, which is not an element it can make - rendering <${written}>.`);
|
|
1225
|
+
}
|
|
1226
|
+
return written;
|
|
1227
|
+
}
|
|
1110
1228
|
schedule(items) {
|
|
1111
1229
|
this.pendingItems = items;
|
|
1112
1230
|
if (this.running)
|
|
1113
1231
|
return;
|
|
1114
1232
|
this.running = true;
|
|
1115
|
-
this.drain();
|
|
1233
|
+
this.draining = this.drain();
|
|
1234
|
+
}
|
|
1235
|
+
draining = null;
|
|
1236
|
+
settled() {
|
|
1237
|
+
return this.draining ?? Promise.resolve();
|
|
1116
1238
|
}
|
|
1117
1239
|
async drain() {
|
|
1118
1240
|
try {
|
|
@@ -1466,6 +1588,15 @@ class Mounter {
|
|
|
1466
1588
|
this.createBlock(el, context, "if");
|
|
1467
1589
|
return;
|
|
1468
1590
|
}
|
|
1591
|
+
if (el.hasAttribute("data-bind-tag")) {
|
|
1592
|
+
if (el.tagName === "TEMPLATE") {
|
|
1593
|
+
warn("$tag on a <template> means nothing - it renders its contents, not itself.", el);
|
|
1594
|
+
el.removeAttribute("data-bind-tag");
|
|
1595
|
+
} else {
|
|
1596
|
+
this.createBlock(el, context, "tag");
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1469
1600
|
if (el.hasAttribute("data-bind-else") || el.hasAttribute("data-bind-else-if")) {
|
|
1470
1601
|
warn("$else / $else-if must directly follow a $if or $else-if sibling", el);
|
|
1471
1602
|
el.remove();
|
|
@@ -1611,12 +1742,22 @@ class Mounter {
|
|
|
1611
1742
|
i += consumed.templates - 1;
|
|
1612
1743
|
continue;
|
|
1613
1744
|
}
|
|
1745
|
+
if (el.hasAttribute("data-bind-tag") && el.tagName !== "TEMPLATE") {
|
|
1746
|
+
const consumed = this.adoptBlock(el, found, d, context, "tag");
|
|
1747
|
+
if (!consumed)
|
|
1748
|
+
return false;
|
|
1749
|
+
d += consumed.dom;
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1614
1752
|
if (el.tagName === "SLOT") {
|
|
1615
1753
|
if (!found[d] || !isSlotStart(found[d])) {
|
|
1616
|
-
|
|
1754
|
+
const fallback = significant(Array.from(el.childNodes));
|
|
1755
|
+
const countable = fallback.every((n) => n.nodeType === 3 || n.nodeType === 1 && !isBlockTemplate(n) && n.tagName !== "SLOT");
|
|
1756
|
+
const share = countable ? found.slice(d, d + fallback.length) : found.slice(d);
|
|
1757
|
+
if (!this.adoptNodes(fallback, share, context, markers)) {
|
|
1617
1758
|
return false;
|
|
1618
1759
|
}
|
|
1619
|
-
d
|
|
1760
|
+
d += share.length;
|
|
1620
1761
|
continue;
|
|
1621
1762
|
}
|
|
1622
1763
|
const name = found[d].data.slice(SLOT_MARK.length);
|
|
@@ -1746,18 +1887,19 @@ class Mounter {
|
|
|
1746
1887
|
const rawExpression = el.getAttribute(`data-bind-${kind}`) ?? "";
|
|
1747
1888
|
const branches = conditional ? this.gatherBranches(el) : [];
|
|
1748
1889
|
const expression = conditional ? composeBranchExpression(branches) : rawExpression;
|
|
1749
|
-
const parsed = conditional ? { alias: null, items: expression } : parseForExpression(rawExpression);
|
|
1890
|
+
const parsed = conditional || kind === "tag" ? { alias: null, items: expression } : parseForExpression(rawExpression);
|
|
1750
1891
|
if (!parsed) {
|
|
1751
1892
|
warn(`Could not parse $for="${expression}" - expected "item in items"`);
|
|
1752
1893
|
el.remove();
|
|
1753
1894
|
return null;
|
|
1754
1895
|
}
|
|
1755
|
-
if (
|
|
1896
|
+
if (kind === "for" && el.hasAttribute("data-bind-if")) {
|
|
1756
1897
|
warn(`$for and $if on the same element are not supported (${rawExpression}). Wrap one in an element.`);
|
|
1757
1898
|
}
|
|
1758
|
-
const keyExpr =
|
|
1899
|
+
const keyExpr = kind === "for" ? el.getAttribute("data-bind-key") : null;
|
|
1759
1900
|
el.removeAttribute(`data-bind-${kind}`);
|
|
1760
|
-
|
|
1901
|
+
if (kind === "for")
|
|
1902
|
+
el.removeAttribute("data-bind-key");
|
|
1761
1903
|
let anchor;
|
|
1762
1904
|
if (existingAnchor) {
|
|
1763
1905
|
anchor = existingAnchor;
|
|
@@ -1772,7 +1914,7 @@ class Mounter {
|
|
|
1772
1914
|
const compiled = this.bindings.compileExpression(keyExpr, params);
|
|
1773
1915
|
return compiled(context.owner.state, context.owner.props, this.cellsProxy(), this.bindings.pendingProbe(), ...locals);
|
|
1774
1916
|
} : null;
|
|
1775
|
-
const block = new ForBlock(context.owner, anchor, templates, parsed.alias, context.params, context.locals, keyFor, this, conditional, parsed.items);
|
|
1917
|
+
const block = new ForBlock(context.owner, anchor, templates, parsed.alias, context.params, context.locals, keyFor, this, conditional, parsed.items, kind === "tag");
|
|
1776
1918
|
context.holder.addBlock(block);
|
|
1777
1919
|
context.holder.addBinding(this.bindings.createForBinding(block, parsed.items, context.params));
|
|
1778
1920
|
return block;
|
|
@@ -1860,9 +2002,10 @@ class Mounter {
|
|
|
1860
2002
|
if (this.ssg) {
|
|
1861
2003
|
fragment.insertBefore(document.createComment(ROW_MARK), fragment.firstChild);
|
|
1862
2004
|
}
|
|
1863
|
-
row.nodes = Array.from(fragment.childNodes);
|
|
1864
2005
|
this.bindings.updateRow(block.owner, row);
|
|
1865
2006
|
row.instances.forEach((child) => this.bindings.updateTree(child));
|
|
2007
|
+
await Promise.all(row.blocks.map((nested) => nested.settled()));
|
|
2008
|
+
row.nodes = Array.from(fragment.childNodes);
|
|
1866
2009
|
return row;
|
|
1867
2010
|
}
|
|
1868
2011
|
connected() {
|
|
@@ -2268,7 +2411,10 @@ function isBlockAnchor(node) {
|
|
|
2268
2411
|
if (node.nodeType !== 8)
|
|
2269
2412
|
return false;
|
|
2270
2413
|
const data = node.data.trim();
|
|
2271
|
-
return data.startsWith("for:") || data.startsWith("if:");
|
|
2414
|
+
return data.startsWith("for:") || data.startsWith("if:") || data.startsWith("tag:");
|
|
2415
|
+
}
|
|
2416
|
+
function isBlockTemplate(el) {
|
|
2417
|
+
return el.hasAttribute("data-bind-for") || el.hasAttribute("data-bind-if") || el.hasAttribute("data-bind-tag");
|
|
2272
2418
|
}
|
|
2273
2419
|
function isInsignificant(node) {
|
|
2274
2420
|
return node.nodeType === 3 && !(node.textContent ?? "").trim();
|
package/dist/ssg.js
CHANGED
|
@@ -610,7 +610,7 @@ class BindingEvaluator {
|
|
|
610
610
|
binding.el.innerHTML = value == null ? "" : String(value);
|
|
611
611
|
break;
|
|
612
612
|
case "attr": {
|
|
613
|
-
const attr = binding.attr;
|
|
613
|
+
const attr = svgAttributeName(binding.el, binding.attr);
|
|
614
614
|
const aria = attr.startsWith("aria-");
|
|
615
615
|
if (BOOLEAN_ATTRIBUTES.has(attr) && !aria) {
|
|
616
616
|
if (value)
|
|
@@ -723,6 +723,70 @@ function isPlain(value) {
|
|
|
723
723
|
const proto = Object.getPrototypeOf(value);
|
|
724
724
|
return proto === Object.prototype || proto === null;
|
|
725
725
|
}
|
|
726
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
727
|
+
var SVG_ATTRIBUTES = new Map([
|
|
728
|
+
"attributeName",
|
|
729
|
+
"attributeType",
|
|
730
|
+
"baseFrequency",
|
|
731
|
+
"baseProfile",
|
|
732
|
+
"calcMode",
|
|
733
|
+
"clipPathUnits",
|
|
734
|
+
"diffuseConstant",
|
|
735
|
+
"edgeMode",
|
|
736
|
+
"filterUnits",
|
|
737
|
+
"glyphRef",
|
|
738
|
+
"gradientTransform",
|
|
739
|
+
"gradientUnits",
|
|
740
|
+
"kernelMatrix",
|
|
741
|
+
"kernelUnitLength",
|
|
742
|
+
"keyPoints",
|
|
743
|
+
"keySplines",
|
|
744
|
+
"keyTimes",
|
|
745
|
+
"lengthAdjust",
|
|
746
|
+
"limitingConeAngle",
|
|
747
|
+
"markerHeight",
|
|
748
|
+
"markerUnits",
|
|
749
|
+
"markerWidth",
|
|
750
|
+
"maskContentUnits",
|
|
751
|
+
"maskUnits",
|
|
752
|
+
"numOctaves",
|
|
753
|
+
"pathLength",
|
|
754
|
+
"patternContentUnits",
|
|
755
|
+
"patternTransform",
|
|
756
|
+
"patternUnits",
|
|
757
|
+
"pointsAtX",
|
|
758
|
+
"pointsAtY",
|
|
759
|
+
"pointsAtZ",
|
|
760
|
+
"preserveAlpha",
|
|
761
|
+
"preserveAspectRatio",
|
|
762
|
+
"primitiveUnits",
|
|
763
|
+
"refX",
|
|
764
|
+
"refY",
|
|
765
|
+
"repeatCount",
|
|
766
|
+
"repeatDur",
|
|
767
|
+
"requiredExtensions",
|
|
768
|
+
"requiredFeatures",
|
|
769
|
+
"specularConstant",
|
|
770
|
+
"specularExponent",
|
|
771
|
+
"spreadMethod",
|
|
772
|
+
"startOffset",
|
|
773
|
+
"stdDeviation",
|
|
774
|
+
"stitchTiles",
|
|
775
|
+
"surfaceScale",
|
|
776
|
+
"systemLanguage",
|
|
777
|
+
"tableValues",
|
|
778
|
+
"targetX",
|
|
779
|
+
"targetY",
|
|
780
|
+
"textLength",
|
|
781
|
+
"viewBox",
|
|
782
|
+
"viewTarget",
|
|
783
|
+
"xChannelSelector",
|
|
784
|
+
"yChannelSelector",
|
|
785
|
+
"zoomAndPan"
|
|
786
|
+
].map((name) => [name.toLowerCase(), name]));
|
|
787
|
+
function svgAttributeName(el, attr) {
|
|
788
|
+
return el.namespaceURI === SVG_NS ? SVG_ATTRIBUTES.get(attr) ?? attr : attr;
|
|
789
|
+
}
|
|
726
790
|
// src/runtime/events/EventBinder.ts
|
|
727
791
|
class EventBinder {
|
|
728
792
|
bind(el, eventType, handlerName, owner, cleanupTarget = owner) {
|
|
@@ -1059,6 +1123,30 @@ class Row {
|
|
|
1059
1123
|
this.locals[this.locals.length - 1] = value;
|
|
1060
1124
|
}
|
|
1061
1125
|
}
|
|
1126
|
+
var TAG_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
1127
|
+
var REFUSED_TAGS = new Set([
|
|
1128
|
+
"script",
|
|
1129
|
+
"style",
|
|
1130
|
+
"template",
|
|
1131
|
+
"slot",
|
|
1132
|
+
"iframe",
|
|
1133
|
+
"object",
|
|
1134
|
+
"embed"
|
|
1135
|
+
]);
|
|
1136
|
+
function retag(source, name) {
|
|
1137
|
+
const doc = source.ownerDocument;
|
|
1138
|
+
const el = source.namespaceURI ? doc.createElementNS(source.namespaceURI, name) : doc.createElement(name);
|
|
1139
|
+
for (const attr of Array.from(source.attributes)) {
|
|
1140
|
+
if (attr.namespaceURI)
|
|
1141
|
+
el.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
|
|
1142
|
+
else
|
|
1143
|
+
el.setAttribute(attr.name, attr.value);
|
|
1144
|
+
}
|
|
1145
|
+
for (const child of Array.from(source.childNodes)) {
|
|
1146
|
+
el.appendChild(child.cloneNode(true));
|
|
1147
|
+
}
|
|
1148
|
+
return el;
|
|
1149
|
+
}
|
|
1062
1150
|
function toIterable(value) {
|
|
1063
1151
|
if (value == null)
|
|
1064
1152
|
return [];
|
|
@@ -1100,12 +1188,13 @@ class ForBlock {
|
|
|
1100
1188
|
renderer;
|
|
1101
1189
|
conditional;
|
|
1102
1190
|
itemsExpr;
|
|
1191
|
+
tagged;
|
|
1103
1192
|
rows = new Map;
|
|
1104
1193
|
order = [];
|
|
1105
1194
|
pendingItems = null;
|
|
1106
1195
|
running = false;
|
|
1107
1196
|
warnedDuplicates = false;
|
|
1108
|
-
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "") {
|
|
1197
|
+
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "", tagged = false) {
|
|
1109
1198
|
this.owner = owner;
|
|
1110
1199
|
this.anchor = anchor;
|
|
1111
1200
|
this.templates = templates;
|
|
@@ -1116,22 +1205,55 @@ class ForBlock {
|
|
|
1116
1205
|
this.renderer = renderer;
|
|
1117
1206
|
this.conditional = conditional;
|
|
1118
1207
|
this.itemsExpr = itemsExpr;
|
|
1208
|
+
this.tagged = tagged;
|
|
1119
1209
|
}
|
|
1210
|
+
retagged = new Map;
|
|
1211
|
+
warnedTag = false;
|
|
1120
1212
|
toItems(value) {
|
|
1121
1213
|
if (this.conditional) {
|
|
1122
1214
|
return Number.isInteger(value) && value >= 0 ? [value] : [];
|
|
1123
1215
|
}
|
|
1216
|
+
if (this.tagged)
|
|
1217
|
+
return [this.tagFor(value)];
|
|
1124
1218
|
return toIterable(value);
|
|
1125
1219
|
}
|
|
1126
1220
|
templateFor(item) {
|
|
1221
|
+
if (this.tagged) {
|
|
1222
|
+
const source = this.templates[0];
|
|
1223
|
+
if (item === source.localName)
|
|
1224
|
+
return source;
|
|
1225
|
+
let made = this.retagged.get(item);
|
|
1226
|
+
if (!made) {
|
|
1227
|
+
made = retag(source, item);
|
|
1228
|
+
this.retagged.set(item, made);
|
|
1229
|
+
}
|
|
1230
|
+
return made;
|
|
1231
|
+
}
|
|
1127
1232
|
return this.conditional ? this.templates[item] : this.templates[0];
|
|
1128
1233
|
}
|
|
1234
|
+
tagFor(value) {
|
|
1235
|
+
const written = this.templates[0].localName;
|
|
1236
|
+
if (value == null || value === "")
|
|
1237
|
+
return written;
|
|
1238
|
+
const name = typeof value === "string" ? value.trim().toLowerCase() : null;
|
|
1239
|
+
if (name && TAG_NAME.test(name) && !REFUSED_TAGS.has(name))
|
|
1240
|
+
return name;
|
|
1241
|
+
if (!this.warnedTag) {
|
|
1242
|
+
this.warnedTag = true;
|
|
1243
|
+
warn(`$tag="${this.itemsExpr}" gave ${JSON.stringify(value)}, which is not an element it can make - rendering <${written}>.`);
|
|
1244
|
+
}
|
|
1245
|
+
return written;
|
|
1246
|
+
}
|
|
1129
1247
|
schedule(items) {
|
|
1130
1248
|
this.pendingItems = items;
|
|
1131
1249
|
if (this.running)
|
|
1132
1250
|
return;
|
|
1133
1251
|
this.running = true;
|
|
1134
|
-
this.drain();
|
|
1252
|
+
this.draining = this.drain();
|
|
1253
|
+
}
|
|
1254
|
+
draining = null;
|
|
1255
|
+
settled() {
|
|
1256
|
+
return this.draining ?? Promise.resolve();
|
|
1135
1257
|
}
|
|
1136
1258
|
async drain() {
|
|
1137
1259
|
try {
|
|
@@ -1489,6 +1611,15 @@ class Mounter {
|
|
|
1489
1611
|
this.createBlock(el, context, "if");
|
|
1490
1612
|
return;
|
|
1491
1613
|
}
|
|
1614
|
+
if (el.hasAttribute("data-bind-tag")) {
|
|
1615
|
+
if (el.tagName === "TEMPLATE") {
|
|
1616
|
+
warn("$tag on a <template> means nothing - it renders its contents, not itself.", el);
|
|
1617
|
+
el.removeAttribute("data-bind-tag");
|
|
1618
|
+
} else {
|
|
1619
|
+
this.createBlock(el, context, "tag");
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1492
1623
|
if (el.hasAttribute("data-bind-else") || el.hasAttribute("data-bind-else-if")) {
|
|
1493
1624
|
warn("$else / $else-if must directly follow a $if or $else-if sibling", el);
|
|
1494
1625
|
el.remove();
|
|
@@ -1634,12 +1765,22 @@ class Mounter {
|
|
|
1634
1765
|
i += consumed.templates - 1;
|
|
1635
1766
|
continue;
|
|
1636
1767
|
}
|
|
1768
|
+
if (el.hasAttribute("data-bind-tag") && el.tagName !== "TEMPLATE") {
|
|
1769
|
+
const consumed = this.adoptBlock(el, found, d, context, "tag");
|
|
1770
|
+
if (!consumed)
|
|
1771
|
+
return false;
|
|
1772
|
+
d += consumed.dom;
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1637
1775
|
if (el.tagName === "SLOT") {
|
|
1638
1776
|
if (!found[d] || !isSlotStart(found[d])) {
|
|
1639
|
-
|
|
1777
|
+
const fallback = significant(Array.from(el.childNodes));
|
|
1778
|
+
const countable = fallback.every((n) => n.nodeType === 3 || n.nodeType === 1 && !isBlockTemplate(n) && n.tagName !== "SLOT");
|
|
1779
|
+
const share = countable ? found.slice(d, d + fallback.length) : found.slice(d);
|
|
1780
|
+
if (!this.adoptNodes(fallback, share, context, markers)) {
|
|
1640
1781
|
return false;
|
|
1641
1782
|
}
|
|
1642
|
-
d
|
|
1783
|
+
d += share.length;
|
|
1643
1784
|
continue;
|
|
1644
1785
|
}
|
|
1645
1786
|
const name = found[d].data.slice(SLOT_MARK.length);
|
|
@@ -1769,18 +1910,19 @@ class Mounter {
|
|
|
1769
1910
|
const rawExpression = el.getAttribute(`data-bind-${kind}`) ?? "";
|
|
1770
1911
|
const branches = conditional ? this.gatherBranches(el) : [];
|
|
1771
1912
|
const expression = conditional ? composeBranchExpression(branches) : rawExpression;
|
|
1772
|
-
const parsed = conditional ? { alias: null, items: expression } : parseForExpression(rawExpression);
|
|
1913
|
+
const parsed = conditional || kind === "tag" ? { alias: null, items: expression } : parseForExpression(rawExpression);
|
|
1773
1914
|
if (!parsed) {
|
|
1774
1915
|
warn(`Could not parse $for="${expression}" - expected "item in items"`);
|
|
1775
1916
|
el.remove();
|
|
1776
1917
|
return null;
|
|
1777
1918
|
}
|
|
1778
|
-
if (
|
|
1919
|
+
if (kind === "for" && el.hasAttribute("data-bind-if")) {
|
|
1779
1920
|
warn(`$for and $if on the same element are not supported (${rawExpression}). Wrap one in an element.`);
|
|
1780
1921
|
}
|
|
1781
|
-
const keyExpr =
|
|
1922
|
+
const keyExpr = kind === "for" ? el.getAttribute("data-bind-key") : null;
|
|
1782
1923
|
el.removeAttribute(`data-bind-${kind}`);
|
|
1783
|
-
|
|
1924
|
+
if (kind === "for")
|
|
1925
|
+
el.removeAttribute("data-bind-key");
|
|
1784
1926
|
let anchor;
|
|
1785
1927
|
if (existingAnchor) {
|
|
1786
1928
|
anchor = existingAnchor;
|
|
@@ -1795,7 +1937,7 @@ class Mounter {
|
|
|
1795
1937
|
const compiled = this.bindings.compileExpression(keyExpr, params);
|
|
1796
1938
|
return compiled(context.owner.state, context.owner.props, this.cellsProxy(), this.bindings.pendingProbe(), ...locals);
|
|
1797
1939
|
} : null;
|
|
1798
|
-
const block = new ForBlock(context.owner, anchor, templates, parsed.alias, context.params, context.locals, keyFor, this, conditional, parsed.items);
|
|
1940
|
+
const block = new ForBlock(context.owner, anchor, templates, parsed.alias, context.params, context.locals, keyFor, this, conditional, parsed.items, kind === "tag");
|
|
1799
1941
|
context.holder.addBlock(block);
|
|
1800
1942
|
context.holder.addBinding(this.bindings.createForBinding(block, parsed.items, context.params));
|
|
1801
1943
|
return block;
|
|
@@ -1883,9 +2025,10 @@ class Mounter {
|
|
|
1883
2025
|
if (this.ssg) {
|
|
1884
2026
|
fragment.insertBefore(document.createComment(ROW_MARK), fragment.firstChild);
|
|
1885
2027
|
}
|
|
1886
|
-
row.nodes = Array.from(fragment.childNodes);
|
|
1887
2028
|
this.bindings.updateRow(block.owner, row);
|
|
1888
2029
|
row.instances.forEach((child) => this.bindings.updateTree(child));
|
|
2030
|
+
await Promise.all(row.blocks.map((nested) => nested.settled()));
|
|
2031
|
+
row.nodes = Array.from(fragment.childNodes);
|
|
1889
2032
|
return row;
|
|
1890
2033
|
}
|
|
1891
2034
|
connected() {
|
|
@@ -2291,7 +2434,10 @@ function isBlockAnchor(node) {
|
|
|
2291
2434
|
if (node.nodeType !== 8)
|
|
2292
2435
|
return false;
|
|
2293
2436
|
const data = node.data.trim();
|
|
2294
|
-
return data.startsWith("for:") || data.startsWith("if:");
|
|
2437
|
+
return data.startsWith("for:") || data.startsWith("if:") || data.startsWith("tag:");
|
|
2438
|
+
}
|
|
2439
|
+
function isBlockTemplate(el) {
|
|
2440
|
+
return el.hasAttribute("data-bind-for") || el.hasAttribute("data-bind-if") || el.hasAttribute("data-bind-tag");
|
|
2295
2441
|
}
|
|
2296
2442
|
function isInsignificant(node) {
|
|
2297
2443
|
return node.nodeType === 3 && !(node.textContent ?? "").trim();
|
|
@@ -73,6 +73,8 @@ export declare class ForBlock {
|
|
|
73
73
|
conditional: boolean;
|
|
74
74
|
/** The collection expression, kept so adoption can re-evaluate it. */
|
|
75
75
|
itemsExpr: string;
|
|
76
|
+
/** `$tag`: one row, of the element the expression names (ADR-063). */
|
|
77
|
+
tagged: boolean;
|
|
76
78
|
rows: Map<string, Row>;
|
|
77
79
|
order: string[];
|
|
78
80
|
private pendingItems;
|
|
@@ -93,7 +95,12 @@ export declare class ForBlock {
|
|
|
93
95
|
/** `$if`: zero or one row, driven by a condition rather than a list. */
|
|
94
96
|
conditional?: boolean,
|
|
95
97
|
/** The collection expression, kept so adoption can re-evaluate it. */
|
|
96
|
-
itemsExpr?: string
|
|
98
|
+
itemsExpr?: string,
|
|
99
|
+
/** `$tag`: one row, of the element the expression names (ADR-063). */
|
|
100
|
+
tagged?: boolean);
|
|
101
|
+
/** `$tag`'s copies of the template under other names, made once each. */
|
|
102
|
+
private retagged;
|
|
103
|
+
private warnedTag;
|
|
97
104
|
/**
|
|
98
105
|
* Turn a bound expression into the rows it implies.
|
|
99
106
|
*
|
|
@@ -103,6 +110,8 @@ export declare class ForBlock {
|
|
|
103
110
|
toItems(value: any): any[];
|
|
104
111
|
/** The markup a row should clone. */
|
|
105
112
|
templateFor(item: any): Element;
|
|
113
|
+
/** The element `$tag` evaluated to, or the one as written. */
|
|
114
|
+
private tagFor;
|
|
106
115
|
/**
|
|
107
116
|
* Queue a reconcile.
|
|
108
117
|
*
|
|
@@ -111,6 +120,14 @@ export declare class ForBlock {
|
|
|
111
120
|
* matters, so it replaces any queued one and the loop runs until settled.
|
|
112
121
|
*/
|
|
113
122
|
schedule(items: any[]): void;
|
|
123
|
+
private draining;
|
|
124
|
+
/**
|
|
125
|
+
* Resolves once every queued reconcile has run.
|
|
126
|
+
*
|
|
127
|
+
* A row built inside another block's new row waits for this before that row
|
|
128
|
+
* counts its nodes, or the rows placed meanwhile are left out of it.
|
|
129
|
+
*/
|
|
130
|
+
settled(): Promise<void>;
|
|
114
131
|
private drain;
|
|
115
132
|
/** Public so adoption can match rendered rows to the current data. */
|
|
116
133
|
keysFor(items: any[]): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/vite.js
CHANGED
|
@@ -349,7 +349,7 @@ class BindingEvaluator {
|
|
|
349
349
|
binding.el.innerHTML = value == null ? "" : String(value);
|
|
350
350
|
break;
|
|
351
351
|
case "attr": {
|
|
352
|
-
const attr = binding.attr;
|
|
352
|
+
const attr = svgAttributeName(binding.el, binding.attr);
|
|
353
353
|
const aria = attr.startsWith("aria-");
|
|
354
354
|
if (BOOLEAN_ATTRIBUTES.has(attr) && !aria) {
|
|
355
355
|
if (value)
|
|
@@ -462,6 +462,70 @@ function isPlain(value) {
|
|
|
462
462
|
const proto = Object.getPrototypeOf(value);
|
|
463
463
|
return proto === Object.prototype || proto === null;
|
|
464
464
|
}
|
|
465
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
466
|
+
var SVG_ATTRIBUTES = new Map([
|
|
467
|
+
"attributeName",
|
|
468
|
+
"attributeType",
|
|
469
|
+
"baseFrequency",
|
|
470
|
+
"baseProfile",
|
|
471
|
+
"calcMode",
|
|
472
|
+
"clipPathUnits",
|
|
473
|
+
"diffuseConstant",
|
|
474
|
+
"edgeMode",
|
|
475
|
+
"filterUnits",
|
|
476
|
+
"glyphRef",
|
|
477
|
+
"gradientTransform",
|
|
478
|
+
"gradientUnits",
|
|
479
|
+
"kernelMatrix",
|
|
480
|
+
"kernelUnitLength",
|
|
481
|
+
"keyPoints",
|
|
482
|
+
"keySplines",
|
|
483
|
+
"keyTimes",
|
|
484
|
+
"lengthAdjust",
|
|
485
|
+
"limitingConeAngle",
|
|
486
|
+
"markerHeight",
|
|
487
|
+
"markerUnits",
|
|
488
|
+
"markerWidth",
|
|
489
|
+
"maskContentUnits",
|
|
490
|
+
"maskUnits",
|
|
491
|
+
"numOctaves",
|
|
492
|
+
"pathLength",
|
|
493
|
+
"patternContentUnits",
|
|
494
|
+
"patternTransform",
|
|
495
|
+
"patternUnits",
|
|
496
|
+
"pointsAtX",
|
|
497
|
+
"pointsAtY",
|
|
498
|
+
"pointsAtZ",
|
|
499
|
+
"preserveAlpha",
|
|
500
|
+
"preserveAspectRatio",
|
|
501
|
+
"primitiveUnits",
|
|
502
|
+
"refX",
|
|
503
|
+
"refY",
|
|
504
|
+
"repeatCount",
|
|
505
|
+
"repeatDur",
|
|
506
|
+
"requiredExtensions",
|
|
507
|
+
"requiredFeatures",
|
|
508
|
+
"specularConstant",
|
|
509
|
+
"specularExponent",
|
|
510
|
+
"spreadMethod",
|
|
511
|
+
"startOffset",
|
|
512
|
+
"stdDeviation",
|
|
513
|
+
"stitchTiles",
|
|
514
|
+
"surfaceScale",
|
|
515
|
+
"systemLanguage",
|
|
516
|
+
"tableValues",
|
|
517
|
+
"targetX",
|
|
518
|
+
"targetY",
|
|
519
|
+
"textLength",
|
|
520
|
+
"viewBox",
|
|
521
|
+
"viewTarget",
|
|
522
|
+
"xChannelSelector",
|
|
523
|
+
"yChannelSelector",
|
|
524
|
+
"zoomAndPan"
|
|
525
|
+
].map((name) => [name.toLowerCase(), name]));
|
|
526
|
+
function svgAttributeName(el, attr) {
|
|
527
|
+
return el.namespaceURI === SVG_NS ? SVG_ATTRIBUTES.get(attr) ?? attr : attr;
|
|
528
|
+
}
|
|
465
529
|
// src/runtime/dom/ForBlock.ts
|
|
466
530
|
class Row {
|
|
467
531
|
locals;
|
|
@@ -490,6 +554,30 @@ class Row {
|
|
|
490
554
|
this.locals[this.locals.length - 1] = value;
|
|
491
555
|
}
|
|
492
556
|
}
|
|
557
|
+
var TAG_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
558
|
+
var REFUSED_TAGS = new Set([
|
|
559
|
+
"script",
|
|
560
|
+
"style",
|
|
561
|
+
"template",
|
|
562
|
+
"slot",
|
|
563
|
+
"iframe",
|
|
564
|
+
"object",
|
|
565
|
+
"embed"
|
|
566
|
+
]);
|
|
567
|
+
function retag(source, name) {
|
|
568
|
+
const doc = source.ownerDocument;
|
|
569
|
+
const el = source.namespaceURI ? doc.createElementNS(source.namespaceURI, name) : doc.createElement(name);
|
|
570
|
+
for (const attr of Array.from(source.attributes)) {
|
|
571
|
+
if (attr.namespaceURI)
|
|
572
|
+
el.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
|
|
573
|
+
else
|
|
574
|
+
el.setAttribute(attr.name, attr.value);
|
|
575
|
+
}
|
|
576
|
+
for (const child of Array.from(source.childNodes)) {
|
|
577
|
+
el.appendChild(child.cloneNode(true));
|
|
578
|
+
}
|
|
579
|
+
return el;
|
|
580
|
+
}
|
|
493
581
|
function toIterable(value) {
|
|
494
582
|
if (value == null)
|
|
495
583
|
return [];
|
|
@@ -531,12 +619,13 @@ class ForBlock {
|
|
|
531
619
|
renderer;
|
|
532
620
|
conditional;
|
|
533
621
|
itemsExpr;
|
|
622
|
+
tagged;
|
|
534
623
|
rows = new Map;
|
|
535
624
|
order = [];
|
|
536
625
|
pendingItems = null;
|
|
537
626
|
running = false;
|
|
538
627
|
warnedDuplicates = false;
|
|
539
|
-
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "") {
|
|
628
|
+
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "", tagged = false) {
|
|
540
629
|
this.owner = owner;
|
|
541
630
|
this.anchor = anchor;
|
|
542
631
|
this.templates = templates;
|
|
@@ -547,22 +636,55 @@ class ForBlock {
|
|
|
547
636
|
this.renderer = renderer;
|
|
548
637
|
this.conditional = conditional;
|
|
549
638
|
this.itemsExpr = itemsExpr;
|
|
639
|
+
this.tagged = tagged;
|
|
550
640
|
}
|
|
641
|
+
retagged = new Map;
|
|
642
|
+
warnedTag = false;
|
|
551
643
|
toItems(value) {
|
|
552
644
|
if (this.conditional) {
|
|
553
645
|
return Number.isInteger(value) && value >= 0 ? [value] : [];
|
|
554
646
|
}
|
|
647
|
+
if (this.tagged)
|
|
648
|
+
return [this.tagFor(value)];
|
|
555
649
|
return toIterable(value);
|
|
556
650
|
}
|
|
557
651
|
templateFor(item) {
|
|
652
|
+
if (this.tagged) {
|
|
653
|
+
const source = this.templates[0];
|
|
654
|
+
if (item === source.localName)
|
|
655
|
+
return source;
|
|
656
|
+
let made = this.retagged.get(item);
|
|
657
|
+
if (!made) {
|
|
658
|
+
made = retag(source, item);
|
|
659
|
+
this.retagged.set(item, made);
|
|
660
|
+
}
|
|
661
|
+
return made;
|
|
662
|
+
}
|
|
558
663
|
return this.conditional ? this.templates[item] : this.templates[0];
|
|
559
664
|
}
|
|
665
|
+
tagFor(value) {
|
|
666
|
+
const written = this.templates[0].localName;
|
|
667
|
+
if (value == null || value === "")
|
|
668
|
+
return written;
|
|
669
|
+
const name = typeof value === "string" ? value.trim().toLowerCase() : null;
|
|
670
|
+
if (name && TAG_NAME.test(name) && !REFUSED_TAGS.has(name))
|
|
671
|
+
return name;
|
|
672
|
+
if (!this.warnedTag) {
|
|
673
|
+
this.warnedTag = true;
|
|
674
|
+
warn(`$tag="${this.itemsExpr}" gave ${JSON.stringify(value)}, which is not an element it can make - rendering <${written}>.`);
|
|
675
|
+
}
|
|
676
|
+
return written;
|
|
677
|
+
}
|
|
560
678
|
schedule(items) {
|
|
561
679
|
this.pendingItems = items;
|
|
562
680
|
if (this.running)
|
|
563
681
|
return;
|
|
564
682
|
this.running = true;
|
|
565
|
-
this.drain();
|
|
683
|
+
this.draining = this.drain();
|
|
684
|
+
}
|
|
685
|
+
draining = null;
|
|
686
|
+
settled() {
|
|
687
|
+
return this.draining ?? Promise.resolve();
|
|
566
688
|
}
|
|
567
689
|
async drain() {
|
|
568
690
|
try {
|
|
@@ -920,6 +1042,15 @@ class Mounter {
|
|
|
920
1042
|
this.createBlock(el, context, "if");
|
|
921
1043
|
return;
|
|
922
1044
|
}
|
|
1045
|
+
if (el.hasAttribute("data-bind-tag")) {
|
|
1046
|
+
if (el.tagName === "TEMPLATE") {
|
|
1047
|
+
warn("$tag on a <template> means nothing - it renders its contents, not itself.", el);
|
|
1048
|
+
el.removeAttribute("data-bind-tag");
|
|
1049
|
+
} else {
|
|
1050
|
+
this.createBlock(el, context, "tag");
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
923
1054
|
if (el.hasAttribute("data-bind-else") || el.hasAttribute("data-bind-else-if")) {
|
|
924
1055
|
warn("$else / $else-if must directly follow a $if or $else-if sibling", el);
|
|
925
1056
|
el.remove();
|
|
@@ -1065,12 +1196,22 @@ class Mounter {
|
|
|
1065
1196
|
i += consumed.templates - 1;
|
|
1066
1197
|
continue;
|
|
1067
1198
|
}
|
|
1199
|
+
if (el.hasAttribute("data-bind-tag") && el.tagName !== "TEMPLATE") {
|
|
1200
|
+
const consumed = this.adoptBlock(el, found, d, context, "tag");
|
|
1201
|
+
if (!consumed)
|
|
1202
|
+
return false;
|
|
1203
|
+
d += consumed.dom;
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1068
1206
|
if (el.tagName === "SLOT") {
|
|
1069
1207
|
if (!found[d] || !isSlotStart(found[d])) {
|
|
1070
|
-
|
|
1208
|
+
const fallback = significant(Array.from(el.childNodes));
|
|
1209
|
+
const countable = fallback.every((n) => n.nodeType === 3 || n.nodeType === 1 && !isBlockTemplate(n) && n.tagName !== "SLOT");
|
|
1210
|
+
const share = countable ? found.slice(d, d + fallback.length) : found.slice(d);
|
|
1211
|
+
if (!this.adoptNodes(fallback, share, context, markers)) {
|
|
1071
1212
|
return false;
|
|
1072
1213
|
}
|
|
1073
|
-
d
|
|
1214
|
+
d += share.length;
|
|
1074
1215
|
continue;
|
|
1075
1216
|
}
|
|
1076
1217
|
const name = found[d].data.slice(SLOT_MARK.length);
|
|
@@ -1200,18 +1341,19 @@ class Mounter {
|
|
|
1200
1341
|
const rawExpression = el.getAttribute(`data-bind-${kind}`) ?? "";
|
|
1201
1342
|
const branches = conditional ? this.gatherBranches(el) : [];
|
|
1202
1343
|
const expression = conditional ? composeBranchExpression(branches) : rawExpression;
|
|
1203
|
-
const parsed = conditional ? { alias: null, items: expression } : parseForExpression(rawExpression);
|
|
1344
|
+
const parsed = conditional || kind === "tag" ? { alias: null, items: expression } : parseForExpression(rawExpression);
|
|
1204
1345
|
if (!parsed) {
|
|
1205
1346
|
warn(`Could not parse $for="${expression}" - expected "item in items"`);
|
|
1206
1347
|
el.remove();
|
|
1207
1348
|
return null;
|
|
1208
1349
|
}
|
|
1209
|
-
if (
|
|
1350
|
+
if (kind === "for" && el.hasAttribute("data-bind-if")) {
|
|
1210
1351
|
warn(`$for and $if on the same element are not supported (${rawExpression}). Wrap one in an element.`);
|
|
1211
1352
|
}
|
|
1212
|
-
const keyExpr =
|
|
1353
|
+
const keyExpr = kind === "for" ? el.getAttribute("data-bind-key") : null;
|
|
1213
1354
|
el.removeAttribute(`data-bind-${kind}`);
|
|
1214
|
-
|
|
1355
|
+
if (kind === "for")
|
|
1356
|
+
el.removeAttribute("data-bind-key");
|
|
1215
1357
|
let anchor;
|
|
1216
1358
|
if (existingAnchor) {
|
|
1217
1359
|
anchor = existingAnchor;
|
|
@@ -1226,7 +1368,7 @@ class Mounter {
|
|
|
1226
1368
|
const compiled = this.bindings.compileExpression(keyExpr, params);
|
|
1227
1369
|
return compiled(context.owner.state, context.owner.props, this.cellsProxy(), this.bindings.pendingProbe(), ...locals);
|
|
1228
1370
|
} : null;
|
|
1229
|
-
const block = new ForBlock(context.owner, anchor, templates, parsed.alias, context.params, context.locals, keyFor, this, conditional, parsed.items);
|
|
1371
|
+
const block = new ForBlock(context.owner, anchor, templates, parsed.alias, context.params, context.locals, keyFor, this, conditional, parsed.items, kind === "tag");
|
|
1230
1372
|
context.holder.addBlock(block);
|
|
1231
1373
|
context.holder.addBinding(this.bindings.createForBinding(block, parsed.items, context.params));
|
|
1232
1374
|
return block;
|
|
@@ -1314,9 +1456,10 @@ class Mounter {
|
|
|
1314
1456
|
if (this.ssg) {
|
|
1315
1457
|
fragment.insertBefore(document.createComment(ROW_MARK), fragment.firstChild);
|
|
1316
1458
|
}
|
|
1317
|
-
row.nodes = Array.from(fragment.childNodes);
|
|
1318
1459
|
this.bindings.updateRow(block.owner, row);
|
|
1319
1460
|
row.instances.forEach((child) => this.bindings.updateTree(child));
|
|
1461
|
+
await Promise.all(row.blocks.map((nested) => nested.settled()));
|
|
1462
|
+
row.nodes = Array.from(fragment.childNodes);
|
|
1320
1463
|
return row;
|
|
1321
1464
|
}
|
|
1322
1465
|
connected() {
|
|
@@ -1722,7 +1865,10 @@ function isBlockAnchor(node) {
|
|
|
1722
1865
|
if (node.nodeType !== 8)
|
|
1723
1866
|
return false;
|
|
1724
1867
|
const data = node.data.trim();
|
|
1725
|
-
return data.startsWith("for:") || data.startsWith("if:");
|
|
1868
|
+
return data.startsWith("for:") || data.startsWith("if:") || data.startsWith("tag:");
|
|
1869
|
+
}
|
|
1870
|
+
function isBlockTemplate(el) {
|
|
1871
|
+
return el.hasAttribute("data-bind-for") || el.hasAttribute("data-bind-if") || el.hasAttribute("data-bind-tag");
|
|
1726
1872
|
}
|
|
1727
1873
|
function isInsignificant(node) {
|
|
1728
1874
|
return node.nodeType === 3 && !(node.textContent ?? "").trim();
|