@loadbare/app 0.5.6 → 0.7.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 +3 -4
- package/dist/build/assemble.d.ts +1 -1
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +81 -7
- package/dist/build/assemble.js.map +1 -0
- package/dist/build/cli.d.ts +2 -2
- package/dist/build/cli.js +3 -2
- package/dist/build/cli.js.map +1 -0
- package/dist/build/elements.js +1 -0
- package/dist/build/elements.js.map +1 -0
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +20 -32
- package/dist/build/expand.js.map +1 -0
- package/dist/build/format.js +1 -0
- package/dist/build/format.js.map +1 -0
- package/dist/build/locations.d.ts +4 -4
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +16 -5
- package/dist/build/locations.js.map +1 -0
- package/dist/build/origins.d.ts +0 -13
- package/dist/build/origins.d.ts.map +1 -1
- package/dist/build/origins.js +33 -8
- package/dist/build/origins.js.map +1 -0
- package/dist/build/package-root.js +1 -0
- package/dist/build/package-root.js.map +1 -0
- package/dist/build/pages.d.ts +7 -3
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +14 -7
- package/dist/build/pages.js.map +1 -0
- package/dist/build/styles.js +1 -0
- package/dist/build/styles.js.map +1 -0
- package/dist/core/lb-constants.d.ts +15 -13
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +104 -53
- package/dist/core/lb-constants.js.map +1 -0
- package/dist/core/lb-types.d.ts +103 -62
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +12 -3
- package/dist/core/lb-types.js.map +1 -0
- package/dist/hub/lb-apply.d.ts +28 -4
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +227 -40
- package/dist/hub/lb-apply.js.map +1 -0
- package/dist/hub/lb-hub.browser.d.ts +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +165 -110
- package/dist/hub/lb-hub.browser.js.map +1 -0
- package/dist/server/lb-express.d.ts +8 -5
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +46 -33
- package/dist/server/lb-express.js.map +1 -0
- package/dist/server/lb-server.d.ts +67 -45
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +56 -18
- package/dist/server/lb-server.js.map +1 -0
- package/docs/TECHREF-1.0.md +1107 -0
- package/docs/analysis-accidental-complexity.md +149 -0
- package/docs/reference/builder.md +4 -4
- package/docs/reference/chrome.md +10 -9
- package/docs/reference/custom-elements.md +54 -46
- package/docs/reference/data-binding.md +179 -97
- package/docs/reference/overview.md +1 -1
- package/docs/reference/page-files.md +64 -49
- package/docs/reference/server.md +25 -6
- package/docs/reference/widgets.md +22 -30
- package/docs/roadmap.md +68 -22
- package/docs/testing.md +47 -17
- package/docs/theory.md +116 -3
- package/docs/tutorials/010-pages-and-navigation.md +8 -8
- package/docs/tutorials/040-displaying-data.md +9 -9
- package/docs/tutorials/050-actions.md +5 -5
- package/docs/tutorials/060-custom-element-code.md +1 -1
- package/docs/tutorials/065-conditional-rendering.md +4 -4
- package/docs/tutorials/070-displaying-a-list.md +24 -47
- package/docs/tutorials/072-inserting-into-a-list.md +18 -15
- package/docs/tutorials/074-deleting-from-a-list.md +15 -17
- package/docs/tutorials/076-updating-a-list-item.md +20 -22
- package/docs/tutorials/080-widget-requests.md +22 -35
- package/docs/tutorials/090-using-widget-libraries.md +1 -1
- package/package.json +2 -3
- package/dist/hub/lb-rows.d.ts +0 -18
- package/dist/hub/lb-rows.d.ts.map +0 -1
- package/dist/hub/lb-rows.js +0 -106
- package/dist/tests/assemble.test.d.ts +0 -8
- package/dist/tests/assemble.test.d.ts.map +0 -1
- package/dist/tests/assemble.test.js +0 -58
- package/dist/tests/elements.test.d.ts +0 -8
- package/dist/tests/elements.test.d.ts.map +0 -1
- package/dist/tests/elements.test.js +0 -118
- package/dist/tests/expand.test.d.ts +0 -10
- package/dist/tests/expand.test.d.ts.map +0 -1
- package/dist/tests/expand.test.js +0 -250
- package/dist/tests/fixtures/elements/collision/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/collision/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/imports.js +0 -1
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.js +0 -1
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.js +0 -1
- package/dist/tests/fixtures/elements/manifest/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest/imports.js +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +0 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.js +0 -1
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.js +0 -1
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts +0 -6
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.js +0 -1
- package/dist/tests/helpers/console.d.ts +0 -20
- package/dist/tests/helpers/console.d.ts.map +0 -1
- package/dist/tests/helpers/console.js +0 -28
- package/dist/tests/helpers/dom.d.ts +0 -18
- package/dist/tests/helpers/dom.d.ts.map +0 -1
- package/dist/tests/helpers/dom.js +0 -22
- package/dist/tests/lb-apply.test.d.ts +0 -8
- package/dist/tests/lb-apply.test.d.ts.map +0 -1
- package/dist/tests/lb-apply.test.js +0 -153
- package/dist/tests/lb-express.test.d.ts +0 -14
- package/dist/tests/lb-express.test.d.ts.map +0 -1
- package/dist/tests/lb-express.test.js +0 -238
- package/dist/tests/lb-rows.test.d.ts +0 -12
- package/dist/tests/lb-rows.test.d.ts.map +0 -1
- package/dist/tests/lb-rows.test.js +0 -336
- package/dist/tests/lb-server.test.d.ts +0 -9
- package/dist/tests/lb-server.test.d.ts.map +0 -1
- package/dist/tests/lb-server.test.js +0 -495
- package/dist/tests/origins.test.d.ts +0 -10
- package/dist/tests/origins.test.d.ts.map +0 -1
- package/dist/tests/origins.test.js +0 -369
- package/dist/tests/pages.test.d.ts +0 -6
- package/dist/tests/pages.test.d.ts.map +0 -1
- package/dist/tests/pages.test.js +0 -98
- package/dist/tests/styles.test.d.ts +0 -7
- package/dist/tests/styles.test.d.ts.map +0 -1
- package/dist/tests/styles.test.js +0 -76
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Assessing the accidental complexity claim
|
|
2
|
+
|
|
3
|
+
[Theory](./theory.md) claims that Loadbare/app carries less accidental
|
|
4
|
+
complexity than React, Angular, or the hypermedia libraries. This document
|
|
5
|
+
tests that claim against [TECHREF-1.0](./TECHREF-1.0.md), which is the
|
|
6
|
+
authoritative statement of what 1.0 means.
|
|
7
|
+
|
|
8
|
+
Written 2026-09-07, against `@loadbare/app` 0.6.0.
|
|
9
|
+
|
|
10
|
+
Brooks separates the difficulty of the user's problem from the work the tool
|
|
11
|
+
demands. The second kind is accidental, and the test applied here is whether
|
|
12
|
+
Loadbare removes such work or relocates it somewhere the author still pays
|
|
13
|
+
for it.
|
|
14
|
+
|
|
15
|
+
## Where the claim holds
|
|
16
|
+
|
|
17
|
+
The whole owned surface fits in the technical reference's cross-reference
|
|
18
|
+
section.
|
|
19
|
+
|
|
20
|
+
| Owned | Count |
|
|
21
|
+
|------------------|-------|
|
|
22
|
+
| `lb-*` attributes | 15 |
|
|
23
|
+
| `lb*` methods | 3 |
|
|
24
|
+
| Builder flags | 4 |
|
|
25
|
+
| Reserved filenames| 8 |
|
|
26
|
+
| Reserved tags | 1 |
|
|
27
|
+
| Reserved events | 1 |
|
|
28
|
+
|
|
29
|
+
React reaches that size before an application adds a router, a data layer, or
|
|
30
|
+
a bundler configuration, and each of those carries a surface of its own.
|
|
31
|
+
Loadbare/app asks for no configuration file today.
|
|
32
|
+
|
|
33
|
+
The server half is the strongest part of the argument. A page is markup, a
|
|
34
|
+
queries file, and a requests file. The application designs no endpoints,
|
|
35
|
+
writes no route table, and picks no serialization contract. Four lines of
|
|
36
|
+
Express carry the data channel. Neither the fat frameworks nor the
|
|
37
|
+
hypermedia libraries remove that category of work; htmx in particular leaves
|
|
38
|
+
the developer designing every endpoint and every fragment it answers with.
|
|
39
|
+
|
|
40
|
+
Building the HTML once removes reconciliation, keys, memoization, and effect
|
|
41
|
+
dependencies together. That is the largest single deletion in the design,
|
|
42
|
+
and it is the one the hypermedia libraries do not make either, since they
|
|
43
|
+
ship markup at run time and carry swap semantics to place it.
|
|
44
|
+
|
|
45
|
+
## What the technical reference already knows
|
|
46
|
+
|
|
47
|
+
Most of what an assessment finds is already on the blocker list. Each
|
|
48
|
+
concern below adds weight to an open item rather than naming a new one.
|
|
49
|
+
|
|
50
|
+
| Concern | Blocker |
|
|
51
|
+
|--------------------------------------------|--------------------------------------------|
|
|
52
|
+
| Values carry no type, so every application formats its own dates and money | Data types |
|
|
53
|
+
| No page is reachable by row | Take a position on the URL space |
|
|
54
|
+
| A widget receives one cell at a time | Whether a widget may receive a whole row |
|
|
55
|
+
| A widget repeats hub code to fire a request| Give a widget a way to fire its own request|
|
|
56
|
+
| Master-detail is unspecified | Decide what a nested list means |
|
|
57
|
+
| Every page redeclares the chrome's queries | Give the chrome a way to state its own queries |
|
|
58
|
+
|
|
59
|
+
The technical reference's own sample query calls `String()` on a count by
|
|
60
|
+
hand, which is the data-type blocker showing up in the documentation.
|
|
61
|
+
|
|
62
|
+
Two [roadmap](./roadmap.md) items carry the same weight as these and appear
|
|
63
|
+
in the first real application. Concurrent writers on one list have no
|
|
64
|
+
version or conflict story. Per-keystroke validation has no home, and the
|
|
65
|
+
roadmap expects it to sit in the widget while the server stays authoritative
|
|
66
|
+
for the same field.
|
|
67
|
+
|
|
68
|
+
Five sections of the technical reference are still marked `UNEDITED`:
|
|
69
|
+
Binding, Requests, Links, Widgets, and Widget authoring. Those are the
|
|
70
|
+
mechanisms an author touches on every page.
|
|
71
|
+
|
|
72
|
+
## Positions that carry a cost
|
|
73
|
+
|
|
74
|
+
Two constraints appear in the body of the technical reference as current
|
|
75
|
+
behavior rather than on the blocker list, so the project has taken a position
|
|
76
|
+
on each. Naming the cost is still fair.
|
|
77
|
+
|
|
78
|
+
The hub reaches its endpoint by absolute path, so an application cannot be
|
|
79
|
+
hosted under a subpath such as `example.com/myapp/`. A deployment that wants
|
|
80
|
+
several applications behind one host gives each one an origin.
|
|
81
|
+
|
|
82
|
+
Every route answers 200 with the same document, so the browser detects an
|
|
83
|
+
unknown page after the fact and the chrome's `lb-unknown-page` dialog reports
|
|
84
|
+
it. A crawler or a monitor that reads status codes sees a healthy response
|
|
85
|
+
for a path the application does not have.
|
|
86
|
+
|
|
87
|
+
## The gap recorded nowhere
|
|
88
|
+
|
|
89
|
+
Loadbare/app offers no way to display or style an element according to the
|
|
90
|
+
value that landed in it.
|
|
91
|
+
|
|
92
|
+
A cell lands on a custom element as the `lb-value` attribute, which a
|
|
93
|
+
stylesheet can select. A cell lands on a native element as its
|
|
94
|
+
`textContent`, which no selector reaches. The hub stamps `lb-pending`,
|
|
95
|
+
`lb-error`, and `lb-row-count`, which cover a request in flight, a request
|
|
96
|
+
that failed, and an empty list. Nothing covers a row whose `status` column
|
|
97
|
+
reads `overdue`.
|
|
98
|
+
|
|
99
|
+
An application that wants this writes a widget, and a widget that fires a
|
|
100
|
+
request pays the cost the widget-protocol blocker already names. So the
|
|
101
|
+
missing piece pushes the author toward the mechanism that is itself
|
|
102
|
+
unfinished.
|
|
103
|
+
|
|
104
|
+
This appears in neither the blockers, the roadmap's open questions, nor the
|
|
105
|
+
decided-against section. It is the one finding here that the technical
|
|
106
|
+
reference does not already record.
|
|
107
|
+
|
|
108
|
+
## The general form of the query gap
|
|
109
|
+
|
|
110
|
+
The URL-space blocker names one consequence of a broader constraint, and
|
|
111
|
+
stating the constraint directly is more useful than stating the consequence.
|
|
112
|
+
|
|
113
|
+
A query takes no argument from the browser. Its signature is `(ctx) => Row`
|
|
114
|
+
or `(ctx) => Row[]`, and `ctx` is what the application built from the Express
|
|
115
|
+
request. The hub's own request carries `page=<name>`. The hub reads
|
|
116
|
+
`location.pathname`, which drops the query string, so a path segment and a
|
|
117
|
+
search parameter are both invisible to the server.
|
|
118
|
+
|
|
119
|
+
An application that shows one selected record therefore holds the selection
|
|
120
|
+
in server state. A click fires an action, the handler records the selection
|
|
121
|
+
where `ctx` reaches it, and the refreshed query reads it back. That works
|
|
122
|
+
today and needs no new mechanism. It puts selection in the same territory as
|
|
123
|
+
the roadmap's concurrent-writers question, and it means a reload or a shared
|
|
124
|
+
link does not carry the record.
|
|
125
|
+
|
|
126
|
+
## Comparison with the hypermedia libraries
|
|
127
|
+
|
|
128
|
+
Theory rejects the existing tools for combining interpolation with
|
|
129
|
+
conditional and list rendering. The distinction is narrower than that.
|
|
130
|
+
Loadbare/app interpolates at build time, with defaults and a substitution
|
|
131
|
+
grammar, and renders lists at run time through `<template lb-key>`. What the
|
|
132
|
+
design rules out is conditionals and interpolation after the build.
|
|
133
|
+
|
|
134
|
+
Loadbare/app also adds a builder, a filename grammar, and a slot and template
|
|
135
|
+
system, where htmx asks for no build step. Against React and Angular the
|
|
136
|
+
surface comparison is decisive. Against htmx it is close, and the server
|
|
137
|
+
half is where Loadbare/app wins instead.
|
|
138
|
+
|
|
139
|
+
## Verdict
|
|
140
|
+
|
|
141
|
+
The claim holds on the server, and it holds on the client for everything the
|
|
142
|
+
reconciliation layer used to cost.
|
|
143
|
+
|
|
144
|
+
On the rest of the client it currently holds partly by not doing several
|
|
145
|
+
things database applications need, and the technical reference lists most of
|
|
146
|
+
them itself. Whether the claim survives 1.0 depends on how the URL space,
|
|
147
|
+
the row hook, the chrome queries, and value-driven display are answered, and
|
|
148
|
+
an answer of "decided against" counts as an answer only where an application
|
|
149
|
+
can still reach the behavior some other way.
|
|
@@ -40,7 +40,7 @@ The builder classifies by name, not location.
|
|
|
40
40
|
|----------------------------|---------------------------------------------------|
|
|
41
41
|
| `chrome.html` | The chrome — see [`chrome.html`](./chrome.md) |
|
|
42
42
|
| `*.page.html` | A page |
|
|
43
|
-
| `*.
|
|
43
|
+
| `*.requests.ts` | A page's requests, matched by base name |
|
|
44
44
|
| `*.queries.ts` | A page's queries, matched by base name |
|
|
45
45
|
| `imports.ts` | The packages this app takes widgets from |
|
|
46
46
|
| `*.css` | A stylesheet — see [CSS](./css.md) |
|
|
@@ -48,7 +48,7 @@ The builder classifies by name, not location.
|
|
|
48
48
|
| `<tag>.browser.ts` | A widget script, named for the tag it registers |
|
|
49
49
|
|
|
50
50
|
Give the application exactly one `chrome.html` and at most one `imports.ts`.
|
|
51
|
-
Give every `.
|
|
51
|
+
Give every `.requests.ts` and `.queries.ts` a `.page.html` of the same base name.
|
|
52
52
|
|
|
53
53
|
Name a widget script `<tag>.browser.ts`, not `<tag>.ts`. Only a file whose
|
|
54
54
|
name carries `.browser` is bundled for the browser; every other module under
|
|
@@ -125,10 +125,10 @@ A tag with neither a script nor a definition in any origin is an error.
|
|
|
125
125
|
The builder expands the chrome and every page against the available widget
|
|
126
126
|
definitions — see [Custom Elements](./custom-elements.md#html) for the
|
|
127
127
|
substitution rules — wraps each expanded page in
|
|
128
|
-
`<template
|
|
128
|
+
`<template lb-page="<name>">`, and splices them into the chrome's `<body>`. It formats the result with
|
|
129
129
|
Prettier when the application has it installed.
|
|
130
130
|
|
|
131
131
|
The builder writes `app.css` only when it finds a stylesheet, and `pages.ts`
|
|
132
|
-
only when some page has a `.
|
|
132
|
+
only when some page has a `.requests.ts` or a `.queries.ts` file. Import `hub`
|
|
133
133
|
from `pages.ts` — see [The Express Server](./server.md) for the rest of the
|
|
134
134
|
wiring.
|
package/docs/reference/chrome.md
CHANGED
|
@@ -24,7 +24,7 @@ Here is a minimal but fully complaint chrome for a typical app:
|
|
|
24
24
|
</head>
|
|
25
25
|
<body hidden>
|
|
26
26
|
<lb-hub>
|
|
27
|
-
<header lb-
|
|
27
|
+
<header lb-row="lb-navigation">
|
|
28
28
|
<h1>Membership Roster</h1>
|
|
29
29
|
<h2 lb-cell="page-label"></h2>
|
|
30
30
|
</header>
|
|
@@ -34,7 +34,7 @@ Here is a minimal but fully complaint chrome for a typical app:
|
|
|
34
34
|
<a href="https://example.org/">Our website</a>
|
|
35
35
|
</nav>
|
|
36
36
|
<main></main>
|
|
37
|
-
<dialog lb-unknown-page lb-
|
|
37
|
+
<dialog lb-unknown-page lb-row="lb-navigation">
|
|
38
38
|
The URL <span lb-cell="page-uri"></span> is not in this app.
|
|
39
39
|
</dialog>
|
|
40
40
|
</lb-hub>
|
|
@@ -60,7 +60,7 @@ Everything else is optional:
|
|
|
60
60
|
|-------------------------------------------|---------------------------------------------|
|
|
61
61
|
| `<link rel="stylesheet" href="/app.css">` | The bundled stylesheet |
|
|
62
62
|
| `<a lb-nav-link>` | Navigation between pages |
|
|
63
|
-
| `lb-
|
|
63
|
+
| `lb-row="lb-navigation"` | Where the page is — see below |
|
|
64
64
|
| `<dialog lb-unknown-page>` | A message when a URL matches no page |
|
|
65
65
|
| Custom elements | The chrome, decomposed into widget files |
|
|
66
66
|
| `<body hidden>` + a `<noscript>` fallback | Avoids a first-load blink — see below |
|
|
@@ -77,8 +77,9 @@ A navigation anchor's `href` is a path, and the path names a page:
|
|
|
77
77
|
landing page is the one named `index.page.html`. An anchor without
|
|
78
78
|
`lb-nav-link` is left alone and behaves like any other link.
|
|
79
79
|
|
|
80
|
-
The `lb-unknown-page` attribute, if used, must appear on a `<dialog
|
|
81
|
-
hub
|
|
80
|
+
The `lb-unknown-page` attribute, if used, must appear on a `<dialog>` inside
|
|
81
|
+
`<lb-hub>`; the builder rejects it anywhere else. The hub opens it when a
|
|
82
|
+
path names no page. What it says is up to the chrome:
|
|
82
83
|
the dialog is a subtree like any other, and it displays where the page is by
|
|
83
84
|
naming the hub's own query, described next.
|
|
84
85
|
|
|
@@ -86,11 +87,11 @@ naming the hub's own query, described next.
|
|
|
86
87
|
|
|
87
88
|
On every navigation the hub lands a query of its own, `lb-navigation`, on
|
|
88
89
|
any subtree inside the hub that names it. It arrives the way a server's
|
|
89
|
-
|
|
90
|
+
row arrives — `lb-row` on the subtree, `lb-cell` on each element that
|
|
90
91
|
shows a value — so a chrome displays the current page with no code at all:
|
|
91
92
|
|
|
92
93
|
```html
|
|
93
|
-
<header lb-
|
|
94
|
+
<header lb-row="lb-navigation">
|
|
94
95
|
<h1>Membership Roster</h1>
|
|
95
96
|
<h2 lb-cell="page-label"></h2>
|
|
96
97
|
</header>
|
|
@@ -112,12 +113,12 @@ namespace: no server answers a query so named. It is landed only where a
|
|
|
112
113
|
subtree names it, so a chrome that displays no navigation is not warned
|
|
113
114
|
about a query with no scope.
|
|
114
115
|
|
|
115
|
-
The same
|
|
116
|
+
The same row is what an unknown-page dialog has to work with. It lands
|
|
116
117
|
before the page host is looked up, so a miss has it too. Name the query on
|
|
117
118
|
the dialog and show whichever cell fits:
|
|
118
119
|
|
|
119
120
|
```html
|
|
120
|
-
<dialog lb-unknown-page lb-
|
|
121
|
+
<dialog lb-unknown-page lb-row="lb-navigation">
|
|
121
122
|
The URL <span lb-cell="page-uri"></span> is not in this app.
|
|
122
123
|
</dialog>
|
|
123
124
|
```
|
|
@@ -223,12 +223,15 @@ as a string literal.
|
|
|
223
223
|
|------------------|------------|
|
|
224
224
|
| `ATTR_VALUE` | `lb-value` |
|
|
225
225
|
| `ATTR_CELL` | `lb-cell` |
|
|
226
|
-
| `
|
|
226
|
+
| `ATTR_LIST` | `lb-list` |
|
|
227
|
+
| `ATTR_ROW` | `lb-row` |
|
|
227
228
|
| `ATTR_KEY` | `lb-key` |
|
|
228
|
-
| `
|
|
229
|
-
| `ATTR_SORT` | `lb-sort` |
|
|
229
|
+
| `ATTR_KEY_VALUE` | `lb-key-value` |
|
|
230
230
|
| `ATTR_ACTION` | `lb-action`|
|
|
231
|
-
| `
|
|
231
|
+
| `ACTION_ROW_INSERT`, `ACTION_ROW_DELETE`, `ACTION_ROW_UPDATE`, `ACTION_CELL_CHANGE` | the reserved `lb-action` values |
|
|
232
|
+
| `LB_ACTIONS` | all four of them, in one array |
|
|
233
|
+
| `LB_RESERVED_PREFIX` | `lb-`, the prefix every reserved name begins with |
|
|
234
|
+
| `ATTR_ROW_COUNT` | `lb-row-count`|
|
|
232
235
|
| `LB_EVENT_NAME` | `lb-request` |
|
|
233
236
|
|
|
234
237
|
### Receiving a value
|
|
@@ -261,81 +264,86 @@ no separate hydration path to write.
|
|
|
261
264
|
### Sending a request
|
|
262
265
|
|
|
263
266
|
A widget that owns its own interaction — a `<select>`'s choice rather than a
|
|
264
|
-
click —
|
|
265
|
-
`
|
|
266
|
-
as its `detail`:
|
|
267
|
+
click — dispatches its own request as a bubbling `CustomEvent` named
|
|
268
|
+
`LB_EVENT_NAME`, carrying the action and, where it wraps a control, that
|
|
269
|
+
control's value as its `detail`:
|
|
267
270
|
|
|
268
271
|
```ts
|
|
269
|
-
import {
|
|
272
|
+
import { LB_EVENT_NAME } from "@loadbare/app/constants";
|
|
270
273
|
import type { HubRequest } from "@loadbare/app/types";
|
|
271
274
|
|
|
272
|
-
const detail: HubRequest = {
|
|
273
|
-
op: "cell-change",
|
|
274
|
-
query: this.closest(`[${ATTR_QUERY}]`)!.getAttribute(ATTR_QUERY)!,
|
|
275
|
-
key: this.closest(`[${ATTR_KEY}]`)!.getAttribute(ATTR_KEY)!,
|
|
276
|
-
cell: this.getAttribute(ATTR_CELL)!,
|
|
277
|
-
value: input.value,
|
|
278
|
-
};
|
|
275
|
+
const detail: HubRequest = { action: "lb-cell-change", value: input.value };
|
|
279
276
|
this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
|
|
280
277
|
```
|
|
281
278
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
[
|
|
285
|
-
|
|
279
|
+
The hub fills in the scope the widget sits in before any ancestor sees the
|
|
280
|
+
event, and does not send a request missing a field its operation requires.
|
|
281
|
+
See [TECHREF-1.0](../TECHREF-1.0.md#requests) for what each operation is
|
|
282
|
+
filled with.
|
|
286
283
|
|
|
287
284
|
Let the event bubble, so an ancestor widget can intercept and stop it before
|
|
288
285
|
the hub sees it. A hand-written widget and a native element carrying
|
|
289
286
|
`lb-action` produce the same event.
|
|
290
287
|
|
|
291
|
-
###
|
|
288
|
+
### Decorating a list
|
|
292
289
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
290
|
+
The hub reconciles every list scope itself. A plain element carrying
|
|
291
|
+
`lb-list` with a row template inside it is a whole list and needs no widget,
|
|
292
|
+
so a widget exists only when the rows need scaffolding or placement that
|
|
293
|
+
only it can decide.
|
|
294
|
+
|
|
295
|
+
Two optional methods say what it decides. Both are named in the `lb`
|
|
296
|
+
namespace, which Loadbare reserves for methods it calls on classes it does
|
|
297
|
+
not own, so a widget's own methods can never collide with a later one:
|
|
296
298
|
|
|
297
299
|
```ts
|
|
298
|
-
import {
|
|
299
|
-
|
|
300
|
+
import type { ListHost, Row } from "@loadbare/app/types";
|
|
301
|
+
|
|
302
|
+
class SortedList extends HTMLElement implements ListHost {
|
|
303
|
+
lbPlaceRow(el: Element, row: Row, template: HTMLTemplateElement) {
|
|
304
|
+
// Where this row goes. Called with the element detached, on its first
|
|
305
|
+
// appearance and again whenever a whole set decides the order.
|
|
306
|
+
}
|
|
300
307
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
308
|
+
lbRowsLanded() {
|
|
309
|
+
// Once, after the result has landed. For scaffolding derived from the
|
|
310
|
+
// rows: a section heading, an <optgroup>, anything that goes when its
|
|
311
|
+
// last row does.
|
|
304
312
|
}
|
|
305
313
|
}
|
|
306
314
|
```
|
|
307
315
|
|
|
308
|
-
|
|
309
|
-
Every built-in list widget uses it, and it settles four things:
|
|
316
|
+
Everything else is the hub's, and a widget never reimplements it:
|
|
310
317
|
|
|
311
|
-
| Concern | What
|
|
318
|
+
| Concern | What the hub does |
|
|
312
319
|
|-------------|---------------------------------------------------------|
|
|
313
|
-
| Cloning | Clones the `<template lb-key="...">` in the
|
|
320
|
+
| Cloning | Clones the `<template lb-key="...">` in the scope |
|
|
314
321
|
| Matching | Updates the row already showing that key, or clones one |
|
|
315
322
|
| Reconciling | Removes the rows the response says are gone |
|
|
316
|
-
| Counting | Stamps `
|
|
323
|
+
| Counting | Stamps `lb-row-count` with the number of rows showing |
|
|
317
324
|
|
|
318
|
-
|
|
319
|
-
absent from it is removed.
|
|
325
|
+
An array is the whole set, so it decides membership and order, and a key
|
|
326
|
+
absent from it is removed. A patch touches only the rows it names and leaves
|
|
320
327
|
every other row's contents and position alone.
|
|
321
328
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
order.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
+
`lbPlaceRow` is called with the row already filled and not yet in the
|
|
330
|
+
document, so a widget that reads a cell to decide where the row goes can. A
|
|
331
|
+
scope without it lands rows immediately before the template, in arrival
|
|
332
|
+
order. `lb-options.browser.ts` and `lb-table.browser.ts` in
|
|
333
|
+
[`@loadbare/widgets`](./widgets.md) are two different placements over the
|
|
334
|
+
same machinery.
|
|
335
|
+
|
|
336
|
+
A list scope with no row template displays nothing, which is not an error.
|
|
329
337
|
|
|
330
|
-
Style an empty list against `
|
|
338
|
+
Style an empty list against `lb-row-count` rather than carrying an empty-state
|
|
331
339
|
conditional in the widget — see
|
|
332
340
|
[Conditional rendering](./data-binding.md#conditional-rendering).
|
|
333
341
|
|
|
334
342
|
### Filling a scope by hand
|
|
335
343
|
|
|
336
|
-
`@loadbare/app
|
|
337
|
-
|
|
338
|
-
|
|
344
|
+
`@loadbare/app` also exports `applyRow(root, row)`, the same row-landing
|
|
345
|
+
operation a page host uses. Call it in a widget that builds a scope of its
|
|
346
|
+
own rather than one the hub reconciles. It fills `root`
|
|
339
347
|
itself when `root` carries a matching `lb-cell`, and every matching
|
|
340
348
|
descendant.
|
|
341
349
|
</content>
|