@loadbare/app 0.14.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 (35) hide show
  1. package/dist/build/elements.d.ts +12 -2
  2. package/dist/build/elements.d.ts.map +1 -1
  3. package/dist/build/elements.js +44 -10
  4. package/dist/build/elements.js.map +1 -1
  5. package/dist/core/lb-constants.d.ts +2 -1
  6. package/dist/core/lb-constants.d.ts.map +1 -1
  7. package/dist/core/lb-constants.js +16 -3
  8. package/dist/core/lb-constants.js.map +1 -1
  9. package/dist/core/lb-types.d.ts +25 -8
  10. package/dist/core/lb-types.d.ts.map +1 -1
  11. package/dist/core/lb-types.js.map +1 -1
  12. package/dist/hub/lb-apply.d.ts +2 -0
  13. package/dist/hub/lb-apply.d.ts.map +1 -1
  14. package/dist/hub/lb-apply.js +18 -29
  15. package/dist/hub/lb-apply.js.map +1 -1
  16. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  17. package/dist/hub/lb-hub.browser.js +41 -5
  18. package/dist/hub/lb-hub.browser.js.map +1 -1
  19. package/dist/hub/lb-lifecycle.d.ts +40 -0
  20. package/dist/hub/lb-lifecycle.d.ts.map +1 -0
  21. package/dist/hub/lb-lifecycle.js +140 -0
  22. package/dist/hub/lb-lifecycle.js.map +1 -0
  23. package/docs/TECHREF-1.0.md +49 -47
  24. package/docs/comparison.md +7 -5
  25. package/docs/possible-ideas.md +6 -0
  26. package/docs/reference/custom-elements.md +106 -87
  27. package/docs/reference/data-binding.md +7 -7
  28. package/docs/reference/widgets.md +2 -2
  29. package/docs/testing.md +2 -2
  30. package/package.json +1 -1
  31. package/skills/loadbare-app/SKILL.md +20 -16
  32. package/skills/loadbare-app/references/TECHREF-1.0.md +49 -47
  33. package/skills/loadbare-app/references/custom-elements.md +106 -87
  34. package/skills/loadbare-app/references/data-binding.md +7 -7
  35. package/skills/loadbare-app/references/widgets.md +2 -2
@@ -278,10 +278,10 @@ A custom element reads `lb-` attributes and never assigns one. The developer
278
278
  writes them in markup, and the hub and the builder write their stamps.
279
279
 
280
280
  Loadbare reserves method names beginning with `lb` on a custom element, for
281
- the methods it calls; see [Holding rows](#holding-rows). The build refuses
282
- a script that declares a method, accessor or field whose name is `lb`, a
283
- capital, and anything else Loadbare does not call, so a misspelled
284
- `lbRowLanded` fails the build rather than never being called.
281
+ the methods it calls; see [When its code runs](#when-its-code-runs). The
282
+ build refuses a script that declares a method, accessor or field whose name
283
+ is `lb`, a capital, and anything else Loadbare does not call, so a
284
+ misspelled `lbRowLanded` fails the build rather than never being called.
285
285
 
286
286
  ### What the hub and an element say to each other
287
287
 
@@ -291,39 +291,80 @@ Each direction has one mechanism for each kind of message:
291
291
  |----------------|--------------------------------------------|-----------------------------|-------|
292
292
  | Hub to element | State that lasts | An attribute the hub stamps | `lb-column-value`, `lb-key-value`, `lb-query-row-count`, `lb-group-*`, `lb-row-*`, `lb-request-pending`, `lb-request-error` |
293
293
  | Hub to element | State the platform already names | A property | A control's `value` |
294
- | Hub to element | Work the hub needs done now, while landing | An optional `lb` method | `lbRowsLanded` |
294
+ | Hub to element | What the hub did | An optional `lb` method | `lbAttached`, `lbRowsLanded`, `lbRequestDone`, `lbDetached` |
295
295
  | Hub to element | A moment an element started | A bubbling event | `lb-request-done` |
296
296
  | Element to hub | A moment | A bubbling event | `lb-request`, and `change`, `click` and `submit` |
297
297
 
298
- State is an attribute: a stylesheet can select on it, and an element that
299
- upgrades late still finds it. A method is for work the hub cannot go on
300
- without, done by one element at one point in landing. A moment is an event,
301
- because the element that cares is often an ancestor of the one it concerns,
302
- and an event reaches it knowing neither.
298
+ State is an attribute, because a stylesheet can select on it. A method is
299
+ how the hub tells one element what it just did to that element. A moment is
300
+ an event, because the element that cares is often an ancestor of the one it
301
+ concerns, and an event reaches it knowing neither.
303
302
 
304
303
  ### When its code runs
305
304
 
306
- The hub creates, places and parks elements, and each of those reaches a
307
- custom element as the browser's lifecycle callbacks. It is the same instance
308
- from its constructor on, however often it moves:
305
+ The hub tells a custom element what it did through four optional methods,
306
+ named for the hub's own work. The `HubLifecycle` interface in
307
+ `@loadbare/app/types` declares them. Here is an element that closes its
308
+ popup on Escape, listening on `document` only while the hub has it on the
309
+ page:
309
310
 
310
- | What the hub does | Callbacks, in order |
311
- |----------------------------------------------------|---------------------------------------------|
312
- | Shows a page | `constructor`, `connectedCallback` |
313
- | Creates a live row, filled before it is placed | `constructor` |
314
- | Places a live row for the first time | `connectedCallback` |
315
- | Places it again, as it does on every set of all rows | `disconnectedCallback`, `connectedCallback` |
316
- | Removes a live row | `disconnectedCallback` |
317
- | Turns `lb-show` off | `disconnectedCallback`, `adoptedCallback` |
318
- | Turns `lb-show` on | `adoptedCallback`, `connectedCallback` |
319
- | Turns on an element the builder shipped absent | `constructor`, `connectedCallback` |
311
+ ```ts
312
+ // src/note-popup.browser.ts
313
+ import type { HubLifecycle } from "@loadbare/app/types";
314
+
315
+ class NotePopup extends HTMLElement implements HubLifecycle {
316
+ #onKey = (e: KeyboardEvent) => {
317
+ if (e.key === "Escape") this.querySelector("dialog")?.close();
318
+ };
320
319
 
321
- The builder ships every `lb-show` element inside its template, and nothing
322
- in a template is upgraded, so such an element has no instance until its
323
- column first turns on. See
324
- [Conditional rendering](./data-binding.md#conditional-rendering).
320
+ lbAttached() {
321
+ document.addEventListener("keydown", this.#onKey);
322
+ }
325
323
 
326
- So each kind of work has one place:
324
+ lbDetached() {
325
+ document.removeEventListener("keydown", this.#onKey);
326
+ }
327
+ }
328
+
329
+ customElements.define("note-popup", NotePopup);
330
+ ```
331
+
332
+ | Method | The hub calls it |
333
+ |---------------------------------|-------------------------------------------------------------------|
334
+ | `lbAttached()` | Once, after it places the element and every column has landed on it |
335
+ | `lbRowsLanded()` | After rows land on the element, with every row placed and stamped |
336
+ | `lbRequestDone(detail, origin)` | After a request committed by the element, or inside it, has finished |
337
+ | `lbDetached()` | Once, after it removes the element |
338
+
339
+ The hub places an element when it starts up the chrome, shows a page,
340
+ creates a live row, or builds a group. It places the chrome and a page as
341
+ the page's first answer lands, or once that round trip fails, so an element
342
+ in either hears `lbAttached` with the page's data in place. It removes an
343
+ element when a live row or a group has finished leaving, and when it shows
344
+ another page in place of the one holding it. Write setup in `lbAttached` and its undoing in
345
+ `lbDetached`; the two pair exactly, so neither guards against running
346
+ twice.
347
+
348
+ Moving a row and turning `lb-show` off and on are neither placing nor
349
+ removing. The element keeps its instance and hears nothing. An element
350
+ under an `lb-show` that is off is placed inside its template, so its
351
+ `lbAttached` runs whether or not it shows. Measure and focus when the user
352
+ acts, never in `lbAttached`, where the element may not be rendered.
353
+
354
+ The hub calls `lbAttached` on an outer custom element before the ones
355
+ inside it, and `lbDetached` on the inner ones first. A table that sets the
356
+ `form` attribute on its controls in `lbAttached` does so before each control
357
+ hears its own.
358
+
359
+ An element a script creates hears none of the four, and neither does the
360
+ copy of a moved row the hub leaves behind while it fades. The hub calls
361
+ them only on what it places.
362
+
363
+ Never write `connectedCallback`, `disconnectedCallback`, `adoptedCallback`
364
+ or `connectedMoveCallback`. The browser calls those on every move, and the
365
+ build refuses a script that declares one.
366
+
367
+ Each kind of work then has one place:
327
368
 
328
369
  1. **A listener on the element itself goes in the constructor.** It is
329
370
  attached once and survives every move, and a live row gets its own
@@ -331,15 +372,11 @@ So each kind of work has one place:
331
372
  element, so the listener needs no child to exist yet. The constructor
332
373
  reads no attribute and no child: an element made with
333
374
  `document.createElement` has neither when it runs.
334
- 2. **A child is looked up when it is needed**, in a handler, a getter,
335
- or `lbRowsLanded`, and never held from setup.
336
- 3. **`connectedCallback` runs on every move**, so it is written to run
337
- again. It rearranges the element's own children into a state it checks
338
- for first, or adds a listener to `document` or `window`, which
339
- `disconnectedCallback` removes. Work done once per instance that needs
340
- the element's attributes, such as a control taking up a value that landed
341
- before it upgraded, goes behind a flag there; see
342
- [Being a control](#being-a-control).
375
+ 2. **A child is looked up when it is needed**, in a handler, a getter, or
376
+ one of the four methods, and never held from setup.
377
+ 3. **Work done once per instance goes in `lbAttached`**, with its undoing
378
+ in `lbDetached`: rearranging the element's own children, or a listener on
379
+ `document` or `window`.
343
380
 
344
381
  ### Being a control
345
382
 
@@ -350,20 +387,10 @@ and commits its `lb-request` on `change`:
350
387
 
351
388
  ```ts
352
389
  // src/star-rating.browser.ts
353
- import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
354
-
355
390
  class StarRating extends HTMLElement {
356
391
  static formAssociated = true;
357
392
  #internals = this.attachInternals();
358
393
  #value = "";
359
- #connected = false;
360
-
361
- connectedCallback() {
362
- if (this.#connected) return;
363
- this.#connected = true;
364
- const stamped = this.getAttribute(ATTR_COLUMN_VALUE);
365
- if (stamped !== null) this.value = stamped;
366
- }
367
394
 
368
395
  get value(): string {
369
396
  return this.#value;
@@ -399,14 +426,6 @@ customElements.define("star-rating", StarRating);
399
426
  Dispatch `change` with `bubbles: true`, as a native control's does, when the
400
427
  user commits an edit. Setting `value` from the hub fires nothing.
401
428
 
402
- A control can receive its value before it is a control. An element the
403
- builder shipped absent, or one whose definition loads after the hub has
404
- landed, is not upgraded when its column lands, so the hub cannot set its
405
- `value` and the value arrives as `lb-column-value` alone. Take the stamp up
406
- the first time the control connects, as `connectedCallback` does above, and
407
- never again: after that the hub sets `value` itself, and a later move must
408
- not undo an edit.
409
-
410
429
  A form owns a form-associated custom element the way it owns an `<input>`,
411
430
  including through the HTML `form` attribute, so a form gathers it on submit.
412
431
  Write `formResetCallback` to restore the default value: the hub resets a form
@@ -571,23 +590,28 @@ Let the event bubble, so an ancestor can stop it before the hub sends it.
571
590
  ### Hearing how it turned out
572
591
 
573
592
  Once what a request brought back has landed, or its round trip has failed,
574
- the hub dispatches `lb-request-done` from the element that committed. It
575
- bubbles, so the element that cares need not be the one that committed: a
576
- dialog hears it from the form inside it.
593
+ the hub calls `lbRequestDone(detail, origin)` on the custom element that
594
+ committed and on every custom element around it, innermost first. `origin`
595
+ is the element that committed, so the element that cares need not be the
596
+ one that committed: a dialog hears it from the form inside it.
577
597
 
578
598
  ```ts
579
- import { LB_DONE_EVENT_NAME } from "@loadbare/app/constants";
580
- import type { HubRequestDone } from "@loadbare/app/types";
581
-
582
- this.addEventListener(LB_DONE_EVENT_NAME, (e) => {
583
- const { request, items, error } = (e as CustomEvent<HubRequestDone>).detail;
584
- if (error) return;
585
- const item = items.find((i) => i.query === request.query);
586
- const created = item?.patch?.rows?.[0];
587
- if (created) this.choose(String(created[item!.key]));
588
- });
599
+ import type { HubLifecycle, HubRequestDone } from "@loadbare/app/types";
600
+
601
+ class NewRowDialog extends HTMLElement implements HubLifecycle {
602
+ lbRequestDone({ request, items, error }: HubRequestDone) {
603
+ if (error) return;
604
+ const item = items.find((i) => i.query === request.query);
605
+ const created = item?.patch?.rows?.[0];
606
+ if (created) this.choose(String(created[item!.key]));
607
+ }
608
+ }
589
609
  ```
590
610
 
611
+ The hub then dispatches `lb-request-done` from the element that committed,
612
+ carrying the same `detail` as a bubbling `CustomEvent`, for any other
613
+ listener. Import its name as `LB_DONE_EVENT_NAME`.
614
+
591
615
  `items` is every response item that landed because of the request, in
592
616
  order. `refusal` is set beside `error` when the handler refused the
593
617
  request, holding its `message` and `columns`. An answer that moved the URL contributes its `lb-url` item, which
@@ -596,11 +620,12 @@ the round trip failed, and then nothing landed. A new row's key is in the
596
620
  answer only when the handler returned it in a patch: a refresh sends all
597
621
  rows, and nothing marks which one is new.
598
622
 
599
- By the time the event is dispatched, `lb-request-pending` is gone and the
600
- page is as the answer left it. A request that was never sent, because the
601
- hub or an ancestor stopped it, gets no `lb-request-done`. An element the
602
- answer removed, such as the row a delete took away, dispatches the event
603
- outside the document, and no ancestor it had there hears it.
623
+ By the time either is called, `lb-request-pending` is gone and the page is
624
+ as the answer left it. A request that was never sent, because the hub or an
625
+ ancestor stopped it, gets neither. An element the answer removed, such as
626
+ the row a delete took away, is outside the document by then: no ancestor it
627
+ had there hears the event, and no custom element the hub has removed hears
628
+ `lbRequestDone`.
604
629
 
605
630
  ### Holding rows
606
631
 
@@ -611,26 +636,22 @@ plain element carrying `lb-query` with a row template inside it needs no
611
636
  code, so a custom element holds rows only to do something with them once
612
637
  they land.
613
638
 
614
- A custom element carrying `lb-query` and a row template may implement one
615
- optional method, which the hub calls:
639
+ A custom element carrying `lb-query` and a row template implements
640
+ `lbRowsLanded`, which the hub calls once the rows have landed, with every
641
+ row placed and stamped:
616
642
 
617
643
  ```ts
618
- import type { RowsHost } from "@loadbare/app/types";
644
+ import type { HubLifecycle } from "@loadbare/app/types";
619
645
  import { ATTR_ROW_REQUESTED, REQUESTED_CREATED } from "@loadbare/app/constants";
620
646
 
621
- class ScrollingList extends HTMLElement implements RowsHost {
647
+ class ScrollingList extends HTMLElement implements HubLifecycle {
622
648
  lbRowsLanded() {
623
- // Once, after the rows have landed, every row placed and stamped.
624
649
  this.querySelector(`[${ATTR_ROW_REQUESTED}="${REQUESTED_CREATED}"]`)
625
650
  ?.scrollIntoView({ block: "nearest" });
626
651
  }
627
652
  }
628
653
  ```
629
654
 
630
- | Method | The hub calls it |
631
- |----------------|-------------------------------------------------------|
632
- | `lbRowsLanded` | After the rows land, with every row placed and stamped |
633
-
634
655
  Everything else is the hub's:
635
656
 
636
657
  | Concern | The hub |
@@ -649,11 +670,9 @@ gives each option and `<optgroup>` what landing does not, a value and a
649
670
  label, and the other scrolls to the row the page's request created or
650
671
  moved.
651
672
 
652
- A custom element the builder shipped absent under `lb-show` has not
653
- upgraded while its column is off, and the hub places the rows that land on
654
- it then as it places any. When the column first turns on and the element
655
- upgrades, the hub lands the query's last answer on it again, as all rows,
656
- so `lbRowsLanded` runs.
673
+ A custom element under an `lb-show` that is off still holds its rows. The
674
+ hub places the rows that land on it then as it places any, and calls its
675
+ `lbRowsLanded`, so it returns current.
657
676
 
658
677
  A custom element that walks its own rows finds them with `LIVE_ROW` from
659
678
  `@loadbare/app/constants`, never by `lb-key-value`: an element a `row` lands
@@ -284,10 +284,9 @@ scaffolding around rows, such as a group's heading or a ghost row. So
284
284
  a query nested in another query's rows need not land again, or land after
285
285
  the outer query, for a new outer row to show its choices.
286
286
 
287
- A custom element the builder shipped absent has not upgraded while its
288
- `lb-show` column is off. When the column first turns on and the element
289
- upgrades, the hub lands the kept answer on it again, so its `lbRowsLanded`
290
- runs. See [Holding rows](./custom-elements.md#holding-rows).
287
+ A custom element under an `lb-show` that is off is filled like any other,
288
+ and its `lbRowsLanded` runs as its rows land. See
289
+ [Holding rows](./custom-elements.md#holding-rows).
291
290
 
292
291
  Nothing is answered from what the hub kept: a request always goes to the
293
292
  server, and a query re-runs only when a response names it. A patch that
@@ -877,9 +876,10 @@ An ancestor may stop the event, and the request is not sent. A custom
877
876
  element may dispatch the event itself; see
878
877
  [Sending a request](./custom-elements.md#sending-a-request).
879
878
 
880
- Once the answer has landed, or the round trip has failed, the hub dispatches
881
- a bubbling `lb-request-done` event from the same element, carrying the
882
- request and what landed because of it, or the error; see
879
+ Once the answer has landed, or the round trip has failed, the hub calls
880
+ `lbRequestDone` on the custom elements from the committing element outward,
881
+ then dispatches a bubbling `lb-request-done` event from the same element.
882
+ Both carry the request and what landed because of it, or the error; see
883
883
  [Hearing how it turned out](./custom-elements.md#hearing-how-it-turned-out).
884
884
 
885
885
  ## The URL
@@ -182,8 +182,8 @@ the hub until the user says yes:
182
182
  | `exp-request` | The confirm button's `lb-request` |
183
183
  | `exp-confirm` | The confirm button's text, `Confirm` when absent |
184
184
 
185
- The widget closes the dialog once `lb-request-done` says the request
186
- succeeded, and leaves it open on a refusal.
185
+ The widget closes the dialog once the request succeeds, and leaves it open
186
+ on a refusal.
187
187
 
188
188
  ## `lb-form`
189
189