@madojs/mado 0.5.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 (162) hide show
  1. package/AGENTS.md +291 -0
  2. package/CHANGELOG.md +23 -0
  3. package/LICENSE +21 -0
  4. package/README.md +371 -0
  5. package/ROADMAP.md +52 -0
  6. package/dist/src/component.d.ts +48 -0
  7. package/dist/src/component.js +140 -0
  8. package/dist/src/component.js.map +1 -0
  9. package/dist/src/context.d.ts +40 -0
  10. package/dist/src/context.js +67 -0
  11. package/dist/src/context.js.map +1 -0
  12. package/dist/src/css.d.ts +54 -0
  13. package/dist/src/css.js +137 -0
  14. package/dist/src/css.js.map +1 -0
  15. package/dist/src/devtools.d.ts +22 -0
  16. package/dist/src/devtools.js +63 -0
  17. package/dist/src/devtools.js.map +1 -0
  18. package/dist/src/diagnostics.d.ts +11 -0
  19. package/dist/src/diagnostics.js +28 -0
  20. package/dist/src/diagnostics.js.map +1 -0
  21. package/dist/src/each.d.ts +39 -0
  22. package/dist/src/each.js +35 -0
  23. package/dist/src/each.js.map +1 -0
  24. package/dist/src/forms.d.ts +71 -0
  25. package/dist/src/forms.js +161 -0
  26. package/dist/src/forms.js.map +1 -0
  27. package/dist/src/head.d.ts +19 -0
  28. package/dist/src/head.js +97 -0
  29. package/dist/src/head.js.map +1 -0
  30. package/dist/src/html/bindings.d.ts +78 -0
  31. package/dist/src/html/bindings.js +304 -0
  32. package/dist/src/html/bindings.js.map +1 -0
  33. package/dist/src/html/parser.d.ts +64 -0
  34. package/dist/src/html/parser.js +521 -0
  35. package/dist/src/html/parser.js.map +1 -0
  36. package/dist/src/html/template-types.d.ts +27 -0
  37. package/dist/src/html/template-types.js +8 -0
  38. package/dist/src/html/template-types.js.map +1 -0
  39. package/dist/src/html/template.d.ts +45 -0
  40. package/dist/src/html/template.js +119 -0
  41. package/dist/src/html/template.js.map +1 -0
  42. package/dist/src/html.d.ts +16 -0
  43. package/dist/src/html.js +16 -0
  44. package/dist/src/html.js.map +1 -0
  45. package/dist/src/index.d.ts +35 -0
  46. package/dist/src/index.js +39 -0
  47. package/dist/src/index.js.map +1 -0
  48. package/dist/src/lazy.d.ts +38 -0
  49. package/dist/src/lazy.js +73 -0
  50. package/dist/src/lazy.js.map +1 -0
  51. package/dist/src/lifecycle.d.ts +45 -0
  52. package/dist/src/lifecycle.js +66 -0
  53. package/dist/src/lifecycle.js.map +1 -0
  54. package/dist/src/page.d.ts +161 -0
  55. package/dist/src/page.js +38 -0
  56. package/dist/src/page.js.map +1 -0
  57. package/dist/src/persisted.d.ts +47 -0
  58. package/dist/src/persisted.js +119 -0
  59. package/dist/src/persisted.js.map +1 -0
  60. package/dist/src/resource.d.ts +120 -0
  61. package/dist/src/resource.js +275 -0
  62. package/dist/src/resource.js.map +1 -0
  63. package/dist/src/router/manifest.d.ts +56 -0
  64. package/dist/src/router/manifest.js +302 -0
  65. package/dist/src/router/manifest.js.map +1 -0
  66. package/dist/src/router/match.d.ts +62 -0
  67. package/dist/src/router/match.js +117 -0
  68. package/dist/src/router/match.js.map +1 -0
  69. package/dist/src/router/navigation.d.ts +89 -0
  70. package/dist/src/router/navigation.js +263 -0
  71. package/dist/src/router/navigation.js.map +1 -0
  72. package/dist/src/router.d.ts +13 -0
  73. package/dist/src/router.js +13 -0
  74. package/dist/src/router.js.map +1 -0
  75. package/dist/src/signal.d.ts +67 -0
  76. package/dist/src/signal.js +238 -0
  77. package/dist/src/signal.js.map +1 -0
  78. package/docs/README.md +12 -0
  79. package/docs/en/00-the-mado-way.md +106 -0
  80. package/docs/en/01-routing.md +204 -0
  81. package/docs/en/02-project-layout.md +58 -0
  82. package/docs/en/03-static-bake.md +251 -0
  83. package/docs/en/04-ide-setup.md +162 -0
  84. package/docs/en/05-why-mado.md +193 -0
  85. package/docs/en/06-for-backenders.md +422 -0
  86. package/docs/en/07-llm-pitfalls.md +486 -0
  87. package/docs/en/08-llm-zero-history-test.md +56 -0
  88. package/docs/en/09-shadow-vs-light-dom.md +122 -0
  89. package/docs/en/README.md +16 -0
  90. package/docs/fr/00-the-mado-way.md +108 -0
  91. package/docs/fr/01-routing.md +202 -0
  92. package/docs/fr/02-project-layout.md +58 -0
  93. package/docs/fr/03-static-bake.md +290 -0
  94. package/docs/fr/04-ide-setup.md +162 -0
  95. package/docs/fr/05-why-mado.md +193 -0
  96. package/docs/fr/06-for-backenders.md +432 -0
  97. package/docs/fr/07-llm-pitfalls.md +487 -0
  98. package/docs/fr/08-llm-zero-history-test.md +60 -0
  99. package/docs/fr/09-shadow-vs-light-dom.md +121 -0
  100. package/docs/fr/README.md +16 -0
  101. package/docs/ru/00-the-mado-way.md +93 -0
  102. package/docs/ru/01-routing.md +194 -0
  103. package/docs/ru/02-project-layout.md +57 -0
  104. package/docs/ru/03-static-bake.md +251 -0
  105. package/docs/ru/04-ide-setup.md +144 -0
  106. package/docs/ru/05-why-mado.md +193 -0
  107. package/docs/ru/06-for-backenders.md +422 -0
  108. package/docs/ru/07-llm-pitfalls.md +485 -0
  109. package/docs/ru/08-llm-zero-history-test.md +56 -0
  110. package/docs/ru/09-shadow-vs-light-dom.md +122 -0
  111. package/docs/ru/README.md +14 -0
  112. package/docs/uk/00-the-mado-way.md +54 -0
  113. package/docs/uk/01-routing.md +82 -0
  114. package/docs/uk/02-project-layout.md +46 -0
  115. package/docs/uk/03-static-bake.md +49 -0
  116. package/docs/uk/04-ide-setup.md +26 -0
  117. package/docs/uk/05-why-mado.md +34 -0
  118. package/docs/uk/06-for-backenders.md +50 -0
  119. package/docs/uk/07-llm-pitfalls.md +82 -0
  120. package/docs/uk/08-llm-zero-history-test.md +31 -0
  121. package/docs/uk/09-shadow-vs-light-dom.md +40 -0
  122. package/docs/uk/README.md +16 -0
  123. package/llms.txt +155 -0
  124. package/package.json +81 -0
  125. package/scripts/bake.mjs +406 -0
  126. package/scripts/bundle.mjs +146 -0
  127. package/scripts/cli.mjs +382 -0
  128. package/scripts/new.mjs +80 -0
  129. package/scripts/preview.mjs +176 -0
  130. package/scripts/release-notes.mjs +66 -0
  131. package/scripts/showcase-regression.mjs +392 -0
  132. package/server/serve.mjs +292 -0
  133. package/starters/crud/README.md +21 -0
  134. package/starters/crud/index.html +20 -0
  135. package/starters/crud/package.json +17 -0
  136. package/starters/crud/src/components/app-shell.ts +51 -0
  137. package/starters/crud/src/components/ticket-detail.ts +33 -0
  138. package/starters/crud/src/components/ticket-form.ts +69 -0
  139. package/starters/crud/src/components/ticket-list.ts +66 -0
  140. package/starters/crud/src/lib/api.ts +76 -0
  141. package/starters/crud/src/main.ts +12 -0
  142. package/starters/crud/src/pages/home.ts +18 -0
  143. package/starters/crud/src/pages/not-found.ts +12 -0
  144. package/starters/crud/src/pages/ticket-detail.ts +6 -0
  145. package/starters/crud/src/pages/ticket-new.ts +6 -0
  146. package/starters/crud/src/pages/tickets.ts +6 -0
  147. package/starters/crud/src/routes.ts +9 -0
  148. package/starters/crud/src/styles/global.ts +155 -0
  149. package/starters/crud/tsconfig.json +15 -0
  150. package/starters/minimal/README.md +19 -0
  151. package/starters/minimal/index.html +20 -0
  152. package/starters/minimal/package.json +17 -0
  153. package/starters/minimal/src/components/app-counter.ts +31 -0
  154. package/starters/minimal/src/main.ts +9 -0
  155. package/starters/minimal/src/pages/home.ts +18 -0
  156. package/starters/minimal/src/pages/not-found.ts +14 -0
  157. package/starters/minimal/src/routes.ts +6 -0
  158. package/starters/minimal/src/styles/global.ts +60 -0
  159. package/starters/minimal/tsconfig.json +15 -0
  160. package/templates/page-detail.ts +63 -0
  161. package/templates/page-form.ts +94 -0
  162. package/templates/page-list.ts +79 -0
@@ -0,0 +1,302 @@
1
+ /**
2
+ * routes() — high-level manifest router with lazy loading, layouts,
3
+ * prefetch and a sync-fast-path for already-loaded pages.
4
+ *
5
+ * On top of the raw router() from navigation.ts this adds:
6
+ * - dynamic-import loaders (code splitting via `() => import(...)`),
7
+ * - nested routes with layouts (via page.ts: nested({ layout, routes })),
8
+ * - per-instance module cache (not global — two routes() calls
9
+ * in the same process do NOT interfere),
10
+ * - hover prefetch and programmatic prefetchPath(),
11
+ * - smart loadingDelay (no progress-bar flicker on fast networks),
12
+ * - sync-fast-path: if the page is already in cache — renders synchronously,
13
+ * without loading state and without a microtask. Removes flicker on back/forward.
14
+ */
15
+ import { signal } from "../signal.js";
16
+ import { html } from "../html/template.js";
17
+ import { applyHead } from "../head.js";
18
+ import { flatten, patternToRegex, } from "./match.js";
19
+ import { router } from "./navigation.js";
20
+ /**
21
+ * Registry of active RoutesContexts. Used by the global
22
+ * prefetchPath() — iterates over all active instances. On router.dispose()
23
+ * the corresponding context is removed from the registry.
24
+ */
25
+ const activeRoutes = new Set();
26
+ /**
27
+ * Create a router from a manifest. Returns the same RouterApi as router().
28
+ */
29
+ export function routes(manifest, options = {}) {
30
+ const ctx = {
31
+ moduleCache: new Map(),
32
+ pathToFlat: new Map(),
33
+ compiledForPrefetch: [],
34
+ renderSeq: 0,
35
+ };
36
+ activeRoutes.add(ctx);
37
+ const flat = flatten(manifest);
38
+ const lowLevel = {};
39
+ for (const [pattern, entry] of flat) {
40
+ lowLevel[pattern] = (params) => renderEntry(ctx, entry, params, options, ++ctx.renderSeq);
41
+ ctx.pathToFlat.set(pattern, entry);
42
+ if (pattern !== "*") {
43
+ ctx.compiledForPrefetch.push({ regex: patternToRegex(pattern), entry });
44
+ }
45
+ }
46
+ const api = router(lowLevel, {
47
+ viewTransitions: options.viewTransitions,
48
+ // Raise prefetch into sub-router: hover on a link → find matching FlatEntry → load loader + layouts.
49
+ prefetch: (pathname) => prefetchPathInContext(ctx, pathname),
50
+ });
51
+ const origDispose = api.dispose;
52
+ api.dispose = () => {
53
+ activeRoutes.delete(ctx);
54
+ origDispose();
55
+ };
56
+ return api;
57
+ }
58
+ // ---------- prefetch ----------
59
+ /**
60
+ * Prefetch-load modules for a path (hover, programmatic).
61
+ * Safe to call repeatedly — cached.
62
+ *
63
+ * Iterates all active routes() and starts loaders for matched entries.
64
+ * If there is no active routes() — no-op.
65
+ */
66
+ export function prefetchPath(pathname) {
67
+ for (const ctx of activeRoutes) {
68
+ prefetchPathInContext(ctx, pathname);
69
+ }
70
+ }
71
+ function prefetchPathInContext(ctx, pathname) {
72
+ const exact = ctx.pathToFlat.get(pathname);
73
+ const entry = exact ?? ctx.compiledForPrefetch.find((c) => c.regex.test(pathname))?.entry;
74
+ if (!entry)
75
+ return;
76
+ loadPage(ctx, entry.loader).catch(() => { });
77
+ for (const lt of entry.layouts)
78
+ loadPage(ctx, lt).catch(() => { });
79
+ }
80
+ // ---------- Loading ----------
81
+ async function loadPage(ctx, loader) {
82
+ const cached = ctx.moduleCache.get(loader);
83
+ if (cached)
84
+ return cached;
85
+ const p = await loader();
86
+ ctx.moduleCache.set(loader, p);
87
+ return p;
88
+ }
89
+ /**
90
+ * Synchronous cache check. If all modules (page + layouts)
91
+ * are already loaded — returns them without a Promise. This allows
92
+ * renderEntry to render the page without loading state and without a microtask.
93
+ */
94
+ function tryLoadSync(ctx, entry) {
95
+ const page = ctx.moduleCache.get(entry.loader);
96
+ if (!page)
97
+ return undefined;
98
+ const layouts = [];
99
+ for (const l of entry.layouts) {
100
+ const lp = ctx.moduleCache.get(l);
101
+ if (!lp)
102
+ return undefined;
103
+ layouts.push(lp);
104
+ }
105
+ return { page, layouts };
106
+ }
107
+ // ---------- Render ----------
108
+ /**
109
+ * Apply the page's title/head. Extracted from renderEntry so it
110
+ * can be called from both the async and sync branch.
111
+ */
112
+ function applyPageMeta(page, params, options) {
113
+ const title = typeof page.title === "function" ? page.title(params) : page.title;
114
+ if (title) {
115
+ document.title = title + (options.titleSuffix ?? "");
116
+ }
117
+ if (page.head) {
118
+ try {
119
+ const baked = readBaked();
120
+ applyHead(page.head(params, baked));
121
+ }
122
+ catch (err) {
123
+ // eslint-disable-next-line no-console
124
+ console.error("[mado] page.head() threw:", err);
125
+ }
126
+ }
127
+ }
128
+ /**
129
+ * SYNC FAST PATH: if the page is already in moduleCache, we render it
130
+ * right now, without loading state and without a microtask. This removes
131
+ * blank/progress flicker on repeat navigations.
132
+ *
133
+ * ASYNC PATH: on a cold navigation the module still needs to be loaded.
134
+ * To avoid flickering with a loading view on fast networks, we use
135
+ * `loadingDelay` (default 100ms): if loading finishes within that time
136
+ * — render the ready page immediately; otherwise show loading.
137
+ */
138
+ function renderEntry(ctx, entry, params, options, seq) {
139
+ // ---------- SYNC FAST PATH ----------
140
+ const sync = tryLoadSync(ctx, entry);
141
+ if (sync) {
142
+ applyPageMeta(sync.page, params, options);
143
+ try {
144
+ return renderWithLayouts(sync.page, sync.layouts, params);
145
+ }
146
+ catch (err) {
147
+ const e = err instanceof Error ? err : new Error(String(err));
148
+ if (sync.page.errorView)
149
+ return sync.page.errorView(e, params);
150
+ return options.error?.(e) ?? html `<pre>${e.message}</pre>`;
151
+ }
152
+ }
153
+ // ---------- ASYNC PATH ----------
154
+ // 'idle' — first window (until loadingDelay): render empty to avoid
155
+ // progress-bar flicker on fast connections.
156
+ // 'loading' — module didn't arrive in time → show loading.
157
+ // 'ready' / 'error' — final states.
158
+ const state = signal({ kind: "idle" });
159
+ let resolved = false;
160
+ const delay = options.loadingDelay ?? 100;
161
+ const timer = delay > 0
162
+ ? setTimeout(() => {
163
+ if (!resolved && !isStale(ctx, seq))
164
+ state.set({ kind: "loading" });
165
+ }, delay)
166
+ : null;
167
+ // delay = 0 means show loading immediately.
168
+ if (delay === 0 && !isStale(ctx, seq))
169
+ state.set({ kind: "loading" });
170
+ Promise.all([
171
+ loadPage(ctx, entry.loader),
172
+ ...entry.layouts.map((l) => loadPage(ctx, l)),
173
+ ]).then(([pg, ...lts]) => {
174
+ resolved = true;
175
+ if (timer)
176
+ clearTimeout(timer);
177
+ if (isStale(ctx, seq))
178
+ return;
179
+ applyPageMeta(pg, params, options);
180
+ state.set({ kind: "ready", page: pg, layouts: lts });
181
+ }, (err) => {
182
+ resolved = true;
183
+ if (timer)
184
+ clearTimeout(timer);
185
+ if (isStale(ctx, seq))
186
+ return;
187
+ const e = err instanceof Error ? err : new Error(String(err));
188
+ state.set({ kind: "error", err: e });
189
+ });
190
+ return html `${() => {
191
+ const s = state();
192
+ if (s.kind === "idle")
193
+ return "";
194
+ if (s.kind === "loading") {
195
+ return options.loading?.() ?? defaultLoadingView();
196
+ }
197
+ if (s.kind === "error") {
198
+ return options.error?.(s.err) ?? html `<pre>${s.err.message}</pre>`;
199
+ }
200
+ try {
201
+ return renderWithLayouts(s.page, s.layouts, params);
202
+ }
203
+ catch (err) {
204
+ const e = err instanceof Error ? err : new Error(String(err));
205
+ if (s.page.errorView)
206
+ return s.page.errorView(e, params);
207
+ return options.error?.(e) ?? html `<pre>${e.message}</pre>`;
208
+ }
209
+ }}`;
210
+ }
211
+ function isStale(ctx, seq) {
212
+ return seq !== ctx.renderSeq;
213
+ }
214
+ /**
215
+ * Wrap a page's view in layouts (from inner to outer).
216
+ * Each layout receives `child` = TemplateResult of the nested page or
217
+ * next layout — composes like a matryoshka.
218
+ */
219
+ function renderWithLayouts(page, layouts, params) {
220
+ const baked = readBaked();
221
+ const data = page.load ? page.load(params, baked) : undefined;
222
+ let view = page.view({
223
+ params,
224
+ data,
225
+ path: () => location.pathname,
226
+ child: null,
227
+ });
228
+ for (let i = layouts.length - 1; i >= 0; i--) {
229
+ const layout = layouts[i];
230
+ const layoutData = layout.load ? layout.load(params) : undefined;
231
+ view = layout.view({
232
+ params,
233
+ data: layoutData,
234
+ path: () => location.pathname,
235
+ child: view,
236
+ });
237
+ }
238
+ return view;
239
+ }
240
+ // ---------- Default loading view ----------
241
+ //
242
+ // Thin progress bar at the top of the screen. Style is injected once
243
+ // globally (lazy, on first show). No animations for prefers-reduced-motion.
244
+ const DEFAULT_LOADING_STYLE_ID = "mado-default-loading-style";
245
+ const DEFAULT_LOADING_CSS = `
246
+ @keyframes mado-progress {
247
+ 0% { transform: translateX(-100%); }
248
+ 50% { transform: translateX(0%); }
249
+ 100% { transform: translateX(100%); }
250
+ }
251
+ .mado-progress-bar {
252
+ position: fixed; top: 0; left: 0; right: 0;
253
+ height: 2px; background: rgba(0,0,0,.06);
254
+ z-index: 2147483647; overflow: hidden;
255
+ pointer-events: none;
256
+ }
257
+ .mado-progress-bar::after {
258
+ content: ""; display: block; height: 100%; width: 40%;
259
+ background: currentColor; opacity: .8;
260
+ animation: mado-progress 1.2s ease-in-out infinite;
261
+ }
262
+ @media (prefers-reduced-motion: reduce) {
263
+ .mado-progress-bar::after { animation: none; width: 100%; }
264
+ }
265
+ `;
266
+ function ensureDefaultLoadingStyle() {
267
+ if (typeof document === "undefined")
268
+ return;
269
+ if (document.getElementById(DEFAULT_LOADING_STYLE_ID))
270
+ return;
271
+ const style = document.createElement("style");
272
+ style.id = DEFAULT_LOADING_STYLE_ID;
273
+ style.textContent = DEFAULT_LOADING_CSS;
274
+ document.head.appendChild(style);
275
+ }
276
+ function defaultLoadingView() {
277
+ ensureDefaultLoadingStyle();
278
+ return html `<div class="mado-progress-bar" aria-hidden="true"></div>`;
279
+ }
280
+ /**
281
+ * Read baked data from `<script id="bake" type="application/json">`
282
+ * placed by `scripts/bake.mjs` during static generation. Returns
283
+ * undefined in SPA mode.
284
+ */
285
+ function readBaked() {
286
+ const el = document.getElementById("bake");
287
+ if (!el || el.textContent == null)
288
+ return undefined;
289
+ try {
290
+ return JSON.parse(el.textContent);
291
+ }
292
+ catch {
293
+ return undefined;
294
+ }
295
+ }
296
+ // ---------- Test hooks ----------
297
+ export const _testHooks = {
298
+ activeRoutesSize() {
299
+ return activeRoutes.size;
300
+ },
301
+ };
302
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../src/router/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Pure pattern matching + flatten/normalize of the manifest.
3
+ *
4
+ * There is NO window/document/history or signals here — only functions
5
+ * over strings and objects. This allows:
6
+ * - testing routing in Node without jsdom;
7
+ * - reusing the same compile/regex in bake / prefetch without duplication.
8
+ */
9
+ import { type Page, type RouteEntry } from "../page.js";
10
+ import type { TemplateResult } from "../html/template-types.js";
11
+ export type RouteParams = Record<string, string>;
12
+ export type RouteHandler = (params: RouteParams) => TemplateResult;
13
+ export type Routes = Record<string, RouteHandler>;
14
+ export interface CompiledRoute {
15
+ pattern: string;
16
+ regex: RegExp;
17
+ keys: string[];
18
+ handler: RouteHandler;
19
+ }
20
+ /**
21
+ * Compile a pattern like `/users/:id` into a `CompiledRoute` with
22
+ * named keys and a regex for matching against `location.pathname`.
23
+ *
24
+ * Special case `*` — wildcard fallback, matches anything.
25
+ */
26
+ export declare function compile(pattern: string, handler: RouteHandler): CompiledRoute;
27
+ /**
28
+ * Find the first matching CompiledRoute for path. Returns already
29
+ * decoded params (decodeURIComponent on each segment).
30
+ * Wildcard (`*`) is skipped — handle it separately as a fallback.
31
+ */
32
+ export declare function matchRoute(path: string, compiled: readonly CompiledRoute[]): {
33
+ route: CompiledRoute;
34
+ params: RouteParams;
35
+ } | null;
36
+ /** Simple regex WITHOUT keys — for prefetch (we only need the match fact). */
37
+ export declare function patternToRegex(pattern: string): RegExp;
38
+ export type RoutesMap = Record<string, RouteEntry>;
39
+ export interface FlatEntry {
40
+ loader: () => Promise<Page> | Page;
41
+ layouts: Array<() => Promise<Page> | Page>;
42
+ }
43
+ /**
44
+ * Unfold a nested manifest into a flat list of `[fullPattern, FlatEntry]`.
45
+ * Accumulates parent layouts along the way, so each leaf route
46
+ * "knows" all its layouts (from outer to inner).
47
+ */
48
+ export declare function flatten(map: RoutesMap, prefix?: string, layouts?: FlatEntry["layouts"]): Array<[string, FlatEntry]>;
49
+ /** Careful path segment joining without duplicate slashes. */
50
+ export declare function joinPath(a: string, b: string): string;
51
+ /**
52
+ * Normalise a RouteEntry into a uniform `() => Promise<Page> | Page`.
53
+ *
54
+ * RouteEntry comes in three forms:
55
+ * - a ready Page → return as-is
56
+ * - dynamic import → await the default export and check it's a Page
57
+ * - something else → throw (bad manifest entry)
58
+ *
59
+ * This function is the single place that handles both forms;
60
+ * the rest of the code works only with the unified loader.
61
+ */
62
+ export declare function normalize(entry: RouteEntry): () => Promise<Page> | Page;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Pure pattern matching + flatten/normalize of the manifest.
3
+ *
4
+ * There is NO window/document/history or signals here — only functions
5
+ * over strings and objects. This allows:
6
+ * - testing routing in Node without jsdom;
7
+ * - reusing the same compile/regex in bake / prefetch without duplication.
8
+ */
9
+ import { isNested, isPage } from "../page.js";
10
+ /**
11
+ * Compile a pattern like `/users/:id` into a `CompiledRoute` with
12
+ * named keys and a regex for matching against `location.pathname`.
13
+ *
14
+ * Special case `*` — wildcard fallback, matches anything.
15
+ */
16
+ export function compile(pattern, handler) {
17
+ if (pattern === "*") {
18
+ return { pattern, regex: /.*/, keys: [], handler };
19
+ }
20
+ const keys = [];
21
+ const re = pattern.replace(/\/$/, "").replace(/:[\w]+/g, (m) => {
22
+ keys.push(m.slice(1));
23
+ return "([^/]+)";
24
+ });
25
+ return {
26
+ pattern,
27
+ regex: new RegExp(`^${re}/?$`),
28
+ keys,
29
+ handler,
30
+ };
31
+ }
32
+ /**
33
+ * Find the first matching CompiledRoute for path. Returns already
34
+ * decoded params (decodeURIComponent on each segment).
35
+ * Wildcard (`*`) is skipped — handle it separately as a fallback.
36
+ */
37
+ export function matchRoute(path, compiled) {
38
+ for (const r of compiled) {
39
+ if (r.pattern === "*")
40
+ continue;
41
+ const m = r.regex.exec(path);
42
+ if (m) {
43
+ const params = {};
44
+ r.keys.forEach((k, i) => {
45
+ params[k] = decodeURIComponent(m[i + 1] ?? "");
46
+ });
47
+ return { route: r, params };
48
+ }
49
+ }
50
+ return null;
51
+ }
52
+ /** Simple regex WITHOUT keys — for prefetch (we only need the match fact). */
53
+ export function patternToRegex(pattern) {
54
+ const re = pattern.replace(/\/$/, "").replace(/:[\w]+/g, "([^/]+)");
55
+ return new RegExp(`^${re}/?$`);
56
+ }
57
+ /**
58
+ * Unfold a nested manifest into a flat list of `[fullPattern, FlatEntry]`.
59
+ * Accumulates parent layouts along the way, so each leaf route
60
+ * "knows" all its layouts (from outer to inner).
61
+ */
62
+ export function flatten(map, prefix = "", layouts = []) {
63
+ const out = [];
64
+ for (const [k, v] of Object.entries(map)) {
65
+ const full = joinPath(prefix, k);
66
+ if (isNested(v)) {
67
+ const nextLayouts = v.layout
68
+ ? [...layouts, normalize(v.layout)]
69
+ : layouts;
70
+ for (const sub of flatten(v.routes, full, nextLayouts))
71
+ out.push(sub);
72
+ }
73
+ else {
74
+ out.push([full || "/", { loader: normalize(v), layouts }]);
75
+ }
76
+ }
77
+ return out;
78
+ }
79
+ /** Careful path segment joining without duplicate slashes. */
80
+ export function joinPath(a, b) {
81
+ if (!a)
82
+ return b;
83
+ if (!b)
84
+ return a;
85
+ if (a.endsWith("/"))
86
+ a = a.slice(0, -1);
87
+ if (b.startsWith("/"))
88
+ b = b.slice(1);
89
+ return `${a}/${b}`;
90
+ }
91
+ /**
92
+ * Normalise a RouteEntry into a uniform `() => Promise<Page> | Page`.
93
+ *
94
+ * RouteEntry comes in three forms:
95
+ * - a ready Page → return as-is
96
+ * - dynamic import → await the default export and check it's a Page
97
+ * - something else → throw (bad manifest entry)
98
+ *
99
+ * This function is the single place that handles both forms;
100
+ * the rest of the code works only with the unified loader.
101
+ */
102
+ export function normalize(entry) {
103
+ if (isPage(entry))
104
+ return () => entry;
105
+ if (typeof entry === "function") {
106
+ return async () => {
107
+ const mod = await entry();
108
+ const p = mod.default;
109
+ if (!isPage(p)) {
110
+ throw new Error("[mado] Lazy route did not return page({...}) as the default export.");
111
+ }
112
+ return p;
113
+ };
114
+ }
115
+ throw new Error("[mado] Invalid entry in routes(): " + String(entry));
116
+ }
117
+ //# sourceMappingURL=match.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.js","sourceRoot":"","sources":["../../../src/router/match.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Browser integration: History API, click interception, hover-prefetch,
3
+ * View Transitions, queryParam.
4
+ *
5
+ * Everything that touches `window` / `document` / `history` lives here,
6
+ * so match.ts remains clean and testable without jsdom.
7
+ */
8
+ import { type Signal } from "./../signal.js";
9
+ import { type Routes } from "./match.js";
10
+ import type { TemplateResult } from "../html/template-types.js";
11
+ export interface RouterApi {
12
+ /** Signal function that returns the current TemplateResult. */
13
+ view: () => TemplateResult;
14
+ /** Current path as a signal. */
15
+ path: () => string;
16
+ /** Programmatic navigation. */
17
+ navigate(to: string, opts?: {
18
+ replace?: boolean;
19
+ }): void;
20
+ /** Remove all listeners and release resources. */
21
+ dispose(): void;
22
+ }
23
+ export interface RouterOptions {
24
+ /**
25
+ * Use the View Transitions API on navigation (smooth crossfade).
26
+ * Default `true` — if the browser doesn't support it, safely
27
+ * falls back to a plain set().
28
+ */
29
+ viewTransitions?: boolean;
30
+ /**
31
+ * Hook for hover prefetch. Receives the pathname of the candidate
32
+ * (without origin, query/hash stripped). Used by routes()
33
+ * to register loaders; raw router() doesn't normally need this.
34
+ */
35
+ prefetch?: (pathname: string) => void;
36
+ }
37
+ /**
38
+ * Minimal History API router.
39
+ *
40
+ * const route = router({
41
+ * '/': () => html`<x-home/>`,
42
+ * '/users/:id': ({ id }) => html`<x-user .id=${id}/>`,
43
+ * '*': () => html`<x-404/>`,
44
+ * });
45
+ *
46
+ * html`<main>${route.view}</main>`
47
+ *
48
+ * Lifecycle: subscribes to popstate + intercepts clicks
49
+ * on `<a data-link>` + hover-prefetch (if hook given). All this
50
+ * is removed in `dispose()` — mandatory to call in tests and
51
+ * dev-overlay, otherwise listener leak.
52
+ */
53
+ export declare function router(routes: Routes, options?: RouterOptions): RouterApi;
54
+ /**
55
+ * Global helper for programmatic navigation. Equivalent to
56
+ * `api.navigate(to)` for any active router — updates the URL
57
+ * via History API and dispatches `popstate`, which all
58
+ * active routers on the page will pick up.
59
+ *
60
+ * import { navigate } from "@madojs/mado";
61
+ * navigate("/users/42");
62
+ *
63
+ * Used inside form handlers / events when you don't have
64
+ * direct access to RouterApi (e.g. inside a component's setup function
65
+ * that didn't receive the router as a parameter).
66
+ */
67
+ export declare function navigate(to: string, opts?: {
68
+ replace?: boolean;
69
+ }): void;
70
+ export interface QueryParam {
71
+ (): string;
72
+ set(value: string | null, opts?: {
73
+ push?: boolean;
74
+ }): void;
75
+ }
76
+ /**
77
+ * Reactive query parameter.
78
+ *
79
+ * const page = queryParam('page', '1');
80
+ * page(); // '1' (or current URL value)
81
+ * page.set('2'); // history.replaceState and re-render
82
+ * page.set(null); // delete the parameter
83
+ */
84
+ export declare function queryParam(name: string, defaultValue?: string): QueryParam;
85
+ /**
86
+ * For typing purposes — same characteristics as Signal<string>.
87
+ * Convenient in places that expect a plain signal.
88
+ */
89
+ export type QuerySignal = Signal<string>;