@loadbare/app 0.19.0 → 0.19.1

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.
@@ -89,7 +89,8 @@ elements as the page needs.
89
89
 
90
90
  What lands depends on the query's kind and on whether the element holds a
91
91
  row template, which is the first `<template>` among its descendants outside
92
- any nested `lb-query`:
92
+ any nested `lb-query`, or, when that template is a [group
93
+ template](#groups), the innermost template inside it:
93
94
 
94
95
  | Kind | Row template | The hub |
95
96
  |--------|--------------|----------------------------------------|
@@ -128,6 +129,9 @@ row's `account`:
128
129
  `lb-column` on an element with `lb-query` is undefined unless the element is
129
130
  a control.
130
131
 
132
+ A query that arrives with nowhere to land is reported to the console,
133
+ unless its name begins with `lb-`.
134
+
131
135
  An element with `lb-column` and no ancestor row receives nothing. The hub
132
136
  still gathers from it, so an insert form writes `lb-column` on controls that
133
137
  no row fills.
@@ -150,7 +154,9 @@ element that is not a control keeps the content the builder placed in it
150
154
  from its element file, and renders `lb-column-value` itself; see
151
155
  [Custom Elements](./custom-elements.md#receiving-a-value).
152
156
 
153
- A file input receives nothing, and the hub reports it to the console.
157
+ A file input receives nothing, not even `lb-column-value`, and the hub
158
+ reports it to the console and gathers nothing from it. A `<select>` with no
159
+ option for the value shows no selection.
154
160
 
155
161
  The hub hands every value to the browser as the server sent it. What a
156
162
  number, a date or a null looks like is decided in the query, or by a custom
@@ -221,7 +227,11 @@ what was typed, through a refresh and through a change of order. A row
221
227
  whose values changed is filled whole, and its controls show what arrived.
222
228
 
223
229
  A key is unique within a query. Two rows with one key in the same answer,
224
- or two live rows showing one key, are reported on the console.
230
+ or two live rows showing one key, are reported on the console. A row
231
+ missing its key column is reported and not landed.
232
+
233
+ A live row already in the markup when the first answer lands is one of the
234
+ query's rows.
225
235
 
226
236
  When the focus is in a live row that placing moves, the hub puts it back on
227
237
  the same element and scrolls it into view. When that row leaves, or a
@@ -266,14 +276,23 @@ server's order places the rows:
266
276
  </ul>
267
277
  ```
268
278
 
269
- Values compare by their JSON type: numbers numerically, strings as the
270
- user's language orders them, `false` before `true`, and `null` last. A
271
- number sent as a string sorts as a string, unless the query declares the
279
+ | Values | Compare |
280
+ |----------------------|----------------------------------------------|
281
+ | Two numbers | Numerically |
282
+ | Two strings | As the user's language orders them |
283
+ | Two booleans | `false` before `true` |
284
+ | Two different types | Booleans, then numbers, then strings |
285
+ | `null` or missing | After everything ascending, first descending |
286
+
287
+ A number sent as a string sorts as a string, unless the query declares the
272
288
  column's [type](./page-files.md#queries): a column declared `number` sorts
273
289
  numerically whatever JSON type its values arrive as, and a value that is no
274
- number sorts with the nulls. Rows that compare equal keep the order they
275
- arrived in. A row moves only when the order puts it somewhere
276
- else.
290
+ value of its declared type compares as `null` does. `readAs` in
291
+ `@loadbare/app/types` is the rule the hub applies.
292
+
293
+ Rows that compare equal keep the order they arrived in, a patch's new rows
294
+ after the rows already showing. A row moves only when the order
295
+ puts it somewhere else, so all rows landing again unchanged move nothing.
277
296
 
278
297
  ### Filters
279
298
 
@@ -323,6 +342,9 @@ The column's type is the one its query declares, if it declares one, and
323
342
  its JSON type otherwise. A value that is not of the column's type, and a
324
343
  column that is null or missing, pass only `!=`.
325
344
 
345
+ `parseFilter`, `passes` and `passesTerm` in `@loadbare/app/types` are the
346
+ rule the hub applies.
347
+
326
348
  ### Groups
327
349
 
328
350
  A group is a run of rows sharing the order's leading term. Write a
@@ -343,11 +365,32 @@ template. The nth group template breaks on the order's nth term.
343
365
  </ul>
344
366
  ```
345
367
 
346
- The group's contents land immediately before its nested template. When that
347
- template is inside the heading's element, the contents land inside it; when
348
- it is beside the heading, they land after it. `<tbody>` and `<optgroup>` do
349
- not nest, so a table's deeper levels are heading rows. Content after the
350
- nested template is the group's footer.
368
+ The hub clones the group template where the order breaks, and the group's
369
+ contents land immediately before the clone of its nested template, so the
370
+ markup's nesting decides the shape:
371
+
372
+ | The nested template is | The group's contents land | Example |
373
+ |------------------------------|------------------------------------|-----------------------------------|
374
+ | Inside the heading's element | Inside it | `<tbody>`, `<li>`, `<details>` |
375
+ | Beside the heading | After the heading, as its siblings | A heading `<tr>` in one `<tbody>` |
376
+
377
+ `<tbody>` and `<optgroup>` do not nest, so a table's levels below the first
378
+ are heading rows beside their rows:
379
+
380
+ ```html
381
+ <table lb-query="accounts">
382
+ <thead><tr><th>Account</th></tr></thead>
383
+ <template lb-group>
384
+ <tbody>
385
+ <tr><th scope="rowgroup" lb-column="category"></th></tr>
386
+ <template><tr><td lb-column="account_name"></td></tr></template>
387
+ </tbody>
388
+ </template>
389
+ </table>
390
+ ```
391
+
392
+ Content after the nested template lands after the group's rows, which is
393
+ where a group's footer goes.
351
394
 
352
395
  A heading is filled from its group's first row, so it may show a column
353
396
  other than the one the group breaks on. The hub stamps each top-level
@@ -356,6 +399,10 @@ element of a group with `lb-group-live`, `lb-group-column` and
356
399
  row. When the order changes, a group whose run goes on is kept and moved,
357
400
  and a group whose run ended leaves.
358
401
 
402
+ A group template with no nested template is reported to the console. More
403
+ group templates than the order has terms show each extra level as one
404
+ group, and are reported once.
405
+
359
406
  ### Aggregates
360
407
 
361
408
  `lb-count`, `lb-sum="col"`, `lb-avg="col"`, `lb-min="col"` and
@@ -366,10 +413,24 @@ or of the whole list outside any group:
366
413
  <tr><td><span lb-count></span> people</td><td lb-sum="salary"></td></tr>
367
414
  ```
368
415
 
369
- A sum and an average are exact, read from JSON numbers and from strings
370
- holding a plain decimal. `null` and empty values are left out. The result
371
- lands unformatted, and covers the rows showing: a row a filter hides is
372
- left out.
416
+ | Attribute | Sets the element to |
417
+ |----------------|-----------------------------------------------------------|
418
+ | `lb-count` | The number of rows |
419
+ | `lb-sum="col"` | The sum of `col` |
420
+ | `lb-avg="col"` | The average of `col` |
421
+ | `lb-min="col"` | The least value of `col`, by the [order](#order)'s comparison |
422
+ | `lb-max="col"` | The greatest, likewise |
423
+
424
+ An aggregate lands as a column does, with `lb-column-value`. A sum and an
425
+ average are exact decimals, read from JSON numbers and from strings holding
426
+ a plain decimal, as Postgres sends `numeric`. A sum has as many places as
427
+ the most precise value, and an average up to two more. `null` and empty
428
+ values are left out, as SQL leaves them out, and any other value is left
429
+ out and reported once. The result lands unformatted.
430
+
431
+ An aggregate covers the rows showing, so a row a filter hides is left out.
432
+ Where the true total matters beyond what the page shows, have the server
433
+ answer it as a row.
373
434
 
374
435
  ### What happened to a row
375
436
 
@@ -384,10 +445,16 @@ Every change to a list is one a stylesheet can see:
384
445
  | `lb-row-leaving` | A row on its way out |
385
446
  | `lb-group-leaving` | A group on its way out |
386
447
 
387
- A row that leaves loses `lb-row-live`, takes `inert`
448
+ The hub touches a row when an answer names it. `lb-row-created`,
449
+ `lb-row-changed` and `lb-row-moved` are cleared when it next does, and one
450
+ applied again is removed and added as two changes, so an animation keyed to
451
+ it plays again. `lb-row-requested` is cleared at every landing of the row's
452
+ query, and set only by a landing that answers a request from the page.
453
+
454
+ A row or a group that leaves loses `lb-row-live` or `lb-group-live`, takes
455
+ `lb-row-leaving` or `lb-group-leaving` and `inert`
388
456
  ([miswart](../miswarts.md#leaving-rows-are-inert)), and is removed once its
389
- animations finish, at once when there are none. `lb-row-requested` is
390
- cleared at every landing; the others when the hub next touches the row.
457
+ animations finish, at once when there are none.
391
458
 
392
459
  A row is moved when its values changed and the rows whose values did not
393
460
  change now stand around it differently. A row it passed is not stamped,
@@ -398,6 +465,11 @@ and a change of the user's order, which changes no values, stamps none.
398
465
  [lb-row-leaving] { animation: depart 200ms forwards; }
399
466
  ```
400
467
 
468
+ A row never travels between its two places, since that takes measuring, and
469
+ measuring takes script. Scrolling to a row is script too: a custom element
470
+ reads `lb-row-requested` in
471
+ [`lbRowsLanded`](./custom-elements.md#holding-rows).
472
+
401
473
  ### What arrives later
402
474
 
403
475
  The hub keeps each query's last answer until the page changes, with any
@@ -451,6 +523,39 @@ hides is not counted. An empty query is a stylesheet rule:
451
523
  The element carrying `lb-query` is a `<div>` here rather than the `<ul>`, so
452
524
  that the message is inside it and one rule reaches both.
453
525
 
526
+ ### Master-detail
527
+
528
+ A master is a `row` query and its detail is a `rows` query, as two names
529
+ answered side by side. A column never holds rows.
530
+
531
+ ```html
532
+ <section lb-query="invoice">
533
+ <h2 lb-column="number"></h2>
534
+ <span lb-column="customer"></span>
535
+ </section>
536
+
537
+ <table>
538
+ <tbody lb-query="invoiceLines">
539
+ <template><tr><td lb-column="item"></td><td lb-column="amount"></td></tr></template>
540
+ </tbody>
541
+ </table>
542
+ ```
543
+
544
+ Many masters, each with its own detail, is one `rows` query of joined rows:
545
+ each detail row carries its master's columns. A [group template](#groups)
546
+ shows each master as a group, ordered by the master's key first.
547
+
548
+ A query nested in another query's row template receives the same rows in
549
+ every live row. That serves a picker offering the same choices on every
550
+ row, and is not a way to show a different detail per row; see
551
+ [What arrives later](#what-arrives-later).
552
+
553
+ Which master a page shows is a query parm, which a query reads off `ctx`,
554
+ since a query takes no argument from the browser; see
555
+ [Query parms](./chrome.md#query-parms). When a write creates or removes the
556
+ master, the handler moves the URL; see
557
+ [Moving the URL](./page-files.md#moving-the-url).
558
+
454
559
  ## Conditional rendering
455
560
 
456
561
  A page holds every element it can show. Write `lb-show` on an element,
@@ -565,10 +670,14 @@ answers with response items, which land like any others.
565
670
  | A control | `change` |
566
671
  | Any other element | `click` |
567
672
 
568
- A click inside an element carrying `lb-request` commits it unless the click
569
- lands on interactive content between them: a link, a button, a control or a
570
- label, as HTML defines interactive content. A click into an input inside a
571
- deletable row focuses the input.
673
+ A click inside an element carrying `lb-request` commits it, as long as the
674
+ element is inside `<lb-hub>` and no interactive content lies between them.
675
+ Interactive content is HTML's: `<a href>`, `<button>`, `<input>` other than
676
+ hidden, `<select>`, `<textarea>`, `<label>`, `<details>`, `<iframe>`,
677
+ `<embed>`, `<audio controls>`, `<video controls>` and `<img usemap>`, plus
678
+ any form-associated custom element. A click on one of them belongs to it: a
679
+ click into an input inside a deletable row focuses the input and deletes
680
+ nothing.
572
681
 
573
682
  A submit button with a form owner commits with its form. On `submit`, the
574
683
  hub sends the submitter's `lb-request` when the submitter carries one, and
@@ -602,7 +711,10 @@ carrying `lb-request`:
602
711
 
603
712
  The hub gathers from controls only, and skips a control that belongs to a
604
713
  query nested inside the group, except that a form gathers the rows of a
605
- query nested in its own; see [A form over a set of rows](#a-form-over-a-set-of-rows). A form owner includes a control outside the
714
+ query nested in its own; see [A form over a set of rows](#a-form-over-a-set-of-rows).
715
+ An element carrying `lb-query` and `lb-column` both, such as a picker,
716
+ belongs to the row around it, and is gathered with it and sends for that
717
+ row's query. A form owner includes a control outside the
606
718
  form that names it with the HTML `form` attribute, which is how a table row,
607
719
  where a form cannot go, sends one:
608
720
 
@@ -674,8 +786,10 @@ query's name, in `rows`:
674
786
  The list of deduction types lives only in the database: the nested query
675
787
  answers it, and the form shows one input per row.
676
788
 
677
- - Every live row of the nested query goes, whatever its values. What an
678
- empty value means, such as no deduction of that type, is the handler's.
789
+ - Every live row of the nested query that holds a control goes, whatever
790
+ its values. What an empty value means, such as no deduction of that type,
791
+ is the handler's. A picker's options hold no control, so a picker in the
792
+ form sends its own value and none of its rows.
679
793
  - One level only. A query nested inside a nested row shows the same rows in
680
794
  every row around it, so it is not gathered.
681
795
  - An insert, an update and a declared request carry `rows`. A delete does
@@ -685,7 +799,9 @@ answers it, and the form shows one input per row.
685
799
  - A live row gathers nothing nested, as before.
686
800
 
687
801
  The handler for the form's query receives `rows` beside `values`, and writes
688
- the master and its details together; see [crud](./page-files.md#crud).
802
+ the master and its details together; see [crud](./page-files.md#crud). The
803
+ server checks that `rows` holds string keys and string values by query, and
804
+ ignores a request whose `rows` does not.
689
805
 
690
806
  A nested query answers once for the page, so its rows are the same wherever
691
807
  they show. The form enters a new master with its details, or edits the
@@ -722,9 +838,14 @@ holds what a submit will gather:
722
838
  </section>
723
839
  ```
724
840
 
725
- The same holds for a request a custom element sends carrying `key: ""`.
726
- An update or delete with no row around it at all has no key either, and
727
- that is reported to the console.
841
+ The same holds for a request a custom element sends carrying `key: ""`. No
842
+ ancestor sees a request not sent, and it gets no `lb-request-done`. An
843
+ update or delete with no row around it at all has no key either, and that
844
+ is reported to the console.
845
+
846
+ A field the request already carries is its issuer's and is kept, so a
847
+ custom element that dispatches its own request with `values` is not
848
+ gathered over.
728
849
 
729
850
  ### Inserting, updating and deleting rows
730
851
 
@@ -741,6 +862,10 @@ The hub sends one of these only when it has what the table lists, and
741
862
  otherwise reports to the console. An insert or an update that gathers
742
863
  nothing is not sent.
743
864
 
865
+ For a query the hub serves, `lb-url` or `lb-choices`, the hub answers
866
+ `lb-row-update` itself, with no round trip; see
867
+ [Query parms](./chrome.md#query-parms) and [Choices](#choices).
868
+
744
869
  A key comes from a live row, or from an element a `row` landed on, so all
745
870
  three work against either kind:
746
871
 
@@ -804,11 +929,13 @@ round trip, including columns no element in the table shows. The user
804
929
  edits a row they can see, so the row is on the page. A row that has to be
805
930
  fetched to be edited is another page, with that row as its master.
806
931
 
807
- The hub stamps the form with the row's key, so the form's request carries
808
- it, and stamps the row `lb-row-editing`. New stamps an empty key, making the
809
- form [a row not yet entered](#a-row-not-yet-entered), and resets every
810
- control to its default. The form's state is in its `lb-key-value`
811
- stamp:
932
+ Pointing a form first empties it: every column it names lands as null, and
933
+ the form resets, so each control returns to its default. Edit then lands
934
+ the row on the form, stamps the form with the row's key, so the form's
935
+ request carries it, and stamps the row `lb-row-editing`. New stamps an
936
+ empty key, making the form [a row not yet entered](#a-row-not-yet-entered).
937
+ Both focus the form's first control. The form's state is in its
938
+ `lb-key-value` stamp:
812
939
 
813
940
  | `lb-key-value` | The form is |
814
941
  |----------------|-----------------------------|
@@ -851,6 +978,11 @@ landing again lands nothing on it, and what the user has typed stays.
851
978
  A button inside a form that carries a command is `type="button"`, or it
852
979
  submits the form instead.
853
980
 
981
+ A command the hub cannot carry out is reported to the console: Edit from
982
+ outside a live row of the form's query, Edit for a key the hub holds no row
983
+ for, and any command aimed at an element that is not a form carrying
984
+ `lb-query`.
985
+
854
986
  ### Selecting rows
855
987
 
856
988
  A checkbox carrying `lb-select` in a row template selects the live row it
@@ -927,7 +1059,7 @@ A button carries no value, so a declared request from one sends the values
927
1059
  of the live row or form it sits in, and the server computes the rest.
928
1060
 
929
1061
  A request name beginning with `lb-` that is not one of the three is refused
930
- by the builder.
1062
+ by the builder in markup, and by the hub when a script dispatches it.
931
1063
 
932
1064
  ### Request state
933
1065
 
@@ -992,14 +1124,23 @@ submit, as for any invalid control, so the user edits before trying again.
992
1124
 
993
1125
  A form-associated custom element takes a column's message when it has
994
1126
  `setCustomValidity` and `reportValidity`; see
995
- [Being a control](./custom-elements.md#being-a-control).
1127
+ [Being a control](./custom-elements.md#being-a-control). A request for
1128
+ several rows takes the whole-request message only. A column the request
1129
+ gathered no control for is reported to the console.
1130
+
1131
+ On the wire a refusal is status 422 with `{ refusal: { message?, columns? } }`;
1132
+ see [The data channel](./server.md#the-data-channel).
996
1133
 
997
1134
  ### The request event
998
1135
 
999
1136
  The hub dispatches every request as a bubbling `lb-request` event from the
1000
1137
  element that committed, with the request as its `detail`, before sending it.
1001
- An ancestor may stop the event, and the request is not sent. A custom
1002
- element may dispatch the event itself; see
1138
+ The hub completes the request on the event's way down, so every ancestor
1139
+ sees it whole on the way up. An ancestor may stop it, and the request is
1140
+ not sent: the event is not cancelable, so an interceptor calls
1141
+ `stopPropagation`, not `preventDefault`. That is how a confirmation wrapper
1142
+ works without the wrapped element knowing about it. A custom element may
1143
+ dispatch the event itself; see
1003
1144
  [Sending a request](./custom-elements.md#sending-a-request).
1004
1145
 
1005
1146
  Once the answer has landed, or the round trip has failed, the hub calls
@@ -1008,6 +1149,37 @@ then dispatches a bubbling `lb-request-done` event from the same element.
1008
1149
  Both carry the request and what landed because of it, or the error; see
1009
1150
  [Hearing how it turned out](./custom-elements.md#hearing-how-it-turned-out).
1010
1151
 
1152
+ ### The round trip
1153
+
1154
+ A request the hub sends has ten seconds to come back. Past that the hub
1155
+ aborts it and treats it as a failure, since `fetch` imposes no deadline of
1156
+ its own. An application cannot change the deadline.
1157
+
1158
+ A round trip fails on a server error, on a network failure, or on that
1159
+ deadline, and all three stamp `lb-request-error` on the element that sent
1160
+ the request. A refusal is not a failure.
1161
+
1162
+ After a successful `lb-row-insert` gathered from a form, the hub resets the
1163
+ form, after it lands the response. A failed insert resets nothing, so the
1164
+ entry can be corrected. An `lb-row-update` resets nothing: the row it sent
1165
+ lands back on its controls.
1166
+
1167
+ A reset of any form, whether the hub's, a reset button's or a script's,
1168
+ returns its controls to their defaults, and the hub removes
1169
+ `lb-column-value` from each of them. A cancelled reset keeps both.
1170
+
1171
+ A page load that fails lands nothing and is reported to the console. A load
1172
+ started by a request for `lb-url` stamps the element that sent it, as any
1173
+ request stamps its element.
1174
+
1175
+ A response answers for the URL its request was sent from, path and query
1176
+ string both. One that returns after the URL has moved lands nothing, since
1177
+ it describes what the user is no longer looking at: a request's answer,
1178
+ including any `url()` it carries, and a page load overtaken by a newer one.
1179
+ A write whose answer is dropped this way has still happened. Its
1180
+ `lb-request-done` carries no items and no error, and an insert still resets
1181
+ its form.
1182
+
1011
1183
  ## The URL
1012
1184
 
1013
1185
  The hub serves one query of its own, `lb-url`: one row holding the path, the
@@ -1056,7 +1228,9 @@ The developer writes `lb-` attributes in markup, and script never assigns
1056
1228
  them. The builder checks the markup of the chrome and of every page, after
1057
1229
  expansion, and refuses to build:
1058
1230
 
1059
- - an `lb-` attribute that is not one of the sixteen the developer writes
1231
+ - an `lb-` attribute that is not one of the sixteen the developer writes;
1232
+ `lb-exp-slot` and `lb-exp-template` are consumed by expansion and never
1233
+ reach the check
1060
1234
  - `lb-request` naming an `lb-` request other than the three Loadbare provides
1061
1235
  - `lb-query` naming a query the page does not declare; every page and the
1062
1236
  chrome may name `lb-url` and `lb-choices`, and the chrome names no other
@@ -1078,6 +1252,22 @@ expansion, and refuses to build:
1078
1252
  template, or beside `lb-column` or `lb-request`
1079
1253
  - a `command` beginning `--lb-` that is not one of the three commands
1080
1254
  - one of the three commands with no `commandfor`
1255
+ - `lb-group` on anything but a `<template>`, or on one holding no
1256
+ `<template>` for its contents
1257
+ - an aggregate on a `<template>`, or any but `lb-count` naming no column
1258
+ - a chrome without exactly one `<lb-hub>` and exactly one `<main>`, a
1259
+ `<main>` outside `<lb-hub>`, or a `<main>` holding anything but whitespace
1260
+ - a page carrying an `<lb-hub>` or a `<main>`
1261
+
1262
+ The builder also reads the script of every custom element it ships, other
1263
+ than `<lb-hub>`, and refuses one that declares a method, accessor or field
1264
+ whose name is `lb`, a capital, and anything Loadbare does not call; see
1265
+ [Reserved methods](./names.md#reserved-methods).
1266
+
1267
+ `lb-column` with no ancestor row is allowed: the hub gathers from it.
1268
+
1269
+ Script never assigns an `lb-` attribute, so the markup states every one
1270
+ that exists, and checking the markup is sound.
1081
1271
 
1082
1272
  To know what each page declares, the builder loads its `.queries.ts` and
1083
1273
  `.requests.ts` modules, so their import-time code runs during the build, as