@loadbare/app 0.8.0 → 0.8.2
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/dist/build/skills-cli.d.ts +12 -0
- package/dist/build/skills-cli.d.ts.map +1 -0
- package/dist/build/skills-cli.js +81 -0
- package/dist/build/skills-cli.js.map +1 -0
- package/dist/build/skills.d.ts +47 -0
- package/dist/build/skills.d.ts.map +1 -0
- package/dist/build/skills.js +124 -0
- package/dist/build/skills.js.map +1 -0
- package/dist/core/lb-constants.d.ts +2 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +18 -4
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +10 -0
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +15 -1
- 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 +116 -35
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts +13 -5
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +26 -7
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +3 -0
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +93 -22
- package/docs/comparison.md +8 -7
- package/docs/reference/chrome.md +5 -3
- package/docs/reference/data-binding.md +28 -0
- package/docs/reference/server.md +11 -0
- package/docs/reference/widgets.md +8 -4
- package/docs/roadmap.md +16 -0
- package/docs/theory.md +8 -1
- package/docs/tutorials/010-pages-and-navigation.md +2 -1
- package/package.json +8 -4
- package/skills/loadbare-app/SKILL.md +275 -0
- package/skills/loadbare-app/references/TECHREF-1.0.md +1260 -0
- package/skills/loadbare-app/references/builder.md +134 -0
- package/skills/loadbare-app/references/chrome.md +160 -0
- package/skills/loadbare-app/references/css.md +44 -0
- package/skills/loadbare-app/references/custom-elements.md +397 -0
- package/skills/loadbare-app/references/data-binding.md +485 -0
- package/skills/loadbare-app/references/overview.md +38 -0
- package/skills/loadbare-app/references/page-files.md +194 -0
- package/skills/loadbare-app/references/server.md +153 -0
- package/skills/loadbare-app/references/widgets.md +178 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
# Custom Elements
|
|
2
|
+
|
|
3
|
+
A widget is a custom element, written as a set of files sharing one tag
|
|
4
|
+
name. It serves two purposes, and an application uses it for either or for
|
|
5
|
+
both at once.
|
|
6
|
+
|
|
7
|
+
| File | Holds |
|
|
8
|
+
|--------------------------|----------------------------------|
|
|
9
|
+
| `<tag-name>.html` | The markup the tag expands into |
|
|
10
|
+
| `<tag-name>.browser.ts` | The class the tag registers |
|
|
11
|
+
|
|
12
|
+
Write one of the two, or both, but write at least one. A tag with neither is
|
|
13
|
+
a build error.
|
|
14
|
+
|
|
15
|
+
The script says `.browser` because that is the file that crosses to the
|
|
16
|
+
browser. Name it `<tag-name>.ts` and the builder leaves it alone: an
|
|
17
|
+
unmarked module is server-side code as far as the build is concerned, which
|
|
18
|
+
is what keeps a `stripe.ts` or a `config.ts` out of the bundle no matter what
|
|
19
|
+
tag someone writes later.
|
|
20
|
+
|
|
21
|
+
Custom elements are the only mechanism Loadbare offers for either purpose.
|
|
22
|
+
An application decomposes its HTML by defining a tag, and delivers behavior
|
|
23
|
+
to the browser by registering one; the builder recognizes no other way to do
|
|
24
|
+
either.
|
|
25
|
+
|
|
26
|
+
Put the files anywhere under `src/`. The builder finds them by name, the
|
|
27
|
+
same way it finds pages — see [The Builder](./builder.md).
|
|
28
|
+
|
|
29
|
+
Loadbare uses light DOM throughout. A widget's markup is ordinary markup in
|
|
30
|
+
the document, visible in view-source, reachable by `closest()` and by the
|
|
31
|
+
application's stylesheets.
|
|
32
|
+
|
|
33
|
+
## HTML
|
|
34
|
+
|
|
35
|
+
An `.html` file named for a tag defines that tag. When the builder finds the
|
|
36
|
+
tag in the chrome, in a page, or in another definition, it inserts the
|
|
37
|
+
definition's content as children of the tag. We call this process
|
|
38
|
+
'HTML expansion'.
|
|
39
|
+
|
|
40
|
+
A package listed in `imports.ts` loads first and an application's own
|
|
41
|
+
definitions load after, so a tag defined in both resolves to the
|
|
42
|
+
application's.
|
|
43
|
+
|
|
44
|
+
### Parameters
|
|
45
|
+
|
|
46
|
+
A definition takes parameters. Three namespaces share the attributes of a
|
|
47
|
+
widget tag, and the prefix says which namespace an attribute is in:
|
|
48
|
+
|
|
49
|
+
| Prefix | Belongs to | Read |
|
|
50
|
+
|-------------|------------|-----------------------------|
|
|
51
|
+
| `exp-` | Expansion | By the builder, at build time |
|
|
52
|
+
| `lb-` | The hub | By Loadbare, in the browser |
|
|
53
|
+
| No prefix | HTML | By the browser, as HTML says |
|
|
54
|
+
|
|
55
|
+
Pass a parameter by writing `exp-<name>` on the tag. Read it in the
|
|
56
|
+
definition by writing `{{<name>}}`. The prefix marks the attribute as
|
|
57
|
+
expansion's input and is not part of the parameter's name, so `exp-label`
|
|
58
|
+
supplies `{{label}}`:
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
<!-- definition: src/note-field.html -->
|
|
62
|
+
<label>{{label}} <input readonly="{{readonly}}" /></label>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<!-- authored -->
|
|
67
|
+
<note-field lb-cell="name" exp-label="Name"></note-field>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Write `class`, `title`, or any other unprefixed attribute on a widget
|
|
71
|
+
freely. They are HTML's, and never collide with a definition's parameters.
|
|
72
|
+
|
|
73
|
+
Write a placeholder as an entire attribute value or an entire text node.
|
|
74
|
+
Nothing inside one is evaluated, and expansion has no data to branch on —
|
|
75
|
+
see [Conditional rendering](./data-binding.md#conditional-rendering) for
|
|
76
|
+
what to write instead of a branch.
|
|
77
|
+
|
|
78
|
+
Name a parameter in lowercase. HTML lowercases attribute names before
|
|
79
|
+
expansion sees them, so `exp-inputClass` arrives as `exp-inputclass` and
|
|
80
|
+
could never fill `{{inputClass}}`. Write `{{input-class}}` instead; a
|
|
81
|
+
definition declaring such a name is rejected when it loads.
|
|
82
|
+
|
|
83
|
+
Leave a parameter unsupplied to drop what reads it. An attribute whose
|
|
84
|
+
value is a placeholder is removed rather than shipped empty, which is how
|
|
85
|
+
`readonly="{{readonly}}"` works as an ordinary boolean attribute. A
|
|
86
|
+
placeholder in a text node resolves to nothing, and the whitespace around
|
|
87
|
+
it survives.
|
|
88
|
+
|
|
89
|
+
Give a placeholder a default with a pipe: `{{button-text|OK}}` reads
|
|
90
|
+
`exp-button-text` when the tag supplies it and `OK` when it does not. The
|
|
91
|
+
default is a literal, not an expression. Whitespace around the name and the
|
|
92
|
+
default is not part of either, so `{{ button-text | OK }}` is the same
|
|
93
|
+
placeholder. An empty default, `readonly="{{readonly|}}"`, is the one that
|
|
94
|
+
differs from no default at all: it emits the attribute, empty, rather than
|
|
95
|
+
dropping it, so a definition can ship a boolean attribute switched on.
|
|
96
|
+
|
|
97
|
+
Every attribute stays on the tag after expansion, `exp-` ones included.
|
|
98
|
+
|
|
99
|
+
Parameter values reach a definition through the DOM rather than through
|
|
100
|
+
string substitution, so a value is never reparsed as markup and needs no
|
|
101
|
+
escaping.
|
|
102
|
+
|
|
103
|
+
### The slot
|
|
104
|
+
|
|
105
|
+
`lb-slot` marks the one element in a definition whose children receive
|
|
106
|
+
whatever the author wrote inside the tag:
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<!-- definition: src/note-card.html -->
|
|
110
|
+
<article class="card"><div lb-slot></div></article>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<!-- authored -->
|
|
115
|
+
<note-card>
|
|
116
|
+
<h3>Meeting notes</h3>
|
|
117
|
+
<p>Bring the roster.</p>
|
|
118
|
+
</note-card>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```html
|
|
122
|
+
<!-- ships -->
|
|
123
|
+
<note-card>
|
|
124
|
+
<article class="card">
|
|
125
|
+
<div>
|
|
126
|
+
<h3>Meeting notes</h3>
|
|
127
|
+
<p>Bring the roster.</p>
|
|
128
|
+
</div>
|
|
129
|
+
</article>
|
|
130
|
+
</note-card>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The tag stays and the definition becomes its children. `lb-slot` itself is
|
|
134
|
+
gone from what ships, having done its job at build time.
|
|
135
|
+
|
|
136
|
+
Give a definition at most one `lb-slot`. A definition with a slot and
|
|
137
|
+
nothing written inside it leaves the slot empty.
|
|
138
|
+
|
|
139
|
+
`lb-slot` is an attribute rather than an element because HTML's content
|
|
140
|
+
model discards foreign elements inside `<select>` and `<table>`. A slot
|
|
141
|
+
marker has to survive on an element the surrounding tag already permits.
|
|
142
|
+
|
|
143
|
+
### Destinations
|
|
144
|
+
|
|
145
|
+
`lb-template` names a destination in a definition. On an authored
|
|
146
|
+
`<template lb-template="name">`, it names the destination that template
|
|
147
|
+
fills:
|
|
148
|
+
|
|
149
|
+
```html
|
|
150
|
+
<!-- definition: src/ledger-table.html -->
|
|
151
|
+
<table>
|
|
152
|
+
<caption>{{caption}}</caption>
|
|
153
|
+
<thead lb-template="head"></thead>
|
|
154
|
+
<tbody lb-slot></tbody>
|
|
155
|
+
</table>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```html
|
|
159
|
+
<!-- authored -->
|
|
160
|
+
<ledger-table exp-caption="Ledger">
|
|
161
|
+
<template lb-template="head">
|
|
162
|
+
<tr><th>Date</th><th>Amount</th></tr>
|
|
163
|
+
</template>
|
|
164
|
+
<tbody>
|
|
165
|
+
<!-- row template -->
|
|
166
|
+
</tbody>
|
|
167
|
+
</ledger-table>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Declare as many destinations as the definition needs, one per name. An
|
|
171
|
+
author fills none, some, or all of them, and a destination nobody fills
|
|
172
|
+
stays and is empty.
|
|
173
|
+
|
|
174
|
+
Wrap authored content for a destination in a `<template>`, which exists only
|
|
175
|
+
to survive the parser. A `<thead>` written directly inside `<ledger-table>`
|
|
176
|
+
is not inside a `<table>`, so the tokenizer drops the tag before expansion
|
|
177
|
+
sees it. A `<template>` survives anywhere and parses its contents as though
|
|
178
|
+
already in place. What lands in the destination is the template's contents —
|
|
179
|
+
an ordinary `<thead>` in view-source — not the template element.
|
|
180
|
+
|
|
181
|
+
### Recursion
|
|
182
|
+
|
|
183
|
+
A definition may use other widgets. Expansion repeats until nothing new
|
|
184
|
+
appears, so a widget built out of other widgets needs nothing declared for
|
|
185
|
+
it.
|
|
186
|
+
|
|
187
|
+
Keep the definition graph acyclic. A cycle is a build error, reported as the
|
|
188
|
+
path that closes it.
|
|
189
|
+
|
|
190
|
+
Expansion runs over `<template>` contents wherever they occur, so a widget's
|
|
191
|
+
row template ships already expanded, `{{placeholder}}` included.
|
|
192
|
+
|
|
193
|
+
### What fails at build time
|
|
194
|
+
|
|
195
|
+
Each of these is reported with the tag and file name, and nothing is
|
|
196
|
+
shipped:
|
|
197
|
+
|
|
198
|
+
- a tag in the `LB-*` namespace with no definition
|
|
199
|
+
- a cycle in the definition graph
|
|
200
|
+
- an `exp-` attribute the definition never declared
|
|
201
|
+
- a `{{placeholder}}` spelled so that no `exp-` attribute could supply it
|
|
202
|
+
- content written inside a tag whose definition has no `lb-slot`
|
|
203
|
+
- more than one `lb-slot` in one definition
|
|
204
|
+
- a `<template lb-template="name">` naming a destination the definition
|
|
205
|
+
does not have
|
|
206
|
+
- two templates for the same destination
|
|
207
|
+
|
|
208
|
+
An unfilled `{{placeholder}}` is not among these. That is presence
|
|
209
|
+
propagation working as intended, indistinguishable from an author who left
|
|
210
|
+
an optional parameter out.
|
|
211
|
+
|
|
212
|
+
## Code
|
|
213
|
+
|
|
214
|
+
A `<tag>.browser.ts` file registers that tag's class. A widget is an
|
|
215
|
+
ordinary custom element — Loadbare imposes no base class — and it takes part
|
|
216
|
+
in data binding through three contracts: it receives a value, it sends a
|
|
217
|
+
request, and it accepts a set of rows.
|
|
218
|
+
|
|
219
|
+
Import every attribute name from `@loadbare/app/constants`. Never write one
|
|
220
|
+
as a string literal.
|
|
221
|
+
|
|
222
|
+
| Constant | Value |
|
|
223
|
+
|------------------|------------|
|
|
224
|
+
| `ATTR_VALUE` | `lb-value` |
|
|
225
|
+
| `ATTR_CELL` | `lb-cell` |
|
|
226
|
+
| `ATTR_SHOW` | `lb-show` |
|
|
227
|
+
| `ATTR_LIST` | `lb-list` |
|
|
228
|
+
| `ATTR_ROW` | `lb-row` |
|
|
229
|
+
| `ATTR_KEY` | `lb-key` |
|
|
230
|
+
| `ATTR_KEY_VALUE` | `lb-key-value` |
|
|
231
|
+
| `ATTR_ACTION` | `lb-action`|
|
|
232
|
+
| `ACTION_ROW_INSERT`, `ACTION_ROW_DELETE`, `ACTION_ROW_UPDATE` | the reserved `lb-action` values |
|
|
233
|
+
| `LB_ACTIONS` | all three of them, in one array |
|
|
234
|
+
| `LB_RESERVED_PREFIX` | `lb-`, the prefix every reserved name begins with |
|
|
235
|
+
| `ATTR_ROW_COUNT` | `lb-row-count`|
|
|
236
|
+
| `LB_EVENT_NAME` | `lb-request` |
|
|
237
|
+
|
|
238
|
+
### Receiving a value
|
|
239
|
+
|
|
240
|
+
A bound cell lands on a widget as the `lb-value` attribute rather than as
|
|
241
|
+
text — see [Data Binding](./data-binding.md#where-a-bound-value-lands).
|
|
242
|
+
Observe it, and render the value however the widget renders things:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
// src/visit-count.browser.ts
|
|
246
|
+
import { ATTR_VALUE } from "@loadbare/app/constants";
|
|
247
|
+
|
|
248
|
+
class VisitCount extends HTMLElement {
|
|
249
|
+
static observedAttributes = [ATTR_VALUE];
|
|
250
|
+
|
|
251
|
+
attributeChangedCallback(_name: string, _old: string, value: string) {
|
|
252
|
+
this.textContent = `visited ${value} times`;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
customElements.define("visit-count", VisitCount);
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`value` is `null` when the attribute is removed, which the hub does to the
|
|
260
|
+
cells a successful insert resets — see
|
|
261
|
+
[TECHREF-1.0](./TECHREF-1.0.md#the-round-trip). Read it as nothing landed,
|
|
262
|
+
and show the widget's default. A blank string is a value that landed.
|
|
263
|
+
|
|
264
|
+
A widget carrying `lb-show`, or inside an element that does, is moved into a
|
|
265
|
+
template while its column is off and back out when it turns on — see
|
|
266
|
+
[Conditional rendering](./data-binding.md#conditional-rendering). Going in, it
|
|
267
|
+
sees `disconnectedCallback` and then `adoptedCallback`; coming out,
|
|
268
|
+
`adoptedCallback` and then `connectedCallback`. It is the same instance
|
|
269
|
+
throughout, and it still receives `lb-value` while it is away, so write
|
|
270
|
+
`connectedCallback` to run more than once, as a row a list reorders already
|
|
271
|
+
requires.
|
|
272
|
+
|
|
273
|
+
List `ATTR_VALUE` in `observedAttributes`, or `attributeChangedCallback`
|
|
274
|
+
never fires. The browser calls it for an attribute already present when an
|
|
275
|
+
element upgrades, not only for one that changes afterward, so a widget's
|
|
276
|
+
first render and every later refresh go through the one callback. There is
|
|
277
|
+
no separate hydration path to write.
|
|
278
|
+
|
|
279
|
+
### Sending a request
|
|
280
|
+
|
|
281
|
+
A widget that owns its own interaction — a `<select>`'s choice rather than a
|
|
282
|
+
click — dispatches its own request as a bubbling `CustomEvent` named
|
|
283
|
+
`LB_EVENT_NAME`, carrying the action and, where it wraps a control, that
|
|
284
|
+
control's value as its `detail`:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
import { LB_EVENT_NAME } from "@loadbare/app/constants";
|
|
288
|
+
import type { HubRequest } from "@loadbare/app/types";
|
|
289
|
+
|
|
290
|
+
const detail: HubRequest = { action: "lb-row-update", value: input.value };
|
|
291
|
+
this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The hub fills in the scope the widget sits in before any ancestor sees the
|
|
295
|
+
event, and does not send a request missing a field its operation requires.
|
|
296
|
+
See [TECHREF-1.0](./TECHREF-1.0.md#requests) for what each operation is
|
|
297
|
+
filled with.
|
|
298
|
+
|
|
299
|
+
A widget that carries `lb-cell` and sends `lb-row-insert` or `lb-row-update`
|
|
300
|
+
sends that one cell. The hub builds `values` from the cell's name and the
|
|
301
|
+
`value` the widget sent, or reads the control the widget wraps when it sent
|
|
302
|
+
none.
|
|
303
|
+
|
|
304
|
+
A widget that carries no `lb-cell` doesn't read its own controls. It
|
|
305
|
+
dispatches the bare action from the row, or from anything inside it, and the
|
|
306
|
+
hub gathers `values` from the row the event came from:
|
|
307
|
+
the nearest `<form>`, `<tr>` or live row around the dispatching element,
|
|
308
|
+
itself included, inside its scope. Every readable `lb-cell` in that row is
|
|
309
|
+
gathered, wherever in the row it sits, except the cells of a scope nested in
|
|
310
|
+
it: a picker carrying `lb-list` and `lb-cell` gives its own value, not its
|
|
311
|
+
options'. A request from an element in no such
|
|
312
|
+
row is not sent, and the console says to use a `<form>`.
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
row.dispatchEvent(
|
|
316
|
+
new CustomEvent(LB_EVENT_NAME, {
|
|
317
|
+
bubbles: true,
|
|
318
|
+
detail: { action: "lb-row-insert" } as HubRequest,
|
|
319
|
+
}),
|
|
320
|
+
);
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Values the widget supplies itself are kept, and nothing is gathered over
|
|
324
|
+
them.
|
|
325
|
+
|
|
326
|
+
When an insert succeeds, the hub resets the controls it gathered to their
|
|
327
|
+
defaults, so a blank row for entering a new one is blank again without the
|
|
328
|
+
widget watching the request settle. A hidden input the widget added keeps
|
|
329
|
+
its value, since a hidden input's default is its value. Values the widget
|
|
330
|
+
supplied in the request were not gathered, and are not reset.
|
|
331
|
+
|
|
332
|
+
Let the event bubble, so an ancestor widget can intercept and stop it before
|
|
333
|
+
the hub sees it. A hand-written widget and a native element carrying
|
|
334
|
+
`lb-action` produce the same event.
|
|
335
|
+
|
|
336
|
+
### Decorating a list
|
|
337
|
+
|
|
338
|
+
The hub reconciles every list scope itself. A plain element carrying
|
|
339
|
+
`lb-list` with a row template inside it is a whole list and needs no widget,
|
|
340
|
+
so a widget exists only when the rows need scaffolding or placement that
|
|
341
|
+
only it can decide.
|
|
342
|
+
|
|
343
|
+
Two optional methods say what it decides. Both are named in the `lb`
|
|
344
|
+
namespace, which Loadbare reserves for methods it calls on classes it does
|
|
345
|
+
not own, so a widget's own methods can never collide with a later one:
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
import type { ListHost, Row } from "@loadbare/app/types";
|
|
349
|
+
|
|
350
|
+
class SortedList extends HTMLElement implements ListHost {
|
|
351
|
+
lbPlaceRow(el: Element, row: Row, template: HTMLTemplateElement) {
|
|
352
|
+
// Where this row goes. Called with the element detached, on its first
|
|
353
|
+
// appearance and again whenever a whole set decides the order.
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
lbRowsLanded() {
|
|
357
|
+
// Once, after the result has landed. For scaffolding derived from the
|
|
358
|
+
// rows: a section heading, an <optgroup>, anything that goes when its
|
|
359
|
+
// last row does.
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Everything else is the hub's, and a widget never reimplements it:
|
|
365
|
+
|
|
366
|
+
| Concern | What the hub does |
|
|
367
|
+
|-------------|---------------------------------------------------------|
|
|
368
|
+
| Cloning | Clones the `<template lb-key="...">` in the scope |
|
|
369
|
+
| Matching | Updates the row already showing that key, or clones one |
|
|
370
|
+
| Reconciling | Removes the rows the response says are gone |
|
|
371
|
+
| Counting | Stamps `lb-row-count` with the number of rows showing |
|
|
372
|
+
|
|
373
|
+
An array is the whole set, so it decides membership and order, and a key
|
|
374
|
+
absent from it is removed. A patch touches only the rows it names and leaves
|
|
375
|
+
every other row's contents and position alone.
|
|
376
|
+
|
|
377
|
+
`lbPlaceRow` is called with the row already filled and not yet in the
|
|
378
|
+
document, so a widget that reads a cell to decide where the row goes can. A
|
|
379
|
+
scope without it lands rows immediately before the template, in arrival
|
|
380
|
+
order. `lb-options.browser.ts` and `lb-table.browser.ts` in
|
|
381
|
+
[`@loadbare/widgets`](./widgets.md) are two different placements over the
|
|
382
|
+
same machinery.
|
|
383
|
+
|
|
384
|
+
A list scope with no row template displays nothing, which is not an error.
|
|
385
|
+
|
|
386
|
+
Style an empty list against `lb-row-count` rather than carrying an empty-state
|
|
387
|
+
conditional in the widget — see
|
|
388
|
+
[Conditional rendering](./data-binding.md#conditional-rendering).
|
|
389
|
+
|
|
390
|
+
### Filling a scope by hand
|
|
391
|
+
|
|
392
|
+
`@loadbare/app` also exports `applyRow(root, row)`, the same row-landing
|
|
393
|
+
operation a page host uses. Call it in a widget that builds a scope of its
|
|
394
|
+
own rather than one the hub reconciles. It fills `root`
|
|
395
|
+
itself when `root` carries a matching `lb-cell`, and every matching
|
|
396
|
+
descendant.
|
|
397
|
+
</content>
|