@loadbare/app 0.19.0 → 0.20.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 -2
- package/dist/build/assemble.js.map +1 -1
- package/dist/core/lb-constants.d.ts +7 -2
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +30 -4
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +21 -0
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +221 -30
- 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 +26 -6
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/docs/comparison.md +23 -13
- package/docs/opinions.md +2 -2
- package/docs/reference/builder.md +11 -2
- package/docs/reference/chrome.md +83 -9
- package/docs/reference/css.md +0 -1
- package/docs/reference/custom-elements.md +7 -28
- package/docs/reference/data-binding.md +320 -50
- package/docs/reference/names.md +258 -0
- package/docs/reference/overview.md +6 -0
- package/docs/reference/page-files.md +47 -10
- package/docs/reference/server.md +54 -1
- package/docs/reference/widgets.md +10 -1
- package/docs/testing.md +5 -1
- package/docs/tutorials/040-displaying-data.md +1 -1
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +26 -14
- package/skills/loadbare-app/references/builder.md +11 -2
- package/skills/loadbare-app/references/chrome.md +83 -9
- package/skills/loadbare-app/references/css.md +0 -1
- package/skills/loadbare-app/references/custom-elements.md +7 -28
- package/skills/loadbare-app/references/data-binding.md +320 -50
- package/skills/loadbare-app/references/names.md +258 -0
- package/skills/loadbare-app/references/overview.md +6 -0
- package/skills/loadbare-app/references/page-files.md +47 -10
- package/skills/loadbare-app/references/server.md +54 -1
- package/skills/loadbare-app/references/widgets.md +10 -1
- package/docs/TECHREF-1.0.md +0 -2267
- package/skills/loadbare-app/references/TECHREF-1.0.md +0 -2267
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_ROW_COUNT, ATTR_ROW_CHANGED, ATTR_ROW_CREATED, ATTR_ROW_LEAVING, ATTR_ROW_LIVE, ATTR_ROW_MOVED, ATTR_ROW_REQUESTED, ATTR_SHOW, ATTR_SUM, KIND_ROW, LIVE_GROUP, LIVE_ROW, REQUESTED_CHANGED, REQUESTED_CREATED, REQUESTED_MOVED, SHOW_NOT, ATTR_SELECT, ATTR_ROW_SELECTED, } from "../core/lb-constants.js";
|
|
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_QUERY_UNSORTED_COUNT, ATTR_ROW_CHANGED, ATTR_ROW_CREATED, ATTR_ROW_LEAVING, ATTR_ROW_LIVE, ATTR_ROW_MOVED, ATTR_ROW_REQUESTED, ATTR_ROW_UNSORTED, 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. */
|
|
@@ -325,6 +325,8 @@ function note(places, el) {
|
|
|
325
325
|
const column = el.getAttribute(ATTR_COLUMN);
|
|
326
326
|
if (column !== null)
|
|
327
327
|
addPlace(places.columns, column, el);
|
|
328
|
+
if (el.hasAttribute(ATTR_URL_PARM))
|
|
329
|
+
places.links.push(el);
|
|
328
330
|
const show = el.getAttribute(ATTR_SHOW);
|
|
329
331
|
if (show === null)
|
|
330
332
|
return;
|
|
@@ -337,7 +339,20 @@ function note(places, el) {
|
|
|
337
339
|
addPlace(places.shows, show, el);
|
|
338
340
|
}
|
|
339
341
|
function noPlaces() {
|
|
340
|
-
return { columns: new Map(), shows: new Map() };
|
|
342
|
+
return { columns: new Map(), shows: new Map(), links: [] };
|
|
343
|
+
}
|
|
344
|
+
/** An element a row lands on, in any way. */
|
|
345
|
+
const PLACE = `[${ATTR_COLUMN}], [${ATTR_SHOW}], [${ATTR_URL_PARM}]`;
|
|
346
|
+
/**
|
|
347
|
+
* Note a row's root among its places: its own `lb-column` and `lb-url-parm`
|
|
348
|
+
* count, as the row is its own. Its own `lb-show` never does.
|
|
349
|
+
*/
|
|
350
|
+
function noteRoot(places, root) {
|
|
351
|
+
if (root.hasAttribute(ATTR_COLUMN)) {
|
|
352
|
+
addPlace(places.columns, root.getAttribute(ATTR_COLUMN), root);
|
|
353
|
+
}
|
|
354
|
+
if (root.hasAttribute(ATTR_URL_PARM))
|
|
355
|
+
places.links.push(root);
|
|
341
356
|
}
|
|
342
357
|
/**
|
|
343
358
|
* The places in root's query, found by searching: root's own `lb-column`
|
|
@@ -346,12 +361,10 @@ function noPlaces() {
|
|
|
346
361
|
*/
|
|
347
362
|
function search(root, self, stop) {
|
|
348
363
|
const places = noPlaces();
|
|
349
|
-
if (self
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
for (const el of below(root, `[${ATTR_COLUMN}], [${ATTR_SHOW}]`, stop)) {
|
|
364
|
+
if (self)
|
|
365
|
+
noteRoot(places, root);
|
|
366
|
+
for (const el of below(root, PLACE, stop))
|
|
353
367
|
note(places, el);
|
|
354
|
-
}
|
|
355
368
|
return places;
|
|
356
369
|
}
|
|
357
370
|
function kids(el) {
|
|
@@ -372,9 +385,8 @@ function compile(row) {
|
|
|
372
385
|
for (let i = 0; i < children.length; i++) {
|
|
373
386
|
const child = children[i];
|
|
374
387
|
const at = [...path, i];
|
|
375
|
-
if (child.
|
|
388
|
+
if (child.matches(PLACE))
|
|
376
389
|
paths.push(at);
|
|
377
|
-
}
|
|
378
390
|
if (child.hasAttribute(ATTR_QUERY))
|
|
379
391
|
continue;
|
|
380
392
|
if (child.localName === "template" && !child.hasAttribute(ATTR_SHOW)) {
|
|
@@ -393,9 +405,7 @@ function compile(row) {
|
|
|
393
405
|
*/
|
|
394
406
|
function placesBy(row, paths) {
|
|
395
407
|
const places = noPlaces();
|
|
396
|
-
|
|
397
|
-
addPlace(places.columns, row.getAttribute(ATTR_COLUMN), row);
|
|
398
|
-
}
|
|
408
|
+
noteRoot(places, row);
|
|
399
409
|
for (const path of paths) {
|
|
400
410
|
let el = row;
|
|
401
411
|
for (const i of path)
|
|
@@ -446,7 +456,71 @@ function fill(places, row) {
|
|
|
446
456
|
* `stop` passes over everything matching it and inside it.
|
|
447
457
|
*/
|
|
448
458
|
export function applyRow(root, row, self = false, stop) {
|
|
449
|
-
|
|
459
|
+
const places = search(root, self, stop);
|
|
460
|
+
fill(places, row);
|
|
461
|
+
link(places, row);
|
|
462
|
+
}
|
|
463
|
+
/** The `href` each link was written with, which every landing starts from. */
|
|
464
|
+
const bases = new WeakMap();
|
|
465
|
+
/** Columns already reported as missing from a row, or as no parm's value. */
|
|
466
|
+
const unlinked = new Set();
|
|
467
|
+
/** The columns an element's `lb-url-parm` names. */
|
|
468
|
+
function parmsOf(el) {
|
|
469
|
+
return (el.getAttribute(ATTR_URL_PARM) ?? "")
|
|
470
|
+
.split(",")
|
|
471
|
+
.map((column) => column.trim())
|
|
472
|
+
.filter((column) => column !== "");
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Write each link's `href` from the whole row: the `href` as written, with
|
|
476
|
+
* each column its `lb-url-parm` names as the parm of the same name. A null,
|
|
477
|
+
* empty or missing column leaves its parm off, as an empty value takes a
|
|
478
|
+
* parm out of the URL.
|
|
479
|
+
*/
|
|
480
|
+
function link(places, row) {
|
|
481
|
+
if (places.links.length === 0)
|
|
482
|
+
return;
|
|
483
|
+
// A row with no values is a blank, not a row missing its columns.
|
|
484
|
+
const real = Object.values(row).some((v) => v !== null && v !== undefined);
|
|
485
|
+
for (const el of places.links) {
|
|
486
|
+
let base = bases.get(el);
|
|
487
|
+
if (base === undefined) {
|
|
488
|
+
base = el.getAttribute("href") ?? "";
|
|
489
|
+
bases.set(el, base);
|
|
490
|
+
}
|
|
491
|
+
const hashAt = base.indexOf("#");
|
|
492
|
+
const hash = hashAt < 0 ? "" : base.slice(hashAt);
|
|
493
|
+
const rest = hashAt < 0 ? base : base.slice(0, hashAt);
|
|
494
|
+
const queryAt = rest.indexOf("?");
|
|
495
|
+
const path = queryAt < 0 ? rest : rest.slice(0, queryAt);
|
|
496
|
+
const parms = new URLSearchParams(queryAt < 0 ? "" : rest.slice(queryAt));
|
|
497
|
+
for (const column of parmsOf(el)) {
|
|
498
|
+
const value = row[column];
|
|
499
|
+
if (value === undefined && real && !unlinked.has(column)) {
|
|
500
|
+
unlinked.add(column);
|
|
501
|
+
console.warn(`lb-hub: <a ${ATTR_URL_PARM}> names '${column}', which the row ` +
|
|
502
|
+
`around it does not have`);
|
|
503
|
+
}
|
|
504
|
+
const atomic = typeof value === "string" ||
|
|
505
|
+
typeof value === "number" ||
|
|
506
|
+
typeof value === "boolean";
|
|
507
|
+
if (!atomic && value !== null && value !== undefined) {
|
|
508
|
+
if (!unlinked.has(column)) {
|
|
509
|
+
unlinked.add(column);
|
|
510
|
+
console.warn(`lb-hub: '${column}' is no value a query parm can hold, and is ` +
|
|
511
|
+
`left off the link`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (!atomic || value === "")
|
|
515
|
+
parms.delete(column);
|
|
516
|
+
else
|
|
517
|
+
parms.set(column, String(value));
|
|
518
|
+
}
|
|
519
|
+
const query = parms.toString();
|
|
520
|
+
const href = `${path}${query ? `?${query}` : ""}${hash}`;
|
|
521
|
+
if (el.getAttribute("href") !== href)
|
|
522
|
+
el.setAttribute("href", href);
|
|
523
|
+
}
|
|
450
524
|
}
|
|
451
525
|
/**
|
|
452
526
|
* A row naming every column root's query shows, each null: what landing it
|
|
@@ -582,7 +656,9 @@ function listFor(el, template) {
|
|
|
582
656
|
el: row,
|
|
583
657
|
places: search(row, true),
|
|
584
658
|
data: {},
|
|
659
|
+
placed: {},
|
|
585
660
|
hidden: false,
|
|
661
|
+
unsorted: false,
|
|
586
662
|
};
|
|
587
663
|
list.byKey.set(key, entry);
|
|
588
664
|
list.root.rows.push(entry);
|
|
@@ -620,7 +696,9 @@ function create(list, doc, key) {
|
|
|
620
696
|
el,
|
|
621
697
|
places: placesBy(el, list.paths),
|
|
622
698
|
data: {},
|
|
699
|
+
placed: {},
|
|
623
700
|
hidden: false,
|
|
701
|
+
unsorted: false,
|
|
624
702
|
};
|
|
625
703
|
list.byKey.set(key, entry);
|
|
626
704
|
return entry;
|
|
@@ -722,7 +800,7 @@ function arrange(list, doc, order, terms) {
|
|
|
722
800
|
const term = terms[level];
|
|
723
801
|
const runs = [];
|
|
724
802
|
for (const entry of entries) {
|
|
725
|
-
const value = term ? entry.
|
|
803
|
+
const value = term ? entry.placed[term.column] : null;
|
|
726
804
|
const id = groupId(term?.column ?? "", value);
|
|
727
805
|
const last = runs.at(-1);
|
|
728
806
|
if (last?.id === id)
|
|
@@ -936,14 +1014,15 @@ function hide(el, hidden) {
|
|
|
936
1014
|
}
|
|
937
1015
|
/**
|
|
938
1016
|
* Finish a group and everything under it, from the rows it shows: each
|
|
939
|
-
* heading filled from the first,
|
|
940
|
-
*
|
|
941
|
-
* returned.
|
|
1017
|
+
* heading filled from the first, as it was when last placed by the order,
|
|
1018
|
+
* each aggregate set from all of them, and a group hidden when a filter hides
|
|
1019
|
+
* every row in it. The rows it shows are returned.
|
|
942
1020
|
*/
|
|
943
1021
|
function finish(group, types) {
|
|
944
|
-
const
|
|
1022
|
+
const shown = group.children.length > 0
|
|
945
1023
|
? group.children.flatMap((child) => finish(child, types))
|
|
946
|
-
: group.rows.filter((entry) => !entry.hidden)
|
|
1024
|
+
: group.rows.filter((entry) => !entry.hidden);
|
|
1025
|
+
const rows = shown.map((entry) => entry.data);
|
|
947
1026
|
const hidden = rows.length === 0 && rowsUnder(group).length > 0;
|
|
948
1027
|
if (hidden !== group.hidden) {
|
|
949
1028
|
group.hidden = hidden;
|
|
@@ -953,11 +1032,11 @@ function finish(group, types) {
|
|
|
953
1032
|
}
|
|
954
1033
|
}
|
|
955
1034
|
}
|
|
956
|
-
if (
|
|
1035
|
+
if (shown[0])
|
|
957
1036
|
for (const head of group.heads)
|
|
958
|
-
fill(head,
|
|
1037
|
+
fill(head, shown[0].placed);
|
|
959
1038
|
aggregate(group.aggregates, rows, types);
|
|
960
|
-
return
|
|
1039
|
+
return shown;
|
|
961
1040
|
}
|
|
962
1041
|
/** The order to place rows by, or none when it cannot be read. */
|
|
963
1042
|
function termsOf(el, order) {
|
|
@@ -969,6 +1048,12 @@ function termsOf(el, order) {
|
|
|
969
1048
|
return [];
|
|
970
1049
|
}
|
|
971
1050
|
}
|
|
1051
|
+
/** An order's terms written back as its text. */
|
|
1052
|
+
function orderText(terms) {
|
|
1053
|
+
return terms
|
|
1054
|
+
.map((term) => (term.descending ? ORDER_DESCENDING : "") + term.column)
|
|
1055
|
+
.join(",");
|
|
1056
|
+
}
|
|
972
1057
|
/** The user's choice in a column of `lb-choices`, when one is made. */
|
|
973
1058
|
function choiceIn(choices, column) {
|
|
974
1059
|
const value = choices?.[column];
|
|
@@ -1044,6 +1129,11 @@ function differs(before, after) {
|
|
|
1044
1129
|
* rows go last. A row that stands where it stood is not touched, so landing
|
|
1045
1130
|
* the same rows again moves nothing. A row a filter the user chose does not
|
|
1046
1131
|
* pass is `hidden`.
|
|
1132
|
+
*
|
|
1133
|
+
* A landing `held` for the user working in the list moves nothing already
|
|
1134
|
+
* showing, whole or a patch. A row it changed or created that the order
|
|
1135
|
+
* would put elsewhere, or that the filter would hide, stays and is stamped
|
|
1136
|
+
* unsorted until the rows are next placed by the order.
|
|
1047
1137
|
*/
|
|
1048
1138
|
export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
1049
1139
|
const list = listFor(el, template);
|
|
@@ -1100,6 +1190,10 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1100
1190
|
if (fresh || options.refill || changed.has(entry))
|
|
1101
1191
|
fill(entry.places, data);
|
|
1102
1192
|
entry.data = { ...entry.data, ...data };
|
|
1193
|
+
// A link reads the whole row, which a patch may send only part of.
|
|
1194
|
+
if (fresh || options.refill || changed.has(entry)) {
|
|
1195
|
+
link(entry.places, entry.data);
|
|
1196
|
+
}
|
|
1103
1197
|
}
|
|
1104
1198
|
for (const entry of gone) {
|
|
1105
1199
|
list.byKey.delete(entry.key);
|
|
@@ -1109,9 +1203,6 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1109
1203
|
// already showing with what is new after it. A sort keeps rows that
|
|
1110
1204
|
// compare equal in this order.
|
|
1111
1205
|
const stayed = shown.filter((entry) => list.byKey.get(entry.key) === entry);
|
|
1112
|
-
let order = whole
|
|
1113
|
-
? [...arriving.keys()].map((key) => list.byKey.get(key))
|
|
1114
|
-
: [...stayed, ...created];
|
|
1115
1206
|
const terms = termsOf(el, orderFor(el, options));
|
|
1116
1207
|
if (list.levels.length > terms.length && !warned.has(el)) {
|
|
1117
1208
|
warned.add(el);
|
|
@@ -1119,16 +1210,48 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1119
1210
|
`template(s) and an order of ${terms.length} term(s); a level with ` +
|
|
1120
1211
|
`no term shows its rows as one group`);
|
|
1121
1212
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1213
|
+
const sort = (entries) => terms.length === 0
|
|
1214
|
+
? entries
|
|
1215
|
+
: entries
|
|
1124
1216
|
.map((entry, i) => ({ entry, i }))
|
|
1125
1217
|
.sort((a, b) => compareRows(a.entry.data, b.entry.data, terms, options.types) ||
|
|
1126
1218
|
a.i - b.i)
|
|
1127
1219
|
.map(({ entry }) => entry);
|
|
1128
|
-
}
|
|
1129
1220
|
const passing = filterFor(el, options.choices, options.types ?? {});
|
|
1130
|
-
|
|
1131
|
-
|
|
1221
|
+
let order;
|
|
1222
|
+
if (options.held) {
|
|
1223
|
+
// The user is working in this list: what already shows stays where it
|
|
1224
|
+
// stands, and what is new goes last in the group it belongs to.
|
|
1225
|
+
for (const entry of created)
|
|
1226
|
+
entry.placed = entry.data;
|
|
1227
|
+
order = heldOrder(stayed, created, groupTerms(list, terms));
|
|
1228
|
+
const touched = new Set([...changed, ...created]);
|
|
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
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
else {
|
|
1246
|
+
order = sort(whole
|
|
1247
|
+
? [...arriving.keys()].map((key) => list.byKey.get(key))
|
|
1248
|
+
: [...stayed, ...created]);
|
|
1249
|
+
for (const entry of order) {
|
|
1250
|
+
entry.placed = entry.data;
|
|
1251
|
+
entry.unsorted = false;
|
|
1252
|
+
filterRow(entry, !passing(entry.data));
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1132
1255
|
const moved = movedRows(stayed, order, changed);
|
|
1133
1256
|
const retired = arrange(list, doc, order, terms);
|
|
1134
1257
|
refocus(list, focused);
|
|
@@ -1152,9 +1275,22 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1152
1275
|
}
|
|
1153
1276
|
}
|
|
1154
1277
|
stamps.done(doc);
|
|
1278
|
+
let unsorted = 0;
|
|
1279
|
+
for (const entry of order) {
|
|
1280
|
+
if (entry.el.hasAttribute(ATTR_ROW_UNSORTED) !== entry.unsorted) {
|
|
1281
|
+
entry.el.toggleAttribute(ATTR_ROW_UNSORTED, entry.unsorted);
|
|
1282
|
+
}
|
|
1283
|
+
if (entry.unsorted)
|
|
1284
|
+
unsorted++;
|
|
1285
|
+
}
|
|
1155
1286
|
const rows = finish(list.root, options.types ?? {});
|
|
1156
1287
|
// Counted from what the hub holds, which is what the rows show.
|
|
1157
1288
|
setStamp(el, ATTR_QUERY_ROW_COUNT, String(rows.length));
|
|
1289
|
+
setStamp(el, ATTR_QUERY_UNSORTED_COUNT, String(unsorted));
|
|
1290
|
+
if (terms.length === 0)
|
|
1291
|
+
el.removeAttribute(ATTR_QUERY_ORDER);
|
|
1292
|
+
else
|
|
1293
|
+
setStamp(el, ATTR_QUERY_ORDER, orderText(terms));
|
|
1158
1294
|
const departing = gone.map((entry) => entry.el);
|
|
1159
1295
|
for (const group of retired)
|
|
1160
1296
|
leaveGroup(group, departing);
|
|
@@ -1162,6 +1298,39 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1162
1298
|
depart(departing);
|
|
1163
1299
|
host.lbRowsLanded?.();
|
|
1164
1300
|
}
|
|
1301
|
+
/** The order's terms a list's groups break on, outermost first. */
|
|
1302
|
+
function groupTerms(list, terms) {
|
|
1303
|
+
return terms.slice(0, list.levels.length);
|
|
1304
|
+
}
|
|
1305
|
+
/** The group a row's columns put it in, as one string. */
|
|
1306
|
+
function groupOf(row, terms) {
|
|
1307
|
+
return terms.map((term) => groupId(term.column, row[term.column])).join("\u0001");
|
|
1308
|
+
}
|
|
1309
|
+
/** Whether a row's columns now put it in another group than it stands in. */
|
|
1310
|
+
function regrouped(entry, list, terms) {
|
|
1311
|
+
const by = groupTerms(list, terms);
|
|
1312
|
+
return groupOf(entry.data, by) !== groupOf(entry.placed, by);
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* The rows of a list the user is working in: those showing where they
|
|
1316
|
+
* stand, and each new one after the last row of its group, or last of all
|
|
1317
|
+
* when its group shows no row.
|
|
1318
|
+
*/
|
|
1319
|
+
function heldOrder(stayed, created, by) {
|
|
1320
|
+
const order = [...stayed];
|
|
1321
|
+
for (const entry of created) {
|
|
1322
|
+
const group = groupOf(entry.placed, by);
|
|
1323
|
+
let at = order.length;
|
|
1324
|
+
if (by.length > 0) {
|
|
1325
|
+
while (at > 0 && groupOf(order[at - 1].placed, by) !== group)
|
|
1326
|
+
at--;
|
|
1327
|
+
if (at === 0)
|
|
1328
|
+
at = order.length;
|
|
1329
|
+
}
|
|
1330
|
+
order.splice(at, 0, entry);
|
|
1331
|
+
}
|
|
1332
|
+
return order;
|
|
1333
|
+
}
|
|
1165
1334
|
function focusedRow(list, doc) {
|
|
1166
1335
|
const active = doc.activeElement;
|
|
1167
1336
|
const row = active?.closest(LIVE_ROW);
|
|
@@ -1352,6 +1521,13 @@ function fillArrivals(root, state) {
|
|
|
1352
1521
|
}
|
|
1353
1522
|
}
|
|
1354
1523
|
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Whether a request from `origin` was sent from inside `el`'s list: `el` is
|
|
1526
|
+
* the nearest element carrying `lb-query` around it.
|
|
1527
|
+
*/
|
|
1528
|
+
function sentFrom(el, origin) {
|
|
1529
|
+
return origin?.parentElement?.closest(`[${ATTR_QUERY}]`) === el;
|
|
1530
|
+
}
|
|
1355
1531
|
/**
|
|
1356
1532
|
* Land response items. Every answer arrives through here, and is kept for
|
|
1357
1533
|
* `root`, so that what arrives after it is filled from it — see forget().
|
|
@@ -1375,6 +1551,7 @@ export function applyResponse(root, items, options = {}) {
|
|
|
1375
1551
|
landItem(el, item, {
|
|
1376
1552
|
...rowsOptions(state, item),
|
|
1377
1553
|
requested: options.requested,
|
|
1554
|
+
held: sentFrom(el, options.origin),
|
|
1378
1555
|
});
|
|
1379
1556
|
}
|
|
1380
1557
|
if (choices.size > 0)
|
|
@@ -1383,4 +1560,18 @@ export function applyResponse(root, items, options = {}) {
|
|
|
1383
1560
|
fillArrivals(root, state);
|
|
1384
1561
|
attachPlaced();
|
|
1385
1562
|
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Place an element's rows by the order again, from the answer kept for its
|
|
1565
|
+
* query, with no round trip: what the user asks for once an edit has left
|
|
1566
|
+
* rows out of order. False when the element shows no rows the hub holds.
|
|
1567
|
+
*/
|
|
1568
|
+
export function resort(root, el) {
|
|
1569
|
+
const state = kept.get(root);
|
|
1570
|
+
const item = state?.answers.get(el.getAttribute(ATTR_QUERY) ?? "");
|
|
1571
|
+
if (!state || !item || item.kind === KIND_ROW || !state.landed.has(el)) {
|
|
1572
|
+
return false;
|
|
1573
|
+
}
|
|
1574
|
+
landItem(el, item, rowsOptions(state, item));
|
|
1575
|
+
return true;
|
|
1576
|
+
}
|
|
1386
1577
|
//# sourceMappingURL=lb-apply.js.map
|