@loadbare/app 0.10.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.
@@ -109,6 +109,13 @@ But the allowed values may depend on other values in the row.
109
109
  - **Land the remaining validations.** One `lb-hub` and one empty `<main>`.
110
110
  Recommend landing these now, because refusing markup that used to build is
111
111
  the kind of change 1.0 gives up.
112
+ - **Decide whether the build validates HTML.** A page can be well formed and
113
+ still break HTML's rules for what an element may hold, such as a `<div>`
114
+ inside a `<p>`. The parser rearranges it silently, the same way at build
115
+ time and in the browser, so nothing reports it. Checking those rules is a
116
+ validator's job, and html-validate is one the builder could run on the
117
+ source or on the expanded output. Deciding it after 1.0 is free if it
118
+ lands as an opt-in, and it may need a configuration key.
112
119
  - **Confirm the three output names.** `app.html`, `client.js` and `app.css`
113
120
  are about to be fixed in `staticRoutes` as well as in the builder.
114
121
 
@@ -251,6 +258,12 @@ A custom element with no element file is left alone. A custom element
251
258
  with neither an element file nor a `.browser.ts` script is a build error, as
252
259
  stated in [Custom elements](#custom-elements).
253
260
 
261
+ Each file is parsed on its own, and expansion joins them through the DOM,
262
+ which applies none of HTML's rules for what an element may hold. The browser
263
+ reads the shipped text by those rules, so a join it would rearrange, such as
264
+ a definition's `<dialog>` inside a page's `<p>`, is a build error naming the
265
+ file, the custom element and the path to what would move.
266
+
254
267
  #### Build time parameters
255
268
 
256
269
  A build time parameter is supplied as an attribute on a custom element,
@@ -418,6 +431,7 @@ query.
418
431
  | -------------------- | -------------------------------- |
419
432
  | `lb-column-value` | The value it set |
420
433
  | `lb-key-value` | The row's key |
434
+ | `lb-row-live` | Nothing; it marks a live row |
421
435
  | `lb-query-row-count` | The number of live rows it holds |
422
436
 
423
437
  The markup names a query and the columns it shows. The kind and the key are
@@ -425,7 +439,8 @@ the server's, and the markup states neither.
425
439
 
426
440
  The row template is the first `<template>` among the descendants of an
427
441
  element with `lb-query`, outside any nested `lb-query`. A live row is an
428
- element the hub cloned from a row template for one row.
442
+ element the hub cloned from a row template for one row, and carries
443
+ `lb-row-live`.
429
444
 
430
445
  | Kind | Row template | The hub |
431
446
  | ------ | ------------ | ----------------------------------- |
@@ -498,6 +513,13 @@ refresh, and does not need to.
498
513
 
499
514
  The hub matches each row to a live row by its key. It stamps
500
515
  `lb-key-value` on each live row, and on an element a `row` lands on itself.
516
+ It stamps `lb-row-live` on each live row and nowhere else, so a `row`
517
+ landed inside another query, such as a total in a table's foot, is not one
518
+ of that query's rows. A stylesheet or a custom element selects live rows
519
+ with `[lb-row-live]`, and never with `[lb-key-value]`.
520
+
521
+ A key is unique within a query. Two rows with one key in the same answer,
522
+ or two live rows showing one key, are reported on the console.
501
523
 
502
524
  All rows decide membership and order: every row is placed in the order
503
525
  given, and a live row whose key did not arrive is removed. A patch touches
@@ -1390,9 +1412,33 @@ class NoteField extends HTMLElement {
1390
1412
  }
1391
1413
  ```
1392
1414
 
1415
+ A control that is not yet upgraded when its column lands receives
1416
+ `lb-column-value` alone, since the hub sets `value` only on a control. That
1417
+ happens to an element the builder shipped absent, and to one whose
1418
+ definition loads after the hub lands. A control takes the stamp up the
1419
+ first time it connects, and never again.
1420
+
1393
1421
  A custom element that is not a control receives `lb-column-value` and
1394
1422
  renders it; its content is never replaced.
1395
1423
 
1424
+ ### Lifecycle
1425
+
1426
+ | The hub | A custom element sees |
1427
+ | ---------------------------------------- | -------------------------------------------- |
1428
+ | Shows a page | `constructor`, `connectedCallback` |
1429
+ | Creates a live row, before placing it | `constructor` |
1430
+ | Places a live row the first time | `connectedCallback` |
1431
+ | Places it again, on every set of all rows | `disconnectedCallback`, `connectedCallback` |
1432
+ | Removes a live row | `disconnectedCallback` |
1433
+ | Turns `lb-show` off | `disconnectedCallback`, `adoptedCallback` |
1434
+ | Turns `lb-show` on | `adoptedCallback`, `connectedCallback` |
1435
+ | Turns on an element shipped absent | `constructor`, `connectedCallback` |
1436
+
1437
+ A listener on the element itself goes in the constructor, which reads no
1438
+ attribute and no child. A child is looked up when it is needed.
1439
+ `connectedCallback` runs on every move and is written to run again; work
1440
+ done once per instance goes behind a flag.
1441
+
1396
1442
  ### Row hooks
1397
1443
 
1398
1444
  | Method | Implemented By | The hub calls it |
@@ -1505,6 +1551,7 @@ it may define tomorrow.
1505
1551
  | `lb-exp-template` | Developer | An element file, and a `<template>` | [Slots and templates](#slots-and-templates) |
1506
1552
  | `lb-column-value` | Hub | Every element set from a column | [How a column lands](#how-a-column-lands) |
1507
1553
  | `lb-key-value` | Hub | A live row, and an element a `row` lands on | [Rows](#rows) |
1554
+ | `lb-row-live` | Hub | A live row | [Rows](#rows) |
1508
1555
  | `lb-query-row-count` | Hub | An element with a row template | [Rows](#rows) |
1509
1556
  | `lb-request-pending` | Hub | The element that issued a request | [Request state](#request-state) |
1510
1557
  | `lb-request-error` | Hub | The element that issued a request | [Request state](#request-state) |
@@ -1566,6 +1613,8 @@ imports them rather than writing a string.
1566
1613
  | `ATTR_SHOW` | `lb-show` |
1567
1614
  | `ATTR_COLUMN_VALUE` | `lb-column-value` |
1568
1615
  | `ATTR_KEY_VALUE` | `lb-key-value` |
1616
+ | `ATTR_ROW_LIVE` | `lb-row-live` |
1617
+ | `LIVE_ROW` | `[lb-row-live]`, the selector for a live row |
1569
1618
  | `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
1570
1619
  | `ATTR_REQUEST` | `lb-request` |
1571
1620
  | `ATTR_REQUEST_PENDING` | `lb-request-pending` |
@@ -190,6 +190,36 @@ path that closes it.
190
190
  Expansion runs over `<template>` contents wherever they occur, so a
191
191
  definition's row template ships already expanded, `{{placeholder}}` included.
192
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.
222
+
193
223
  ### What fails at build time
194
224
 
195
225
  Each of these is reported with the tag and file name, and nothing is
@@ -204,6 +234,8 @@ shipped:
204
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
@@ -228,6 +260,8 @@ one as a string literal:
228
260
  | `ATTR_REQUEST` | `lb-request` |
229
261
  | `ATTR_COLUMN_VALUE` | `lb-column-value` |
230
262
  | `ATTR_KEY_VALUE` | `lb-key-value` |
263
+ | `ATTR_ROW_LIVE` | `lb-row-live` |
264
+ | `LIVE_ROW` | `[lb-row-live]` |
231
265
  | `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
232
266
  | `ATTR_REQUEST_PENDING` | `lb-request-pending` |
233
267
  | `ATTR_REQUEST_ERROR` | `lb-request-error` |
@@ -244,6 +278,46 @@ writes them in markup, and the hub and the builder write their stamps.
244
278
  Loadbare reserves method names beginning with `lb` on a custom element, for
245
279
  the methods it calls; see [Holding rows](#holding-rows).
246
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
+
247
321
  ### Being a control
248
322
 
249
323
  A form-associated custom element with a `value` property that fires `change`
@@ -253,10 +327,20 @@ and commits its `lb-request` on `change`:
253
327
 
254
328
  ```ts
255
329
  // src/star-rating.browser.ts
330
+ import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
331
+
256
332
  class StarRating extends HTMLElement {
257
333
  static formAssociated = true;
258
334
  #internals = this.attachInternals();
259
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
+ }
260
344
 
261
345
  get value(): string {
262
346
  return this.#value;
@@ -292,6 +376,14 @@ customElements.define("star-rating", StarRating);
292
376
  Dispatch `change` with `bubbles: true`, as a native control's does, when the
293
377
  user commits an edit. Setting `value` from the hub fires nothing.
294
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
+
295
387
  A form owns a form-associated custom element the way it owns an `<input>`,
296
388
  including through the HTML `form` attribute, so a form gathers it on submit.
297
389
  Write `formResetCallback` to restore the default value: the hub resets a form
@@ -330,14 +422,9 @@ List `ATTR_COLUMN_VALUE` in `observedAttributes`. The browser calls
330
422
  upgrades, as well as for one that changes afterward, so the first render and
331
423
  every later one go through the one callback.
332
424
 
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.
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).
341
428
 
342
429
  ### Sending a request
343
430
 
@@ -401,7 +488,7 @@ Everything else is the hub's:
401
488
  | Cloning | Clones the row template once per new key |
402
489
  | Matching | Fills the live row already showing that key |
403
490
  | Removing | Removes the live rows the response says are gone |
404
- | Stamping | Stamps each live row with `lb-key-value` |
491
+ | Stamping | Stamps each live row with `lb-row-live` and `lb-key-value` |
405
492
  | Counting | Stamps `lb-query-row-count` with the number of live rows |
406
493
 
407
494
  `lbPlaceRow` receives the live row already filled and not yet in the
@@ -411,6 +498,11 @@ template, in arrival order. `lb-options.browser.ts` and
411
498
  `lb-table.browser.ts` in [`@loadbare/widgets`](./widgets.md) are two
412
499
  different placements over the same landing.
413
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
+
414
506
  Style an empty query against `lb-query-row-count` rather than carrying an
415
507
  empty state in the custom element — see
416
508
  [Counting rows](./data-binding.md#counting-rows).
@@ -58,6 +58,7 @@ builder writes a stamp, and a stylesheet or a custom element reads it.
58
58
  |----------------------|----------------------------------------------------|
59
59
  | `lb-column-value` | The value the hub set from a column |
60
60
  | `lb-key-value` | The key of a live row, or of a `row` landed on an element |
61
+ | `lb-row-live` | The element is a live row |
61
62
  | `lb-query-row-count` | The number of live rows an element holds |
62
63
  | `lb-request-pending` | The element's request is in flight |
63
64
  | `lb-request-error` | The element's last request failed |
@@ -86,8 +87,11 @@ any nested `lb-query`:
86
87
  | `rows` | no | Lands nothing |
87
88
 
88
89
  A live row is an element the hub cloned from a row template for one row.
89
- The hub stamps it with `lb-key-value`, the row's key. An element a `row`
90
- lands on itself carries `lb-key-value` as well.
90
+ The hub stamps it with `lb-row-live`, and with `lb-key-value`, the row's
91
+ key. An element a `row` lands on itself carries `lb-key-value` as well, but
92
+ not `lb-row-live`, so a `row` shown inside another query, such as a total in
93
+ a table's foot, is not one of that query's rows. Select live rows with
94
+ `[lb-row-live]`, never with `[lb-key-value]`.
91
95
 
92
96
  Write `lb-column` on each element that shows a column. The element reads
93
97
  from its nearest ancestor row: the live row it is in, or the element a
@@ -153,6 +157,9 @@ membership and order: a live row whose key did not arrive is removed. A
153
157
  patch changes only the rows it names, and every other live row keeps its
154
158
  content and its place.
155
159
 
160
+ A key is unique within a query. Two rows with one key in the same answer,
161
+ or two live rows showing one key, are reported on the console.
162
+
156
163
  A live row's root counts as a column when it carries `lb-column`, which is
157
164
  how an `<option>`, whose content is text, shows the column it is.
158
165
 
@@ -16,6 +16,35 @@ every other path names the page whose stub it is.
16
16
  Put the files anywhere under `src/`. The builder pairs them by stub, not by
17
17
  directory; `src/pages/` is the convention.
18
18
 
19
+ ## What the page files are
20
+
21
+ A page's queries are that page's view model, answered on the server. Each
22
+ is shaped for the elements that name it, on one page, so it answers in the
23
+ form the page shows: a date formatted, an amount with its separators, and
24
+ the phrase a reader sees, such as "no postings yet" or "12 postings",
25
+ rather than a count for the browser to word. The hub does no conversion,
26
+ so there is no later place for that work to go. The markup says where each
27
+ value goes, and the query says what it is.
28
+
29
+ A value the page reads back, rather than only shows, stays data:
30
+
31
+ | Value | Read back by | So it is |
32
+ |------------------------------------|---------------------------------|-----------------------------------|
33
+ | A column that lands on a control | The request that gathers it | What the handler expects to receive |
34
+ | The key column | Every request from the row | What the handler looks the row up by |
35
+ | A column `lb-show` names | The hub | `null` or `false` for off |
36
+ | A column a stylesheet selects on | `[lb-column-value="…"]` | A fixed token, not wording |
37
+
38
+ The page files are not a data layer; they sit on one. The application
39
+ opens its own database and keeps its rules there, or in code its handlers
40
+ share, and the page files stay thin because integrity is not their job.
41
+
42
+ What two pages share goes beneath them, not between. Two pages showing the
43
+ same data each write the query, or both read a database view. Shared
44
+ wording and shared parm reading go in a helper module the queries import.
45
+ Everything that belongs to one page stays in one place, so a change to a
46
+ page touches that page's files.
47
+
19
48
  ## HTML
20
49
 
21
50
  Write the page as a fragment. The fragment lands in `<main>`, which