@loadbare/app 0.25.0 → 0.27.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 +1 -2
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +31 -37
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/cli.d.ts +4 -10
- package/dist/build/cli.d.ts.map +1 -1
- package/dist/build/cli.js +11 -33
- package/dist/build/cli.js.map +1 -1
- package/dist/build/declared.js +1 -1
- package/dist/build/declared.js.map +1 -1
- package/dist/core/lb-constants.d.ts +15 -13
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +47 -51
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +12 -11
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +7 -0
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +10 -34
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +92 -151
- 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 +115 -127
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/hub/lb-lifecycle.d.ts.map +1 -1
- package/dist/hub/lb-lifecycle.js +4 -11
- package/dist/hub/lb-lifecycle.js.map +1 -1
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +3 -3
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +35 -19
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +141 -88
- package/dist/server/lb-server.js.map +1 -1
- package/docs/binding-cases.md +150 -0
- package/docs/comparison.md +34 -38
- package/docs/opinions.md +2 -2
- package/docs/reference/chrome.md +8 -10
- package/docs/reference/css.md +9 -10
- package/docs/reference/custom-elements.md +11 -8
- package/docs/reference/data-binding.md +93 -134
- package/docs/reference/names.md +21 -22
- package/docs/reference/page-files.md +24 -26
- package/docs/reference/server.md +3 -4
- package/docs/roadmap.md +2 -2
- package/docs/testing.md +5 -6
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +55 -48
- package/skills/loadbare-app/references/chrome.md +8 -10
- package/skills/loadbare-app/references/css.md +9 -10
- package/skills/loadbare-app/references/custom-elements.md +11 -8
- package/skills/loadbare-app/references/data-binding.md +93 -134
- package/skills/loadbare-app/references/names.md +21 -22
- package/skills/loadbare-app/references/page-files.md +24 -26
- package/skills/loadbare-app/references/server.md +3 -4
package/dist/hub/lb-apply.d.ts
CHANGED
|
@@ -36,11 +36,6 @@ export declare function gatherInto(values: Record<string, ParmValue>, column: st
|
|
|
36
36
|
* showing.
|
|
37
37
|
*/
|
|
38
38
|
export declare function within(root: Element, selector: string): Element[];
|
|
39
|
-
/**
|
|
40
|
-
* Tick or clear an `lb-select` checkbox, and stamp the live row it selects
|
|
41
|
-
* to match.
|
|
42
|
-
*/
|
|
43
|
-
export declare function select(box: Element, on: boolean): void;
|
|
44
39
|
/**
|
|
45
40
|
* Fill from one row: every `lb-column` and `lb-show` in root's query.
|
|
46
41
|
*
|
|
@@ -88,12 +83,12 @@ export declare function addUnentered(el: Element): boolean;
|
|
|
88
83
|
export declare function discardUnentered(row: Element): boolean;
|
|
89
84
|
/** How a list of rows lands: in what order, and whether a request asked. */
|
|
90
85
|
export interface RowsOptions {
|
|
91
|
-
/** The
|
|
86
|
+
/** The query's resolved order, from its row of `lb-sort`. */
|
|
92
87
|
order?: string;
|
|
93
88
|
/** The column types the server declared. */
|
|
94
89
|
types?: ColumnTypes;
|
|
95
|
-
/** The
|
|
96
|
-
|
|
90
|
+
/** The query's row of `lb-filters`. */
|
|
91
|
+
filters?: Row;
|
|
97
92
|
/**
|
|
98
93
|
* The landing answers a request from this page, so a side the list
|
|
99
94
|
* places by hand leaves its rows where they stand.
|
|
@@ -113,29 +108,10 @@ export interface RowsOptions {
|
|
|
113
108
|
doc?: Document;
|
|
114
109
|
}
|
|
115
110
|
/**
|
|
116
|
-
* Land rows through a row template
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* landing loop lands each arriving row's values on its live row, as a `row`
|
|
121
|
-
* lands, passing over a column that already shows its value. Then every live
|
|
122
|
-
* row is placed by the order in one fragment, and the browser styles once.
|
|
123
|
-
*
|
|
124
|
-
* All rows decide membership: a row whose key did not arrive leaves. A patch
|
|
125
|
-
* disturbs only what it names — a row it did not mention keeps its contents,
|
|
126
|
-
* and keeps its place unless a row the patch named now sorts around it.
|
|
127
|
-
*
|
|
128
|
-
* With no order, all rows show in the order they arrived, and a patch's new
|
|
129
|
-
* rows go last. A row that stands where it stood is not touched, so landing
|
|
130
|
-
* the same rows again moves nothing. A row a filter the user chose does not
|
|
131
|
-
* pass is `hidden`.
|
|
132
|
-
*
|
|
133
|
-
* A landing that answers a request leaves alone each side `lb-manual` names
|
|
134
|
-
* on the element, whole or a patch. Under `order`, nothing already showing
|
|
135
|
-
* moves, and a new row goes last in its group. Under `filter`, a row the
|
|
136
|
-
* answer changed or created stays showing. A row left where the order or
|
|
137
|
-
* the filter would not leave it is stamped unplaced until the rows are next
|
|
138
|
-
* placed.
|
|
111
|
+
* Land rows through a row template: a live row per key, placed by the order,
|
|
112
|
+
* with groups built from the markup's group templates where the order breaks.
|
|
113
|
+
* A patch disturbs only what it names — rows it leaves out keep their
|
|
114
|
+
* contents and their place.
|
|
139
115
|
*/
|
|
140
116
|
export declare function applyRows(el: Element, template: HTMLTemplateElement, keyColumn: string, result: RowsResult, options?: RowsOptions): void;
|
|
141
117
|
/** The answer kept for a query, as all of its rows. */
|
|
@@ -160,9 +136,9 @@ export interface LandOptions {
|
|
|
160
136
|
/**
|
|
161
137
|
* Land response items. Every answer arrives through here, and is kept for
|
|
162
138
|
* `root`, so that what arrives after it is filled from it — see forget().
|
|
163
|
-
* An answer to `lb-
|
|
164
|
-
* everything has landed, each custom element placed since the last
|
|
165
|
-
* is attached.
|
|
139
|
+
* An answer to `lb-sort` or `lb-filters` places again the rows it changes.
|
|
140
|
+
* Once everything has landed, each custom element placed since the last
|
|
141
|
+
* landing is attached.
|
|
166
142
|
*/
|
|
167
143
|
export declare function applyResponse(root: ParentNode, items: ResponseItem[], options?: LandOptions): void;
|
|
168
144
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-apply.d.ts","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lb-apply.d.ts","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"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"}
|
package/dist/hub/lb-apply.js
CHANGED
|
@@ -1,45 +1,10 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* Landing
|
|
4
|
-
*
|
|
5
|
-
* The hub lands a response item's rows on every element whose `lb-query`
|
|
6
|
-
* names its query. The item says the query's kind and key, so the markup
|
|
7
|
-
* says neither:
|
|
8
|
-
*
|
|
9
|
-
* kind row template the hub
|
|
10
|
-
* row no lands the row on the element itself
|
|
11
|
-
* row yes lands one live row
|
|
12
|
-
* rows yes lands one live row per row
|
|
13
|
-
* rows no lands nothing
|
|
14
|
-
*
|
|
15
|
-
* Filling a live row and filling an element a `row` lands on are the same
|
|
16
|
-
* operation, and the only difference is how much of the page the root
|
|
17
|
-
* covers.
|
|
18
|
-
*
|
|
19
|
-
* An answer is kept after it lands. An element that names a query and
|
|
20
|
-
* arrives after that query's answer — a picker in a new live row, a ghost
|
|
21
|
-
* row a custom element built — is filled from the kept answer at the end of
|
|
22
|
-
* the next landing, so a query need not land again, and need not land after
|
|
23
|
-
* the one whose rows hold it, just to fill what is new.
|
|
24
|
-
*
|
|
25
|
-
* Every custom element landing places is told so once the landing is done,
|
|
26
|
-
* and every one it removes is told so once it is gone — see lb-lifecycle.
|
|
27
|
-
*
|
|
28
|
-
* A `rows` query is placed by its order: the user's, from the choice its
|
|
29
|
-
* element's `lb-order` names, or the one the server declared, or, with
|
|
30
|
-
* neither, the order its rows arrived in. Groups are built from the markup's
|
|
31
|
-
* group templates as the order breaks, and every change to a list — a row
|
|
32
|
-
* created, changed, moved or leaving, a group appearing or leaving — is one
|
|
33
|
-
* a stylesheet can see. A row a filter the user chose does not pass is
|
|
34
|
-
* hidden.
|
|
35
|
-
*
|
|
36
|
-
* A condition is the one other thing landing moves. An element whose
|
|
37
|
-
* `lb-show` column is off stands in the document inside a template, and
|
|
38
|
-
* landing reaches in there as it reaches everywhere else, so a branch that
|
|
39
|
-
* returns is already current. Nothing but landing reaches in.
|
|
3
|
+
* Landing: rows from response items onto the elements that name their query.
|
|
4
|
+
* `applyRows` fills a row template; `applyRow` fills one element.
|
|
40
5
|
*/
|
|
41
|
-
import {
|
|
42
|
-
import { compareRows, compareValues, isPatch, parseFilter, parseOrder, passes, readAs, } from "../core/lb-types.js";
|
|
6
|
+
import { AGGREGATE_COUNT, AGGREGATE_MAX, AGGREGATE_MIN, AGGREGATE_SUM, ATTR_AGGREGATE, ATTR_COLUMN, ATTR_FILTER, FILTERS_COLUMN_QUERY, FILTERS_QUERY, SORT_COLUMN_ORDER, SORT_COLUMN_QUERY, SORT_QUERY, LB_RESERVED_PREFIX, ATTR_COLUMN_VALUE, ATTR_GROUP, ATTR_GROUP_COLUMN, ATTR_GROUP_LEAVING, ATTR_GROUP_LIVE, ATTR_GROUP_VALUE, ATTR_KEY_VALUE, 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_URL_PARM, KIND_ROW, LIVE_GROUP, LIVE_ROW, ORDER_DESCENDING, REQUESTED_CHANGED, REQUESTED_CREATED, REQUESTED_MOVED, SHOW_NOT, } from "../core/lb-constants.js";
|
|
7
|
+
import { compareRows, compareValues, isPatch, parseFilter, parseOrder, passes, readAs, oneRow, } from "../core/lb-types.js";
|
|
43
8
|
import { attachPlaced, detachRemoved, LEAVING, PARKED, placed, upgradeAbsent, } from "./lb-lifecycle.js";
|
|
44
9
|
/** A DocumentFragment's nodeType. Tier 3 has no `Node` global to read it from. */
|
|
45
10
|
const FRAGMENT_NODE = 11;
|
|
@@ -317,14 +282,6 @@ function everywhere(root, selector) {
|
|
|
317
282
|
}
|
|
318
283
|
return found;
|
|
319
284
|
}
|
|
320
|
-
/**
|
|
321
|
-
* Tick or clear an `lb-select` checkbox, and stamp the live row it selects
|
|
322
|
-
* to match.
|
|
323
|
-
*/
|
|
324
|
-
export function select(box, on) {
|
|
325
|
-
box.checked = on;
|
|
326
|
-
box.closest(LIVE_ROW)?.toggleAttribute(ATTR_ROW_SELECTED, on);
|
|
327
|
-
}
|
|
328
285
|
/**
|
|
329
286
|
* Put an element in the document or take it out, as its `lb-show` column
|
|
330
287
|
* says.
|
|
@@ -347,12 +304,6 @@ function present(el, on) {
|
|
|
347
304
|
}
|
|
348
305
|
if (on || el.parentNode?.nodeType === FRAGMENT_NODE)
|
|
349
306
|
return;
|
|
350
|
-
// A selection hidden is a selection abandoned: a tick kept out of sight
|
|
351
|
-
// would come back stale, and nothing else could clear it.
|
|
352
|
-
for (const box of [el, ...el.querySelectorAll(`[${ATTR_SELECT}]`)]) {
|
|
353
|
-
if (box.hasAttribute(ATTR_SELECT))
|
|
354
|
-
select(box, false);
|
|
355
|
-
}
|
|
356
307
|
const parked = el.ownerDocument.createElement("template");
|
|
357
308
|
parked.setAttribute(ATTR_SHOW, el.getAttribute(ATTR_SHOW));
|
|
358
309
|
el.replaceWith(parked);
|
|
@@ -386,8 +337,10 @@ function addPlace(map, name, el) {
|
|
|
386
337
|
*/
|
|
387
338
|
function note(places, el) {
|
|
388
339
|
const column = el.getAttribute(ATTR_COLUMN);
|
|
389
|
-
|
|
340
|
+
// An aggregate's column is the one it totals, which no row lands on.
|
|
341
|
+
if (column !== null && !el.hasAttribute(ATTR_AGGREGATE)) {
|
|
390
342
|
addPlace(places.columns, column, el);
|
|
343
|
+
}
|
|
391
344
|
if (el.hasAttribute(ATTR_URL_PARM))
|
|
392
345
|
places.links.push(el);
|
|
393
346
|
const show = el.getAttribute(ATTR_SHOW);
|
|
@@ -411,7 +364,7 @@ const PLACE = `[${ATTR_COLUMN}], [${ATTR_SHOW}], [${ATTR_URL_PARM}]`;
|
|
|
411
364
|
* count, as the row is its own. Its own `lb-show` never does.
|
|
412
365
|
*/
|
|
413
366
|
function noteRoot(places, root) {
|
|
414
|
-
if (root.hasAttribute(ATTR_COLUMN)) {
|
|
367
|
+
if (root.hasAttribute(ATTR_COLUMN) && !root.hasAttribute(ATTR_AGGREGATE)) {
|
|
415
368
|
addPlace(places.columns, root.getAttribute(ATTR_COLUMN), root);
|
|
416
369
|
}
|
|
417
370
|
if (root.hasAttribute(ATTR_URL_PARM))
|
|
@@ -592,7 +545,7 @@ function link(places, row) {
|
|
|
592
545
|
*/
|
|
593
546
|
export function blankRow(root) {
|
|
594
547
|
const row = {};
|
|
595
|
-
for (const el of below(root, `[${ATTR_COLUMN}]`)) {
|
|
548
|
+
for (const el of below(root, `[${ATTR_COLUMN}]:not([${ATTR_AGGREGATE}])`)) {
|
|
596
549
|
row[el.getAttribute(ATTR_COLUMN)] = null;
|
|
597
550
|
}
|
|
598
551
|
for (const el of below(root, `[${ATTR_SHOW}]`)) {
|
|
@@ -644,7 +597,7 @@ const warned = new WeakSet();
|
|
|
644
597
|
*/
|
|
645
598
|
const SCAFFOLD_STOP = `${LIVE_ROW}, ${LIVE_GROUP}, ${LEAVING}`;
|
|
646
599
|
/** An element carrying any aggregate. */
|
|
647
|
-
const AGGREGATE =
|
|
600
|
+
const AGGREGATE = `[${ATTR_AGGREGATE}]`;
|
|
648
601
|
/**
|
|
649
602
|
* The template a group's contents go in front of: the first `<template>` in
|
|
650
603
|
* the content outside any nested `lb-query`, and not one holding an absent
|
|
@@ -737,6 +690,7 @@ function rowsUnder(group) {
|
|
|
737
690
|
? group.children.flatMap(rowsUnder)
|
|
738
691
|
: group.rows;
|
|
739
692
|
}
|
|
693
|
+
/**
|
|
740
694
|
/**
|
|
741
695
|
* A new live row for a key, copied from the row template: upgraded, stamped
|
|
742
696
|
* with its key, its places found by the template's paths, and no values yet.
|
|
@@ -956,6 +910,12 @@ function arrange(list, doc, order, terms) {
|
|
|
956
910
|
visit(list.root, 0, order);
|
|
957
911
|
return retired;
|
|
958
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* The rows that moved: rows whose values changed, and which now stand
|
|
915
|
+
* somewhere else among the rows whose values did not. A neighbour a moved
|
|
916
|
+
* row passed did not move, and neither did a row whose new values left it
|
|
917
|
+
* where it was.
|
|
918
|
+
|
|
959
919
|
/**
|
|
960
920
|
* The rows that moved: rows whose values changed, and which now stand
|
|
961
921
|
* somewhere else among the rows whose values did not. A neighbour a moved
|
|
@@ -1093,15 +1053,18 @@ const reported = new Set();
|
|
|
1093
1053
|
* leaves them out; a value that is not a number is left out and reported.
|
|
1094
1054
|
*/
|
|
1095
1055
|
function aggregateOf(name, column, rows, type) {
|
|
1096
|
-
if (name ===
|
|
1056
|
+
if (name === AGGREGATE_COUNT && column === "")
|
|
1097
1057
|
return rows.length;
|
|
1098
1058
|
const values = rows
|
|
1099
1059
|
.map((row) => row[column])
|
|
1100
1060
|
.filter((v) => v !== null && v !== undefined && v !== "");
|
|
1061
|
+
// As SQL's count(column): the values that are not null.
|
|
1062
|
+
if (name === AGGREGATE_COUNT)
|
|
1063
|
+
return values.length;
|
|
1101
1064
|
if (values.length === 0)
|
|
1102
1065
|
return null;
|
|
1103
|
-
if (name ===
|
|
1104
|
-
const sign = name ===
|
|
1066
|
+
if (name === AGGREGATE_MIN || name === AGGREGATE_MAX) {
|
|
1067
|
+
const sign = name === AGGREGATE_MIN ? 1 : -1;
|
|
1105
1068
|
return values.reduce((a, b) => sign * compareValues(readAs(b, type), readAs(a, type)) < 0 ? b : a);
|
|
1106
1069
|
}
|
|
1107
1070
|
const decimals = [];
|
|
@@ -1111,7 +1074,8 @@ function aggregateOf(name, column, rows, type) {
|
|
|
1111
1074
|
decimals.push(d);
|
|
1112
1075
|
else if (!reported.has(`${name}:${column}`)) {
|
|
1113
1076
|
reported.add(`${name}:${column}`);
|
|
1114
|
-
console.warn(`lb-hub: ${
|
|
1077
|
+
console.warn(`lb-hub: ${ATTR_AGGREGATE}="${name}" ${ATTR_COLUMN}="${column}" ` +
|
|
1078
|
+
`left out ${JSON.stringify(value)}, ` +
|
|
1115
1079
|
`which is not a number`);
|
|
1116
1080
|
}
|
|
1117
1081
|
}
|
|
@@ -1119,7 +1083,7 @@ function aggregateOf(name, column, rows, type) {
|
|
|
1119
1083
|
return null;
|
|
1120
1084
|
const scale = Math.max(...decimals.map((d) => d.scale));
|
|
1121
1085
|
const sum = decimals.reduce((total, d) => total + rescale(d, scale), 0n);
|
|
1122
|
-
if (name ===
|
|
1086
|
+
if (name === AGGREGATE_SUM)
|
|
1123
1087
|
return decimalText(sum, scale);
|
|
1124
1088
|
// An average, rounded half away from zero at two places past the values',
|
|
1125
1089
|
// which are then dropped while they are zeros.
|
|
@@ -1134,8 +1098,8 @@ function aggregateOf(name, column, rows, type) {
|
|
|
1134
1098
|
/** Set each aggregate element from rows, each column read as its type. */
|
|
1135
1099
|
function aggregate(elements, rows, types) {
|
|
1136
1100
|
for (const el of elements) {
|
|
1137
|
-
const name =
|
|
1138
|
-
const column = el.getAttribute(
|
|
1101
|
+
const name = el.getAttribute(ATTR_AGGREGATE) ?? "";
|
|
1102
|
+
const column = el.getAttribute(ATTR_COLUMN) ?? "";
|
|
1139
1103
|
land(el, aggregateOf(name, column, rows, types[column]));
|
|
1140
1104
|
}
|
|
1141
1105
|
}
|
|
@@ -1190,7 +1154,7 @@ function orderText(terms) {
|
|
|
1190
1154
|
.map((term) => (term.descending ? ORDER_DESCENDING : "") + term.column)
|
|
1191
1155
|
.join(",");
|
|
1192
1156
|
}
|
|
1193
|
-
/** The
|
|
1157
|
+
/** The filter in a column of the query's `lb-filters` row, when one is made. */
|
|
1194
1158
|
function choiceIn(choices, column) {
|
|
1195
1159
|
const value = choices?.[column];
|
|
1196
1160
|
return value === null || value === undefined || value === ""
|
|
@@ -1205,73 +1169,47 @@ function filtered(el) {
|
|
|
1205
1169
|
.filter((column) => column !== "");
|
|
1206
1170
|
}
|
|
1207
1171
|
/**
|
|
1208
|
-
* The order an element's rows are placed by:
|
|
1209
|
-
* `lb-choices` its `lb-order` names, else the one the server declared.
|
|
1172
|
+
* The order an element's rows are placed by: its query's row of `lb-sort`.
|
|
1210
1173
|
*/
|
|
1211
|
-
function
|
|
1212
|
-
const
|
|
1213
|
-
const
|
|
1214
|
-
return
|
|
1174
|
+
function sortOf(state, query) {
|
|
1175
|
+
const row = state.answers.get(SORT_QUERY)?.rows?.find((r) => r[SORT_COLUMN_QUERY] === query);
|
|
1176
|
+
const order = row?.[SORT_COLUMN_ORDER];
|
|
1177
|
+
return typeof order === "string" && order !== "" ? order : undefined;
|
|
1178
|
+
}
|
|
1179
|
+
/** The query's row of `lb-filters`, when it has one. */
|
|
1180
|
+
function filtersOf(state, query) {
|
|
1181
|
+
return state.answers.get(FILTERS_QUERY)?.rows?.find((r) => r[FILTERS_COLUMN_QUERY] === query);
|
|
1215
1182
|
}
|
|
1216
1183
|
/**
|
|
1217
1184
|
* Whether a row passes every filter the user chose for the element: for each
|
|
1218
|
-
* column its `lb-filter` names, the filter in the
|
|
1219
|
-
* the same name.
|
|
1185
|
+
* column its `lb-filter` names, the filter in the query's row of
|
|
1186
|
+
* `lb-filters` of the same name.
|
|
1220
1187
|
*/
|
|
1221
|
-
function filterFor(el,
|
|
1222
|
-
const
|
|
1188
|
+
function filterFor(el, filters, types) {
|
|
1189
|
+
const terms = [];
|
|
1223
1190
|
for (const column of filtered(el)) {
|
|
1224
|
-
const text = choiceIn(
|
|
1225
|
-
const
|
|
1226
|
-
if (
|
|
1227
|
-
|
|
1191
|
+
const text = choiceIn(filters, column);
|
|
1192
|
+
const parsed = text === undefined ? [] : parseFilter(text);
|
|
1193
|
+
if (parsed.length > 0)
|
|
1194
|
+
terms.push([column, parsed]);
|
|
1228
1195
|
}
|
|
1229
|
-
return (row) =>
|
|
1196
|
+
return (row) => terms.every(([column, parsed]) => passes(row[column], parsed, types[column]));
|
|
1230
1197
|
}
|
|
1231
|
-
/**
|
|
1232
|
-
* Hide or show a row for a filter. A selection hidden is a selection
|
|
1233
|
-
* abandoned, as it is for `lb-show`.
|
|
1234
|
-
*/
|
|
1235
1198
|
function filterRow(entry, hidden) {
|
|
1236
1199
|
if (entry.hidden === hidden)
|
|
1237
1200
|
return;
|
|
1238
1201
|
entry.hidden = hidden;
|
|
1239
1202
|
hide(entry.el, hidden);
|
|
1240
|
-
if (!hidden)
|
|
1241
|
-
return;
|
|
1242
|
-
for (const box of [entry.el, ...entry.el.querySelectorAll(`[${ATTR_SELECT}]`)]) {
|
|
1243
|
-
if (box.hasAttribute(ATTR_SELECT))
|
|
1244
|
-
select(box, false);
|
|
1245
|
-
}
|
|
1246
1203
|
}
|
|
1247
1204
|
/** Whether a row's columns as landed differ from what they were. */
|
|
1248
1205
|
function differs(before, after) {
|
|
1249
1206
|
return Object.entries(after).some(([column, value]) => !same(before[column], value));
|
|
1250
1207
|
}
|
|
1251
1208
|
/**
|
|
1252
|
-
* Land rows through a row template
|
|
1253
|
-
*
|
|
1254
|
-
*
|
|
1255
|
-
*
|
|
1256
|
-
* landing loop lands each arriving row's values on its live row, as a `row`
|
|
1257
|
-
* lands, passing over a column that already shows its value. Then every live
|
|
1258
|
-
* row is placed by the order in one fragment, and the browser styles once.
|
|
1259
|
-
*
|
|
1260
|
-
* All rows decide membership: a row whose key did not arrive leaves. A patch
|
|
1261
|
-
* disturbs only what it names — a row it did not mention keeps its contents,
|
|
1262
|
-
* and keeps its place unless a row the patch named now sorts around it.
|
|
1263
|
-
*
|
|
1264
|
-
* With no order, all rows show in the order they arrived, and a patch's new
|
|
1265
|
-
* rows go last. A row that stands where it stood is not touched, so landing
|
|
1266
|
-
* the same rows again moves nothing. A row a filter the user chose does not
|
|
1267
|
-
* pass is `hidden`.
|
|
1268
|
-
*
|
|
1269
|
-
* A landing that answers a request leaves alone each side `lb-manual` names
|
|
1270
|
-
* on the element, whole or a patch. Under `order`, nothing already showing
|
|
1271
|
-
* moves, and a new row goes last in its group. Under `filter`, a row the
|
|
1272
|
-
* answer changed or created stays showing. A row left where the order or
|
|
1273
|
-
* the filter would not leave it is stamped unplaced until the rows are next
|
|
1274
|
-
* placed.
|
|
1209
|
+
* Land rows through a row template: a live row per key, placed by the order,
|
|
1210
|
+
* with groups built from the markup's group templates where the order breaks.
|
|
1211
|
+
* A patch disturbs only what it names — rows it leaves out keep their
|
|
1212
|
+
* contents and their place.
|
|
1275
1213
|
*/
|
|
1276
1214
|
export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
1277
1215
|
const list = listFor(el, template);
|
|
@@ -1285,7 +1223,6 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1285
1223
|
// What a request did is said by this landing alone.
|
|
1286
1224
|
for (const entry of shown)
|
|
1287
1225
|
entry.el.removeAttribute(ATTR_ROW_REQUESTED);
|
|
1288
|
-
// The rows arriving, by key.
|
|
1289
1226
|
const arriving = new Map();
|
|
1290
1227
|
for (const data of whole ? result : (result.rows ?? [])) {
|
|
1291
1228
|
if (data[keyColumn] === undefined) {
|
|
@@ -1310,7 +1247,6 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1310
1247
|
const gone = whole
|
|
1311
1248
|
? shown.filter((entry) => !arriving.has(entry.key))
|
|
1312
1249
|
: dropped.flatMap((key) => list.byKey.get(key) ?? []);
|
|
1313
|
-
// The creation loop.
|
|
1314
1250
|
const created = new Set();
|
|
1315
1251
|
for (const key of arriving.keys()) {
|
|
1316
1252
|
if (!list.byKey.has(key))
|
|
@@ -1341,7 +1277,7 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1341
1277
|
// already showing with what is new after it. A sort keeps rows that
|
|
1342
1278
|
// compare equal in this order.
|
|
1343
1279
|
const stayed = shown.filter((entry) => list.byKey.get(entry.key) === entry);
|
|
1344
|
-
const terms = termsOf(el,
|
|
1280
|
+
const terms = termsOf(el, options.order);
|
|
1345
1281
|
if (list.levels.length > terms.length && !warned.has(el)) {
|
|
1346
1282
|
warned.add(el);
|
|
1347
1283
|
console.warn(`lb-hub: <${el.localName}> has ${list.levels.length} group ` +
|
|
@@ -1355,7 +1291,7 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1355
1291
|
.sort((a, b) => compareRows(a.entry.data, b.entry.data, terms, options.types) ||
|
|
1356
1292
|
a.i - b.i)
|
|
1357
1293
|
.map(({ entry }) => entry);
|
|
1358
|
-
const passing = filterFor(el, options.
|
|
1294
|
+
const passing = filterFor(el, options.filters, options.types ?? {});
|
|
1359
1295
|
const manual = options.requested ? manualOf(el) : NO_MANUAL;
|
|
1360
1296
|
const touched = new Set([...changed, ...created]);
|
|
1361
1297
|
// A row left out of place stays touched until the rows are placed again.
|
|
@@ -1511,7 +1447,7 @@ function refocus(list, focused) {
|
|
|
1511
1447
|
function landItem(el, item, options = {}) {
|
|
1512
1448
|
const template = lists.get(el)?.top ?? templateIn(el);
|
|
1513
1449
|
if (item.kind === KIND_ROW) {
|
|
1514
|
-
const row = item
|
|
1450
|
+
const row = oneRow(item);
|
|
1515
1451
|
if (template) {
|
|
1516
1452
|
applyRows(el, template, item.key, row ? [row] : [], options);
|
|
1517
1453
|
return;
|
|
@@ -1523,8 +1459,8 @@ function landItem(el, item, options = {}) {
|
|
|
1523
1459
|
el.removeAttribute(ATTR_KEY_VALUE);
|
|
1524
1460
|
return;
|
|
1525
1461
|
}
|
|
1526
|
-
applyRow(el, row);
|
|
1527
1462
|
const key = row[item.key];
|
|
1463
|
+
applyRow(el, row);
|
|
1528
1464
|
if (key !== undefined && key !== null) {
|
|
1529
1465
|
setStamp(el, ATTR_KEY_VALUE, String(key));
|
|
1530
1466
|
}
|
|
@@ -1563,9 +1499,9 @@ export function keptAnswer(root, query) {
|
|
|
1563
1499
|
function rowsOptions(state, item) {
|
|
1564
1500
|
const declared = state.answers.get(item.query) ?? item;
|
|
1565
1501
|
return {
|
|
1566
|
-
order: declared.order,
|
|
1502
|
+
order: sortOf(state, item.query) ?? declared.order,
|
|
1567
1503
|
types: declared.types,
|
|
1568
|
-
|
|
1504
|
+
filters: filtersOf(state, item.query),
|
|
1569
1505
|
doc: state.doc,
|
|
1570
1506
|
};
|
|
1571
1507
|
}
|
|
@@ -1619,38 +1555,41 @@ function keep(answers, item) {
|
|
|
1619
1555
|
rows: patched(before.rows ?? [], item.key, item.patch),
|
|
1620
1556
|
});
|
|
1621
1557
|
}
|
|
1622
|
-
/** The
|
|
1623
|
-
function
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1558
|
+
/** The queries whose sort or filters an answer changes. */
|
|
1559
|
+
function changedQueries(state, item) {
|
|
1560
|
+
const rowsOf = (answer) => {
|
|
1561
|
+
const map = new Map();
|
|
1562
|
+
for (const row of answer?.rows ?? []) {
|
|
1563
|
+
const query = row[SORT_COLUMN_QUERY];
|
|
1564
|
+
if (typeof query === "string")
|
|
1565
|
+
map.set(query, row);
|
|
1566
|
+
}
|
|
1567
|
+
return map;
|
|
1568
|
+
};
|
|
1569
|
+
const before = rowsOf(state.answers.get(item.query));
|
|
1570
|
+
const after = rowsOf(item);
|
|
1571
|
+
const queries = new Set();
|
|
1572
|
+
for (const query of new Set([...before.keys(), ...after.keys()])) {
|
|
1573
|
+
if (JSON.stringify(before.get(query)) !== JSON.stringify(after.get(query))) {
|
|
1574
|
+
queries.add(query);
|
|
1575
|
+
}
|
|
1630
1576
|
}
|
|
1631
|
-
return
|
|
1632
|
-
}
|
|
1633
|
-
/** Whether an element's order or filter reads any of the columns. */
|
|
1634
|
-
function reads(el, columns) {
|
|
1635
|
-
const order = el.getAttribute(ATTR_ORDER);
|
|
1636
|
-
return ((order !== null && columns.has(order)) ||
|
|
1637
|
-
filtered(el).some((column) => columns.has(column)));
|
|
1577
|
+
return queries;
|
|
1638
1578
|
}
|
|
1639
1579
|
/**
|
|
1640
|
-
* Place again the rows of every
|
|
1641
|
-
*
|
|
1642
|
-
*
|
|
1643
|
-
* for nothing.
|
|
1580
|
+
* Place again the rows of every query whose sort or filters changed. Each
|
|
1581
|
+
* lands its query's kept answer again, whole, so it shows what a round trip
|
|
1582
|
+
* carrying the change would have, and the server is asked for nothing.
|
|
1644
1583
|
*/
|
|
1645
|
-
function rechoose(root, state,
|
|
1646
|
-
for (const
|
|
1647
|
-
|
|
1584
|
+
function rechoose(root, state, queries) {
|
|
1585
|
+
for (const query of queries) {
|
|
1586
|
+
const item = state.answers.get(query);
|
|
1587
|
+
if (!item || item.kind === KIND_ROW)
|
|
1648
1588
|
continue;
|
|
1649
1589
|
for (const el of everywhere(root, `[${ATTR_QUERY}="${query}"]`)) {
|
|
1650
1590
|
if (!state.landed.has(el) || el.closest(LEAVING))
|
|
1651
1591
|
continue;
|
|
1652
|
-
|
|
1653
|
-
landItem(el, item, rowsOptions(state, item));
|
|
1592
|
+
landItem(el, item, rowsOptions(state, item));
|
|
1654
1593
|
}
|
|
1655
1594
|
}
|
|
1656
1595
|
}
|
|
@@ -1677,14 +1616,16 @@ function fillArrivals(root, state) {
|
|
|
1677
1616
|
/**
|
|
1678
1617
|
* Land response items. Every answer arrives through here, and is kept for
|
|
1679
1618
|
* `root`, so that what arrives after it is filled from it — see forget().
|
|
1680
|
-
* An answer to `lb-
|
|
1681
|
-
* everything has landed, each custom element placed since the last
|
|
1682
|
-
* is attached.
|
|
1619
|
+
* An answer to `lb-sort` or `lb-filters` places again the rows it changes.
|
|
1620
|
+
* Once everything has landed, each custom element placed since the last
|
|
1621
|
+
* landing is attached.
|
|
1683
1622
|
*/
|
|
1684
1623
|
export function applyResponse(root, items, options = {}) {
|
|
1685
1624
|
const state = keptFor(root);
|
|
1686
1625
|
for (const item of items) {
|
|
1687
|
-
const
|
|
1626
|
+
const changed = item.query === SORT_QUERY || item.query === FILTERS_QUERY
|
|
1627
|
+
? changedQueries(state, item)
|
|
1628
|
+
: new Set();
|
|
1688
1629
|
keep(state.answers, item);
|
|
1689
1630
|
const targets = everywhere(root, `[${ATTR_QUERY}="${item.query}"]`).filter((el) => !el.closest(LEAVING));
|
|
1690
1631
|
if (targets.length === 0 &&
|
|
@@ -1699,8 +1640,8 @@ export function applyResponse(root, items, options = {}) {
|
|
|
1699
1640
|
requested: options.requested,
|
|
1700
1641
|
});
|
|
1701
1642
|
}
|
|
1702
|
-
if (
|
|
1703
|
-
rechoose(root, state,
|
|
1643
|
+
if (changed.size > 0)
|
|
1644
|
+
rechoose(root, state, changed);
|
|
1704
1645
|
}
|
|
1705
1646
|
fillArrivals(root, state);
|
|
1706
1647
|
attachPlaced();
|