@loadbare/app 0.9.0 → 0.11.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 (82) hide show
  1. package/README.md +3 -3
  2. package/dist/build/assemble.d.ts.map +1 -1
  3. package/dist/build/assemble.js +107 -90
  4. package/dist/build/assemble.js.map +1 -1
  5. package/dist/build/expand.d.ts +6 -1
  6. package/dist/build/expand.d.ts.map +1 -1
  7. package/dist/build/expand.js +112 -26
  8. package/dist/build/expand.js.map +1 -1
  9. package/dist/build/locations.d.ts +2 -3
  10. package/dist/build/locations.d.ts.map +1 -1
  11. package/dist/build/locations.js +2 -3
  12. package/dist/build/locations.js.map +1 -1
  13. package/dist/build/pages.d.ts +3 -4
  14. package/dist/build/pages.d.ts.map +1 -1
  15. package/dist/build/pages.js +3 -4
  16. package/dist/build/pages.js.map +1 -1
  17. package/dist/core/lb-constants.d.ts +27 -24
  18. package/dist/core/lb-constants.d.ts.map +1 -1
  19. package/dist/core/lb-constants.js +103 -168
  20. package/dist/core/lb-constants.js.map +1 -1
  21. package/dist/core/lb-types.d.ts +64 -77
  22. package/dist/core/lb-types.d.ts.map +1 -1
  23. package/dist/core/lb-types.js +40 -7
  24. package/dist/core/lb-types.js.map +1 -1
  25. package/dist/hub/lb-apply.d.ts +47 -37
  26. package/dist/hub/lb-apply.d.ts.map +1 -1
  27. package/dist/hub/lb-apply.js +195 -199
  28. package/dist/hub/lb-apply.js.map +1 -1
  29. package/dist/hub/lb-hub.browser.d.ts +1 -1
  30. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  31. package/dist/hub/lb-hub.browser.js +410 -449
  32. package/dist/hub/lb-hub.browser.js.map +1 -1
  33. package/dist/server/lb-express.d.ts +5 -5
  34. package/dist/server/lb-express.d.ts.map +1 -1
  35. package/dist/server/lb-express.js +35 -66
  36. package/dist/server/lb-express.js.map +1 -1
  37. package/dist/server/lb-server.d.ts +77 -135
  38. package/dist/server/lb-server.d.ts.map +1 -1
  39. package/dist/server/lb-server.js +132 -79
  40. package/dist/server/lb-server.js.map +1 -1
  41. package/docs/TECHREF-1.0.md +908 -585
  42. package/docs/comparison.md +243 -185
  43. package/docs/prior-art.md +15 -14
  44. package/docs/reference/builder.md +9 -3
  45. package/docs/reference/chrome.md +107 -56
  46. package/docs/reference/custom-elements.md +291 -173
  47. package/docs/reference/data-binding.md +381 -374
  48. package/docs/reference/overview.md +12 -10
  49. package/docs/reference/page-files.md +164 -99
  50. package/docs/reference/server.md +2 -2
  51. package/docs/reference/widgets.md +104 -110
  52. package/docs/roadmap.md +32 -39
  53. package/docs/terms-of-art.md +57 -0
  54. package/docs/testing.md +97 -68
  55. package/docs/theory.md +92 -58
  56. package/docs/tutorials/010-pages-and-navigation.md +20 -12
  57. package/docs/tutorials/020-css.md +6 -3
  58. package/docs/tutorials/030-html-decomposition.md +9 -7
  59. package/docs/tutorials/040-displaying-data.md +30 -13
  60. package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
  61. package/docs/tutorials/060-custom-element-code.md +17 -16
  62. package/docs/tutorials/065-conditional-rendering.md +34 -23
  63. package/docs/tutorials/070-displaying-a-list.md +29 -21
  64. package/docs/tutorials/072-inserting-into-a-list.md +24 -16
  65. package/docs/tutorials/074-deleting-from-a-list.md +9 -7
  66. package/docs/tutorials/076-updating-a-list-item.md +11 -10
  67. package/docs/tutorials/080-widget-requests.md +71 -43
  68. package/docs/tutorials/090-using-widget-libraries.md +22 -22
  69. package/docs/what-does-loadbare-extend.md +124 -0
  70. package/package.json +1 -1
  71. package/skills/loadbare-app/SKILL.md +201 -123
  72. package/skills/loadbare-app/references/TECHREF-1.0.md +908 -585
  73. package/skills/loadbare-app/references/builder.md +9 -3
  74. package/skills/loadbare-app/references/chrome.md +107 -56
  75. package/skills/loadbare-app/references/custom-elements.md +291 -173
  76. package/skills/loadbare-app/references/data-binding.md +381 -374
  77. package/skills/loadbare-app/references/overview.md +12 -10
  78. package/skills/loadbare-app/references/page-files.md +164 -99
  79. package/skills/loadbare-app/references/server.md +2 -2
  80. package/skills/loadbare-app/references/widgets.md +104 -110
  81. package/docs/analysis-accidental-complexity.md +0 -149
  82. 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,45 @@ 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
+
193
+ ### Where a custom element can go
194
+
195
+ HTML limits what an element may hold. A `<p>` holds only phrasing content,
196
+ such as text, `<span>` and `<button>`, and a `<button>` may not hold another
197
+ `<button>`. The parser does not refuse markup that breaks these rules; it
198
+ rearranges it. `<p>Total <div>12</div></p>` becomes a paragraph, then a div,
199
+ then an empty paragraph, with nothing logged.
200
+
201
+ Each file is parsed on its own, so a page file and an element file each get
202
+ the tree the browser would give them. Expansion then places one inside the
203
+ other, and that join is where a rule can break. A definition containing a
204
+ `<div>` or a `<dialog>` cannot go inside a `<p>`, since the browser would
205
+ close the paragraph early and move the rest of the definition out of its
206
+ element. The build refuses such a page, naming the file, the custom element
207
+ and the path to what would move:
208
+
209
+ ```
210
+ assemble: 'src/pages/ledger.page.html': expand: <lb-confirm> puts <dialog>
211
+ where the browser's parser would not keep it, at p > lb-confirm > dialog.
212
+ ```
213
+
214
+ Put a custom element whose definition holds block content, such as a
215
+ `<div>`, a `<dialog>` or a `<p>`, in a `<div>`, a `<li>`, a `<td>` or another
216
+ element that holds flow content.
217
+
218
+ Never write a custom element inside a `<select>`, an `<option>` or a
219
+ `<textarea>`. The parser drops the tag inside the first two, and inside a
220
+ `<textarea>` it is text. Both happen when the page file is read, before
221
+ expansion, so the build cannot see the element to report it.
192
222
 
193
223
  ### What fails at build time
194
224
 
@@ -199,54 +229,181 @@ shipped:
199
229
  - a cycle in the definition graph
200
230
  - an `exp-` attribute the definition never declared
201
231
  - 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
232
+ - content written inside a tag whose definition has no `lb-exp-slot`
233
+ - more than one `lb-exp-slot` in one definition
234
+ - a `<template lb-exp-template="name">` naming a destination the definition
205
235
  does not have
206
236
  - two templates for the same destination
237
+ - a custom element placed where the browser's parser would move its
238
+ contents; see [Where a custom element can go](#where-a-custom-element-can-go)
207
239
 
208
240
  An unfilled `{{placeholder}}` is not among these. That is presence
209
241
  propagation working as intended, indistinguishable from an author who left
210
242
  an optional parameter out.
211
243
 
244
+
212
245
  ## Code
213
246
 
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` |
247
+ A `<tag>.browser.ts` file registers that tag's class. The class is an
248
+ ordinary custom element, and Loadbare imposes no base class. A custom element
249
+ takes part in data binding in one of four ways: it is a control, it renders
250
+ a value, it sends a request, or it holds rows.
251
+
252
+ Import every Loadbare name from `@loadbare/app/constants`, and never write
253
+ one as a string literal:
254
+
255
+ | Constant | Value |
256
+ |------------------------|----------------------|
257
+ | `ATTR_QUERY` | `lb-query` |
258
+ | `ATTR_COLUMN` | `lb-column` |
259
+ | `ATTR_SHOW` | `lb-show` |
260
+ | `ATTR_REQUEST` | `lb-request` |
261
+ | `ATTR_COLUMN_VALUE` | `lb-column-value` |
262
+ | `ATTR_KEY_VALUE` | `lb-key-value` |
263
+ | `ATTR_ROW_LIVE` | `lb-row-live` |
264
+ | `LIVE_ROW` | `[lb-row-live]` |
265
+ | `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
266
+ | `ATTR_REQUEST_PENDING` | `lb-request-pending` |
267
+ | `ATTR_REQUEST_ERROR` | `lb-request-error` |
268
+ | `REQUEST_ROW_INSERT`, `REQUEST_ROW_UPDATE`, `REQUEST_ROW_DELETE` | The requests Loadbare provides |
269
+ | `LB_ROW_REQUESTS` | All three, in one array |
270
+ | `LB_EVENT_NAME` | `lb-request`, the event every request travels as |
271
+ | `URL_QUERY` | `lb-url` |
272
+ | `URL_COLUMN_PATH`, `URL_COLUMN_PAGE_LABEL`, `URL_COLUMN_PAGE_UNKNOWN` | `lb-path`, `lb-page-label`, `lb-page-unknown` |
273
+ | `LB_RESERVED_PREFIX` | `lb-` |
274
+
275
+ A custom element reads `lb-` attributes and never assigns one. The developer
276
+ writes them in markup, and the hub and the builder write their stamps.
277
+
278
+ Loadbare reserves method names beginning with `lb` on a custom element, for
279
+ the methods it calls; see [Holding rows](#holding-rows).
280
+
281
+ ### When its code runs
282
+
283
+ The hub creates, places and parks elements, and each of those reaches a
284
+ custom element as the browser's lifecycle callbacks. It is the same instance
285
+ from its constructor on, however often it moves:
286
+
287
+ | What the hub does | Callbacks, in order |
288
+ |----------------------------------------------------|---------------------------------------------|
289
+ | Shows a page | `constructor`, `connectedCallback` |
290
+ | Creates a live row, filled before it is placed | `constructor` |
291
+ | Places a live row for the first time | `connectedCallback` |
292
+ | Places it again, as it does on every set of all rows | `disconnectedCallback`, `connectedCallback` |
293
+ | Removes a live row | `disconnectedCallback` |
294
+ | Turns `lb-show` off | `disconnectedCallback`, `adoptedCallback` |
295
+ | Turns `lb-show` on | `adoptedCallback`, `connectedCallback` |
296
+ | Turns on an element the builder shipped absent | `constructor`, `connectedCallback` |
297
+
298
+ The builder ships every `lb-show` element inside its template, and nothing
299
+ in a template is upgraded, so such an element has no instance until its
300
+ column first turns on. See
301
+ [Conditional rendering](./data-binding.md#conditional-rendering).
302
+
303
+ So each kind of work has one place:
304
+
305
+ 1. **A listener on the element itself goes in the constructor.** It is
306
+ attached once and survives every move, and a live row gets its own
307
+ because each row is a new instance. An event from a child bubbles to the
308
+ element, so the listener needs no child to exist yet. The constructor
309
+ reads no attribute and no child: an element made with
310
+ `document.createElement` has neither when it runs.
311
+ 2. **A child is looked up when it is needed**, in a handler, a getter,
312
+ `lbPlaceRow` or `lbRowsLanded`, and never held from setup.
313
+ 3. **`connectedCallback` runs on every move**, so it is written to run
314
+ again. It rearranges the element's own children into a state it checks
315
+ for first, or adds a listener to `document` or `window`, which
316
+ `disconnectedCallback` removes. Work done once per instance that needs
317
+ the element's attributes, such as a control taking up a value that landed
318
+ before it upgraded, goes behind a flag there; see
319
+ [Being a control](#being-a-control).
320
+
321
+ ### Being a control
322
+
323
+ A form-associated custom element with a `value` property that fires `change`
324
+ is a control, the same as an `<input>`. The hub sets its `value` from the
325
+ column its `lb-column` names, gathers its `value` back under that column,
326
+ and commits its `lb-request` on `change`:
327
+
328
+ ```ts
329
+ // src/star-rating.browser.ts
330
+ import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
331
+
332
+ class StarRating extends HTMLElement {
333
+ static formAssociated = true;
334
+ #internals = this.attachInternals();
335
+ #value = "";
336
+ #connected = false;
337
+
338
+ connectedCallback() {
339
+ if (this.#connected) return;
340
+ this.#connected = true;
341
+ const stamped = this.getAttribute(ATTR_COLUMN_VALUE);
342
+ if (stamped !== null) this.value = stamped;
343
+ }
344
+
345
+ get value(): string {
346
+ return this.#value;
347
+ }
348
+
349
+ set value(value: string) {
350
+ this.#value = value ?? "";
351
+ this.#internals.setFormValue(this.#value);
352
+ this.render();
353
+ }
354
+
355
+ formResetCallback() {
356
+ this.value = "";
357
+ }
358
+
359
+ pick(stars: number) {
360
+ this.value = String(stars);
361
+ this.dispatchEvent(new Event("change", { bubbles: true }));
362
+ }
363
+
364
+ render() {
365
+ // Draw this.#value stars.
366
+ }
367
+ }
368
+
369
+ customElements.define("star-rating", StarRating);
370
+ ```
371
+
372
+ ```html
373
+ <star-rating lb-column="rating" lb-request="lb-row-update"></star-rating>
374
+ ```
375
+
376
+ Dispatch `change` with `bubbles: true`, as a native control's does, when the
377
+ user commits an edit. Setting `value` from the hub fires nothing.
378
+
379
+ A control can receive its value before it is a control. An element the
380
+ builder shipped absent, or one whose definition loads after the hub has
381
+ landed, is not upgraded when its column lands, so the hub cannot set its
382
+ `value` and the value arrives as `lb-column-value` alone. Take the stamp up
383
+ the first time the control connects, as `connectedCallback` does above, and
384
+ never again: after that the hub sets `value` itself, and a later move must
385
+ not undo an edit.
386
+
387
+ A form owns a form-associated custom element the way it owns an `<input>`,
388
+ including through the HTML `form` attribute, so a form gathers it on submit.
389
+ Write `formResetCallback` to restore the default value: the hub resets a form
390
+ after a successful insert.
391
+
392
+ The `<lb-input>`, `<lb-select>`, `<lb-options>` and `<lb-picker>` widgets
393
+ are controls; see [The Basic Widget Library](./widgets.md).
237
394
 
238
395
  ### Receiving a value
239
396
 
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:
397
+ A custom element that is not a control keeps the content the builder placed
398
+ in it, and receives a column as its `lb-column-value` attribute. Observe it,
399
+ and render the value:
243
400
 
244
401
  ```ts
245
402
  // src/visit-count.browser.ts
246
- import { ATTR_VALUE } from "@loadbare/app/constants";
403
+ import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
247
404
 
248
405
  class VisitCount extends HTMLElement {
249
- static observedAttributes = [ATTR_VALUE];
406
+ static observedAttributes = [ATTR_COLUMN_VALUE];
250
407
 
251
408
  attributeChangedCallback(_name: string, _old: string, value: string) {
252
409
  this.textContent = `visited ${value} times`;
@@ -256,142 +413,103 @@ class VisitCount extends HTMLElement {
256
413
  customElements.define("visit-count", VisitCount);
257
414
  ```
258
415
 
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.
416
+ ```html
417
+ <p lb-query="visits"><visit-count lb-column="count"></visit-count></p>
418
+ ```
419
+
420
+ List `ATTR_COLUMN_VALUE` in `observedAttributes`. The browser calls
421
+ `attributeChangedCallback` for an attribute already present when an element
422
+ upgrades, as well as for one that changes afterward, so the first render and
423
+ every later one go through the one callback.
424
+
425
+ A custom element carrying `lb-show`, or inside an element that does, still
426
+ receives its column while it is away, so it returns current; see
427
+ [When its code runs](#when-its-code-runs).
278
428
 
279
429
  ### Sending a request
280
430
 
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`:
431
+ A custom element that carries `lb-request` and is not a control commits on
432
+ `click`, as a button does. To send a request at a moment of its own, a
433
+ custom element dispatches the `lb-request` event itself, a bubbling
434
+ `CustomEvent` whose `detail` is the request:
285
435
 
286
436
  ```ts
287
437
  import { LB_EVENT_NAME } from "@loadbare/app/constants";
288
438
  import type { HubRequest } from "@loadbare/app/types";
289
439
 
290
- const detail: HubRequest = { action: "lb-row-update", value: input.value };
440
+ const detail: HubRequest = { name: "archiveOld" };
291
441
  this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
292
442
  ```
293
443
 
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>`.
444
+ The hub completes the request from where the element sits before any
445
+ ancestor sees the event, gathering as it does for an element carrying
446
+ `lb-request`; see [Gathering](./data-binding.md#gathering). A field the
447
+ element supplies in `detail` is kept. The hub does not send a request
448
+ missing a field its name needs, and stamps the dispatching element with
449
+ `lb-request-pending` and `lb-request-error` as it would any other.
313
450
 
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.
451
+ Let the event bubble, so an ancestor can stop it before the hub sends it.
331
452
 
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.
453
+ ### Holding rows
335
454
 
336
- ### Decorating a list
455
+ The hub lands every row template itself. A plain element carrying `lb-query`
456
+ with a row template inside it needs no code, so a custom element holds rows
457
+ only when they need placement or scaffolding that only it can decide.
337
458
 
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.
342
-
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:
459
+ A custom element carrying `lb-query` and a row template may implement two
460
+ optional methods, which the hub calls:
346
461
 
347
462
  ```ts
348
- import type { ListHost, Row } from "@loadbare/app/types";
463
+ import type { RowsHost, Row } from "@loadbare/app/types";
349
464
 
350
- class SortedList extends HTMLElement implements ListHost {
465
+ class SortedList extends HTMLElement implements RowsHost {
351
466
  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.
467
+ // Where this live row goes. Called with the live row detached, on its
468
+ // first appearance and again whenever all rows decide the order.
354
469
  }
355
470
 
356
471
  lbRowsLanded() {
357
- // Once, after the result has landed. For scaffolding derived from the
472
+ // Once, after the rows have landed. For scaffolding derived from the
358
473
  // rows: a section heading, an <optgroup>, anything that goes when its
359
474
  // last row does.
360
475
  }
361
476
  }
362
477
  ```
363
478
 
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>
479
+ | Method | The hub calls it |
480
+ |----------------|---------------------|
481
+ | `lbPlaceRow` | To place a live row |
482
+ | `lbRowsLanded` | After the rows land |
483
+
484
+ Everything else is the hub's:
485
+
486
+ | Concern | The hub |
487
+ |-------------|----------------------------------------------------------|
488
+ | Cloning | Clones the row template once per new key |
489
+ | Matching | Fills the live row already showing that key |
490
+ | Removing | Removes the live rows the response says are gone |
491
+ | Stamping | Stamps each live row with `lb-row-live` and `lb-key-value` |
492
+ | Counting | Stamps `lb-query-row-count` with the number of live rows |
493
+
494
+ `lbPlaceRow` receives the live row already filled and not yet in the
495
+ document, so a custom element that reads a column to decide where the row
496
+ goes can. Without it, the hub places a live row immediately before the row
497
+ template, in arrival order. `lb-options.browser.ts` and
498
+ `lb-table.browser.ts` in [`@loadbare/widgets`](./widgets.md) are two
499
+ different placements over the same landing.
500
+
501
+ A custom element that walks its own rows finds them with `LIVE_ROW` from
502
+ `@loadbare/app/constants`, never by `lb-key-value`: an element a `row` lands
503
+ on carries a key too, and may sit among the rows, as a total in a table's
504
+ foot does.
505
+
506
+ Style an empty query against `lb-query-row-count` rather than carrying an
507
+ empty state in the custom element — see
508
+ [Counting rows](./data-binding.md#counting-rows).
509
+
510
+ ### Filling a subtree by hand
511
+
512
+ `@loadbare/app` exports `applyRow(root, row)`, the operation that lands a
513
+ `row` on an element. Call it in a custom element that fills a subtree of its
514
+ own rather than one the hub lands on. It sets every element carrying a
515
+ matching `lb-column` inside `root`, outside any nested `lb-query`.