@loadbare/app 0.8.2 → 0.10.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 (80) hide show
  1. package/README.md +3 -3
  2. package/dist/build/assemble.d.ts.map +1 -1
  3. package/dist/build/assemble.js +74 -66
  4. package/dist/build/assemble.js.map +1 -1
  5. package/dist/build/expand.d.ts.map +1 -1
  6. package/dist/build/expand.js +20 -19
  7. package/dist/build/expand.js.map +1 -1
  8. package/dist/build/locations.d.ts +2 -3
  9. package/dist/build/locations.d.ts.map +1 -1
  10. package/dist/build/locations.js +2 -3
  11. package/dist/build/locations.js.map +1 -1
  12. package/dist/build/pages.d.ts +3 -4
  13. package/dist/build/pages.d.ts.map +1 -1
  14. package/dist/build/pages.js +3 -4
  15. package/dist/build/pages.js.map +1 -1
  16. package/dist/core/lb-constants.d.ts +25 -23
  17. package/dist/core/lb-constants.d.ts.map +1 -1
  18. package/dist/core/lb-constants.js +95 -158
  19. package/dist/core/lb-constants.js.map +1 -1
  20. package/dist/core/lb-types.d.ts +73 -75
  21. package/dist/core/lb-types.d.ts.map +1 -1
  22. package/dist/core/lb-types.js +58 -5
  23. package/dist/core/lb-types.js.map +1 -1
  24. package/dist/hub/lb-apply.d.ts +47 -37
  25. package/dist/hub/lb-apply.d.ts.map +1 -1
  26. package/dist/hub/lb-apply.js +174 -193
  27. package/dist/hub/lb-apply.js.map +1 -1
  28. package/dist/hub/lb-hub.browser.d.ts +1 -1
  29. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  30. package/dist/hub/lb-hub.browser.js +419 -415
  31. package/dist/hub/lb-hub.browser.js.map +1 -1
  32. package/dist/server/lb-express.d.ts +20 -13
  33. package/dist/server/lb-express.d.ts.map +1 -1
  34. package/dist/server/lb-express.js +50 -52
  35. package/dist/server/lb-express.js.map +1 -1
  36. package/dist/server/lb-server.d.ts +81 -116
  37. package/dist/server/lb-server.d.ts.map +1 -1
  38. package/dist/server/lb-server.js +151 -48
  39. package/dist/server/lb-server.js.map +1 -1
  40. package/docs/TECHREF-1.0.md +893 -558
  41. package/docs/comparison.md +222 -185
  42. package/docs/prior-art.md +15 -14
  43. package/docs/reference/builder.md +9 -3
  44. package/docs/reference/chrome.md +107 -56
  45. package/docs/reference/custom-elements.md +199 -173
  46. package/docs/reference/data-binding.md +375 -370
  47. package/docs/reference/overview.md +12 -10
  48. package/docs/reference/page-files.md +161 -86
  49. package/docs/reference/server.md +13 -7
  50. package/docs/reference/widgets.md +104 -110
  51. package/docs/roadmap.md +36 -31
  52. package/docs/terms-of-art.md +57 -0
  53. package/docs/testing.md +97 -68
  54. package/docs/theory.md +92 -58
  55. package/docs/tutorials/010-pages-and-navigation.md +20 -12
  56. package/docs/tutorials/020-css.md +6 -3
  57. package/docs/tutorials/030-html-decomposition.md +9 -7
  58. package/docs/tutorials/040-displaying-data.md +30 -13
  59. package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
  60. package/docs/tutorials/060-custom-element-code.md +17 -16
  61. package/docs/tutorials/065-conditional-rendering.md +34 -23
  62. package/docs/tutorials/070-displaying-a-list.md +29 -21
  63. package/docs/tutorials/072-inserting-into-a-list.md +24 -16
  64. package/docs/tutorials/074-deleting-from-a-list.md +9 -7
  65. package/docs/tutorials/076-updating-a-list-item.md +11 -10
  66. package/docs/tutorials/080-widget-requests.md +71 -43
  67. package/docs/tutorials/090-using-widget-libraries.md +22 -22
  68. package/package.json +1 -1
  69. package/skills/loadbare-app/SKILL.md +178 -111
  70. package/skills/loadbare-app/references/TECHREF-1.0.md +893 -558
  71. package/skills/loadbare-app/references/builder.md +9 -3
  72. package/skills/loadbare-app/references/chrome.md +107 -56
  73. package/skills/loadbare-app/references/custom-elements.md +199 -173
  74. package/skills/loadbare-app/references/data-binding.md +375 -370
  75. package/skills/loadbare-app/references/overview.md +12 -10
  76. package/skills/loadbare-app/references/page-files.md +161 -86
  77. package/skills/loadbare-app/references/server.md +13 -7
  78. package/skills/loadbare-app/references/widgets.md +104 -110
  79. package/docs/analysis-accidental-complexity.md +0 -149
  80. package/docs/analysis-closed-set.md +0 -210
@@ -1,12 +1,12 @@
1
1
  # Custom Elements
2
2
 
3
- A widget is a custom element, written as a set of files sharing one tag
4
- name. It serves two purposes, and an application uses it for either or for
5
- both at once.
3
+ A custom element is written as a set of files sharing one tag name. It
4
+ serves two purposes, and an application uses it for either or for both at
5
+ once.
6
6
 
7
7
  | File | Holds |
8
8
  |--------------------------|----------------------------------|
9
- | `<tag-name>.html` | The markup the tag expands into |
9
+ | `<tag-name>.html` | The element file: the markup the tag expands into |
10
10
  | `<tag-name>.browser.ts` | The class the tag registers |
11
11
 
12
12
  Write one of the two, or both, but write at least one. A tag with neither is
@@ -26,13 +26,13 @@ either.
26
26
  Put the files anywhere under `src/`. The builder finds them by name, the
27
27
  same way it finds pages — see [The Builder](./builder.md).
28
28
 
29
- Loadbare uses light DOM throughout. A widget's markup is ordinary markup in
29
+ Loadbare uses light DOM throughout. A custom element's markup is ordinary markup in
30
30
  the document, visible in view-source, reachable by `closest()` and by the
31
31
  application's stylesheets.
32
32
 
33
33
  ## HTML
34
34
 
35
- An `.html` file named for a tag defines that tag. When the builder finds the
35
+ An element file is an `.html` file named for a tag, and defines that tag. When the builder finds the
36
36
  tag in the chrome, in a page, or in another definition, it inserts the
37
37
  definition's content as children of the tag. We call this process
38
38
  'HTML expansion'.
@@ -44,13 +44,13 @@ application's.
44
44
  ### Parameters
45
45
 
46
46
  A definition takes parameters. Three namespaces share the attributes of a
47
- widget tag, and the prefix says which namespace an attribute is in:
47
+ custom element's tag, and the prefix says which namespace an attribute is in:
48
48
 
49
- | Prefix | Belongs to | Read |
50
- |-------------|------------|-----------------------------|
51
- | `exp-` | Expansion | By the builder, at build time |
52
- | `lb-` | The hub | By Loadbare, in the browser |
53
- | No prefix | HTML | By the browser, as HTML says |
49
+ | Prefix | Belongs to | Read |
50
+ |-------------|------------|-------------------------------|
51
+ | `exp-` | The definition | By the builder, at build time |
52
+ | `lb-` | Loadbare | By the builder and the hub |
53
+ | No prefix | HTML | By the browser, as HTML says |
54
54
 
55
55
  Pass a parameter by writing `exp-<name>` on the tag. Read it in the
56
56
  definition by writing `{{<name>}}`. The prefix marks the attribute as
@@ -64,11 +64,11 @@ supplies `{{label}}`:
64
64
 
65
65
  ```html
66
66
  <!-- authored -->
67
- <note-field lb-cell="name" exp-label="Name"></note-field>
67
+ <note-field exp-label="Name"></note-field>
68
68
  ```
69
69
 
70
- Write `class`, `title`, or any other unprefixed attribute on a widget
71
- freely. They are HTML's, and never collide with a definition's parameters.
70
+ Write `class`, `title`, or any other unprefixed attribute on a custom
71
+ element freely. They are HTML's, and never collide with a definition's parameters.
72
72
 
73
73
  Write a placeholder as an entire attribute value or an entire text node.
74
74
  Nothing inside one is evaluated, and expansion has no data to branch on —
@@ -102,12 +102,12 @@ escaping.
102
102
 
103
103
  ### The slot
104
104
 
105
- `lb-slot` marks the one element in a definition whose children receive
105
+ `lb-exp-slot` marks the one element in a definition whose children receive
106
106
  whatever the author wrote inside the tag:
107
107
 
108
108
  ```html
109
109
  <!-- definition: src/note-card.html -->
110
- <article class="card"><div lb-slot></div></article>
110
+ <article class="card"><div lb-exp-slot></div></article>
111
111
  ```
112
112
 
113
113
  ```html
@@ -130,35 +130,35 @@ whatever the author wrote inside the tag:
130
130
  </note-card>
131
131
  ```
132
132
 
133
- The tag stays and the definition becomes its children. `lb-slot` itself is
134
- gone from what ships, having done its job at build time.
133
+ The tag stays and the definition becomes its children. The builder removes
134
+ `lb-exp-slot` from what ships.
135
135
 
136
- Give a definition at most one `lb-slot`. A definition with a slot and
136
+ Give a definition at most one `lb-exp-slot`. A definition with a slot and
137
137
  nothing written inside it leaves the slot empty.
138
138
 
139
- `lb-slot` is an attribute rather than an element because HTML's content
139
+ `lb-exp-slot` is an attribute rather than an element because HTML's content
140
140
  model discards foreign elements inside `<select>` and `<table>`. A slot
141
141
  marker has to survive on an element the surrounding tag already permits.
142
142
 
143
143
  ### Destinations
144
144
 
145
- `lb-template` names a destination in a definition. On an authored
146
- `<template lb-template="name">`, it names the destination that template
145
+ `lb-exp-template` names a destination in a definition. On an authored
146
+ `<template lb-exp-template="name">`, it names the destination that template
147
147
  fills:
148
148
 
149
149
  ```html
150
150
  <!-- definition: src/ledger-table.html -->
151
151
  <table>
152
152
  <caption>{{caption}}</caption>
153
- <thead lb-template="head"></thead>
154
- <tbody lb-slot></tbody>
153
+ <thead lb-exp-template="head"></thead>
154
+ <tbody lb-exp-slot></tbody>
155
155
  </table>
156
156
  ```
157
157
 
158
158
  ```html
159
159
  <!-- authored -->
160
160
  <ledger-table exp-caption="Ledger">
161
- <template lb-template="head">
161
+ <template lb-exp-template="head">
162
162
  <tr><th>Date</th><th>Amount</th></tr>
163
163
  </template>
164
164
  <tbody>
@@ -180,15 +180,15 @@ an ordinary `<thead>` in view-source — not the template element.
180
180
 
181
181
  ### Recursion
182
182
 
183
- A definition may use other widgets. Expansion repeats until nothing new
184
- appears, so a widget built out of other widgets needs nothing declared for
185
- it.
183
+ A definition may use other custom elements. Expansion repeats until nothing
184
+ new appears, so a custom element built out of others needs nothing declared
185
+ for it.
186
186
 
187
187
  Keep the definition graph acyclic. A cycle is a build error, reported as the
188
188
  path that closes it.
189
189
 
190
- Expansion runs over `<template>` contents wherever they occur, so a widget's
191
- row template ships already expanded, `{{placeholder}}` included.
190
+ Expansion runs over `<template>` contents wherever they occur, so a
191
+ definition's row template ships already expanded, `{{placeholder}}` included.
192
192
 
193
193
  ### What fails at build time
194
194
 
@@ -199,9 +199,9 @@ shipped:
199
199
  - a cycle in the definition graph
200
200
  - an `exp-` attribute the definition never declared
201
201
  - a `{{placeholder}}` spelled so that no `exp-` attribute could supply it
202
- - content written inside a tag whose definition has no `lb-slot`
203
- - more than one `lb-slot` in one definition
204
- - a `<template lb-template="name">` naming a destination the definition
202
+ - content written inside a tag whose definition has no `lb-exp-slot`
203
+ - more than one `lb-exp-slot` in one definition
204
+ - a `<template lb-exp-template="name">` naming a destination the definition
205
205
  does not have
206
206
  - two templates for the same destination
207
207
 
@@ -209,44 +209,109 @@ An unfilled `{{placeholder}}` is not among these. That is presence
209
209
  propagation working as intended, indistinguishable from an author who left
210
210
  an optional parameter out.
211
211
 
212
+
212
213
  ## Code
213
214
 
214
- A `<tag>.browser.ts` file registers that tag's class. A widget is an
215
- ordinary custom element — Loadbare imposes no base class — and it takes part
216
- in data binding through three contracts: it receives a value, it sends a
217
- request, and it accepts a set of rows.
218
-
219
- Import every attribute name from `@loadbare/app/constants`. Never write one
220
- as a string literal.
221
-
222
- | Constant | Value |
223
- |------------------|------------|
224
- | `ATTR_VALUE` | `lb-value` |
225
- | `ATTR_CELL` | `lb-cell` |
226
- | `ATTR_SHOW` | `lb-show` |
227
- | `ATTR_LIST` | `lb-list` |
228
- | `ATTR_ROW` | `lb-row` |
229
- | `ATTR_KEY` | `lb-key` |
230
- | `ATTR_KEY_VALUE` | `lb-key-value` |
231
- | `ATTR_ACTION` | `lb-action`|
232
- | `ACTION_ROW_INSERT`, `ACTION_ROW_DELETE`, `ACTION_ROW_UPDATE` | the reserved `lb-action` values |
233
- | `LB_ACTIONS` | all three of them, in one array |
234
- | `LB_RESERVED_PREFIX` | `lb-`, the prefix every reserved name begins with |
235
- | `ATTR_ROW_COUNT` | `lb-row-count`|
236
- | `LB_EVENT_NAME` | `lb-request` |
215
+ A `<tag>.browser.ts` file registers that tag's class. The class is an
216
+ ordinary custom element, and Loadbare imposes no base class. A custom element
217
+ takes part in data binding in one of four ways: it is a control, it renders
218
+ a value, it sends a request, or it holds rows.
219
+
220
+ Import every Loadbare name from `@loadbare/app/constants`, and never write
221
+ one as a string literal:
222
+
223
+ | Constant | Value |
224
+ |------------------------|----------------------|
225
+ | `ATTR_QUERY` | `lb-query` |
226
+ | `ATTR_COLUMN` | `lb-column` |
227
+ | `ATTR_SHOW` | `lb-show` |
228
+ | `ATTR_REQUEST` | `lb-request` |
229
+ | `ATTR_COLUMN_VALUE` | `lb-column-value` |
230
+ | `ATTR_KEY_VALUE` | `lb-key-value` |
231
+ | `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
232
+ | `ATTR_REQUEST_PENDING` | `lb-request-pending` |
233
+ | `ATTR_REQUEST_ERROR` | `lb-request-error` |
234
+ | `REQUEST_ROW_INSERT`, `REQUEST_ROW_UPDATE`, `REQUEST_ROW_DELETE` | The requests Loadbare provides |
235
+ | `LB_ROW_REQUESTS` | All three, in one array |
236
+ | `LB_EVENT_NAME` | `lb-request`, the event every request travels as |
237
+ | `URL_QUERY` | `lb-url` |
238
+ | `URL_COLUMN_PATH`, `URL_COLUMN_PAGE_LABEL`, `URL_COLUMN_PAGE_UNKNOWN` | `lb-path`, `lb-page-label`, `lb-page-unknown` |
239
+ | `LB_RESERVED_PREFIX` | `lb-` |
240
+
241
+ A custom element reads `lb-` attributes and never assigns one. The developer
242
+ writes them in markup, and the hub and the builder write their stamps.
243
+
244
+ Loadbare reserves method names beginning with `lb` on a custom element, for
245
+ the methods it calls; see [Holding rows](#holding-rows).
246
+
247
+ ### Being a control
248
+
249
+ A form-associated custom element with a `value` property that fires `change`
250
+ is a control, the same as an `<input>`. The hub sets its `value` from the
251
+ column its `lb-column` names, gathers its `value` back under that column,
252
+ and commits its `lb-request` on `change`:
253
+
254
+ ```ts
255
+ // src/star-rating.browser.ts
256
+ class StarRating extends HTMLElement {
257
+ static formAssociated = true;
258
+ #internals = this.attachInternals();
259
+ #value = "";
260
+
261
+ get value(): string {
262
+ return this.#value;
263
+ }
264
+
265
+ set value(value: string) {
266
+ this.#value = value ?? "";
267
+ this.#internals.setFormValue(this.#value);
268
+ this.render();
269
+ }
270
+
271
+ formResetCallback() {
272
+ this.value = "";
273
+ }
274
+
275
+ pick(stars: number) {
276
+ this.value = String(stars);
277
+ this.dispatchEvent(new Event("change", { bubbles: true }));
278
+ }
279
+
280
+ render() {
281
+ // Draw this.#value stars.
282
+ }
283
+ }
284
+
285
+ customElements.define("star-rating", StarRating);
286
+ ```
287
+
288
+ ```html
289
+ <star-rating lb-column="rating" lb-request="lb-row-update"></star-rating>
290
+ ```
291
+
292
+ Dispatch `change` with `bubbles: true`, as a native control's does, when the
293
+ user commits an edit. Setting `value` from the hub fires nothing.
294
+
295
+ A form owns a form-associated custom element the way it owns an `<input>`,
296
+ including through the HTML `form` attribute, so a form gathers it on submit.
297
+ Write `formResetCallback` to restore the default value: the hub resets a form
298
+ after a successful insert.
299
+
300
+ The `<lb-input>`, `<lb-select>`, `<lb-options>` and `<lb-picker>` widgets
301
+ are controls; see [The Basic Widget Library](./widgets.md).
237
302
 
238
303
  ### Receiving a value
239
304
 
240
- A bound cell lands on a widget as the `lb-value` attribute rather than as
241
- text — see [Data Binding](./data-binding.md#where-a-bound-value-lands).
242
- Observe it, and render the value however the widget renders things:
305
+ A custom element that is not a control keeps the content the builder placed
306
+ in it, and receives a column as its `lb-column-value` attribute. Observe it,
307
+ and render the value:
243
308
 
244
309
  ```ts
245
310
  // src/visit-count.browser.ts
246
- import { ATTR_VALUE } from "@loadbare/app/constants";
311
+ import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
247
312
 
248
313
  class VisitCount extends HTMLElement {
249
- static observedAttributes = [ATTR_VALUE];
314
+ static observedAttributes = [ATTR_COLUMN_VALUE];
250
315
 
251
316
  attributeChangedCallback(_name: string, _old: string, value: string) {
252
317
  this.textContent = `visited ${value} times`;
@@ -256,142 +321,103 @@ class VisitCount extends HTMLElement {
256
321
  customElements.define("visit-count", VisitCount);
257
322
  ```
258
323
 
259
- `value` is `null` when the attribute is removed, which the hub does to the
260
- cells a successful insert resets — see
261
- [TECHREF-1.0](./TECHREF-1.0.md#the-round-trip). Read it as nothing landed,
262
- and show the widget's default. A blank string is a value that landed.
263
-
264
- A widget carrying `lb-show`, or inside an element that does, is moved into a
265
- template while its column is off and back out when it turns on — see
266
- [Conditional rendering](./data-binding.md#conditional-rendering). Going in, it
267
- sees `disconnectedCallback` and then `adoptedCallback`; coming out,
268
- `adoptedCallback` and then `connectedCallback`. It is the same instance
269
- throughout, and it still receives `lb-value` while it is away, so write
270
- `connectedCallback` to run more than once, as a row a list reorders already
271
- requires.
272
-
273
- List `ATTR_VALUE` in `observedAttributes`, or `attributeChangedCallback`
274
- never fires. The browser calls it for an attribute already present when an
275
- element upgrades, not only for one that changes afterward, so a widget's
276
- first render and every later refresh go through the one callback. There is
277
- no separate hydration path to write.
324
+ ```html
325
+ <p lb-query="visits"><visit-count lb-column="count"></visit-count></p>
326
+ ```
327
+
328
+ List `ATTR_COLUMN_VALUE` in `observedAttributes`. The browser calls
329
+ `attributeChangedCallback` for an attribute already present when an element
330
+ upgrades, as well as for one that changes afterward, so the first render and
331
+ every later one go through the one callback.
332
+
333
+ A custom element carrying `lb-show`, or inside an element that does, is
334
+ moved into a template while its column is off and back out when it turns on
335
+ — see [Conditional rendering](./data-binding.md#conditional-rendering).
336
+ Going in, it sees `disconnectedCallback` and then `adoptedCallback`; coming
337
+ out, `adoptedCallback` and then `connectedCallback`. It is the same instance
338
+ throughout, and it still receives its column while it is away, so write
339
+ `connectedCallback` to run more than once, as a live row the hub reorders
340
+ already requires.
278
341
 
279
342
  ### Sending a request
280
343
 
281
- A widget that owns its own interaction — a `<select>`'s choice rather than a
282
- click — dispatches its own request as a bubbling `CustomEvent` named
283
- `LB_EVENT_NAME`, carrying the action and, where it wraps a control, that
284
- control's value as its `detail`:
344
+ A custom element that carries `lb-request` and is not a control commits on
345
+ `click`, as a button does. To send a request at a moment of its own, a
346
+ custom element dispatches the `lb-request` event itself, a bubbling
347
+ `CustomEvent` whose `detail` is the request:
285
348
 
286
349
  ```ts
287
350
  import { LB_EVENT_NAME } from "@loadbare/app/constants";
288
351
  import type { HubRequest } from "@loadbare/app/types";
289
352
 
290
- const detail: HubRequest = { action: "lb-row-update", value: input.value };
353
+ const detail: HubRequest = { name: "archiveOld" };
291
354
  this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
292
355
  ```
293
356
 
294
- The hub fills in the scope the widget sits in before any ancestor sees the
295
- event, and does not send a request missing a field its operation requires.
296
- See [TECHREF-1.0](./TECHREF-1.0.md#requests) for what each operation is
297
- filled with.
298
-
299
- A widget that carries `lb-cell` and sends `lb-row-insert` or `lb-row-update`
300
- sends that one cell. The hub builds `values` from the cell's name and the
301
- `value` the widget sent, or reads the control the widget wraps when it sent
302
- none.
303
-
304
- A widget that carries no `lb-cell` doesn't read its own controls. It
305
- dispatches the bare action from the row, or from anything inside it, and the
306
- hub gathers `values` from the row the event came from:
307
- the nearest `<form>`, `<tr>` or live row around the dispatching element,
308
- itself included, inside its scope. Every readable `lb-cell` in that row is
309
- gathered, wherever in the row it sits, except the cells of a scope nested in
310
- it: a picker carrying `lb-list` and `lb-cell` gives its own value, not its
311
- options'. A request from an element in no such
312
- row is not sent, and the console says to use a `<form>`.
313
-
314
- ```ts
315
- row.dispatchEvent(
316
- new CustomEvent(LB_EVENT_NAME, {
317
- bubbles: true,
318
- detail: { action: "lb-row-insert" } as HubRequest,
319
- }),
320
- );
321
- ```
322
-
323
- Values the widget supplies itself are kept, and nothing is gathered over
324
- them.
325
-
326
- When an insert succeeds, the hub resets the controls it gathered to their
327
- defaults, so a blank row for entering a new one is blank again without the
328
- widget watching the request settle. A hidden input the widget added keeps
329
- its value, since a hidden input's default is its value. Values the widget
330
- supplied in the request were not gathered, and are not reset.
357
+ The hub completes the request from where the element sits before any
358
+ ancestor sees the event, gathering as it does for an element carrying
359
+ `lb-request`; see [Gathering](./data-binding.md#gathering). A field the
360
+ element supplies in `detail` is kept. The hub does not send a request
361
+ missing a field its name needs, and stamps the dispatching element with
362
+ `lb-request-pending` and `lb-request-error` as it would any other.
331
363
 
332
- Let the event bubble, so an ancestor widget can intercept and stop it before
333
- the hub sees it. A hand-written widget and a native element carrying
334
- `lb-action` produce the same event.
364
+ Let the event bubble, so an ancestor can stop it before the hub sends it.
335
365
 
336
- ### Decorating a list
366
+ ### Holding rows
337
367
 
338
- The hub reconciles every list scope itself. A plain element carrying
339
- `lb-list` with a row template inside it is a whole list and needs no widget,
340
- so a widget exists only when the rows need scaffolding or placement that
341
- only it can decide.
368
+ The hub lands every row template itself. A plain element carrying `lb-query`
369
+ with a row template inside it needs no code, so a custom element holds rows
370
+ only when they need placement or scaffolding that only it can decide.
342
371
 
343
- Two optional methods say what it decides. Both are named in the `lb`
344
- namespace, which Loadbare reserves for methods it calls on classes it does
345
- not own, so a widget's own methods can never collide with a later one:
372
+ A custom element carrying `lb-query` and a row template may implement two
373
+ optional methods, which the hub calls:
346
374
 
347
375
  ```ts
348
- import type { ListHost, Row } from "@loadbare/app/types";
376
+ import type { RowsHost, Row } from "@loadbare/app/types";
349
377
 
350
- class SortedList extends HTMLElement implements ListHost {
378
+ class SortedList extends HTMLElement implements RowsHost {
351
379
  lbPlaceRow(el: Element, row: Row, template: HTMLTemplateElement) {
352
- // Where this row goes. Called with the element detached, on its first
353
- // appearance and again whenever a whole set decides the order.
380
+ // Where this live row goes. Called with the live row detached, on its
381
+ // first appearance and again whenever all rows decide the order.
354
382
  }
355
383
 
356
384
  lbRowsLanded() {
357
- // Once, after the result has landed. For scaffolding derived from the
385
+ // Once, after the rows have landed. For scaffolding derived from the
358
386
  // rows: a section heading, an <optgroup>, anything that goes when its
359
387
  // last row does.
360
388
  }
361
389
  }
362
390
  ```
363
391
 
364
- Everything else is the hub's, and a widget never reimplements it:
365
-
366
- | Concern | What the hub does |
367
- |-------------|---------------------------------------------------------|
368
- | Cloning | Clones the `<template lb-key="...">` in the scope |
369
- | Matching | Updates the row already showing that key, or clones one |
370
- | Reconciling | Removes the rows the response says are gone |
371
- | Counting | Stamps `lb-row-count` with the number of rows showing |
372
-
373
- An array is the whole set, so it decides membership and order, and a key
374
- absent from it is removed. A patch touches only the rows it names and leaves
375
- every other row's contents and position alone.
376
-
377
- `lbPlaceRow` is called with the row already filled and not yet in the
378
- document, so a widget that reads a cell to decide where the row goes can. A
379
- scope without it lands rows immediately before the template, in arrival
380
- order. `lb-options.browser.ts` and `lb-table.browser.ts` in
381
- [`@loadbare/widgets`](./widgets.md) are two different placements over the
382
- same machinery.
383
-
384
- A list scope with no row template displays nothing, which is not an error.
385
-
386
- Style an empty list against `lb-row-count` rather than carrying an empty-state
387
- conditional in the widget — see
388
- [Conditional rendering](./data-binding.md#conditional-rendering).
389
-
390
- ### Filling a scope by hand
391
-
392
- `@loadbare/app` also exports `applyRow(root, row)`, the same row-landing
393
- operation a page host uses. Call it in a widget that builds a scope of its
394
- own rather than one the hub reconciles. It fills `root`
395
- itself when `root` carries a matching `lb-cell`, and every matching
396
- descendant.
397
- </content>
392
+ | Method | The hub calls it |
393
+ |----------------|---------------------|
394
+ | `lbPlaceRow` | To place a live row |
395
+ | `lbRowsLanded` | After the rows land |
396
+
397
+ Everything else is the hub's:
398
+
399
+ | Concern | The hub |
400
+ |-------------|----------------------------------------------------------|
401
+ | Cloning | Clones the row template once per new key |
402
+ | Matching | Fills the live row already showing that key |
403
+ | Removing | Removes the live rows the response says are gone |
404
+ | Stamping | Stamps each live row with `lb-key-value` |
405
+ | Counting | Stamps `lb-query-row-count` with the number of live rows |
406
+
407
+ `lbPlaceRow` receives the live row already filled and not yet in the
408
+ document, so a custom element that reads a column to decide where the row
409
+ goes can. Without it, the hub places a live row immediately before the row
410
+ template, in arrival order. `lb-options.browser.ts` and
411
+ `lb-table.browser.ts` in [`@loadbare/widgets`](./widgets.md) are two
412
+ different placements over the same landing.
413
+
414
+ Style an empty query against `lb-query-row-count` rather than carrying an
415
+ empty state in the custom element — see
416
+ [Counting rows](./data-binding.md#counting-rows).
417
+
418
+ ### Filling a subtree by hand
419
+
420
+ `@loadbare/app` exports `applyRow(root, row)`, the operation that lands a
421
+ `row` on an element. Call it in a custom element that fills a subtree of its
422
+ own rather than one the hub lands on. It sets every element carrying a
423
+ matching `lb-column` inside `root`, outside any nested `lb-query`.