@loadbare/app 0.19.1 → 0.21.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.
@@ -268,7 +268,7 @@ Each direction has one mechanism for each kind of message:
268
268
 
269
269
  | Direction | What | How | Today |
270
270
  |----------------|--------------------------------------------|-----------------------------|-------|
271
- | 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`, `lb-request-refused` |
271
+ | Hub to element | State that lasts | An attribute the hub stamps | `lb-column-value`, `lb-key-value`, `lb-query-row-count`, `lb-query-unsorted-count`, `lb-query-order`, `lb-group-*`, `lb-row-*`, `lb-request-pending`, `lb-request-error`, `lb-request-refused` |
272
272
  | Hub to element | State the platform already names | A property | A control's `value` |
273
273
  | Hub to element | What the hub did | An optional `lb` method | `lbAttached`, `lbRowsLanded`, `lbRequestDone`, `lbDetached` |
274
274
  | Hub to element | A moment an element started | A bubbling event | `lb-request-done` |
@@ -627,7 +627,8 @@ had there hears the event, and no custom element the hub has removed hears
627
627
  ### Holding rows
628
628
 
629
629
  The hub lands every row template itself, places every row by the query's
630
- order, and builds every group the markup's group templates describe; see
630
+ order (see [Rows the user is working in](./data-binding.md#rows-the-user-is-working-in)
631
+ for when it does not), and builds every group the markup's group templates describe; see
631
632
  [Order](./data-binding.md#order) and [Groups](./data-binding.md#groups). A
632
633
  plain element carrying `lb-query` with a row template inside it needs no
633
634
  code, so a custom element holds rows only to do something with them once
@@ -655,12 +656,12 @@ Everything else is the hub's:
655
656
  |-------------|----------------------------------------------------------|
656
657
  | Cloning | Clones the row template once per new key |
657
658
  | Matching | Fills the live row already showing that key, when its values changed |
658
- | Placing | Places every live row by the user's order or the query's |
659
+ | Placing | Places every live row by the user's order or the query's, and leaves them where they stand for an edit from inside the list |
659
660
  | Filtering | Hides a row the user's filter does not pass, and a group whose rows are all hidden |
660
661
  | Grouping | Builds a group where the order breaks, keeps it while its run goes on, and removes it with its last row |
661
662
  | Removing | Marks the live rows the response says are gone as leaving, and removes them |
662
663
  | Stamping | Stamps each live row with `lb-row-live`, `lb-key-value`, and what happened to it |
663
- | Counting | Stamps `lb-query-row-count` with the number of live rows showing |
664
+ | Counting | Stamps `lb-query-row-count` with the number of live rows showing, `lb-query-unsorted-count` with the number out of order, and `lb-query-order` with the order placing them |
664
665
 
665
666
  `lb-options.browser.ts` and `lb-table.browser.ts` in
666
667
  [`@loadbare/widgets`](./widgets.md) are two uses of `lbRowsLanded`: one
@@ -2,7 +2,7 @@
2
2
 
3
3
  A page shows server data by naming queries and columns in its HTML, and asks
4
4
  the server to change that data by naming requests. The developer writes
5
- sixteen `lb-` attributes. The server declares every query a page may show and
5
+ seventeen `lb-` attributes. The server declares every query a page may show and
6
6
  every request it may send, and refuses anything it has not declared.
7
7
 
8
8
  Everything is a query. A query is a name for rows, of kind `row` or `rows`,
@@ -54,6 +54,7 @@ each with its key, and permits the three requests on `roster`; see
54
54
  | `lb-select` | A checkbox in a row template | Nothing: it selects its live row; see [Selecting rows](#selecting-rows) |
55
55
  | `lb-request` | Any element | The request it sends when it commits |
56
56
  | `lb-url-link` | An `<a>` | A link between pages; see [chrome](./chrome.md#links) |
57
+ | `lb-url-parm` | An `<a lb-url-link>` in a row | Columns of the row its `href` carries as parms; see [chrome](./chrome.md#a-link-from-a-row) |
57
58
  | `lb-url-push` | An element with `lb-request` | A URL change that pushes a history entry |
58
59
  | `lb-url-unknown` | A `<dialog>` in the chrome | The dialog for a URL that names no page |
59
60
 
@@ -66,8 +67,10 @@ builder writes a stamp, and a stylesheet or a custom element reads it.
66
67
  | `lb-key-value` | The key of a live row, or of a `row` landed on an element |
67
68
  | `lb-row-live` | The element is a live row |
68
69
  | `lb-query-row-count` | The number of live rows an element shows |
70
+ | `lb-query-unsorted-count` | The number of them out of order; see [Rows the user is working in](#rows-the-user-is-working-in) |
71
+ | `lb-query-order` | The order placing them; see [Order](#order) |
69
72
  | `lb-group-live`, `lb-group-column`, `lb-group-value` | A group, the column it breaks on, and its value; see [Groups](#groups) |
70
- | `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested` | What happened to a row; see [What happened to a row](#what-happened-to-a-row) |
73
+ | `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested`, `lb-row-unsorted` | What happened to a row; see [What happened to a row](#what-happened-to-a-row) |
71
74
  | `lb-row-leaving`, `lb-group-leaving` | A row or group on its way out |
72
75
  | `lb-row-editing` | The row a form is pointed at; see [Editing a row in one form](#editing-a-row-in-one-form) |
73
76
  | `lb-row-selected` | A row whose `lb-select` is ticked |
@@ -147,6 +150,10 @@ no row fills.
147
150
  | Any other custom element | `lb-column-value` only |
148
151
  | Any other element | Its text content, and `lb-column-value` |
149
152
 
153
+ A column lands in one attribute besides: the `href` of a link carrying
154
+ `lb-url-parm` that names it; see
155
+ [A link from a row](./chrome.md#a-link-from-a-row).
156
+
150
157
  A control is an `<input>`, `<select>` or `<textarea>`, or a form-associated
151
158
  custom element with a `value` property that fires `change`. The hub sets a
152
159
  control's `value`, and gathers it back under the same column. A custom
@@ -218,7 +225,9 @@ places it immediately before the template:
218
225
  The hub matches each row to a live row by its key. All rows decide
219
226
  membership: a live row whose key did not arrive leaves. A patch changes only
220
227
  the rows it names, and every other live row keeps its content. The hub
221
- places every live row by the query's order; see [Order](#order). A row that
228
+ places live rows by the query's order, except that an edit from inside the
229
+ list leaves them where they stand; see [Order](#order) and
230
+ [Rows the user is working in](#rows-the-user-is-working-in). A row that
222
231
  does not pass the user's filter is `hidden`; see [Filters](#filters).
223
232
 
224
233
  A live row whose values arrive as it already shows them is not filled
@@ -290,10 +299,71 @@ numerically whatever JSON type its values arrive as, and a value that is no
290
299
  value of its declared type compares as `null` does. `readAs` in
291
300
  `@loadbare/app/types` is the rule the hub applies.
292
301
 
302
+ The element carrying `lb-query` carries `lb-query-order`, the order
303
+ placing its rows as an order's text, the user's when one is chosen and
304
+ the server's otherwise. Rows with no order carry none.
305
+
293
306
  Rows that compare equal keep the order they arrived in, a patch's new rows
294
307
  after the rows already showing. A row moves only when the order
295
308
  puts it somewhere else, so all rows landing again unchanged move nothing.
296
309
 
310
+ ### Rows the user is working in
311
+
312
+ A landing that answers a request sent from inside a list moves none of that
313
+ list's rows. The request is from inside the list when the list is the
314
+ nearest element carrying `lb-query` around the element that sent it: a
315
+ control in a live row, or a ghost row. A form that
316
+ [`--lb-edit`](#editing-a-row-in-one-form) pointed at a live row finishes an
317
+ edit begun in that row, so its request is from inside the row's list,
318
+ wherever the form sits. Any other form elsewhere on the page is not inside
319
+ the list. A user who changes a value sees the value change, and the row
320
+ stays where they were working:
321
+
322
+ | The answer | The row |
323
+ |------------------------------------------|---------------------------------------------------|
324
+ | Changes a column the order reads | Stays where it stands |
325
+ | Creates a row | Goes after the last row of its group, or last of all |
326
+ | Drops a row | Leaves |
327
+ | Changes a row so the filter hides it | Stays showing |
328
+ | Changes the column its group breaks on | Stays under its group's heading |
329
+
330
+ A patch and all rows answering the same edit land the same way. A row left
331
+ where the order or the filter would not leave it is stamped
332
+ `lb-row-unsorted`, and the element carrying `lb-query` carries
333
+ `lb-query-unsorted-count`, the number of such rows. Another element naming
334
+ the same query places its rows by the order: the user was not working
335
+ there.
336
+
337
+ The rows are placed by the order again, and every `lb-row-unsorted` is
338
+ cleared, when the user asks: when the page loads, when the user's order or
339
+ filter changes, or on `--lb-sort`. A button names the list with HTML's
340
+ `commandfor`, and a stylesheet shows it only while it has something to do:
341
+
342
+ ```html
343
+ <div id="roster" lb-query="roster">
344
+ <button type="button" class="resort" commandfor="roster" command="--lb-sort">Re-sort</button>
345
+ <ul>
346
+ <template><li><input lb-column="name" lb-request="lb-row-update" /></li></template>
347
+ </ul>
348
+ </div>
349
+ ```
350
+
351
+ ```css
352
+ .resort {
353
+ display: none;
354
+ }
355
+ [lb-query-unsorted-count]:not([lb-query-unsorted-count="0"]) .resort {
356
+ display: revert;
357
+ }
358
+ ```
359
+
360
+ `--lb-sort` places the rows the hub holds, with no round trip. Aimed at an
361
+ element that carries no `lb-query`, or shows no rows the hub holds, it is
362
+ reported to the console. A custom element that reorders rows by a request of
363
+ its own dispatches the same command on the list once the answer lands, with
364
+ `new CommandEvent("command", { command: "--lb-sort" })`: the user asked for
365
+ the move.
366
+
297
367
  ### Filters
298
368
 
299
369
  Write `lb-filter` on the element carrying `lb-query`, naming the columns of
@@ -311,7 +381,9 @@ its rows the user may filter, separated by commas. The column of
311
381
  ```
312
382
 
313
383
  A row that does not pass every filter is `hidden`, and a ticked `lb-select`
314
- in it is cleared. A group whose rows are all hidden is hidden too
384
+ in it is cleared. A row an edit from inside the list changed stays showing
385
+ until the rows are placed again; see
386
+ [Rows the user is working in](#rows-the-user-is-working-in). A group whose rows are all hidden is hidden too
315
387
  ([miswart](../miswarts.md#rows-hidden-by-a-filter)). An empty
316
388
  choice filters nothing.
317
389
 
@@ -393,7 +465,9 @@ Content after the nested template lands after the group's rows, which is
393
465
  where a group's footer goes.
394
466
 
395
467
  A heading is filled from its group's first row, so it may show a column
396
- other than the one the group breaks on. The hub stamps each top-level
468
+ other than the one the group breaks on. A row an edit from inside the list
469
+ moved to another group stays under its old heading, and the heading shows
470
+ the row as it stood, until the rows are placed again. The hub stamps each top-level
397
471
  element of a group with `lb-group-live`, `lb-group-column` and
398
472
  `lb-group-value`. A row creates its group, and a group leaves with its last
399
473
  row. When the order changes, a group whose run goes on is kept and moved,
@@ -442,6 +516,7 @@ Every change to a list is one a stylesheet can see:
442
516
  | `lb-row-changed` | Its values changed when the hub last touched it |
443
517
  | `lb-row-moved` | Its values changed, and it now stands elsewhere |
444
518
  | `lb-row-requested` | `created`, `changed` or `moved` by this page's request |
519
+ | `lb-row-unsorted` | Stands where the order would not put it |
445
520
  | `lb-row-leaving` | A row on its way out |
446
521
  | `lb-group-leaving` | A group on its way out |
447
522
 
@@ -450,6 +525,9 @@ The hub touches a row when an answer names it. `lb-row-created`,
450
525
  applied again is removed and added as two changes, so an animation keyed to
451
526
  it plays again. `lb-row-requested` is cleared at every landing of the row's
452
527
  query, and set only by a landing that answers a request from the page.
528
+ `lb-row-unsorted` is a state, not an event: it stays until the rows are
529
+ placed by the order; see
530
+ [Rows the user is working in](#rows-the-user-is-working-in).
453
531
 
454
532
  A row or a group that leaves loses `lb-row-live` or `lb-group-live`, takes
455
533
  `lb-row-leaving` or `lb-group-leaving` and `inert`
@@ -498,7 +576,9 @@ insert form above: it sits inside `lb-query="roster"` so its request is for
498
576
 
499
577
  The hub stamps every element that holds a row template with
500
578
  `lb-query-row-count`, the number of live rows it shows. A row a filter
501
- hides is not counted. An empty query is a stylesheet rule:
579
+ hides is not counted. Beside it, `lb-query-unsorted-count` is the number of
580
+ rows out of order; see
581
+ [Rows the user is working in](#rows-the-user-is-working-in). An empty query is a stylesheet rule:
502
582
 
503
583
  ```html
504
584
  <div lb-query="roster">
@@ -956,6 +1036,12 @@ An `lb-show` in the form reads from the row it was pointed at. With no row,
956
1036
  every column is null, so `lb-show="!id"` shows only while entering a new
957
1037
  row.
958
1038
 
1039
+ A request from a form Edit pointed is sent from inside the row's list, so
1040
+ the row stays where the user was working, even when the filter would now
1041
+ hide it or the order would move it; see
1042
+ [Rows the user is working in](#rows-the-user-is-working-in). A form New
1043
+ pointed is not inside any list, and its rows are placed by the order.
1044
+
959
1045
  A successful request from the form lets it go, as Cancel does: the form
960
1046
  empties, its controls return to their defaults, and the stamps go. A failed
961
1047
  one leaves the form as the user had it, to correct.
@@ -980,8 +1066,8 @@ submits the form instead.
980
1066
 
981
1067
  A command the hub cannot carry out is reported to the console: Edit from
982
1068
  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`.
1069
+ for, and any of these three commands aimed at an element that is not a form
1070
+ carrying `lb-query`.
985
1071
 
986
1072
  ### Selecting rows
987
1073
 
@@ -1228,7 +1314,7 @@ The developer writes `lb-` attributes in markup, and script never assigns
1228
1314
  them. The builder checks the markup of the chrome and of every page, after
1229
1315
  expansion, and refuses to build:
1230
1316
 
1231
- - an `lb-` attribute that is not one of the sixteen the developer writes;
1317
+ - an `lb-` attribute that is not one of the seventeen the developer writes;
1232
1318
  `lb-exp-slot` and `lb-exp-template` are consumed by expansion and never
1233
1319
  reach the check
1234
1320
  - `lb-request` naming an `lb-` request other than the three Loadbare provides
@@ -1241,6 +1327,9 @@ expansion, and refuses to build:
1241
1327
  a form's own when the form carries `lb-query`, and otherwise the nearest
1242
1328
  `lb-query` around the element
1243
1329
  - `lb-url-link` on anything but an `<a>`
1330
+ - `lb-url-parm` on anything but an `<a>` carrying `lb-url-link`, naming no
1331
+ column, naming columns not separated by commas or one beginning `lb-`, or
1332
+ with no row around it
1244
1333
  - `lb-url-push` on an element without `lb-request`
1245
1334
  - `lb-url-unknown` on anything but a `<dialog>`, or outside `<lb-hub>`
1246
1335
  - `lb-show` on a `<template>`, or on a row template's root
@@ -1250,8 +1339,8 @@ expansion, and refuses to build:
1250
1339
  no column
1251
1340
  - `lb-select` on anything but `<input type="checkbox">`, outside a row
1252
1341
  template, or beside `lb-column` or `lb-request`
1253
- - a `command` beginning `--lb-` that is not one of the three commands
1254
- - one of the three commands with no `commandfor`
1342
+ - a `command` beginning `--lb-` that is not one of the four commands
1343
+ - one of the four commands with no `commandfor`
1255
1344
  - `lb-group` on anything but a `<template>`, or on one holding no
1256
1345
  `<template>` for its contents
1257
1346
  - an aggregate on a `<template>`, or any but `lb-count` naming no column
@@ -66,7 +66,7 @@ A widget package declares these. An application never does.
66
66
 
67
67
  ## Attributes
68
68
 
69
- The developer writes sixteen attributes in pages and the chrome, and the
69
+ The developer writes seventeen attributes in pages and the chrome, and the
70
70
  builder refuses any other `lb-` attribute there. `lb-exp-slot` and
71
71
  `lb-exp-template` are written for expansion, which removes them before the
72
72
  check. The hub and the builder write the rest, as stamps, and script never
@@ -84,6 +84,7 @@ assigns one.
84
84
  | `lb-select` | Developer | [Selecting rows](./data-binding.md#selecting-rows) |
85
85
  | `lb-request` | Developer | [Requests](./data-binding.md#requests) |
86
86
  | `lb-url-link` | Developer | [Links](./chrome.md#links) |
87
+ | `lb-url-parm` | Developer | [A link from a row](./chrome.md#a-link-from-a-row) |
87
88
  | `lb-url-push` | Developer | [Query parms](./chrome.md#query-parms) |
88
89
  | `lb-url-unknown` | Developer | [An unknown page](./chrome.md#an-unknown-page) |
89
90
  | `lb-exp-slot` | Developer, consumed by expansion | [The slot](./custom-elements.md#the-slot) |
@@ -92,6 +93,8 @@ assigns one.
92
93
  | `lb-key-value` | Hub | [Landing](./data-binding.md#landing), [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form) |
93
94
  | `lb-row-live` | Hub | [Landing](./data-binding.md#landing) |
94
95
  | `lb-query-row-count` | Hub | [Counting rows](./data-binding.md#counting-rows) |
96
+ | `lb-query-unsorted-count`, `lb-row-unsorted` | Hub | [Rows the user is working in](./data-binding.md#rows-the-user-is-working-in) |
97
+ | `lb-query-order` | Hub | [Order](./data-binding.md#order) |
95
98
  | `lb-group-live`, `lb-group-column`, `lb-group-value` | Hub | [Groups](./data-binding.md#groups) |
96
99
  | `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested` | Hub | [What happened to a row](./data-binding.md#what-happened-to-a-row) |
97
100
  | `lb-row-leaving`, `lb-group-leaving` | Hub | [What happened to a row](./data-binding.md#what-happened-to-a-row) |
@@ -135,6 +138,7 @@ Loadbare/app owns every value of HTML's `command` attribute beginning with
135
138
  | `--lb-edit` | [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form) |
136
139
  | `--lb-new` | [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form) |
137
140
  | `--lb-release` | [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form) |
141
+ | `--lb-sort` | [Rows the user is working in](./data-binding.md#rows-the-user-is-working-in) |
138
142
 
139
143
  ## Reserved tags
140
144
 
@@ -191,6 +195,8 @@ imports them rather than writing a string.
191
195
  | `ATTR_ROW_LIVE` | `lb-row-live` |
192
196
  | `LIVE_ROW` | `[lb-row-live]`, the selector for a live row |
193
197
  | `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
198
+ | `ATTR_QUERY_UNSORTED_COUNT` | `lb-query-unsorted-count` |
199
+ | `ATTR_QUERY_ORDER` | `lb-query-order` |
194
200
  | `ATTR_GROUP` | `lb-group` |
195
201
  | `ATTR_GROUP_LIVE` | `lb-group-live` |
196
202
  | `ATTR_GROUP_COLUMN` | `lb-group-column` |
@@ -209,6 +215,7 @@ imports them rather than writing a string.
209
215
  | `ATTR_ROW_MOVED` | `lb-row-moved` |
210
216
  | `ATTR_ROW_REQUESTED` | `lb-row-requested` |
211
217
  | `REQUESTED_CREATED`, `REQUESTED_CHANGED`, `REQUESTED_MOVED` | `created`, `changed`, `moved` |
218
+ | `ATTR_ROW_UNSORTED` | `lb-row-unsorted` |
212
219
  | `ATTR_ROW_LEAVING` | `lb-row-leaving` |
213
220
  | `ATTR_GROUP_LEAVING` | `lb-group-leaving` |
214
221
  | `ATTR_REQUEST` | `lb-request` |
@@ -218,8 +225,8 @@ imports them rather than writing a string.
218
225
  | `ATTR_REQUEST_ERROR` | `lb-request-error` |
219
226
  | `ATTR_REQUEST_REFUSED` | `lb-request-refused` |
220
227
  | `LB_REFUSAL_STATUS` | `422` |
221
- | `COMMAND_EDIT`, `COMMAND_NEW`, `COMMAND_RELEASE` | `--lb-edit`, `--lb-new`, `--lb-release` |
222
- | `LB_COMMANDS` | All three commands |
228
+ | `COMMAND_EDIT`, `COMMAND_NEW`, `COMMAND_RELEASE`, `COMMAND_SORT` | `--lb-edit`, `--lb-new`, `--lb-release`, `--lb-sort` |
229
+ | `LB_COMMANDS` | All four commands |
223
230
  | `LB_COMMAND_PREFIX` | `--lb-` |
224
231
  | `ATTR_ROW_EDITING` | `lb-row-editing` |
225
232
  | `ATTR_SELECT` | `lb-select` |
@@ -238,13 +245,14 @@ imports them rather than writing a string.
238
245
  | `URL_COLUMN_PAGE_LABEL` | `lb-page-label` |
239
246
  | `URL_COLUMN_PAGE_UNKNOWN` | `lb-page-unknown` |
240
247
  | `ATTR_URL_LINK` | `lb-url-link` |
248
+ | `ATTR_URL_PARM` | `lb-url-parm` |
241
249
  | `ATTR_URL_PUSH` | `lb-url-push` |
242
250
  | `ATTR_URL_UNKNOWN` | `lb-url-unknown` |
243
251
  | `ATTR_EXP_SLOT` | `lb-exp-slot` |
244
252
  | `ATTR_EXP_TEMPLATE` | `lb-exp-template` |
245
253
  | `ATTR_PAGE` | `lb-page` |
246
254
  | `ATTR_PAGE_TITLE` | `lb-page-title` |
247
- | `DEVELOPER_ATTRIBUTES` | The sixteen attributes a developer writes |
255
+ | `DEVELOPER_ATTRIBUTES` | The seventeen attributes a developer writes |
248
256
  | `HUB_TAG_NAME` | `lb-hub` |
249
257
  | `LB_ENDPOINT` | `/lb` |
250
258
  | `LB_REQUEST_TIMEOUT_MS` | `10000` |
@@ -382,7 +382,8 @@ one row dropped, one row edited — and leave `refresh` empty. A refreshed
382
382
  A change that seems to need a refresh usually has a patch:
383
383
 
384
384
  - A row whose position changes: the hub places it by the query's
385
- [order](./data-binding.md#order).
385
+ [order](./data-binding.md#order), or leaves it where the user is working
386
+ when the request came from inside its list; a refresh would do the same.
386
387
  - A group that appears or goes: the hub makes a group with its first row
387
388
  and removes it with its last, so no row stands in for an empty one.
388
389
  - A row whose other columns change with the edit: re-read the row and patch
@@ -155,7 +155,9 @@ row, the row template, and optionally a footer, each as a `<template>`:
155
155
  | `<template lb-exp-template="foot">` | The `<tfoot>` |
156
156
  | Any other `<template>` | The row or group template, in the `<tbody>` |
157
157
 
158
- - The hub places the rows by the query's order and builds its groups. The
158
+ - The hub places the rows by the query's order and builds its groups, and
159
+ leaves them where they stand for an edit from inside the table; see
160
+ [Rows the user is working in](./data-binding.md#rows-the-user-is-working-in). The
159
161
  rows land in the one `<tbody>`, so a group here is a heading row with its
160
162
  rows after it.
161
163
  - `lb-order` and `lb-filter` go on the `lb-table` itself, beside
@@ -174,6 +176,13 @@ row, the row template, and optionally a footer, each as a `<template>`:
174
176
  - Text selected in a cell whose `lb-column` the table's `lb-filter` names
175
177
  becomes that column's filter in `lb-choices`: `=` for the whole text,
176
178
  `^=` from its start, `$=` up to its end, `*=` otherwise.
179
+ - The heading of the order's leading column carries a triangle for the
180
+ direction, read from [`lb-query-order`](./data-binding.md#order). A
181
+ heading's column is the `lb-column` in the cell at its position in the row
182
+ template. While rows stand out of order the triangle carries
183
+ `data-unsorted` and a click on its heading re-sorts. With `lb-order`, a
184
+ click on the sorted heading reverses the order and a click on another
185
+ sorts by its column. Not on a grouped table.
177
186
 
178
187
  ## `lb-confirm`
179
188
 
package/docs/testing.md CHANGED
@@ -208,7 +208,9 @@ evidence here.
208
208
  - `lb-query-row-count` counts the rows showing, so all rows and a patch
209
209
  ending in the same state report the same number, and a row a filter hides
210
210
  is not counted
211
- - the order places all rows and a patch alike, groups come and go with
211
+ - the order places all rows and a patch alike, except where an edit came
212
+ from inside the list, which leaves its rows standing and stamped until
213
+ the user asks for the order; groups come and go with
212
214
  their rows to any depth, aggregates follow every landing, and every change
213
215
  leaves a stamp a stylesheet can see (`lb-order.test.ts`)
214
216
  - a filter's text reads and passes values as documented, and a choice of
@@ -279,6 +281,8 @@ What the hub is tested for here:
279
281
  `lb-page-label`, and a column per query parm, and sets the document title
280
282
  - `lb-url-link` pushes state and swaps the page on a plain primary click,
281
283
  `popstate` reverses it, and an ordinary anchor is left alone
284
+ - `lb-url-parm` writes a link's `href` from its row as held, starting from
285
+ the `href` as written every time (`lb-apply.test.ts`)
282
286
  - a control inside `lb-query="lb-url"` writes its parm, replacing the history
283
287
  entry or pushing one with `lb-url-push`, and the page's queries reload
284
288
  - an `lb-url` item in a server answer writes the URL and lands the page
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loadbare/app",
3
3
  "description": "High performance web app framework for server-bound applications",
4
- "version": "0.19.1",
4
+ "version": "0.21.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: loadbare-app
3
- description: Build server-bound web applications with Loadbare/app. Covers the file conventions the builder finds by name (`chrome.html`, `*.page.html`, `*.queries.ts`, `*.requests.ts`, `imports.ts`, `<tag>.html`, `<tag>.browser.ts`); the `lb-*` attribute vocabulary that lands query rows on HTML and issues requests (`lb-query`, `lb-column`, `lb-show`, `lb-request`); queries declared with `row(key, run)` and `rows(key, run)`; the URL as the hub's own query `lb-url`, with `lb-url-link`, `lb-url-push` and `lb-url-unknown`; the user's order and filters as the hub's query `lb-choices`, read by `lb-order` and `lb-filter`; one form pointed at a row by the `--lb-edit`, `--lb-new` and `--lb-release` commands; build-time expansion with `exp-*` parameters, `lb-exp-slot` and `lb-exp-template`; custom elements as form-associated controls and row hosts; the Express wiring with `hubRoutes`; and the `@loadbare/widgets` library. Use whenever a task involves `@loadbare/app`, `@loadbare/widgets`, `loadbare-app-build`, an `lb-` attribute, or a page, query, request or custom element file in a Loadbare application. The model is not React, htmx or REST, and cannot be inferred from them.
3
+ description: Build server-bound web applications with Loadbare/app. Covers the file conventions the builder finds by name (`chrome.html`, `*.page.html`, `*.queries.ts`, `*.requests.ts`, `imports.ts`, `<tag>.html`, `<tag>.browser.ts`); the `lb-*` attribute vocabulary that lands query rows on HTML and issues requests (`lb-query`, `lb-column`, `lb-show`, `lb-request`); queries declared with `row(key, run)` and `rows(key, run)`; the URL as the hub's own query `lb-url`, with `lb-url-link`, `lb-url-parm`, `lb-url-push` and `lb-url-unknown`; the user's order and filters as the hub's query `lb-choices`, read by `lb-order` and `lb-filter`; one form pointed at a row by the `--lb-edit`, `--lb-new` and `--lb-release` commands; re-sorting a list with `--lb-sort`; build-time expansion with `exp-*` parameters, `lb-exp-slot` and `lb-exp-template`; custom elements as form-associated controls and row hosts; the Express wiring with `hubRoutes`; and the `@loadbare/widgets` library. Use whenever a task involves `@loadbare/app`, `@loadbare/widgets`, `loadbare-app-build`, an `lb-` attribute, or a page, query, request or custom element file in a Loadbare application. The model is not React, htmx or REST, and cannot be inferred from them.
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  package: "@loadbare/app"
@@ -191,6 +191,12 @@ row is filled from the nested query's last answer, whatever order they land
191
191
  in.
192
192
 
193
193
  **A `rows` query declares its order, and the hub places every row by it.**
194
+ An edit sent from inside a list, or from a form `--lb-edit` pointed at one
195
+ of its rows, leaves that list's rows where they stand, stamped `lb-row-unsorted` where out of order, until the page loads, the
196
+ user's order or filter changes, or a button with `commandfor` naming the
197
+ list sends `command="--lb-sort"`, which places them with no round trip;
198
+ show it by `[lb-query-unsorted-count]`. Never refresh to make rows hold
199
+ still, and never to put them back in order.
194
200
  `rows("id", run, { order: "category_order,name" })`; a column after `-` sorts
195
201
  descending, and values compare by JSON type, so a numeric sort column is a
196
202
  JSON number or is declared one: `types: { amount: "number" }` beside
@@ -265,7 +271,8 @@ in every row. Write one form naming the set's query, outside every row,
265
271
  often in a `<dialog>`, and give it an `id`. A `type="button"` with
266
272
  `commandfor="<id>"` and `command="--lb-edit"` in a live row fills it from
267
273
  that row with no round trip; `--lb-new` starts it empty for an insert;
268
- `--lb-release` lets it go. The form's `lb-key-value` is the row's key, empty
274
+ `--lb-release` lets it go. (`--lb-sort` is the fourth command, for a list,
275
+ not a form.) The form's `lb-key-value` is the row's key, empty
269
276
  for a new row, absent when not in use, and a stylesheet shows Save or Save
270
277
  new by it. A successful request from the form, or its dialog closing, lets
271
278
  it go. Which row is being edited is never a query parm. A row that must be
@@ -321,7 +328,8 @@ picker in a row the request adds: its answer did not change, and the hub
321
328
  fills a new row from the last one.
322
329
 
323
330
  **The usual reasons for a refresh each have a patch.**
324
- - A row whose position changes: the hub places it by the query's order.
331
+ - A row whose position changes: the hub places it by the query's order, or
332
+ holds it where the user is working; a refresh changes neither.
325
333
  - A group that appears or goes: the hub makes it with its first row and
326
334
  removes it with its last.
327
335
  - A row whose other columns change with the edit: re-read the row and patch
@@ -359,7 +367,9 @@ per query parm. A control inside `lb-query="lb-url"` carrying
359
367
  `lb-request="lb-row-update"` writes its parm; the hub replaces the history
360
368
  entry, or pushes one when the control carries `lb-url-push`, and reloads the
361
369
  page's queries with no request to the server. An `<a lb-url-link>` moves
362
- to its `href`. Queries still take no arguments: `contextFor(req, parms)`
370
+ to its `href`; inside a row, `lb-url-parm="topic_id"` adds that column of
371
+ the row to the `href` as the parm of the same name, so a link to another
372
+ page narrowed by the row is a link, never a request or script. Queries still take no arguments: `contextFor(req, parms)`
363
373
  puts parms onto `ctx`, and a query reads them there. Read them from
364
374
  `parms`, never `req.query`, because `contextFor` may be called twice for one
365
375
  request. A parm is user input, so validate it where it is read. Do not
@@ -393,10 +403,10 @@ whole data channel.
393
403
 
394
404
  **The developer writes `lb-*` attributes in markup, and script never
395
405
  assigns them.** The builder refuses any `lb-` attribute other than the
396
- sixteen in [Attributes](references/names.md#attributes): `lb-query`,
406
+ seventeen in [Attributes](references/names.md#attributes): `lb-query`,
397
407
  `lb-column`, `lb-show`, `lb-order`, `lb-filter`, `lb-group`, `lb-count`,
398
408
  `lb-sum`, `lb-avg`, `lb-min`, `lb-max`, `lb-select`, `lb-request`,
399
- `lb-url-link`, `lb-url-push` and `lb-url-unknown`. `lb-exp-slot` and
409
+ `lb-url-link`, `lb-url-parm`, `lb-url-push` and `lb-url-unknown`. `lb-exp-slot` and
400
410
  `lb-exp-template` are expansion's, and never reach the page. Everything
401
411
  else with the prefix is a stamp the hub or the builder writes.
402
412
 
@@ -60,6 +60,7 @@ Everything else is optional:
60
60
  |-------------------------------------------|---------------------------------------------|
61
61
  | `<link rel="stylesheet" href="/app.css">` | The bundled stylesheet |
62
62
  | `<a lb-url-link>` | A link between pages |
63
+ | `<a lb-url-link lb-url-parm>` | A link carrying columns of its row |
63
64
  | `lb-query="lb-url"` | Where the page is — see below |
64
65
  | `<dialog lb-url-unknown>` | A message when a URL matches no page |
65
66
  | Custom elements | The chrome, decomposed into element files |
@@ -157,6 +158,45 @@ Path space is flat: `/members` shows `members.page.html` wherever under
157
158
  `/transactions?date_begin=2026-09-01` opens the page narrowed to those
158
159
  dates.
159
160
 
161
+ ### A link from a row
162
+
163
+ Write `lb-url-parm` on an `<a lb-url-link>` inside a row to carry columns
164
+ of that row to the page the link names. It names columns, separated by
165
+ commas, and the hub adds each to the link's query string as the parm of the
166
+ same name:
167
+
168
+ ```html
169
+ <table lb-query="topics">
170
+ <template>
171
+ <tr>
172
+ <td lb-column="title"></td>
173
+ <td><a href="/events" lb-url-link lb-url-parm="topic_id" lb-column="times_presented"></a></td>
174
+ </tr>
175
+ </template>
176
+ </table>
177
+ ```
178
+
179
+ In topic 5's row the link is `<a href="/events?topic_id=5">`. The `href` is
180
+ written when the row lands and rewritten when it changes, so hovering,
181
+ copying, a middle click and a plain click all go to the same place.
182
+
183
+ - The parm is the column. Where the names differ, the row's query renames
184
+ the column in its SQL: `select id as topic_id`.
185
+ - The hub reads the row it holds, so a column no element shows is carried
186
+ as well as one that is.
187
+ - The `href` as written is the base. Its path, its parms and its fragment
188
+ are kept, and a named column replaces a parm of the same name.
189
+ - A null, empty or missing column leaves its parm off, as an empty value
190
+ takes a parm out of the URL. A string is written as it is, a number or a
191
+ boolean as JSON writes it, and any other value is left off and reported
192
+ on the console, as is a named column the row does not have.
193
+ - The row is the nearest around the link: a live row, or an element a `row`
194
+ landed on.
195
+
196
+ `lb-url-parm` goes only on a link carrying `lb-url-link`, since the parms it
197
+ writes are the application's own; see
198
+ [The markup checks](./data-binding.md#the-markup-checks).
199
+
160
200
  ### Query parms
161
201
 
162
202
  A query parm is a column of `lb-url`. A control inside `lb-query="lb-url"`
@@ -268,7 +268,7 @@ Each direction has one mechanism for each kind of message:
268
268
 
269
269
  | Direction | What | How | Today |
270
270
  |----------------|--------------------------------------------|-----------------------------|-------|
271
- | 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`, `lb-request-refused` |
271
+ | Hub to element | State that lasts | An attribute the hub stamps | `lb-column-value`, `lb-key-value`, `lb-query-row-count`, `lb-query-unsorted-count`, `lb-query-order`, `lb-group-*`, `lb-row-*`, `lb-request-pending`, `lb-request-error`, `lb-request-refused` |
272
272
  | Hub to element | State the platform already names | A property | A control's `value` |
273
273
  | Hub to element | What the hub did | An optional `lb` method | `lbAttached`, `lbRowsLanded`, `lbRequestDone`, `lbDetached` |
274
274
  | Hub to element | A moment an element started | A bubbling event | `lb-request-done` |
@@ -627,7 +627,8 @@ had there hears the event, and no custom element the hub has removed hears
627
627
  ### Holding rows
628
628
 
629
629
  The hub lands every row template itself, places every row by the query's
630
- order, and builds every group the markup's group templates describe; see
630
+ order (see [Rows the user is working in](./data-binding.md#rows-the-user-is-working-in)
631
+ for when it does not), and builds every group the markup's group templates describe; see
631
632
  [Order](./data-binding.md#order) and [Groups](./data-binding.md#groups). A
632
633
  plain element carrying `lb-query` with a row template inside it needs no
633
634
  code, so a custom element holds rows only to do something with them once
@@ -655,12 +656,12 @@ Everything else is the hub's:
655
656
  |-------------|----------------------------------------------------------|
656
657
  | Cloning | Clones the row template once per new key |
657
658
  | Matching | Fills the live row already showing that key, when its values changed |
658
- | Placing | Places every live row by the user's order or the query's |
659
+ | Placing | Places every live row by the user's order or the query's, and leaves them where they stand for an edit from inside the list |
659
660
  | Filtering | Hides a row the user's filter does not pass, and a group whose rows are all hidden |
660
661
  | Grouping | Builds a group where the order breaks, keeps it while its run goes on, and removes it with its last row |
661
662
  | Removing | Marks the live rows the response says are gone as leaving, and removes them |
662
663
  | Stamping | Stamps each live row with `lb-row-live`, `lb-key-value`, and what happened to it |
663
- | Counting | Stamps `lb-query-row-count` with the number of live rows showing |
664
+ | Counting | Stamps `lb-query-row-count` with the number of live rows showing, `lb-query-unsorted-count` with the number out of order, and `lb-query-order` with the order placing them |
664
665
 
665
666
  `lb-options.browser.ts` and `lb-table.browser.ts` in
666
667
  [`@loadbare/widgets`](./widgets.md) are two uses of `lbRowsLanded`: one