@loadbare/app 0.18.1 → 0.19.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 +17 -17
- package/dist/build/assemble.d.ts +7 -1
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +81 -11
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/cli.d.ts +3 -0
- package/dist/build/cli.d.ts.map +1 -1
- package/dist/build/cli.js +6 -1
- package/dist/build/cli.js.map +1 -1
- package/dist/build/declared.d.ts +25 -0
- package/dist/build/declared.d.ts.map +1 -0
- package/dist/build/declared.js +62 -0
- package/dist/build/declared.js.map +1 -0
- package/dist/build/pages.d.ts +6 -0
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +32 -12
- package/dist/build/pages.js.map +1 -1
- package/dist/core/lb-types.d.ts +6 -3
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +10 -3
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/server/lb-server.d.ts +5 -2
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +12 -3
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +33 -85
- package/docs/comparison.md +695 -482
- package/docs/departures.md +63 -0
- package/docs/miswarts.md +140 -0
- package/docs/opinions.md +155 -0
- package/docs/reference/builder.md +9 -2
- package/docs/reference/chrome.md +2 -1
- package/docs/reference/css.md +163 -0
- package/docs/reference/custom-elements.md +1 -1
- package/docs/reference/data-binding.md +44 -10
- package/docs/reference/page-files.md +38 -5
- package/docs/reference/widgets.md +24 -2
- package/docs/roadmap.md +55 -43
- package/docs/testing.md +62 -28
- package/docs/theory.md +348 -108
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +16 -9
- package/skills/loadbare-app/references/TECHREF-1.0.md +33 -85
- package/skills/loadbare-app/references/builder.md +9 -2
- package/skills/loadbare-app/references/chrome.md +2 -1
- package/skills/loadbare-app/references/css.md +163 -0
- package/skills/loadbare-app/references/custom-elements.md +1 -1
- package/skills/loadbare-app/references/data-binding.md +44 -10
- package/skills/loadbare-app/references/miswarts.md +140 -0
- package/skills/loadbare-app/references/page-files.md +38 -5
- package/skills/loadbare-app/references/widgets.md +24 -2
- package/docs/possible-ideas.md +0 -314
- package/docs/theory-old.md +0 -839
- package/docs/what-does-loadbare-extend.md +0 -124
|
@@ -96,7 +96,7 @@ export const queries: Queries = {
|
|
|
96
96
|
|
|
97
97
|
| Declared with | Answers with |
|
|
98
98
|
|----------------------------|-------------------------------------------|
|
|
99
|
-
| `row(key, run)` | One row, an object
|
|
99
|
+
| `row(key, run)` | One row, an object, or `null` for none |
|
|
100
100
|
| `rows(key, run, options)` | All its rows, an array of objects |
|
|
101
101
|
|
|
102
102
|
`options` are a `rows` query's order and column types. `order` is the
|
|
@@ -229,8 +229,9 @@ export const requests: Requests = {
|
|
|
229
229
|
};
|
|
230
230
|
```
|
|
231
231
|
|
|
232
|
-
Declare every request the page allows. The
|
|
233
|
-
does not declare
|
|
232
|
+
Declare every request the page allows. The builder refuses markup naming a
|
|
233
|
+
request the page does not declare, and the server refuses one that arrives
|
|
234
|
+
anyway; see [The markup checks](./data-binding.md#the-markup-checks).
|
|
234
235
|
|
|
235
236
|
### crud
|
|
236
237
|
|
|
@@ -293,8 +294,9 @@ export const requests: Requests = {
|
|
|
293
294
|
};
|
|
294
295
|
```
|
|
295
296
|
|
|
296
|
-
Declare every request the query permits. The
|
|
297
|
-
query does not declare, and
|
|
297
|
+
Declare every request the query permits. The builder refuses markup sending
|
|
298
|
+
a request a query does not declare, and the server refuses one that arrives
|
|
299
|
+
anyway. A query with no `crud` entry permits none.
|
|
298
300
|
|
|
299
301
|
Write `rowUpdate` to set the columns `values` names and leave every other
|
|
300
302
|
column as it is. A form or a live row sends every control it gathers, and a
|
|
@@ -303,6 +305,36 @@ columns the page may edit.
|
|
|
303
305
|
|
|
304
306
|
Every value in `values` is a string, as a control's value is.
|
|
305
307
|
|
|
308
|
+
Two users writing the same row overlap only on the columns their requests
|
|
309
|
+
carry, and the last write wins. A single control sends its own column, so
|
|
310
|
+
two users editing different columns of a row do not overwrite each other.
|
|
311
|
+
A form sends every control it gathers, so two forms saved over one row
|
|
312
|
+
overlap on all of their columns.
|
|
313
|
+
|
|
314
|
+
A write whose outcome matters states its precondition in its own SQL, and
|
|
315
|
+
refuses when no row was updated:
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
handlers: {
|
|
319
|
+
approve: {
|
|
320
|
+
run: async (ctx, { key }) => {
|
|
321
|
+
const updated = await ctx.db.run(
|
|
322
|
+
"UPDATE invoice SET status = 'approved' WHERE id = $1 AND status = 'pending'",
|
|
323
|
+
[key],
|
|
324
|
+
);
|
|
325
|
+
if (updated === 0) {
|
|
326
|
+
throw refusal("Someone else has already decided this invoice.");
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
refresh: ["invoice"],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The request's name says what the user meant, and the precondition follows
|
|
335
|
+
from it, so no earlier value is needed. See
|
|
336
|
+
[Refusing a request](#refusing-a-request).
|
|
337
|
+
|
|
306
338
|
### refresh and patch
|
|
307
339
|
|
|
308
340
|
List in `refresh` every query whose whole answer the request changed.
|
|
@@ -317,6 +349,7 @@ queries:
|
|
|
317
349
|
| Answer | States |
|
|
318
350
|
|--------------------------|---------------------------------------------|
|
|
319
351
|
| `{ ... }` | The row of a `row` query |
|
|
352
|
+
| `null` | A `row` query found no row |
|
|
320
353
|
| `[...]` | All rows of a `rows` query, and their order |
|
|
321
354
|
| `patch({ rows: [...] })` | These rows arrive or change; the rest stand |
|
|
322
355
|
| `patch({ drop: [...] })` | These keys are gone; the rest stand |
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# The Basic Widget Library
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Thirteen widgets: `lb-input`, `lb-select`, `lb-options`, `lb-picker`,
|
|
4
4
|
`lb-table`, `lb-unknown-page`, `lb-confirm`, `lb-form`, `lb-insert-dialog`,
|
|
5
|
-
`lb-master` and `lb-
|
|
5
|
+
`lb-master`, `lb-checkbox`, `lb-number` and `lb-date`. Every one of them is an ordinary custom element, written
|
|
6
6
|
against the contracts in [Custom Elements](./custom-elements.md#html) for its
|
|
7
7
|
definition and [Custom Elements](./custom-elements.md#code) for its class.
|
|
8
8
|
|
|
@@ -258,6 +258,28 @@ checked. The hub sets a native checkbox's `checked` directly.
|
|
|
258
258
|
| `exp-label` | The label's text |
|
|
259
259
|
| `exp-request` | The checkbox's `lb-request`, none when absent |
|
|
260
260
|
|
|
261
|
+
## `lb-number` and `lb-date`
|
|
262
|
+
|
|
263
|
+
A number shown by the browser's `Intl.NumberFormat`, and a date or time
|
|
264
|
+
shown by `Intl.DateTimeFormat`. Neither is a control: `lb-column` goes on
|
|
265
|
+
the tag, and the widget renders the `lb-column-value` the hub stamps; see
|
|
266
|
+
[Receiving a value](./custom-elements.md#receiving-a-value).
|
|
267
|
+
|
|
268
|
+
```html
|
|
269
|
+
<lb-number lb-column="balance" exp-style="currency" exp-currency="USD"></lb-number>
|
|
270
|
+
<lb-date lb-column="due" exp-date-style="medium"></lb-date>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Each parameter is one of Intl's options in kebab-case, as `exp-currency-sign`
|
|
274
|
+
for `currencySign`, plus `exp-locale`. A value that is no number or date
|
|
275
|
+
shows as it arrived, and a date alone, `YYYY-MM-DD`, shows as that day in
|
|
276
|
+
every time zone. The stamp keeps the raw value, so a stylesheet selects on
|
|
277
|
+
it:
|
|
278
|
+
|
|
279
|
+
```css
|
|
280
|
+
lb-number[lb-column-value^="-"] { color: var(--negative); }
|
|
281
|
+
```
|
|
282
|
+
|
|
261
283
|
## `lb-unknown-page`
|
|
262
284
|
|
|
263
285
|
The chrome's dialog for a URL that names no page, as one tag:
|
package/docs/possible-ideas.md
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
# Possible Ideas
|
|
2
|
-
|
|
3
|
-
> **LLM-authored, not yet revised by a person.** Drafted by Claude on
|
|
4
|
-
> 2026-10-02 against `@loadbare/app` 0.12.0, from a session in ef. Nothing
|
|
5
|
-
> here is decided or planned.
|
|
6
|
-
|
|
7
|
-
> **Status, 2026-10-04.** The lifecycle in the hub's terms was adopted, on
|
|
8
|
-
> custom elements rather than on behaviors: see
|
|
9
|
-
> [When its code runs](./reference/custom-elements.md#when-its-code-runs).
|
|
10
|
-
> Behavior by attribute, and expansion that replaces the tag, were set aside.
|
|
11
|
-
> What follows is kept as the record of how the decision was reached.
|
|
12
|
-
|
|
13
|
-
## Why these are being considered
|
|
14
|
-
|
|
15
|
-
A custom element is the platform's one way to attach code to markup, and
|
|
16
|
-
widget work keeps running into what that costs.
|
|
17
|
-
|
|
18
|
-
- No decomposition. Code that concerns one row has nowhere to live but a
|
|
19
|
-
wrapper around the whole table, so `lb-table` owns everything inside it.
|
|
20
|
-
That is how page-specific behavior ends up in a shared widget: the COA
|
|
21
|
-
page's ghost row per section grew because row-level code could attach
|
|
22
|
-
only to the table. It is the overfit the README's test guards against.
|
|
23
|
-
- The browser's lifecycle is not the hub's. A moved row is disconnected and
|
|
24
|
-
connected, the copy a move leaves behind is constructed, and an `lb-show`
|
|
25
|
-
toggle adopts twice, so `connectedCallback` must be written to run again.
|
|
26
|
-
- Clutter. The page's source is the widget's dialect of named templates
|
|
27
|
-
rather than HTML, and the shipped page keeps a wrapper tag around what it
|
|
28
|
-
expanded.
|
|
29
|
-
- Plumbing through the wrapper. The tag carrying the code is not the
|
|
30
|
-
control: `WrappedControl` forwards `value`, fires `change` again, and needs
|
|
31
|
-
`ElementInternals` to join a form. Focus is searched for inside the
|
|
32
|
-
wrapper, and `autofocus` on a wrapped `<input>` is lost.
|
|
33
|
-
- Content models reach forms too. A `<form>` cannot wrap a `<tr>`, so the
|
|
34
|
-
ghost row joins its controls to a hidden form outside the table by the
|
|
35
|
-
`form` attribute. A form cannot hold a form, so `lb-insert-dialog` is
|
|
36
|
-
written once outside every row instead of beside the control it serves.
|
|
37
|
-
- Ids repeat in a row template. `lb-confirm` finds its dialog by position,
|
|
38
|
-
because `commandfor`, HTML's own way to open one with no script, works
|
|
39
|
-
only by id.
|
|
40
|
-
- Upgrade timing is the hub's problem. An element shipped inside an
|
|
41
|
-
`lb-show` template, or whose definition loads late, has not upgraded, so
|
|
42
|
-
the hub keeps `waiting()`, lands kept answers again once it upgrades, and
|
|
43
|
-
stamps `lb-column-value` for a control to take up on first connect.
|
|
44
|
-
- A few lines cost a wrapper. `lb-options` has a script mostly to copy
|
|
45
|
-
`lb-group-value` into an `<optgroup>`'s `label` and each key into an
|
|
46
|
-
option's `value`, and for that the whole `<select>` sits inside a custom
|
|
47
|
-
element.
|
|
48
|
-
- Parameters cannot carry structure. Expansion parameters are words, so
|
|
49
|
-
when groups became a template, `lb-picker` lost `exp-group` and a grouped
|
|
50
|
-
picker went back to a hand-written `lb-options`.
|
|
51
|
-
- CSS and accessibility see an element HTML does not define. A wrapper tag
|
|
52
|
-
has no role, shifts child and `:nth-child` selectors, and has no place in
|
|
53
|
-
a table. The usual remedy, `display: contents`, has a history of
|
|
54
|
-
accessibility bugs.
|
|
55
|
-
- One element, one class. A ghost row that should also save as it is typed
|
|
56
|
-
in needs a widget combining the two.
|
|
57
|
-
|
|
58
|
-
## Code on elements the parser constrains
|
|
59
|
-
|
|
60
|
-
Loadbare's data vocabulary is attributes, and an attribute goes on any
|
|
61
|
-
element: `<tr lb-query>`, `<option lb-column>`, `<tbody lb-show>`. Behavior
|
|
62
|
-
is different. The platform attaches code to markup through a custom element
|
|
63
|
-
tag, and a tag must survive the parser. In a table, a `<select>`, or a `<p>`
|
|
64
|
-
it does not: an unexpected tag is dropped, moved out of the table, or closes
|
|
65
|
-
the paragraph.
|
|
66
|
-
|
|
67
|
-
So code that concerns one row lives on a wrapper around the whole table.
|
|
68
|
-
`lb-table` owns the `<table>`, and the page writes its contents as named
|
|
69
|
-
templates (`head`, `foot`, `ghost`) that the builder joins back into a
|
|
70
|
-
table. Each new need is another named destination, and the page's source
|
|
71
|
-
reads as `lb-table`'s shape rather than a table's. The shipped HTML is
|
|
72
|
-
honest; the source is a dialect.
|
|
73
|
-
|
|
74
|
-
Two ways to put code on the element itself follow, and only the second
|
|
75
|
-
survives.
|
|
76
|
-
|
|
77
|
-
### Customized built-ins
|
|
78
|
-
|
|
79
|
-
```html
|
|
80
|
-
<tr is="lb-ghost-row">…</tr>
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
The platform's own answer: a class extending `HTMLTableRowElement`, which
|
|
84
|
-
the parser treats as a `<tr>`. WebKit has declined to ship it, and every
|
|
85
|
-
browser on iOS and iPadOS is WebKit, so a polyfill, usually a
|
|
86
|
-
`MutationObserver`, would be the code path on every iPhone and iPad for
|
|
87
|
-
good. That rules it out.
|
|
88
|
-
|
|
89
|
-
### Behavior by attribute
|
|
90
|
-
|
|
91
|
-
```html
|
|
92
|
-
<tr lb-behavior="ghost-row">…</tr>
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Code bound to any element by an attribute, as Stimulus does with
|
|
96
|
-
`data-controller`. It fits the parser everywhere and needs no polyfill.
|
|
97
|
-
|
|
98
|
-
- What usually drives it is a `MutationObserver`, which is incidental: the
|
|
99
|
-
code runs because something noticed. The hub, though, already knows every
|
|
100
|
-
time it puts an element in the page: it shows a page, clones a row, builds
|
|
101
|
-
a group, and moves an element in and out of an `lb-show` template. A
|
|
102
|
-
behavior attached and detached at those moments would be explicit.
|
|
103
|
-
- Elements the hub does not add (a widget's own scaffolding, anything a
|
|
104
|
-
script creates) would get no behavior, which may be the right rule rather
|
|
105
|
-
than a limitation.
|
|
106
|
-
- No form association of its own, and mostly none needed: a behavior on a
|
|
107
|
-
native control leaves it a native control, which joins its form, takes
|
|
108
|
-
focus and honors `autofocus` as it always did. Only a control whose UI is
|
|
109
|
-
built from scratch needs `ElementInternals`, and stays an autonomous
|
|
110
|
-
custom element.
|
|
111
|
-
- The platform has discussed a registry for custom attributes, which would
|
|
112
|
-
be this idea built in. It has not shipped. Without it the browser has no
|
|
113
|
-
part in a behavior: everything below is the hub's.
|
|
114
|
-
|
|
115
|
-
#### A lifecycle in the hub's terms
|
|
116
|
-
|
|
117
|
-
A custom element's lifecycle is the browser's, and it speaks of insertion:
|
|
118
|
-
connected, disconnected, adopted. The hub's work rarely lines up with it. A
|
|
119
|
-
moved row is disconnected and connected, the copy a move leaves behind is
|
|
120
|
-
constructed, an `lb-show` toggle adopts twice, which is why a custom element
|
|
121
|
-
must write `connectedCallback` to run again.
|
|
122
|
-
|
|
123
|
-
A behavior's lifecycle would be calls the hub makes from its own code, at
|
|
124
|
-
moments it already knows, named for what the hub did:
|
|
125
|
-
|
|
126
|
-
| The hub | A behavior hears |
|
|
127
|
-
| -------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
128
|
-
| Shows a page, places a cloned row, builds a group, first shows an element shipped absent | `attached()`, once, after the element is placed and filled |
|
|
129
|
-
| Lands new values on the element's row | `landed(row)` |
|
|
130
|
-
| Moves a row the order put elsewhere | `moved()`; the instance is kept |
|
|
131
|
-
| Leaves a copy of a moved row behind | Nothing; the copy gets no behavior |
|
|
132
|
-
| Turns `lb-show` off, then on | `hidden()`, then `shown()`; the instance is kept |
|
|
133
|
-
| Removes a row, once its animations end | `detached()`, once |
|
|
134
|
-
| Finishes a request issued from inside the element | `requestDone(detail)` |
|
|
135
|
-
|
|
136
|
-
- `attached` and `detached` pair exactly, so setup and teardown need no
|
|
137
|
-
guard against running twice.
|
|
138
|
-
- The calls are the code-side twin of the stamps a stylesheet already sees
|
|
139
|
-
(`lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-leaving`):
|
|
140
|
-
one vocabulary for CSS and code, in the hub's relational terms.
|
|
141
|
-
- The hub keeps one instance per element and behavior name. An element may
|
|
142
|
-
carry several, `lb-behavior="ghost-row autosave"`, which a tag cannot.
|
|
143
|
-
- A behavior is a class in a file the builder finds by name, as it finds
|
|
144
|
-
`<tag>.browser.ts`, and a name with no file is a build error.
|
|
145
|
-
|
|
146
|
-
What the hub would have to define and keep: whether `attached` comes before
|
|
147
|
-
or after the rows inside a host land, and the order of calls among nested
|
|
148
|
-
behaviors. A custom element gets its ordering from the platform; here it is
|
|
149
|
-
loadbare's to state.
|
|
150
|
-
|
|
151
|
-
A rule for choosing: a custom element makes a new kind of element (its own
|
|
152
|
-
markup, expansion, or a control built from scratch); a behavior adds code to
|
|
153
|
-
an element HTML already has.
|
|
154
|
-
|
|
155
|
-
### Where it would land
|
|
156
|
-
|
|
157
|
-
Behavior by attribute lets a page write a real `<table lb-query>` with code
|
|
158
|
-
on the rows that need it, and leaves `lb-table` to supply only what is truly
|
|
159
|
-
the table's. It keeps the parser and the browser matrix out of it, at the
|
|
160
|
-
cost of a lifecycle the hub would define.
|
|
161
|
-
|
|
162
|
-
## Expansion that replaces the tag
|
|
163
|
-
|
|
164
|
-
Today a custom element's tag does two jobs: at build time it is expanded,
|
|
165
|
-
and at run time it carries code. Because the code needs the tag, the tag
|
|
166
|
-
ships as a wrapper around what it expanded, and the wrapper is the source of
|
|
167
|
-
most widget friction: focus searched for inside it, values forwarded through
|
|
168
|
-
it, `autofocus` lost, and no place in a table.
|
|
169
|
-
|
|
170
|
-
With behaviors carrying the code, expansion is free to stop shipping the
|
|
171
|
-
tag.
|
|
172
|
-
|
|
173
|
-
```html
|
|
174
|
-
<!-- page -->
|
|
175
|
-
<lb-field exp-label="Name" lb-column="name"></lb-field>
|
|
176
|
-
|
|
177
|
-
<!-- what ships -->
|
|
178
|
-
<!-- lb-field exp-label="Name" lb-column="name" -->
|
|
179
|
-
<label lb-behavior="lb-field">Name <input lb-column="name" /></label>
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
- **The tag becomes a comment.** The shipped DOM keeps a trail back to the
|
|
183
|
-
source at no run-time cost. A comment is legal anywhere, tables included,
|
|
184
|
-
and is invisible to CSS, `:nth-child` and the hub's counting.
|
|
185
|
-
- **The builder puts the behavior on the root.** When
|
|
186
|
-
`<name>.behavior.ts` exists, the expanded root gets `lb-behavior="<name>"`;
|
|
187
|
-
the author never writes it. `[lb-behavior~="lb-field"]` then serves CSS
|
|
188
|
-
and `querySelector` as the tag selector `lb-field` does now.
|
|
189
|
-
- **Every widget inherits one base class.** A `Behavior` holds `this.el`,
|
|
190
|
-
has empty methods for every call in the hub's lifecycle, and types
|
|
191
|
-
`landed(row)` and `requestDone(detail)`. A widget overrides only what it
|
|
192
|
-
uses, as it extends `HTMLElement` or `WrappedControl` today.
|
|
193
|
-
|
|
194
|
-
The file convention decides which path an element takes:
|
|
195
|
-
|
|
196
|
-
| The element has | The builder | At run time |
|
|
197
|
-
| ------------------------------------------ | -------------------------------------------------- | ---------------------- |
|
|
198
|
-
| An element file, no script | Replaces the tag, leaves a comment | Plain HTML |
|
|
199
|
-
| An element file and `<name>.behavior.ts` | Replaces the tag, puts `lb-behavior` on the root | The hub's lifecycle |
|
|
200
|
-
| `<name>.browser.ts` | Keeps the tag, as today | The browser's lifecycle |
|
|
201
|
-
|
|
202
|
-
The third row is for controls built from scratch, the only elements that
|
|
203
|
-
still need `ElementInternals`.
|
|
204
|
-
|
|
205
|
-
### A flag to keep the tag
|
|
206
|
-
|
|
207
|
-
Considered and set aside. It gives every widget two DOM shapes, so its CSS
|
|
208
|
-
and code must work in both. It cannot apply where replacing pays off most,
|
|
209
|
-
since a kept tag still has no place in a table. And what people would keep
|
|
210
|
-
the tag for, a name to find and style the widget by and a trail to its
|
|
211
|
-
source, the `lb-behavior` attribute and the comment already give.
|
|
212
|
-
|
|
213
|
-
### To settle
|
|
214
|
-
|
|
215
|
-
1. **One root.** A behavior attaches to one element, so an element file with
|
|
216
|
-
a behavior has exactly one top-level element, and anything more is a
|
|
217
|
-
build error.
|
|
218
|
-
2. **Where the tag's attributes go.** The wrapper used to keep them; once
|
|
219
|
-
the tag is gone each needs a destination. The natural default is the
|
|
220
|
-
root, and `lb-field` breaks it: its root is the `<label>`, but
|
|
221
|
-
`lb-column` belongs on the `<input>` inside while `lb-show` belongs on the
|
|
222
|
-
label. Either the element file marks a target for data attributes, or
|
|
223
|
-
the page passes them as parameters (`exp-column`, written into the file as
|
|
224
|
-
`lb-column="{{column}}"`), which works today but reads less naturally
|
|
225
|
-
than `lb-column`. This wants worked examples before a rule.
|
|
226
|
-
|
|
227
|
-
## The case for behaviors
|
|
228
|
-
|
|
229
|
-
Today's model is simple to state: a widget is a name, and a name is one or
|
|
230
|
-
both of a `.html` and a `.browser.ts`. One name is one element the
|
|
231
|
-
developer writes, so what a widget does is easy to predict. Most of what
|
|
232
|
-
that costs is paid inside Loadbare, in service of the simple model. The one
|
|
233
|
-
place it leaks to the developer is the lifecycle, where a widget author must
|
|
234
|
-
follow strict rules to get the behavior they expect.
|
|
235
|
-
|
|
236
|
-
Behaviors would simplify the hub, but they seem to spread code that a
|
|
237
|
-
custom element keeps in one file. The containment comes from the element
|
|
238
|
-
file, though, not from the tag.
|
|
239
|
-
|
|
240
|
-
### What one file bundles today
|
|
241
|
-
|
|
242
|
-
`lb-table`'s script does three jobs, joined by where they sit rather than by
|
|
243
|
-
any data they share:
|
|
244
|
-
|
|
245
|
-
| Job | Reads and writes |
|
|
246
|
-
| -------------------------- | -------------------------------------------------- |
|
|
247
|
-
| Table scaffolding | Caption, head and foot destinations; expansion only |
|
|
248
|
-
| The ghost row | The ghost row's controls, its form, and focus |
|
|
249
|
-
| Scrolling to the saved row | The list's rows and their stamps |
|
|
250
|
-
|
|
251
|
-
It is one file, and three concerns in one class, none reusable without the
|
|
252
|
-
others. A `<ul>` cannot have a ghost row without becoming an `lb-table`.
|
|
253
|
-
|
|
254
|
-
### Two units
|
|
255
|
-
|
|
256
|
-
- A widget is markup. Its element file decides what elements exist, at
|
|
257
|
-
build time, as now.
|
|
258
|
-
- A behavior is what one element does. Its code attaches to the element
|
|
259
|
-
that carries it, and runs on the hub's lifecycle.
|
|
260
|
-
|
|
261
|
-
An element file may write `lb-behavior` on its own inner elements:
|
|
262
|
-
|
|
263
|
-
```html
|
|
264
|
-
<!-- lb-table.html -->
|
|
265
|
-
<table lb-behavior="scroll-to-saved">
|
|
266
|
-
<thead lb-exp-template="head"></thead>
|
|
267
|
-
<tbody lb-exp-slot></tbody>
|
|
268
|
-
<tbody lb-exp-template="ghost" lb-behavior="ghost-row"></tbody>
|
|
269
|
-
</table>
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
`lb-table` stays the one place to read, and its element file says which code
|
|
273
|
-
runs on which element. The code is split into files by job, and the widget
|
|
274
|
-
assembles them. A page that wants a ghost row in a `<ul>` writes
|
|
275
|
-
`lb-behavior="ghost-row"` on it.
|
|
276
|
-
|
|
277
|
-
### Where code goes
|
|
278
|
-
|
|
279
|
-
Code goes on the element it reads and writes.
|
|
280
|
-
|
|
281
|
-
- The ghost row's code touches the ghost row's controls and form, so it is a
|
|
282
|
-
behavior on the ghost row.
|
|
283
|
-
- Scrolling touches the list's rows, so it is a behavior on the list.
|
|
284
|
-
- Code that seems to need two elements is the sign to stop. Behaviors never
|
|
285
|
-
call each other. They speak only through what widgets use today:
|
|
286
|
-
attributes the hub stamps, and bubbling events such as `lb-request-done`.
|
|
287
|
-
The scrolling behavior knows nothing of the ghost row; it reads
|
|
288
|
-
`lb-row-requested`.
|
|
289
|
-
|
|
290
|
-
The rule also refuses the overfit that started this: a ghost row per
|
|
291
|
-
section would be a behavior on the table reaching into every section.
|
|
292
|
-
|
|
293
|
-
### Where the leak moves
|
|
294
|
-
|
|
295
|
-
Today the leak is the browser's lifecycle: `connectedCallback` written to
|
|
296
|
-
run again, and a value stamp adopted on first connect. With behaviors it
|
|
297
|
-
becomes three rules Loadbare states:
|
|
298
|
-
|
|
299
|
-
1. Only elements the hub places get behaviors.
|
|
300
|
-
2. A behavior is attached and detached exactly once.
|
|
301
|
-
3. Behaviors speak only through stamps and events.
|
|
302
|
-
|
|
303
|
-
They are fewer, and in the hub's terms rather than the browser's. The cost
|
|
304
|
-
moves to Loadbare's design: the ordering the platform gives custom elements
|
|
305
|
-
must be stated, such as whether a host's behavior attaches before or after
|
|
306
|
-
its rows land, and the order among nested behaviors.
|
|
307
|
-
|
|
308
|
-
### Both, not one
|
|
309
|
-
|
|
310
|
-
Custom elements stay. A control built from scratch still needs
|
|
311
|
-
`ElementInternals`, and a tag with no code is expansion alone. Behaviors
|
|
312
|
-
cover what a tag does badly: code on an element HTML already has, inside
|
|
313
|
-
content models that forbid a tag. The developer's question becomes whether
|
|
314
|
-
they are making a new kind of element, or giving an existing element a job.
|