@madojs/mado 0.11.1 → 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.
Files changed (298) hide show
  1. package/AGENTS.md +116 -32
  2. package/CHANGELOG.md +216 -1
  3. package/README.md +158 -173
  4. package/dist/src/component.d.ts +6 -3
  5. package/dist/src/component.d.ts.map +1 -0
  6. package/dist/src/component.js +163 -36
  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 +34 -5
  15. package/dist/src/css.js.map +1 -1
  16. package/dist/src/devtools-hook.d.ts +14 -0
  17. package/dist/src/devtools-hook.d.ts.map +1 -0
  18. package/dist/src/devtools-hook.js +27 -0
  19. package/dist/src/devtools-hook.js.map +1 -0
  20. package/dist/src/devtools.d.ts +24 -22
  21. package/dist/src/devtools.d.ts.map +1 -0
  22. package/dist/src/devtools.js +230 -58
  23. package/dist/src/devtools.js.map +1 -1
  24. package/dist/src/diagnostics.d.ts +12 -0
  25. package/dist/src/diagnostics.d.ts.map +1 -0
  26. package/dist/src/diagnostics.js +68 -8
  27. package/dist/src/diagnostics.js.map +1 -1
  28. package/dist/src/each.d.ts +1 -5
  29. package/dist/src/each.d.ts.map +1 -0
  30. package/dist/src/each.js +0 -8
  31. package/dist/src/each.js.map +1 -1
  32. package/dist/src/forms.d.ts +27 -96
  33. package/dist/src/forms.d.ts.map +1 -0
  34. package/dist/src/forms.js +184 -394
  35. package/dist/src/forms.js.map +1 -1
  36. package/dist/src/head.d.ts +7 -6
  37. package/dist/src/head.d.ts.map +1 -0
  38. package/dist/src/head.js +8 -7
  39. package/dist/src/head.js.map +1 -1
  40. package/dist/src/html/bindings.d.ts +1 -0
  41. package/dist/src/html/bindings.d.ts.map +1 -0
  42. package/dist/src/html/bindings.js.map +1 -1
  43. package/dist/src/html/parser.d.ts +4 -5
  44. package/dist/src/html/parser.d.ts.map +1 -0
  45. package/dist/src/html/parser.js +64 -15
  46. package/dist/src/html/parser.js.map +1 -1
  47. package/dist/src/html/template-types.d.ts +1 -0
  48. package/dist/src/html/template-types.d.ts.map +1 -0
  49. package/dist/src/html/template-types.js.map +1 -1
  50. package/dist/src/html/template.d.ts +5 -1
  51. package/dist/src/html/template.d.ts.map +1 -0
  52. package/dist/src/html/template.js +102 -13
  53. package/dist/src/html/template.js.map +1 -1
  54. package/dist/src/index.d.ts +15 -12
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/index.js +18 -10
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/json.d.ts +3 -0
  59. package/dist/src/json.d.ts.map +1 -0
  60. package/dist/src/json.js +14 -0
  61. package/dist/src/json.js.map +1 -0
  62. package/dist/src/lifecycle.d.ts +1 -0
  63. package/dist/src/lifecycle.d.ts.map +1 -0
  64. package/dist/src/lifecycle.js +3 -2
  65. package/dist/src/lifecycle.js.map +1 -1
  66. package/dist/src/page.d.ts +63 -28
  67. package/dist/src/page.d.ts.map +1 -0
  68. package/dist/src/page.js +5 -0
  69. package/dist/src/page.js.map +1 -1
  70. package/dist/src/persisted.d.ts +6 -1
  71. package/dist/src/persisted.d.ts.map +1 -0
  72. package/dist/src/persisted.js +17 -11
  73. package/dist/src/persisted.js.map +1 -1
  74. package/dist/src/resource.d.ts +2 -1
  75. package/dist/src/resource.d.ts.map +1 -0
  76. package/dist/src/resource.js +128 -50
  77. package/dist/src/resource.js.map +1 -1
  78. package/dist/src/router/base.d.ts +66 -0
  79. package/dist/src/router/base.d.ts.map +1 -0
  80. package/dist/src/router/base.js +168 -0
  81. package/dist/src/router/base.js.map +1 -0
  82. package/dist/src/router/manifest.d.ts +1 -0
  83. package/dist/src/router/manifest.d.ts.map +1 -0
  84. package/dist/src/router/manifest.js +156 -171
  85. package/dist/src/router/manifest.js.map +1 -1
  86. package/dist/src/router/match.d.ts +17 -2
  87. package/dist/src/router/match.d.ts.map +1 -0
  88. package/dist/src/router/match.js +60 -6
  89. package/dist/src/router/match.js.map +1 -1
  90. package/dist/src/router/navigation.d.ts +1 -0
  91. package/dist/src/router/navigation.d.ts.map +1 -0
  92. package/dist/src/router/navigation.js +58 -9
  93. package/dist/src/router/navigation.js.map +1 -1
  94. package/dist/src/signal.d.ts +1 -0
  95. package/dist/src/signal.d.ts.map +1 -0
  96. package/dist/src/signal.js +46 -17
  97. package/dist/src/signal.js.map +1 -1
  98. package/dist/src/static-runtime.d.ts +82 -0
  99. package/dist/src/static-runtime.d.ts.map +1 -0
  100. package/dist/src/static-runtime.js +209 -0
  101. package/dist/src/static-runtime.js.map +1 -0
  102. package/dist/src/vite/index.d.ts +32 -3
  103. package/dist/src/vite/index.d.ts.map +1 -0
  104. package/dist/src/vite/index.js +49 -17
  105. package/dist/src/vite/index.js.map +1 -1
  106. package/docs/README.md +5 -8
  107. package/docs/architecture/adr/0001-browser-static-snapshots.md +132 -0
  108. package/docs/architecture/v1-roadmap.md +19 -0
  109. package/docs/en/00-the-mado-way.md +1 -1
  110. package/docs/en/01-quickstart.md +183 -0
  111. package/docs/en/10-pages-and-components.md +279 -0
  112. package/docs/en/11-templates-and-signals.md +211 -0
  113. package/docs/en/12-routing.md +229 -0
  114. package/docs/en/13-data.md +227 -0
  115. package/docs/en/14-forms.md +91 -0
  116. package/docs/en/15-static-snapshots.md +181 -0
  117. package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +56 -4
  118. package/docs/en/{13-deployment.md → 20-deployment.md} +79 -17
  119. package/docs/en/{15-error-handling.md → 21-error-handling.md} +9 -6
  120. package/docs/en/{14-testing.md → 22-testing.md} +6 -5
  121. package/docs/en/23-cookbook.md +292 -0
  122. package/docs/en/24-devtools-and-diagnostics.md +45 -0
  123. package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +17 -9
  124. package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +5 -5
  125. package/docs/en/33-migration-0.12-0.13.md +56 -0
  126. package/docs/en/40-llm-guide.md +774 -0
  127. package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +28 -25
  128. package/docs/en/{05-why-mado.md → 42-why-mado.md} +3 -3
  129. package/docs/en/README.md +99 -27
  130. package/docs/recipes/nginx/Containerfile +26 -0
  131. package/docs/recipes/nginx/nginx.conf +42 -0
  132. package/llms.txt +263 -210
  133. package/package.json +51 -23
  134. package/scripts/clean.mjs +8 -0
  135. package/scripts/cli/generate.mjs +57 -10
  136. package/scripts/cli/help.mjs +13 -5
  137. package/scripts/cli/index.mjs +31 -7
  138. package/scripts/cli/init.mjs +19 -19
  139. package/scripts/cli/release.mjs +38 -83
  140. package/scripts/cli/run.mjs +5 -3
  141. package/scripts/logger.mjs +117 -0
  142. package/scripts/output-guard.mjs +128 -0
  143. package/scripts/preview.mjs +105 -24
  144. package/scripts/static/browser.mjs +695 -0
  145. package/scripts/static/discover.mjs +284 -0
  146. package/scripts/static/output.mjs +141 -0
  147. package/scripts/static/serialize.mjs +212 -0
  148. package/scripts/static/server.mjs +167 -0
  149. package/scripts/static.mjs +205 -0
  150. package/starters/default/README.md +57 -55
  151. package/starters/default/package.json +6 -12
  152. package/starters/default/src/app.routes.ts +20 -33
  153. package/starters/default/src/components/feature-card.component.ts +40 -0
  154. package/starters/default/src/components/live-counter.component.ts +57 -0
  155. package/starters/default/src/content/guides.ts +55 -0
  156. package/starters/default/src/main.ts +9 -13
  157. package/starters/default/src/pages/app.page.ts +41 -0
  158. package/starters/default/src/pages/guide.page.ts +40 -0
  159. package/starters/default/src/pages/home.page.ts +49 -0
  160. package/starters/default/src/pages/not-found.page.ts +18 -0
  161. package/starters/default/src/styles/document.css +39 -0
  162. package/starters/default/src/styles/reset.css +37 -0
  163. package/starters/default/src/styles/tokens.css +30 -0
  164. package/starters/default/src/styles.d.ts +3 -1
  165. package/starters/default/src/vite-env.d.ts +1 -1
  166. package/starters/default/tsconfig.node.json +13 -0
  167. package/starters/default/vite.config.ts +14 -4
  168. package/starters/modular/README.md +142 -0
  169. package/starters/modular/index.html +13 -0
  170. package/starters/modular/package.json +30 -0
  171. package/starters/modular/src/app.routes.ts +39 -0
  172. package/starters/{default → modular}/src/layouts/app-shell.layout.ts +9 -7
  173. package/starters/{default → modular}/src/layouts/auth-shell.layout.ts +3 -3
  174. package/starters/modular/src/main.ts +16 -0
  175. package/starters/{default → modular}/src/modules/auth/login.page.ts +5 -12
  176. package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +3 -4
  177. package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +4 -4
  178. package/starters/{default → modular}/src/modules/home/home.page.ts +5 -7
  179. package/starters/modular/src/modules/home/not-found.page.ts +14 -0
  180. package/starters/{default → modular}/src/shared/styles/reset.css +21 -1
  181. package/starters/modular/src/styles.d.ts +1 -0
  182. package/starters/modular/src/vite-env.d.ts +1 -0
  183. package/starters/modular/tsconfig.json +33 -0
  184. package/starters/modular/tsconfig.node.json +13 -0
  185. package/starters/modular/vite.config.ts +132 -0
  186. package/TODO.md +0 -79
  187. package/dist/src/lazy.d.ts +0 -38
  188. package/dist/src/lazy.js +0 -73
  189. package/dist/src/lazy.js.map +0 -1
  190. package/docs/en/01-routing.md +0 -152
  191. package/docs/en/02-project-layout.md +0 -124
  192. package/docs/en/03-static-bake.md +0 -249
  193. package/docs/en/04-ide-setup.md +0 -162
  194. package/docs/en/07-llm-pitfalls.md +0 -724
  195. package/docs/en/08-llm-zero-history-test.md +0 -56
  196. package/docs/en/09-shadow-vs-light-dom.md +0 -174
  197. package/docs/en/11-layouts.md +0 -113
  198. package/docs/en/12-auth-and-api.md +0 -124
  199. package/docs/en/16-bake-cookbook.md +0 -100
  200. package/docs/en/17-shadow-dom-forms.md +0 -192
  201. package/docs/fr/00-the-mado-way.md +0 -85
  202. package/docs/fr/01-routing.md +0 -120
  203. package/docs/fr/02-project-layout.md +0 -84
  204. package/docs/fr/03-static-bake.md +0 -289
  205. package/docs/fr/04-ide-setup.md +0 -162
  206. package/docs/fr/05-why-mado.md +0 -193
  207. package/docs/fr/06-for-backenders.md +0 -438
  208. package/docs/fr/07-llm-pitfalls.md +0 -625
  209. package/docs/fr/08-llm-zero-history-test.md +0 -38
  210. package/docs/fr/09-shadow-vs-light-dom.md +0 -65
  211. package/docs/fr/10-app-architecture.md +0 -138
  212. package/docs/fr/11-layouts.md +0 -47
  213. package/docs/fr/12-auth-and-api.md +0 -76
  214. package/docs/fr/13-deployment.md +0 -57
  215. package/docs/fr/14-testing.md +0 -41
  216. package/docs/fr/15-error-handling.md +0 -50
  217. package/docs/fr/16-bake-cookbook.md +0 -88
  218. package/docs/fr/17-shadow-dom-forms.md +0 -196
  219. package/docs/fr/18-api-freeze-map.md +0 -63
  220. package/docs/fr/19-reactivity-ordering.md +0 -97
  221. package/docs/fr/20-v1-stability.md +0 -88
  222. package/docs/fr/README.md +0 -27
  223. package/docs/ru/00-the-mado-way.md +0 -84
  224. package/docs/ru/01-routing.md +0 -119
  225. package/docs/ru/02-project-layout.md +0 -84
  226. package/docs/ru/03-static-bake.md +0 -250
  227. package/docs/ru/04-ide-setup.md +0 -144
  228. package/docs/ru/05-why-mado.md +0 -193
  229. package/docs/ru/06-for-backenders.md +0 -428
  230. package/docs/ru/07-llm-pitfalls.md +0 -624
  231. package/docs/ru/08-llm-zero-history-test.md +0 -57
  232. package/docs/ru/09-shadow-vs-light-dom.md +0 -63
  233. package/docs/ru/10-app-architecture.md +0 -152
  234. package/docs/ru/11-layouts.md +0 -47
  235. package/docs/ru/12-auth-and-api.md +0 -75
  236. package/docs/ru/13-deployment.md +0 -66
  237. package/docs/ru/14-testing.md +0 -50
  238. package/docs/ru/15-error-handling.md +0 -56
  239. package/docs/ru/16-bake-cookbook.md +0 -95
  240. package/docs/ru/17-shadow-dom-forms.md +0 -193
  241. package/docs/ru/18-api-freeze-map.md +0 -64
  242. package/docs/ru/19-reactivity-ordering.md +0 -95
  243. package/docs/ru/20-v1-stability.md +0 -82
  244. package/docs/ru/README.md +0 -25
  245. package/docs/uk/00-the-mado-way.md +0 -82
  246. package/docs/uk/01-routing.md +0 -76
  247. package/docs/uk/02-project-layout.md +0 -73
  248. package/docs/uk/03-static-bake.md +0 -48
  249. package/docs/uk/04-ide-setup.md +0 -26
  250. package/docs/uk/05-why-mado.md +0 -34
  251. package/docs/uk/06-for-backenders.md +0 -55
  252. package/docs/uk/07-llm-pitfalls.md +0 -145
  253. package/docs/uk/08-llm-zero-history-test.md +0 -34
  254. package/docs/uk/09-shadow-vs-light-dom.md +0 -58
  255. package/docs/uk/10-app-architecture.md +0 -97
  256. package/docs/uk/11-layouts.md +0 -47
  257. package/docs/uk/12-auth-and-api.md +0 -70
  258. package/docs/uk/13-deployment.md +0 -40
  259. package/docs/uk/14-testing.md +0 -34
  260. package/docs/uk/15-error-handling.md +0 -32
  261. package/docs/uk/16-bake-cookbook.md +0 -36
  262. package/docs/uk/17-shadow-dom-forms.md +0 -193
  263. package/docs/uk/18-api-freeze-map.md +0 -61
  264. package/docs/uk/19-reactivity-ordering.md +0 -95
  265. package/docs/uk/20-v1-stability.md +0 -83
  266. package/docs/uk/README.md +0 -27
  267. package/scripts/bake.mjs +0 -570
  268. package/scripts/package-smoke.mjs +0 -77
  269. package/scripts/release-notes.mjs +0 -66
  270. package/scripts/size-budget.mjs +0 -91
  271. package/starters/default/src/modules/home/not-found.page.ts +0 -11
  272. package/starters/default/src/shared/ui/x-button.component.ts +0 -49
  273. /package/docs/en/{19-reactivity-ordering.md → 31-reactivity-ordering.md} +0 -0
  274. /package/starters/{default → modular}/.editorconfig +0 -0
  275. /package/starters/{default → modular}/eslint.config.mjs +0 -0
  276. /package/starters/{default → modular}/public/favicon.svg +0 -0
  277. /package/starters/{default → modular}/src/modules/auth/_contracts/auth-api.types.ts +0 -0
  278. /package/starters/{default → modular}/src/modules/auth/auth.connector.ts +0 -0
  279. /package/starters/{default → modular}/src/modules/auth/auth.guard.ts +0 -0
  280. /package/starters/{default → modular}/src/modules/auth/auth.public.ts +0 -0
  281. /package/starters/{default → modular}/src/modules/auth/auth.routes.ts +0 -0
  282. /package/starters/{default → modular}/src/modules/auth/auth.service.ts +0 -0
  283. /package/starters/{default → modular}/src/modules/auth/auth.types.ts +0 -0
  284. /package/starters/{default → modular}/src/modules/billing/_contracts/stripe.types.ts +0 -0
  285. /package/starters/{default → modular}/src/modules/billing/api/stripe.connector.ts +0 -0
  286. /package/starters/{default → modular}/src/modules/billing/billing.public.ts +0 -0
  287. /package/starters/{default → modular}/src/modules/billing/billing.routes.ts +0 -0
  288. /package/starters/{default → modular}/src/modules/billing/billing.types.ts +0 -0
  289. /package/starters/{default → modular}/src/modules/billing/components/invoice-status-badge.component.ts +0 -0
  290. /package/starters/{default → modular}/src/modules/billing/data/invoices.resource.ts +0 -0
  291. /package/starters/{default → modular}/src/shared/http/http-client.ts +0 -0
  292. /package/starters/{default → modular}/src/shared/http/http-error.ts +0 -0
  293. /package/starters/{default → modular}/src/shared/http/interceptors.ts +0 -0
  294. /package/starters/{default → modular}/src/shared/lib/format-date.ts +0 -0
  295. /package/starters/{default → modular}/src/shared/styles/content.css +0 -0
  296. /package/starters/{default → modular}/src/shared/styles/shell.css +0 -0
  297. /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
  298. /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
@@ -12,14 +12,88 @@
12
12
  * The returned render function is called via an effect, so any signals
13
13
  * read inside it automatically re-render the template.
14
14
  *
15
- * Shadow DOM (open) is used by default. It can be disabled, and
16
- * styles will be scoped via @scope (or a tag-prefix fallback).
15
+ * Shadow DOM (open, serializable) is used by default. It can be disabled for
16
+ * advanced integration cases, and styles will be scoped via @scope
17
+ * (or a tag-prefix fallback).
17
18
  */
18
19
  import { signal, effect } from "./signal.js";
19
- import { html, render } from "./html/template.js";
20
- import { adopt, scopeStyles } from "./css.js";
20
+ import { html, render, unmount } from "./html/template.js";
21
+ import { adopt, createStylesheet, scopeStyles, } from "./css.js";
21
22
  import { createLifecycle, runInLifecycle, } from "./lifecycle.js";
22
23
  import { warnOnce } from "./diagnostics.js";
24
+ import { emitDevtools } from "./devtools-hook.js";
25
+ /**
26
+ * Components upgraded inside a server-side or build-time snapshot live
27
+ * temporarily under `<#app data-mado-static>`. Their setup() is intentionally
28
+ * NOT run yet: Mado replaces the snapshot tree atomically when the SPA
29
+ * boots, and any work performed by setup() before takeover would leak.
30
+ *
31
+ * The set lets the runtime double-check, after takeover, that no deferred
32
+ * element survives — see _flushDeferredStaticElements().
33
+ */
34
+ const deferredStaticElements = new Set();
35
+ /**
36
+ * Walk composed ancestry (Light DOM parentNode plus ShadowRoot.host) and
37
+ * return true when the element lives inside a tree marked with
38
+ * `data-mado-static`. Used to defer component activation until the SPA
39
+ * takeover atomically removes that tree.
40
+ */
41
+ function isInsideStaticTree(node) {
42
+ let n = node;
43
+ while (n) {
44
+ if (n instanceof Element &&
45
+ n.hasAttribute &&
46
+ n.hasAttribute("data-mado-static")) {
47
+ return true;
48
+ }
49
+ const parent = typeof ShadowRoot !== "undefined" && n instanceof ShadowRoot
50
+ ? n.host
51
+ : n.parentNode;
52
+ n = parent;
53
+ }
54
+ return false;
55
+ }
56
+ /** @internal */
57
+ export function _markDeferredForStatic(host) {
58
+ deferredStaticElements.add(host);
59
+ }
60
+ /** @internal */
61
+ export function _isDeferredForStatic(host) {
62
+ return deferredStaticElements.has(host);
63
+ }
64
+ /**
65
+ * Called by render() after a root takeover has atomically replaced the
66
+ * `data-mado-static` tree. By that time every component that was
67
+ * mid-snapshot must either be:
68
+ * 1. removed with the static tree (the typical case), or
69
+ * 2. still connected but no longer under a static ancestor — in which
70
+ * case the SPA tree placed it live and we activate it now.
71
+ * Any element that fails both checks is a stuck snapshot leaf; emit a
72
+ * single diagnostic so the developer notices the orphan.
73
+ *
74
+ * @internal
75
+ */
76
+ export function _flushDeferredStaticElements() {
77
+ for (const el of [...deferredStaticElements]) {
78
+ if (!el.isConnected) {
79
+ deferredStaticElements.delete(el);
80
+ continue;
81
+ }
82
+ if (!isInsideStaticTree(el)) {
83
+ // Reset the guard so the live connectedCallback runs setup().
84
+ deferredStaticElements.delete(el);
85
+ // Force a clean re-enter of connectedCallback. Browsers fire that
86
+ // callback exactly once per insertion, so we toggle the element out
87
+ // and back in. Avoid this if the element is the document root.
88
+ const parent = el.parentNode;
89
+ const next = el.nextSibling;
90
+ if (parent) {
91
+ parent.removeChild(el);
92
+ parent.insertBefore(el, next);
93
+ }
94
+ }
95
+ }
96
+ }
23
97
  export function component(tagName, setup, options = {}) {
24
98
  if (!tagName.includes("-")) {
25
99
  warnOnce(`component-invalid-tag-${tagName}`, `component("${tagName}") skipped: Custom Element names must contain a hyphen.`);
@@ -52,7 +126,13 @@ export function component(tagName, setup, options = {}) {
52
126
  #attrDefaults = new Map();
53
127
  constructor() {
54
128
  super();
55
- this.#root = useShadow ? this.attachShadow({ mode: "open" }) : this;
129
+ this.#root = useShadow
130
+ ? this.shadowRoot ??
131
+ this.attachShadow({
132
+ mode: "open",
133
+ serializable: true,
134
+ })
135
+ : this;
56
136
  }
57
137
  connectedCallback() {
58
138
  // A keyed move (each() relocating via insertBefore) fires
@@ -62,7 +142,18 @@ export function component(tagName, setup, options = {}) {
62
142
  this.#teardownQueued = false;
63
143
  if (this.#connected)
64
144
  return;
65
- this.#connected = true;
145
+ // Deferred activation inside a static snapshot tree. The runtime keeps
146
+ // setup()/effect() asleep until the SPA performs root takeover; the
147
+ // existing DSD inside shadowRoot stays as inert first-paint markup.
148
+ // When the SPA replaces the static root, this element is either
149
+ // removed with that tree (the common case) or re-inserted live —
150
+ // in which case connectedCallback() runs again and reaches setup().
151
+ if (isInsideStaticTree(this)) {
152
+ _markDeferredForStatic(this);
153
+ return;
154
+ }
155
+ if (typeof __MADO_DEVTOOLS__ === "undefined" || __MADO_DEVTOOLS__)
156
+ emitDevtools("component:connect", this, { tagName });
66
157
  if (stylesheets.length > 0) {
67
158
  if (useShadow) {
68
159
  adopt(this.#root, ...stylesheets);
@@ -75,45 +166,76 @@ export function component(tagName, setup, options = {}) {
75
166
  // called from setup() (resource, ...) will see it via
76
167
  // getCurrentLifecycle() and register its own cleanup.
77
168
  const lifecycle = createLifecycle();
78
- this.#lifecycle = lifecycle;
79
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
+ });
80
180
  const ctx = {
81
181
  host: this,
82
182
  // ctx.onDispose proxies to lifecycle — the single source of truth
83
183
  // for component cleanups (including auto-cleanup from
84
184
  // resource(), navigator listeners, etc.).
85
185
  onDispose: (fn) => lifecycle.onDispose(fn),
86
- attr(name, defaultValue = "") {
87
- let s = host.#attrSignals.get(name);
88
- if (!s) {
89
- host.#attrDefaults.set(name, defaultValue);
90
- s = signal(host.getAttribute(name) ?? defaultValue);
91
- host.#attrSignals.set(name, s);
92
- }
93
- return s;
94
- },
186
+ attr,
95
187
  };
96
- this.#renderer = runInLifecycle(lifecycle, () => setup(ctx));
97
- // After setup(), install a single MutationObserver for all attrs
98
- // registered via ctx.attr().
99
- if (this.#attrSignals.size > 0) {
100
- const attrNames = [...this.#attrSignals.keys()];
101
- const obs = new MutationObserver((mutations) => {
102
- for (const m of mutations) {
103
- const s = this.#attrSignals.get(m.attributeName);
104
- const fallback = this.#attrDefaults.get(m.attributeName) ?? "";
105
- if (s)
106
- s.set(this.getAttribute(m.attributeName) ?? fallback);
107
- }
108
- });
109
- obs.observe(this, { attributes: true, attributeFilter: attrNames });
110
- 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;
111
232
  }
112
- this.#effectDispose = effect(() => {
113
- render(this.#renderer(), this.#root);
114
- });
115
233
  }
116
234
  disconnectedCallback() {
235
+ // A deferred snapshot leaf was removed by takeover before setup() ever
236
+ // ran. Clear the deferred marker so the registry does not accumulate
237
+ // dead references.
238
+ deferredStaticElements.delete(this);
117
239
  // Defer teardown to a microtask. A keyed move (each() relocating a node
118
240
  // via insertBefore) fires disconnectedCallback → connectedCallback in the
119
241
  // same tick; connectedCallback clears #teardownQueued so the teardown is
@@ -133,10 +255,16 @@ export function component(tagName, setup, options = {}) {
133
255
  }
134
256
  /** Synchronously dispose effects and lifecycle. */
135
257
  #teardown() {
258
+ if (typeof __MADO_DEVTOOLS__ === "undefined" || __MADO_DEVTOOLS__)
259
+ emitDevtools("component:dispose", this, { tagName });
136
260
  this.#effectDispose?.();
137
261
  this.#effectDispose = null;
262
+ unmount(this.#root);
138
263
  this.#lifecycle?.dispose();
139
264
  this.#lifecycle = null;
265
+ this.#renderer = null;
266
+ this.#attrSignals.clear();
267
+ this.#attrDefaults.clear();
140
268
  this.#connected = false;
141
269
  }
142
270
  }
@@ -151,8 +279,7 @@ function normalizeStyles(input, tagName, useShadow) {
151
279
  return arr.map((s) => {
152
280
  let sheet;
153
281
  if (typeof s === "string") {
154
- sheet = new CSSStyleSheet();
155
- sheet.replaceSync(s);
282
+ sheet = createStylesheet(s);
156
283
  }
157
284
  else {
158
285
  sheet = s;
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,MAAM,EAAe,MAAM,aAAa,CAAC;AAElD,MAAM,aAAa,GAAG,cAAc,CAAC;AAQrC,MAAM,UAAU,aAAa,CAAI,YAAe;IAC9C,OAAO;QACL,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC;QACtB,YAAY;KACb,CAAC;AACJ,CAAC;AAOD;;;GAGG;AACH,MAAM,UAAU,OAAO,CACrB,IAAiB,EACjB,GAAe,EACf,OAAsB;IAEtB,MAAM,GAAG,GACP,OAAO,OAAO,KAAK,UAAU;QAC3B,CAAC,CAAE,OAAqB;QACxB,CAAC,CAAC,MAAM,CAAC,OAAY,CAAC,CAAC;IAE3B,MAAM,OAAO,GAAG,CAAC,CAAQ,EAAE,EAAE;QAC3B,MAAM,EAAE,GAAG,CAAoC,CAAC;QAChD,IAAI,EAAE,CAAC,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG;YAAE,OAAO;QACtC,EAAE,CAAC,MAAM,CAAC,KAAK,GAAG,GAAsB,CAAC;QACzC,CAAC,CAAC,eAAe,EAAE,CAAC;IACtB,CAAC,CAAC;IACF,IAAI,CAAC,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IAE9C,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAI,IAAiB,EAAE,GAAe;IAC1D,MAAM,MAAM,GAAuB,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;IACpD,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,aAAa,EAAE;QAC7B,MAAM;QACN,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,IAAI;QACd,UAAU,EAAE,IAAI;KACjB,CAAC,CACH,CAAC;IACF,IAAI,MAAM,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC,KAAkB,CAAC;IACnD,OAAO,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAClC,CAAC"}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/context.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,OAAO,EAAE,MAAM,EAAE,MAAM,EAA8B,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAcxC,MAAM,UAAU,aAAa,CAAI,YAAe;IAC9C,OAAO;QACL,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,MAAM,CAAC,cAAc,CAAC;QAC3B,YAAY;KACb,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CACrB,IAAiB,EACjB,OAAmB,EACnB,OAAsB;IAEtB,MAAM,MAAM,GAAG,QAAQ,CAAI,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAY,CAAC,CAAC;IACrE,MAAM,WAAW,GAAG,IAAI,GAAG,EAA8C,CAAC;IAC1E,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,MAAM,OAAO,GAAG,CAAC,KAAY,EAAQ,EAAE;QACrC,MAAM,OAAO,GAAG,KAAkD,CAAC;QACnE,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG;YAAE,OAAO;QACpD,KAAK,CAAC,eAAe,EAAE,CAAC;QAExB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YACvC,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;QACzC,IAAI,MAAM,GAAG,IAAI,CAAC;QAClB,MAAM,WAAW,GAAG,GAAS,EAAE;YAC7B,IAAI,CAAC,MAAM;gBAAE,OAAO;YACpB,MAAM,GAAG,KAAK,CAAC;YACf,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC,CAAC;QACF,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1B,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,WAAW,CAAC,CAAC;IACvC,CAAC,CAAC;IAEF,IAAI,CAAC,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IAC9C,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,EAAE;QAC9B,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;QACvB,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,WAAW,CAAC;YAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,QAAQ;YAAE,OAAO;QACrB,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,CAAC,mBAAmB,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACjD,WAAW,CAAC,KAAK,EAAE,CAAC;QACpB,WAAW,EAAE,CAAC;IAChB,CAAC,CAAC;IACF,MAAM,SAAS,GAAG,mBAAmB,EAAE,CAAC;IACxC,IAAI,SAAS;QAAE,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;SACvC,CAAC;QACJ,QAAQ,CACN,oCAAoC,EACpC,6GAA6G,CAC9G,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,MAAM,CAAI,IAAiB,EAAE,OAAmB;IAC9D,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,WAAiC,CAAC;IACtC,MAAM,MAAM,GAAiC;QAC3C,OAAO,EAAE,OAAO,CAAC,GAAG;QACpB,SAAS,EAAE,IAAI;QACf,QAAQ,CAAC,IAAI,EAAE,OAAO;YACpB,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAChB,IAAI,OAAO;gBAAE,WAAW,GAAG,OAAO,CAAC;QACrC,CAAC;KACF,CAAC;IAEF,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAA+B,aAAa,EAAE;QAC3D,MAAM;QACN,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,IAAI;KACf,CAAC,CACH,CAAC;IAEF,mBAAmB,EAAE,EAAE,SAAS,CAAC,GAAG,EAAE;QACpC,WAAW,EAAE,EAAE,CAAC;QAChB,WAAW,GAAG,SAAS,CAAC;IAC1B,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAI,KAAoB;IACvC,OAAO,OAAO,KAAK,KAAK,UAAU;QAChC,OAAQ,KAA4B,CAAC,GAAG,KAAK,UAAU;QACvD,OAAQ,KAA4B,CAAC,IAAI,KAAK,UAAU,CAAC;AAC7D,CAAC","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"]}
package/dist/src/css.d.ts CHANGED
@@ -52,3 +52,4 @@ export declare function adopt(root: ShadowRoot, ...sheets: CSSResult[]): void;
52
52
  * → 'x-button button { color: red }'
53
53
  */
54
54
  export declare function scopeStyles(tagName: string, sheet: CSSResult): CSSResult;
55
+ //# sourceMappingURL=css.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css.d.ts","sourceRoot":"","sources":["../../src/css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,SAAS,GAAG,aAAa,CAAC;AAItC;;;;;;;GAOG;AACH,wBAAgB,GAAG,CACjB,OAAO,EAAE,oBAAoB,EAC7B,GAAG,MAAM,EAAE,OAAO,EAAE,GACnB,SAAS,CAwBX;AAoBD;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CASrE;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAMpE;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,CA4BxE"}
package/dist/src/css.js CHANGED
@@ -16,6 +16,7 @@
16
16
  * - if the browser has @scope (Chrome 118+, Safari 17.4+), we wrap styles.
17
17
  * - fallback: prefix selectors with the tag — a simple regex at string level.
18
18
  */
19
+ const stylesheetText = new WeakMap();
19
20
  /**
20
21
  * Tagged literal for CSS. Returns a CSSStyleSheet ready for
21
22
  * adoptedStyleSheets. Value interpolation — only primitives or
@@ -34,8 +35,7 @@ export function css(strings, ...values) {
34
35
  continue;
35
36
  if (v instanceof CSSStyleSheet) {
36
37
  // composition — insert all rules
37
- for (const rule of v.cssRules)
38
- text += rule.cssText;
38
+ text += getStylesheetText(v) ?? rulesText(v);
39
39
  continue;
40
40
  }
41
41
  const s = String(v);
@@ -47,8 +47,24 @@ export function css(strings, ...values) {
47
47
  }
48
48
  const sheet = new CSSStyleSheet();
49
49
  sheet.replaceSync(text);
50
+ stylesheetText.set(sheet, text);
50
51
  return sheet;
51
52
  }
53
+ /** @internal */
54
+ export function createStylesheet(text) {
55
+ const sheet = new CSSStyleSheet();
56
+ sheet.replaceSync(text);
57
+ stylesheetText.set(sheet, text);
58
+ return sheet;
59
+ }
60
+ /** @internal */
61
+ export function getStylesheetText(sheet) {
62
+ return stylesheetText.get(sheet);
63
+ }
64
+ /** @internal */
65
+ export function getStylesheetHash(sheet) {
66
+ return stableHash(getStylesheetText(sheet) ?? rulesText(sheet));
67
+ }
52
68
  /**
53
69
  * Build an inline-style string from a CSS variables object.
54
70
  *
@@ -104,9 +120,7 @@ const hasScope = (() => {
104
120
  * → 'x-button button { color: red }'
105
121
  */
106
122
  export function scopeStyles(tagName, sheet) {
107
- let text = "";
108
- for (const rule of sheet.cssRules)
109
- text += rule.cssText;
123
+ const text = getStylesheetText(sheet) ?? rulesText(sheet);
110
124
  let scoped;
111
125
  if (hasScope) {
112
126
  scoped = `@scope (${tagName}) { ${text} }`;
@@ -132,6 +146,21 @@ export function scopeStyles(tagName, sheet) {
132
146
  }
133
147
  const out = new CSSStyleSheet();
134
148
  out.replaceSync(scoped);
149
+ stylesheetText.set(out, scoped);
135
150
  return out;
136
151
  }
152
+ function rulesText(sheet) {
153
+ let text = "";
154
+ for (const rule of sheet.cssRules)
155
+ text += rule.cssText;
156
+ return text;
157
+ }
158
+ function stableHash(text) {
159
+ let hash = 0x811c9dc5;
160
+ for (let i = 0; i < text.length; i++) {
161
+ hash ^= text.charCodeAt(i);
162
+ hash = Math.imul(hash, 0x01000193);
163
+ }
164
+ return (hash >>> 0).toString(36);
165
+ }
137
166
  //# sourceMappingURL=css.js.map