@madojs/mado 0.6.1 → 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/AGENTS.md +82 -30
- package/CHANGELOG.md +98 -3
- package/dist/src/component.d.ts +17 -4
- package/dist/src/component.js +26 -4
- package/dist/src/component.js.map +1 -1
- package/docs/en/07-llm-pitfalls.md +197 -60
- package/docs/en/08-llm-zero-history-test.md +1 -1
- package/docs/en/17-shadow-dom-forms.md +192 -0
- package/docs/en/README.md +20 -19
- package/docs/fr/07-llm-pitfalls.md +196 -60
- package/docs/fr/17-shadow-dom-forms.md +196 -0
- package/docs/fr/README.md +20 -19
- package/docs/ru/07-llm-pitfalls.md +198 -61
- package/docs/ru/08-llm-zero-history-test.md +39 -38
- package/docs/ru/09-shadow-vs-light-dom.md +97 -81
- package/docs/ru/17-shadow-dom-forms.md +193 -0
- package/docs/ru/README.md +20 -19
- package/docs/uk/07-llm-pitfalls.md +64 -3
- package/docs/uk/17-shadow-dom-forms.md +193 -0
- package/docs/uk/README.md +20 -19
- package/llms.txt +50 -1
- package/package.json +2 -2
- package/scripts/bake.mjs +25 -19
- package/scripts/cli.mjs +22 -33
- package/starters/admin/src/components/x-button.ts +40 -13
- package/starters/admin/src/components/x-input.ts +50 -19
- package/starters/admin/src/lib/api.ts +55 -4
package/AGENTS.md
CHANGED
|
@@ -91,6 +91,36 @@ component("x-timer", (ctx) => {
|
|
|
91
91
|
|
|
92
92
|
**`resource()`, `effect()`, and subscriptions inside `setup()` hook into the lifecycle automatically** — no need to write onDispose for them.
|
|
93
93
|
|
|
94
|
+
### 4b. Reactive attributes — `ctx.attr()`
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// ❌ NO (reading once, never reactive)
|
|
98
|
+
component("x-badge", ({ host }) => () => {
|
|
99
|
+
const variant = host.getAttribute("variant") ?? "default";
|
|
100
|
+
return html`<span class=${variant}>...</span>`;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ❌ NO (MutationObserver boilerplate)
|
|
104
|
+
component("x-badge", ({ host, onDispose }) => {
|
|
105
|
+
const variant = signal(host.getAttribute("variant") ?? "default");
|
|
106
|
+
const obs = new MutationObserver(() =>
|
|
107
|
+
variant.set(host.getAttribute("variant") ?? "default"),
|
|
108
|
+
);
|
|
109
|
+
obs.observe(host, { attributes: true, attributeFilter: ["variant"] });
|
|
110
|
+
onDispose(() => obs.disconnect());
|
|
111
|
+
return () => html`<span class=${variant}>...</span>`;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ✅ YES — one line, reactive, no cleanup needed
|
|
115
|
+
component("x-badge", ({ attr }) => {
|
|
116
|
+
const variant = attr("variant", "default");
|
|
117
|
+
return () => html`<span class=${() => `badge-${variant()}`}>...</span>`;
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`ctx.attr(name, defaultValue?)` returns a `Signal<string>` that auto-updates.
|
|
122
|
+
The attribute is automatically added to `observedAttributes`.
|
|
123
|
+
|
|
94
124
|
### 5. Reactive value in template child position = function
|
|
95
125
|
|
|
96
126
|
The most common AI mistake:
|
|
@@ -99,13 +129,13 @@ The most common AI mistake:
|
|
|
99
129
|
const count = signal(0);
|
|
100
130
|
|
|
101
131
|
// ❌ NOT REACTIVE — count() is read once
|
|
102
|
-
html`<div>${count() * 2}</div
|
|
132
|
+
html`<div>${count() * 2}</div>`;
|
|
103
133
|
|
|
104
134
|
// ✅ REACTIVE — the function will be called when count changes
|
|
105
|
-
html`<div>${() => count() * 2}</div
|
|
135
|
+
html`<div>${() => count() * 2}</div>`;
|
|
106
136
|
|
|
107
137
|
// ✅ ALSO OK — the signal itself is a function, Mado recognizes it
|
|
108
|
-
html`<div>${count}</div
|
|
138
|
+
html`<div>${count}</div>`;
|
|
109
139
|
```
|
|
110
140
|
|
|
111
141
|
**Rule of thumb:** if there is a signal call (with parentheses) inside `${...}`, wrap it in `() => ...`.
|
|
@@ -114,17 +144,17 @@ html`<div>${count}</div>`
|
|
|
114
144
|
|
|
115
145
|
```ts
|
|
116
146
|
// string/number → attribute
|
|
117
|
-
html`<a href=${url}>...</a
|
|
147
|
+
html`<a href=${url}>...</a>`;
|
|
118
148
|
|
|
119
149
|
// DOM property (objects, numbers without serialization, .value for input)
|
|
120
|
-
html`<input .value=${user.name}
|
|
121
|
-
html`<my-list .items=${arr}
|
|
150
|
+
html`<input .value=${user.name} />`;
|
|
151
|
+
html`<my-list .items=${arr}></my-list>`;
|
|
122
152
|
|
|
123
153
|
// boolean attribute (toggle)
|
|
124
|
-
html`<button ?disabled=${isLoading}>...</button
|
|
154
|
+
html`<button ?disabled=${isLoading}>...</button>`;
|
|
125
155
|
|
|
126
156
|
// event
|
|
127
|
-
html`<button @click=${fn}>...</button
|
|
157
|
+
html`<button @click=${fn}>...</button>`;
|
|
128
158
|
```
|
|
129
159
|
|
|
130
160
|
Common mistake: `disabled=${loading()}` — this attempts to set a **string** attribute `disabled="true"` or `disabled="false"`, which does not work correctly. **Use `?disabled=`.**
|
|
@@ -135,10 +165,19 @@ Common mistake: `disabled=${loading()}` — this attempts to set a **string** at
|
|
|
135
165
|
import { each } from "@madojs/mado";
|
|
136
166
|
|
|
137
167
|
// ❌ Works, but no keyed reconciliation → loses focus on reorder
|
|
138
|
-
html`<ul
|
|
168
|
+
html`<ul>
|
|
169
|
+
${() => items().map((t) => html`<li>${t.name}</li>`)}
|
|
170
|
+
</ul>`;
|
|
139
171
|
|
|
140
172
|
// ✅ Correct: keyed, reuses DOM nodes
|
|
141
|
-
html`<ul
|
|
173
|
+
html`<ul>
|
|
174
|
+
${() =>
|
|
175
|
+
each(
|
|
176
|
+
items(),
|
|
177
|
+
(t) => t.id,
|
|
178
|
+
(t) => html`<li>${t.name}</li>`,
|
|
179
|
+
)}
|
|
180
|
+
</ul>`;
|
|
142
181
|
```
|
|
143
182
|
|
|
144
183
|
### 8. Routing — `routes()` + `page()`
|
|
@@ -198,14 +237,20 @@ import { useForm } from "@madojs/mado";
|
|
|
198
237
|
|
|
199
238
|
const f = useForm({
|
|
200
239
|
email: { required: true, type: "email" },
|
|
201
|
-
age:
|
|
240
|
+
age: { required: true, type: "number", min: 18 },
|
|
202
241
|
});
|
|
203
242
|
|
|
204
243
|
html`
|
|
205
|
-
<form
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
244
|
+
<form
|
|
245
|
+
@submit=${f.onSubmit(async (v) => {
|
|
246
|
+
await api.save(v);
|
|
247
|
+
})}
|
|
248
|
+
>
|
|
249
|
+
<input name="email" @input=${f.onInput} @blur=${f.onBlur} />
|
|
250
|
+
${() =>
|
|
251
|
+
f.touched().email && f.errors().email
|
|
252
|
+
? html`<small>${f.errors().email}</small>`
|
|
253
|
+
: null}
|
|
209
254
|
<button ?disabled=${() => !f.isValid() || f.submitting()}>Save</button>
|
|
210
255
|
</form>
|
|
211
256
|
`;
|
|
@@ -220,16 +265,23 @@ import { component, css, html } from "@madojs/mado";
|
|
|
220
265
|
|
|
221
266
|
component("x-card", () => () => html`<div><slot></slot></div>`, {
|
|
222
267
|
styles: css`
|
|
223
|
-
:host {
|
|
224
|
-
|
|
225
|
-
|
|
268
|
+
:host {
|
|
269
|
+
display: block;
|
|
270
|
+
padding: 1rem;
|
|
271
|
+
}
|
|
272
|
+
div {
|
|
273
|
+
background: var(--bg);
|
|
274
|
+
}
|
|
275
|
+
::slotted(h2) {
|
|
276
|
+
margin: 0;
|
|
277
|
+
}
|
|
226
278
|
`,
|
|
227
279
|
});
|
|
228
280
|
|
|
229
281
|
// Light DOM (without Shadow), global styles:
|
|
230
282
|
component("x-shell", () => () => html`...`, {
|
|
231
|
-
shadow: false,
|
|
232
|
-
styles: css`x-shell header { ... }`,
|
|
283
|
+
shadow: false, // disables Shadow DOM
|
|
284
|
+
styles: css`x-shell header { ... }`, // selectors are written as usual
|
|
233
285
|
});
|
|
234
286
|
```
|
|
235
287
|
|
|
@@ -314,17 +366,17 @@ When generating an app, prefer the blessed production shape from
|
|
|
314
366
|
|
|
315
367
|
## Where to find specific answers
|
|
316
368
|
|
|
317
|
-
| Question
|
|
318
|
-
|
|
319
|
-
| How does reactivity work?
|
|
320
|
-
| How are templates parsed?
|
|
321
|
-
| How does the router work?
|
|
322
|
-
| How does resource + cache work?
|
|
323
|
-
| How do forms work?
|
|
369
|
+
| Question | File |
|
|
370
|
+
| -------------------------------- | -------------------------------- |
|
|
371
|
+
| How does reactivity work? | `src/signal.ts` (283 lines) |
|
|
372
|
+
| How are templates parsed? | `src/html.ts` (1013 lines) |
|
|
373
|
+
| How does the router work? | `src/router.ts` (~530 lines) |
|
|
374
|
+
| How does resource + cache work? | `src/resource.ts` (297 lines) |
|
|
375
|
+
| How do forms work? | `src/forms.ts` (212 lines) |
|
|
324
376
|
| How should an app be structured? | `docs/en/10-app-architecture.md` |
|
|
325
|
-
| How should errors be handled?
|
|
326
|
-
| How should bake be used?
|
|
327
|
-
| When something goes wrong
|
|
377
|
+
| How should errors be handled? | `docs/en/15-error-handling.md` |
|
|
378
|
+
| How should bake be used? | `docs/en/16-bake-cookbook.md` |
|
|
379
|
+
| When something goes wrong | `docs/en/07-llm-pitfalls.md` |
|
|
328
380
|
|
|
329
381
|
## Before committing
|
|
330
382
|
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,88 @@
|
|
|
4
4
|
|
|
5
5
|
Nothing yet.
|
|
6
6
|
|
|
7
|
+
## 0.7.0
|
|
8
|
+
|
|
9
|
+
Reactive component props, Shadow DOM + Forms fixes, deterministic releases,
|
|
10
|
+
and `mado serve` unification. Motivated by stress-test findings in a real-world
|
|
11
|
+
admin panel (see `MADO_TEST_REPORT.md`).
|
|
12
|
+
|
|
13
|
+
### Breaking Changes
|
|
14
|
+
|
|
15
|
+
- **`mado serve` in app-mode** no longer uses the legacy `serveStaticProject()`
|
|
16
|
+
fallback. It now always goes through `server/serve.mjs`, which means
|
|
17
|
+
`--host`, `--port`, `mado.config.json` dev.proxy, and HMR all work for
|
|
18
|
+
generated apps. If you relied on the old no-HMR behaviour, pass
|
|
19
|
+
`NO_HMR=1 mado serve`.
|
|
20
|
+
|
|
21
|
+
### Added — Framework
|
|
22
|
+
|
|
23
|
+
- **`ctx.attr(name, defaultValue?)`** — reactive attribute accessor for
|
|
24
|
+
components. Returns a `Signal<string>` that auto-updates when the attribute
|
|
25
|
+
changes on the host element via `attributeChangedCallback`. No more
|
|
26
|
+
`MutationObserver` boilerplate in every component.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
component("x-badge", ({ attr }) => {
|
|
30
|
+
const variant = attr("variant", "default");
|
|
31
|
+
return () =>
|
|
32
|
+
html`<span class=${() => `badge-${variant()}`}><slot></slot></span>`;
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Attributes used with `ctx.attr()` are automatically added to
|
|
37
|
+
`observedAttributes`.
|
|
38
|
+
|
|
39
|
+
### Added — Starters
|
|
40
|
+
|
|
41
|
+
- **`apiFetcher<T>()`** in `starters/admin/src/lib/api.ts` — a fetcher for
|
|
42
|
+
`resource()` that attaches the Bearer token from memory. Use for protected
|
|
43
|
+
endpoints instead of the plain `jsonFetcher()`.
|
|
44
|
+
- **`x-button`**: now bridges Shadow DOM → Light DOM form submit via
|
|
45
|
+
`form.requestSubmit()`. Buttons inside Shadow DOM cannot natively trigger
|
|
46
|
+
`<form>` submit in Light DOM — this is now handled automatically.
|
|
47
|
+
- **`x-button`**: uses `ctx.attr("disabled")` for reactive disabled state.
|
|
48
|
+
External `?disabled=${() => !form.isValid()}` now correctly enables/disables
|
|
49
|
+
the inner button.
|
|
50
|
+
- **`x-input`**: proxies `.name` and `.value` DOM properties on the host
|
|
51
|
+
element so that `useForm().onInput` works after Shadow DOM event retargeting.
|
|
52
|
+
|
|
53
|
+
### Added — CLI / Build
|
|
54
|
+
|
|
55
|
+
- **`mado release --no-clean`**: release now cleans the entire `out/` directory
|
|
56
|
+
before building (deterministic artifacts). Pass `--no-clean` to opt out.
|
|
57
|
+
Previously stale assets, removed bake routes, and deleted public files could
|
|
58
|
+
linger in the deploy artifact.
|
|
59
|
+
- **`scripts/bake.mjs`**: `<title>` now falls back to `page.title` if
|
|
60
|
+
`head().title` is not explicitly set. Previously baked HTML kept the template
|
|
61
|
+
`<title>` from `index.html` — a critical SEO gap.
|
|
62
|
+
|
|
63
|
+
### Added — Documentation
|
|
64
|
+
|
|
65
|
+
- **`docs/en/17-shadow-dom-forms.md`** — full recipe for using `useForm()` with
|
|
66
|
+
Shadow DOM components (proxy properties, form submit bridge, ctx.attr()).
|
|
67
|
+
- **`llms.txt`**: added `ctx.attr()` section, `apiFetcher` recipe, and Shadow
|
|
68
|
+
DOM + Forms guidance.
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
|
|
72
|
+
- **`x-button` in starters**: the disabled state was read once from
|
|
73
|
+
`host.hasAttribute("disabled")` in the render function — never updating when
|
|
74
|
+
the attribute changed externally. Every form using `?disabled` on `x-button`
|
|
75
|
+
was broken from the start.
|
|
76
|
+
- **`x-input` in starters**: `useForm().onInput` received `undefined` for
|
|
77
|
+
`name` and `value` because Shadow DOM retargets `e.target` from the inner
|
|
78
|
+
`<input>` to `<x-input>`, which had no DOM properties.
|
|
79
|
+
- **`jsonFetcher()`**: the admin starter relied on `jsonFetcher()` for protected
|
|
80
|
+
endpoints but it sends no Authorization header. Documented the pattern and
|
|
81
|
+
added `apiFetcher()`.
|
|
82
|
+
- **`mado serve`**: app-mode did not respect `--host`, `--port`, or config
|
|
83
|
+
settings. All flag pass-through now goes through `server/serve.mjs`.
|
|
84
|
+
- **`mado release`**: stale files from deleted bake routes or removed public
|
|
85
|
+
assets could remain in `out/`. Now cleans `out/` fully before building.
|
|
86
|
+
- **`mado bake`**: `<title>` was not set in baked HTML if only `page.title`
|
|
87
|
+
was defined (without `head().title`).
|
|
88
|
+
|
|
7
89
|
## 0.6.1
|
|
8
90
|
|
|
9
91
|
Starter & release-pipeline hardening pass. No public API breaks.
|
|
@@ -12,6 +94,7 @@ starter / bundle / bake / dev-server contour. All fixes verified by
|
|
|
12
94
|
regression tests added in this release.
|
|
13
95
|
|
|
14
96
|
### Fixed
|
|
97
|
+
|
|
15
98
|
- **Starters**: every `index.html` in `starters/{admin,crud,minimal}/` now
|
|
16
99
|
uses root-absolute paths in the importmap and entry `<script>` tag
|
|
17
100
|
(`/node_modules/@madojs/mado/...`, `/dist/main.js`). Relative paths
|
|
@@ -20,7 +103,7 @@ regression tests added in this release.
|
|
|
20
103
|
`/admin/orders/dist/main.js` → 404 → blank page). Inline comments in each
|
|
21
104
|
file explain the trap so it does not get reverted.
|
|
22
105
|
- **Starters/admin**: `pages/admin/order-detail.ts` now uses `each(items,
|
|
23
|
-
|
|
106
|
+
key, render)` instead of `o.items.map(...)`, matching `llms.txt` rule #3
|
|
24
107
|
and the framework's own pitfalls documentation.
|
|
25
108
|
- **`scripts/bundle.mjs`**: cleans stale hashed assets before every build.
|
|
26
109
|
Previously each `mado bundle` / `mado release` left old `main-<hash>.js`
|
|
@@ -57,6 +140,7 @@ regression tests added in this release.
|
|
|
57
140
|
had succeeded.
|
|
58
141
|
|
|
59
142
|
### Added
|
|
143
|
+
|
|
60
144
|
- **`mado dev` / `mado serve` flag pass-through**: `cli.mjs` now splits
|
|
61
145
|
positional arguments from flags via `splitDevArgs()`, so calls like
|
|
62
146
|
`mado dev --host 127.0.0.1`, `mado dev showcase --port 6000` and
|
|
@@ -71,7 +155,7 @@ regression tests added in this release.
|
|
|
71
155
|
- **`scripts/bake.mjs`**: fails loudly when the manifest exists but no
|
|
72
156
|
page declares `bake: { paths, data }`. The previous behaviour produced
|
|
73
157
|
`0 pages + sitemap.xml` silently with exit code 0, making `mado
|
|
74
|
-
|
|
158
|
+
release` look successful while shipping only the SPA shell with no
|
|
75
159
|
SEO-friendly HTML. The new warning prints the skipped routes, a
|
|
76
160
|
worked example bake snippet, and exits non-zero. Override with
|
|
77
161
|
`MADO_BAKE_ALLOW_EMPTY=1` for intentional SPA-only deploys.
|
|
@@ -99,12 +183,14 @@ regression tests added in this release.
|
|
|
99
183
|
response.
|
|
100
184
|
|
|
101
185
|
### Changed
|
|
186
|
+
|
|
102
187
|
- **`server/serve.mjs`** default host is now `localhost` (was implicitly
|
|
103
188
|
`0.0.0.0`). LAN exposure is opt-in via `mado dev --host 0.0.0.0` or
|
|
104
189
|
`HOST=0.0.0.0`. The startup banner shows both the bound host and a
|
|
105
190
|
click-friendly URL (`localhost` substituted when bound to `0.0.0.0`).
|
|
106
191
|
|
|
107
192
|
### Notes
|
|
193
|
+
|
|
108
194
|
- No public API changes; no migrations required. Apps that previously
|
|
109
195
|
worked on a fresh-out-of-the-box `mado init` did so only because
|
|
110
196
|
someone manually fixed the starter's relative paths and dev deps —
|
|
@@ -124,12 +210,13 @@ pipeline, core hardening and v1 recipe docs.
|
|
|
124
210
|
Phase 1 — Repo-vs-app split:
|
|
125
211
|
|
|
126
212
|
### Added
|
|
213
|
+
|
|
127
214
|
- `MADO_V1_PLAN.md` — executable tracker for the v1 push.
|
|
128
215
|
- `scripts/_config.mjs` — single configuration loader (defaults < `mado.config.json`
|
|
129
216
|
< CLI flags). Exports `loadConfig`, `detectContext`, `parseFlags`,
|
|
130
217
|
`resolveProjectPath`. [v1 F1.1]
|
|
131
218
|
- `mado release` command: one-shot `typecheck + build + bundle + bake + copy
|
|
132
|
-
|
|
219
|
+
public/ → out/` pipeline so apps have exactly one command to ship. [v1 F1.3]
|
|
133
220
|
- `mado.config.json` shipped in the `minimal` and `crud` starters with the
|
|
134
221
|
default app-mode layout (`src/routes.ts`, `index.html`, `out/`). [v1 F1.4]
|
|
135
222
|
- Tests: `test/config-loader.test.mjs`, `test/bake-cli.test.mjs` (11 + 3
|
|
@@ -137,6 +224,7 @@ Phase 1 — Repo-vs-app split:
|
|
|
137
224
|
flags, and the no-more-silent-`[object Object]` contract). [v1 F1.6]
|
|
138
225
|
|
|
139
226
|
### Changed
|
|
227
|
+
|
|
140
228
|
- `scripts/bake.mjs` now reads configuration from `mado.config.json` and
|
|
141
229
|
accepts `--entry`, `--template`, `--out`, `--base-url` flags. In app-mode
|
|
142
230
|
defaults are `src/routes.ts` + `index.html` + `out/baked/`; the
|
|
@@ -158,6 +246,7 @@ Phase 1 — Repo-vs-app split:
|
|
|
158
246
|
Phase 2 — One blessed way:
|
|
159
247
|
|
|
160
248
|
### Added
|
|
249
|
+
|
|
161
250
|
- `layout()` factory in `src/page.ts` (alias of `nested()`) plus `Guard` and
|
|
162
251
|
`GuardResult` types. Exported from the public API. [v1 F2.1 / F2.3]
|
|
163
252
|
- Route guards: nested groups and individual pages accept `guard: Guard | Guard[]`.
|
|
@@ -182,6 +271,7 @@ Phase 2 — One blessed way:
|
|
|
182
271
|
Phase 3 — Bake first-class + Release pipeline:
|
|
183
272
|
|
|
184
273
|
### Added
|
|
274
|
+
|
|
185
275
|
- `mado release` writes `_redirects` (`/* /index.html 200`) and `_headers`
|
|
186
276
|
(immutable for `/assets/*`, no-cache for HTML) into `out/` when they do not
|
|
187
277
|
exist, so Cloudflare Pages / Netlify deploys "just work". [v1 F3.7]
|
|
@@ -195,6 +285,7 @@ Phase 3 — Bake first-class + Release pipeline:
|
|
|
195
285
|
and copied `public/` assets are all present. [v1 F3.10]
|
|
196
286
|
|
|
197
287
|
### Changed
|
|
288
|
+
|
|
198
289
|
- `scripts/preview.mjs` now reads `mado.config.json` (`build.out`, `dev.port`),
|
|
199
290
|
refuses to auto-build by default in app-mode, and asks the user to run
|
|
200
291
|
`mado release` first. Legacy auto-build is opt-in via `PREVIEW_AUTOBUILD=1`
|
|
@@ -209,6 +300,7 @@ Phase 3 — Bake first-class + Release pipeline:
|
|
|
209
300
|
dependencies. The startup banner prints the active proxy table. [v1 F3.6]
|
|
210
301
|
|
|
211
302
|
### Deferred to v0.7
|
|
303
|
+
|
|
212
304
|
- `mado dev` does not yet serve baked routes inline. Workaround: run
|
|
213
305
|
`mado release && mado preview`. [v1 F3.2]
|
|
214
306
|
- `mado check` (bake-safety scan over `bake:` routes) is not exposed yet.
|
|
@@ -218,6 +310,7 @@ Phase 3 — Bake first-class + Release pipeline:
|
|
|
218
310
|
Phase 4 — Core hardening:
|
|
219
311
|
|
|
220
312
|
### Added
|
|
313
|
+
|
|
221
314
|
- `computed(fn, { equals })` option to suppress subscriber reruns when an
|
|
222
315
|
observed computed recomputes to an equal value. [v1 F4.3]
|
|
223
316
|
- HTML directives: `unsafeHTML()`, `ref()`, `classMap()` and `styleMap()` are
|
|
@@ -239,6 +332,7 @@ Phase 4 — Core hardening:
|
|
|
239
332
|
its regression tests. [v1 F4.9]
|
|
240
333
|
|
|
241
334
|
### Changed
|
|
335
|
+
|
|
242
336
|
- `computed()` now releases dependency subscriptions after unobserved reads and
|
|
243
337
|
after the last subscriber is disposed, avoiding long-lived stale subscriptions
|
|
244
338
|
in the signal graph. [v1 F4.1]
|
|
@@ -257,6 +351,7 @@ Phase 4 — Core hardening:
|
|
|
257
351
|
Phase 5 — Documentation:
|
|
258
352
|
|
|
259
353
|
### Added
|
|
354
|
+
|
|
260
355
|
- `docs/en/10-app-architecture.md`, `14-testing.md`, `15-error-handling.md`
|
|
261
356
|
and `16-bake-cookbook.md` complete the v1 English recipe set. [v1 F5.1-F5.4]
|
|
262
357
|
- `AGENTS.md` now includes an "App architecture for LLM" section and `llms.txt`
|
package/dist/src/component.d.ts
CHANGED
|
@@ -15,13 +15,25 @@
|
|
|
15
15
|
* Shadow DOM (open) is used by default. It can be disabled, and
|
|
16
16
|
* styles will be scoped via @scope (or a tag-prefix fallback).
|
|
17
17
|
*/
|
|
18
|
-
import { type Disposer } from "./signal.js";
|
|
18
|
+
import { type Signal, type Disposer } from "./signal.js";
|
|
19
19
|
import { html, type TemplateResult } from "./html.js";
|
|
20
20
|
import { type CSSResult } from "./css.js";
|
|
21
21
|
export interface ComponentContext {
|
|
22
22
|
host: HTMLElement;
|
|
23
23
|
/** Run cleanup when the component is removed. */
|
|
24
24
|
onDispose(fn: Disposer): void;
|
|
25
|
+
/**
|
|
26
|
+
* Reactive attribute accessor. Returns a Signal<string> that updates
|
|
27
|
+
* automatically whenever the attribute changes on the host element.
|
|
28
|
+
*
|
|
29
|
+
* const variant = ctx.attr("variant", "primary");
|
|
30
|
+
* return () => html`<div class=${variant()}>…</div>`;
|
|
31
|
+
*
|
|
32
|
+
* No MutationObserver boilerplate needed — the signal updates via
|
|
33
|
+
* attributeChangedCallback. The attribute name is automatically added to
|
|
34
|
+
* observedAttributes if not already listed.
|
|
35
|
+
*/
|
|
36
|
+
attr(name: string, defaultValue?: string): Signal<string>;
|
|
25
37
|
}
|
|
26
38
|
export type SetupFn = (ctx: ComponentContext) => () => TemplateResult;
|
|
27
39
|
export type StyleInput = string | CSSResult | Array<string | CSSResult>;
|
|
@@ -38,9 +50,10 @@ export interface ComponentOptions {
|
|
|
38
50
|
/**
|
|
39
51
|
* List of observed attributes.
|
|
40
52
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
53
|
+
* Attributes listed here are reflected to host[attr] via
|
|
54
|
+
* attributeChangedCallback and also power ctx.attr() reactive signals.
|
|
55
|
+
* You only need to list them here if you use the legacy property reflection
|
|
56
|
+
* pattern. ctx.attr() automatically registers any attribute it tracks.
|
|
44
57
|
*/
|
|
45
58
|
observedAttributes?: readonly string[];
|
|
46
59
|
}
|
package/dist/src/component.js
CHANGED
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
* Shadow DOM (open) is used by default. It can be disabled, and
|
|
16
16
|
* styles will be scoped via @scope (or a tag-prefix fallback).
|
|
17
17
|
*/
|
|
18
|
-
import { effect } from "./signal.js";
|
|
18
|
+
import { signal, effect } from "./signal.js";
|
|
19
19
|
import { html, render } from "./html.js";
|
|
20
20
|
import { adopt, scopeStyles } from "./css.js";
|
|
21
|
-
import { createLifecycle, runInLifecycle } from "./lifecycle.js";
|
|
21
|
+
import { createLifecycle, runInLifecycle, } from "./lifecycle.js";
|
|
22
22
|
import { warnOnce } from "./diagnostics.js";
|
|
23
23
|
export function component(tagName, setup, options = {}) {
|
|
24
24
|
if (!tagName.includes("-")) {
|
|
@@ -36,18 +36,22 @@ export function component(tagName, setup, options = {}) {
|
|
|
36
36
|
}
|
|
37
37
|
const useShadow = options.shadow !== false;
|
|
38
38
|
const observed = options.observedAttributes ?? [];
|
|
39
|
+
// Collect attribute names that ctx.attr() will track. These are merged with
|
|
40
|
+
// options.observedAttributes to form the final static observedAttributes.
|
|
41
|
+
const attrSignalNames = new Set(observed);
|
|
39
42
|
// Normalize styles to an array of CSSStyleSheet once.
|
|
40
43
|
// Sheets are shared across all instances — memory is not duplicated.
|
|
41
44
|
const stylesheets = normalizeStyles(options.styles, tagName, useShadow);
|
|
42
45
|
class MadoElement extends HTMLElement {
|
|
43
46
|
static get observedAttributes() {
|
|
44
|
-
return [...
|
|
47
|
+
return [...attrSignalNames];
|
|
45
48
|
}
|
|
46
49
|
#root;
|
|
47
50
|
#renderer = null;
|
|
48
51
|
#effectDispose = null;
|
|
49
52
|
#lifecycle = null;
|
|
50
53
|
#connected = false;
|
|
54
|
+
#attrSignals = new Map();
|
|
51
55
|
constructor() {
|
|
52
56
|
super();
|
|
53
57
|
this.#root = useShadow ? this.attachShadow({ mode: "open" }) : this;
|
|
@@ -69,12 +73,25 @@ export function component(tagName, setup, options = {}) {
|
|
|
69
73
|
// getCurrentLifecycle() and register its own cleanup.
|
|
70
74
|
const lifecycle = createLifecycle();
|
|
71
75
|
this.#lifecycle = lifecycle;
|
|
76
|
+
const host = this;
|
|
72
77
|
const ctx = {
|
|
73
78
|
host: this,
|
|
74
79
|
// ctx.onDispose proxies to lifecycle — the single source of truth
|
|
75
80
|
// for component cleanups (including auto-cleanup from
|
|
76
81
|
// resource(), navigator listeners, etc.).
|
|
77
82
|
onDispose: (fn) => lifecycle.onDispose(fn),
|
|
83
|
+
attr(name, defaultValue = "") {
|
|
84
|
+
let s = host.#attrSignals.get(name);
|
|
85
|
+
if (!s) {
|
|
86
|
+
s = signal(host.getAttribute(name) ?? defaultValue);
|
|
87
|
+
host.#attrSignals.set(name, s);
|
|
88
|
+
// Ensure the attribute is observed. For the current instance
|
|
89
|
+
// we already have the signal; for future registrations of
|
|
90
|
+
// the same tag (HMR) the set persists.
|
|
91
|
+
attrSignalNames.add(name);
|
|
92
|
+
}
|
|
93
|
+
return s;
|
|
94
|
+
},
|
|
78
95
|
};
|
|
79
96
|
this.#renderer = runInLifecycle(lifecycle, () => setup(ctx));
|
|
80
97
|
this.#effectDispose = effect(() => {
|
|
@@ -89,7 +106,12 @@ export function component(tagName, setup, options = {}) {
|
|
|
89
106
|
this.#connected = false;
|
|
90
107
|
}
|
|
91
108
|
attributeChangedCallback(name, _old, value) {
|
|
92
|
-
//
|
|
109
|
+
// Update ctx.attr() signal if it exists for this attribute.
|
|
110
|
+
const s = this.#attrSignals.get(name);
|
|
111
|
+
if (s) {
|
|
112
|
+
s.set(value ?? "");
|
|
113
|
+
}
|
|
114
|
+
// Legacy reflection: reflect attribute to property.
|
|
93
115
|
this[name] = value;
|
|
94
116
|
}
|
|
95
117
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/component.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/component.ts"],"names":[],"mappings":"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"}
|