@loadbare/app 0.22.0 → 0.24.0
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/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +28 -1
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/build.d.ts +16 -0
- package/dist/build/build.d.ts.map +1 -1
- package/dist/build/build.js +77 -5
- package/dist/build/build.js.map +1 -1
- package/dist/core/lb-constants.d.ts +8 -5
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +20 -11
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +19 -3
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +35 -5
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +18 -22
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +127 -57
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +15 -22
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-server.d.ts +2 -2
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +29 -9
- package/dist/server/lb-server.js.map +1 -1
- package/docs/comparison.md +11 -11
- package/docs/miswarts.md +7 -5
- package/docs/reference/chrome.md +42 -0
- package/docs/reference/custom-elements.md +9 -4
- package/docs/reference/data-binding.md +89 -67
- package/docs/reference/dev-server.md +10 -3
- package/docs/reference/names.md +10 -7
- package/docs/reference/page-files.md +8 -4
- package/docs/temp-proposals.md +251 -0
- package/docs/testing.md +3 -3
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +24 -12
- package/skills/loadbare-app/references/chrome.md +42 -0
- package/skills/loadbare-app/references/custom-elements.md +9 -4
- package/skills/loadbare-app/references/data-binding.md +89 -67
- package/skills/loadbare-app/references/dev-server.md +10 -3
- package/skills/loadbare-app/references/miswarts.md +7 -5
- package/skills/loadbare-app/references/names.md +10 -7
- package/skills/loadbare-app/references/page-files.md +8 -4
package/dist/hub/lb-apply.js
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* landing reaches in there as it reaches everywhere else, so a branch that
|
|
39
39
|
* returns is already current. Nothing but landing reaches in.
|
|
40
40
|
*/
|
|
41
|
-
import { AGGREGATES, ATTR_COLUMN, ATTR_FILTER, ATTR_ORDER, CHOICES_QUERY, LB_RESERVED_PREFIX, ATTR_COLUMN_VALUE, ATTR_COUNT, ATTR_GROUP, ATTR_GROUP_COLUMN, ATTR_GROUP_LEAVING, ATTR_GROUP_LIVE, ATTR_GROUP_VALUE, ATTR_KEY_VALUE, ATTR_MAX, ATTR_MIN, ATTR_QUERY, ATTR_QUERY_ORDER, ATTR_QUERY_ROW_COUNT,
|
|
41
|
+
import { AGGREGATES, ATTR_COLUMN, ATTR_FILTER, ATTR_ORDER, CHOICES_QUERY, LB_RESERVED_PREFIX, ATTR_COLUMN_VALUE, ATTR_COUNT, ATTR_GROUP, ATTR_GROUP_COLUMN, ATTR_GROUP_LEAVING, ATTR_GROUP_LIVE, ATTR_GROUP_VALUE, ATTR_KEY_VALUE, ATTR_MAX, ATTR_MIN, ATTR_QUERY, ATTR_QUERY_ORDER, ATTR_QUERY_ROW_COUNT, ATTR_MANUAL, MANUAL_FILTER, MANUAL_ORDER, ATTR_QUERY_UNPLACED_COUNT, ATTR_ROW_CHANGED, ATTR_ROW_CREATED, ATTR_ROW_LEAVING, ATTR_ROW_LIVE, ATTR_ROW_MOVED, ATTR_ROW_REQUESTED, ATTR_ROW_UNPLACED, ATTR_SHOW, ATTR_SUM, ATTR_URL_PARM, KIND_ROW, LIVE_GROUP, LIVE_ROW, ORDER_DESCENDING, REQUESTED_CHANGED, REQUESTED_CREATED, REQUESTED_MOVED, SHOW_NOT, ATTR_SELECT, ATTR_ROW_SELECTED, } from "../core/lb-constants.js";
|
|
42
42
|
import { compareRows, compareValues, isPatch, parseFilter, parseOrder, passes, readAs, } from "../core/lb-types.js";
|
|
43
43
|
import { attachPlaced, detachRemoved, LEAVING, PARKED, placed, upgradeAbsent, } from "./lb-lifecycle.js";
|
|
44
44
|
/** A DocumentFragment's nodeType. Tier 3 has no `Node` global to read it from. */
|
|
@@ -100,23 +100,58 @@ export function isFormAssociated(el) {
|
|
|
100
100
|
export function isControl(el) {
|
|
101
101
|
return (isNativeControl(el) || isCheckbox(el) || isRadio(el) || isFormAssociated(el));
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* A control that holds several values: a `<select multiple>`, or a checkbox
|
|
105
|
+
* carrying a `value`, one of a set naming one column. A checkbox with no
|
|
106
|
+
* `value` is a flag.
|
|
107
|
+
*/
|
|
108
|
+
function holdsSeveral(el) {
|
|
109
|
+
if (el.localName === "select")
|
|
110
|
+
return el.multiple;
|
|
111
|
+
return isCheckbox(el) && el.hasAttribute("value");
|
|
112
|
+
}
|
|
113
|
+
/** A landed value as the list of strings a control holding several shows. */
|
|
114
|
+
function listOf(value) {
|
|
115
|
+
if (Array.isArray(value))
|
|
116
|
+
return value.map((v) => String(v));
|
|
117
|
+
if (value === null || value === undefined || value === "")
|
|
118
|
+
return [];
|
|
119
|
+
return [String(value)];
|
|
120
|
+
}
|
|
103
121
|
/**
|
|
104
122
|
* What a control gathers: a checkbox `"true"` or `"false"`, so unchecked
|
|
105
|
-
* still says so,
|
|
123
|
+
* still says so, a `<select multiple>` the values of its selected options,
|
|
124
|
+
* and any other control its `value`, which a custom element may give as
|
|
125
|
+
* several.
|
|
106
126
|
*/
|
|
107
127
|
function controlValue(el) {
|
|
108
|
-
|
|
128
|
+
if (isCheckbox(el))
|
|
129
|
+
return String(el.checked);
|
|
130
|
+
if (el.localName === "select" && el.multiple) {
|
|
131
|
+
return [...el.selectedOptions].map((o) => o.value);
|
|
132
|
+
}
|
|
133
|
+
const value = el.value;
|
|
134
|
+
return Array.isArray(value) ? value.map((v) => String(v)) : el.value;
|
|
109
135
|
}
|
|
110
136
|
/**
|
|
111
137
|
* Gather a control into `values` under its column. Of the radio buttons
|
|
112
138
|
* naming one column, the one checked gives the value, and `""` stands when
|
|
113
|
-
* none is.
|
|
139
|
+
* none is. Of the checkboxes in a set naming one column, the checked ones
|
|
140
|
+
* give their values, and none checked gives none.
|
|
114
141
|
*/
|
|
115
142
|
export function gatherInto(values, column, el) {
|
|
116
143
|
if (isRadio(el) && !el.checked) {
|
|
117
144
|
values[column] ??= "";
|
|
118
145
|
return;
|
|
119
146
|
}
|
|
147
|
+
if (isCheckbox(el) && holdsSeveral(el)) {
|
|
148
|
+
const had = values[column];
|
|
149
|
+
const set = Array.isArray(had) ? had : [];
|
|
150
|
+
values[column] = set;
|
|
151
|
+
if (el.checked)
|
|
152
|
+
set.push(el.value);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
120
155
|
values[column] = controlValue(el);
|
|
121
156
|
}
|
|
122
157
|
/**
|
|
@@ -126,6 +161,12 @@ export function gatherInto(values, column, el) {
|
|
|
126
161
|
* text content is set. Every one carries the value as `lb-column-value`.
|
|
127
162
|
*/
|
|
128
163
|
function land(el, value) {
|
|
164
|
+
if (holdsSeveral(el))
|
|
165
|
+
return landSeveral(el, value);
|
|
166
|
+
// A custom element control takes several values as they are. Anything
|
|
167
|
+
// else holds one, and shows the first, as a URL's parm reads.
|
|
168
|
+
if (Array.isArray(value) && !isFormAssociated(el))
|
|
169
|
+
value = value[0];
|
|
129
170
|
// Untouched, whatever the server sent: the browser decides what a
|
|
130
171
|
// non-string looks like. See Row in core/lb-types.ts.
|
|
131
172
|
if (value === null || value === undefined) {
|
|
@@ -141,9 +182,11 @@ function land(el, value) {
|
|
|
141
182
|
return;
|
|
142
183
|
}
|
|
143
184
|
if (isCheckbox(el)) {
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
185
|
+
// Off for false, and for what an unchecked checkbox gathers or an absent
|
|
186
|
+
// query parm lands as, so a checkbox lands what it gathered even from a
|
|
187
|
+
// query whose values are strings, such as lb-url.
|
|
188
|
+
el.checked =
|
|
189
|
+
value !== false && value !== "false" && value !== "";
|
|
147
190
|
}
|
|
148
191
|
else if (isRadio(el)) {
|
|
149
192
|
el.checked =
|
|
@@ -161,6 +204,26 @@ function land(el, value) {
|
|
|
161
204
|
}
|
|
162
205
|
setStamp(el, ATTR_COLUMN_VALUE, String(value));
|
|
163
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Set a control that holds several values: a checkbox in a set is checked
|
|
209
|
+
* when its `value` is among them, and a `<select multiple>` selects each
|
|
210
|
+
* option whose `value` is.
|
|
211
|
+
*/
|
|
212
|
+
function landSeveral(el, value) {
|
|
213
|
+
const list = listOf(value);
|
|
214
|
+
if (isCheckbox(el)) {
|
|
215
|
+
el.checked = list.includes(el.value);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
for (const option of el.options) {
|
|
219
|
+
option.selected = list.includes(option.value);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (list.length === 0)
|
|
223
|
+
el.removeAttribute(ATTR_COLUMN_VALUE);
|
|
224
|
+
else
|
|
225
|
+
setStamp(el, ATTR_COLUMN_VALUE, JSON.stringify(list));
|
|
226
|
+
}
|
|
164
227
|
/**
|
|
165
228
|
* Set an element's text, unless it already holds exactly that text. Writing
|
|
166
229
|
* the same text again replaces the text node, which is two mutations and a
|
|
@@ -658,7 +721,7 @@ function listFor(el, template) {
|
|
|
658
721
|
data: {},
|
|
659
722
|
placed: {},
|
|
660
723
|
hidden: false,
|
|
661
|
-
|
|
724
|
+
unplaced: false,
|
|
662
725
|
};
|
|
663
726
|
list.byKey.set(key, entry);
|
|
664
727
|
list.root.rows.push(entry);
|
|
@@ -698,7 +761,7 @@ function create(list, doc, key) {
|
|
|
698
761
|
data: {},
|
|
699
762
|
placed: {},
|
|
700
763
|
hidden: false,
|
|
701
|
-
|
|
764
|
+
unplaced: false,
|
|
702
765
|
};
|
|
703
766
|
list.byKey.set(key, entry);
|
|
704
767
|
return entry;
|
|
@@ -1014,7 +1077,8 @@ function hide(el, hidden) {
|
|
|
1014
1077
|
}
|
|
1015
1078
|
/**
|
|
1016
1079
|
* Finish a group and everything under it, from the rows it shows: each
|
|
1017
|
-
* heading filled from the first, as it was when last placed by
|
|
1080
|
+
* heading filled and linked from the first, as it was when last placed by
|
|
1081
|
+
* the order,
|
|
1018
1082
|
* each aggregate set from all of them, and a group hidden when a filter hides
|
|
1019
1083
|
* every row in it. The rows it shows are returned.
|
|
1020
1084
|
*/
|
|
@@ -1032,9 +1096,12 @@ function finish(group, types) {
|
|
|
1032
1096
|
}
|
|
1033
1097
|
}
|
|
1034
1098
|
}
|
|
1035
|
-
if (shown[0])
|
|
1036
|
-
for (const head of group.heads)
|
|
1099
|
+
if (shown[0]) {
|
|
1100
|
+
for (const head of group.heads) {
|
|
1037
1101
|
fill(head, shown[0].placed);
|
|
1102
|
+
link(head, shown[0].placed);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1038
1105
|
aggregate(group.aggregates, rows, types);
|
|
1039
1106
|
return shown;
|
|
1040
1107
|
}
|
|
@@ -1130,10 +1197,12 @@ function differs(before, after) {
|
|
|
1130
1197
|
* the same rows again moves nothing. A row a filter the user chose does not
|
|
1131
1198
|
* pass is `hidden`.
|
|
1132
1199
|
*
|
|
1133
|
-
* A landing
|
|
1134
|
-
*
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
1200
|
+
* A landing that answers a request leaves alone each side `lb-manual` names
|
|
1201
|
+
* on the element, whole or a patch. Under `order`, nothing already showing
|
|
1202
|
+
* moves, and a new row goes last in its group. Under `filter`, a row the
|
|
1203
|
+
* answer changed or created stays showing. A row left where the order or
|
|
1204
|
+
* the filter would not leave it is stamped unplaced until the rows are next
|
|
1205
|
+
* placed.
|
|
1137
1206
|
*/
|
|
1138
1207
|
export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
1139
1208
|
const list = listFor(el, template);
|
|
@@ -1218,39 +1287,39 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1218
1287
|
a.i - b.i)
|
|
1219
1288
|
.map(({ entry }) => entry);
|
|
1220
1289
|
const passing = filterFor(el, options.choices, options.types ?? {});
|
|
1290
|
+
const manual = options.requested ? manualOf(el) : NO_MANUAL;
|
|
1291
|
+
const touched = new Set([...changed, ...created]);
|
|
1292
|
+
// A row left out of place stays touched until the rows are placed again.
|
|
1293
|
+
for (const entry of shown)
|
|
1294
|
+
if (entry.unplaced)
|
|
1295
|
+
touched.add(entry);
|
|
1221
1296
|
let order;
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
// stands, and what is new goes last
|
|
1297
|
+
let out = new Set();
|
|
1298
|
+
if (manual.order) {
|
|
1299
|
+
// What already shows stays where it stands, and what is new goes last
|
|
1300
|
+
// in the group it belongs to.
|
|
1225
1301
|
for (const entry of created)
|
|
1226
1302
|
entry.placed = entry.data;
|
|
1227
1303
|
order = heldOrder(stayed, created, groupTerms(list, terms));
|
|
1228
|
-
|
|
1229
|
-
for (const entry of order)
|
|
1230
|
-
if (entry.unsorted)
|
|
1231
|
-
touched.add(entry);
|
|
1232
|
-
const out = movedRows(order, sort(order), touched);
|
|
1233
|
-
for (const entry of order) {
|
|
1234
|
-
const fails = !passing(entry.data);
|
|
1235
|
-
// A row the user just changed does not vanish from under them.
|
|
1236
|
-
const kept = touched.has(entry) && !entry.hidden;
|
|
1237
|
-
filterRow(entry, fails && !kept);
|
|
1238
|
-
// A row stands in the group it was placed in until it is placed again.
|
|
1239
|
-
const moves = regrouped(entry, list, terms);
|
|
1240
|
-
if (!moves)
|
|
1241
|
-
entry.placed = entry.data;
|
|
1242
|
-
entry.unsorted = out.has(entry) || (kept && fails) || moves;
|
|
1243
|
-
}
|
|
1304
|
+
out = movedRows(order, sort(order), touched);
|
|
1244
1305
|
}
|
|
1245
1306
|
else {
|
|
1246
1307
|
order = sort(whole
|
|
1247
1308
|
? [...arriving.keys()].map((key) => list.byKey.get(key))
|
|
1248
1309
|
: [...stayed, ...created]);
|
|
1249
|
-
|
|
1310
|
+
}
|
|
1311
|
+
for (const entry of order) {
|
|
1312
|
+
const fails = !passing(entry.data);
|
|
1313
|
+
// A row the answer touched does not vanish from under the user.
|
|
1314
|
+
const kept = manual.filter && touched.has(entry) && !entry.hidden && fails;
|
|
1315
|
+
const hides = fails && !kept;
|
|
1316
|
+
filterRow(entry, hides);
|
|
1317
|
+
// A row stands in the group it was placed in until it is placed again.
|
|
1318
|
+
const moves = manual.order && regrouped(entry, list, terms);
|
|
1319
|
+
if (!moves)
|
|
1250
1320
|
entry.placed = entry.data;
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
}
|
|
1321
|
+
// A row nobody sees stands nowhere the user would notice.
|
|
1322
|
+
entry.unplaced = !hides && (out.has(entry) || kept || moves);
|
|
1254
1323
|
}
|
|
1255
1324
|
const moved = movedRows(stayed, order, changed);
|
|
1256
1325
|
const retired = arrange(list, doc, order, terms);
|
|
@@ -1275,18 +1344,18 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1275
1344
|
}
|
|
1276
1345
|
}
|
|
1277
1346
|
stamps.done(doc);
|
|
1278
|
-
let
|
|
1347
|
+
let unplaced = 0;
|
|
1279
1348
|
for (const entry of order) {
|
|
1280
|
-
if (entry.el.hasAttribute(
|
|
1281
|
-
entry.el.toggleAttribute(
|
|
1349
|
+
if (entry.el.hasAttribute(ATTR_ROW_UNPLACED) !== entry.unplaced) {
|
|
1350
|
+
entry.el.toggleAttribute(ATTR_ROW_UNPLACED, entry.unplaced);
|
|
1282
1351
|
}
|
|
1283
|
-
if (entry.
|
|
1284
|
-
|
|
1352
|
+
if (entry.unplaced)
|
|
1353
|
+
unplaced++;
|
|
1285
1354
|
}
|
|
1286
1355
|
const rows = finish(list.root, options.types ?? {});
|
|
1287
1356
|
// Counted from what the hub holds, which is what the rows show.
|
|
1288
1357
|
setStamp(el, ATTR_QUERY_ROW_COUNT, String(rows.length));
|
|
1289
|
-
setStamp(el,
|
|
1358
|
+
setStamp(el, ATTR_QUERY_UNPLACED_COUNT, String(unplaced));
|
|
1290
1359
|
if (terms.length === 0)
|
|
1291
1360
|
el.removeAttribute(ATTR_QUERY_ORDER);
|
|
1292
1361
|
else
|
|
@@ -1298,6 +1367,14 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1298
1367
|
depart(departing);
|
|
1299
1368
|
host.lbRowsLanded?.();
|
|
1300
1369
|
}
|
|
1370
|
+
const NO_MANUAL = { order: false, filter: false };
|
|
1371
|
+
function manualOf(el) {
|
|
1372
|
+
const sides = (el.getAttribute(ATTR_MANUAL) ?? "").split(/\s+/);
|
|
1373
|
+
return {
|
|
1374
|
+
order: sides.includes(MANUAL_ORDER),
|
|
1375
|
+
filter: sides.includes(MANUAL_FILTER),
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1301
1378
|
/** The order's terms a list's groups break on, outermost first. */
|
|
1302
1379
|
function groupTerms(list, terms) {
|
|
1303
1380
|
return terms.slice(0, list.levels.length);
|
|
@@ -1527,13 +1604,6 @@ function fillArrivals(root, state) {
|
|
|
1527
1604
|
}
|
|
1528
1605
|
}
|
|
1529
1606
|
}
|
|
1530
|
-
/**
|
|
1531
|
-
* Whether a request from `origin` was sent from inside `el`'s list: `el` is
|
|
1532
|
-
* the nearest element carrying `lb-query` around it.
|
|
1533
|
-
*/
|
|
1534
|
-
function sentFrom(el, origin) {
|
|
1535
|
-
return origin?.parentElement?.closest(`[${ATTR_QUERY}]`) === el;
|
|
1536
|
-
}
|
|
1537
1607
|
/**
|
|
1538
1608
|
* Land response items. Every answer arrives through here, and is kept for
|
|
1539
1609
|
* `root`, so that what arrives after it is filled from it — see forget().
|
|
@@ -1557,7 +1627,6 @@ export function applyResponse(root, items, options = {}) {
|
|
|
1557
1627
|
landItem(el, item, {
|
|
1558
1628
|
...rowsOptions(state, item),
|
|
1559
1629
|
requested: options.requested,
|
|
1560
|
-
held: sentFrom(el, options.origin),
|
|
1561
1630
|
});
|
|
1562
1631
|
}
|
|
1563
1632
|
if (choices.size > 0)
|
|
@@ -1567,11 +1636,12 @@ export function applyResponse(root, items, options = {}) {
|
|
|
1567
1636
|
attachPlaced();
|
|
1568
1637
|
}
|
|
1569
1638
|
/**
|
|
1570
|
-
* Place an element's rows by the order again, from the answer
|
|
1571
|
-
* query, with no round trip: what the user asks for once an
|
|
1572
|
-
* rows out of
|
|
1639
|
+
* Place an element's rows by the order and the filter again, from the answer
|
|
1640
|
+
* kept for its query, with no round trip: what the user asks for once an
|
|
1641
|
+
* answer has left rows out of place. False when the element shows no rows
|
|
1642
|
+
* the hub holds.
|
|
1573
1643
|
*/
|
|
1574
|
-
export function
|
|
1644
|
+
export function placeAgain(root, el) {
|
|
1575
1645
|
const state = kept.get(root);
|
|
1576
1646
|
const item = state?.answers.get(el.getAttribute(ATTR_QUERY) ?? "");
|
|
1577
1647
|
if (!state || !item || item.kind === KIND_ROW || !state.landed.has(el)) {
|