@madojs/mado 0.12.0 → 0.13.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 +15 -8
- package/CHANGELOG.md +58 -0
- package/README.md +25 -3
- package/dist/src/component.d.ts +3 -1
- package/dist/src/component.d.ts.map +1 -0
- package/dist/src/component.js +65 -30
- package/dist/src/component.js.map +1 -1
- package/dist/src/context.d.ts +13 -32
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +73 -48
- package/dist/src/context.js.map +1 -1
- package/dist/src/css.d.ts +1 -0
- package/dist/src/css.d.ts.map +1 -0
- package/dist/src/css.js.map +1 -1
- package/dist/src/devtools-hook.d.ts +14 -0
- package/dist/src/devtools-hook.d.ts.map +1 -0
- package/dist/src/devtools-hook.js +27 -0
- package/dist/src/devtools-hook.js.map +1 -0
- package/dist/src/devtools.d.ts +24 -22
- package/dist/src/devtools.d.ts.map +1 -0
- package/dist/src/devtools.js +230 -58
- package/dist/src/devtools.js.map +1 -1
- package/dist/src/diagnostics.d.ts +12 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +68 -8
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/each.d.ts +1 -5
- package/dist/src/each.d.ts.map +1 -0
- package/dist/src/each.js +0 -8
- package/dist/src/each.js.map +1 -1
- package/dist/src/forms.d.ts +27 -96
- package/dist/src/forms.d.ts.map +1 -0
- package/dist/src/forms.js +184 -394
- package/dist/src/forms.js.map +1 -1
- package/dist/src/head.d.ts +1 -0
- package/dist/src/head.d.ts.map +1 -0
- package/dist/src/head.js +2 -1
- package/dist/src/head.js.map +1 -1
- package/dist/src/html/bindings.d.ts +1 -0
- package/dist/src/html/bindings.d.ts.map +1 -0
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html/parser.d.ts +3 -4
- package/dist/src/html/parser.d.ts.map +1 -0
- package/dist/src/html/parser.js +63 -14
- package/dist/src/html/parser.js.map +1 -1
- package/dist/src/html/template-types.d.ts +1 -0
- package/dist/src/html/template-types.d.ts.map +1 -0
- package/dist/src/html/template-types.js.map +1 -1
- package/dist/src/html/template.d.ts +5 -1
- package/dist/src/html/template.d.ts.map +1 -0
- package/dist/src/html/template.js +78 -3
- package/dist/src/html/template.js.map +1 -1
- package/dist/src/index.d.ts +7 -8
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +5 -7
- package/dist/src/index.js.map +1 -1
- package/dist/src/json.d.ts +3 -0
- package/dist/src/json.d.ts.map +1 -0
- package/dist/src/json.js +14 -0
- package/dist/src/json.js.map +1 -0
- package/dist/src/lifecycle.d.ts +1 -0
- package/dist/src/lifecycle.d.ts.map +1 -0
- package/dist/src/lifecycle.js +3 -2
- package/dist/src/lifecycle.js.map +1 -1
- package/dist/src/page.d.ts +1 -0
- package/dist/src/page.d.ts.map +1 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/persisted.d.ts +6 -1
- package/dist/src/persisted.d.ts.map +1 -0
- package/dist/src/persisted.js +17 -11
- package/dist/src/persisted.js.map +1 -1
- package/dist/src/resource.d.ts +2 -1
- package/dist/src/resource.d.ts.map +1 -0
- package/dist/src/resource.js +126 -49
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/base.d.ts +1 -0
- package/dist/src/router/base.d.ts.map +1 -0
- package/dist/src/router/base.js.map +1 -1
- package/dist/src/router/manifest.d.ts +1 -0
- package/dist/src/router/manifest.d.ts.map +1 -0
- package/dist/src/router/manifest.js +89 -148
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +1 -0
- package/dist/src/router/match.d.ts.map +1 -0
- package/dist/src/router/match.js +19 -5
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.d.ts +1 -0
- package/dist/src/router/navigation.d.ts.map +1 -0
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/signal.d.ts +1 -0
- package/dist/src/signal.d.ts.map +1 -0
- package/dist/src/signal.js +46 -17
- package/dist/src/signal.js.map +1 -1
- package/dist/src/static-runtime.d.ts +1 -0
- package/dist/src/static-runtime.d.ts.map +1 -0
- package/dist/src/static-runtime.js.map +1 -1
- package/dist/src/vite/index.d.ts +1 -0
- package/dist/src/vite/index.d.ts.map +1 -0
- package/dist/src/vite/index.js +4 -3
- package/dist/src/vite/index.js.map +1 -1
- package/docs/architecture/v1-roadmap.md +19 -0
- package/docs/en/10-pages-and-components.md +9 -1
- package/docs/en/13-data.md +8 -6
- package/docs/en/14-forms.md +66 -219
- package/docs/en/15-static-snapshots.md +4 -4
- package/docs/en/16-app-architecture.md +3 -2
- package/docs/en/20-deployment.md +21 -8
- package/docs/en/21-error-handling.md +9 -6
- package/docs/en/24-devtools-and-diagnostics.md +45 -0
- package/docs/en/30-api-freeze-map.md +8 -9
- package/docs/en/32-v1-stability.md +2 -2
- package/docs/en/33-migration-0.12-0.13.md +56 -0
- package/docs/en/40-llm-guide.md +7 -9
- package/docs/en/41-for-backenders.md +13 -9
- package/docs/en/42-why-mado.md +2 -2
- package/docs/en/README.md +2 -0
- package/docs/recipes/nginx/nginx.conf +2 -2
- package/llms.txt +25 -7
- package/package.json +37 -16
- package/scripts/clean.mjs +8 -0
- package/scripts/cli/generate.mjs +11 -14
- package/scripts/cli/help.mjs +2 -0
- package/scripts/cli/index.mjs +14 -6
- package/scripts/cli/init.mjs +13 -18
- package/scripts/cli/release.mjs +22 -19
- package/scripts/cli/run.mjs +5 -3
- package/scripts/logger.mjs +117 -0
- package/scripts/output-guard.mjs +128 -0
- package/scripts/preview.mjs +11 -19
- package/scripts/static/browser.mjs +49 -8
- package/scripts/static/discover.mjs +4 -1
- package/scripts/static.mjs +21 -7
- package/starters/default/package.json +4 -4
- package/starters/default/tsconfig.node.json +13 -0
- package/starters/default/vite.config.ts +0 -3
- package/starters/modular/README.md +4 -4
- package/starters/modular/package.json +4 -4
- package/starters/modular/src/layouts/app-shell.layout.ts +1 -2
- package/starters/modular/src/modules/auth/login.page.ts +5 -12
- package/starters/modular/src/modules/billing/pages/invoice-detail.page.ts +3 -4
- package/starters/modular/src/modules/billing/pages/invoices-list.page.ts +1 -1
- package/starters/modular/src/shared/styles/reset.css +21 -1
- package/starters/modular/tsconfig.json +10 -1
- package/starters/modular/tsconfig.node.json +13 -0
- package/starters/modular/vite.config.ts +6 -5
- package/dist/src/lazy.d.ts +0 -38
- package/dist/src/lazy.js +0 -73
- package/dist/src/lazy.js.map +0 -1
- package/scripts/bake.mjs +0 -5
- package/scripts/docs-lint.mjs +0 -170
- package/scripts/package-smoke.mjs +0 -135
- package/scripts/release-notes.mjs +0 -66
- package/scripts/size-budget.mjs +0 -95
- package/starters/modular/src/shared/ui/x-button.component.ts +0 -49
package/AGENTS.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
- **Mado** — a calm native-first web framework for both static sites
|
|
11
11
|
and live SPAs. One Web Component model, one page model, one release
|
|
12
12
|
command.
|
|
13
|
-
- Current
|
|
13
|
+
- Current development release: **0.13.0**.
|
|
14
14
|
- Built on Web Components + signals + tagged-template `html`.
|
|
15
15
|
- **Vite is the canonical transport** for development, build and the
|
|
16
16
|
static snapshot pipeline. Generated apps depend on `typescript`,
|
|
@@ -301,8 +301,9 @@ const { register, handleSubmit } = useForm({ resolver: yupResolver(schema) });
|
|
|
301
301
|
import { useForm } from "@madojs/mado";
|
|
302
302
|
|
|
303
303
|
const f = useForm({
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
initial: { email: "", age: "" as number | "" },
|
|
305
|
+
validate: async (values, { signal }) =>
|
|
306
|
+
await api.valid(values, { signal }) ? null : { email: "Unavailable" },
|
|
306
307
|
});
|
|
307
308
|
|
|
308
309
|
html`
|
|
@@ -311,7 +312,8 @@ html`
|
|
|
311
312
|
await api.save(v);
|
|
312
313
|
})}
|
|
313
314
|
>
|
|
314
|
-
<input name="email" @input=${f.onInput} @blur=${f.onBlur} />
|
|
315
|
+
<input name="email" type="email" required @input=${f.onInput} @blur=${f.onBlur} />
|
|
316
|
+
<input name="age" type="number" min="18" @input=${f.onInput} />
|
|
315
317
|
${() =>
|
|
316
318
|
f.touched().email && f.errors().email
|
|
317
319
|
? html`<small>${f.errors().email}</small>`
|
|
@@ -321,7 +323,8 @@ html`
|
|
|
321
323
|
`;
|
|
322
324
|
```
|
|
323
325
|
|
|
324
|
-
|
|
326
|
+
HTML owns native constraints. Custom validation receives an `AbortSignal`.
|
|
327
|
+
Use `setField`, not a schema/field-array abstraction.
|
|
325
328
|
|
|
326
329
|
### 11. Styles — `css\`\`` + Shadow DOM by default
|
|
327
330
|
|
|
@@ -368,6 +371,10 @@ component("x-child", ({ host }) => {
|
|
|
368
371
|
});
|
|
369
372
|
```
|
|
370
373
|
|
|
374
|
+
This interoperates through the Web Components `context-request` protocol.
|
|
375
|
+
Providers may expose function values; only real Mado Signals are treated as
|
|
376
|
+
reactive sources. Subscriptions are removed with the active lifecycle.
|
|
377
|
+
|
|
371
378
|
### 13. Component registration imports
|
|
372
379
|
|
|
373
380
|
Custom elements are global after registration, but the browser never imports a
|
|
@@ -517,11 +524,11 @@ When generating an app, prefer the blessed production shape from
|
|
|
517
524
|
|
|
518
525
|
| Question | File |
|
|
519
526
|
| -------------------------------- | -------------------------------- |
|
|
520
|
-
| How does reactivity work? | `src/signal.ts`
|
|
527
|
+
| How does reactivity work? | `src/signal.ts` |
|
|
521
528
|
| How are templates parsed? | `src/html/` |
|
|
522
529
|
| How does the router work? | `src/router/` |
|
|
523
|
-
| How does resource + cache work? | `src/resource.ts`
|
|
524
|
-
| How do forms work? | `src/forms.ts`
|
|
530
|
+
| How does resource + cache work? | `src/resource.ts` |
|
|
531
|
+
| How do forms work? | `src/forms.ts` |
|
|
525
532
|
| How should an app be structured? | `docs/en/16-app-architecture.md` |
|
|
526
533
|
| How should errors be handled? | `docs/en/21-error-handling.md` |
|
|
527
534
|
| How should static snapshots be used? | `docs/en/15-static-snapshots.md` / `docs/en/23-cookbook.md` |
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0 - 2026-07-11
|
|
4
|
+
|
|
5
|
+
The final planned pre-v1 breaking release hardens ownership and freezes the
|
|
6
|
+
small platform-first API.
|
|
7
|
+
|
|
8
|
+
### Breaking
|
|
9
|
+
|
|
10
|
+
- Forms now use `useForm({ initial, validate? })`; native controls and
|
|
11
|
+
`ValidityState` own constraints. The schema and field-array APIs are removed.
|
|
12
|
+
- The root surface drops `lazy`, `list` and accidental low-level exports. The
|
|
13
|
+
only public subpaths are the package root, `/vite` and `/devtools.js`.
|
|
14
|
+
- `page.load` is synchronous and returns a value or `Resource`; Promises are
|
|
15
|
+
rejected with a targeted diagnostic.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `render()` returns a disposer and public `unmount(container)` releases owned
|
|
20
|
+
bindings, events, effects and nested templates.
|
|
21
|
+
- Structured browser and CLI diagnostics, including JSON CLI output, log-level
|
|
22
|
+
controls and `NO_COLOR` support.
|
|
23
|
+
- A development-only Shadow DOM devtools overlay with reactivity, component,
|
|
24
|
+
router/data and timeline/error views, plus an exported controller.
|
|
25
|
+
- The Web Components `context-request` protocol with subscriptions and
|
|
26
|
+
lifecycle cleanup.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Destructive output paths are realpath-checked and cleaned only when owned by
|
|
31
|
+
a `.mado-output` sentinel.
|
|
32
|
+
- JSON-LD/static seed serialization escapes script-breaking input.
|
|
33
|
+
- Effect, component, render and navigation teardown is transactional and
|
|
34
|
+
idempotent, including initial failures and same-tick DOM moves.
|
|
35
|
+
- Route literals are escaped, template markers are opaque and deterministic,
|
|
36
|
+
unsupported self-closing/template-child shapes fail early.
|
|
37
|
+
- Resource cache identity includes the fetcher; `staleTime: 0`, expiry,
|
|
38
|
+
invalidation, refresh and concurrent mutation generations have defined
|
|
39
|
+
semantics. Persisted signal disposal no longer deletes storage implicitly.
|
|
40
|
+
- Static capture blocks undeclared external requests, preserves form/focus
|
|
41
|
+
takeover state, runs bounded route capture and emits portable build metadata.
|
|
42
|
+
- `mado build` and published route discovery now feed the standalone static
|
|
43
|
+
pipeline correctly.
|
|
44
|
+
|
|
45
|
+
### Tooling and release
|
|
46
|
+
|
|
47
|
+
- Framework/default starter use TypeScript 7; the ESLint modular starter stays
|
|
48
|
+
on TypeScript 6. Node 22.12+ and Vite 8 are the supported baseline.
|
|
49
|
+
- Lightning CSS was removed from starters in favour of Vite's standard CSS
|
|
50
|
+
pipeline.
|
|
51
|
+
- Reproducible tarballs, inline source maps, API golden files, compiled docs
|
|
52
|
+
snippets and a single `verify:release` gate were added.
|
|
53
|
+
- CI covers Node 22/24, Chromium/Firefox/WebKit modular flows and Windows
|
|
54
|
+
package smoke. Runtime and devtools bundles have separate size budgets.
|
|
55
|
+
- Windows package smoke invokes `npm-cli.js` through the active `node.exe`
|
|
56
|
+
instead of relying on `npm`/`npx` command shims, and launches preview
|
|
57
|
+
directly so CI does not leak a nested process tree.
|
|
58
|
+
|
|
59
|
+
See [the migration guide](./docs/en/33-migration-0.12-0.13.md).
|
|
60
|
+
|
|
3
61
|
## 0.12.0 - 2026-06-24
|
|
4
62
|
|
|
5
63
|
### Stable polish
|
package/README.md
CHANGED
|
@@ -181,17 +181,25 @@ const save = mutation(api.saveUser, {
|
|
|
181
181
|
Cache, loading/error state, abort, refresh, optimistic `mutate()`,
|
|
182
182
|
glob-based invalidation. Lifecycle-aware inside components.
|
|
183
183
|
|
|
184
|
-
### Forms —
|
|
184
|
+
### Forms — browser constraint validation
|
|
185
185
|
|
|
186
186
|
```ts
|
|
187
187
|
import { useForm, html } from "@madojs/mado";
|
|
188
188
|
|
|
189
189
|
const form = useForm({
|
|
190
|
-
|
|
191
|
-
age: { type: "number", min: 18 },
|
|
190
|
+
initial: { email: "", age: "" as number | "" },
|
|
192
191
|
});
|
|
192
|
+
|
|
193
|
+
html`<form @submit=${form.onSubmit(save)}>
|
|
194
|
+
<input name="email" type="email" required @input=${form.onInput} />
|
|
195
|
+
<input name="age" type="number" min="18" @input=${form.onInput} />
|
|
196
|
+
<button type="submit">Save</button>
|
|
197
|
+
</form>`;
|
|
193
198
|
```
|
|
194
199
|
|
|
200
|
+
HTML owns constraints and keyboard/form semantics; Mado supplies typed values,
|
|
201
|
+
errors, touched/dirty state and abortable async validation.
|
|
202
|
+
|
|
195
203
|
### Static snapshots — SEO without SSR
|
|
196
204
|
|
|
197
205
|
```bash
|
|
@@ -224,6 +232,20 @@ mado preview # serve out/ like a real static host
|
|
|
224
232
|
mado new <kind> <path> # scaffold canonical files
|
|
225
233
|
```
|
|
226
234
|
|
|
235
|
+
All CLI records share `level`, `scope`, `code`, `message`, `data` and a
|
|
236
|
+
timestamp. Use `--log-level`, `--log-format=pretty|plain|json`,
|
|
237
|
+
`MADO_LOG_LEVEL`, `MADO_LOG_FORMAT` or `NO_COLOR` for automation.
|
|
238
|
+
|
|
239
|
+
## Devtools
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
import { devtools } from "@madojs/mado/devtools.js";
|
|
243
|
+
devtools.open();
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The development-only Shadow DOM overlay is toggled with `Alt+Shift+M` and
|
|
247
|
+
inspects reactivity, components, routing, data and structured diagnostics.
|
|
248
|
+
|
|
227
249
|
## Honest boundaries
|
|
228
250
|
|
|
229
251
|
- No server renderer.
|
package/dist/src/component.d.ts
CHANGED
|
@@ -34,7 +34,8 @@ export interface ComponentContext {
|
|
|
34
34
|
* No MutationObserver boilerplate needed. The signal updates via a
|
|
35
35
|
* per-instance MutationObserver registered during setup().
|
|
36
36
|
*/
|
|
37
|
-
attr(name: string
|
|
37
|
+
attr(name: string): Signal<string | null>;
|
|
38
|
+
attr(name: string, defaultValue: string): Signal<string>;
|
|
38
39
|
}
|
|
39
40
|
export type SetupFn = (ctx: ComponentContext) => () => TemplateResult;
|
|
40
41
|
export type StyleInput = string | CSSResult | Array<string | CSSResult>;
|
|
@@ -51,3 +52,4 @@ export interface ComponentOptions {
|
|
|
51
52
|
}
|
|
52
53
|
export declare function component(tagName: string, setup: SetupFn, options?: ComponentOptions): void;
|
|
53
54
|
export { html };
|
|
55
|
+
//# sourceMappingURL=component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/component.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAkB,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,IAAI,EAAmB,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,UAAU,CAAC;AAyFlB,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,iDAAiD;IACjD,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC9B;;;;;;;;;OASG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;CAC1D;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,gBAAgB,KAAK,MAAM,cAAc,CAAC;AAEtE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;AAExE,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,gBAAqB,GAC7B,IAAI,CAmMN;AA8CD,OAAO,EAAE,IAAI,EAAE,CAAC"}
|
package/dist/src/component.js
CHANGED
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
* (or a tag-prefix fallback).
|
|
18
18
|
*/
|
|
19
19
|
import { signal, effect } from "./signal.js";
|
|
20
|
-
import { html, render } from "./html/template.js";
|
|
20
|
+
import { html, render, unmount } from "./html/template.js";
|
|
21
21
|
import { adopt, createStylesheet, scopeStyles, } from "./css.js";
|
|
22
22
|
import { createLifecycle, runInLifecycle, } from "./lifecycle.js";
|
|
23
23
|
import { warnOnce } from "./diagnostics.js";
|
|
24
|
+
import { emitDevtools } from "./devtools-hook.js";
|
|
24
25
|
/**
|
|
25
26
|
* Components upgraded inside a server-side or build-time snapshot live
|
|
26
27
|
* temporarily under `<#app data-mado-static>`. Their setup() is intentionally
|
|
@@ -151,7 +152,8 @@ export function component(tagName, setup, options = {}) {
|
|
|
151
152
|
_markDeferredForStatic(this);
|
|
152
153
|
return;
|
|
153
154
|
}
|
|
154
|
-
|
|
155
|
+
if (typeof __MADO_DEVTOOLS__ === "undefined" || __MADO_DEVTOOLS__)
|
|
156
|
+
emitDevtools("component:connect", this, { tagName });
|
|
155
157
|
if (stylesheets.length > 0) {
|
|
156
158
|
if (useShadow) {
|
|
157
159
|
adopt(this.#root, ...stylesheets);
|
|
@@ -164,43 +166,70 @@ export function component(tagName, setup, options = {}) {
|
|
|
164
166
|
// called from setup() (resource, ...) will see it via
|
|
165
167
|
// getCurrentLifecycle() and register its own cleanup.
|
|
166
168
|
const lifecycle = createLifecycle();
|
|
167
|
-
this.#lifecycle = lifecycle;
|
|
168
169
|
const host = this;
|
|
170
|
+
const attr = ((name, defaultValue) => {
|
|
171
|
+
let s = host.#attrSignals.get(name);
|
|
172
|
+
if (!s) {
|
|
173
|
+
const fallback = defaultValue ?? null;
|
|
174
|
+
host.#attrDefaults.set(name, fallback);
|
|
175
|
+
s = signal(host.getAttribute(name) ?? fallback);
|
|
176
|
+
host.#attrSignals.set(name, s);
|
|
177
|
+
}
|
|
178
|
+
return s;
|
|
179
|
+
});
|
|
169
180
|
const ctx = {
|
|
170
181
|
host: this,
|
|
171
182
|
// ctx.onDispose proxies to lifecycle — the single source of truth
|
|
172
183
|
// for component cleanups (including auto-cleanup from
|
|
173
184
|
// resource(), navigator listeners, etc.).
|
|
174
185
|
onDispose: (fn) => lifecycle.onDispose(fn),
|
|
175
|
-
attr
|
|
176
|
-
let s = host.#attrSignals.get(name);
|
|
177
|
-
if (!s) {
|
|
178
|
-
host.#attrDefaults.set(name, defaultValue);
|
|
179
|
-
s = signal(host.getAttribute(name) ?? defaultValue);
|
|
180
|
-
host.#attrSignals.set(name, s);
|
|
181
|
-
}
|
|
182
|
-
return s;
|
|
183
|
-
},
|
|
186
|
+
attr,
|
|
184
187
|
};
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
188
|
+
try {
|
|
189
|
+
const renderer = runInLifecycle(lifecycle, () => setup(ctx));
|
|
190
|
+
if (typeof renderer !== "function") {
|
|
191
|
+
throw new TypeError(`component("${tagName}") setup must return a renderer function`);
|
|
192
|
+
}
|
|
193
|
+
// After setup(), install a single MutationObserver for all attrs
|
|
194
|
+
// registered via ctx.attr().
|
|
195
|
+
if (this.#attrSignals.size > 0) {
|
|
196
|
+
const attrNames = [...this.#attrSignals.keys()];
|
|
197
|
+
const obs = new MutationObserver((mutations) => {
|
|
198
|
+
for (const m of mutations) {
|
|
199
|
+
const s = this.#attrSignals.get(m.attributeName);
|
|
200
|
+
const fallback = this.#attrDefaults.get(m.attributeName) ?? null;
|
|
201
|
+
if (s)
|
|
202
|
+
s.set(this.getAttribute(m.attributeName) ?? fallback);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
obs.observe(this, { attributes: true, attributeFilter: attrNames });
|
|
206
|
+
lifecycle.onDispose(() => obs.disconnect());
|
|
207
|
+
}
|
|
208
|
+
const effectDispose = runInLifecycle(lifecycle, () => effect(() => {
|
|
209
|
+
if (typeof __MADO_DEVTOOLS__ === "undefined" || __MADO_DEVTOOLS__)
|
|
210
|
+
emitDevtools("component:render", this, { tagName });
|
|
211
|
+
render(renderer(), this.#root);
|
|
212
|
+
}));
|
|
213
|
+
this.#renderer = renderer;
|
|
214
|
+
this.#effectDispose = effectDispose;
|
|
215
|
+
this.#lifecycle = lifecycle;
|
|
216
|
+
this.#connected = true;
|
|
217
|
+
if (typeof __MADO_DEVTOOLS__ === "undefined" || __MADO_DEVTOOLS__)
|
|
218
|
+
emitDevtools("component:ready", this, { tagName });
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
unmount(this.#root);
|
|
222
|
+
lifecycle.dispose();
|
|
223
|
+
this.#renderer = null;
|
|
224
|
+
this.#effectDispose = null;
|
|
225
|
+
this.#lifecycle = null;
|
|
226
|
+
this.#attrSignals.clear();
|
|
227
|
+
this.#attrDefaults.clear();
|
|
228
|
+
this.#connected = false;
|
|
229
|
+
if (typeof __MADO_DEVTOOLS__ === "undefined" || __MADO_DEVTOOLS__)
|
|
230
|
+
emitDevtools("component:error", this, { tagName, error: err });
|
|
231
|
+
throw err;
|
|
200
232
|
}
|
|
201
|
-
this.#effectDispose = effect(() => {
|
|
202
|
-
render(this.#renderer(), this.#root);
|
|
203
|
-
});
|
|
204
233
|
}
|
|
205
234
|
disconnectedCallback() {
|
|
206
235
|
// A deferred snapshot leaf was removed by takeover before setup() ever
|
|
@@ -226,10 +255,16 @@ export function component(tagName, setup, options = {}) {
|
|
|
226
255
|
}
|
|
227
256
|
/** Synchronously dispose effects and lifecycle. */
|
|
228
257
|
#teardown() {
|
|
258
|
+
if (typeof __MADO_DEVTOOLS__ === "undefined" || __MADO_DEVTOOLS__)
|
|
259
|
+
emitDevtools("component:dispose", this, { tagName });
|
|
229
260
|
this.#effectDispose?.();
|
|
230
261
|
this.#effectDispose = null;
|
|
262
|
+
unmount(this.#root);
|
|
231
263
|
this.#lifecycle?.dispose();
|
|
232
264
|
this.#lifecycle = null;
|
|
265
|
+
this.#renderer = null;
|
|
266
|
+
this.#attrSignals.clear();
|
|
267
|
+
this.#attrDefaults.clear();
|
|
233
268
|
this.#connected = false;
|
|
234
269
|
}
|
|
235
270
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/component.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/component.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wrapper around Custom Elements.\n *\n * component('x-counter', () => {\n * const count = signal(0);\n * return () => html`<button @click=${() => count.update(n=>n+1)}>${count}</button>`;\n * }, {\n * styles: css`button { padding: .5rem }`,\n * });\n *\n * The setup function is called once on the first connectedCallback.\n * The returned render function is called via an effect, so any signals\n * read inside it automatically re-render the template.\n *\n * Shadow DOM (open, serializable) is used by default. It can be disabled for\n * advanced integration cases, and styles will be scoped via @scope\n * (or a tag-prefix fallback).\n */\n\nimport { signal, effect, type Signal, type Disposer } from \"./signal.js\";\nimport { html, render, unmount } from \"./html/template.js\";\nimport type { TemplateResult } from \"./html/template-types.js\";\nimport {\n adopt,\n createStylesheet,\n scopeStyles,\n type CSSResult,\n} from \"./css.js\";\nimport {\n createLifecycle,\n runInLifecycle,\n type LifecycleHandle,\n} from \"./lifecycle.js\";\nimport { warnOnce } from \"./diagnostics.js\";\nimport { emitDevtools } from \"./devtools-hook.js\";\n\n/**\n * Components upgraded inside a server-side or build-time snapshot live\n * temporarily under `<#app data-mado-static>`. Their setup() is intentionally\n * NOT run yet: Mado replaces the snapshot tree atomically when the SPA\n * boots, and any work performed by setup() before takeover would leak.\n *\n * The set lets the runtime double-check, after takeover, that no deferred\n * element survives — see _flushDeferredStaticElements().\n */\nconst deferredStaticElements = new Set<HTMLElement>();\n\n/**\n * Walk composed ancestry (Light DOM parentNode plus ShadowRoot.host) and\n * return true when the element lives inside a tree marked with\n * `data-mado-static`. Used to defer component activation until the SPA\n * takeover atomically removes that tree.\n */\nfunction isInsideStaticTree(node: Node | null): boolean {\n let n: Node | null = node;\n while (n) {\n if (\n n instanceof Element &&\n (n as Element).hasAttribute &&\n (n as Element).hasAttribute(\"data-mado-static\")\n ) {\n return true;\n }\n const parent: Node | null =\n typeof ShadowRoot !== \"undefined\" && n instanceof ShadowRoot\n ? n.host\n : n.parentNode;\n n = parent;\n }\n return false;\n}\n\n/** @internal */\nexport function _markDeferredForStatic(host: HTMLElement): void {\n deferredStaticElements.add(host);\n}\n\n/** @internal */\nexport function _isDeferredForStatic(host: HTMLElement): boolean {\n return deferredStaticElements.has(host);\n}\n\n/**\n * Called by render() after a root takeover has atomically replaced the\n * `data-mado-static` tree. By that time every component that was\n * mid-snapshot must either be:\n * 1. removed with the static tree (the typical case), or\n * 2. still connected but no longer under a static ancestor — in which\n * case the SPA tree placed it live and we activate it now.\n * Any element that fails both checks is a stuck snapshot leaf; emit a\n * single diagnostic so the developer notices the orphan.\n *\n * @internal\n */\nexport function _flushDeferredStaticElements(): void {\n for (const el of [...deferredStaticElements]) {\n if (!el.isConnected) {\n deferredStaticElements.delete(el);\n continue;\n }\n if (!isInsideStaticTree(el)) {\n // Reset the guard so the live connectedCallback runs setup().\n deferredStaticElements.delete(el);\n // Force a clean re-enter of connectedCallback. Browsers fire that\n // callback exactly once per insertion, so we toggle the element out\n // and back in. Avoid this if the element is the document root.\n const parent = el.parentNode;\n const next = el.nextSibling;\n if (parent) {\n parent.removeChild(el);\n parent.insertBefore(el, next);\n }\n }\n }\n}\n\nexport interface ComponentContext {\n host: HTMLElement;\n /** Run cleanup when the component is removed. */\n onDispose(fn: Disposer): void;\n /**\n * Reactive attribute accessor. Returns a Signal<string> that updates\n * automatically whenever the attribute changes on the host element.\n *\n * const variant = ctx.attr(\"variant\", \"primary\");\n * return () => html`<div class=${variant()}>…</div>`;\n *\n * No MutationObserver boilerplate needed. The signal updates via a\n * per-instance MutationObserver registered during setup().\n */\n attr(name: string): Signal<string | null>;\n attr(name: string, defaultValue: string): Signal<string>;\n}\n\nexport type SetupFn = (ctx: ComponentContext) => () => TemplateResult;\n\nexport type StyleInput = string | CSSResult | Array<string | CSSResult>;\n\nexport interface ComponentOptions {\n /** Enable Shadow DOM (default: true). */\n shadow?: boolean;\n /**\n * Component styles. Accepts:\n * - a CSS string (quick start)\n * - a CSSStyleSheet via `css\\`...\\`` (recommended — one copy in memory)\n * - an array of the above\n */\n styles?: StyleInput;\n}\n\nexport function component(\n tagName: string,\n setup: SetupFn,\n options: ComponentOptions = {},\n): void {\n if (!tagName.includes(\"-\")) {\n warnOnce(\n `component-invalid-tag-${tagName}`,\n `component(\"${tagName}\") skipped: Custom Element names must contain a hyphen.`,\n );\n return;\n }\n\n const existingMeta = registered.get(tagName);\n if (customElements.get(tagName)) {\n if (\n !existingMeta ||\n existingMeta.setup !== setup ||\n !sameComponentOptions(existingMeta.options, options)\n ) {\n warnOnce(\n `component-duplicate-${tagName}`,\n `component(\"${tagName}\") is already registered. Re-registration with different setup/options is ignored.`,\n );\n }\n return; // idempotent hot-reload\n }\n\n const useShadow = options.shadow !== false;\n\n // Normalize styles to an array of CSSStyleSheet once.\n // Sheets are shared across all instances — memory is not duplicated.\n const stylesheets: CSSResult[] = normalizeStyles(\n options.styles,\n tagName,\n useShadow,\n );\n\n class MadoElement extends HTMLElement {\n #root: Element | ShadowRoot;\n #renderer: (() => TemplateResult) | null = null;\n #effectDispose: Disposer | null = null;\n #lifecycle: LifecycleHandle | null = null;\n #connected = false;\n // True while a teardown is queued on the microtask after a\n // disconnectedCallback. Used to cancel teardown when the element is\n // re-inserted in the same tick (a keyed move via insertBefore).\n #teardownQueued = false;\n #attrSignals = new Map<string, Signal<string | null>>();\n #attrDefaults = new Map<string, string | null>();\n\n constructor() {\n super();\n this.#root = useShadow\n ? this.shadowRoot ??\n this.attachShadow({\n mode: \"open\",\n serializable: true,\n } as ShadowRootInit)\n : this;\n }\n\n connectedCallback() {\n // A keyed move (each() relocating via insertBefore) fires\n // disconnectedCallback → connectedCallback synchronously. The teardown\n // queued by disconnectedCallback is cancelled here so state survives the\n // move and setup() is NOT re-run.\n this.#teardownQueued = false;\n if (this.#connected) return;\n\n // Deferred activation inside a static snapshot tree. The runtime keeps\n // setup()/effect() asleep until the SPA performs root takeover; the\n // existing DSD inside shadowRoot stays as inert first-paint markup.\n // When the SPA replaces the static root, this element is either\n // removed with that tree (the common case) or re-inserted live —\n // in which case connectedCallback() runs again and reaches setup().\n if (isInsideStaticTree(this)) {\n _markDeferredForStatic(this);\n return;\n }\n if (typeof __MADO_DEVTOOLS__ === \"undefined\" || __MADO_DEVTOOLS__) emitDevtools(\"component:connect\", this, { tagName });\n\n if (stylesheets.length > 0) {\n if (useShadow) {\n adopt(this.#root as ShadowRoot, ...stylesheets);\n } else {\n installGlobalSheets(stylesheets);\n }\n }\n\n // create a lifecycle for this component. Any function\n // called from setup() (resource, ...) will see it via\n // getCurrentLifecycle() and register its own cleanup.\n const lifecycle = createLifecycle();\n const host = this;\n\n const attr = ((name: string, defaultValue?: string) => {\n let s = host.#attrSignals.get(name);\n if (!s) {\n const fallback = defaultValue ?? null;\n host.#attrDefaults.set(name, fallback);\n s = signal(host.getAttribute(name) ?? fallback);\n host.#attrSignals.set(name, s);\n }\n return s;\n }) as ComponentContext[\"attr\"];\n\n const ctx: ComponentContext = {\n host: this,\n // ctx.onDispose proxies to lifecycle — the single source of truth\n // for component cleanups (including auto-cleanup from\n // resource(), navigator listeners, etc.).\n onDispose: (fn) => lifecycle.onDispose(fn),\n attr,\n };\n\n try {\n const renderer = runInLifecycle(lifecycle, () => setup(ctx));\n if (typeof renderer !== \"function\") {\n throw new TypeError(`component(\"${tagName}\") setup must return a renderer function`);\n }\n\n // After setup(), install a single MutationObserver for all attrs\n // registered via ctx.attr().\n if (this.#attrSignals.size > 0) {\n const attrNames = [...this.#attrSignals.keys()];\n const obs = new MutationObserver((mutations) => {\n for (const m of mutations) {\n const s = this.#attrSignals.get(m.attributeName!);\n const fallback = this.#attrDefaults.get(m.attributeName!) ?? null;\n if (s) s.set(this.getAttribute(m.attributeName!) ?? fallback);\n }\n });\n obs.observe(this, { attributes: true, attributeFilter: attrNames });\n lifecycle.onDispose(() => obs.disconnect());\n }\n\n const effectDispose = runInLifecycle(lifecycle, () => effect(() => {\n if (typeof __MADO_DEVTOOLS__ === \"undefined\" || __MADO_DEVTOOLS__) emitDevtools(\"component:render\", this, { tagName });\n render(renderer(), this.#root);\n }));\n this.#renderer = renderer;\n this.#effectDispose = effectDispose;\n this.#lifecycle = lifecycle;\n this.#connected = true;\n if (typeof __MADO_DEVTOOLS__ === \"undefined\" || __MADO_DEVTOOLS__) emitDevtools(\"component:ready\", this, { tagName });\n } catch (err) {\n unmount(this.#root);\n lifecycle.dispose();\n this.#renderer = null;\n this.#effectDispose = null;\n this.#lifecycle = null;\n this.#attrSignals.clear();\n this.#attrDefaults.clear();\n this.#connected = false;\n if (typeof __MADO_DEVTOOLS__ === \"undefined\" || __MADO_DEVTOOLS__) emitDevtools(\"component:error\", this, { tagName, error: err });\n throw err;\n }\n }\n\n disconnectedCallback() {\n // A deferred snapshot leaf was removed by takeover before setup() ever\n // ran. Clear the deferred marker so the registry does not accumulate\n // dead references.\n deferredStaticElements.delete(this);\n\n // Defer teardown to a microtask. A keyed move (each() relocating a node\n // via insertBefore) fires disconnectedCallback → connectedCallback in the\n // same tick; connectedCallback clears #teardownQueued so the teardown is\n // skipped and component state survives the move. A genuine removal leaves\n // the element disconnected, so the microtask tears it down.\n if (this.#teardownQueued) return;\n this.#teardownQueued = true;\n queueMicrotask(() => {\n if (!this.#teardownQueued) return; // re-inserted in the same tick\n this.#teardownQueued = false;\n if (this.isConnected) return; // moved back into the document\n this.#teardown();\n });\n }\n\n /** Synchronously dispose effects and lifecycle. */\n #teardown() {\n if (typeof __MADO_DEVTOOLS__ === \"undefined\" || __MADO_DEVTOOLS__) emitDevtools(\"component:dispose\", this, { tagName });\n this.#effectDispose?.();\n this.#effectDispose = null;\n unmount(this.#root);\n this.#lifecycle?.dispose();\n this.#lifecycle = null;\n this.#renderer = null;\n this.#attrSignals.clear();\n this.#attrDefaults.clear();\n this.#connected = false;\n }\n\n }\n\n customElements.define(tagName, MadoElement);\n registered.set(tagName, { setup, options });\n}\n\n// ---------- helpers ----------\n\nfunction normalizeStyles(\n input: StyleInput | undefined,\n tagName: string,\n useShadow: boolean,\n): CSSResult[] {\n if (!input) return [];\n const arr = Array.isArray(input) ? input : [input];\n return arr.map((s) => {\n let sheet: CSSResult;\n if (typeof s === \"string\") {\n sheet = createStylesheet(s);\n } else {\n sheet = s;\n }\n // light DOM: scope by tag name\n return useShadow ? sheet : scopeStyles(tagName, sheet);\n });\n}\n\nconst installedGlobal = new WeakSet<CSSResult>();\nconst registered = new Map<\n string,\n { setup: SetupFn; options: ComponentOptions }\n>();\n\nfunction sameComponentOptions(\n a: ComponentOptions,\n b: ComponentOptions,\n): boolean {\n if (a.shadow !== b.shadow) return false;\n if (a.styles !== b.styles) return false;\n return true;\n}\n\nfunction installGlobalSheets(sheets: CSSResult[]): void {\n const toAdd = sheets.filter((s) => !installedGlobal.has(s));\n if (toAdd.length === 0) return;\n for (const s of toAdd) installedGlobal.add(s);\n document.adoptedStyleSheets = [...document.adoptedStyleSheets, ...toAdd];\n}\n\n// Convenience re-export.\nexport { html };\n"]}
|
package/dist/src/context.d.ts
CHANGED
|
@@ -1,40 +1,21 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* const ThemeCtx = createContext<'light'|'dark'>('light');
|
|
5
|
-
*
|
|
6
|
-
* component('x-app', ({ host }) => {
|
|
7
|
-
* provide(host, ThemeCtx, signal('dark'));
|
|
8
|
-
* return () => html`<x-child/>`;
|
|
9
|
-
* });
|
|
10
|
-
*
|
|
11
|
-
* component('x-child', ({ host }) => {
|
|
12
|
-
* const theme = inject(host, ThemeCtx); // signal: () => 'dark' | 'light'
|
|
13
|
-
* return () => html`<div data-theme=${theme}>...</div>`;
|
|
14
|
-
* });
|
|
15
|
-
*
|
|
16
|
-
* How it works:
|
|
17
|
-
* provide() listens for the 'mado:context' event on the host — when a child
|
|
18
|
-
* component dispatches it (bubbles), the parent writes the current signal
|
|
19
|
-
* into the event detail and calls preventDefault.
|
|
20
|
-
* inject() dispatches the event and reads the result.
|
|
21
|
-
* This fully conforms to the Web Components Community Context Protocol.
|
|
22
|
-
*/
|
|
23
|
-
import { type Signal } from "./signal.js";
|
|
1
|
+
/** Context DI implemented through the Web Components Context Protocol. */
|
|
2
|
+
import { type Disposer, type Signal } from "./signal.js";
|
|
24
3
|
export interface Context<T> {
|
|
25
4
|
readonly _ctx: true;
|
|
26
5
|
readonly key: symbol;
|
|
27
6
|
readonly defaultValue: T;
|
|
28
7
|
}
|
|
8
|
+
export interface ContextRequestEventDetail<T = unknown> {
|
|
9
|
+
context: unknown;
|
|
10
|
+
callback(value: T, unsubscribe?: Disposer): void;
|
|
11
|
+
subscribe?: boolean;
|
|
12
|
+
}
|
|
29
13
|
export declare function createContext<T>(defaultValue: T): Context<T>;
|
|
30
14
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*/
|
|
34
|
-
export declare function provide<T>(host: HTMLElement, ctx: Context<T>, initial: T | Signal<T>): Signal<T>;
|
|
35
|
-
/**
|
|
36
|
-
* Request a context value. Walks up the DOM tree (including
|
|
37
|
-
* Shadow DOM via composed) to the first provider. If not found —
|
|
38
|
-
* returns the defaultValue wrapped in a signal.
|
|
15
|
+
* Provide a context value from a host. The returned signal updates protocol
|
|
16
|
+
* subscribers. Listener/effect cleanup follows the current Mado lifecycle.
|
|
39
17
|
*/
|
|
40
|
-
export declare function
|
|
18
|
+
export declare function provide<T>(host: HTMLElement, context: Context<T>, initial: T | Signal<T>): Signal<T>;
|
|
19
|
+
/** Request the nearest provider and expose its current value as a Signal. */
|
|
20
|
+
export declare function inject<T>(host: HTMLElement, context: Context<T>): Signal<T>;
|
|
21
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/context.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,OAAO,EAAkB,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAMzE,MAAM,WAAW,OAAO,CAAC,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,yBAAyB,CAAC,CAAC,GAAG,OAAO;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,WAAW,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IACjD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAM5D;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,CAAC,EACvB,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GACrB,MAAM,CAAC,CAAC,CAAC,CAiDX;AAED,6EAA6E;AAC7E,wBAAgB,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAyB3E"}
|
package/dist/src/context.js
CHANGED
|
@@ -1,67 +1,92 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* component('x-app', ({ host }) => {
|
|
7
|
-
* provide(host, ThemeCtx, signal('dark'));
|
|
8
|
-
* return () => html`<x-child/>`;
|
|
9
|
-
* });
|
|
10
|
-
*
|
|
11
|
-
* component('x-child', ({ host }) => {
|
|
12
|
-
* const theme = inject(host, ThemeCtx); // signal: () => 'dark' | 'light'
|
|
13
|
-
* return () => html`<div data-theme=${theme}>...</div>`;
|
|
14
|
-
* });
|
|
15
|
-
*
|
|
16
|
-
* How it works:
|
|
17
|
-
* provide() listens for the 'mado:context' event on the host — when a child
|
|
18
|
-
* component dispatches it (bubbles), the parent writes the current signal
|
|
19
|
-
* into the event detail and calls preventDefault.
|
|
20
|
-
* inject() dispatches the event and reads the result.
|
|
21
|
-
* This fully conforms to the Web Components Community Context Protocol.
|
|
22
|
-
*/
|
|
23
|
-
import { signal } from "./signal.js";
|
|
24
|
-
const CONTEXT_EVENT = "mado:context";
|
|
1
|
+
/** Context DI implemented through the Web Components Context Protocol. */
|
|
2
|
+
import { effect, signal } from "./signal.js";
|
|
3
|
+
import { getCurrentLifecycle } from "./lifecycle.js";
|
|
4
|
+
import { warnOnce } from "./diagnostics.js";
|
|
5
|
+
const CONTEXT_EVENT = "context-request";
|
|
25
6
|
export function createContext(defaultValue) {
|
|
26
7
|
return {
|
|
27
8
|
_ctx: true,
|
|
28
|
-
key: Symbol("
|
|
9
|
+
key: Symbol("mado-context"),
|
|
29
10
|
defaultValue,
|
|
30
11
|
};
|
|
31
12
|
}
|
|
32
13
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
14
|
+
* Provide a context value from a host. The returned signal updates protocol
|
|
15
|
+
* subscribers. Listener/effect cleanup follows the current Mado lifecycle.
|
|
35
16
|
*/
|
|
36
|
-
export function provide(host,
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const handler = (
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
17
|
+
export function provide(host, context, initial) {
|
|
18
|
+
const source = isSignal(initial) ? initial : signal(initial);
|
|
19
|
+
const subscribers = new Set();
|
|
20
|
+
let disposed = false;
|
|
21
|
+
const handler = (event) => {
|
|
22
|
+
const request = event;
|
|
23
|
+
if (request.detail?.context !== context.key)
|
|
24
|
+
return;
|
|
25
|
+
event.stopPropagation();
|
|
26
|
+
if (!request.detail.subscribe) {
|
|
27
|
+
request.detail.callback(source.peek());
|
|
43
28
|
return;
|
|
44
|
-
|
|
45
|
-
|
|
29
|
+
}
|
|
30
|
+
const callback = request.detail.callback;
|
|
31
|
+
let active = true;
|
|
32
|
+
const unsubscribe = () => {
|
|
33
|
+
if (!active)
|
|
34
|
+
return;
|
|
35
|
+
active = false;
|
|
36
|
+
subscribers.delete(callback);
|
|
37
|
+
};
|
|
38
|
+
subscribers.add(callback);
|
|
39
|
+
callback(source.peek(), unsubscribe);
|
|
46
40
|
};
|
|
47
41
|
host.addEventListener(CONTEXT_EVENT, handler);
|
|
48
|
-
|
|
42
|
+
const stopPublish = effect(() => {
|
|
43
|
+
const value = source();
|
|
44
|
+
for (const callback of [...subscribers])
|
|
45
|
+
callback(value);
|
|
46
|
+
});
|
|
47
|
+
const dispose = () => {
|
|
48
|
+
if (disposed)
|
|
49
|
+
return;
|
|
50
|
+
disposed = true;
|
|
51
|
+
host.removeEventListener(CONTEXT_EVENT, handler);
|
|
52
|
+
subscribers.clear();
|
|
53
|
+
stopPublish();
|
|
54
|
+
};
|
|
55
|
+
const lifecycle = getCurrentLifecycle();
|
|
56
|
+
if (lifecycle)
|
|
57
|
+
lifecycle.onDispose(dispose);
|
|
58
|
+
else {
|
|
59
|
+
warnOnce("context-provider-outside-lifecycle", "provide() called outside a component/page lifecycle; its event listener cannot be cleaned up automatically.");
|
|
60
|
+
}
|
|
61
|
+
return source;
|
|
49
62
|
}
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
/** Request the nearest provider and expose its current value as a Signal. */
|
|
64
|
+
export function inject(host, context) {
|
|
65
|
+
const value = signal(context.defaultValue);
|
|
66
|
+
let unsubscribe;
|
|
67
|
+
const detail = {
|
|
68
|
+
context: context.key,
|
|
69
|
+
subscribe: true,
|
|
70
|
+
callback(next, dispose) {
|
|
71
|
+
value.set(next);
|
|
72
|
+
if (dispose)
|
|
73
|
+
unsubscribe = dispose;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
57
76
|
host.dispatchEvent(new CustomEvent(CONTEXT_EVENT, {
|
|
58
77
|
detail,
|
|
59
78
|
bubbles: true,
|
|
60
79
|
composed: true,
|
|
61
|
-
cancelable: true,
|
|
62
80
|
}));
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
81
|
+
getCurrentLifecycle()?.onDispose(() => {
|
|
82
|
+
unsubscribe?.();
|
|
83
|
+
unsubscribe = undefined;
|
|
84
|
+
});
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
function isSignal(value) {
|
|
88
|
+
return typeof value === "function" &&
|
|
89
|
+
typeof value.set === "function" &&
|
|
90
|
+
typeof value.peek === "function";
|
|
66
91
|
}
|
|
67
92
|
//# sourceMappingURL=context.js.map
|
package/dist/src/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/context.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["/** Context DI implemented through the Web Components Context Protocol. */\n\nimport { effect, signal, type Disposer, type Signal } from \"./signal.js\";\nimport { getCurrentLifecycle } from \"./lifecycle.js\";\nimport { warnOnce } from \"./diagnostics.js\";\n\nconst CONTEXT_EVENT = \"context-request\";\n\nexport interface Context<T> {\n readonly _ctx: true;\n readonly key: symbol;\n readonly defaultValue: T;\n}\n\nexport interface ContextRequestEventDetail<T = unknown> {\n context: unknown;\n callback(value: T, unsubscribe?: Disposer): void;\n subscribe?: boolean;\n}\n\nexport function createContext<T>(defaultValue: T): Context<T> {\n return {\n _ctx: true,\n key: Symbol(\"mado-context\"),\n defaultValue,\n };\n}\n\n/**\n * Provide a context value from a host. The returned signal updates protocol\n * subscribers. Listener/effect cleanup follows the current Mado lifecycle.\n */\nexport function provide<T>(\n host: HTMLElement,\n context: Context<T>,\n initial: T | Signal<T>,\n): Signal<T> {\n const source = isSignal<T>(initial) ? initial : signal(initial as T);\n const subscribers = new Set<(value: T, unsubscribe?: Disposer) => void>();\n let disposed = false;\n\n const handler = (event: Event): void => {\n const request = event as CustomEvent<ContextRequestEventDetail<T>>;\n if (request.detail?.context !== context.key) return;\n event.stopPropagation();\n\n if (!request.detail.subscribe) {\n request.detail.callback(source.peek());\n return;\n }\n\n const callback = request.detail.callback;\n let active = true;\n const unsubscribe = (): void => {\n if (!active) return;\n active = false;\n subscribers.delete(callback);\n };\n subscribers.add(callback);\n callback(source.peek(), unsubscribe);\n };\n\n host.addEventListener(CONTEXT_EVENT, handler);\n const stopPublish = effect(() => {\n const value = source();\n for (const callback of [...subscribers]) callback(value);\n });\n\n const dispose = (): void => {\n if (disposed) return;\n disposed = true;\n host.removeEventListener(CONTEXT_EVENT, handler);\n subscribers.clear();\n stopPublish();\n };\n const lifecycle = getCurrentLifecycle();\n if (lifecycle) lifecycle.onDispose(dispose);\n else {\n warnOnce(\n \"context-provider-outside-lifecycle\",\n \"provide() called outside a component/page lifecycle; its event listener cannot be cleaned up automatically.\",\n );\n }\n\n return source;\n}\n\n/** Request the nearest provider and expose its current value as a Signal. */\nexport function inject<T>(host: HTMLElement, context: Context<T>): Signal<T> {\n const value = signal(context.defaultValue);\n let unsubscribe: Disposer | undefined;\n const detail: ContextRequestEventDetail<T> = {\n context: context.key,\n subscribe: true,\n callback(next, dispose) {\n value.set(next);\n if (dispose) unsubscribe = dispose;\n },\n };\n\n host.dispatchEvent(\n new CustomEvent<ContextRequestEventDetail<T>>(CONTEXT_EVENT, {\n detail,\n bubbles: true,\n composed: true,\n }),\n );\n\n getCurrentLifecycle()?.onDispose(() => {\n unsubscribe?.();\n unsubscribe = undefined;\n });\n return value;\n}\n\nfunction isSignal<T>(value: T | Signal<T>): value is Signal<T> {\n return typeof value === \"function\" &&\n typeof (value as Partial<Signal<T>>).set === \"function\" &&\n typeof (value as Partial<Signal<T>>).peek === \"function\";\n}\n"]}
|