@loadbare/app 0.21.0 → 0.22.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 (57) hide show
  1. package/README.md +18 -0
  2. package/dist/build/assemble.d.ts.map +1 -1
  3. package/dist/build/assemble.js +8 -3
  4. package/dist/build/assemble.js.map +1 -1
  5. package/dist/build/build.d.ts +62 -0
  6. package/dist/build/build.d.ts.map +1 -0
  7. package/dist/build/build.js +148 -0
  8. package/dist/build/build.js.map +1 -0
  9. package/dist/build/cli.d.ts +6 -1
  10. package/dist/build/cli.d.ts.map +1 -1
  11. package/dist/build/cli.js +25 -78
  12. package/dist/build/cli.js.map +1 -1
  13. package/dist/build/declared.d.ts.map +1 -1
  14. package/dist/build/declared.js +3 -0
  15. package/dist/build/declared.js.map +1 -1
  16. package/dist/build/skills-cli.d.ts +2 -1
  17. package/dist/build/skills-cli.d.ts.map +1 -1
  18. package/dist/build/skills-cli.js +8 -1
  19. package/dist/build/skills-cli.js.map +1 -1
  20. package/dist/build/skills.d.ts +2 -0
  21. package/dist/build/skills.d.ts.map +1 -1
  22. package/dist/build/skills.js +2 -0
  23. package/dist/build/skills.js.map +1 -1
  24. package/dist/core/lb-constants.d.ts +14 -0
  25. package/dist/core/lb-constants.d.ts.map +1 -1
  26. package/dist/core/lb-constants.js +25 -0
  27. package/dist/core/lb-constants.js.map +1 -1
  28. package/dist/dev/lb-dev.d.ts +128 -0
  29. package/dist/dev/lb-dev.d.ts.map +1 -0
  30. package/dist/dev/lb-dev.js +289 -0
  31. package/dist/dev/lb-dev.js.map +1 -0
  32. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  33. package/dist/hub/lb-hub.browser.js +119 -15
  34. package/dist/hub/lb-hub.browser.js.map +1 -1
  35. package/docs/comparison.md +4 -3
  36. package/docs/reference/builder.md +12 -4
  37. package/docs/reference/chrome.md +52 -1
  38. package/docs/reference/data-binding.md +15 -9
  39. package/docs/reference/dev-server.md +171 -0
  40. package/docs/reference/names.md +14 -1
  41. package/docs/reference/overview.md +1 -0
  42. package/docs/reference/server.md +8 -3
  43. package/docs/reference/widgets.md +10 -277
  44. package/docs/testing.md +2 -1
  45. package/docs/tutorials/000-getting-started.md +1 -1
  46. package/docs/tutorials/040-displaying-data.md +1 -1
  47. package/docs/tutorials/090-using-widget-libraries.md +1 -1
  48. package/package.json +4 -2
  49. package/skills/loadbare-app/SKILL.md +28 -10
  50. package/skills/loadbare-app/references/builder.md +12 -4
  51. package/skills/loadbare-app/references/chrome.md +52 -1
  52. package/skills/loadbare-app/references/data-binding.md +15 -9
  53. package/skills/loadbare-app/references/dev-server.md +171 -0
  54. package/skills/loadbare-app/references/names.md +14 -1
  55. package/skills/loadbare-app/references/overview.md +1 -0
  56. package/skills/loadbare-app/references/server.md +8 -3
  57. package/skills/loadbare-app/references/widgets.md +10 -277
@@ -0,0 +1,171 @@
1
+ # The Dev Server
2
+
3
+ `@loadbare/app/dev` runs an application while it is being written. After
4
+ any change, to markup, CSS, a query, a handler, a server module or the
5
+ server's entry file, the developer refreshes the browser. Nothing is ever
6
+ restarted by hand, a refresh never meets a refused connection, and it never
7
+ loads a half-written `client.js`.
8
+
9
+ ```ts
10
+ // dev.ts
11
+ import { devServer } from "@loadbare/app/dev";
12
+
13
+ await devServer({ server: "server.ts" });
14
+ ```
15
+
16
+ ```json
17
+ {
18
+ "scripts": {
19
+ "dev": "node dev.ts"
20
+ }
21
+ }
22
+ ```
23
+
24
+ ## Why a refresh is enough
25
+
26
+ - **The static output is read per request.** The server reads
27
+ `dist/app.html`, and sends `client.js` and `app.css` from disk, so markup,
28
+ CSS and browser code need only a rebuild.
29
+ - **Only server modules need a restart.** `dist/pages.ts` and everything it
30
+ imports are loaded once, and Node cannot unload a module. A new process is
31
+ the honest answer, and a Loadbare server starts in well under a second.
32
+ - **The URL holds the page's state.** A refresh returns the user to the
33
+ same page, row and query parms, so swapping code into a live page buys
34
+ almost nothing.
35
+
36
+ ## What it does
37
+
38
+ ```
39
+ browser --> devServer :port --> the application's server :appPort (a child)
40
+ |
41
+ +-- the build, watching src (in process)
42
+ ```
43
+
44
+ 1. Builds `dist/`, then rebuilds whenever an `.html`, `.ts` or `.css` file
45
+ under `src` changes. A burst of changes, such as an editor's save or a
46
+ branch switch, is one build.
47
+ 2. Runs the application's server as a child process, exactly as it runs in
48
+ production, on `appPort`.
49
+ 3. Restarts that server after a build when a server-side file changed: any
50
+ `.ts` under `src` other than a `.browser.ts`, or a change to the page
51
+ registry `dist/pages.ts`, which a page gained or lost changes. It also
52
+ restarts it when the server's entry file changes. CSS, HTML and browser
53
+ code never restart it.
54
+ 4. Holds every request while a build runs or the server restarts, and sends
55
+ it on when both are done.
56
+ 5. Keeps serving the last good build when a build fails, and says why in
57
+ the console.
58
+ 6. Answers 502, "The app server is down; see the dev console.", when the
59
+ server has exited, for example on a throw at import, and starts it again
60
+ on the next save.
61
+ 7. Says plainly when its port is in use, and exits.
62
+ 8. Passes everything else through, method, path, headers and body,
63
+ `POST /lb/*` included.
64
+ 9. Stops the server however the dev server ends: Ctrl-C, a kill, or a
65
+ crash.
66
+
67
+ The application's server reads its port from the environment, as the
68
+ [server recipe](./server.md) does:
69
+
70
+ ```ts
71
+ app.listen(Number(process.env.PORT ?? 8787));
72
+ ```
73
+
74
+ ## Options
75
+
76
+ Every option but `server` has a default.
77
+
78
+ | Option | Default | Meaning |
79
+ |-----------|----------------------------------|--------------------------------------------------------|
80
+ | `server` | (required) | The application's server entry file |
81
+ | `port` | `PORT` from the environment, or `8787` | The port the browser uses |
82
+ | `host` | `127.0.0.1` | The host that port listens on |
83
+ | `appPort` | `port + 10000` | Where the application's server listens |
84
+ | `portEnv` | `PORT` | The variable the application's server reads its port from |
85
+ | `node` | `["--env-file-if-exists=.env"]` | Node's arguments before the entry file |
86
+ | `src` | `src` | The builder's source tree |
87
+ | `out` | `dist` | The builder's output |
88
+ | `log` | the console | Where progress goes, one line at a time |
89
+
90
+ A server written in TypeScript runs under Node's own type stripping, Node
91
+ 22.18 and later. For a runner such as tsx, name it in `node`:
92
+ `node: ["--import", "tsx"]`.
93
+
94
+ `devServer` resolves once the first build is done and the server accepts
95
+ connections, with `{ port, close() }`.
96
+
97
+ ## Assembling your own
98
+
99
+ `devServer` is a short composition of parts that are exported on their own.
100
+ An application that wants something else, such as other files that restart
101
+ the server, a second server, or a reload pushed to the browser, builds its
102
+ own from them.
103
+
104
+ | Part | From | Does |
105
+ |--------------|------------------------|--------------------------------------------------------------|
106
+ | `watchApp` | `@loadbare/app/build` | Builds, rebuilds on a change, and reports each build |
107
+ | `buildApp` | `@loadbare/app/build` | Builds once |
108
+ | `appServer` | `@loadbare/app/dev` | The application's server as a child process, restarted on demand |
109
+ | `gate` | `@loadbare/app/dev` | Holds requests while any reason to hold them stands |
110
+ | `devProxy` | `@loadbare/app/dev` | The browser's port, passing requests through a gate |
111
+
112
+ `watchApp({ src, out, minify, onStart, onBuilt })` builds at once, then
113
+ again on each change. `onStart(changed)` is called as a build begins, with
114
+ the files under `src` that changed, relative to it, and an empty list for
115
+ the first. `onBuilt(outcome)` is called as it ends, with `{ ok: true,
116
+ changed, built }` or `{ ok: false, changed, error }`; `built.files` lists
117
+ what was written. Builds never overlap: changes during one are gathered into
118
+ the next. It returns `{ ready, close() }`, where `ready` resolves with the
119
+ first build's outcome. Paths resolve against the working directory.
120
+
121
+ `buildApp({ src, out, minify })` builds once, and throws when the build
122
+ fails. A build writes nothing until every output is ready, so a failed one
123
+ leaves the last good build in place. `loadbare-app-build` is a thin command
124
+ over these two.
125
+
126
+ `appServer({ entry, port, portEnv, node, log })` returns `{ restart(),
127
+ stop(), state }`. `restart()` stops the server if it runs, starts it, and
128
+ resolves once it accepts connections, or has exited instead. `state` is
129
+ `starting`, `running`, `down` for a server that exited on its own, or
130
+ `stopped`. Once stopped, it never starts again. The child is killed when
131
+ this process exits, however it exits.
132
+
133
+ `gate()` returns `{ close(reason), open(reason), wait(), isOpen }`. Each
134
+ `close` is matched by its own `open`, and `wait()` resolves once every
135
+ close has been. A build and a restart can each hold requests without one
136
+ ending the other's hold.
137
+
138
+ `devProxy({ port, host, target, gate, isDown })` listens on `port` and
139
+ passes every request to `target` once the gate is open, answering 502 while
140
+ `isDown()` is true. It resolves with the listening server, and rejects with
141
+ a plain message when the port is in use.
142
+
143
+ A dev server that also restarts on a change to `config/`:
144
+
145
+ ```ts
146
+ import { watch } from "node:fs";
147
+ import { watchApp } from "@loadbare/app/build";
148
+ import { appServer, devProxy, gate } from "@loadbare/app/dev";
149
+
150
+ const held = gate();
151
+ const app = appServer({ entry: "server.ts", port: 18787 });
152
+ const restart = async () => {
153
+ held.close("server");
154
+ await app.restart();
155
+ held.open("server");
156
+ };
157
+
158
+ const watcher = watchApp({
159
+ onStart: () => held.close("build"),
160
+ onBuilt: async (outcome) => {
161
+ console.log(outcome.ok ? outcome.built.summary : outcome.error.message);
162
+ if (outcome.changed.some((f) => f.endsWith(".ts"))) await restart();
163
+ held.open("build");
164
+ },
165
+ });
166
+ watch("config", { recursive: true }, () => void restart());
167
+
168
+ await watcher.ready;
169
+ await restart();
170
+ await devProxy({ port: 8787, target: 18787, gate: held, isDown: () => app.state === "down" });
171
+ ```
@@ -103,6 +103,7 @@ assigns one.
103
103
  | `lb-request-pending` | Hub | [Request state](./data-binding.md#request-state) |
104
104
  | `lb-request-error` | Hub | [Request state](./data-binding.md#request-state) |
105
105
  | `lb-request-refused` | Hub | [Refusals](./data-binding.md#refusals) |
106
+ | `lb-load-state`, `lb-server-state`, `lb-error`, `lb-pending`, `lb-as-of` on `<lb-hub>` | Hub | [The hub's state](./chrome.md#the-hubs-state) |
106
107
  | `lb-show` on a `<template>` | Hub and builder | [Where an absent element is](./data-binding.md#where-an-absent-element-is) |
107
108
  | `lb-page` | Builder | [What the builder writes](./builder.md#what-the-builder-writes) |
108
109
  | `lb-page-title` | Builder | [The page title](./page-files.md#the-page-title) |
@@ -110,12 +111,13 @@ assigns one.
110
111
  ## Reserved queries
111
112
 
112
113
  The hub serves these in the browser. Every page and the chrome may name
113
- them, and a page declares neither.
114
+ them, and a page declares none.
114
115
 
115
116
  | Query | Kind | Key | Columns | Defined in |
116
117
  | ------------ | ----- | --------- | --------------------------------------------------------------- | ------------------------------------- |
117
118
  | `lb-url` | `row` | `lb-path` | `lb-path`, `lb-page-label`, `lb-page-unknown`, every query parm | [The URL](./chrome.md#the-url) |
118
119
  | `lb-choices` | `row` | `lb-path` | `lb-path`, every choice | [Choices](./data-binding.md#choices) |
120
+ | `lb-hub` | `row` | `lb-path` | `lb-path`, `lb-load-state`, `lb-server-state`, `lb-error`, `lb-pending`, `lb-as-of` | [The hub's state](./chrome.md#the-hubs-state) |
119
121
 
120
122
  No query parm is reserved: a column of `lb-url` whose name begins with
121
123
  `lb-` is never a query parm.
@@ -175,6 +177,8 @@ accessor or field named `lb` and a capital.
175
177
  | -------------------------- | ---------------------------------------------------------------- | ---------------------------------------------- |
176
178
  | `@loadbare/app/server` | `row`, `rows`, `patch`, `url`, `choices`, `refusal`, `createHub`, and the types `Queries`, `Requests`, `HubContext`, `Hub` | [Page Files](./page-files.md), [The data channel](./server.md#the-data-channel) |
177
179
  | `@loadbare/app/express` | `hubRoutes` | [The Express Server](./server.md) |
180
+ | `@loadbare/app/dev` | `devServer`, `appServer`, `gate`, `devProxy`, `DOWN_MESSAGE` | [The Dev Server](./dev-server.md) |
181
+ | `@loadbare/app/build` | `buildApp`, `watchApp` | [Assembling your own](./dev-server.md#assembling-your-own) |
178
182
  | `@loadbare/app/types` | `HubRequest`, `ResponseItem`, `HubRequestDone`, `HubLifecycle`, and the rules the hub applies: `readAs`, `parseFilter`, `passes`, `passesTerm` | [The data channel](./server.md#the-data-channel), [Order](./data-binding.md#order), [Filters](./data-binding.md#filters) |
179
183
  | `@loadbare/app` | `applyRow` | [Filling a subtree by hand](./custom-elements.md#filling-a-subtree-by-hand) |
180
184
  | `@loadbare/app/constants` | Every name on this page; see [Constants](#constants) | [Code](./custom-elements.md#code) |
@@ -244,6 +248,15 @@ imports them rather than writing a string.
244
248
  | `URL_COLUMN_PATH` | `lb-path` |
245
249
  | `URL_COLUMN_PAGE_LABEL` | `lb-page-label` |
246
250
  | `URL_COLUMN_PAGE_UNKNOWN` | `lb-page-unknown` |
251
+ | `HUB_QUERY` | `lb-hub` |
252
+ | `HUB_COLUMN_LOAD_STATE` | `lb-load-state` |
253
+ | `HUB_COLUMN_SERVER_STATE` | `lb-server-state` |
254
+ | `HUB_COLUMN_ERROR` | `lb-error` |
255
+ | `HUB_COLUMN_PENDING` | `lb-pending` |
256
+ | `HUB_COLUMN_AS_OF` | `lb-as-of` |
257
+ | `HUB_LOAD_LOADING`, `HUB_LOAD_LOADED`, `HUB_LOAD_FAILED` | `loading`, `loaded`, `failed` |
258
+ | `HUB_SERVER_REACHABLE`, `HUB_SERVER_FAILING`, `HUB_SERVER_UNREACHABLE` | `reachable`, `failing`, `unreachable` |
259
+ | `HUB_ERROR_NETWORK`, `HUB_ERROR_TIMEOUT` | `network`, `timeout` |
247
260
  | `ATTR_URL_LINK` | `lb-url-link` |
248
261
  | `ATTR_URL_PARM` | `lb-url-parm` |
249
262
  | `ATTR_URL_PUSH` | `lb-url-push` |
@@ -19,6 +19,7 @@ serves, and the custom elements those pages are made of.
19
19
  | [`imports.ts`](./builder.md#widgets-from-packages) | The packages this app takes widgets from |
20
20
  | [`loadbare-app-build`](./builder.md#running-the-builder) | Configuring the build command |
21
21
  | [CSS](./css.md) | How the builder packages css |
22
+ | [The dev server](./dev-server.md) | Refresh after any change, never restart |
22
23
 
23
24
  ## Application Pages
24
25
 
@@ -35,7 +35,7 @@ app.get(/.*/, (_req, res) =>
35
35
  res.type("html").send(readFileSync(path.join(DIST, "app.html"), "utf-8")),
36
36
  );
37
37
 
38
- app.listen(8787);
38
+ app.listen(Number(process.env.PORT ?? 8787));
39
39
  ```
40
40
 
41
41
  ## What the server serves
@@ -96,16 +96,21 @@ The limit covers every page's requests.
96
96
  ## Running the server
97
97
 
98
98
  Run the server under a TypeScript-capable runner. The builder writes
99
- `dist/pages.ts`, which exports `hub`, as TypeScript:
99
+ `dist/pages.ts`, which exports `hub`, as TypeScript. During development,
100
+ [the dev server](./dev-server.md) runs it, and restarts it when a server
101
+ module changes:
100
102
 
101
103
  ```json
102
104
  {
103
105
  "scripts": {
104
- "dev": "loadbare-app-build --watch & tsx server.ts"
106
+ "dev": "node dev.ts"
105
107
  }
106
108
  }
107
109
  ```
108
110
 
111
+ The server reads its port from `PORT`, which is how the dev server tells it
112
+ where to listen.
113
+
109
114
  Node 22.18 and later also runs it with no runner at all, since Node strips
110
115
  TypeScript types itself. `dist/pages.ts` imports each `.requests.ts` and
111
116
  `.queries.ts` file by its full name, extension included, because Node looks
@@ -1,8 +1,7 @@
1
1
  # The Basic Widget Library
2
2
 
3
- Thirteen widgets: `lb-input`, `lb-select`, `lb-options`, `lb-picker`,
4
- `lb-table`, `lb-unknown-page`, `lb-confirm`, `lb-form`, `lb-insert-dialog`,
5
- `lb-master`, `lb-checkbox`, `lb-number` and `lb-date`. Every one of them is an ordinary custom element, written
3
+ `@loadbare/widgets` is the basic widget set: controls, a table, dialogs, and
4
+ elements that format a value. Every one of them is an ordinary custom element, written
6
5
  against the contracts in [Custom Elements](./custom-elements.md#html) for its
7
6
  definition and [Custom Elements](./custom-elements.md#code) for its class.
8
7
 
@@ -30,282 +29,16 @@ see [Being a control](./custom-elements.md#being-a-control). Each passes a
30
29
  refusal's message for its column to the native control inside it; see
31
30
  [Refusals](./data-binding.md#refusals).
32
31
 
33
- ## `lb-input`
32
+ ## Each widget
34
33
 
35
- Wraps an `<input>`. The hub sets its `value` from the column `lb-column`
36
- names, and `lb-request` names what the widget sends on `change`:
34
+ Each widget's parameters, the attributes it reads, and what a change sends
35
+ are documented once, in the package that ships it: one page per widget,
36
+ which the loadbare-widgets skill carries. Install that skill beside this one:
37
37
 
38
- ```html
39
- <lb-input lb-column="name" lb-request="lb-row-update" exp-label="Name"></lb-input>
40
38
  ```
41
-
42
- Inside a form, leave `lb-request` off. The form gathers the widget on submit
43
- like any other control, so a widget that also sent its own request would
44
- write the same edit twice.
45
-
46
- | Parameter | Fills |
47
- |----------------|----------------------------------|
48
- | `exp-label` | The visible `<label>` text |
49
- | `exp-type` | The input's `type`, `text` when absent |
50
- | `exp-readonly` | The input's `readonly` attribute |
51
-
52
- ## `lb-select`
53
-
54
- Wraps a `<select>` whose `<option>`s the page writes inside the tag:
55
-
56
- ```html
57
- <lb-select lb-column="team" lb-request="lb-row-update" exp-label="Team">
58
- <option value="">No team</option>
59
- <option value="Engines">Engines</option>
60
- </lb-select>
61
- ```
62
-
63
- | Parameter | Fills |
64
- |-------------|----------------------------|
65
- | `exp-label` | The visible `<label>` text |
66
-
67
- ## `lb-options`
68
-
69
- A `<select>` whose `<option>`s are the rows of the query it names. The page
70
- writes the row template inside the widget:
71
-
72
- ```html
73
- <lb-options lb-query="statuses" lb-column="status" lb-request="lb-row-update" exp-label="Status">
74
- <template><option lb-column="label"></option></template>
75
- </lb-options>
76
- ```
77
-
78
- | Parameter | Fills |
79
- |-------------|----------------------------|
80
- | `exp-label` | The visible `<label>` text |
81
- | `exp-size` | The `<select>`'s `size`, an open list rather than a drop-down |
82
-
83
- - Each option's `value` is its row's key, the `lb-key-value` the hub stamps
84
- on the live row, so the widget holds a key and shows a label.
85
- - A group template holding an `<optgroup>` puts the options in groups, one
86
- per run of the query's order's leading term. The widget labels each
87
- `<optgroup>` from the `lb-group-value` the hub stamps on it:
88
-
89
- ```html
90
- <template lb-group>
91
- <optgroup><template><option lb-column="label"></option></template></optgroup>
92
- </template>
93
- ```
94
- - A heading option marked `data-group-label` in the `<optgroup>`, `hidden`
95
- and `disabled`, labels the group with its text instead, so the label can
96
- be a column of the group's first row.
97
- - Its `value` selects the option with that key, including an option that
98
- arrives after the value did.
99
-
100
- `lb-picker` is the same class with the row template supplied by its
101
- definition.
102
-
103
- ## `lb-picker`
104
-
105
- `lb-options`, with the row template built in, for a list where every row is
106
- one option showing one column:
107
-
108
- ```html
109
- <lb-picker
110
- lb-query="statuses"
111
- lb-column="status"
112
- lb-request="lb-row-update"
113
- exp-label="Status"
114
- exp-column="label"
115
- ></lb-picker>
39
+ npx loadbare-widgets skills install
116
40
  ```
117
41
 
118
- | Parameter | Fills |
119
- |--------------|-----------------------------------------|
120
- | `exp-label` | The visible `<label>` text |
121
- | `exp-column` | The column each option shows |
122
-
123
- An option built from two columns, a row with a second element, or options
124
- in groups, is `lb-options` with the page's own templates.
125
-
126
- ## `lb-table`
127
-
128
- A `<table>` that supplies its own scaffolding. The page supplies the heading
129
- row, the row template, and optionally a footer, each as a `<template>`:
130
-
131
- ```html
132
- <lb-table lb-query="ledger" exp-caption="Ledger">
133
- <template lb-exp-template="head">
134
- <tr><th>Date</th><th>Amount</th></tr>
135
- </template>
136
- <template lb-group>
137
- <tr><th colspan="2" lb-column="month"></th></tr>
138
- <template>
139
- <tr><td lb-column="date"></td><td lb-column="amount"></td></tr>
140
- </template>
141
- </template>
142
- <template lb-exp-template="foot">
143
- <tr><td>Total</td><td lb-sum="amount"></td></tr>
144
- </template>
145
- </lb-table>
146
- ```
147
-
148
- | Parameter | Fills |
149
- |---------------|----------------------|
150
- | `exp-caption` | The `<caption>` text |
151
-
152
- | Template | Fills |
153
- |-----------------------------------|-----------------------------------------|
154
- | `<template lb-exp-template="head">` | The `<thead>` |
155
- | `<template lb-exp-template="foot">` | The `<tfoot>` |
156
- | Any other `<template>` | The row or group template, in the `<tbody>` |
157
-
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
161
- rows land in the one `<tbody>`, so a group here is a heading row with its
162
- rows after it.
163
- - `lb-order` and `lb-filter` go on the `lb-table` itself, beside
164
- `lb-query`; see [Order](./data-binding.md#order) and
165
- [Filters](./data-binding.md#filters).
166
- - The footer is not a row of `ledger`. An aggregate there covers every row
167
- showing;
168
- a figure that is not a sum of the rows, such as a budget, is a `<tr>`
169
- naming a `row` query of its own.
170
- - A `<template lb-exp-template="ghost">` is a blank row for entering a new
171
- one, in a `<tbody>` of its own. The widget joins its controls to a form
172
- outside the table, since a `<form>` cannot wrap a `<tr>`, and puts the
173
- cursor back in it once an insert from it succeeds.
174
- - After rows land, the widget scrolls the row the page's own request created
175
- or moved into view.
176
- - Text selected in a cell whose `lb-column` the table's `lb-filter` names
177
- becomes that column's filter in `lb-choices`: `=` for the whole text,
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.
186
-
187
- ## `lb-confirm`
188
-
189
- A button that asks before it sends. The page writes the question inside the
190
- tag, and the request goes on the dialog's confirm button, so nothing reaches
191
- the hub until the user says yes:
192
-
193
- ```html
194
- <lb-confirm exp-label="Delete" exp-request="lb-row-delete">
195
- Delete <b lb-column="name"></b>?
196
- </lb-confirm>
197
- ```
198
-
199
- | Parameter | Fills |
200
- |---------------|--------------------------------------------------|
201
- | `exp-label` | The text of the button that opens the dialog |
202
- | `exp-request` | The confirm button's `lb-request` |
203
- | `exp-confirm` | The confirm button's text, `Confirm` when absent |
204
-
205
- The widget closes the dialog once the request succeeds, and leaves it open
206
- on a refusal.
207
-
208
- ## `lb-form`
209
-
210
- A `<form>` over one row's controls. A row with a key saves each control's
211
- column on `change`; a row whose key is empty is new, and the hub sends no
212
- update for it, so its controls wait for a submit. A definition only.
213
-
214
- | Parameter | Fills |
215
- |-----------|-----------------|
216
- | `exp-id` | The form's `id` |
217
-
218
- ## `lb-insert-dialog`
219
-
220
- A row entered in a modal dialog, written once outside every row and opened
221
- with `commandfor` and `command="show-modal"`. Save is the form's
222
- `lb-row-insert`; on success the widget chooses the new row in the nearest
223
- `lb-options` around the button that opened it, and closes. The handler
224
- answers with a patch holding the new row.
225
-
226
- While open, the button that opened it carries `insert-dialog-opener` and the
227
- widget carries `anchored`, for a stylesheet to place the dialog beside the
228
- button with CSS anchor positioning.
229
-
230
- | Parameter | Fills |
231
- |-------------|---------------------------------------------------|
232
- | `exp-id` | The dialog's `id` |
233
- | `exp-title` | The dialog's heading |
234
- | `exp-query` | The form's `lb-query`, which it inserts into |
235
- | `exp-save` | The Save button's text, `Save` when absent |
236
-
237
- ## `lb-master`
238
-
239
- A set, one of its rows, and the buttons that act on the set: New, Copy,
240
- Delete, Save new and Cancel, each shown only for a row on record or a new
241
- one. A definition only, built from `lb-form` and `lb-confirm`. New sends
242
- `new` and Copy sends `copy`, requests the page declares; `new` returns
243
- `url()` emptying the parm that names the row.
244
-
245
- | Parameter | Fills |
246
- |---------------|--------------------------------------------|
247
- | `exp-row` | The `lb-query` of the row the form shows |
248
- | `exp-form-id` | The form's `id`, `master-form` when absent |
249
-
250
- ## `lb-checkbox`
251
-
252
- A checkbox and its label as one element, for an application to style. A
253
- definition only: a native checkbox is already a control (see
254
- [Checkboxes](./data-binding.md#checkboxes)), so the widget is the markup, a
255
- `<label>` holding the `<input type="checkbox">` and a `<span>` with the
256
- text. The column and request go on the checkbox inside, so they arrive as
257
- expansion attributes.
258
-
259
- Unlike `lb-input`, it has no script making the tag the control. A value
260
- that lands before a widget's script has loaded arrives only as the string
261
- stamp `lb-column-value`, and a script checkbox would read `"false"` as
262
- checked. The hub sets a native checkbox's `checked` directly.
263
-
264
- | Parameter | Fills |
265
- |---------------|-----------------------------------------------|
266
- | `exp-column` | The checkbox's `lb-column` |
267
- | `exp-label` | The label's text |
268
- | `exp-request` | The checkbox's `lb-request`, none when absent |
269
-
270
- ## `lb-number` and `lb-date`
271
-
272
- A number shown by the browser's `Intl.NumberFormat`, and a date or time
273
- shown by `Intl.DateTimeFormat`. Neither is a control: `lb-column` goes on
274
- the tag, and the widget renders the `lb-column-value` the hub stamps; see
275
- [Receiving a value](./custom-elements.md#receiving-a-value).
276
-
277
- ```html
278
- <lb-number lb-column="balance" exp-style="currency" exp-currency="USD"></lb-number>
279
- <lb-date lb-column="due" exp-date-style="medium"></lb-date>
280
- ```
281
-
282
- Each parameter is one of Intl's options in kebab-case, as `exp-currency-sign`
283
- for `currencySign`, plus `exp-locale`. A value that is no number or date
284
- shows as it arrived, and a date alone, `YYYY-MM-DD`, shows as that day in
285
- every time zone. The stamp keeps the raw value, so a stylesheet selects on
286
- it:
287
-
288
- ```css
289
- lb-number[lb-column-value^="-"] { color: var(--negative); }
290
- ```
291
-
292
- ## `lb-unknown-page`
293
-
294
- The chrome's dialog for a URL that names no page, as one tag:
295
-
296
- ```html
297
- <lb-hub>
298
- <nav>...</nav>
299
- <main></main>
300
- <lb-unknown-page></lb-unknown-page>
301
- </lb-hub>
302
- ```
303
-
304
- It expands to a `<dialog lb-url-unknown lb-query="lb-url">` showing
305
- `lb-path`, and the hub opens it when no page's stub matches the path. See
306
- [An unknown page](./chrome.md#an-unknown-page) for the same dialog written
307
- by hand.
308
-
309
- | Parameter | Fills |
310
- |-------------------|------------------------------------------|
311
- | `exp-button-text` | The text of the button that closes it, `OK` by default |
42
+ For a reader without the skill, the package's
43
+ [README](https://gitlab.com/kendowns/loadbare/-/blob/main/packages/widgets/README.md)
44
+ lists the widgets and links each page.