@madojs/mado 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/AGENTS.md +15 -8
  2. package/CHANGELOG.md +58 -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 +49 -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-types.d.ts","sourceRoot":"","sources":["../../../src/html/template-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;CACrC;AAED,eAAO,MAAM,gBAAgB,MAAO,OAAO,KAAG,CAAC,IAAI,cAC0B,CAAC;AAE9E;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,MAAM,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;IAChB,QAAQ,EAAE,oBAAoB,CAAC;CAChC"}
@@ -1 +1 @@
1
- {"version":3,"file":"template-types.js","sourceRoot":"","sources":["../../../src/html/template-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAU,EAAuB,EAAE,CAClE,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAoB,CAAC,KAAK,KAAK,IAAI,CAAC"}
1
+ {"version":3,"file":"template-types.js","sourceRoot":"","sources":["../../../src/html/template-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAU,EAAuB,EAAE,CAClE,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAoB,CAAC,KAAK,KAAK,IAAI,CAAC","sourcesContent":["/**\n * Small shared type module extracted from html.ts to avoid a cycle:\n * bindings.ts knows about TemplateResult / InstantiatedTemplate, template.ts\n * instantiates through them, and neither module depends on the other directly\n * (instantiate is passed into bindings as a parameter).\n */\n\nexport interface TemplateResult {\n readonly _mado: true;\n readonly strings: TemplateStringsArray;\n readonly values: readonly unknown[];\n}\n\nexport const isTemplateResult = (v: unknown): v is TemplateResult =>\n typeof v === \"object\" && v !== null && (v as TemplateResult)._mado === true;\n\n/**\n * Ready-to-use template instance: already cloned, bindings attached, nodes\n * extracted. Insert the fragment into the DOM, then call update() when values\n * change and dispose() when removing it.\n *\n * `_strings` lets keyed each decide whether an instance can be reused\n * (same tagged literal) or must be recreated.\n */\nexport interface InstantiatedTemplate {\n fragment: DocumentFragment;\n nodes: Node[];\n update(values: readonly unknown[]): void;\n dispose(): void;\n _strings: TemplateStringsArray;\n}\n"]}
@@ -9,6 +9,7 @@
9
9
  *
10
10
  * Only the glue lives here: parser and bindings are in neighbour files.
11
11
  */
12
+ import type { Disposer } from "../signal.js";
12
13
  import type { InstantiatedTemplate, TemplateResult } from "./template-types.js";
13
14
  /**
14
15
  * `html\`<div>${value}</div>\`` → template descriptor.
@@ -42,4 +43,7 @@ export declare function instantiate(result: TemplateResult): InstantiatedTemplat
42
43
  * Container can be either an Element or a ShadowRoot (used
43
44
  * in component() for rendering into a shadow tree).
44
45
  */
45
- export declare function render(result: TemplateResult, container: Element | ShadowRoot): void;
46
+ export declare function render(result: TemplateResult, container: Element | ShadowRoot): Disposer;
47
+ /** Dispose the template currently owned by a render container. */
48
+ export declare function unmount(container: Element | ShadowRoot): void;
49
+ //# sourceMappingURL=template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../../src/html/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAc7C,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACf,MAAM,qBAAqB,CAAC;AAG7B;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAClB,OAAO,EAAE,oBAAoB,EAC7B,GAAG,MAAM,EAAE,OAAO,EAAE,GACnB,cAAc,CAEhB;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,oBAAoB,CAsDxE;AAOD;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,CACpB,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,OAAO,GAAG,UAAU,GAC9B,QAAQ,CAqDV;AA+DD,kEAAkE;AAClE,wBAAgB,OAAO,CAAC,SAAS,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAK7D"}
@@ -102,13 +102,19 @@ const rendered = new WeakMap();
102
102
  * in component() for rendering into a shadow tree).
103
103
  */
104
104
  export function render(result, container) {
105
- const existing = rendered.get(container);
106
- if (existing && existing.nodes[0]?.parentNode === container) {
105
+ let existing = rendered.get(container);
106
+ if (existing && !isMountedIn(existing, container)) {
107
+ existing.dispose();
108
+ rendered.delete(container);
109
+ existing = undefined;
110
+ }
111
+ if (existing) {
107
112
  if (existing._strings === result.strings) {
108
113
  existing.update(result.values);
109
- return;
114
+ return renderDisposer(container, existing);
110
115
  }
111
116
  existing.dispose();
117
+ rendered.delete(container);
112
118
  }
113
119
  // Static snapshots write first-paint markup into #app and mark the
114
120
  // container. That markup is not hydrated: once the client app starts, Mado
@@ -128,16 +134,85 @@ export function render(result, container) {
128
134
  // any frame where the container is visibly empty.
129
135
  const inst = instantiate(result);
130
136
  if (isStaticContainer) {
137
+ const takeoverState = captureTakeoverState(container);
131
138
  // Order matters: remove the marker BEFORE inserting the live fragment so
132
139
  // newly-connecting Custom Elements no longer see a static ancestor and
133
140
  // run setup() exactly once.
134
141
  container.removeAttribute("data-mado-static");
135
142
  container.replaceChildren(inst.fragment);
136
143
  _flushDeferredStaticElements();
144
+ restoreTakeoverState(container, takeoverState);
137
145
  }
138
146
  else {
139
147
  container.appendChild(inst.fragment);
140
148
  }
141
149
  rendered.set(container, inst);
150
+ return renderDisposer(container, inst);
151
+ }
152
+ function captureTakeoverState(container) {
153
+ const active = document.activeElement;
154
+ return collectControls(container).map((control) => [
155
+ control.value,
156
+ "checked" in control ? control.checked : undefined,
157
+ control === active,
158
+ "selectionStart" in control ? control.selectionStart : undefined,
159
+ "selectionEnd" in control ? control.selectionEnd : undefined,
160
+ "selectionDirection" in control ? control.selectionDirection : undefined,
161
+ ]);
162
+ }
163
+ function restoreTakeoverState(container, states, attempts = 120) {
164
+ const controls = collectControls(container);
165
+ if (controls.length < states.length && attempts > 0) {
166
+ requestAnimationFrame(() => restoreTakeoverState(container, states, attempts - 1));
167
+ return;
168
+ }
169
+ for (let index = 0; index < states.length; index++) {
170
+ const state = states[index];
171
+ const control = controls[index];
172
+ if (!control)
173
+ continue;
174
+ if (!(control.localName === "input" && control.type === "file")) {
175
+ control.value = state[0];
176
+ }
177
+ if (state[1] !== undefined && "checked" in control)
178
+ control.checked = state[1];
179
+ if (state[2]) {
180
+ control.focus();
181
+ if (state[3] !== undefined && "setSelectionRange" in control) {
182
+ control.setSelectionRange?.(state[3], state[4] ?? null, state[5] ?? undefined);
183
+ }
184
+ }
185
+ }
186
+ }
187
+ function collectControls(root) {
188
+ const controls = [...root.querySelectorAll("input,select,textarea")];
189
+ for (const element of root.querySelectorAll("*")) {
190
+ if (element.shadowRoot)
191
+ controls.push(...collectControls(element.shadowRoot));
192
+ }
193
+ return controls;
194
+ }
195
+ /** Dispose the template currently owned by a render container. */
196
+ export function unmount(container) {
197
+ const instance = rendered.get(container);
198
+ if (!instance)
199
+ return;
200
+ rendered.delete(container);
201
+ instance.dispose();
202
+ }
203
+ function renderDisposer(container, instance) {
204
+ let disposed = false;
205
+ return () => {
206
+ if (disposed)
207
+ return;
208
+ disposed = true;
209
+ if (rendered.get(container) !== instance)
210
+ return;
211
+ unmount(container);
212
+ };
213
+ }
214
+ function isMountedIn(instance, container) {
215
+ return instance.nodes.length === 0 ||
216
+ instance.nodes.every((node) => node.parentNode === container);
142
217
  }
143
218
  //# sourceMappingURL=template.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/html/template.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/html/template.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Template instantiation + public `html\\`\\`` tag + `render()`.\n *\n * Data flow:\n * html`...` → TemplateResult { strings, values }\n * parseTemplate(strings) → ParsedTemplate { template, bindings } (cached by strings)\n * instantiate(result) → InstantiatedTemplate { fragment, nodes, update, dispose }\n * render(result, host) → clones or reuses instance in host\n *\n * Only the glue lives here: parser and bindings are in neighbour files.\n */\n\nimport type { Disposer } from \"../signal.js\";\nimport { warnOnce } from \"../diagnostics.js\";\nimport {\n parseTemplate,\n resolvePath,\n type AttrBindingSpec,\n} from \"./parser.js\";\nimport {\n bindAttr,\n bindChild,\n createChildState,\n disposeChildState,\n type ChildState,\n} from \"./bindings.js\";\nimport type {\n InstantiatedTemplate,\n TemplateResult,\n} from \"./template-types.js\";\nimport { _flushDeferredStaticElements } from \"../component.js\";\n\n/**\n * `html\\`<div>${value}</div>\\`` → template descriptor.\n *\n * By itself renders and parses NOTHING — this simply captures\n * { strings, values } from the tagged-template literal. The heavy work\n * (parsing + cloning) happens on the first `render()` /\n * `instantiate()` call and is cached by `strings` identity.\n */\nexport function html(\n strings: TemplateStringsArray,\n ...values: unknown[]\n): TemplateResult {\n return { _mado: true, strings, values };\n}\n\n/**\n * Create a ready template instance: clones the pre-parsed template,\n * resolves all BindingSpec → concrete DOM nodes of the clone, binds\n * initial values. The returned object is self-contained: update(values)\n * patches only what actually changed, dispose() cleans up effects\n * and removes nodes from the DOM.\n *\n * Exported (not only used from render()) so that\n * keyed `each` reconciliation can manage instance lifetimes directly.\n */\nexport function instantiate(result: TemplateResult): InstantiatedTemplate {\n const parsed = parseTemplate(result.strings);\n const fragment = parsed.template.content.cloneNode(true) as DocumentFragment;\n\n const disposers: Disposer[] = [];\n const childStates: Map<number, ChildState> = new Map();\n const attrBound: Map<number, { el: Element; spec: AttrBindingSpec }> =\n new Map();\n\n // Resolve all BindingSpec.path → concrete nodes of the cloned\n // fragment. This is done ONCE, in the instance creation phase.\n for (const b of parsed.bindings) {\n if (b.type === \"child\") {\n const parent = resolvePath(fragment, b.path);\n const placeholder = parent.childNodes[b.childIndex] as Comment;\n childStates.set(b.id, createChildState(placeholder));\n } else {\n const el = resolvePath(fragment, b.path) as Element;\n attrBound.set(b.id, { el, spec: b });\n }\n }\n\n const update = (values: readonly unknown[]) => {\n // Before each update unsubscribe from the previous pass's subscriptions.\n // This is needed because one of the values might have been a signal\n // but now is not (or vice versa), and we need a fresh re-subscribe.\n for (const d of disposers.splice(0)) d();\n\n for (const b of parsed.bindings) {\n if (b.type === \"child\") {\n const st = childStates.get(b.id)!;\n bindChild(st, values[b.slot], disposers, instantiate);\n } else {\n const ab = attrBound.get(b.id)!;\n bindAttr(ab.el, ab.spec, values, disposers);\n }\n }\n };\n\n update(result.values);\n\n const nodes = [...fragment.childNodes];\n\n return {\n fragment,\n nodes,\n update,\n dispose() {\n for (const d of disposers.splice(0)) d();\n for (const st of childStates.values()) disposeChildState(st);\n for (const n of nodes) n.parentNode?.removeChild(n);\n },\n _strings: result.strings,\n };\n}\n\n// ---------- Public render ----------\n\n\nconst rendered = new WeakMap<Element | ShadowRoot, InstantiatedTemplate>();\n\n/**\n * Render a TemplateResult into a container.\n *\n * Semantics:\n * - first call → instantiate + appendChild;\n * - repeated call with the same tagged literal (same strings identity) →\n * update(values), DOM is not recreated;\n * - repeated call with a different literal → dispose old + new instantiate.\n *\n * Container can be either an Element or a ShadowRoot (used\n * in component() for rendering into a shadow tree).\n */\nexport function render(\n result: TemplateResult,\n container: Element | ShadowRoot,\n): Disposer {\n let existing = rendered.get(container);\n if (existing && !isMountedIn(existing, container)) {\n existing.dispose();\n rendered.delete(container);\n existing = undefined;\n }\n if (existing) {\n if (existing._strings === result.strings) {\n existing.update(result.values);\n return renderDisposer(container, existing);\n }\n existing.dispose();\n rendered.delete(container);\n }\n\n // Static snapshots write first-paint markup into #app and mark the\n // container. That markup is not hydrated: once the client app starts, Mado\n // owns the container again and atomically replaces it with live bindings.\n const isStaticContainer =\n !existing &&\n \"hasAttribute\" in container &&\n container.hasAttribute(\"data-mado-static\");\n\n if (!isStaticContainer && !existing && container.childNodes.length > 0) {\n warnOnce(\n \"render-unmanaged-dom\",\n \"render() called on a container with existing DOM that was not created by Mado. It will remain alongside the new render output.\",\n );\n }\n\n // Build the live fragment OFF-DOM first. This guarantees that:\n // - any new Custom Element inside the fragment is parsed and constructed\n // but its connectedCallback() does not fire until insertion,\n // - the old static tree (whose deferred children skipped setup()) is\n // still in the DOM as inert first-paint markup,\n // - we then swap children atomically with replaceChildren(), avoiding\n // any frame where the container is visibly empty.\n const inst = instantiate(result);\n if (isStaticContainer) {\n const takeoverState = captureTakeoverState(container);\n // Order matters: remove the marker BEFORE inserting the live fragment so\n // newly-connecting Custom Elements no longer see a static ancestor and\n // run setup() exactly once.\n container.removeAttribute(\"data-mado-static\");\n container.replaceChildren(inst.fragment);\n _flushDeferredStaticElements();\n restoreTakeoverState(container, takeoverState);\n } else {\n container.appendChild(inst.fragment);\n }\n rendered.set(container, inst);\n return renderDisposer(container, inst);\n}\n\ntype Control = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;\ntype ControlState = [\n value: string,\n checked: boolean | undefined,\n focused: boolean,\n selectionStart: number | null | undefined,\n selectionEnd: number | null | undefined,\n selectionDirection: \"forward\" | \"backward\" | \"none\" | null | undefined,\n];\n\nfunction captureTakeoverState(container: Element | ShadowRoot): ControlState[] {\n const active = document.activeElement;\n return collectControls(container).map((control) => [\n control.value,\n \"checked\" in control ? control.checked : undefined,\n control === active,\n \"selectionStart\" in control ? control.selectionStart : undefined,\n \"selectionEnd\" in control ? control.selectionEnd : undefined,\n \"selectionDirection\" in control ? control.selectionDirection : undefined,\n ]);\n}\n\nfunction restoreTakeoverState(\n container: Element | ShadowRoot,\n states: ControlState[],\n attempts = 120,\n): void {\n const controls = collectControls(container);\n if (controls.length < states.length && attempts > 0) {\n requestAnimationFrame(() => restoreTakeoverState(container, states, attempts - 1));\n return;\n }\n for (let index = 0; index < states.length; index++) {\n const state = states[index]!;\n const control = controls[index];\n if (!control) continue;\n if (!(control.localName === \"input\" && (control as HTMLInputElement).type === \"file\")) {\n control.value = state[0];\n }\n if (state[1] !== undefined && \"checked\" in control) control.checked = state[1];\n if (state[2]) {\n control.focus();\n if (state[3] !== undefined && \"setSelectionRange\" in control) {\n control.setSelectionRange?.(\n state[3],\n state[4] ?? null,\n state[5] ?? undefined,\n );\n }\n }\n }\n}\n\nfunction collectControls(root: Element | ShadowRoot): Control[] {\n const controls = [...root.querySelectorAll<Control>(\"input,select,textarea\")];\n for (const element of root.querySelectorAll(\"*\")) {\n if (element.shadowRoot) controls.push(...collectControls(element.shadowRoot));\n }\n return controls;\n}\n\n/** Dispose the template currently owned by a render container. */\nexport function unmount(container: Element | ShadowRoot): void {\n const instance = rendered.get(container);\n if (!instance) return;\n rendered.delete(container);\n instance.dispose();\n}\n\nfunction renderDisposer(\n container: Element | ShadowRoot,\n instance: InstantiatedTemplate,\n): Disposer {\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n if (rendered.get(container) !== instance) return;\n unmount(container);\n };\n}\n\nfunction isMountedIn(\n instance: InstantiatedTemplate,\n container: Element | ShadowRoot,\n): boolean {\n return instance.nodes.length === 0 ||\n instance.nodes.every((node) => node.parentNode === container);\n}\n"]}
@@ -12,14 +12,14 @@
12
12
  */
13
13
  export { signal, computed, effect, untracked, batch, flushSync, } from "./signal.js";
14
14
  export type { Signal, Computed, ComputedOptions, Disposer } from "./signal.js";
15
- export { html, render, instantiate } from "./html/template.js";
16
- export { unsafeHTML, ref, classMap, styleMap, isHtmlDirective, } from "./html/bindings.js";
15
+ export { html, render, unmount } from "./html/template.js";
16
+ export { unsafeHTML, ref, classMap, styleMap, } from "./html/bindings.js";
17
17
  export type { TemplateResult } from "./html/template-types.js";
18
18
  export type { HtmlDirective, UnsafeHTMLDirective, RefCallback, RefDirective, ClassMap, ClassMapDirective, StyleMap, StyleMapDirective, } from "./html/bindings.js";
19
- export { each, list } from "./each.js";
19
+ export { each } from "./each.js";
20
20
  export { component } from "./component.js";
21
21
  export type { ComponentContext, ComponentOptions, SetupFn, StyleInput, } from "./component.js";
22
- export { css, cssVars, adopt, scopeStyles } from "./css.js";
22
+ export { css, cssVars } from "./css.js";
23
23
  export type { CSSResult } from "./css.js";
24
24
  export { router, queryParam, navigate, } from "./router/navigation.js";
25
25
  export { routes, prefetchPath } from "./router/manifest.js";
@@ -27,18 +27,17 @@ export { appBase, routeUrl } from "./router/base.js";
27
27
  export type { RouterApi, QueryParam, QuerySignal, } from "./router/navigation.js";
28
28
  export type { RouteHandler, RouteParams, Routes, RoutesMap, } from "./router/match.js";
29
29
  export type { RoutesOptions } from "./router/manifest.js";
30
- export { page, layout, isPage, isLayoutGroup } from "./page.js";
30
+ export { page, layout } from "./page.js";
31
31
  export type { Page, PageContext, PageData, RouteEntry, LayoutRoutes, HeadMeta, StaticPageConfig, Guard, GuardResult, JsonPrimitive, JsonValue, } from "./page.js";
32
32
  export { applyHead } from "./head.js";
33
33
  export { resource, mutation, invalidate, jsonFetcher, HttpError, } from "./resource.js";
34
34
  export type { Resource, ResourceOptions, Mutation, MutationOptions, } from "./resource.js";
35
35
  export { useForm } from "./forms.js";
36
- export type { FormApi, FormValue, FormValues, FormErrors, FieldValidator, AsyncFieldValidator, FieldSchema, FieldArrayApi, Schema, UseFormOptions, } from "./forms.js";
36
+ export type { FormApi, FormValue, FormValues, FormErrors, FormTouched, FormValidator, FormValidationContext, UseFormOptions, } from "./forms.js";
37
37
  export { createContext, provide, inject } from "./context.js";
38
38
  export type { Context } from "./context.js";
39
39
  export { persisted } from "./persisted.js";
40
40
  export type { PersistedOptions, PersistedSignal } from "./persisted.js";
41
- export { lazy } from "./lazy.js";
42
- export type { LazyOptions } from "./lazy.js";
43
41
  export { getCurrentLifecycle, runInLifecycle, createLifecycle, } from "./lifecycle.js";
44
42
  export type { Lifecycle, LifecycleHandle } from "./lifecycle.js";
43
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EACL,MAAM,EACN,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,SAAS,GACV,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG/E,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EACL,UAAU,EACV,GAAG,EACH,QAAQ,EACR,QAAQ,GACT,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACR,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,OAAO,EACP,UAAU,GACX,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,EACL,MAAM,EACN,UAAU,EACV,QAAQ,GACT,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAO5D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,YAAY,EACV,SAAS,EACT,UAAU,EACV,WAAW,GACZ,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,YAAY,EACZ,WAAW,EACX,MAAM,EACN,SAAS,GACV,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACzC,YAAY,EACV,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,KAAK,EACL,WAAW,EACX,aAAa,EACb,SAAS,GACV,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,WAAW,EACX,SAAS,GACV,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,eAAe,GAChB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,YAAY,EACV,OAAO,EACP,SAAS,EACT,UAAU,EACV,UAAU,EACV,WAAW,EACX,aAAa,EACb,qBAAqB,EACrB,cAAc,GACf,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC9D,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG5C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAOxE,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/src/index.js CHANGED
@@ -13,13 +13,13 @@
13
13
  // --- core ---
14
14
  export { signal, computed, effect, untracked, batch, flushSync, } from "./signal.js";
15
15
  // --- rendering ---
16
- export { html, render, instantiate } from "./html/template.js";
17
- export { unsafeHTML, ref, classMap, styleMap, isHtmlDirective, } from "./html/bindings.js";
18
- export { each, list } from "./each.js";
16
+ export { html, render, unmount } from "./html/template.js";
17
+ export { unsafeHTML, ref, classMap, styleMap, } from "./html/bindings.js";
18
+ export { each } from "./each.js";
19
19
  // --- components ---
20
20
  export { component } from "./component.js";
21
21
  // --- styles ---
22
- export { css, cssVars, adopt, scopeStyles } from "./css.js";
22
+ export { css, cssVars } from "./css.js";
23
23
  // --- routing ---
24
24
  export { router, queryParam, navigate, } from "./router/navigation.js";
25
25
  export { routes, prefetchPath } from "./router/manifest.js";
@@ -30,7 +30,7 @@ export { routes, prefetchPath } from "./router/manifest.js";
30
30
  // implementation details: they remain unexported until a concrete
31
31
  // downstream use case appears.
32
32
  export { appBase, routeUrl } from "./router/base.js";
33
- export { page, layout, isPage, isLayoutGroup } from "./page.js";
33
+ export { page, layout } from "./page.js";
34
34
  export { applyHead } from "./head.js";
35
35
  // --- data ---
36
36
  export { resource, mutation, invalidate, jsonFetcher, HttpError, } from "./resource.js";
@@ -40,8 +40,6 @@ export { useForm } from "./forms.js";
40
40
  export { createContext, provide, inject } from "./context.js";
41
41
  // --- persistence ---
42
42
  export { persisted } from "./persisted.js";
43
- // --- lazy ---
44
- export { lazy } from "./lazy.js";
45
43
  // --- lifecycle (advanced API) ---
46
44
  //
47
45
  // Normally you don't need this: inside component() the lifecycle is created
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAe;AACf,OAAO,EACL,MAAM,EACN,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,SAAS,GACV,MAAM,aAAa,CAAC;AAGrB,oBAAoB;AACpB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EACL,UAAU,EACV,GAAG,EACH,QAAQ,EACR,QAAQ,EACR,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAa5B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEvC,qBAAqB;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAQ3C,iBAAiB;AACjB,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAG5D,kBAAkB;AAClB,OAAO,EACL,MAAM,EACN,UAAU,EACV,QAAQ,GACT,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC5D,oEAAoE;AACpE,qEAAqE;AACrE,uEAAuE;AACvE,8DAA8D;AAC9D,kEAAkE;AAClE,+BAA+B;AAC/B,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAcrD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAehE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,eAAe;AACf,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,WAAW,EACX,SAAS,GACV,MAAM,eAAe,CAAC;AAQvB,gBAAgB;AAChB,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAcrC,aAAa;AACb,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG9D,sBAAsB;AACtB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,eAAe;AACf,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,mCAAmC;AACnC,EAAE;AACF,4EAA4E;AAC5E,8DAA8D;AAC9D,2DAA2D;AAC3D,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,eAAe,GAChB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAe;AACf,OAAO,EACL,MAAM,EACN,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,SAAS,GACV,MAAM,aAAa,CAAC;AAGrB,oBAAoB;AACpB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EACL,UAAU,EACV,GAAG,EACH,QAAQ,EACR,QAAQ,GACT,MAAM,oBAAoB,CAAC;AAa5B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,qBAAqB;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAQ3C,iBAAiB;AACjB,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGxC,kBAAkB;AAClB,OAAO,EACL,MAAM,EACN,UAAU,EACV,QAAQ,GACT,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC5D,oEAAoE;AACpE,qEAAqE;AACrE,uEAAuE;AACvE,8DAA8D;AAC9D,kEAAkE;AAClE,+BAA+B;AAC/B,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAcrD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAezC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,eAAe;AACf,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,WAAW,EACX,SAAS,GACV,MAAM,eAAe,CAAC;AAQvB,gBAAgB;AAChB,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAYrC,aAAa;AACb,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG9D,sBAAsB;AACtB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,mCAAmC;AACnC,EAAE;AACF,4EAA4E;AAC5E,8DAA8D;AAC9D,2DAA2D;AAC3D,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,eAAe,GAChB,MAAM,gBAAgB,CAAC","sourcesContent":["/**\n * Mado — public API.\n *\n * Import everything from one place:\n * import { signal, computed, effect, component, html, router } from \"@madojs/mado\";\n *\n * Vite is the canonical transport: every official starter, generator\n * and CLI command resolves `@madojs/mado` from `node_modules` through\n * the Vite plugin (`@madojs/mado/vite`). Native ESM consumers (tests,\n * partial-SSR probes) get the same surface area; anything that touches\n * `import.meta.env.BASE_URL` falls back to \"/\" outside Vite.\n */\n\n// --- core ---\nexport {\n signal,\n computed,\n effect,\n untracked,\n batch,\n flushSync,\n} from \"./signal.js\";\nexport type { Signal, Computed, ComputedOptions, Disposer } from \"./signal.js\";\n\n// --- rendering ---\nexport { html, render, unmount } from \"./html/template.js\";\nexport {\n unsafeHTML,\n ref,\n classMap,\n styleMap,\n} from \"./html/bindings.js\";\nexport type { TemplateResult } from \"./html/template-types.js\";\nexport type {\n HtmlDirective,\n UnsafeHTMLDirective,\n RefCallback,\n RefDirective,\n ClassMap,\n ClassMapDirective,\n StyleMap,\n StyleMapDirective,\n} from \"./html/bindings.js\";\n\nexport { each } from \"./each.js\";\n\n// --- components ---\nexport { component } from \"./component.js\";\nexport type {\n ComponentContext,\n ComponentOptions,\n SetupFn,\n StyleInput,\n} from \"./component.js\";\n\n// --- styles ---\nexport { css, cssVars } from \"./css.js\";\nexport type { CSSResult } from \"./css.js\";\n\n// --- routing ---\nexport {\n router,\n queryParam,\n navigate,\n} from \"./router/navigation.js\";\nexport { routes, prefetchPath } from \"./router/manifest.js\";\n// Application code should use `routeUrl()` exclusively for building\n// internal links. `appBase` is exposed for integrations that need to\n// read the active prefix (e.g. building absolute canonical URLs from a\n// component). `normalizeBase`, `stripBase` and `withBase` are\n// implementation details: they remain unexported until a concrete\n// downstream use case appears.\nexport { appBase, routeUrl } from \"./router/base.js\";\nexport type {\n RouterApi,\n QueryParam,\n QuerySignal,\n} from \"./router/navigation.js\";\nexport type {\n RouteHandler,\n RouteParams,\n Routes,\n RoutesMap,\n} from \"./router/match.js\";\nexport type { RoutesOptions } from \"./router/manifest.js\";\n\nexport { page, layout } from \"./page.js\";\nexport type {\n Page,\n PageContext,\n PageData,\n RouteEntry,\n LayoutRoutes,\n HeadMeta,\n StaticPageConfig,\n Guard,\n GuardResult,\n JsonPrimitive,\n JsonValue,\n} from \"./page.js\";\n\nexport { applyHead } from \"./head.js\";\n\n// --- data ---\nexport {\n resource,\n mutation,\n invalidate,\n jsonFetcher,\n HttpError,\n} from \"./resource.js\";\nexport type {\n Resource,\n ResourceOptions,\n Mutation,\n MutationOptions,\n} from \"./resource.js\";\n\n// --- forms ---\nexport { useForm } from \"./forms.js\";\nexport type {\n FormApi,\n FormValue,\n FormValues,\n FormErrors,\n FormTouched,\n FormValidator,\n FormValidationContext,\n UseFormOptions,\n} from \"./forms.js\";\n\n// --- DI ---\nexport { createContext, provide, inject } from \"./context.js\";\nexport type { Context } from \"./context.js\";\n\n// --- persistence ---\nexport { persisted } from \"./persisted.js\";\nexport type { PersistedOptions, PersistedSignal } from \"./persisted.js\";\n\n// --- lifecycle (advanced API) ---\n//\n// Normally you don't need this: inside component() the lifecycle is created\n// automatically. Only needed if you want to use resource() or\n// other helpers outside a component and still get cleanup.\nexport {\n getCurrentLifecycle,\n runInLifecycle,\n createLifecycle,\n} from \"./lifecycle.js\";\nexport type { Lifecycle, LifecycleHandle } from \"./lifecycle.js\";\n"]}
@@ -0,0 +1,3 @@
1
+ /** Serialize JSON for an inline script text context without allowing `</script>`. */
2
+ export declare function serializeJsonForScript(value: unknown): string;
3
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/json.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAW7D"}
@@ -0,0 +1,14 @@
1
+ /** Serialize JSON for an inline script text context without allowing `</script>`. */
2
+ export function serializeJsonForScript(value) {
3
+ const json = JSON.stringify(value);
4
+ if (json === undefined) {
5
+ throw new TypeError("value cannot be serialized as JSON");
6
+ }
7
+ return json
8
+ .replace(/</g, "\\u003C")
9
+ .replace(/>/g, "\\u003E")
10
+ .replace(/&/g, "\\u0026")
11
+ .replace(/\u2028/g, "\\u2028")
12
+ .replace(/\u2029/g, "\\u2029");
13
+ }
14
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/json.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,MAAM,UAAU,sBAAsB,CAAC,KAAc;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,SAAS,CAAC,oCAAoC,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,IAAI;SACR,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC;SAC7B,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;AACnC,CAAC","sourcesContent":["/** Serialize JSON for an inline script text context without allowing `</script>`. */\nexport function serializeJsonForScript(value: unknown): string {\n const json = JSON.stringify(value);\n if (json === undefined) {\n throw new TypeError(\"value cannot be serialized as JSON\");\n }\n return json\n .replace(/</g, \"\\\\u003C\")\n .replace(/>/g, \"\\\\u003E\")\n .replace(/&/g, \"\\\\u0026\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n"]}
@@ -43,3 +43,4 @@ export interface LifecycleHandle extends Lifecycle {
43
43
  dispose(): void;
44
44
  }
45
45
  export declare function createLifecycle(): LifecycleHandle;
46
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,MAAM,WAAW,SAAS;IACxB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC/B;AAID;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,SAAS,GAAG,IAAI,CAEtD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAQ/D;AAED;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,eAAe,IAAI,eAAe,CAmCjD"}
@@ -20,6 +20,7 @@
20
20
  * if (lc) lc.onDispose(() => abort.abort());
21
21
  * else console.warn('[mado] resource() outside component — cleanup must be manual');
22
22
  */
23
+ import { reportError } from "./diagnostics.js";
23
24
  let current = null;
24
25
  /**
25
26
  * Return the currently active lifecycle, or null if code runs
@@ -57,7 +58,7 @@ export function createLifecycle() {
57
58
  }
58
59
  catch (err) {
59
60
  // eslint-disable-next-line no-console
60
- console.error("[mado] cleanup threw:", err);
61
+ reportError("lifecycle", "cleanup", "cleanup threw", err);
61
62
  }
62
63
  return;
63
64
  }
@@ -74,7 +75,7 @@ export function createLifecycle() {
74
75
  }
75
76
  catch (err) {
76
77
  // eslint-disable-next-line no-console
77
- console.error("[mado] cleanup threw:", err);
78
+ reportError("lifecycle", "cleanup", "cleanup threw", err);
78
79
  }
79
80
  }
80
81
  disposers.length = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AASH,IAAI,OAAO,GAAqB,IAAI,CAAC;AAErC;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAI,EAAa,EAAE,EAAW;IAC1D,MAAM,IAAI,GAAG,OAAO,CAAC;IACrB,OAAO,GAAG,EAAE,CAAC;IACb,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;AACH,CAAC;AAUD,MAAM,UAAU,eAAe;IAC7B,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,OAAO;QACL,SAAS,CAAC,EAAE;YACV,oEAAoE;YACpE,qEAAqE;YACrE,wEAAwE;YACxE,wDAAwD;YACxD,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,CAAC;oBACH,EAAE,EAAE,CAAC;gBACP,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,sCAAsC;oBACtC,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;gBAC9C,CAAC;gBACD,OAAO;YACT,CAAC;YACD,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC;QACD,OAAO;YACL,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,qCAAqC;YACrC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC/C,IAAI,CAAC;oBACH,SAAS,CAAC,CAAC,CAAE,EAAE,CAAC;gBAClB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,sCAAsC;oBACtC,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;YACD,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAO/C,IAAI,OAAO,GAAqB,IAAI,CAAC;AAErC;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAI,EAAa,EAAE,EAAW;IAC1D,MAAM,IAAI,GAAG,OAAO,CAAC;IACrB,OAAO,GAAG,EAAE,CAAC;IACb,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;AACH,CAAC;AAUD,MAAM,UAAU,eAAe;IAC7B,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,OAAO;QACL,SAAS,CAAC,EAAE;YACV,oEAAoE;YACpE,qEAAqE;YACrE,wEAAwE;YACxE,wDAAwD;YACxD,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,CAAC;oBACH,EAAE,EAAE,CAAC;gBACP,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,sCAAsC;oBACtC,WAAW,CAAC,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC;gBAC5D,CAAC;gBACD,OAAO;YACT,CAAC;YACD,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC;QACD,OAAO;YACL,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,qCAAqC;YACrC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC/C,IAAI,CAAC;oBACH,SAAS,CAAC,CAAC,CAAE,EAAE,CAAC;gBAClB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,sCAAsC;oBACtC,WAAW,CAAC,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC;gBAC5D,CAAC;YACH,CAAC;YACD,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Lifecycle context for auto-cleanup of resources inside a component.\n *\n * Core idea: when a component-setup runs, we push the current\n * \"lifecycle\" — an object with onDispose() — onto a module-local stack.\n * Any function like resource() that creates long-lived subscriptions\n * (timers, listeners, network subscriptions) can call getCurrentLifecycle()\n * and register its own cleanup.\n *\n * This avoids leaks on component unmount — without explicitly threading\n * ComponentContext into every helper.\n *\n * Usage:\n *\n * // in component.ts\n * runInLifecycle(myLifecycle, () => setup(ctx));\n *\n * // in resource.ts\n * const lc = getCurrentLifecycle();\n * if (lc) lc.onDispose(() => abort.abort());\n * else console.warn('[mado] resource() outside component — cleanup must be manual');\n */\n\nimport type { Disposer } from \"./signal.js\";\nimport { reportError } from \"./diagnostics.js\";\n\nexport interface Lifecycle {\n /** Register a cleanup function. Called when the lifecycle is disposed. */\n onDispose(fn: Disposer): void;\n}\n\nlet current: Lifecycle | null = null;\n\n/**\n * Return the currently active lifecycle, or null if code runs\n * outside a component setup.\n */\nexport function getCurrentLifecycle(): Lifecycle | null {\n return current;\n}\n\n/**\n * Execute fn with the given lifecycle set. Supports nesting:\n * the previous lifecycle is restored after fn returns (including exceptions).\n */\nexport function runInLifecycle<T>(lc: Lifecycle, fn: () => T): T {\n const prev = current;\n current = lc;\n try {\n return fn();\n } finally {\n current = prev;\n }\n}\n\n/**\n * Create a new lifecycle. Returns the Lifecycle interface and a\n * dispose() method that runs all registered cleanup callbacks.\n */\nexport interface LifecycleHandle extends Lifecycle {\n dispose(): void;\n}\n\nexport function createLifecycle(): LifecycleHandle {\n const disposers: Disposer[] = [];\n let disposed = false;\n return {\n onDispose(fn) {\n // If the lifecycle is already disposed, run the cleanup immediately\n // rather than dropping it silently. Async page code that registers a\n // cleanup after navigation (e.g. in a resolved promise) still gets torn\n // down. Matches Solid/Vue. (FABLE_REPORT.md finding #9)\n if (disposed) {\n try {\n fn();\n } catch (err) {\n // eslint-disable-next-line no-console\n reportError(\"lifecycle\", \"cleanup\", \"cleanup threw\", err);\n }\n return;\n }\n disposers.push(fn);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n // reverse order — LIFO, like a stack\n for (let i = disposers.length - 1; i >= 0; i--) {\n try {\n disposers[i]!();\n } catch (err) {\n // eslint-disable-next-line no-console\n reportError(\"lifecycle\", \"cleanup\", \"cleanup threw\", err);\n }\n }\n disposers.length = 0;\n },\n };\n}\n"]}
@@ -257,3 +257,4 @@ export interface LayoutRoutes {
257
257
  */
258
258
  export declare function layout(spec: Omit<LayoutRoutes, "_layout">): LayoutRoutes;
259
259
  export declare const isLayoutGroup: (v: unknown) => v is LayoutRoutes;
260
+ //# sourceMappingURL=page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/page.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAkMH;;;;;;;;GAQG;AACH,MAAM,UAAU,IAAI,CAIlB,IAAkC;IAClC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAU,EAAa,EAAE,CAC9C,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAU,CAAC,KAAK,KAAK,IAAI,CAAC;AAmCpE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,MAAM,CAAC,IAAmC;IACxD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAU,EAAqB,EAAE,CAC7D,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAkB,CAAC,OAAO,KAAK,IAAI,CAAC"}
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAkMH;;;;;;;;GAQG;AACH,MAAM,UAAU,IAAI,CAIlB,IAAkC;IAClC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAU,EAAa,EAAE,CAC9C,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAU,CAAC,KAAK,KAAK,IAAI,CAAC;AAmCpE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,MAAM,CAAC,IAAmC;IACxD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAU,EAAqB,EAAE,CAC7D,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAkB,CAAC,OAAO,KAAK,IAAI,CAAC","sourcesContent":["/**\n * Page contract — a uniform format for describing a page.\n *\n * // pages/user-profile.ts\n * import { page, html, resource, jsonFetcher } from '@madojs/mado';\n *\n * export default page({\n * title: ({ id }) => `User #${id}`,\n * load: ({ id }) => resource(() => `/api/users/${id}`, jsonFetcher()),\n * view: ({ params, data }) => html`\n * <h1>${() => data?.data()?.name ?? '\\u2026'}</h1>\n * `,\n * });\n *\n * // app.routes.ts\n * import { routes } from '@madojs/mado';\n *\n * export default routes({\n * '/users/:id': () => import('./modules/users/pages/user-profile.page.js'),\n * });\n *\n * The contract is strict: exactly three slots — title / load / view.\n * If a page exports something other than Page — tsc will stop it before build.\n * \"One right way\" — as in Rust: fewer branches, fewer bugs.\n */\n\nimport type { TemplateResult } from \"./html/template-types.js\";\n\n/** Route params (values from :placeholders). */\nexport type RouteParams = Record<string, string>;\n\n/**\n * JSON-compatible value. Static seeds and any data crossing the build-time →\n * snapshot → client boundary must satisfy this contract: only primitives,\n * plain arrays and plain object trees. Date, Map, Set, class instances,\n * functions, undefined values and circular references are not allowed and\n * fail at serialization time with a route-tagged error.\n */\nexport type JsonPrimitive = string | number | boolean | null;\nexport type JsonValue =\n | JsonPrimitive\n | JsonValue[]\n | { [key: string]: JsonValue };\n\n/**\n * Loaded data. If load returned a Resource — this is `Resource<T>`,\n * otherwise — a plain value. The view decides what to do with it.\n */\nexport type PageData<D> = D;\n\nexport interface PageContext<P extends RouteParams, D> {\n /** URL parameters. */\n params: P;\n /** Result of load() — usually Resource<T> with data/error/loading. */\n data: D;\n /** Current path (as a signal function). */\n path: () => string;\n /**\n * Child view (for layout components).\n * For regular pages always null.\n */\n child: TemplateResult | null;\n /**\n * Register cleanup that runs when the page navigates away.\n * Use for timers, manual subscriptions, or anything not automatically\n * managed by resource()/effect().\n *\n * view: ({ onDispose }) => {\n * const id = setInterval(tick, 3000);\n * onDispose(() => clearInterval(id));\n * return html`...`;\n * }\n */\n onDispose?: (fn: () => void) => void;\n}\n\n/**\n * Metadata for <head>. Static snapshots write it into HTML, and the SPA\n * runtime updates it on route changes.\n */\nexport interface HeadMeta {\n /** If set — overrides page.title. */\n title?: string;\n description?: string;\n /** Canonical URL — important for SEO with duplicate content. */\n canonical?: string;\n /** OpenGraph for social networks. */\n og?: {\n title?: string;\n description?: string;\n image?: string;\n type?: string;\n url?: string;\n };\n /** Twitter Cards (inherits og.* if not set). */\n twitter?: {\n card?: \"summary\" | \"summary_large_image\";\n title?: string;\n description?: string;\n image?: string;\n };\n /** Arbitrary meta tags: { name: 'robots', content: 'index,follow' } */\n meta?: Array<{ name?: string; property?: string; content: string }>;\n /** Arbitrary link tags: { rel: 'alternate', href: '/en/...' } */\n link?: Array<{ rel: string; href: string; hreflang?: string }>;\n /** JSON-LD structure (Schema.org). Will be inserted as <script type=\"application/ld+json\">. */\n jsonLd?: unknown;\n}\n\n/**\n * Static snapshot configuration: gives the build script enough information\n * to enumerate public routes and seed browser-rendered snapshots.\n *\n * `S` is the JSON-safe seed type (build-time → snapshot → client boundary).\n * It is intentionally decoupled from the runtime `D` data type: a seed of\n * type `Product` is fine even when `page.load()` returns `Resource<Product>`.\n *\n * `static.paths` and `static.initialData` MUST be browser-bundle-safe,\n * build-executable, and secret-free. They are executed during build\n * discovery, but their module also remains in the client bundle: do not\n * import Node-only APIs, do not embed credentials, do not perform side\n * effects at module top level.\n */\nexport interface StaticPageConfig<\n P extends RouteParams,\n S extends JsonValue,\n> {\n /**\n * Route parameter sets to materialize.\n *\n * Optional for literal routes such as \"/\".\n * Required for dynamic routes such as \"/products/:slug\".\n */\n paths?: () => Promise<P[]> | P[];\n /**\n * Optional build-time seed. Passed to page.head(params, seed)\n * and page.load(params, seed).\n *\n * Used both at build time (to render the snapshot) and at client boot\n * (so the runtime does not re-fetch what the snapshot already knows).\n * The seed is consumed exactly once per route commit.\n */\n initialData?: (params: P) => Promise<S> | S;\n}\n\n/**\n * Guard verdict.\n *\n * undefined / void / true → pass: continue to the next guard or render the page.\n * false / { halt: true } → stop: render nothing.\n * string → redirect to that path.\n * { redirect: url } → navigate to `url`.\n */\nexport type GuardResult =\n | void\n | undefined\n | boolean\n | string\n | { halt: true }\n | { redirect: string; replace?: boolean };\n\n/**\n * Guard function. Runs before a page (or any page in a layout group) is rendered.\n *\n * const requireAuth: Guard = ({ path }) => {\n * if (isLoggedIn()) return;\n * return { redirect: `/login?return=${encodeURIComponent(path)}` };\n * };\n *\n * Guards can be async. They are evaluated in order: each parent group's guards\n * first (outer → inner), then the page's own guards. The first non-pass verdict\n * wins.\n */\nexport type Guard = (ctx: {\n params: RouteParams;\n path: string;\n}) => GuardResult | Promise<GuardResult>;\n\nexport interface Page<\n P extends RouteParams = RouteParams,\n D = unknown,\n S extends JsonValue = JsonValue,\n> {\n readonly _page: true;\n title?: string | ((params: P) => string);\n /**\n * Runtime data loader. Receives params and the optional static seed\n * (only present on the first client boot of a static route, never\n * during SPA navigation).\n */\n load?: (params: P, seed?: S) => D;\n view: (ctx: PageContext<P, D>) => TemplateResult;\n /**\n * <head> metadata. Receives params and the optional static seed.\n *\n * `head` is intentionally built from `(params, seed)` and not from the\n * runtime `load()` result: head must be deterministic and JSON-derivable\n * so that it appears in the raw snapshot HTML for SEO and social\n * crawlers.\n */\n head?: (params: P, seed?: S) => HeadMeta;\n /**\n * Static HTML snapshot declaration. Used only by build-time discovery.\n * Non-static routes remain ordinary SPA routes.\n */\n static?: true | StaticPageConfig<P, S>;\n /**\n * Local error boundary. Catches errors from view() and load() of this page.\n * If not set — the global `error` from routes() is used.\n */\n errorView?: (err: Error, params: P) => TemplateResult;\n /**\n * Route guards for this page. Evaluated after any guards from the\n * enclosing layout groups. See `Guard` for the verdict contract.\n */\n guard?: Guard | Guard[];\n}\n\n/**\n * Page factory. A typed wrapper over a plain object —\n * needed only for type inference and the single \"_page\" stamp.\n *\n * The three generic parameters are inferred from `spec` in the common case:\n * page({ static: true, view: () => html`...` })\n * Provide them explicitly only for non-trivial shapes:\n * page<{ slug: string }, Resource<Product>, Product>({ ... })\n */\nexport function page<\n P extends RouteParams = RouteParams,\n D = unknown,\n S extends JsonValue = JsonValue,\n>(spec: Omit<Page<P, D, S>, \"_page\">): Page<P, D, S> {\n return { _page: true, ...spec };\n}\n\nexport const isPage = (v: unknown): v is Page =>\n typeof v === \"object\" && v !== null && (v as Page)._page === true;\n\n// ---------- Routes manifest ----------\n\n/**\n * Manifest entry — what should respond to a path.\n * - Page → serve immediately, without import (for eager pages)\n * - () => Promise<{default:Page}> → lazy via dynamic import\n * - LayoutRoutes → a route group with a shared layout\n *\n * Using Page<any, any> so that user-defined page<{slug:string}>\n * can be assigned here (TS disallows Page<{slug:string}> → Page<RouteParams>\n * due to parameter contravariance).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyPage = Page<any, any, any>;\n\nexport type RouteEntry =\n | AnyPage\n | (() => Promise<{ default: AnyPage }>)\n | LayoutRoutes;\n\nexport interface LayoutRoutes {\n readonly _layout: true;\n /** Layout page wrapping children. Will receive child=TemplateResult. */\n layout?: AnyPage | (() => Promise<{ default: AnyPage }>);\n /** Sub-routes. Keys are relative (\"\" , \"users\", \"users/:id\"). */\n routes: Record<string, RouteEntry>;\n /**\n * Guards applied to every page below this group, before the page's own\n * guards. Single guard or ordered array. Async OK.\n */\n guard?: Guard | Guard[];\n}\n\n/**\n * `layout()` creates a route group with a shared shell:\n *\n * \"/admin\": layout({\n * layout: () => import(\"./layouts/admin.js\"),\n * guard: () => import(\"./lib/auth.js\").then(m => m.requireAuth),\n * routes: {\n * \"/\": () => import(\"./pages/admin/dashboard.js\"),\n * \"/orders\": () => import(\"./pages/admin/orders.js\"),\n * },\n * })\n */\nexport function layout(spec: Omit<LayoutRoutes, \"_layout\">): LayoutRoutes {\n return { _layout: true, ...spec };\n}\n\nexport const isLayoutGroup = (v: unknown): v is LayoutRoutes =>\n typeof v === \"object\" && v !== null && (v as LayoutRoutes)._layout === true;\n"]}
@@ -41,7 +41,12 @@ export interface PersistedOptions<T> {
41
41
  syncTabs?: boolean;
42
42
  }
43
43
  export interface PersistedSignal<T> extends Signal<T> {
44
- /** Remove the value from storage and unsubscribe from the bus. */
44
+ /** Stop storage/channel subscriptions while preserving the stored value. */
45
+ dispose(): void;
46
+ /** Remove the stored value without disposing the signal. */
47
+ clear(): void;
48
+ /** Remove the stored value and dispose all persistence subscriptions. */
45
49
  destroy(): void;
46
50
  }
47
51
  export declare function persisted<T>(key: string, base: Signal<T>, options?: PersistedOptions<T>): PersistedSignal<T>;
52
+ //# sourceMappingURL=persisted.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persisted.d.ts","sourceRoot":"","sources":["../../src/persisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAyB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAIjE,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,sCAAsC;IACtC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;IACjC,+CAA+C;IAC/C,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,CAAC,CAAC;IACnD,4EAA4E;IAC5E,OAAO,IAAI,IAAI,CAAC;IAChB,4DAA4D;IAC5D,KAAK,IAAI,IAAI,CAAC;IACd,yEAAyE;IACzE,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,SAAS,CAAC,CAAC,EACzB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EACf,OAAO,GAAE,gBAAgB,CAAC,CAAC,CAAM,GAChC,eAAe,CAAC,CAAC,CAAC,CA4HpB"}
@@ -46,7 +46,7 @@ export function persisted(key, base, options = {}) {
46
46
  // never suppresses the echo — without this guard two tabs ping-pong forever.
47
47
  // Seeded with the current value so the creation-time publish is a no-op.
48
48
  let lastSync = serialize(base.peek());
49
- let destroyed = false;
49
+ let disposed = false;
50
50
  // Collected so destroy() actually tears everything down. The original code
51
51
  // dropped these disposers, so destroy() left the write effect alive and the
52
52
  // next set() re-created the storage key.
@@ -54,7 +54,7 @@ export function persisted(key, base, options = {}) {
54
54
  // 2) Write on each change (optionally debounced)
55
55
  let writeTimer = null;
56
56
  const flushWrite = (v) => {
57
- if (!storage || destroyed)
57
+ if (!storage || disposed)
58
58
  return;
59
59
  try {
60
60
  storage.setItem(fullKey, serialize(v));
@@ -79,7 +79,7 @@ export function persisted(key, base, options = {}) {
79
79
  let bc = null;
80
80
  if (wantSync && typeof BroadcastChannel !== "undefined") {
81
81
  try {
82
- bc = new BroadcastChannel(`mado:persisted:${key}`);
82
+ bc = new BroadcastChannel(`mado:persisted:${options.storage ?? "local"}:${fullKey}`);
83
83
  const onMessage = (e) => {
84
84
  try {
85
85
  // Record what arrived BEFORE applying it, so the publisher effect
@@ -108,10 +108,10 @@ export function persisted(key, base, options = {}) {
108
108
  }
109
109
  }
110
110
  const out = base;
111
- out.destroy = () => {
112
- if (destroyed)
111
+ out.dispose = () => {
112
+ if (disposed)
113
113
  return;
114
- destroyed = true;
114
+ disposed = true;
115
115
  if (writeTimer) {
116
116
  clearTimeout(writeTimer);
117
117
  writeTimer = null;
@@ -119,6 +119,10 @@ export function persisted(key, base, options = {}) {
119
119
  // Dispose the write + publish effects so later set() calls are inert.
120
120
  for (const d of disposers.splice(0))
121
121
  d();
122
+ bc?.close();
123
+ bc = null;
124
+ };
125
+ out.clear = () => {
122
126
  if (storage) {
123
127
  try {
124
128
  storage.removeItem(fullKey);
@@ -127,12 +131,14 @@ export function persisted(key, base, options = {}) {
127
131
  /* noop */
128
132
  }
129
133
  }
130
- bc?.close();
131
- bc = null;
132
134
  };
133
- // Tie destroy() to the surrounding component/page lifecycle when present, so
134
- // a persisted() created inside setup() does not leak its effects/channel.
135
- getCurrentLifecycle()?.onDispose(() => out.destroy());
135
+ out.destroy = () => {
136
+ out.clear();
137
+ out.dispose();
138
+ };
139
+ // Lifecycle cleanup stops subscriptions but deliberately preserves the
140
+ // persisted value. Removing storage is an explicit clear()/destroy() action.
141
+ getCurrentLifecycle()?.onDispose(() => out.dispose());
136
142
  return out;
137
143
  }
138
144
  function safeStorage(name) {
@@ -1 +1 @@
1
- {"version":3,"file":"persisted.js","sourceRoot":"","sources":["../../src/persisted.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"persisted.js","sourceRoot":"","sources":["../../src/persisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,MAAM,EAA8B,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAiCrD,MAAM,UAAU,SAAS,CACvB,GAAW,EACX,IAAe,EACf,OAAO,GAAwB,EAAE;IAEjC,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC;IAC5C,MAAM,OAAO,GAAG,MAAM,GAAG,GAAG,CAAC;IAC7B,MAAM,OAAO,GACX,OAAO,CAAC,OAAO,KAAK,SAAS;QAC3B,CAAC,CAAC,WAAW,CAAC,gBAAgB,CAAC;QAC/B,CAAC,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC;IAClC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC;IACtD,MAAM,WAAW,GACf,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAM,CAAC,CAAC;IAE7D,wBAAwB;IACxB,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACrC,IAAI,GAAG,IAAI,IAAI;gBAAE,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,2BAA2B;QAC7B,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,yEAAyE;IACzE,IAAI,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACtC,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,2EAA2E;IAC3E,4EAA4E;IAC5E,yCAAyC;IACzC,MAAM,SAAS,GAAe,EAAE,CAAC;IAEjC,iDAAiD;IACjD,IAAI,UAAU,GAAyC,IAAI,CAAC;IAC5D,MAAM,UAAU,GAAG,CAAC,CAAI,EAAE,EAAE;QAC1B,IAAI,CAAC,OAAO,IAAI,QAAQ;YAAE,OAAO;QACjC,IAAI,CAAC;YACH,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,0BAA0B;QAC5B,CAAC;IACH,CAAC,CAAC;IAEF,SAAS,CAAC,IAAI,CACZ,MAAM,CAAC,GAAG,EAAE;QACV,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;QACjB,IAAI,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;YAC7C,IAAI,UAAU;gBAAE,YAAY,CAAC,UAAU,CAAC,CAAC;YACzC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;QACjE,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,CAAC,CAAC,CAAC;QAChB,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,oDAAoD;IACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC;IACnE,IAAI,EAAE,GAA4B,IAAI,CAAC;IACvC,IAAI,QAAQ,IAAI,OAAO,gBAAgB,KAAK,WAAW,EAAE,CAAC;QACxD,IAAI,CAAC;YACH,EAAE,GAAG,IAAI,gBAAgB,CACvB,kBAAkB,OAAO,CAAC,OAAO,IAAI,OAAO,IAAI,OAAO,EAAE,CAC1D,CAAC;YACF,MAAM,SAAS,GAAG,CAAC,CAAe,EAAE,EAAE;gBACpC,IAAI,CAAC;oBACH,kEAAkE;oBAClE,qEAAqE;oBACrE,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,IAAS,CAAC,CAAC;oBAClC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAS,CAAC,CAAC;gBACxB,CAAC;gBAAC,MAAM,CAAC;oBACP,UAAU;gBACZ,CAAC;YACH,CAAC,CAAC;YACF,EAAE,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAA0B,CAAC,CAAC;YAC3D,wEAAwE;YACxE,yDAAyD;YACzD,SAAS,CAAC,IAAI,CACZ,MAAM,CAAC,GAAG,EAAE;gBACV,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;gBACjB,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;gBACvB,IAAI,CAAC,KAAK,QAAQ;oBAAE,OAAO,CAAC,uCAAuC;gBACnE,QAAQ,GAAG,CAAC,CAAC;gBACb,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;YACrB,CAAC,CAAC,CACH,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,EAAE,GAAG,IAAI,CAAC;QACZ,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,IAA0B,CAAC;IACvC,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE;QACjB,IAAI,QAAQ;YAAE,OAAO;QACrB,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,UAAU,EAAE,CAAC;YACf,YAAY,CAAC,UAAU,CAAC,CAAC;YACzB,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,sEAAsE;QACtE,KAAK,MAAM,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;YAAE,CAAC,EAAE,CAAC;QACzC,EAAE,EAAE,KAAK,EAAE,CAAC;QACZ,EAAE,GAAG,IAAI,CAAC;IACZ,CAAC,CAAC;IACF,GAAG,CAAC,KAAK,GAAG,GAAG,EAAE;QACf,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC;gBACH,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC;gBACP,UAAU;YACZ,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE;QACjB,GAAG,CAAC,KAAK,EAAE,CAAC;QACZ,GAAG,CAAC,OAAO,EAAE,CAAC;IAChB,CAAC,CAAC;IAEF,uEAAuE;IACvE,6EAA6E;IAC7E,mBAAmB,EAAE,EAAE,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAEtD,OAAO,GAAG,CAAC;AACb,CAAC;AAGD,SAAS,WAAW,CAAC,IAAuC;IAC1D,IAAI,CAAC;QACH,MAAM,CAAC,GAAI,UAA6D,CACtE,IAAI,CACL,CAAC;QACF,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACpB,yDAAyD;QACzD,MAAM,KAAK,GAAG,eAAe,CAAC;QAC9B,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACtB,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACpB,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["/**\n * persisted() wraps a signal in localStorage / sessionStorage.\n *\n * Useful for:\n * - theme (light/dark)\n * - selected language\n * - last viewed product\n * - form drafts\n *\n * const theme = persisted('theme', signal<'light'|'dark'>('light'));\n * const draft = persisted('newPost.draft', signal(''),\n * { storage: 'session', debounce: 300 });\n *\n * Returns the same Signal API. Reads from storage on startup; writes on every\n * change (optionally debounced). Synchronizes across tabs via BroadcastChannel.\n *\n * Notes:\n * - JSON.parse/stringify; Date/Map/Set need a custom serializer.\n * - On QuotaExceeded or private-mode failures it silently falls back to memory.\n * - destroy() optionally closes the BroadcastChannel subscription.\n */\n\nimport { effect, type Disposer, type Signal } from \"./signal.js\";\nimport { getCurrentLifecycle } from \"./lifecycle.js\";\n\n\nexport interface PersistedOptions<T> {\n /** \"local\" (default) or \"session\". */\n storage?: \"local\" | \"session\";\n /** Write delay in ms. Default 0 (synchronous). */\n debounce?: number;\n /**\n * Key prefix. Default \"mado:\". Helps avoid collisions\n * with other scripts on the page.\n */\n keyPrefix?: string;\n /** Custom serialiser. Default JSON.stringify. */\n serialize?: (value: T) => string;\n /** Custom deserialiser. Default JSON.parse. */\n deserialize?: (raw: string) => T;\n /**\n * Cross-tab synchronisation via BroadcastChannel.\n * Default true for \"local\", false for \"session\".\n */\n syncTabs?: boolean;\n}\n\nexport interface PersistedSignal<T> extends Signal<T> {\n /** Stop storage/channel subscriptions while preserving the stored value. */\n dispose(): void;\n /** Remove the stored value without disposing the signal. */\n clear(): void;\n /** Remove the stored value and dispose all persistence subscriptions. */\n destroy(): void;\n}\n\nexport function persisted<T>(\n key: string,\n base: Signal<T>,\n options: PersistedOptions<T> = {},\n): PersistedSignal<T> {\n const prefix = options.keyPrefix ?? \"mado:\";\n const fullKey = prefix + key;\n const storage =\n options.storage === \"session\"\n ? safeStorage(\"sessionStorage\")\n : safeStorage(\"localStorage\");\n const serialize = options.serialize ?? JSON.stringify;\n const deserialize =\n options.deserialize ?? ((s: string) => JSON.parse(s) as T);\n\n // 1) Read initial value\n if (storage) {\n try {\n const raw = storage.getItem(fullKey);\n if (raw != null) base.set(deserialize(raw));\n } catch {\n /* corrupt JSON — ignore */\n }\n }\n\n // Echo guard: the serialized form of the last value we either received from\n // the channel or published to it. For object/array values, structured clone\n // gives a new identity on every cross-tab hop, so Object.is inside signal.set\n // never suppresses the echo — without this guard two tabs ping-pong forever.\n // Seeded with the current value so the creation-time publish is a no-op.\n let lastSync = serialize(base.peek());\n let disposed = false;\n\n // Collected so destroy() actually tears everything down. The original code\n // dropped these disposers, so destroy() left the write effect alive and the\n // next set() re-created the storage key.\n const disposers: Disposer[] = [];\n\n // 2) Write on each change (optionally debounced)\n let writeTimer: ReturnType<typeof setTimeout> | null = null;\n const flushWrite = (v: T) => {\n if (!storage || disposed) return;\n try {\n storage.setItem(fullKey, serialize(v));\n } catch {\n /* QuotaExceeded — skip */\n }\n };\n\n disposers.push(\n effect(() => {\n const v = base();\n if (options.debounce && options.debounce > 0) {\n if (writeTimer) clearTimeout(writeTimer);\n writeTimer = setTimeout(() => flushWrite(v), options.debounce);\n } else {\n flushWrite(v);\n }\n }),\n );\n\n // 3) Cross-tab synchronisation via BroadcastChannel\n const wantSync = options.syncTabs ?? options.storage !== \"session\";\n let bc: BroadcastChannel | null = null;\n if (wantSync && typeof BroadcastChannel !== \"undefined\") {\n try {\n bc = new BroadcastChannel(\n `mado:persisted:${options.storage ?? \"local\"}:${fullKey}`,\n );\n const onMessage = (e: MessageEvent) => {\n try {\n // Record what arrived BEFORE applying it, so the publisher effect\n // below recognises this value as remote-origin and does not echo it.\n lastSync = serialize(e.data as T);\n base.set(e.data as T);\n } catch {\n /* noop */\n }\n };\n bc.addEventListener(\"message\", onMessage as EventListener);\n // publish changes — but never re-publish a value that just arrived from\n // another tab (which is what created the infinite loop).\n disposers.push(\n effect(() => {\n const v = base();\n const s = serialize(v);\n if (s === lastSync) return; // unchanged or remote-origin → no echo\n lastSync = s;\n bc?.postMessage(v);\n }),\n );\n } catch {\n bc = null;\n }\n }\n\n const out = base as PersistedSignal<T>;\n out.dispose = () => {\n if (disposed) return;\n disposed = true;\n if (writeTimer) {\n clearTimeout(writeTimer);\n writeTimer = null;\n }\n // Dispose the write + publish effects so later set() calls are inert.\n for (const d of disposers.splice(0)) d();\n bc?.close();\n bc = null;\n };\n out.clear = () => {\n if (storage) {\n try {\n storage.removeItem(fullKey);\n } catch {\n /* noop */\n }\n }\n };\n out.destroy = () => {\n out.clear();\n out.dispose();\n };\n\n // Lifecycle cleanup stops subscriptions but deliberately preserves the\n // persisted value. Removing storage is an explicit clear()/destroy() action.\n getCurrentLifecycle()?.onDispose(() => out.dispose());\n\n return out;\n}\n\n\nfunction safeStorage(name: \"localStorage\" | \"sessionStorage\"): Storage | null {\n try {\n const s = (globalThis as unknown as Record<string, Storage | undefined>)[\n name\n ];\n if (!s) return null;\n // Some browsers throw on storage access in private mode.\n const probe = \"__madoprobe__\";\n s.setItem(probe, \"1\");\n s.removeItem(probe);\n return s;\n } catch {\n return null;\n }\n}\n"]}
@@ -47,7 +47,7 @@ export interface Resource<T> {
47
47
  /** Signal: current key (useful for debugging and DI) */
48
48
  key: () => string;
49
49
  /** Force re-run the request. */
50
- refresh(): void;
50
+ refresh(): Promise<T>;
51
51
  /**
52
52
  * Locally replace the data (optimistic update).
53
53
  * The cache is also updated.
@@ -125,3 +125,4 @@ export declare class HttpError extends Error {
125
125
  */
126
126
  export declare function jsonFetcher<T>(init?: RequestInit): (url: string, signal: AbortSignal) => Promise<T>;
127
127
  export {};
128
+ //# sourceMappingURL=resource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/resource.ts"],"names":[],"mappings":"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"}