@lightspeed-js/core 0.4.5 → 0.4.6
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 +60 -2
- package/dist/index.js +85 -9
- package/dist/ssg.js +85 -9
- package/dist/types/src/runtime/dom/ForBlock.d.ts +18 -1
- package/dist/types/test/tag.test.d.ts +1 -0
- package/dist/vite.js +85 -9
- package/package.json +1 -1
package/dist/compile.js
CHANGED
|
@@ -1133,6 +1133,30 @@ function isPlain(value) {
|
|
|
1133
1133
|
return proto === Object.prototype || proto === null;
|
|
1134
1134
|
}
|
|
1135
1135
|
// src/runtime/dom/ForBlock.ts
|
|
1136
|
+
var TAG_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
1137
|
+
var REFUSED_TAGS = new Set([
|
|
1138
|
+
"script",
|
|
1139
|
+
"style",
|
|
1140
|
+
"template",
|
|
1141
|
+
"slot",
|
|
1142
|
+
"iframe",
|
|
1143
|
+
"object",
|
|
1144
|
+
"embed"
|
|
1145
|
+
]);
|
|
1146
|
+
function retag(source, name) {
|
|
1147
|
+
const doc = source.ownerDocument;
|
|
1148
|
+
const el = source.namespaceURI ? doc.createElementNS(source.namespaceURI, name) : doc.createElement(name);
|
|
1149
|
+
for (const attr of Array.from(source.attributes)) {
|
|
1150
|
+
if (attr.namespaceURI)
|
|
1151
|
+
el.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
|
|
1152
|
+
else
|
|
1153
|
+
el.setAttribute(attr.name, attr.value);
|
|
1154
|
+
}
|
|
1155
|
+
for (const child of Array.from(source.childNodes)) {
|
|
1156
|
+
el.appendChild(child.cloneNode(true));
|
|
1157
|
+
}
|
|
1158
|
+
return el;
|
|
1159
|
+
}
|
|
1136
1160
|
function toIterable(value) {
|
|
1137
1161
|
if (value == null)
|
|
1138
1162
|
return [];
|
|
@@ -1174,12 +1198,13 @@ class ForBlock {
|
|
|
1174
1198
|
renderer;
|
|
1175
1199
|
conditional;
|
|
1176
1200
|
itemsExpr;
|
|
1201
|
+
tagged;
|
|
1177
1202
|
rows = new Map;
|
|
1178
1203
|
order = [];
|
|
1179
1204
|
pendingItems = null;
|
|
1180
1205
|
running = false;
|
|
1181
1206
|
warnedDuplicates = false;
|
|
1182
|
-
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "") {
|
|
1207
|
+
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "", tagged = false) {
|
|
1183
1208
|
this.owner = owner;
|
|
1184
1209
|
this.anchor = anchor;
|
|
1185
1210
|
this.templates = templates;
|
|
@@ -1190,22 +1215,55 @@ class ForBlock {
|
|
|
1190
1215
|
this.renderer = renderer;
|
|
1191
1216
|
this.conditional = conditional;
|
|
1192
1217
|
this.itemsExpr = itemsExpr;
|
|
1218
|
+
this.tagged = tagged;
|
|
1193
1219
|
}
|
|
1220
|
+
retagged = new Map;
|
|
1221
|
+
warnedTag = false;
|
|
1194
1222
|
toItems(value) {
|
|
1195
1223
|
if (this.conditional) {
|
|
1196
1224
|
return Number.isInteger(value) && value >= 0 ? [value] : [];
|
|
1197
1225
|
}
|
|
1226
|
+
if (this.tagged)
|
|
1227
|
+
return [this.tagFor(value)];
|
|
1198
1228
|
return toIterable(value);
|
|
1199
1229
|
}
|
|
1200
1230
|
templateFor(item) {
|
|
1231
|
+
if (this.tagged) {
|
|
1232
|
+
const source = this.templates[0];
|
|
1233
|
+
if (item === source.localName)
|
|
1234
|
+
return source;
|
|
1235
|
+
let made = this.retagged.get(item);
|
|
1236
|
+
if (!made) {
|
|
1237
|
+
made = retag(source, item);
|
|
1238
|
+
this.retagged.set(item, made);
|
|
1239
|
+
}
|
|
1240
|
+
return made;
|
|
1241
|
+
}
|
|
1201
1242
|
return this.conditional ? this.templates[item] : this.templates[0];
|
|
1202
1243
|
}
|
|
1244
|
+
tagFor(value) {
|
|
1245
|
+
const written = this.templates[0].localName;
|
|
1246
|
+
if (value == null || value === "")
|
|
1247
|
+
return written;
|
|
1248
|
+
const name = typeof value === "string" ? value.trim().toLowerCase() : null;
|
|
1249
|
+
if (name && TAG_NAME.test(name) && !REFUSED_TAGS.has(name))
|
|
1250
|
+
return name;
|
|
1251
|
+
if (!this.warnedTag) {
|
|
1252
|
+
this.warnedTag = true;
|
|
1253
|
+
warn(`$tag="${this.itemsExpr}" gave ${JSON.stringify(value)}, which is not an element it can make - rendering <${written}>.`);
|
|
1254
|
+
}
|
|
1255
|
+
return written;
|
|
1256
|
+
}
|
|
1203
1257
|
schedule(items) {
|
|
1204
1258
|
this.pendingItems = items;
|
|
1205
1259
|
if (this.running)
|
|
1206
1260
|
return;
|
|
1207
1261
|
this.running = true;
|
|
1208
|
-
this.drain();
|
|
1262
|
+
this.draining = this.drain();
|
|
1263
|
+
}
|
|
1264
|
+
draining = null;
|
|
1265
|
+
settled() {
|
|
1266
|
+
return this.draining ?? Promise.resolve();
|
|
1209
1267
|
}
|
|
1210
1268
|
async drain() {
|
|
1211
1269
|
try {
|
package/dist/index.js
CHANGED
|
@@ -1040,6 +1040,30 @@ class Row {
|
|
|
1040
1040
|
this.locals[this.locals.length - 1] = value;
|
|
1041
1041
|
}
|
|
1042
1042
|
}
|
|
1043
|
+
var TAG_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
1044
|
+
var REFUSED_TAGS = new Set([
|
|
1045
|
+
"script",
|
|
1046
|
+
"style",
|
|
1047
|
+
"template",
|
|
1048
|
+
"slot",
|
|
1049
|
+
"iframe",
|
|
1050
|
+
"object",
|
|
1051
|
+
"embed"
|
|
1052
|
+
]);
|
|
1053
|
+
function retag(source, name) {
|
|
1054
|
+
const doc = source.ownerDocument;
|
|
1055
|
+
const el = source.namespaceURI ? doc.createElementNS(source.namespaceURI, name) : doc.createElement(name);
|
|
1056
|
+
for (const attr of Array.from(source.attributes)) {
|
|
1057
|
+
if (attr.namespaceURI)
|
|
1058
|
+
el.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
|
|
1059
|
+
else
|
|
1060
|
+
el.setAttribute(attr.name, attr.value);
|
|
1061
|
+
}
|
|
1062
|
+
for (const child of Array.from(source.childNodes)) {
|
|
1063
|
+
el.appendChild(child.cloneNode(true));
|
|
1064
|
+
}
|
|
1065
|
+
return el;
|
|
1066
|
+
}
|
|
1043
1067
|
function toIterable(value) {
|
|
1044
1068
|
if (value == null)
|
|
1045
1069
|
return [];
|
|
@@ -1081,12 +1105,13 @@ class ForBlock {
|
|
|
1081
1105
|
renderer;
|
|
1082
1106
|
conditional;
|
|
1083
1107
|
itemsExpr;
|
|
1108
|
+
tagged;
|
|
1084
1109
|
rows = new Map;
|
|
1085
1110
|
order = [];
|
|
1086
1111
|
pendingItems = null;
|
|
1087
1112
|
running = false;
|
|
1088
1113
|
warnedDuplicates = false;
|
|
1089
|
-
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "") {
|
|
1114
|
+
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "", tagged = false) {
|
|
1090
1115
|
this.owner = owner;
|
|
1091
1116
|
this.anchor = anchor;
|
|
1092
1117
|
this.templates = templates;
|
|
@@ -1097,22 +1122,55 @@ class ForBlock {
|
|
|
1097
1122
|
this.renderer = renderer;
|
|
1098
1123
|
this.conditional = conditional;
|
|
1099
1124
|
this.itemsExpr = itemsExpr;
|
|
1125
|
+
this.tagged = tagged;
|
|
1100
1126
|
}
|
|
1127
|
+
retagged = new Map;
|
|
1128
|
+
warnedTag = false;
|
|
1101
1129
|
toItems(value) {
|
|
1102
1130
|
if (this.conditional) {
|
|
1103
1131
|
return Number.isInteger(value) && value >= 0 ? [value] : [];
|
|
1104
1132
|
}
|
|
1133
|
+
if (this.tagged)
|
|
1134
|
+
return [this.tagFor(value)];
|
|
1105
1135
|
return toIterable(value);
|
|
1106
1136
|
}
|
|
1107
1137
|
templateFor(item) {
|
|
1138
|
+
if (this.tagged) {
|
|
1139
|
+
const source = this.templates[0];
|
|
1140
|
+
if (item === source.localName)
|
|
1141
|
+
return source;
|
|
1142
|
+
let made = this.retagged.get(item);
|
|
1143
|
+
if (!made) {
|
|
1144
|
+
made = retag(source, item);
|
|
1145
|
+
this.retagged.set(item, made);
|
|
1146
|
+
}
|
|
1147
|
+
return made;
|
|
1148
|
+
}
|
|
1108
1149
|
return this.conditional ? this.templates[item] : this.templates[0];
|
|
1109
1150
|
}
|
|
1151
|
+
tagFor(value) {
|
|
1152
|
+
const written = this.templates[0].localName;
|
|
1153
|
+
if (value == null || value === "")
|
|
1154
|
+
return written;
|
|
1155
|
+
const name = typeof value === "string" ? value.trim().toLowerCase() : null;
|
|
1156
|
+
if (name && TAG_NAME.test(name) && !REFUSED_TAGS.has(name))
|
|
1157
|
+
return name;
|
|
1158
|
+
if (!this.warnedTag) {
|
|
1159
|
+
this.warnedTag = true;
|
|
1160
|
+
warn(`$tag="${this.itemsExpr}" gave ${JSON.stringify(value)}, which is not an element it can make - rendering <${written}>.`);
|
|
1161
|
+
}
|
|
1162
|
+
return written;
|
|
1163
|
+
}
|
|
1110
1164
|
schedule(items) {
|
|
1111
1165
|
this.pendingItems = items;
|
|
1112
1166
|
if (this.running)
|
|
1113
1167
|
return;
|
|
1114
1168
|
this.running = true;
|
|
1115
|
-
this.drain();
|
|
1169
|
+
this.draining = this.drain();
|
|
1170
|
+
}
|
|
1171
|
+
draining = null;
|
|
1172
|
+
settled() {
|
|
1173
|
+
return this.draining ?? Promise.resolve();
|
|
1116
1174
|
}
|
|
1117
1175
|
async drain() {
|
|
1118
1176
|
try {
|
|
@@ -1466,6 +1524,15 @@ class Mounter {
|
|
|
1466
1524
|
this.createBlock(el, context, "if");
|
|
1467
1525
|
return;
|
|
1468
1526
|
}
|
|
1527
|
+
if (el.hasAttribute("data-bind-tag")) {
|
|
1528
|
+
if (el.tagName === "TEMPLATE") {
|
|
1529
|
+
warn("$tag on a <template> means nothing - it renders its contents, not itself.", el);
|
|
1530
|
+
el.removeAttribute("data-bind-tag");
|
|
1531
|
+
} else {
|
|
1532
|
+
this.createBlock(el, context, "tag");
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1469
1536
|
if (el.hasAttribute("data-bind-else") || el.hasAttribute("data-bind-else-if")) {
|
|
1470
1537
|
warn("$else / $else-if must directly follow a $if or $else-if sibling", el);
|
|
1471
1538
|
el.remove();
|
|
@@ -1611,6 +1678,13 @@ class Mounter {
|
|
|
1611
1678
|
i += consumed.templates - 1;
|
|
1612
1679
|
continue;
|
|
1613
1680
|
}
|
|
1681
|
+
if (el.hasAttribute("data-bind-tag") && el.tagName !== "TEMPLATE") {
|
|
1682
|
+
const consumed = this.adoptBlock(el, found, d, context, "tag");
|
|
1683
|
+
if (!consumed)
|
|
1684
|
+
return false;
|
|
1685
|
+
d += consumed.dom;
|
|
1686
|
+
continue;
|
|
1687
|
+
}
|
|
1614
1688
|
if (el.tagName === "SLOT") {
|
|
1615
1689
|
if (!found[d] || !isSlotStart(found[d])) {
|
|
1616
1690
|
if (!this.adoptNodes(Array.from(el.childNodes), found.slice(d), context, markers)) {
|
|
@@ -1746,18 +1820,19 @@ class Mounter {
|
|
|
1746
1820
|
const rawExpression = el.getAttribute(`data-bind-${kind}`) ?? "";
|
|
1747
1821
|
const branches = conditional ? this.gatherBranches(el) : [];
|
|
1748
1822
|
const expression = conditional ? composeBranchExpression(branches) : rawExpression;
|
|
1749
|
-
const parsed = conditional ? { alias: null, items: expression } : parseForExpression(rawExpression);
|
|
1823
|
+
const parsed = conditional || kind === "tag" ? { alias: null, items: expression } : parseForExpression(rawExpression);
|
|
1750
1824
|
if (!parsed) {
|
|
1751
1825
|
warn(`Could not parse $for="${expression}" - expected "item in items"`);
|
|
1752
1826
|
el.remove();
|
|
1753
1827
|
return null;
|
|
1754
1828
|
}
|
|
1755
|
-
if (
|
|
1829
|
+
if (kind === "for" && el.hasAttribute("data-bind-if")) {
|
|
1756
1830
|
warn(`$for and $if on the same element are not supported (${rawExpression}). Wrap one in an element.`);
|
|
1757
1831
|
}
|
|
1758
|
-
const keyExpr =
|
|
1832
|
+
const keyExpr = kind === "for" ? el.getAttribute("data-bind-key") : null;
|
|
1759
1833
|
el.removeAttribute(`data-bind-${kind}`);
|
|
1760
|
-
|
|
1834
|
+
if (kind === "for")
|
|
1835
|
+
el.removeAttribute("data-bind-key");
|
|
1761
1836
|
let anchor;
|
|
1762
1837
|
if (existingAnchor) {
|
|
1763
1838
|
anchor = existingAnchor;
|
|
@@ -1772,7 +1847,7 @@ class Mounter {
|
|
|
1772
1847
|
const compiled = this.bindings.compileExpression(keyExpr, params);
|
|
1773
1848
|
return compiled(context.owner.state, context.owner.props, this.cellsProxy(), this.bindings.pendingProbe(), ...locals);
|
|
1774
1849
|
} : null;
|
|
1775
|
-
const block = new ForBlock(context.owner, anchor, templates, parsed.alias, context.params, context.locals, keyFor, this, conditional, parsed.items);
|
|
1850
|
+
const block = new ForBlock(context.owner, anchor, templates, parsed.alias, context.params, context.locals, keyFor, this, conditional, parsed.items, kind === "tag");
|
|
1776
1851
|
context.holder.addBlock(block);
|
|
1777
1852
|
context.holder.addBinding(this.bindings.createForBinding(block, parsed.items, context.params));
|
|
1778
1853
|
return block;
|
|
@@ -1860,9 +1935,10 @@ class Mounter {
|
|
|
1860
1935
|
if (this.ssg) {
|
|
1861
1936
|
fragment.insertBefore(document.createComment(ROW_MARK), fragment.firstChild);
|
|
1862
1937
|
}
|
|
1863
|
-
row.nodes = Array.from(fragment.childNodes);
|
|
1864
1938
|
this.bindings.updateRow(block.owner, row);
|
|
1865
1939
|
row.instances.forEach((child) => this.bindings.updateTree(child));
|
|
1940
|
+
await Promise.all(row.blocks.map((nested) => nested.settled()));
|
|
1941
|
+
row.nodes = Array.from(fragment.childNodes);
|
|
1866
1942
|
return row;
|
|
1867
1943
|
}
|
|
1868
1944
|
connected() {
|
|
@@ -2268,7 +2344,7 @@ function isBlockAnchor(node) {
|
|
|
2268
2344
|
if (node.nodeType !== 8)
|
|
2269
2345
|
return false;
|
|
2270
2346
|
const data = node.data.trim();
|
|
2271
|
-
return data.startsWith("for:") || data.startsWith("if:");
|
|
2347
|
+
return data.startsWith("for:") || data.startsWith("if:") || data.startsWith("tag:");
|
|
2272
2348
|
}
|
|
2273
2349
|
function isInsignificant(node) {
|
|
2274
2350
|
return node.nodeType === 3 && !(node.textContent ?? "").trim();
|
package/dist/ssg.js
CHANGED
|
@@ -1059,6 +1059,30 @@ class Row {
|
|
|
1059
1059
|
this.locals[this.locals.length - 1] = value;
|
|
1060
1060
|
}
|
|
1061
1061
|
}
|
|
1062
|
+
var TAG_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
1063
|
+
var REFUSED_TAGS = new Set([
|
|
1064
|
+
"script",
|
|
1065
|
+
"style",
|
|
1066
|
+
"template",
|
|
1067
|
+
"slot",
|
|
1068
|
+
"iframe",
|
|
1069
|
+
"object",
|
|
1070
|
+
"embed"
|
|
1071
|
+
]);
|
|
1072
|
+
function retag(source, name) {
|
|
1073
|
+
const doc = source.ownerDocument;
|
|
1074
|
+
const el = source.namespaceURI ? doc.createElementNS(source.namespaceURI, name) : doc.createElement(name);
|
|
1075
|
+
for (const attr of Array.from(source.attributes)) {
|
|
1076
|
+
if (attr.namespaceURI)
|
|
1077
|
+
el.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
|
|
1078
|
+
else
|
|
1079
|
+
el.setAttribute(attr.name, attr.value);
|
|
1080
|
+
}
|
|
1081
|
+
for (const child of Array.from(source.childNodes)) {
|
|
1082
|
+
el.appendChild(child.cloneNode(true));
|
|
1083
|
+
}
|
|
1084
|
+
return el;
|
|
1085
|
+
}
|
|
1062
1086
|
function toIterable(value) {
|
|
1063
1087
|
if (value == null)
|
|
1064
1088
|
return [];
|
|
@@ -1100,12 +1124,13 @@ class ForBlock {
|
|
|
1100
1124
|
renderer;
|
|
1101
1125
|
conditional;
|
|
1102
1126
|
itemsExpr;
|
|
1127
|
+
tagged;
|
|
1103
1128
|
rows = new Map;
|
|
1104
1129
|
order = [];
|
|
1105
1130
|
pendingItems = null;
|
|
1106
1131
|
running = false;
|
|
1107
1132
|
warnedDuplicates = false;
|
|
1108
|
-
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "") {
|
|
1133
|
+
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "", tagged = false) {
|
|
1109
1134
|
this.owner = owner;
|
|
1110
1135
|
this.anchor = anchor;
|
|
1111
1136
|
this.templates = templates;
|
|
@@ -1116,22 +1141,55 @@ class ForBlock {
|
|
|
1116
1141
|
this.renderer = renderer;
|
|
1117
1142
|
this.conditional = conditional;
|
|
1118
1143
|
this.itemsExpr = itemsExpr;
|
|
1144
|
+
this.tagged = tagged;
|
|
1119
1145
|
}
|
|
1146
|
+
retagged = new Map;
|
|
1147
|
+
warnedTag = false;
|
|
1120
1148
|
toItems(value) {
|
|
1121
1149
|
if (this.conditional) {
|
|
1122
1150
|
return Number.isInteger(value) && value >= 0 ? [value] : [];
|
|
1123
1151
|
}
|
|
1152
|
+
if (this.tagged)
|
|
1153
|
+
return [this.tagFor(value)];
|
|
1124
1154
|
return toIterable(value);
|
|
1125
1155
|
}
|
|
1126
1156
|
templateFor(item) {
|
|
1157
|
+
if (this.tagged) {
|
|
1158
|
+
const source = this.templates[0];
|
|
1159
|
+
if (item === source.localName)
|
|
1160
|
+
return source;
|
|
1161
|
+
let made = this.retagged.get(item);
|
|
1162
|
+
if (!made) {
|
|
1163
|
+
made = retag(source, item);
|
|
1164
|
+
this.retagged.set(item, made);
|
|
1165
|
+
}
|
|
1166
|
+
return made;
|
|
1167
|
+
}
|
|
1127
1168
|
return this.conditional ? this.templates[item] : this.templates[0];
|
|
1128
1169
|
}
|
|
1170
|
+
tagFor(value) {
|
|
1171
|
+
const written = this.templates[0].localName;
|
|
1172
|
+
if (value == null || value === "")
|
|
1173
|
+
return written;
|
|
1174
|
+
const name = typeof value === "string" ? value.trim().toLowerCase() : null;
|
|
1175
|
+
if (name && TAG_NAME.test(name) && !REFUSED_TAGS.has(name))
|
|
1176
|
+
return name;
|
|
1177
|
+
if (!this.warnedTag) {
|
|
1178
|
+
this.warnedTag = true;
|
|
1179
|
+
warn(`$tag="${this.itemsExpr}" gave ${JSON.stringify(value)}, which is not an element it can make - rendering <${written}>.`);
|
|
1180
|
+
}
|
|
1181
|
+
return written;
|
|
1182
|
+
}
|
|
1129
1183
|
schedule(items) {
|
|
1130
1184
|
this.pendingItems = items;
|
|
1131
1185
|
if (this.running)
|
|
1132
1186
|
return;
|
|
1133
1187
|
this.running = true;
|
|
1134
|
-
this.drain();
|
|
1188
|
+
this.draining = this.drain();
|
|
1189
|
+
}
|
|
1190
|
+
draining = null;
|
|
1191
|
+
settled() {
|
|
1192
|
+
return this.draining ?? Promise.resolve();
|
|
1135
1193
|
}
|
|
1136
1194
|
async drain() {
|
|
1137
1195
|
try {
|
|
@@ -1489,6 +1547,15 @@ class Mounter {
|
|
|
1489
1547
|
this.createBlock(el, context, "if");
|
|
1490
1548
|
return;
|
|
1491
1549
|
}
|
|
1550
|
+
if (el.hasAttribute("data-bind-tag")) {
|
|
1551
|
+
if (el.tagName === "TEMPLATE") {
|
|
1552
|
+
warn("$tag on a <template> means nothing - it renders its contents, not itself.", el);
|
|
1553
|
+
el.removeAttribute("data-bind-tag");
|
|
1554
|
+
} else {
|
|
1555
|
+
this.createBlock(el, context, "tag");
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1492
1559
|
if (el.hasAttribute("data-bind-else") || el.hasAttribute("data-bind-else-if")) {
|
|
1493
1560
|
warn("$else / $else-if must directly follow a $if or $else-if sibling", el);
|
|
1494
1561
|
el.remove();
|
|
@@ -1634,6 +1701,13 @@ class Mounter {
|
|
|
1634
1701
|
i += consumed.templates - 1;
|
|
1635
1702
|
continue;
|
|
1636
1703
|
}
|
|
1704
|
+
if (el.hasAttribute("data-bind-tag") && el.tagName !== "TEMPLATE") {
|
|
1705
|
+
const consumed = this.adoptBlock(el, found, d, context, "tag");
|
|
1706
|
+
if (!consumed)
|
|
1707
|
+
return false;
|
|
1708
|
+
d += consumed.dom;
|
|
1709
|
+
continue;
|
|
1710
|
+
}
|
|
1637
1711
|
if (el.tagName === "SLOT") {
|
|
1638
1712
|
if (!found[d] || !isSlotStart(found[d])) {
|
|
1639
1713
|
if (!this.adoptNodes(Array.from(el.childNodes), found.slice(d), context, markers)) {
|
|
@@ -1769,18 +1843,19 @@ class Mounter {
|
|
|
1769
1843
|
const rawExpression = el.getAttribute(`data-bind-${kind}`) ?? "";
|
|
1770
1844
|
const branches = conditional ? this.gatherBranches(el) : [];
|
|
1771
1845
|
const expression = conditional ? composeBranchExpression(branches) : rawExpression;
|
|
1772
|
-
const parsed = conditional ? { alias: null, items: expression } : parseForExpression(rawExpression);
|
|
1846
|
+
const parsed = conditional || kind === "tag" ? { alias: null, items: expression } : parseForExpression(rawExpression);
|
|
1773
1847
|
if (!parsed) {
|
|
1774
1848
|
warn(`Could not parse $for="${expression}" - expected "item in items"`);
|
|
1775
1849
|
el.remove();
|
|
1776
1850
|
return null;
|
|
1777
1851
|
}
|
|
1778
|
-
if (
|
|
1852
|
+
if (kind === "for" && el.hasAttribute("data-bind-if")) {
|
|
1779
1853
|
warn(`$for and $if on the same element are not supported (${rawExpression}). Wrap one in an element.`);
|
|
1780
1854
|
}
|
|
1781
|
-
const keyExpr =
|
|
1855
|
+
const keyExpr = kind === "for" ? el.getAttribute("data-bind-key") : null;
|
|
1782
1856
|
el.removeAttribute(`data-bind-${kind}`);
|
|
1783
|
-
|
|
1857
|
+
if (kind === "for")
|
|
1858
|
+
el.removeAttribute("data-bind-key");
|
|
1784
1859
|
let anchor;
|
|
1785
1860
|
if (existingAnchor) {
|
|
1786
1861
|
anchor = existingAnchor;
|
|
@@ -1795,7 +1870,7 @@ class Mounter {
|
|
|
1795
1870
|
const compiled = this.bindings.compileExpression(keyExpr, params);
|
|
1796
1871
|
return compiled(context.owner.state, context.owner.props, this.cellsProxy(), this.bindings.pendingProbe(), ...locals);
|
|
1797
1872
|
} : null;
|
|
1798
|
-
const block = new ForBlock(context.owner, anchor, templates, parsed.alias, context.params, context.locals, keyFor, this, conditional, parsed.items);
|
|
1873
|
+
const block = new ForBlock(context.owner, anchor, templates, parsed.alias, context.params, context.locals, keyFor, this, conditional, parsed.items, kind === "tag");
|
|
1799
1874
|
context.holder.addBlock(block);
|
|
1800
1875
|
context.holder.addBinding(this.bindings.createForBinding(block, parsed.items, context.params));
|
|
1801
1876
|
return block;
|
|
@@ -1883,9 +1958,10 @@ class Mounter {
|
|
|
1883
1958
|
if (this.ssg) {
|
|
1884
1959
|
fragment.insertBefore(document.createComment(ROW_MARK), fragment.firstChild);
|
|
1885
1960
|
}
|
|
1886
|
-
row.nodes = Array.from(fragment.childNodes);
|
|
1887
1961
|
this.bindings.updateRow(block.owner, row);
|
|
1888
1962
|
row.instances.forEach((child) => this.bindings.updateTree(child));
|
|
1963
|
+
await Promise.all(row.blocks.map((nested) => nested.settled()));
|
|
1964
|
+
row.nodes = Array.from(fragment.childNodes);
|
|
1889
1965
|
return row;
|
|
1890
1966
|
}
|
|
1891
1967
|
connected() {
|
|
@@ -2291,7 +2367,7 @@ function isBlockAnchor(node) {
|
|
|
2291
2367
|
if (node.nodeType !== 8)
|
|
2292
2368
|
return false;
|
|
2293
2369
|
const data = node.data.trim();
|
|
2294
|
-
return data.startsWith("for:") || data.startsWith("if:");
|
|
2370
|
+
return data.startsWith("for:") || data.startsWith("if:") || data.startsWith("tag:");
|
|
2295
2371
|
}
|
|
2296
2372
|
function isInsignificant(node) {
|
|
2297
2373
|
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 {};
|
package/dist/vite.js
CHANGED
|
@@ -490,6 +490,30 @@ class Row {
|
|
|
490
490
|
this.locals[this.locals.length - 1] = value;
|
|
491
491
|
}
|
|
492
492
|
}
|
|
493
|
+
var TAG_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
494
|
+
var REFUSED_TAGS = new Set([
|
|
495
|
+
"script",
|
|
496
|
+
"style",
|
|
497
|
+
"template",
|
|
498
|
+
"slot",
|
|
499
|
+
"iframe",
|
|
500
|
+
"object",
|
|
501
|
+
"embed"
|
|
502
|
+
]);
|
|
503
|
+
function retag(source, name) {
|
|
504
|
+
const doc = source.ownerDocument;
|
|
505
|
+
const el = source.namespaceURI ? doc.createElementNS(source.namespaceURI, name) : doc.createElement(name);
|
|
506
|
+
for (const attr of Array.from(source.attributes)) {
|
|
507
|
+
if (attr.namespaceURI)
|
|
508
|
+
el.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
|
|
509
|
+
else
|
|
510
|
+
el.setAttribute(attr.name, attr.value);
|
|
511
|
+
}
|
|
512
|
+
for (const child of Array.from(source.childNodes)) {
|
|
513
|
+
el.appendChild(child.cloneNode(true));
|
|
514
|
+
}
|
|
515
|
+
return el;
|
|
516
|
+
}
|
|
493
517
|
function toIterable(value) {
|
|
494
518
|
if (value == null)
|
|
495
519
|
return [];
|
|
@@ -531,12 +555,13 @@ class ForBlock {
|
|
|
531
555
|
renderer;
|
|
532
556
|
conditional;
|
|
533
557
|
itemsExpr;
|
|
558
|
+
tagged;
|
|
534
559
|
rows = new Map;
|
|
535
560
|
order = [];
|
|
536
561
|
pendingItems = null;
|
|
537
562
|
running = false;
|
|
538
563
|
warnedDuplicates = false;
|
|
539
|
-
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "") {
|
|
564
|
+
constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "", tagged = false) {
|
|
540
565
|
this.owner = owner;
|
|
541
566
|
this.anchor = anchor;
|
|
542
567
|
this.templates = templates;
|
|
@@ -547,22 +572,55 @@ class ForBlock {
|
|
|
547
572
|
this.renderer = renderer;
|
|
548
573
|
this.conditional = conditional;
|
|
549
574
|
this.itemsExpr = itemsExpr;
|
|
575
|
+
this.tagged = tagged;
|
|
550
576
|
}
|
|
577
|
+
retagged = new Map;
|
|
578
|
+
warnedTag = false;
|
|
551
579
|
toItems(value) {
|
|
552
580
|
if (this.conditional) {
|
|
553
581
|
return Number.isInteger(value) && value >= 0 ? [value] : [];
|
|
554
582
|
}
|
|
583
|
+
if (this.tagged)
|
|
584
|
+
return [this.tagFor(value)];
|
|
555
585
|
return toIterable(value);
|
|
556
586
|
}
|
|
557
587
|
templateFor(item) {
|
|
588
|
+
if (this.tagged) {
|
|
589
|
+
const source = this.templates[0];
|
|
590
|
+
if (item === source.localName)
|
|
591
|
+
return source;
|
|
592
|
+
let made = this.retagged.get(item);
|
|
593
|
+
if (!made) {
|
|
594
|
+
made = retag(source, item);
|
|
595
|
+
this.retagged.set(item, made);
|
|
596
|
+
}
|
|
597
|
+
return made;
|
|
598
|
+
}
|
|
558
599
|
return this.conditional ? this.templates[item] : this.templates[0];
|
|
559
600
|
}
|
|
601
|
+
tagFor(value) {
|
|
602
|
+
const written = this.templates[0].localName;
|
|
603
|
+
if (value == null || value === "")
|
|
604
|
+
return written;
|
|
605
|
+
const name = typeof value === "string" ? value.trim().toLowerCase() : null;
|
|
606
|
+
if (name && TAG_NAME.test(name) && !REFUSED_TAGS.has(name))
|
|
607
|
+
return name;
|
|
608
|
+
if (!this.warnedTag) {
|
|
609
|
+
this.warnedTag = true;
|
|
610
|
+
warn(`$tag="${this.itemsExpr}" gave ${JSON.stringify(value)}, which is not an element it can make - rendering <${written}>.`);
|
|
611
|
+
}
|
|
612
|
+
return written;
|
|
613
|
+
}
|
|
560
614
|
schedule(items) {
|
|
561
615
|
this.pendingItems = items;
|
|
562
616
|
if (this.running)
|
|
563
617
|
return;
|
|
564
618
|
this.running = true;
|
|
565
|
-
this.drain();
|
|
619
|
+
this.draining = this.drain();
|
|
620
|
+
}
|
|
621
|
+
draining = null;
|
|
622
|
+
settled() {
|
|
623
|
+
return this.draining ?? Promise.resolve();
|
|
566
624
|
}
|
|
567
625
|
async drain() {
|
|
568
626
|
try {
|
|
@@ -920,6 +978,15 @@ class Mounter {
|
|
|
920
978
|
this.createBlock(el, context, "if");
|
|
921
979
|
return;
|
|
922
980
|
}
|
|
981
|
+
if (el.hasAttribute("data-bind-tag")) {
|
|
982
|
+
if (el.tagName === "TEMPLATE") {
|
|
983
|
+
warn("$tag on a <template> means nothing - it renders its contents, not itself.", el);
|
|
984
|
+
el.removeAttribute("data-bind-tag");
|
|
985
|
+
} else {
|
|
986
|
+
this.createBlock(el, context, "tag");
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
923
990
|
if (el.hasAttribute("data-bind-else") || el.hasAttribute("data-bind-else-if")) {
|
|
924
991
|
warn("$else / $else-if must directly follow a $if or $else-if sibling", el);
|
|
925
992
|
el.remove();
|
|
@@ -1065,6 +1132,13 @@ class Mounter {
|
|
|
1065
1132
|
i += consumed.templates - 1;
|
|
1066
1133
|
continue;
|
|
1067
1134
|
}
|
|
1135
|
+
if (el.hasAttribute("data-bind-tag") && el.tagName !== "TEMPLATE") {
|
|
1136
|
+
const consumed = this.adoptBlock(el, found, d, context, "tag");
|
|
1137
|
+
if (!consumed)
|
|
1138
|
+
return false;
|
|
1139
|
+
d += consumed.dom;
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1068
1142
|
if (el.tagName === "SLOT") {
|
|
1069
1143
|
if (!found[d] || !isSlotStart(found[d])) {
|
|
1070
1144
|
if (!this.adoptNodes(Array.from(el.childNodes), found.slice(d), context, markers)) {
|
|
@@ -1200,18 +1274,19 @@ class Mounter {
|
|
|
1200
1274
|
const rawExpression = el.getAttribute(`data-bind-${kind}`) ?? "";
|
|
1201
1275
|
const branches = conditional ? this.gatherBranches(el) : [];
|
|
1202
1276
|
const expression = conditional ? composeBranchExpression(branches) : rawExpression;
|
|
1203
|
-
const parsed = conditional ? { alias: null, items: expression } : parseForExpression(rawExpression);
|
|
1277
|
+
const parsed = conditional || kind === "tag" ? { alias: null, items: expression } : parseForExpression(rawExpression);
|
|
1204
1278
|
if (!parsed) {
|
|
1205
1279
|
warn(`Could not parse $for="${expression}" - expected "item in items"`);
|
|
1206
1280
|
el.remove();
|
|
1207
1281
|
return null;
|
|
1208
1282
|
}
|
|
1209
|
-
if (
|
|
1283
|
+
if (kind === "for" && el.hasAttribute("data-bind-if")) {
|
|
1210
1284
|
warn(`$for and $if on the same element are not supported (${rawExpression}). Wrap one in an element.`);
|
|
1211
1285
|
}
|
|
1212
|
-
const keyExpr =
|
|
1286
|
+
const keyExpr = kind === "for" ? el.getAttribute("data-bind-key") : null;
|
|
1213
1287
|
el.removeAttribute(`data-bind-${kind}`);
|
|
1214
|
-
|
|
1288
|
+
if (kind === "for")
|
|
1289
|
+
el.removeAttribute("data-bind-key");
|
|
1215
1290
|
let anchor;
|
|
1216
1291
|
if (existingAnchor) {
|
|
1217
1292
|
anchor = existingAnchor;
|
|
@@ -1226,7 +1301,7 @@ class Mounter {
|
|
|
1226
1301
|
const compiled = this.bindings.compileExpression(keyExpr, params);
|
|
1227
1302
|
return compiled(context.owner.state, context.owner.props, this.cellsProxy(), this.bindings.pendingProbe(), ...locals);
|
|
1228
1303
|
} : null;
|
|
1229
|
-
const block = new ForBlock(context.owner, anchor, templates, parsed.alias, context.params, context.locals, keyFor, this, conditional, parsed.items);
|
|
1304
|
+
const block = new ForBlock(context.owner, anchor, templates, parsed.alias, context.params, context.locals, keyFor, this, conditional, parsed.items, kind === "tag");
|
|
1230
1305
|
context.holder.addBlock(block);
|
|
1231
1306
|
context.holder.addBinding(this.bindings.createForBinding(block, parsed.items, context.params));
|
|
1232
1307
|
return block;
|
|
@@ -1314,9 +1389,10 @@ class Mounter {
|
|
|
1314
1389
|
if (this.ssg) {
|
|
1315
1390
|
fragment.insertBefore(document.createComment(ROW_MARK), fragment.firstChild);
|
|
1316
1391
|
}
|
|
1317
|
-
row.nodes = Array.from(fragment.childNodes);
|
|
1318
1392
|
this.bindings.updateRow(block.owner, row);
|
|
1319
1393
|
row.instances.forEach((child) => this.bindings.updateTree(child));
|
|
1394
|
+
await Promise.all(row.blocks.map((nested) => nested.settled()));
|
|
1395
|
+
row.nodes = Array.from(fragment.childNodes);
|
|
1320
1396
|
return row;
|
|
1321
1397
|
}
|
|
1322
1398
|
connected() {
|
|
@@ -1722,7 +1798,7 @@ function isBlockAnchor(node) {
|
|
|
1722
1798
|
if (node.nodeType !== 8)
|
|
1723
1799
|
return false;
|
|
1724
1800
|
const data = node.data.trim();
|
|
1725
|
-
return data.startsWith("for:") || data.startsWith("if:");
|
|
1801
|
+
return data.startsWith("for:") || data.startsWith("if:") || data.startsWith("tag:");
|
|
1726
1802
|
}
|
|
1727
1803
|
function isInsignificant(node) {
|
|
1728
1804
|
return node.nodeType === 3 && !(node.textContent ?? "").trim();
|