@loadbare/app 0.12.0 → 0.14.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 (56) hide show
  1. package/dist/build/assemble.d.ts.map +1 -1
  2. package/dist/build/assemble.js +87 -1
  3. package/dist/build/assemble.js.map +1 -1
  4. package/dist/build/cli.js +2 -1
  5. package/dist/build/cli.js.map +1 -1
  6. package/dist/build/elements.d.ts +9 -1
  7. package/dist/build/elements.d.ts.map +1 -1
  8. package/dist/build/elements.js +50 -0
  9. package/dist/build/elements.js.map +1 -1
  10. package/dist/build/origins.d.ts +2 -0
  11. package/dist/build/origins.d.ts.map +1 -1
  12. package/dist/build/origins.js +1 -1
  13. package/dist/build/origins.js.map +1 -1
  14. package/dist/core/lb-constants.d.ts +34 -1
  15. package/dist/core/lb-constants.d.ts.map +1 -1
  16. package/dist/core/lb-constants.js +105 -0
  17. package/dist/core/lb-constants.js.map +1 -1
  18. package/dist/core/lb-types.d.ts +74 -18
  19. package/dist/core/lb-types.d.ts.map +1 -1
  20. package/dist/core/lb-types.js +74 -11
  21. package/dist/core/lb-types.js.map +1 -1
  22. package/dist/hub/lb-apply.d.ts +57 -18
  23. package/dist/hub/lb-apply.d.ts.map +1 -1
  24. package/dist/hub/lb-apply.js +661 -63
  25. package/dist/hub/lb-apply.js.map +1 -1
  26. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  27. package/dist/hub/lb-hub.browser.js +400 -33
  28. package/dist/hub/lb-hub.browser.js.map +1 -1
  29. package/dist/server/lb-express.d.ts +19 -7
  30. package/dist/server/lb-express.d.ts.map +1 -1
  31. package/dist/server/lb-express.js +62 -26
  32. package/dist/server/lb-express.js.map +1 -1
  33. package/dist/server/lb-server.d.ts +80 -11
  34. package/dist/server/lb-server.d.ts.map +1 -1
  35. package/dist/server/lb-server.js +185 -33
  36. package/dist/server/lb-server.js.map +1 -1
  37. package/docs/TECHREF-1.0.md +584 -111
  38. package/docs/comparison.md +17 -13
  39. package/docs/possible-ideas.md +308 -0
  40. package/docs/reference/chrome.md +7 -1
  41. package/docs/reference/custom-elements.md +134 -39
  42. package/docs/reference/data-binding.md +405 -17
  43. package/docs/reference/page-files.md +101 -19
  44. package/docs/reference/server.md +24 -0
  45. package/docs/reference/widgets.md +118 -25
  46. package/docs/roadmap.md +17 -6
  47. package/docs/testing.md +21 -10
  48. package/package.json +2 -3
  49. package/skills/loadbare-app/SKILL.md +92 -30
  50. package/skills/loadbare-app/references/TECHREF-1.0.md +584 -111
  51. package/skills/loadbare-app/references/chrome.md +7 -1
  52. package/skills/loadbare-app/references/custom-elements.md +134 -39
  53. package/skills/loadbare-app/references/data-binding.md +405 -17
  54. package/skills/loadbare-app/references/page-files.md +101 -19
  55. package/skills/loadbare-app/references/server.md +24 -0
  56. package/skills/loadbare-app/references/widgets.md +118 -25
@@ -72,6 +72,10 @@ Put everything the user interacts with inside `<lb-hub>`. The hub normally
72
72
  sits directly inside `<body>`, with banner, nav, footer and `<main>` inside
73
73
  it, so Loadbare can act on all of them.
74
74
 
75
+ Write exactly one `<lb-hub>` and exactly one `<main>`, with the `<main>`
76
+ inside the hub and empty but for whitespace. The builder rejects a chrome
77
+ that does otherwise.
78
+
75
79
  The chrome's `<title>` is the document title until the first page shows.
76
80
  From then on the hub sets the document title to the page's label; see
77
81
  [The URL](#the-url).
@@ -165,7 +169,9 @@ pushes one instead when the element carrying `lb-request` also carries
165
169
 
166
170
  The hub answers the request itself, with no round trip, then reloads the
167
171
  page's queries at the new URL. The page stays in place, and rows that come
168
- back keep their place.
172
+ back keep their place. A request that changes only `lb-order-<query>`, the
173
+ user's [order](./data-binding.md#order) for a query the hub sorts itself,
174
+ re-sorts the rows already on the page and loads nothing.
169
175
 
170
176
  A control whose column the URL does not carry lands empty, so every control
171
177
  inside `lb-query="lb-url"` shows what the address bar says, after a reload
@@ -278,7 +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).
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.
282
285
 
283
286
  ### What the hub and an element say to each other
284
287
 
@@ -286,9 +289,9 @@ Each direction has one mechanism for each kind of message:
286
289
 
287
290
  | Direction | What | How | Today |
288
291
  |----------------|--------------------------------------------|-----------------------------|-------|
289
- | Hub to element | State that lasts | An attribute the hub stamps | `lb-column-value`, `lb-key-value`, `lb-query-row-count`, `lb-request-pending`, `lb-request-error` |
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` |
290
293
  | Hub to element | State the platform already names | A property | A control's `value` |
291
- | Hub to element | Work the hub needs done now, while landing | An optional `lb` method | `lbPlaceRow`, `lbRowsLanded` |
294
+ | Hub to element | Work the hub needs done now, while landing | An optional `lb` method | `lbRowsLanded` |
292
295
  | Hub to element | A moment an element started | A bubbling event | `lb-request-done` |
293
296
  | Element to hub | A moment | A bubbling event | `lb-request`, and `change`, `click` and `submit` |
294
297
 
@@ -329,7 +332,7 @@ So each kind of work has one place:
329
332
  reads no attribute and no child: an element made with
330
333
  `document.createElement` has neither when it runs.
331
334
  2. **A child is looked up when it is needed**, in a handler, a getter,
332
- `lbPlaceRow` or `lbRowsLanded`, and never held from setup.
335
+ or `lbRowsLanded`, and never held from setup.
333
336
  3. **`connectedCallback` runs on every move**, so it is written to run
334
337
  again. It rearranges the element's own children into a state it checks
335
338
  for first, or adds a listener to `document` or `window`, which
@@ -409,9 +412,104 @@ including through the HTML `form` attribute, so a form gathers it on submit.
409
412
  Write `formResetCallback` to restore the default value: the hub resets a form
410
413
  after a successful insert.
411
414
 
415
+ A control takes the message of a refusal naming its column when it has
416
+ `setCustomValidity` and `reportValidity`, as a native control does; see
417
+ [Refusals](./data-binding.md#refusals). A control wrapping a native one
418
+ passes both to it, so the browser shows the message there and focus lands
419
+ there.
420
+
412
421
  The `<lb-input>`, `<lb-select>`, `<lb-options>` and `<lb-picker>` widgets
413
422
  are controls; see [The Basic Widget Library](./widgets.md).
414
423
 
424
+ ### Example: a file's text as a value
425
+
426
+ The hub takes no file input as a control. A custom element wraps one and is
427
+ the control in its place. This one reads the chosen file as text, which
428
+ suits a CSV or a bank statement, and sends the file's name under a second
429
+ column:
430
+
431
+ ```html
432
+ <!-- src/file-text.html -->
433
+ <label>{{label}} <input type="file" accept="{{accept}}" /></label>
434
+ <input type="hidden" lb-column="{{name-column}}" />
435
+ ```
436
+
437
+ ```ts
438
+ // src/file-text.browser.ts
439
+ class FileText extends HTMLElement {
440
+ static formAssociated = true;
441
+ #internals = this.attachInternals();
442
+ #value = "";
443
+
444
+ constructor() {
445
+ super();
446
+ this.addEventListener("change", (e) => {
447
+ const input = e.target as HTMLInputElement;
448
+ if (e.target === this || input.type !== "file") return;
449
+ e.stopPropagation();
450
+ void this.read(input);
451
+ });
452
+ }
453
+
454
+ get value(): string {
455
+ return this.#value;
456
+ }
457
+
458
+ set value(value: string) {
459
+ this.#value = value ?? "";
460
+ this.#internals.setFormValue(this.#value);
461
+ }
462
+
463
+ formResetCallback() {
464
+ this.value = "";
465
+ this.setName("");
466
+ }
467
+
468
+ async read(input: HTMLInputElement) {
469
+ const file = input.files?.[0] ?? null;
470
+ this.value = file ? await file.text() : "";
471
+ this.setName(file?.name ?? "");
472
+ this.dispatchEvent(new Event("change", { bubbles: true }));
473
+ }
474
+
475
+ setName(name: string) {
476
+ const hidden = this.querySelector<HTMLInputElement>('input[type="hidden"]');
477
+ if (hidden) hidden.value = name;
478
+ }
479
+ }
480
+
481
+ customElements.define("file-text", FileText);
482
+ ```
483
+
484
+ ```html
485
+ <!-- src/uploads.page.html -->
486
+ <form lb-query="uploads" lb-request="lb-row-insert">
487
+ <file-text
488
+ lb-column="filetext"
489
+ exp-name-column="filename"
490
+ exp-label="Statement file"
491
+ exp-accept=".csv,.ofx"
492
+ ></file-text>
493
+ <button type="submit">Upload</button>
494
+ </form>
495
+ ```
496
+
497
+ Reading a file is asynchronous, so the element stops the file input's own
498
+ `change` and fires its own once the text is in hand. A form submitted before
499
+ then gathers the empty value, and the handler refuses it as no file.
500
+
501
+ A control gathers one column, so the name rides in a hidden input that the
502
+ hub gathers as a control of its own. The form gathers both on submit. Its
503
+ reset after the insert clears the file input but not the hidden one, whose
504
+ `value` is its default, so `formResetCallback` clears the name itself.
505
+
506
+ The text travels in the request's JSON body, which `hubRoutes` limits to
507
+ 100 KB unless the server raises it; see [Options](./server.md#options).
508
+
509
+ The choices here are this application's: text rather than binary, the whole
510
+ file in the request, the name in a second column. A different application
511
+ writes its own element, under its own name, to make different ones.
512
+
415
513
  ### Receiving a value
416
514
 
417
515
  A custom element that is not a control keeps the content the builder placed
@@ -491,7 +589,8 @@ this.addEventListener(LB_DONE_EVENT_NAME, (e) => {
491
589
  ```
492
590
 
493
591
  `items` is every response item that landed because of the request, in
494
- order. An answer that moved the URL contributes its `lb-url` item, which
592
+ order. `refusal` is set beside `error` when the handler refused the
593
+ request, holding its `message` and `columns`. An answer that moved the URL contributes its `lb-url` item, which
495
594
  carries a key only the server knew, and the page load. `error` is set when
496
595
  the round trip failed, and then nothing landed. A new row's key is in the
497
596
  answer only when the handler returned it in a patch: a refresh sends all
@@ -505,34 +604,32 @@ outside the document, and no ancestor it had there hears it.
505
604
 
506
605
  ### Holding rows
507
606
 
508
- The hub lands every row template itself. A plain element carrying `lb-query`
509
- with a row template inside it needs no code, so a custom element holds rows
510
- only when they need placement or scaffolding that only it can decide.
607
+ The hub lands every row template itself, places every row by the query's
608
+ order, and builds every group the markup's group templates describe; see
609
+ [Order](./data-binding.md#order) and [Groups](./data-binding.md#groups). A
610
+ plain element carrying `lb-query` with a row template inside it needs no
611
+ code, so a custom element holds rows only to do something with them once
612
+ they land.
511
613
 
512
- A custom element carrying `lb-query` and a row template may implement two
513
- optional methods, which the hub calls:
614
+ A custom element carrying `lb-query` and a row template may implement one
615
+ optional method, which the hub calls:
514
616
 
515
617
  ```ts
516
- import type { RowsHost, Row } from "@loadbare/app/types";
517
-
518
- class SortedList extends HTMLElement implements RowsHost {
519
- lbPlaceRow(el: Element, row: Row, template: HTMLTemplateElement) {
520
- // Where this live row goes. Called with the live row detached, on its
521
- // first appearance and again whenever all rows decide the order.
522
- }
618
+ import type { RowsHost } from "@loadbare/app/types";
619
+ import { ATTR_ROW_REQUESTED, REQUESTED_CREATED } from "@loadbare/app/constants";
523
620
 
621
+ class ScrollingList extends HTMLElement implements RowsHost {
524
622
  lbRowsLanded() {
525
- // Once, after the rows have landed. For scaffolding derived from the
526
- // rows: a section heading, an <optgroup>, anything that goes when its
527
- // last row does.
623
+ // Once, after the rows have landed, every row placed and stamped.
624
+ this.querySelector(`[${ATTR_ROW_REQUESTED}="${REQUESTED_CREATED}"]`)
625
+ ?.scrollIntoView({ block: "nearest" });
528
626
  }
529
627
  }
530
628
  ```
531
629
 
532
- | Method | The hub calls it |
533
- |----------------|---------------------|
534
- | `lbPlaceRow` | To place a live row |
535
- | `lbRowsLanded` | After the rows land |
630
+ | Method | The hub calls it |
631
+ |----------------|-------------------------------------------------------|
632
+ | `lbRowsLanded` | After the rows land, with every row placed and stamped |
536
633
 
537
634
  Everything else is the hub's:
538
635
 
@@ -540,25 +637,23 @@ Everything else is the hub's:
540
637
  |-------------|----------------------------------------------------------|
541
638
  | Cloning | Clones the row template once per new key |
542
639
  | Matching | Fills the live row already showing that key |
543
- | Removing | Removes the live rows the response says are gone |
544
- | Stamping | Stamps each live row with `lb-row-live` and `lb-key-value` |
640
+ | Placing | Places every live row by the query's order |
641
+ | Grouping | Builds a group where the order breaks, and removes it with its last row |
642
+ | Removing | Marks the live rows the response says are gone as leaving, and removes them |
643
+ | Stamping | Stamps each live row with `lb-row-live`, `lb-key-value`, and what happened to it |
545
644
  | Counting | Stamps `lb-query-row-count` with the number of live rows |
546
645
 
547
- `lbPlaceRow` receives the live row already filled and not yet in the
548
- document, so a custom element that reads a column to decide where the row
549
- goes can. Without it, the hub places a live row immediately before the row
550
- template, in arrival order. `lb-options.browser.ts` and
551
- `lb-table.browser.ts` in [`@loadbare/widgets`](./widgets.md) are two
552
- different placements over the same landing.
646
+ `lb-options.browser.ts` and `lb-table.browser.ts` in
647
+ [`@loadbare/widgets`](./widgets.md) are two uses of `lbRowsLanded`: one
648
+ gives each option and `<optgroup>` what landing does not, a value and a
649
+ label, and the other scrolls to the row the page's request created or
650
+ moved.
553
651
 
554
652
  A custom element the builder shipped absent under `lb-show` has not
555
- upgraded while its column is off, so it has no `lbPlaceRow` when rows land
556
- on it then, and the hub places them before the template. When the column
557
- first turns on and the element upgrades, the hub lands the query's last
558
- answer on it again, as all rows, so every row goes through `lbPlaceRow` and
559
- `lbRowsLanded` runs. Once upgraded it stays so: when its branch goes away and
560
- comes back, it has placed every row that landed meanwhile, and nothing is
561
- landed again.
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.
562
657
 
563
658
  A custom element that walks its own rows finds them with `LIVE_ROW` from
564
659
  `@loadbare/app/constants`, never by `lb-key-value`: an element a `row` lands