@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.
- package/AGENTS.md +291 -0
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +371 -0
- package/ROADMAP.md +52 -0
- package/dist/src/component.d.ts +48 -0
- package/dist/src/component.js +140 -0
- package/dist/src/component.js.map +1 -0
- package/dist/src/context.d.ts +40 -0
- package/dist/src/context.js +67 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/css.d.ts +54 -0
- package/dist/src/css.js +137 -0
- package/dist/src/css.js.map +1 -0
- package/dist/src/devtools.d.ts +22 -0
- package/dist/src/devtools.js +63 -0
- package/dist/src/devtools.js.map +1 -0
- package/dist/src/diagnostics.d.ts +11 -0
- package/dist/src/diagnostics.js +28 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/each.d.ts +39 -0
- package/dist/src/each.js +35 -0
- package/dist/src/each.js.map +1 -0
- package/dist/src/forms.d.ts +71 -0
- package/dist/src/forms.js +161 -0
- package/dist/src/forms.js.map +1 -0
- package/dist/src/head.d.ts +19 -0
- package/dist/src/head.js +97 -0
- package/dist/src/head.js.map +1 -0
- package/dist/src/html/bindings.d.ts +78 -0
- package/dist/src/html/bindings.js +304 -0
- package/dist/src/html/bindings.js.map +1 -0
- package/dist/src/html/parser.d.ts +64 -0
- package/dist/src/html/parser.js +521 -0
- package/dist/src/html/parser.js.map +1 -0
- package/dist/src/html/template-types.d.ts +27 -0
- package/dist/src/html/template-types.js +8 -0
- package/dist/src/html/template-types.js.map +1 -0
- package/dist/src/html/template.d.ts +45 -0
- package/dist/src/html/template.js +119 -0
- package/dist/src/html/template.js.map +1 -0
- package/dist/src/html.d.ts +16 -0
- package/dist/src/html.js +16 -0
- package/dist/src/html.js.map +1 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.js +39 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lazy.d.ts +38 -0
- package/dist/src/lazy.js +73 -0
- package/dist/src/lazy.js.map +1 -0
- package/dist/src/lifecycle.d.ts +45 -0
- package/dist/src/lifecycle.js +66 -0
- package/dist/src/lifecycle.js.map +1 -0
- package/dist/src/page.d.ts +161 -0
- package/dist/src/page.js +38 -0
- package/dist/src/page.js.map +1 -0
- package/dist/src/persisted.d.ts +47 -0
- package/dist/src/persisted.js +119 -0
- package/dist/src/persisted.js.map +1 -0
- package/dist/src/resource.d.ts +120 -0
- package/dist/src/resource.js +275 -0
- package/dist/src/resource.js.map +1 -0
- package/dist/src/router/manifest.d.ts +56 -0
- package/dist/src/router/manifest.js +302 -0
- package/dist/src/router/manifest.js.map +1 -0
- package/dist/src/router/match.d.ts +62 -0
- package/dist/src/router/match.js +117 -0
- package/dist/src/router/match.js.map +1 -0
- package/dist/src/router/navigation.d.ts +89 -0
- package/dist/src/router/navigation.js +263 -0
- package/dist/src/router/navigation.js.map +1 -0
- package/dist/src/router.d.ts +13 -0
- package/dist/src/router.js +13 -0
- package/dist/src/router.js.map +1 -0
- package/dist/src/signal.d.ts +67 -0
- package/dist/src/signal.js +238 -0
- package/dist/src/signal.js.map +1 -0
- package/docs/README.md +12 -0
- package/docs/en/00-the-mado-way.md +106 -0
- package/docs/en/01-routing.md +204 -0
- package/docs/en/02-project-layout.md +58 -0
- package/docs/en/03-static-bake.md +251 -0
- package/docs/en/04-ide-setup.md +162 -0
- package/docs/en/05-why-mado.md +193 -0
- package/docs/en/06-for-backenders.md +422 -0
- package/docs/en/07-llm-pitfalls.md +486 -0
- package/docs/en/08-llm-zero-history-test.md +56 -0
- package/docs/en/09-shadow-vs-light-dom.md +122 -0
- package/docs/en/README.md +16 -0
- package/docs/fr/00-the-mado-way.md +108 -0
- package/docs/fr/01-routing.md +202 -0
- package/docs/fr/02-project-layout.md +58 -0
- package/docs/fr/03-static-bake.md +290 -0
- package/docs/fr/04-ide-setup.md +162 -0
- package/docs/fr/05-why-mado.md +193 -0
- package/docs/fr/06-for-backenders.md +432 -0
- package/docs/fr/07-llm-pitfalls.md +487 -0
- package/docs/fr/08-llm-zero-history-test.md +60 -0
- package/docs/fr/09-shadow-vs-light-dom.md +121 -0
- package/docs/fr/README.md +16 -0
- package/docs/ru/00-the-mado-way.md +93 -0
- package/docs/ru/01-routing.md +194 -0
- package/docs/ru/02-project-layout.md +57 -0
- package/docs/ru/03-static-bake.md +251 -0
- package/docs/ru/04-ide-setup.md +144 -0
- package/docs/ru/05-why-mado.md +193 -0
- package/docs/ru/06-for-backenders.md +422 -0
- package/docs/ru/07-llm-pitfalls.md +485 -0
- package/docs/ru/08-llm-zero-history-test.md +56 -0
- package/docs/ru/09-shadow-vs-light-dom.md +122 -0
- package/docs/ru/README.md +14 -0
- package/docs/uk/00-the-mado-way.md +54 -0
- package/docs/uk/01-routing.md +82 -0
- package/docs/uk/02-project-layout.md +46 -0
- package/docs/uk/03-static-bake.md +49 -0
- package/docs/uk/04-ide-setup.md +26 -0
- package/docs/uk/05-why-mado.md +34 -0
- package/docs/uk/06-for-backenders.md +50 -0
- package/docs/uk/07-llm-pitfalls.md +82 -0
- package/docs/uk/08-llm-zero-history-test.md +31 -0
- package/docs/uk/09-shadow-vs-light-dom.md +40 -0
- package/docs/uk/README.md +16 -0
- package/llms.txt +155 -0
- package/package.json +81 -0
- package/scripts/bake.mjs +406 -0
- package/scripts/bundle.mjs +146 -0
- package/scripts/cli.mjs +382 -0
- package/scripts/new.mjs +80 -0
- package/scripts/preview.mjs +176 -0
- package/scripts/release-notes.mjs +66 -0
- package/scripts/showcase-regression.mjs +392 -0
- package/server/serve.mjs +292 -0
- package/starters/crud/README.md +21 -0
- package/starters/crud/index.html +20 -0
- package/starters/crud/package.json +17 -0
- package/starters/crud/src/components/app-shell.ts +51 -0
- package/starters/crud/src/components/ticket-detail.ts +33 -0
- package/starters/crud/src/components/ticket-form.ts +69 -0
- package/starters/crud/src/components/ticket-list.ts +66 -0
- package/starters/crud/src/lib/api.ts +76 -0
- package/starters/crud/src/main.ts +12 -0
- package/starters/crud/src/pages/home.ts +18 -0
- package/starters/crud/src/pages/not-found.ts +12 -0
- package/starters/crud/src/pages/ticket-detail.ts +6 -0
- package/starters/crud/src/pages/ticket-new.ts +6 -0
- package/starters/crud/src/pages/tickets.ts +6 -0
- package/starters/crud/src/routes.ts +9 -0
- package/starters/crud/src/styles/global.ts +155 -0
- package/starters/crud/tsconfig.json +15 -0
- package/starters/minimal/README.md +19 -0
- package/starters/minimal/index.html +20 -0
- package/starters/minimal/package.json +17 -0
- package/starters/minimal/src/components/app-counter.ts +31 -0
- package/starters/minimal/src/main.ts +9 -0
- package/starters/minimal/src/pages/home.ts +18 -0
- package/starters/minimal/src/pages/not-found.ts +14 -0
- package/starters/minimal/src/routes.ts +6 -0
- package/starters/minimal/src/styles/global.ts +60 -0
- package/starters/minimal/tsconfig.json +15 -0
- package/templates/page-detail.ts +63 -0
- package/templates/page-form.ts +94 -0
- 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>;
|