@loadbare/app 0.23.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/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 +115 -51
- 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 +4 -3
- package/docs/reference/chrome.md +42 -0
- package/docs/reference/custom-elements.md +9 -4
- package/docs/reference/data-binding.md +79 -61
- package/docs/reference/names.md +10 -7
- package/docs/reference/page-files.md +8 -4
- package/docs/testing.md +3 -3
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +23 -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 +79 -61
- package/skills/loadbare-app/references/miswarts.md +4 -3
- package/skills/loadbare-app/references/names.md +10 -7
- package/skills/loadbare-app/references/page-files.md +8 -4
package/dist/core/lb-types.js
CHANGED
|
@@ -4,6 +4,18 @@ import { FILTER_OPERATORS, LB_RESERVED_PREFIX, LB_ROW_REQUESTS, ORDER_DESCENDING
|
|
|
4
4
|
export function isPatch(result) {
|
|
5
5
|
return !Array.isArray(result);
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* The query parms of a URL as a row of `lb-url`: a parm the URL carries
|
|
9
|
+
* once is a string, and one it repeats is every value it carries, in order.
|
|
10
|
+
*/
|
|
11
|
+
export function parmsRow(parms) {
|
|
12
|
+
const row = {};
|
|
13
|
+
for (const name of new Set(parms.keys())) {
|
|
14
|
+
const all = parms.getAll(name);
|
|
15
|
+
row[name] = all.length === 1 ? all[0] : all;
|
|
16
|
+
}
|
|
17
|
+
return row;
|
|
18
|
+
}
|
|
7
19
|
/** Whether a name is one of the requests Loadbare provides. */
|
|
8
20
|
export function isRowRequest(name) {
|
|
9
21
|
return LB_ROW_REQUESTS.includes(name);
|
|
@@ -21,10 +33,27 @@ export function isRowRequest(name) {
|
|
|
21
33
|
export function withQueryParms(search, parms) {
|
|
22
34
|
const next = new URLSearchParams(search);
|
|
23
35
|
for (const [name, value] of Object.entries(parms)) {
|
|
24
|
-
if (value
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
if (!Array.isArray(value)) {
|
|
37
|
+
if (value === "")
|
|
38
|
+
next.delete(name);
|
|
39
|
+
else
|
|
40
|
+
next.set(name, value);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
// Several values repeat the parm, as an HTML form sends them. Values
|
|
44
|
+
// already there are left where they stand, so the URL is unchanged.
|
|
45
|
+
const kept = value.filter((v) => v !== "");
|
|
46
|
+
const had = next.getAll(name);
|
|
47
|
+
if (kept.length === had.length && kept.every((v, i) => v === had[i])) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (kept.length === 1) {
|
|
51
|
+
next.set(name, kept[0]);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
next.delete(name);
|
|
55
|
+
for (const v of kept)
|
|
56
|
+
next.append(name, v);
|
|
28
57
|
}
|
|
29
58
|
return next;
|
|
30
59
|
}
|
|
@@ -224,10 +253,11 @@ export function passes(value, terms, type) {
|
|
|
224
253
|
*/
|
|
225
254
|
export function nextLocation(page, parms, row) {
|
|
226
255
|
const named = {};
|
|
256
|
+
const text = (value) => value === null || value === undefined ? "" : String(value);
|
|
227
257
|
for (const [name, value] of Object.entries(row)) {
|
|
228
258
|
if (name.startsWith(LB_RESERVED_PREFIX))
|
|
229
259
|
continue;
|
|
230
|
-
named[name] = value
|
|
260
|
+
named[name] = Array.isArray(value) ? value.map(text) : text(value);
|
|
231
261
|
}
|
|
232
262
|
const path = row[URL_COLUMN_PATH];
|
|
233
263
|
const next = typeof path === "string" ? pageNameFor(path) : page;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-types.js","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n\n// The wire vocabulary. Shared by browser and server.\n\nimport {\n COLUMN_TYPES,\n KIND_ROW,\n FILTER_OPERATORS,\n KIND_ROWS,\n LB_RESERVED_PREFIX,\n LB_ROW_REQUESTS,\n ORDER_DESCENDING,\n URL_COLUMN_PATH,\n} from \"./lb-constants.js\";\n\n/**\n * One row: its columns, by name.\n *\n * A value is whatever the server serialized. The hub knows nothing about its\n * type and enforces nothing: it hands each value to the browser untouched,\n * and the browser renders it as it sees fit. What a number, a date or a null\n * should look like is the application's decision, made in the query.\n */\nexport type Row = Record<string, unknown>;\n\n/**\n * Part of a `rows` query's answer — see docs/reference/server.md, \"Queries\".\n *\n * Rows named here are added or updated, keys in `drop` are removed, and\n * anything unnamed is left alone. The hub places what it names by the\n * query's order. Add and remove are one result rather than two because the\n * interesting cases are both at once — a row whose sort key changed has to\n * move, a swap is one out and one in — and two messages would paint the\n * intermediate state.\n */\nexport interface Patch {\n rows?: Row[];\n drop?: unknown[];\n}\n\n/** A type a `rows` query may declare for a column — see COLUMN_TYPES. */\nexport type ColumnType = (typeof COLUMN_TYPES)[number];\n\n/** The types a `rows` query declares for its columns, by column. */\nexport type ColumnTypes = Record<string, ColumnType>;\n\n/** A query's kind, declared by the server and carried by every answer. */\nexport type Kind = typeof KIND_ROW | typeof KIND_ROWS;\n\n/**\n * What a `rows` query answers with: all rows, or a patch. `Array.isArray`\n * tells the two apart. All rows arrive in the order the server chose, which\n * is the order they show in when the query declares none.\n */\nexport type RowsResult = Row[] | Patch;\n\nexport function isPatch(result: RowsResult): result is Patch {\n return !Array.isArray(result);\n}\n\n/**\n * What any one query answers with, as the application writes it. A `row`\n * query that finds no row answers `null`.\n */\nexport type HubResult = Row | RowsResult | null;\n\n/**\n * What application code hands the engine: answers by query name. The engine\n * turns each into a response item, adding the kind and key the query\n * declared.\n */\nexport type HubData = Record<string, HubResult>;\n\n/**\n * One query's answer on the wire: its name, its kind, its key's column name,\n * and exactly one of a row, all rows, or a patch. A patch answers a `rows`\n * query only.\n *\n * A `rows` query also carries the order and column types it declared, once,\n * on the answer that loads the page. The hub keeps them with the answer for\n * every answer after it.\n */\nexport interface ResponseItem {\n query: string;\n kind: Kind;\n key: string;\n row?: Row | null;\n rows?: Row[];\n patch?: Patch;\n order?: string;\n types?: ColumnTypes;\n}\n\n/** Every response the server sends: a page load and a request alike. */\nexport type HubResponse = ResponseItem[];\n\n/**\n * Every request has one shape. The name picks the handler; the rest is\n * present when the hub found it.\n */\nexport interface HubRequest {\n name: string;\n query?: string;\n key?: string;\n /**\n * The rows a request acts on together, by key: those the user selected,\n * for a request that names no one row. Never sent beside `key`.\n */\n keys?: string[];\n values?: Record<string, string>;\n /**\n * The rows of the queries nested in a form, by query name, each with the\n * values of its own controls: a set of rows gathered beside the form's\n * own values.\n */\n rows?: Record<string, GatheredRow[]>;\n}\n\n/** One nested row a form gathered: its key, and its controls' values. */\nexport interface GatheredRow {\n key: string;\n values: Record<string, string>;\n}\n\n/**\n * The detail of the `lb-request-done` event: the request as the hub\n * completed and sent it, and what came of it. `items` is every response\n * item that landed because of it, in the order it landed. `error` is set\n * when the request's own round trip failed, and then nothing landed.\n */\nexport interface HubRequestDone {\n request: HubRequest;\n items: ResponseItem[];\n error?: Error;\n /** Set beside `error` when the handler refused the request. */\n refusal?: HubRefusal;\n}\n\n/**\n * Why a handler refused a request, in words written for the user: a\n * message for the request as a whole, a message per column the request\n * gathered, or both. Sent with status 422; any other failure sends nothing.\n */\nexport interface HubRefusal {\n message?: string;\n columns?: Record<string, string>;\n}\n\n/** The fields a handler receives: the request, less its name. */\nexport type RequestFields = Omit<HubRequest, \"name\">;\n\n/** Whether a name is one of the requests Loadbare provides. */\nexport function isRowRequest(name: string): boolean {\n return (LB_ROW_REQUESTS as readonly string[]).includes(name);\n}\n\n/**\n * A query string with some parms set, and the rest as they were. An empty\n * value takes its parm out, since empty is every parm's default and a\n * default is left out of the URL.\n *\n * One rule on both sides of the wire: the hub applies it to the address bar,\n * and the server to the query string it loads the page at after a request\n * returned `lb-url`. The two must agree, or what the page shows would differ\n * from what the address bar says.\n */\nexport function withQueryParms(\n search: URLSearchParams,\n parms: Record<string, string>,\n): URLSearchParams {\n const next = new URLSearchParams(search);\n for (const [name, value] of Object.entries(parms)) {\n if (value === \"\") next.delete(name);\n else next.set(name, value);\n }\n return next;\n}\n\n/**\n * A path names a page by its stub, and nothing finer. The bare path resolves\n * to `index`, the same convention a static file server follows.\n */\nexport function pageNameFor(path: string): string {\n const name = path.replace(/^\\/+|\\/+$/g, \"\");\n return name === \"\" ? \"index\" : name;\n}\n\n/** One term of an order: a column, and whether it sorts descending. */\nexport interface OrderTerm {\n column: string;\n descending: boolean;\n}\n\n/**\n * An order's terms, from its text: columns separated by commas, each\n * descending when it begins with `-`. Throws on a term that names no column.\n * An empty text is no order.\n */\nexport function parseOrder(text: string): OrderTerm[] {\n if (text.trim() === \"\") return [];\n return text.split(\",\").map((raw) => {\n const term = raw.trim();\n const descending = term.startsWith(ORDER_DESCENDING);\n const column = descending ? term.slice(ORDER_DESCENDING.length) : term;\n if (column === \"\" || column.startsWith(ORDER_DESCENDING)) {\n throw new Error(`the order '${text}' has a term naming no column`);\n }\n return { column, descending };\n });\n}\n\n/** Strings compare as the user's language orders them. */\nconst collator = new Intl.Collator();\n\n/** How a value sorts among values of other types: by its JSON type. */\nfunction rank(value: unknown): number {\n switch (typeof value) {\n case \"boolean\":\n return 0;\n case \"number\":\n return 1;\n case \"string\":\n return 2;\n default:\n return 3;\n }\n}\n\n/**\n * Compare two column values, ascending.\n *\n * A value compares by its JSON type, and nothing is guessed: numbers\n * numerically, booleans false before true, strings as the user's language\n * orders them. A number that arrives as a string sorts as a string, so a\n * numeric sort column is sent as a JSON number. Null and a missing value\n * sort after everything, as Postgres sorts them ascending, and so first\n * descending.\n */\nexport function compareValues(a: unknown, b: unknown): number {\n const missingA = a === null || a === undefined;\n const missingB = b === null || b === undefined;\n if (missingA || missingB) return missingA === missingB ? 0 : missingA ? 1 : -1;\n const ra = rank(a);\n const rb = rank(b);\n if (ra !== rb) return ra - rb;\n if (typeof a === \"number\") return a - (b as number);\n if (typeof a === \"boolean\") return Number(a) - Number(b);\n if (typeof a === \"string\") return collator.compare(a, b as string);\n return collator.compare(JSON.stringify(a), JSON.stringify(b));\n}\n\n/**\n * A value read as a declared column type: a number for `number`, a boolean\n * for `boolean`, text for `text`. A value that is no value of its type reads\n * as missing, and compares as a null does. A column with no declared type\n * reads as it arrived.\n */\nexport function readAs(value: unknown, type: ColumnType | undefined): unknown {\n if (type === undefined || value === null || value === undefined) return value;\n switch (type) {\n case \"number\": {\n if (typeof value === \"number\") return value;\n if (typeof value !== \"string\" || value.trim() === \"\") return undefined;\n const n = Number(value);\n return Number.isNaN(n) ? undefined : n;\n }\n case \"boolean\":\n if (typeof value === \"boolean\") return value;\n return value === \"true\" ? true : value === \"false\" ? false : undefined;\n default:\n return typeof value === \"object\" ? JSON.stringify(value) : String(value);\n }\n}\n\n/** Compare two rows by an order's terms, each column read as its type. */\nexport function compareRows(\n a: Row,\n b: Row,\n terms: OrderTerm[],\n types: ColumnTypes = {},\n): number {\n for (const { column, descending } of terms) {\n const c = compareValues(\n readAs(a[column], types[column]),\n readAs(b[column], types[column]),\n );\n if (c !== 0) return descending ? -c : c;\n }\n return 0;\n}\n\n/** A filter's operator — see FILTER_OPERATORS. */\nexport type FilterOperator = (typeof FILTER_OPERATORS)[number];\n\n/** One term of a filter: an operator, and the value it compares with. */\nexport interface FilterTerm {\n operator: FilterOperator;\n value: string;\n}\n\n/** Where a filter's next term begins: whitespace before an operator. */\nconst NEXT_TERM = new RegExp(\n `\\\\s+(?=${FILTER_OPERATORS.map((op) => op.replace(/[$^*]/g, \"\\\\$&\")).join(\"|\")})`,\n);\n\n/**\n * A filter's terms, from its text: each an operator and the value after it,\n * a new term beginning wherever whitespace comes before an operator, so a\n * value may hold spaces. Text with no operator before it is `*=`. A term\n * with no value is not yet a term, and is left out, so a filter being typed\n * filters nothing until it says something. Nothing is evaluated.\n */\nexport function parseFilter(text: string): FilterTerm[] {\n const terms: FilterTerm[] = [];\n for (const raw of text.trim().split(NEXT_TERM)) {\n const operator =\n FILTER_OPERATORS.find((op) => raw.startsWith(op)) ?? \"*=\";\n const value = raw.slice(raw.startsWith(operator) ? operator.length : 0);\n if (value.trim() !== \"\") terms.push({ operator, value: value.trim() });\n }\n return terms;\n}\n\n/**\n * The filter's value as the column's own type, so a comparison is the one\n * the order makes: a number for a number, a boolean for a boolean, text for\n * anything else. Undefined when the text is no value of that type.\n */\nfunction asTypeOf(text: string, like: unknown): unknown {\n if (typeof like === \"number\") {\n const n = Number(text);\n return text.trim() === \"\" || Number.isNaN(n) ? undefined : n;\n }\n if (typeof like === \"boolean\") {\n return text === \"true\" ? true : text === \"false\" ? false : undefined;\n }\n return text;\n}\n\n/**\n * Whether a column's value passes one term.\n *\n * `^=`, `$=` and `*=` read the value as text, ignoring case: it begins with,\n * ends with or contains the term's value. The rest compare as the order\n * compares, with the column read as its declared type, if it has one, and\n * the term's value read as the column's type. A missing value, and one that\n * is no value of its declared type, passes only `!=`.\n */\nexport function passesTerm(\n value: unknown,\n { operator, value: text }: FilterTerm,\n type?: ColumnType,\n): boolean {\n if (value === null || value === undefined) return operator === \"!=\";\n if (operator === \"^=\" || operator === \"$=\" || operator === \"*=\") {\n const have = String(value).toLocaleLowerCase();\n const want = text.toLocaleLowerCase();\n if (operator === \"^=\") return have.startsWith(want);\n if (operator === \"$=\") return have.endsWith(want);\n return have.includes(want);\n }\n const read = readAs(value, type);\n const typed = read === undefined ? undefined : asTypeOf(text, read);\n if (typed === undefined) return operator === \"!=\";\n const c = compareValues(read, typed);\n switch (operator) {\n case \"=\":\n return c === 0;\n case \"!=\":\n return c !== 0;\n case \"<\":\n return c < 0;\n case \"<=\":\n return c <= 0;\n case \">\":\n return c > 0;\n default:\n return c >= 0;\n }\n}\n\n/** Whether a column's value passes every term of a filter. */\nexport function passes(\n value: unknown,\n terms: FilterTerm[],\n type?: ColumnType,\n): boolean {\n return terms.every((term) => passesTerm(value, term, type));\n}\n\n/**\n * Where a row for `lb-url` leads, from a page and its query parms.\n *\n * A column whose name begins with `lb-` is Loadbare's, and never a query\n * parm; every other column is one. An `lb-path` naming another page enters\n * it with only the parms the row names. Otherwise the named parms are set,\n * or taken out when empty, and the rest are kept.\n *\n * One rule on both sides of the wire: the server loads the page it names,\n * and the hub writes the URL it names. The two must agree, or what the page\n * shows would differ from what the address bar says.\n */\nexport function nextLocation(\n page: string,\n parms: URLSearchParams,\n row: Row,\n): { page: string; parms: URLSearchParams; moved: boolean } {\n const named: Record<string, string> = {};\n for (const [name, value] of Object.entries(row)) {\n if (name.startsWith(LB_RESERVED_PREFIX)) continue;\n named[name] = value === null || value === undefined ? \"\" : String(value);\n }\n const path = row[URL_COLUMN_PATH];\n const next = typeof path === \"string\" ? pageNameFor(path) : page;\n const moved = next !== page;\n return {\n page: next,\n parms: withQueryParms(moved ? new URLSearchParams() : parms, named),\n moved,\n };\n}\n\n/**\n * What the hub tells a custom element it did, in its own terms rather than\n * the browser's: a custom element implements any of these in place of\n * `connectedCallback` and its kin, which the builder refuses.\n *\n * Each is a method on an element the hub does not own, so it carries the\n * `lb` prefix Loadbare reserves for exactly that — see\n * docs/reference/custom-elements.md.\n */\nexport interface HubLifecycle {\n /**\n * Called once, after the hub has placed the element and every column has\n * landed on it. An element under an `lb-show` that is off is placed inside\n * its template, so it may not be rendered.\n */\n lbAttached?(): void;\n /**\n * Called once after a result has landed on an element holding rows, with\n * every row in place and every stamp set. The hub reconciles and places\n * every row itself, by the query's order, so a custom element reacts to\n * rows and never places them.\n */\n lbRowsLanded?(): void;\n /**\n * Called once a request committed by this element, or by one inside it,\n * has finished, innermost first and before `lb-request-done` is\n * dispatched. `origin` is the element that committed.\n */\n lbRequestDone?(detail: HubRequestDone, origin: Element): void;\n /** Called once, after the hub has removed the element. */\n lbDetached?(): void;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"lb-types.js","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n\n// The wire vocabulary. Shared by browser and server.\n\nimport {\n COLUMN_TYPES,\n KIND_ROW,\n FILTER_OPERATORS,\n KIND_ROWS,\n LB_RESERVED_PREFIX,\n LB_ROW_REQUESTS,\n ORDER_DESCENDING,\n URL_COLUMN_PATH,\n} from \"./lb-constants.js\";\n\n/**\n * One row: its columns, by name.\n *\n * A value is whatever the server serialized. The hub knows nothing about its\n * type and enforces nothing: it hands each value to the browser untouched,\n * and the browser renders it as it sees fit. What a number, a date or a null\n * should look like is the application's decision, made in the query.\n */\nexport type Row = Record<string, unknown>;\n\n/**\n * Part of a `rows` query's answer — see docs/reference/server.md, \"Queries\".\n *\n * Rows named here are added or updated, keys in `drop` are removed, and\n * anything unnamed is left alone. The hub places what it names by the\n * query's order. Add and remove are one result rather than two because the\n * interesting cases are both at once — a row whose sort key changed has to\n * move, a swap is one out and one in — and two messages would paint the\n * intermediate state.\n */\nexport interface Patch {\n rows?: Row[];\n drop?: unknown[];\n}\n\n/** A type a `rows` query may declare for a column — see COLUMN_TYPES. */\nexport type ColumnType = (typeof COLUMN_TYPES)[number];\n\n/** The types a `rows` query declares for its columns, by column. */\nexport type ColumnTypes = Record<string, ColumnType>;\n\n/** A query's kind, declared by the server and carried by every answer. */\nexport type Kind = typeof KIND_ROW | typeof KIND_ROWS;\n\n/**\n * What a `rows` query answers with: all rows, or a patch. `Array.isArray`\n * tells the two apart. All rows arrive in the order the server chose, which\n * is the order they show in when the query declares none.\n */\nexport type RowsResult = Row[] | Patch;\n\nexport function isPatch(result: RowsResult): result is Patch {\n return !Array.isArray(result);\n}\n\n/**\n * What any one query answers with, as the application writes it. A `row`\n * query that finds no row answers `null`.\n */\nexport type HubResult = Row | RowsResult | null;\n\n/**\n * What application code hands the engine: answers by query name. The engine\n * turns each into a response item, adding the kind and key the query\n * declared.\n */\nexport type HubData = Record<string, HubResult>;\n\n/**\n * One query's answer on the wire: its name, its kind, its key's column name,\n * and exactly one of a row, all rows, or a patch. A patch answers a `rows`\n * query only.\n *\n * A `rows` query also carries the order and column types it declared, once,\n * on the answer that loads the page. The hub keeps them with the answer for\n * every answer after it.\n */\nexport interface ResponseItem {\n query: string;\n kind: Kind;\n key: string;\n row?: Row | null;\n rows?: Row[];\n patch?: Patch;\n order?: string;\n types?: ColumnTypes;\n}\n\n/** Every response the server sends: a page load and a request alike. */\nexport type HubResponse = ResponseItem[];\n\n/**\n * Every request has one shape. The name picks the handler; the rest is\n * present when the hub found it.\n */\nexport interface HubRequest {\n name: string;\n query?: string;\n key?: string;\n /**\n * The rows a request acts on together, by key: those the user selected,\n * for a request that names no one row. Never sent beside `key`.\n */\n keys?: string[];\n /**\n * The gathered controls' values, by column. A column of `lb-url` may hold\n * several, as a URL repeats a parm; every other column holds one.\n */\n values?: Record<string, ParmValue>;\n /**\n * The rows of the queries nested in a form, by query name, each with the\n * values of its own controls: a set of rows gathered beside the form's\n * own values.\n */\n rows?: Record<string, GatheredRow[]>;\n}\n\n/** One nested row a form gathered: its key, and its controls' values. */\nexport interface GatheredRow {\n key: string;\n values: Record<string, string>;\n}\n\n/**\n * What a control gathers: one value, or several from a control that holds\n * several, which only a column of `lb-url` takes.\n */\nexport type ParmValue = string | string[];\n\n/**\n * The query parms of a URL as a row of `lb-url`: a parm the URL carries\n * once is a string, and one it repeats is every value it carries, in order.\n */\nexport function parmsRow(parms: URLSearchParams): Row {\n const row: Row = {};\n for (const name of new Set(parms.keys())) {\n const all = parms.getAll(name);\n row[name] = all.length === 1 ? all[0] : all;\n }\n return row;\n}\n\n/**\n * The detail of the `lb-request-done` event: the request as the hub\n * completed and sent it, and what came of it. `items` is every response\n * item that landed because of it, in the order it landed. `error` is set\n * when the request's own round trip failed, and then nothing landed.\n */\nexport interface HubRequestDone {\n request: HubRequest;\n items: ResponseItem[];\n error?: Error;\n /** Set beside `error` when the handler refused the request. */\n refusal?: HubRefusal;\n}\n\n/**\n * Why a handler refused a request, in words written for the user: a\n * message for the request as a whole, a message per column the request\n * gathered, or both. Sent with status 422; any other failure sends nothing.\n */\nexport interface HubRefusal {\n message?: string;\n columns?: Record<string, string>;\n}\n\n/** The fields a handler receives: the request, less its name. */\nexport type RequestFields = Omit<HubRequest, \"name\" | \"values\"> & {\n values?: Record<string, string>;\n};\n\n/** Whether a name is one of the requests Loadbare provides. */\nexport function isRowRequest(name: string): boolean {\n return (LB_ROW_REQUESTS as readonly string[]).includes(name);\n}\n\n/**\n * A query string with some parms set, and the rest as they were. An empty\n * value takes its parm out, since empty is every parm's default and a\n * default is left out of the URL.\n *\n * One rule on both sides of the wire: the hub applies it to the address bar,\n * and the server to the query string it loads the page at after a request\n * returned `lb-url`. The two must agree, or what the page shows would differ\n * from what the address bar says.\n */\nexport function withQueryParms(\n search: URLSearchParams,\n parms: Record<string, ParmValue>,\n): URLSearchParams {\n const next = new URLSearchParams(search);\n for (const [name, value] of Object.entries(parms)) {\n if (!Array.isArray(value)) {\n if (value === \"\") next.delete(name);\n else next.set(name, value);\n continue;\n }\n // Several values repeat the parm, as an HTML form sends them. Values\n // already there are left where they stand, so the URL is unchanged.\n const kept = value.filter((v) => v !== \"\");\n const had = next.getAll(name);\n if (kept.length === had.length && kept.every((v, i) => v === had[i])) {\n continue;\n }\n if (kept.length === 1) {\n next.set(name, kept[0]!);\n continue;\n }\n next.delete(name);\n for (const v of kept) next.append(name, v);\n }\n return next;\n}\n\n/**\n * A path names a page by its stub, and nothing finer. The bare path resolves\n * to `index`, the same convention a static file server follows.\n */\nexport function pageNameFor(path: string): string {\n const name = path.replace(/^\\/+|\\/+$/g, \"\");\n return name === \"\" ? \"index\" : name;\n}\n\n/** One term of an order: a column, and whether it sorts descending. */\nexport interface OrderTerm {\n column: string;\n descending: boolean;\n}\n\n/**\n * An order's terms, from its text: columns separated by commas, each\n * descending when it begins with `-`. Throws on a term that names no column.\n * An empty text is no order.\n */\nexport function parseOrder(text: string): OrderTerm[] {\n if (text.trim() === \"\") return [];\n return text.split(\",\").map((raw) => {\n const term = raw.trim();\n const descending = term.startsWith(ORDER_DESCENDING);\n const column = descending ? term.slice(ORDER_DESCENDING.length) : term;\n if (column === \"\" || column.startsWith(ORDER_DESCENDING)) {\n throw new Error(`the order '${text}' has a term naming no column`);\n }\n return { column, descending };\n });\n}\n\n/** Strings compare as the user's language orders them. */\nconst collator = new Intl.Collator();\n\n/** How a value sorts among values of other types: by its JSON type. */\nfunction rank(value: unknown): number {\n switch (typeof value) {\n case \"boolean\":\n return 0;\n case \"number\":\n return 1;\n case \"string\":\n return 2;\n default:\n return 3;\n }\n}\n\n/**\n * Compare two column values, ascending.\n *\n * A value compares by its JSON type, and nothing is guessed: numbers\n * numerically, booleans false before true, strings as the user's language\n * orders them. A number that arrives as a string sorts as a string, so a\n * numeric sort column is sent as a JSON number. Null and a missing value\n * sort after everything, as Postgres sorts them ascending, and so first\n * descending.\n */\nexport function compareValues(a: unknown, b: unknown): number {\n const missingA = a === null || a === undefined;\n const missingB = b === null || b === undefined;\n if (missingA || missingB) return missingA === missingB ? 0 : missingA ? 1 : -1;\n const ra = rank(a);\n const rb = rank(b);\n if (ra !== rb) return ra - rb;\n if (typeof a === \"number\") return a - (b as number);\n if (typeof a === \"boolean\") return Number(a) - Number(b);\n if (typeof a === \"string\") return collator.compare(a, b as string);\n return collator.compare(JSON.stringify(a), JSON.stringify(b));\n}\n\n/**\n * A value read as a declared column type: a number for `number`, a boolean\n * for `boolean`, text for `text`. A value that is no value of its type reads\n * as missing, and compares as a null does. A column with no declared type\n * reads as it arrived.\n */\nexport function readAs(value: unknown, type: ColumnType | undefined): unknown {\n if (type === undefined || value === null || value === undefined) return value;\n switch (type) {\n case \"number\": {\n if (typeof value === \"number\") return value;\n if (typeof value !== \"string\" || value.trim() === \"\") return undefined;\n const n = Number(value);\n return Number.isNaN(n) ? undefined : n;\n }\n case \"boolean\":\n if (typeof value === \"boolean\") return value;\n return value === \"true\" ? true : value === \"false\" ? false : undefined;\n default:\n return typeof value === \"object\" ? JSON.stringify(value) : String(value);\n }\n}\n\n/** Compare two rows by an order's terms, each column read as its type. */\nexport function compareRows(\n a: Row,\n b: Row,\n terms: OrderTerm[],\n types: ColumnTypes = {},\n): number {\n for (const { column, descending } of terms) {\n const c = compareValues(\n readAs(a[column], types[column]),\n readAs(b[column], types[column]),\n );\n if (c !== 0) return descending ? -c : c;\n }\n return 0;\n}\n\n/** A filter's operator — see FILTER_OPERATORS. */\nexport type FilterOperator = (typeof FILTER_OPERATORS)[number];\n\n/** One term of a filter: an operator, and the value it compares with. */\nexport interface FilterTerm {\n operator: FilterOperator;\n value: string;\n}\n\n/** Where a filter's next term begins: whitespace before an operator. */\nconst NEXT_TERM = new RegExp(\n `\\\\s+(?=${FILTER_OPERATORS.map((op) => op.replace(/[$^*]/g, \"\\\\$&\")).join(\"|\")})`,\n);\n\n/**\n * A filter's terms, from its text: each an operator and the value after it,\n * a new term beginning wherever whitespace comes before an operator, so a\n * value may hold spaces. Text with no operator before it is `*=`. A term\n * with no value is not yet a term, and is left out, so a filter being typed\n * filters nothing until it says something. Nothing is evaluated.\n */\nexport function parseFilter(text: string): FilterTerm[] {\n const terms: FilterTerm[] = [];\n for (const raw of text.trim().split(NEXT_TERM)) {\n const operator =\n FILTER_OPERATORS.find((op) => raw.startsWith(op)) ?? \"*=\";\n const value = raw.slice(raw.startsWith(operator) ? operator.length : 0);\n if (value.trim() !== \"\") terms.push({ operator, value: value.trim() });\n }\n return terms;\n}\n\n/**\n * The filter's value as the column's own type, so a comparison is the one\n * the order makes: a number for a number, a boolean for a boolean, text for\n * anything else. Undefined when the text is no value of that type.\n */\nfunction asTypeOf(text: string, like: unknown): unknown {\n if (typeof like === \"number\") {\n const n = Number(text);\n return text.trim() === \"\" || Number.isNaN(n) ? undefined : n;\n }\n if (typeof like === \"boolean\") {\n return text === \"true\" ? true : text === \"false\" ? false : undefined;\n }\n return text;\n}\n\n/**\n * Whether a column's value passes one term.\n *\n * `^=`, `$=` and `*=` read the value as text, ignoring case: it begins with,\n * ends with or contains the term's value. The rest compare as the order\n * compares, with the column read as its declared type, if it has one, and\n * the term's value read as the column's type. A missing value, and one that\n * is no value of its declared type, passes only `!=`.\n */\nexport function passesTerm(\n value: unknown,\n { operator, value: text }: FilterTerm,\n type?: ColumnType,\n): boolean {\n if (value === null || value === undefined) return operator === \"!=\";\n if (operator === \"^=\" || operator === \"$=\" || operator === \"*=\") {\n const have = String(value).toLocaleLowerCase();\n const want = text.toLocaleLowerCase();\n if (operator === \"^=\") return have.startsWith(want);\n if (operator === \"$=\") return have.endsWith(want);\n return have.includes(want);\n }\n const read = readAs(value, type);\n const typed = read === undefined ? undefined : asTypeOf(text, read);\n if (typed === undefined) return operator === \"!=\";\n const c = compareValues(read, typed);\n switch (operator) {\n case \"=\":\n return c === 0;\n case \"!=\":\n return c !== 0;\n case \"<\":\n return c < 0;\n case \"<=\":\n return c <= 0;\n case \">\":\n return c > 0;\n default:\n return c >= 0;\n }\n}\n\n/** Whether a column's value passes every term of a filter. */\nexport function passes(\n value: unknown,\n terms: FilterTerm[],\n type?: ColumnType,\n): boolean {\n return terms.every((term) => passesTerm(value, term, type));\n}\n\n/**\n * Where a row for `lb-url` leads, from a page and its query parms.\n *\n * A column whose name begins with `lb-` is Loadbare's, and never a query\n * parm; every other column is one. An `lb-path` naming another page enters\n * it with only the parms the row names. Otherwise the named parms are set,\n * or taken out when empty, and the rest are kept.\n *\n * One rule on both sides of the wire: the server loads the page it names,\n * and the hub writes the URL it names. The two must agree, or what the page\n * shows would differ from what the address bar says.\n */\nexport function nextLocation(\n page: string,\n parms: URLSearchParams,\n row: Row,\n): { page: string; parms: URLSearchParams; moved: boolean } {\n const named: Record<string, ParmValue> = {};\n const text = (value: unknown): string =>\n value === null || value === undefined ? \"\" : String(value);\n for (const [name, value] of Object.entries(row)) {\n if (name.startsWith(LB_RESERVED_PREFIX)) continue;\n named[name] = Array.isArray(value) ? value.map(text) : text(value);\n }\n const path = row[URL_COLUMN_PATH];\n const next = typeof path === \"string\" ? pageNameFor(path) : page;\n const moved = next !== page;\n return {\n page: next,\n parms: withQueryParms(moved ? new URLSearchParams() : parms, named),\n moved,\n };\n}\n\n/**\n * What the hub tells a custom element it did, in its own terms rather than\n * the browser's: a custom element implements any of these in place of\n * `connectedCallback` and its kin, which the builder refuses.\n *\n * Each is a method on an element the hub does not own, so it carries the\n * `lb` prefix Loadbare reserves for exactly that — see\n * docs/reference/custom-elements.md.\n */\nexport interface HubLifecycle {\n /**\n * Called once, after the hub has placed the element and every column has\n * landed on it. An element under an `lb-show` that is off is placed inside\n * its template, so it may not be rendered.\n */\n lbAttached?(): void;\n /**\n * Called once after a result has landed on an element holding rows, with\n * every row in place and every stamp set. The hub reconciles and places\n * every row itself, by the query's order, so a custom element reacts to\n * rows and never places them.\n */\n lbRowsLanded?(): void;\n /**\n * Called once a request committed by this element, or by one inside it,\n * has finished, innermost first and before `lb-request-done` is\n * dispatched. `origin` is the element that committed.\n */\n lbRequestDone?(detail: HubRequestDone, origin: Element): void;\n /** Called once, after the hub has removed the element. */\n lbDetached?(): void;\n}\n"]}
|
package/dist/hub/lb-apply.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ColumnTypes, type ResponseItem, type Row, type RowsResult } from "../core/lb-types.js";
|
|
1
|
+
import { type ColumnTypes, type ParmValue, type ResponseItem, type Row, type RowsResult } from "../core/lb-types.js";
|
|
2
2
|
/** A control's value, whether it is native or a custom element. */
|
|
3
3
|
export interface Control extends Element {
|
|
4
4
|
value: string;
|
|
@@ -25,9 +25,10 @@ export declare function isControl(el: Element): el is Control;
|
|
|
25
25
|
/**
|
|
26
26
|
* Gather a control into `values` under its column. Of the radio buttons
|
|
27
27
|
* naming one column, the one checked gives the value, and `""` stands when
|
|
28
|
-
* none is.
|
|
28
|
+
* none is. Of the checkboxes in a set naming one column, the checked ones
|
|
29
|
+
* give their values, and none checked gives none.
|
|
29
30
|
*/
|
|
30
|
-
export declare function gatherInto(values: Record<string,
|
|
31
|
+
export declare function gatherInto(values: Record<string, ParmValue>, column: string, el: Control): void;
|
|
31
32
|
/**
|
|
32
33
|
* Root if it matches, then every descendant in root's query that does.
|
|
33
34
|
* Gathering finds columns through here. It does not reach into an absent
|
|
@@ -80,13 +81,11 @@ export interface RowsOptions {
|
|
|
80
81
|
types?: ColumnTypes;
|
|
81
82
|
/** The user's choices: the row of `lb-choices`. */
|
|
82
83
|
choices?: Row;
|
|
83
|
-
/** The landing answers a request from this page. */
|
|
84
|
-
requested?: boolean;
|
|
85
84
|
/**
|
|
86
|
-
* The
|
|
87
|
-
*
|
|
85
|
+
* The landing answers a request from this page, so a side the list
|
|
86
|
+
* places by hand leaves its rows where they stand.
|
|
88
87
|
*/
|
|
89
|
-
|
|
88
|
+
requested?: boolean;
|
|
90
89
|
/**
|
|
91
90
|
* Fill every row, changed or not: the element has not been filled for
|
|
92
91
|
* real before, so what its rows show cannot be trusted.
|
|
@@ -118,10 +117,12 @@ export interface RowsOptions {
|
|
|
118
117
|
* the same rows again moves nothing. A row a filter the user chose does not
|
|
119
118
|
* pass is `hidden`.
|
|
120
119
|
*
|
|
121
|
-
* A landing
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
120
|
+
* A landing that answers a request leaves alone each side `lb-manual` names
|
|
121
|
+
* on the element, whole or a patch. Under `order`, nothing already showing
|
|
122
|
+
* moves, and a new row goes last in its group. Under `filter`, a row the
|
|
123
|
+
* answer changed or created stays showing. A row left where the order or
|
|
124
|
+
* the filter would not leave it is stamped unplaced until the rows are next
|
|
125
|
+
* placed.
|
|
125
126
|
*/
|
|
126
127
|
export declare function applyRows(el: Element, template: HTMLTemplateElement, keyColumn: string, result: RowsResult, options?: RowsOptions): void;
|
|
127
128
|
/** The answer kept for a query, as all of its rows. */
|
|
@@ -142,12 +143,6 @@ export interface LandOptions {
|
|
|
142
143
|
quiet?: readonly string[];
|
|
143
144
|
/** The items answer a request from this page. */
|
|
144
145
|
requested?: boolean;
|
|
145
|
-
/**
|
|
146
|
-
* The element that sent the request, or the live row a form that sent it
|
|
147
|
-
* is pointed at. The list it was sent from inside keeps its rows where
|
|
148
|
-
* they stand.
|
|
149
|
-
*/
|
|
150
|
-
origin?: Element;
|
|
151
146
|
}
|
|
152
147
|
/**
|
|
153
148
|
* Land response items. Every answer arrives through here, and is kept for
|
|
@@ -158,9 +153,10 @@ export interface LandOptions {
|
|
|
158
153
|
*/
|
|
159
154
|
export declare function applyResponse(root: ParentNode, items: ResponseItem[], options?: LandOptions): void;
|
|
160
155
|
/**
|
|
161
|
-
* Place an element's rows by the order again, from the answer
|
|
162
|
-
* query, with no round trip: what the user asks for once an
|
|
163
|
-
* rows out of
|
|
156
|
+
* Place an element's rows by the order and the filter again, from the answer
|
|
157
|
+
* kept for its query, with no round trip: what the user asks for once an
|
|
158
|
+
* answer has left rows out of place. False when the element shows no rows
|
|
159
|
+
* the hub holds.
|
|
164
160
|
*/
|
|
165
|
-
export declare function
|
|
161
|
+
export declare function placeAgain(root: ParentNode, el: Element): boolean;
|
|
166
162
|
//# sourceMappingURL=lb-apply.d.ts.map
|
|
@@ -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
|
@@ -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) {
|
|
@@ -163,6 +204,26 @@ function land(el, value) {
|
|
|
163
204
|
}
|
|
164
205
|
setStamp(el, ATTR_COLUMN_VALUE, String(value));
|
|
165
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
|
+
}
|
|
166
227
|
/**
|
|
167
228
|
* Set an element's text, unless it already holds exactly that text. Writing
|
|
168
229
|
* the same text again replaces the text node, which is two mutations and a
|
|
@@ -660,7 +721,7 @@ function listFor(el, template) {
|
|
|
660
721
|
data: {},
|
|
661
722
|
placed: {},
|
|
662
723
|
hidden: false,
|
|
663
|
-
|
|
724
|
+
unplaced: false,
|
|
664
725
|
};
|
|
665
726
|
list.byKey.set(key, entry);
|
|
666
727
|
list.root.rows.push(entry);
|
|
@@ -700,7 +761,7 @@ function create(list, doc, key) {
|
|
|
700
761
|
data: {},
|
|
701
762
|
placed: {},
|
|
702
763
|
hidden: false,
|
|
703
|
-
|
|
764
|
+
unplaced: false,
|
|
704
765
|
};
|
|
705
766
|
list.byKey.set(key, entry);
|
|
706
767
|
return entry;
|
|
@@ -1136,10 +1197,12 @@ function differs(before, after) {
|
|
|
1136
1197
|
* the same rows again moves nothing. A row a filter the user chose does not
|
|
1137
1198
|
* pass is `hidden`.
|
|
1138
1199
|
*
|
|
1139
|
-
* A landing
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
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.
|
|
1143
1206
|
*/
|
|
1144
1207
|
export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
1145
1208
|
const list = listFor(el, template);
|
|
@@ -1224,39 +1287,39 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1224
1287
|
a.i - b.i)
|
|
1225
1288
|
.map(({ entry }) => entry);
|
|
1226
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);
|
|
1227
1296
|
let order;
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
// 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.
|
|
1231
1301
|
for (const entry of created)
|
|
1232
1302
|
entry.placed = entry.data;
|
|
1233
1303
|
order = heldOrder(stayed, created, groupTerms(list, terms));
|
|
1234
|
-
|
|
1235
|
-
for (const entry of order)
|
|
1236
|
-
if (entry.unsorted)
|
|
1237
|
-
touched.add(entry);
|
|
1238
|
-
const out = movedRows(order, sort(order), touched);
|
|
1239
|
-
for (const entry of order) {
|
|
1240
|
-
const fails = !passing(entry.data);
|
|
1241
|
-
// A row the user just changed does not vanish from under them.
|
|
1242
|
-
const kept = touched.has(entry) && !entry.hidden;
|
|
1243
|
-
filterRow(entry, fails && !kept);
|
|
1244
|
-
// A row stands in the group it was placed in until it is placed again.
|
|
1245
|
-
const moves = regrouped(entry, list, terms);
|
|
1246
|
-
if (!moves)
|
|
1247
|
-
entry.placed = entry.data;
|
|
1248
|
-
entry.unsorted = out.has(entry) || (kept && fails) || moves;
|
|
1249
|
-
}
|
|
1304
|
+
out = movedRows(order, sort(order), touched);
|
|
1250
1305
|
}
|
|
1251
1306
|
else {
|
|
1252
1307
|
order = sort(whole
|
|
1253
1308
|
? [...arriving.keys()].map((key) => list.byKey.get(key))
|
|
1254
1309
|
: [...stayed, ...created]);
|
|
1255
|
-
|
|
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)
|
|
1256
1320
|
entry.placed = entry.data;
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
}
|
|
1321
|
+
// A row nobody sees stands nowhere the user would notice.
|
|
1322
|
+
entry.unplaced = !hides && (out.has(entry) || kept || moves);
|
|
1260
1323
|
}
|
|
1261
1324
|
const moved = movedRows(stayed, order, changed);
|
|
1262
1325
|
const retired = arrange(list, doc, order, terms);
|
|
@@ -1281,18 +1344,18 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1281
1344
|
}
|
|
1282
1345
|
}
|
|
1283
1346
|
stamps.done(doc);
|
|
1284
|
-
let
|
|
1347
|
+
let unplaced = 0;
|
|
1285
1348
|
for (const entry of order) {
|
|
1286
|
-
if (entry.el.hasAttribute(
|
|
1287
|
-
entry.el.toggleAttribute(
|
|
1349
|
+
if (entry.el.hasAttribute(ATTR_ROW_UNPLACED) !== entry.unplaced) {
|
|
1350
|
+
entry.el.toggleAttribute(ATTR_ROW_UNPLACED, entry.unplaced);
|
|
1288
1351
|
}
|
|
1289
|
-
if (entry.
|
|
1290
|
-
|
|
1352
|
+
if (entry.unplaced)
|
|
1353
|
+
unplaced++;
|
|
1291
1354
|
}
|
|
1292
1355
|
const rows = finish(list.root, options.types ?? {});
|
|
1293
1356
|
// Counted from what the hub holds, which is what the rows show.
|
|
1294
1357
|
setStamp(el, ATTR_QUERY_ROW_COUNT, String(rows.length));
|
|
1295
|
-
setStamp(el,
|
|
1358
|
+
setStamp(el, ATTR_QUERY_UNPLACED_COUNT, String(unplaced));
|
|
1296
1359
|
if (terms.length === 0)
|
|
1297
1360
|
el.removeAttribute(ATTR_QUERY_ORDER);
|
|
1298
1361
|
else
|
|
@@ -1304,6 +1367,14 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1304
1367
|
depart(departing);
|
|
1305
1368
|
host.lbRowsLanded?.();
|
|
1306
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
|
+
}
|
|
1307
1378
|
/** The order's terms a list's groups break on, outermost first. */
|
|
1308
1379
|
function groupTerms(list, terms) {
|
|
1309
1380
|
return terms.slice(0, list.levels.length);
|
|
@@ -1533,13 +1604,6 @@ function fillArrivals(root, state) {
|
|
|
1533
1604
|
}
|
|
1534
1605
|
}
|
|
1535
1606
|
}
|
|
1536
|
-
/**
|
|
1537
|
-
* Whether a request from `origin` was sent from inside `el`'s list: `el` is
|
|
1538
|
-
* the nearest element carrying `lb-query` around it.
|
|
1539
|
-
*/
|
|
1540
|
-
function sentFrom(el, origin) {
|
|
1541
|
-
return origin?.parentElement?.closest(`[${ATTR_QUERY}]`) === el;
|
|
1542
|
-
}
|
|
1543
1607
|
/**
|
|
1544
1608
|
* Land response items. Every answer arrives through here, and is kept for
|
|
1545
1609
|
* `root`, so that what arrives after it is filled from it — see forget().
|
|
@@ -1563,7 +1627,6 @@ export function applyResponse(root, items, options = {}) {
|
|
|
1563
1627
|
landItem(el, item, {
|
|
1564
1628
|
...rowsOptions(state, item),
|
|
1565
1629
|
requested: options.requested,
|
|
1566
|
-
held: sentFrom(el, options.origin),
|
|
1567
1630
|
});
|
|
1568
1631
|
}
|
|
1569
1632
|
if (choices.size > 0)
|
|
@@ -1573,11 +1636,12 @@ export function applyResponse(root, items, options = {}) {
|
|
|
1573
1636
|
attachPlaced();
|
|
1574
1637
|
}
|
|
1575
1638
|
/**
|
|
1576
|
-
* Place an element's rows by the order again, from the answer
|
|
1577
|
-
* query, with no round trip: what the user asks for once an
|
|
1578
|
-
* 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.
|
|
1579
1643
|
*/
|
|
1580
|
-
export function
|
|
1644
|
+
export function placeAgain(root, el) {
|
|
1581
1645
|
const state = kept.get(root);
|
|
1582
1646
|
const item = state?.answers.get(el.getAttribute(ATTR_QUERY) ?? "");
|
|
1583
1647
|
if (!state || !item || item.kind === KIND_ROW || !state.landed.has(el)) {
|