@madojs/mado 0.12.0 → 0.13.1

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.
Files changed (154) hide show
  1. package/AGENTS.md +15 -8
  2. package/CHANGELOG.md +66 -0
  3. package/README.md +25 -3
  4. package/dist/src/component.d.ts +3 -1
  5. package/dist/src/component.d.ts.map +1 -0
  6. package/dist/src/component.js +65 -30
  7. package/dist/src/component.js.map +1 -1
  8. package/dist/src/context.d.ts +13 -32
  9. package/dist/src/context.d.ts.map +1 -0
  10. package/dist/src/context.js +73 -48
  11. package/dist/src/context.js.map +1 -1
  12. package/dist/src/css.d.ts +1 -0
  13. package/dist/src/css.d.ts.map +1 -0
  14. package/dist/src/css.js.map +1 -1
  15. package/dist/src/devtools-hook.d.ts +14 -0
  16. package/dist/src/devtools-hook.d.ts.map +1 -0
  17. package/dist/src/devtools-hook.js +27 -0
  18. package/dist/src/devtools-hook.js.map +1 -0
  19. package/dist/src/devtools.d.ts +24 -22
  20. package/dist/src/devtools.d.ts.map +1 -0
  21. package/dist/src/devtools.js +230 -58
  22. package/dist/src/devtools.js.map +1 -1
  23. package/dist/src/diagnostics.d.ts +12 -0
  24. package/dist/src/diagnostics.d.ts.map +1 -0
  25. package/dist/src/diagnostics.js +68 -8
  26. package/dist/src/diagnostics.js.map +1 -1
  27. package/dist/src/each.d.ts +1 -5
  28. package/dist/src/each.d.ts.map +1 -0
  29. package/dist/src/each.js +0 -8
  30. package/dist/src/each.js.map +1 -1
  31. package/dist/src/forms.d.ts +27 -96
  32. package/dist/src/forms.d.ts.map +1 -0
  33. package/dist/src/forms.js +184 -394
  34. package/dist/src/forms.js.map +1 -1
  35. package/dist/src/head.d.ts +1 -0
  36. package/dist/src/head.d.ts.map +1 -0
  37. package/dist/src/head.js +2 -1
  38. package/dist/src/head.js.map +1 -1
  39. package/dist/src/html/bindings.d.ts +1 -0
  40. package/dist/src/html/bindings.d.ts.map +1 -0
  41. package/dist/src/html/bindings.js.map +1 -1
  42. package/dist/src/html/parser.d.ts +3 -4
  43. package/dist/src/html/parser.d.ts.map +1 -0
  44. package/dist/src/html/parser.js +63 -14
  45. package/dist/src/html/parser.js.map +1 -1
  46. package/dist/src/html/template-types.d.ts +1 -0
  47. package/dist/src/html/template-types.d.ts.map +1 -0
  48. package/dist/src/html/template-types.js.map +1 -1
  49. package/dist/src/html/template.d.ts +5 -1
  50. package/dist/src/html/template.d.ts.map +1 -0
  51. package/dist/src/html/template.js +78 -3
  52. package/dist/src/html/template.js.map +1 -1
  53. package/dist/src/index.d.ts +7 -8
  54. package/dist/src/index.d.ts.map +1 -0
  55. package/dist/src/index.js +5 -7
  56. package/dist/src/index.js.map +1 -1
  57. package/dist/src/json.d.ts +3 -0
  58. package/dist/src/json.d.ts.map +1 -0
  59. package/dist/src/json.js +14 -0
  60. package/dist/src/json.js.map +1 -0
  61. package/dist/src/lifecycle.d.ts +1 -0
  62. package/dist/src/lifecycle.d.ts.map +1 -0
  63. package/dist/src/lifecycle.js +3 -2
  64. package/dist/src/lifecycle.js.map +1 -1
  65. package/dist/src/page.d.ts +1 -0
  66. package/dist/src/page.d.ts.map +1 -0
  67. package/dist/src/page.js.map +1 -1
  68. package/dist/src/persisted.d.ts +6 -1
  69. package/dist/src/persisted.d.ts.map +1 -0
  70. package/dist/src/persisted.js +17 -11
  71. package/dist/src/persisted.js.map +1 -1
  72. package/dist/src/resource.d.ts +2 -1
  73. package/dist/src/resource.d.ts.map +1 -0
  74. package/dist/src/resource.js +126 -49
  75. package/dist/src/resource.js.map +1 -1
  76. package/dist/src/router/base.d.ts +1 -0
  77. package/dist/src/router/base.d.ts.map +1 -0
  78. package/dist/src/router/base.js.map +1 -1
  79. package/dist/src/router/manifest.d.ts +1 -0
  80. package/dist/src/router/manifest.d.ts.map +1 -0
  81. package/dist/src/router/manifest.js +89 -148
  82. package/dist/src/router/manifest.js.map +1 -1
  83. package/dist/src/router/match.d.ts +1 -0
  84. package/dist/src/router/match.d.ts.map +1 -0
  85. package/dist/src/router/match.js +19 -5
  86. package/dist/src/router/match.js.map +1 -1
  87. package/dist/src/router/navigation.d.ts +1 -0
  88. package/dist/src/router/navigation.d.ts.map +1 -0
  89. package/dist/src/router/navigation.js.map +1 -1
  90. package/dist/src/signal.d.ts +1 -0
  91. package/dist/src/signal.d.ts.map +1 -0
  92. package/dist/src/signal.js +46 -17
  93. package/dist/src/signal.js.map +1 -1
  94. package/dist/src/static-runtime.d.ts +1 -0
  95. package/dist/src/static-runtime.d.ts.map +1 -0
  96. package/dist/src/static-runtime.js.map +1 -1
  97. package/dist/src/vite/index.d.ts +1 -0
  98. package/dist/src/vite/index.d.ts.map +1 -0
  99. package/dist/src/vite/index.js +4 -3
  100. package/dist/src/vite/index.js.map +1 -1
  101. package/docs/architecture/v1-roadmap.md +19 -0
  102. package/docs/en/10-pages-and-components.md +9 -1
  103. package/docs/en/13-data.md +8 -6
  104. package/docs/en/14-forms.md +66 -219
  105. package/docs/en/15-static-snapshots.md +4 -4
  106. package/docs/en/16-app-architecture.md +3 -2
  107. package/docs/en/20-deployment.md +21 -8
  108. package/docs/en/21-error-handling.md +9 -6
  109. package/docs/en/24-devtools-and-diagnostics.md +45 -0
  110. package/docs/en/30-api-freeze-map.md +8 -9
  111. package/docs/en/32-v1-stability.md +2 -2
  112. package/docs/en/33-migration-0.12-0.13.md +56 -0
  113. package/docs/en/40-llm-guide.md +7 -9
  114. package/docs/en/41-for-backenders.md +13 -9
  115. package/docs/en/42-why-mado.md +2 -2
  116. package/docs/en/README.md +2 -0
  117. package/docs/recipes/nginx/nginx.conf +2 -2
  118. package/llms.txt +25 -7
  119. package/package.json +37 -16
  120. package/scripts/clean.mjs +8 -0
  121. package/scripts/cli/generate.mjs +11 -14
  122. package/scripts/cli/help.mjs +2 -0
  123. package/scripts/cli/index.mjs +14 -6
  124. package/scripts/cli/init.mjs +13 -18
  125. package/scripts/cli/release.mjs +22 -19
  126. package/scripts/cli/run.mjs +5 -3
  127. package/scripts/logger.mjs +117 -0
  128. package/scripts/output-guard.mjs +128 -0
  129. package/scripts/preview.mjs +11 -19
  130. package/scripts/static/browser.mjs +58 -8
  131. package/scripts/static/discover.mjs +4 -1
  132. package/scripts/static.mjs +21 -7
  133. package/starters/default/package.json +4 -4
  134. package/starters/default/tsconfig.node.json +13 -0
  135. package/starters/default/vite.config.ts +0 -3
  136. package/starters/modular/README.md +4 -4
  137. package/starters/modular/package.json +4 -4
  138. package/starters/modular/src/layouts/app-shell.layout.ts +1 -2
  139. package/starters/modular/src/modules/auth/login.page.ts +5 -12
  140. package/starters/modular/src/modules/billing/pages/invoice-detail.page.ts +3 -4
  141. package/starters/modular/src/modules/billing/pages/invoices-list.page.ts +1 -1
  142. package/starters/modular/src/shared/styles/reset.css +21 -1
  143. package/starters/modular/tsconfig.json +10 -1
  144. package/starters/modular/tsconfig.node.json +13 -0
  145. package/starters/modular/vite.config.ts +6 -5
  146. package/dist/src/lazy.d.ts +0 -38
  147. package/dist/src/lazy.js +0 -73
  148. package/dist/src/lazy.js.map +0 -1
  149. package/scripts/bake.mjs +0 -5
  150. package/scripts/docs-lint.mjs +0 -170
  151. package/scripts/package-smoke.mjs +0 -135
  152. package/scripts/release-notes.mjs +0 -66
  153. package/scripts/size-budget.mjs +0 -95
  154. 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 stable release: **0.12.0**.
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
- email: { required: true, type: "email" },
305
- age: { required: true, type: "number", min: 18 },
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
- Custom validation `validate: (values) => ({ field: 'error message' } | null)`.
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` (283 lines) |
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` (297 lines) |
524
- | How do forms work? | `src/forms.ts` (212 lines) |
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,71 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.1 - 2026-07-11
4
+
5
+ ### Fixed
6
+
7
+ - Static capture now waits for asynchronous module bootstrap before reading
8
+ the runtime handshake. This removes a top-level-await race that surfaced on
9
+ slower Windows CI runners as `window.__MADO_STATIC__ was not installed`.
10
+
11
+ ## 0.13.0 - 2026-07-11
12
+
13
+ The final planned pre-v1 breaking release hardens ownership and freezes the
14
+ small platform-first API.
15
+
16
+ ### Breaking
17
+
18
+ - Forms now use `useForm({ initial, validate? })`; native controls and
19
+ `ValidityState` own constraints. The schema and field-array APIs are removed.
20
+ - The root surface drops `lazy`, `list` and accidental low-level exports. The
21
+ only public subpaths are the package root, `/vite` and `/devtools.js`.
22
+ - `page.load` is synchronous and returns a value or `Resource`; Promises are
23
+ rejected with a targeted diagnostic.
24
+
25
+ ### Added
26
+
27
+ - `render()` returns a disposer and public `unmount(container)` releases owned
28
+ bindings, events, effects and nested templates.
29
+ - Structured browser and CLI diagnostics, including JSON CLI output, log-level
30
+ controls and `NO_COLOR` support.
31
+ - A development-only Shadow DOM devtools overlay with reactivity, component,
32
+ router/data and timeline/error views, plus an exported controller.
33
+ - The Web Components `context-request` protocol with subscriptions and
34
+ lifecycle cleanup.
35
+
36
+ ### Fixed
37
+
38
+ - Destructive output paths are realpath-checked and cleaned only when owned by
39
+ a `.mado-output` sentinel.
40
+ - JSON-LD/static seed serialization escapes script-breaking input.
41
+ - Effect, component, render and navigation teardown is transactional and
42
+ idempotent, including initial failures and same-tick DOM moves.
43
+ - Route literals are escaped, template markers are opaque and deterministic,
44
+ unsupported self-closing/template-child shapes fail early.
45
+ - Resource cache identity includes the fetcher; `staleTime: 0`, expiry,
46
+ invalidation, refresh and concurrent mutation generations have defined
47
+ semantics. Persisted signal disposal no longer deletes storage implicitly.
48
+ - Static capture blocks undeclared external requests, preserves form/focus
49
+ takeover state, runs bounded route capture and emits portable build metadata.
50
+ - `mado build` and published route discovery now feed the standalone static
51
+ pipeline correctly.
52
+
53
+ ### Tooling and release
54
+
55
+ - Framework/default starter use TypeScript 7; the ESLint modular starter stays
56
+ on TypeScript 6. Node 22.12+ and Vite 8 are the supported baseline.
57
+ - Lightning CSS was removed from starters in favour of Vite's standard CSS
58
+ pipeline.
59
+ - Reproducible tarballs, inline source maps, API golden files, compiled docs
60
+ snippets and a single `verify:release` gate were added.
61
+ - CI covers Node 22/24, Chromium/Firefox/WebKit modular flows and Windows
62
+ package smoke. Runtime and devtools bundles have separate size budgets.
63
+ - Windows package smoke invokes `npm-cli.js` through the active `node.exe`
64
+ instead of relying on `npm`/`npx` command shims, and launches preview
65
+ directly so CI does not leak a nested process tree.
66
+
67
+ See [the migration guide](./docs/en/33-migration-0.12-0.13.md).
68
+
3
69
  ## 0.12.0 - 2026-06-24
4
70
 
5
71
  ### 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 — schema-based validation
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
- email: { required: true, type: "email" },
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.
@@ -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, defaultValue?: string): Signal<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"}
@@ -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
- this.#connected = true;
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(name, defaultValue = "") {
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
- this.#renderer = runInLifecycle(lifecycle, () => setup(ctx));
186
- // After setup(), install a single MutationObserver for all attrs
187
- // registered via ctx.attr().
188
- if (this.#attrSignals.size > 0) {
189
- const attrNames = [...this.#attrSignals.keys()];
190
- const obs = new MutationObserver((mutations) => {
191
- for (const m of mutations) {
192
- const s = this.#attrSignals.get(m.attributeName);
193
- const fallback = this.#attrDefaults.get(m.attributeName) ?? "";
194
- if (s)
195
- s.set(this.getAttribute(m.attributeName) ?? fallback);
196
- }
197
- });
198
- obs.observe(this, { attributes: true, attributeFilter: attrNames });
199
- lifecycle.onDispose(() => obs.disconnect());
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"]}
@@ -1,40 +1,21 @@
1
- /**
2
- * Context (DI) without props drilling. A native pattern from Lit / Open Web Components.
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
- * Declare that this host provides a value for the given context.
32
- * Returns the signal itself so the provider can update it.
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 inject<T>(host: HTMLElement, ctx: Context<T>): Signal<T>;
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"}
@@ -1,67 +1,92 @@
1
- /**
2
- * Context (DI) without props drilling. A native pattern from Lit / Open Web Components.
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 { 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("madoctx"),
9
+ key: Symbol("mado-context"),
29
10
  defaultValue,
30
11
  };
31
12
  }
32
13
  /**
33
- * Declare that this host provides a value for the given context.
34
- * Returns the signal itself so the provider can update it.
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, ctx, initial) {
37
- const sig = typeof initial === "function"
38
- ? initial
39
- : signal(initial);
40
- const handler = (e) => {
41
- const ce = e;
42
- if (ce.detail.key !== ctx.key)
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
- ce.detail.value = sig;
45
- e.stopPropagation();
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
- return sig;
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
- * Request a context value. Walks up the DOM tree (including
52
- * Shadow DOM via composed) to the first provider. If not found —
53
- * returns the defaultValue wrapped in a signal.
54
- */
55
- export function inject(host, ctx) {
56
- const detail = { key: ctx.key };
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
- if (detail.value)
64
- return detail.value;
65
- return signal(ctx.defaultValue);
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