@loadbare/app 0.13.0 → 0.15.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.
Files changed (51) hide show
  1. package/dist/build/assemble.d.ts.map +1 -1
  2. package/dist/build/assemble.js +31 -1
  3. package/dist/build/assemble.js.map +1 -1
  4. package/dist/build/elements.d.ts +12 -2
  5. package/dist/build/elements.d.ts.map +1 -1
  6. package/dist/build/elements.js +44 -10
  7. package/dist/build/elements.js.map +1 -1
  8. package/dist/core/lb-constants.d.ts +13 -2
  9. package/dist/core/lb-constants.d.ts.map +1 -1
  10. package/dist/core/lb-constants.js +50 -3
  11. package/dist/core/lb-constants.js.map +1 -1
  12. package/dist/core/lb-types.d.ts +52 -8
  13. package/dist/core/lb-types.d.ts.map +1 -1
  14. package/dist/core/lb-types.js.map +1 -1
  15. package/dist/hub/lb-apply.d.ts +22 -3
  16. package/dist/hub/lb-apply.d.ts.map +1 -1
  17. package/dist/hub/lb-apply.js +82 -37
  18. package/dist/hub/lb-apply.js.map +1 -1
  19. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  20. package/dist/hub/lb-hub.browser.js +382 -26
  21. package/dist/hub/lb-hub.browser.js.map +1 -1
  22. package/dist/hub/lb-lifecycle.d.ts +40 -0
  23. package/dist/hub/lb-lifecycle.d.ts.map +1 -0
  24. package/dist/hub/lb-lifecycle.js +140 -0
  25. package/dist/hub/lb-lifecycle.js.map +1 -0
  26. package/dist/server/lb-express.d.ts +13 -3
  27. package/dist/server/lb-express.d.ts.map +1 -1
  28. package/dist/server/lb-express.js +30 -12
  29. package/dist/server/lb-express.js.map +1 -1
  30. package/dist/server/lb-server.d.ts +31 -1
  31. package/dist/server/lb-server.d.ts.map +1 -1
  32. package/dist/server/lb-server.js +112 -2
  33. package/dist/server/lb-server.js.map +1 -1
  34. package/docs/TECHREF-1.0.md +343 -68
  35. package/docs/comparison.md +13 -9
  36. package/docs/possible-ideas.md +152 -26
  37. package/docs/reference/custom-elements.md +203 -88
  38. package/docs/reference/data-binding.md +310 -11
  39. package/docs/reference/page-files.md +76 -3
  40. package/docs/reference/server.md +20 -0
  41. package/docs/reference/widgets.md +35 -12
  42. package/docs/roadmap.md +9 -2
  43. package/docs/testing.md +2 -2
  44. package/package.json +1 -1
  45. package/skills/loadbare-app/SKILL.md +70 -23
  46. package/skills/loadbare-app/references/TECHREF-1.0.md +343 -68
  47. package/skills/loadbare-app/references/custom-elements.md +203 -88
  48. package/skills/loadbare-app/references/data-binding.md +310 -11
  49. package/skills/loadbare-app/references/page-files.md +76 -3
  50. package/skills/loadbare-app/references/server.md +20 -0
  51. package/skills/loadbare-app/references/widgets.md +35 -12
@@ -67,7 +67,23 @@ export interface HubRequest {
67
67
  name: string;
68
68
  query?: string;
69
69
  key?: string;
70
+ /**
71
+ * The rows a request acts on together, by key: those the user selected,
72
+ * for a request that names no one row. Never sent beside `key`.
73
+ */
74
+ keys?: string[];
70
75
  values?: Record<string, string>;
76
+ /**
77
+ * The rows of the queries nested in a form, by query name, each with the
78
+ * values of its own controls: a set of rows gathered beside the form's
79
+ * own values.
80
+ */
81
+ rows?: Record<string, GatheredRow[]>;
82
+ }
83
+ /** One nested row a form gathered: its key, and its controls' values. */
84
+ export interface GatheredRow {
85
+ key: string;
86
+ values: Record<string, string>;
71
87
  }
72
88
  /**
73
89
  * The detail of the `lb-request-done` event: the request as the hub
@@ -79,6 +95,17 @@ export interface HubRequestDone {
79
95
  request: HubRequest;
80
96
  items: ResponseItem[];
81
97
  error?: Error;
98
+ /** Set beside `error` when the handler refused the request. */
99
+ refusal?: HubRefusal;
100
+ }
101
+ /**
102
+ * Why a handler refused a request, in words written for the user: a
103
+ * message for the request as a whole, a message per column the request
104
+ * gathered, or both. Sent with status 422; any other failure sends nothing.
105
+ */
106
+ export interface HubRefusal {
107
+ message?: string;
108
+ columns?: Record<string, string>;
82
109
  }
83
110
  /** The fields a handler receives: the request, less its name. */
84
111
  export type RequestFields = Omit<HubRequest, "name">;
@@ -144,18 +171,35 @@ export declare function nextLocation(page: string, parms: URLSearchParams, row:
144
171
  moved: boolean;
145
172
  };
146
173
  /**
147
- * The hook a custom element holding rows may implement.
174
+ * What the hub tells a custom element it did, in its own terms rather than
175
+ * the browser's: a custom element implements any of these in place of
176
+ * `connectedCallback` and its kin, which the builder refuses.
148
177
  *
149
- * The hub reconciles and places every row itself, by the query's order, so a
150
- * custom element reacts to rows and never places them. It is a method on an
151
- * element the hub does not own, so it carries the `lb` prefix Loadbare
152
- * reserves for exactly that — see docs/reference/custom-elements.md.
178
+ * Each is a method on an element the hub does not own, so it carries the
179
+ * `lb` prefix Loadbare reserves for exactly that — see
180
+ * docs/reference/custom-elements.md.
153
181
  */
154
- export interface RowsHost {
182
+ export interface HubLifecycle {
183
+ /**
184
+ * Called once, after the hub has placed the element and every column has
185
+ * landed on it. An element under an `lb-show` that is off is placed inside
186
+ * its template, so it may not be rendered.
187
+ */
188
+ lbAttached?(): void;
155
189
  /**
156
- * Called once after a result has landed, with every row in place and
157
- * every stamp set: for anything the element derives from its rows.
190
+ * Called once after a result has landed on an element holding rows, with
191
+ * every row in place and every stamp set. The hub reconciles and places
192
+ * every row itself, by the query's order, so a custom element reacts to
193
+ * rows and never places them.
158
194
  */
159
195
  lbRowsLanded?(): void;
196
+ /**
197
+ * Called once a request committed by this element, or by one inside it,
198
+ * has finished, innermost first and before `lb-request-done` is
199
+ * dispatched. `origin` is the element that committed.
200
+ */
201
+ lbRequestDone?(detail: HubRequestDone, origin: Element): void;
202
+ /** Called once, after the hub has removed the element. */
203
+ lbDetached?(): void;
160
204
  }
161
205
  //# sourceMappingURL=lb-types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lb-types.d.ts","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lb-types.d.ts","sourceRoot":"","sources":["../../core/lb-types.ts"],"names":[],"mappings":"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"}
@@ -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 KIND_ROW,\n KIND_ROWS,\n LB_RESERVED_PREFIX,\n LB_ROW_REQUESTS,\n ORDER_DESCENDING,\n ORDER_PARM_PREFIX,\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 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/** What any one query answers with, as the application writes it. */\nexport type HubResult = Row | RowsResult;\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 what it declared about order: the order the\n * server gives it, and whether the query reads the order parm itself.\n */\nexport interface ResponseItem {\n query: string;\n kind: Kind;\n key: string;\n row?: Row;\n rows?: Row[];\n patch?: Patch;\n order?: string;\n serverSortedByUrl?: boolean;\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 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}\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/**\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, except an order parm, which is one; 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 */\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/** The query parm carrying the user's order for a query. */\nexport function orderParm(query: string): string {\n return ORDER_PARM_PREFIX + query;\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/** Compare two rows by an order's terms. */\nexport function compareRows(a: Row, b: Row, terms: OrderTerm[]): number {\n for (const { column, descending } of terms) {\n const c = compareValues(a[column], b[column]);\n if (c !== 0) return descending ? -c : c;\n }\n return 0;\n}\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 (\n name.startsWith(LB_RESERVED_PREFIX) &&\n !name.startsWith(ORDER_PARM_PREFIX)\n ) {\n continue;\n }\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 * The hook a custom element holding rows may implement.\n *\n * The hub reconciles and places every row itself, by the query's order, so a\n * custom element reacts to rows and never places them. It is a method on an\n * element the hub does not own, so it carries the `lb` prefix Loadbare\n * reserves for exactly that — see docs/reference/custom-elements.md.\n */\nexport interface RowsHost {\n /**\n * Called once after a result has landed, with every row in place and\n * every stamp set: for anything the element derives from its rows.\n */\n lbRowsLanded?(): 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 KIND_ROW,\n KIND_ROWS,\n LB_RESERVED_PREFIX,\n LB_ROW_REQUESTS,\n ORDER_DESCENDING,\n ORDER_PARM_PREFIX,\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 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/** What any one query answers with, as the application writes it. */\nexport type HubResult = Row | RowsResult;\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 what it declared about order: the order the\n * server gives it, and whether the query reads the order parm itself.\n */\nexport interface ResponseItem {\n query: string;\n kind: Kind;\n key: string;\n row?: Row;\n rows?: Row[];\n patch?: Patch;\n order?: string;\n serverSortedByUrl?: boolean;\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/**\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, except an order parm, which is one; 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 */\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/** The query parm carrying the user's order for a query. */\nexport function orderParm(query: string): string {\n return ORDER_PARM_PREFIX + query;\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/** Compare two rows by an order's terms. */\nexport function compareRows(a: Row, b: Row, terms: OrderTerm[]): number {\n for (const { column, descending } of terms) {\n const c = compareValues(a[column], b[column]);\n if (c !== 0) return descending ? -c : c;\n }\n return 0;\n}\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 (\n name.startsWith(LB_RESERVED_PREFIX) &&\n !name.startsWith(ORDER_PARM_PREFIX)\n ) {\n continue;\n }\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"]}
@@ -9,11 +9,17 @@ export interface Control extends Element {
9
9
  */
10
10
  export declare function isFormAssociated(el: Element): boolean;
11
11
  /**
12
- * A control: an `<input>`, `<select>` or `<textarea>`, or a form-associated
13
- * custom element with a `value` property. Landing and gathering draw the
14
- * same line, so a value read back is the one that landed there.
12
+ * A control: an `<input>`, `<select>` or `<textarea>`, a checkbox, or a
13
+ * form-associated custom element with a `value` property. Landing and
14
+ * gathering draw the same line, so a value read back is the one that landed
15
+ * there.
15
16
  */
16
17
  export declare function isControl(el: Element): el is Control;
18
+ /**
19
+ * What a control gathers: a checkbox `"true"` or `"false"`, so unchecked
20
+ * still says so, and any other control its `value`.
21
+ */
22
+ export declare function controlValue(el: Control): string;
17
23
  /**
18
24
  * Root if it matches, then every descendant in root's query that does.
19
25
  * Gathering finds columns through here. It does not reach into an absent
@@ -21,6 +27,11 @@ export declare function isControl(el: Element): el is Control;
21
27
  * showing.
22
28
  */
23
29
  export declare function within(root: Element, selector: string): Element[];
30
+ /**
31
+ * Tick or clear an `lb-select` checkbox, and stamp the live row it selects
32
+ * to match.
33
+ */
34
+ export declare function select(box: Element, on: boolean): void;
24
35
  /**
25
36
  * Fill from one row: every `lb-column` and `lb-show` in root's query.
26
37
  *
@@ -31,6 +42,12 @@ export declare function within(root: Element, selector: string): Element[];
31
42
  * alone here.
32
43
  */
33
44
  export declare function applyRow(root: Element, row: Row, liveRow?: boolean, stop?: string): void;
45
+ /**
46
+ * A row naming every column root's query shows, each null: what landing it
47
+ * leaves is root as it stands with no row. Absent branches are counted, so a
48
+ * column only an absent element names is cleared too.
49
+ */
50
+ export declare function blankRow(root: Element): Row;
34
51
  /**
35
52
  * The row template: the first `<template>` among the element's descendants,
36
53
  * outside any nested `lb-query`. A template holding an absent branch is not a
@@ -80,6 +97,8 @@ export interface LandOptions {
80
97
  /**
81
98
  * Land response items. Every answer arrives through here, and is kept for
82
99
  * `root`, so that what arrives after it is filled from it — see forget().
100
+ * Once everything has landed, each custom element placed since the last
101
+ * landing is attached.
83
102
  */
84
103
  export declare function applyResponse(root: ParentNode, items: ResponseItem[], options?: LandOptions): void;
85
104
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"lb-apply.d.ts","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"AAsEA,OAAO,EAOL,KAAK,YAAY,EACjB,KAAK,GAAG,EAER,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AA0B7B,mEAAmE;AACnE,MAAM,WAAW,OAAQ,SAAQ,OAAO;IACtC,KAAK,EAAE,MAAM,CAAC;CACf;AAiBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAGrD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,IAAI,OAAO,CAEpD;AA6DD;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAMjE;AAoED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,OAAO,EACb,GAAG,EAAE,GAAG,EACR,OAAO,UAAQ,EACf,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI,CAuBN;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CAKlE;AAshBD,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,EAAE,EAAE,OAAO,EACX,QAAQ,EAAE,mBAAmB,EAC7B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,WAAgB,GACxB,IAAI,CAkJN;AAmDD,uDAAuD;AACvD,wBAAgB,UAAU,CACxB,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,GACZ,YAAY,GAAG,SAAS,CAE1B;AAOD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAE7C;AAyED,+BAA+B;AAC/B,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACjD,iDAAiD;IACjD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,YAAY,EAAE,EACrB,OAAO,GAAE,WAAgB,GACxB,IAAI,CAuBN;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAK/D"}
1
+ {"version":3,"file":"lb-apply.d.ts","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"AAyEA,OAAO,EAQL,KAAK,YAAY,EACjB,KAAK,GAAG,EACR,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AA+B7B,mEAAmE;AACnE,MAAM,WAAW,OAAQ,SAAQ,OAAO;IACtC,KAAK,EAAE,MAAM,CAAC;CACf;AA4BD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAGrD;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,IAAI,OAAO,CAEpD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAEhD;AAkED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAMjE;AA0CD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAGtD;AAiCD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,OAAO,EACb,GAAG,EAAE,GAAG,EACR,OAAO,UAAQ,EACf,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI,CAuBN;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,GAAG,CAU3C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CAKlE;AAyhBD,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,EAAE,EAAE,OAAO,EACX,QAAQ,EAAE,mBAAmB,EAC7B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,WAAgB,GACxB,IAAI,CAoJN;AAmDD,uDAAuD;AACvD,wBAAgB,UAAU,CACxB,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,GACZ,YAAY,GAAG,SAAS,CAE1B;AAOD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAE7C;AAwDD,+BAA+B;AAC/B,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACjD,iDAAiD;IACjD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,YAAY,EAAE,EACrB,OAAO,GAAE,WAAgB,GACxB,IAAI,CAwBN;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAK/D"}
@@ -20,9 +20,10 @@
20
20
  * arrives after that query's answer — a picker in a new live row, a ghost
21
21
  * row a custom element built — is filled from the kept answer at the end of
22
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. A custom element
24
- * shipped inside an absent branch has not upgraded, so it is filled again
25
- * once it has, and places its rows itself.
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.
26
27
  *
27
28
  * A `rows` query is placed by its order: the one the URL carries for it, or
28
29
  * the one the server declared, or, with neither, the order its rows arrived
@@ -35,16 +36,13 @@
35
36
  * landing reaches in there as it reaches everywhere else, so a branch that
36
37
  * returns is already current. Nothing but landing reaches in.
37
38
  */
38
- import { AGGREGATES, ATTR_COLUMN, 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, } from "../core/lb-constants.js";
39
+ import { AGGREGATES, ATTR_COLUMN, 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";
39
40
  import { compareRows, compareValues, isPatch, parseOrder, } from "../core/lb-types.js";
40
- /** The template holding an absent branch — see ATTR_SHOW. */
41
- const PARKED = `template[${ATTR_SHOW}]`;
41
+ import { attachPlaced, detachRemoved, LEAVING, PARKED, placed, upgradeAbsent, } from "./lb-lifecycle.js";
42
42
  /** A DocumentFragment's nodeType. Tier 3 has no `Node` global to read it from. */
43
43
  const FRAGMENT_NODE = 11;
44
44
  /** An Element's nodeType, for the same reason. */
45
45
  const ELEMENT_NODE = 1;
46
- /** A row or a group on its way out. */
47
- const LEAVING = `[${ATTR_ROW_LEAVING}], [${ATTR_GROUP_LEAVING}]`;
48
46
  /**
49
47
  * What a group's heading and aggregates stop at: the rows and groups inside
50
48
  * it, live or leaving, which are filled from rows of their own.
@@ -52,7 +50,10 @@ const LEAVING = `[${ATTR_ROW_LEAVING}], [${ATTR_GROUP_LEAVING}]`;
52
50
  const SCAFFOLD_STOP = `${LIVE_ROW}, ${LIVE_GROUP}, ${LEAVING}`;
53
51
  /** An element carrying any aggregate. */
54
52
  const AGGREGATE = AGGREGATES.map((name) => `[${name}]`).join(", ");
55
- /** The input types whose state is not their `value`, or cannot be set. */
53
+ /**
54
+ * The input types whose state is not their `value`, or cannot be set. A
55
+ * checkbox's state is `checked`, and it is a control by its own rule.
56
+ */
56
57
  const UNLANDED_INPUTS = ["checkbox", "radio", "file"];
57
58
  /**
58
59
  * A native control whose state is its `value`: a `<select>`, a
@@ -67,6 +68,14 @@ function isNativeControl(el) {
67
68
  return (el.localName === "input" &&
68
69
  !UNLANDED_INPUTS.includes(el.type));
69
70
  }
71
+ /**
72
+ * A checkbox, whose state is `checked` rather than `value`. HTML's quirk,
73
+ * and so the hub's: it lands and gathers by its checked state, so every other
74
+ * part of the vocabulary treats it as any control.
75
+ */
76
+ function isCheckbox(el) {
77
+ return (el.localName === "input" && el.type === "checkbox");
78
+ }
70
79
  /**
71
80
  * A form-associated custom element with a `value` property. HTML marks one
72
81
  * with `static formAssociated = true` on its class, and a control has a value.
@@ -76,12 +85,20 @@ export function isFormAssociated(el) {
76
85
  return ctor.formAssociated === true && "value" in el;
77
86
  }
78
87
  /**
79
- * A control: an `<input>`, `<select>` or `<textarea>`, or a form-associated
80
- * custom element with a `value` property. Landing and gathering draw the
81
- * same line, so a value read back is the one that landed there.
88
+ * A control: an `<input>`, `<select>` or `<textarea>`, a checkbox, or a
89
+ * form-associated custom element with a `value` property. Landing and
90
+ * gathering draw the same line, so a value read back is the one that landed
91
+ * there.
82
92
  */
83
93
  export function isControl(el) {
84
- return isNativeControl(el) || isFormAssociated(el);
94
+ return isNativeControl(el) || isCheckbox(el) || isFormAssociated(el);
95
+ }
96
+ /**
97
+ * What a control gathers: a checkbox `"true"` or `"false"`, so unchecked
98
+ * still says so, and any other control its `value`.
99
+ */
100
+ export function controlValue(el) {
101
+ return isCheckbox(el) ? String(el.checked) : el.value;
85
102
  }
86
103
  /**
87
104
  * Set an element from a column. A control's `value` is set. A custom element
@@ -93,7 +110,9 @@ function land(el, value) {
93
110
  // Untouched, whatever the server sent: the browser decides what a
94
111
  // non-string looks like. See Row in core/lb-types.ts.
95
112
  if (value === null || value === undefined) {
96
- if (isControl(el))
113
+ if (isCheckbox(el))
114
+ el.checked = false;
115
+ else if (isControl(el))
97
116
  el.value = "";
98
117
  else if (!el.localName.includes("-") && el.localName !== "input") {
99
118
  el.textContent = "";
@@ -101,7 +120,12 @@ function land(el, value) {
101
120
  el.removeAttribute(ATTR_COLUMN_VALUE);
102
121
  return;
103
122
  }
104
- if (isControl(el)) {
123
+ if (isCheckbox(el)) {
124
+ // Checked by the rule `lb-show` is on by: only null and false are off,
125
+ // and no string is read.
126
+ el.checked = value !== false;
127
+ }
128
+ else if (isControl(el)) {
105
129
  el.value = value;
106
130
  }
107
131
  else if (el.localName === "input") {
@@ -186,6 +210,14 @@ function everywhere(root, selector) {
186
210
  }
187
211
  return found;
188
212
  }
213
+ /**
214
+ * Tick or clear an `lb-select` checkbox, and stamp the live row it selects
215
+ * to match.
216
+ */
217
+ export function select(box, on) {
218
+ box.checked = on;
219
+ box.closest(LIVE_ROW)?.toggleAttribute(ATTR_ROW_SELECTED, on);
220
+ }
189
221
  /**
190
222
  * Put an element in the document or take it out, as its `lb-show` column
191
223
  * says.
@@ -208,6 +240,12 @@ function present(el, on) {
208
240
  }
209
241
  if (on || el.parentNode?.nodeType === FRAGMENT_NODE)
210
242
  return;
243
+ // A selection hidden is a selection abandoned: a tick kept out of sight
244
+ // would come back stale, and nothing else could clear it.
245
+ for (const box of [el, ...el.querySelectorAll(`[${ATTR_SELECT}]`)]) {
246
+ if (box.hasAttribute(ATTR_SELECT))
247
+ select(box, false);
248
+ }
211
249
  const parked = el.ownerDocument.createElement("template");
212
250
  parked.setAttribute(ATTR_SHOW, el.getAttribute(ATTR_SHOW));
213
251
  el.replaceWith(parked);
@@ -244,6 +282,22 @@ export function applyRow(root, row, liveRow = false, stop) {
244
282
  }
245
283
  }
246
284
  }
285
+ /**
286
+ * A row naming every column root's query shows, each null: what landing it
287
+ * leaves is root as it stands with no row. Absent branches are counted, so a
288
+ * column only an absent element names is cleared too.
289
+ */
290
+ export function blankRow(root) {
291
+ const row = {};
292
+ for (const el of below(root, `[${ATTR_COLUMN}]`)) {
293
+ row[el.getAttribute(ATTR_COLUMN)] = null;
294
+ }
295
+ for (const el of below(root, `[${ATTR_SHOW}]`)) {
296
+ const show = el.getAttribute(ATTR_SHOW);
297
+ row[show.startsWith(SHOW_NOT) ? show.slice(SHOW_NOT.length) : show] = null;
298
+ }
299
+ return row;
300
+ }
247
301
  /**
248
302
  * The row template: the first `<template>` among the element's descendants,
249
303
  * outside any nested `lb-query`. A template holding an absent branch is not a
@@ -383,6 +437,7 @@ function depart(nodes) {
383
437
  const go = () => {
384
438
  for (const node of nodes)
385
439
  node.remove();
440
+ detachRemoved(nodes);
386
441
  };
387
442
  if (running.length === 0)
388
443
  go();
@@ -420,12 +475,14 @@ function leaveGroup(group) {
420
475
  function makeGroup(doc, template, id, term, value) {
421
476
  // Imported rather than cloned, for the reason a live row is.
422
477
  const fragment = doc.importNode(template.content, true);
478
+ upgradeAbsent(fragment, doc);
423
479
  const slot = slotIn(fragment);
424
480
  const nodes = [...fragment.childNodes];
425
481
  for (const node of nodes) {
426
482
  if (node.nodeType !== ELEMENT_NODE || node === slot)
427
483
  continue;
428
484
  const el = node;
485
+ placed(el);
429
486
  el.setAttribute(ATTR_GROUP_LIVE, "");
430
487
  if (term)
431
488
  el.setAttribute(ATTR_GROUP_COLUMN, term.column);
@@ -761,6 +818,8 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
761
818
  // until inserted, and a form-associated control would be filled as if
762
819
  // it were not one.
763
820
  row = el.ownerDocument.importNode(list.rowTemplate.content.firstElementChild, true);
821
+ upgradeAbsent(row, el.ownerDocument);
822
+ placed(row);
764
823
  row.setAttribute(ATTR_ROW_LIVE, "");
765
824
  row.setAttribute(ATTR_KEY_VALUE, key);
766
825
  shown.set(key, row);
@@ -773,9 +832,9 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
773
832
  changed: (prior?.changed ?? false) || differs(before, data),
774
833
  });
775
834
  list.data.set(row, { ...before, ...data });
776
- // Fill before insertion. The attributes are already there when the row
777
- // upgrades, which is the same thing that makes a first landing and a
778
- // refresh one operation everywhere else.
835
+ // Fill before insertion, on a row already upgraded, which is the same
836
+ // thing that makes a first landing and a refresh one operation
837
+ // everywhere else.
779
838
  applyRow(row, data, true);
780
839
  };
781
840
  if (Array.isArray(result)) {
@@ -937,22 +996,6 @@ function keep(answers, item) {
937
996
  rows: patched(before.rows ?? [], item.key, item.patch),
938
997
  });
939
998
  }
940
- /**
941
- * A custom element whose class is defined and has not yet run on it: one the
942
- * builder shipped inside an absent branch's template, where nothing upgrades.
943
- * It has none of the hooks landing calls, so rows that land on it are not
944
- * placed by it. A landing on it does not count, and once `lb-show` brings it
945
- * into the document and it upgrades, it is filled again from the kept answer.
946
- *
947
- * An element with no class, such as one defined by an element file alone,
948
- * never upgrades, and is never waiting.
949
- */
950
- function waiting(el) {
951
- const registry = globalThis
952
- .customElements;
953
- const upgraded = registry?.get(el.localName);
954
- return upgraded !== undefined && !(el instanceof upgraded);
955
- }
956
999
  /**
957
1000
  * Fill every element that names a kept query and has had nothing land on it.
958
1001
  * Filling one can build more — a live row holding a picker of its own — so
@@ -964,7 +1007,7 @@ function fillArrivals(root, state) {
964
1007
  filled = false;
965
1008
  for (const [query, item] of answers) {
966
1009
  for (const el of everywhere(root, `[${ATTR_QUERY}="${query}"]`)) {
967
- if (landed.has(el) || waiting(el) || el.closest(LEAVING))
1010
+ if (landed.has(el) || el.closest(LEAVING))
968
1011
  continue;
969
1012
  landed.add(el);
970
1013
  landItem(el, item, { order: orderOf(state, item) });
@@ -976,6 +1019,8 @@ function fillArrivals(root, state) {
976
1019
  /**
977
1020
  * Land response items. Every answer arrives through here, and is kept for
978
1021
  * `root`, so that what arrives after it is filled from it — see forget().
1022
+ * Once everything has landed, each custom element placed since the last
1023
+ * landing is attached.
979
1024
  */
980
1025
  export function applyResponse(root, items, options = {}) {
981
1026
  const state = keptFor(root);
@@ -991,8 +1036,7 @@ export function applyResponse(root, items, options = {}) {
991
1036
  continue;
992
1037
  }
993
1038
  for (const el of targets) {
994
- if (!waiting(el))
995
- state.landed.add(el);
1039
+ state.landed.add(el);
996
1040
  landItem(el, item, {
997
1041
  order: orderOf(state, item),
998
1042
  requested: options.requested,
@@ -1000,6 +1044,7 @@ export function applyResponse(root, items, options = {}) {
1000
1044
  }
1001
1045
  }
1002
1046
  fillArrivals(root, state);
1047
+ attachPlaced();
1003
1048
  }
1004
1049
  /**
1005
1050
  * Land a query's kept answer again, as all of its rows: what the hub does