@loadbare/app 0.20.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.
- package/README.md +18 -0
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +8 -3
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/build.d.ts +62 -0
- package/dist/build/build.d.ts.map +1 -0
- package/dist/build/build.js +148 -0
- package/dist/build/build.js.map +1 -0
- package/dist/build/cli.d.ts +6 -1
- package/dist/build/cli.d.ts.map +1 -1
- package/dist/build/cli.js +25 -78
- package/dist/build/cli.js.map +1 -1
- package/dist/build/declared.d.ts.map +1 -1
- package/dist/build/declared.js +3 -0
- package/dist/build/declared.js.map +1 -1
- package/dist/build/skills-cli.d.ts +2 -1
- package/dist/build/skills-cli.d.ts.map +1 -1
- package/dist/build/skills-cli.js +8 -1
- package/dist/build/skills-cli.js.map +1 -1
- package/dist/build/skills.d.ts +2 -0
- package/dist/build/skills.d.ts.map +1 -1
- package/dist/build/skills.js +2 -0
- package/dist/build/skills.js.map +1 -1
- package/dist/core/lb-constants.d.ts +14 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +25 -0
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/dev/lb-dev.d.ts +128 -0
- package/dist/dev/lb-dev.d.ts.map +1 -0
- package/dist/dev/lb-dev.js +289 -0
- package/dist/dev/lb-dev.js.map +1 -0
- package/dist/hub/lb-apply.d.ts +10 -2
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +8 -2
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +127 -19
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/docs/comparison.md +4 -3
- package/docs/reference/builder.md +12 -4
- package/docs/reference/chrome.md +52 -1
- package/docs/reference/data-binding.md +27 -12
- package/docs/reference/dev-server.md +171 -0
- package/docs/reference/names.md +14 -1
- package/docs/reference/overview.md +1 -0
- package/docs/reference/server.md +8 -3
- package/docs/reference/widgets.md +10 -277
- package/docs/testing.md +2 -1
- package/docs/tutorials/000-getting-started.md +1 -1
- package/docs/tutorials/040-displaying-data.md +1 -1
- package/docs/tutorials/090-using-widget-libraries.md +1 -1
- package/package.json +4 -2
- package/skills/loadbare-app/SKILL.md +30 -12
- package/skills/loadbare-app/references/builder.md +12 -4
- package/skills/loadbare-app/references/chrome.md +52 -1
- package/skills/loadbare-app/references/data-binding.md +27 -12
- package/skills/loadbare-app/references/dev-server.md +171 -0
- package/skills/loadbare-app/references/names.md +14 -1
- package/skills/loadbare-app/references/overview.md +1 -0
- package/skills/loadbare-app/references/server.md +8 -3
- package/skills/loadbare-app/references/widgets.md +10 -277
|
@@ -119,9 +119,9 @@ from its nearest ancestor row: the live row it is in, or the element a
|
|
|
119
119
|
it reads from that query's rows.
|
|
120
120
|
|
|
121
121
|
An element with `lb-query` and `lb-column` both shows the rows of its own
|
|
122
|
-
query and holds the column of the row around it. This
|
|
123
|
-
[
|
|
124
|
-
|
|
122
|
+
query and holds the column of the row around it. This `<lb-options>`, from
|
|
123
|
+
[`@loadbare/widgets`](./widgets.md), lists the accounts and holds its row's
|
|
124
|
+
`account`:
|
|
125
125
|
|
|
126
126
|
```html
|
|
127
127
|
<lb-options lb-query="accounts" lb-column="account">
|
|
@@ -167,8 +167,8 @@ option for the value shows no selection.
|
|
|
167
167
|
|
|
168
168
|
The hub hands every value to the browser as the server sent it. What a
|
|
169
169
|
number, a date or a null looks like is decided in the query, or by a custom
|
|
170
|
-
element that formats it, such as
|
|
171
|
-
[`
|
|
170
|
+
element that formats it, such as `lb-number` and `lb-date` in
|
|
171
|
+
[`@loadbare/widgets`](./widgets.md). Send a
|
|
172
172
|
column the user sorts or filters raw, and format it in the element.
|
|
173
173
|
|
|
174
174
|
### Checkboxes
|
|
@@ -312,9 +312,12 @@ puts it somewhere else, so all rows landing again unchanged move nothing.
|
|
|
312
312
|
A landing that answers a request sent from inside a list moves none of that
|
|
313
313
|
list's rows. The request is from inside the list when the list is the
|
|
314
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
|
|
316
|
-
|
|
317
|
-
row
|
|
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:
|
|
318
321
|
|
|
319
322
|
| The answer | The row |
|
|
320
323
|
|------------------------------------------|---------------------------------------------------|
|
|
@@ -941,7 +944,8 @@ nothing is not sent.
|
|
|
941
944
|
|
|
942
945
|
For a query the hub serves, `lb-url` or `lb-choices`, the hub answers
|
|
943
946
|
`lb-row-update` itself, with no round trip; see
|
|
944
|
-
[Query parms](./chrome.md#query-parms) and [Choices](#choices).
|
|
947
|
+
[Query parms](./chrome.md#query-parms) and [Choices](#choices). The third,
|
|
948
|
+
`lb-hub`, takes no request.
|
|
945
949
|
|
|
946
950
|
A key comes from a live row, or from an element a `row` landed on, so all
|
|
947
951
|
three work against either kind:
|
|
@@ -958,8 +962,11 @@ update from a form or a live row sends every control it gathers. The
|
|
|
958
962
|
application writes one `rowUpdate` for both, setting the columns `values`
|
|
959
963
|
names, as an SQL UPDATE does.
|
|
960
964
|
|
|
961
|
-
After a successful insert gathered from a form, the hub resets the form
|
|
962
|
-
|
|
965
|
+
After a successful insert gathered from a form, the hub resets the form,
|
|
966
|
+
unless the answer landed the new row on it. A form over a `row` query not
|
|
967
|
+
yet entered, whose insert answers `url()` to the new key, then shows the row
|
|
968
|
+
just saved, on record. A failed insert leaves the entry for the user to
|
|
969
|
+
correct.
|
|
963
970
|
|
|
964
971
|
Leave `lb-request` off a control inside a form or a live row that sends its
|
|
965
972
|
own update, or the same edit is written twice.
|
|
@@ -1033,6 +1040,12 @@ An `lb-show` in the form reads from the row it was pointed at. With no row,
|
|
|
1033
1040
|
every column is null, so `lb-show="!id"` shows only while entering a new
|
|
1034
1041
|
row.
|
|
1035
1042
|
|
|
1043
|
+
A request from a form Edit pointed is sent from inside the row's list, so
|
|
1044
|
+
the row stays where the user was working, even when the filter would now
|
|
1045
|
+
hide it or the order would move it; see
|
|
1046
|
+
[Rows the user is working in](#rows-the-user-is-working-in). A form New
|
|
1047
|
+
pointed is not inside any list, and its rows are placed by the order.
|
|
1048
|
+
|
|
1036
1049
|
A successful request from the form lets it go, as Cancel does: the form
|
|
1037
1050
|
empties, its controls return to their defaults, and the stamps go. A failed
|
|
1038
1051
|
one leaves the form as the user had it, to correct.
|
|
@@ -1245,7 +1258,9 @@ A reset of any form, whether the hub's, a reset button's or a script's,
|
|
|
1245
1258
|
returns its controls to their defaults, and the hub removes
|
|
1246
1259
|
`lb-column-value` from each of them. A cancelled reset keeps both.
|
|
1247
1260
|
|
|
1248
|
-
A page load that fails lands nothing and is reported to the console
|
|
1261
|
+
A page load that fails lands nothing and is reported to the console, and
|
|
1262
|
+
the query `lb-hub` says so; see [The hub's state](./chrome.md#the-hubs-state).
|
|
1263
|
+
A load
|
|
1249
1264
|
started by a request for `lb-url` stamps the element that sent it, as any
|
|
1250
1265
|
request stamps its element.
|
|
1251
1266
|
|
|
@@ -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
|
+
```
|
package/docs/reference/names.md
CHANGED
|
@@ -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
|
|
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
|
|
package/docs/reference/server.md
CHANGED
|
@@ -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": "
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
##
|
|
32
|
+
## Each widget
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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.
|
package/docs/testing.md
CHANGED
|
@@ -274,7 +274,8 @@ What the hub is tested for here:
|
|
|
274
274
|
a pending element is ignored
|
|
275
275
|
- a round trip the server never answers is aborted at the ten-second
|
|
276
276
|
deadline, under mocked timers, and fails the way any round trip fails
|
|
277
|
-
- a successful insert gathered from a form resets the form
|
|
277
|
+
- a successful insert gathered from a form resets the form, unless the
|
|
278
|
+
answer landed the new row on it
|
|
278
279
|
- a path with no page reports, lands `lb-page-unknown`, and opens the
|
|
279
280
|
`lb-url-unknown` dialog; two pages for one stub report and take the first
|
|
280
281
|
- `lb-url` lands with the path, the page's `lb-page-title` or stub as
|