@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,263 @@
|
|
|
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 { signal } from "./../signal.js";
|
|
9
|
+
import { compile, matchRoute, } from "./match.js";
|
|
10
|
+
/**
|
|
11
|
+
* Minimal History API router.
|
|
12
|
+
*
|
|
13
|
+
* const route = router({
|
|
14
|
+
* '/': () => html`<x-home/>`,
|
|
15
|
+
* '/users/:id': ({ id }) => html`<x-user .id=${id}/>`,
|
|
16
|
+
* '*': () => html`<x-404/>`,
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* html`<main>${route.view}</main>`
|
|
20
|
+
*
|
|
21
|
+
* Lifecycle: subscribes to popstate + intercepts clicks
|
|
22
|
+
* on `<a data-link>` + hover-prefetch (if hook given). All this
|
|
23
|
+
* is removed in `dispose()` — mandatory to call in tests and
|
|
24
|
+
* dev-overlay, otherwise listener leak.
|
|
25
|
+
*/
|
|
26
|
+
export function router(routes, options = {}) {
|
|
27
|
+
const useViewTransitions = options.viewTransitions !== false;
|
|
28
|
+
const compiled = Object.entries(routes).map(([p, h]) => compile(p, h));
|
|
29
|
+
const fallback = compiled.find((r) => r.pattern === "*") ?? defaultFallback();
|
|
30
|
+
const path = signal(location.pathname);
|
|
31
|
+
const cleanups = [];
|
|
32
|
+
let disposed = false;
|
|
33
|
+
// -- popstate
|
|
34
|
+
const onPop = () => {
|
|
35
|
+
if (disposed)
|
|
36
|
+
return;
|
|
37
|
+
path.set(location.pathname);
|
|
38
|
+
};
|
|
39
|
+
window.addEventListener("popstate", onPop);
|
|
40
|
+
cleanups.push(() => window.removeEventListener("popstate", onPop));
|
|
41
|
+
// -- global click interception on <a data-link>
|
|
42
|
+
const onClick = (e) => {
|
|
43
|
+
if (disposed)
|
|
44
|
+
return;
|
|
45
|
+
const a = findAnchor(e, "a[data-link]");
|
|
46
|
+
if (!a)
|
|
47
|
+
return;
|
|
48
|
+
if (e.defaultPrevented)
|
|
49
|
+
return;
|
|
50
|
+
if (e.button !== 0)
|
|
51
|
+
return;
|
|
52
|
+
const me = e;
|
|
53
|
+
if (me.metaKey || me.ctrlKey || me.shiftKey || me.altKey)
|
|
54
|
+
return;
|
|
55
|
+
const url = new URL(a.href, location.href);
|
|
56
|
+
if (url.origin !== location.origin)
|
|
57
|
+
return;
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
api.navigate(url.pathname + url.search + url.hash);
|
|
60
|
+
};
|
|
61
|
+
document.addEventListener("click", onClick);
|
|
62
|
+
cleanups.push(() => document.removeEventListener("click", onClick));
|
|
63
|
+
// -- hover-prefetch (>=50ms on <a data-link:not([data-no-prefetch])>)
|
|
64
|
+
let prefetchTimer = null;
|
|
65
|
+
const onMouseOver = (e) => {
|
|
66
|
+
if (disposed)
|
|
67
|
+
return;
|
|
68
|
+
if (!options.prefetch)
|
|
69
|
+
return;
|
|
70
|
+
const a = findAnchor(e, "a[data-link]:not([data-no-prefetch])");
|
|
71
|
+
if (!a)
|
|
72
|
+
return;
|
|
73
|
+
if (prefetchTimer)
|
|
74
|
+
clearTimeout(prefetchTimer);
|
|
75
|
+
prefetchTimer = setTimeout(() => {
|
|
76
|
+
try {
|
|
77
|
+
const url = new URL(a.href, location.href);
|
|
78
|
+
if (url.origin !== location.origin)
|
|
79
|
+
return;
|
|
80
|
+
options.prefetch(url.pathname);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* noop */
|
|
84
|
+
}
|
|
85
|
+
}, 50);
|
|
86
|
+
};
|
|
87
|
+
const onMouseOut = () => {
|
|
88
|
+
if (prefetchTimer) {
|
|
89
|
+
clearTimeout(prefetchTimer);
|
|
90
|
+
prefetchTimer = null;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
document.addEventListener("mouseover", onMouseOver);
|
|
94
|
+
document.addEventListener("mouseout", onMouseOut);
|
|
95
|
+
cleanups.push(() => {
|
|
96
|
+
document.removeEventListener("mouseover", onMouseOver);
|
|
97
|
+
document.removeEventListener("mouseout", onMouseOut);
|
|
98
|
+
if (prefetchTimer)
|
|
99
|
+
clearTimeout(prefetchTimer);
|
|
100
|
+
});
|
|
101
|
+
const api = {
|
|
102
|
+
view: () => {
|
|
103
|
+
const p = path();
|
|
104
|
+
const m = matchRoute(p, compiled);
|
|
105
|
+
if (m)
|
|
106
|
+
return m.route.handler(m.params);
|
|
107
|
+
return fallback.handler({});
|
|
108
|
+
},
|
|
109
|
+
path,
|
|
110
|
+
navigate(to, opts) {
|
|
111
|
+
const apply = () => {
|
|
112
|
+
if (opts?.replace)
|
|
113
|
+
history.replaceState(null, "", to);
|
|
114
|
+
else
|
|
115
|
+
history.pushState(null, "", to);
|
|
116
|
+
path.set(location.pathname);
|
|
117
|
+
scrollToTop();
|
|
118
|
+
};
|
|
119
|
+
// View Transitions API: smooth crossfade between pages,
|
|
120
|
+
// if the browser supports it. Psychologically removes flashing
|
|
121
|
+
// even if the new page renders in 50-100ms.
|
|
122
|
+
const doc = document;
|
|
123
|
+
if (useViewTransitions && typeof doc.startViewTransition === "function") {
|
|
124
|
+
doc.startViewTransition(apply);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
apply();
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
dispose() {
|
|
131
|
+
if (disposed)
|
|
132
|
+
return;
|
|
133
|
+
disposed = true;
|
|
134
|
+
for (const c of cleanups.splice(0)) {
|
|
135
|
+
try {
|
|
136
|
+
c();
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
/* noop */
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
return api;
|
|
145
|
+
}
|
|
146
|
+
function findAnchor(e, selector) {
|
|
147
|
+
const fromElement = (node) => {
|
|
148
|
+
if (!(node instanceof Element))
|
|
149
|
+
return null;
|
|
150
|
+
const match = node.matches(selector) ? node : node.closest(selector);
|
|
151
|
+
if (!match || !("href" in match))
|
|
152
|
+
return null;
|
|
153
|
+
return match;
|
|
154
|
+
};
|
|
155
|
+
const direct = fromElement(e.target);
|
|
156
|
+
if (direct)
|
|
157
|
+
return direct;
|
|
158
|
+
if (typeof e.composedPath !== "function")
|
|
159
|
+
return null;
|
|
160
|
+
for (const node of e.composedPath()) {
|
|
161
|
+
const found = fromElement(node);
|
|
162
|
+
if (found)
|
|
163
|
+
return found;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
/** Default 404, if the manifest had no `'*'`. */
|
|
168
|
+
function defaultFallback() {
|
|
169
|
+
return compile("*", () => {
|
|
170
|
+
const tpl = document.createElement("template");
|
|
171
|
+
tpl.innerHTML = `<pre>404: ${location.pathname}</pre>`;
|
|
172
|
+
return {
|
|
173
|
+
_mado: true,
|
|
174
|
+
strings: Object.assign([""], { raw: [""] }),
|
|
175
|
+
values: [tpl.content],
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
// ---------- navigate() ----------
|
|
180
|
+
/**
|
|
181
|
+
* Global helper for programmatic navigation. Equivalent to
|
|
182
|
+
* `api.navigate(to)` for any active router — updates the URL
|
|
183
|
+
* via History API and dispatches `popstate`, which all
|
|
184
|
+
* active routers on the page will pick up.
|
|
185
|
+
*
|
|
186
|
+
* import { navigate } from "@madojs/mado";
|
|
187
|
+
* navigate("/users/42");
|
|
188
|
+
*
|
|
189
|
+
* Used inside form handlers / events when you don't have
|
|
190
|
+
* direct access to RouterApi (e.g. inside a component's setup function
|
|
191
|
+
* that didn't receive the router as a parameter).
|
|
192
|
+
*/
|
|
193
|
+
export function navigate(to, opts) {
|
|
194
|
+
if (typeof history === "undefined" || typeof window === "undefined")
|
|
195
|
+
return;
|
|
196
|
+
if (opts?.replace)
|
|
197
|
+
history.replaceState(null, "", to);
|
|
198
|
+
else
|
|
199
|
+
history.pushState(null, "", to);
|
|
200
|
+
scrollToTop();
|
|
201
|
+
// popstate is NOT dispatched automatically on pushState/replaceState,
|
|
202
|
+
// so we dispatch it manually — all active routers will hear and update.
|
|
203
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
204
|
+
}
|
|
205
|
+
function scrollToTop() {
|
|
206
|
+
const maybeWindow = window;
|
|
207
|
+
if (typeof maybeWindow.scrollTo !== "function")
|
|
208
|
+
return;
|
|
209
|
+
try {
|
|
210
|
+
maybeWindow.scrollTo({ top: 0, left: 0 });
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
/* noop */
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// ---------- queryParam ----------
|
|
217
|
+
//
|
|
218
|
+
// Reactive wrapper over ?foo=bar. Reading → signal, .set() → replaceState.
|
|
219
|
+
// All queryParams in the app are synchronised via a shared signal bus.
|
|
220
|
+
// The bus is lazily initialised on first queryParam() call, because
|
|
221
|
+
// in SSR/test environments window/location may be connected later.
|
|
222
|
+
let queryBus = null;
|
|
223
|
+
function ensureQueryBus() {
|
|
224
|
+
if (queryBus)
|
|
225
|
+
return queryBus;
|
|
226
|
+
queryBus = signal(location.search);
|
|
227
|
+
window.addEventListener("popstate", () => queryBus.set(location.search));
|
|
228
|
+
return queryBus;
|
|
229
|
+
}
|
|
230
|
+
function syncQuery(next, push) {
|
|
231
|
+
const url = `${location.pathname}?${next.toString()}${location.hash}`.replace(/\?$/, "");
|
|
232
|
+
if (push)
|
|
233
|
+
history.pushState(null, "", url);
|
|
234
|
+
else
|
|
235
|
+
history.replaceState(null, "", url);
|
|
236
|
+
ensureQueryBus().set(location.search);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Reactive query parameter.
|
|
240
|
+
*
|
|
241
|
+
* const page = queryParam('page', '1');
|
|
242
|
+
* page(); // '1' (or current URL value)
|
|
243
|
+
* page.set('2'); // history.replaceState and re-render
|
|
244
|
+
* page.set(null); // delete the parameter
|
|
245
|
+
*/
|
|
246
|
+
export function queryParam(name, defaultValue = "") {
|
|
247
|
+
const bus = ensureQueryBus();
|
|
248
|
+
const read = (() => {
|
|
249
|
+
const search = bus();
|
|
250
|
+
const params = new URLSearchParams(search);
|
|
251
|
+
return params.get(name) ?? defaultValue;
|
|
252
|
+
});
|
|
253
|
+
read.set = (value, opts) => {
|
|
254
|
+
const params = new URLSearchParams(location.search);
|
|
255
|
+
if (value === null || value === "")
|
|
256
|
+
params.delete(name);
|
|
257
|
+
else
|
|
258
|
+
params.set(name, value);
|
|
259
|
+
syncQuery(params, opts?.push ?? false);
|
|
260
|
+
};
|
|
261
|
+
return read;
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=navigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../../src/router/navigation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router module entry point. The implementation lives in `./router/`:
|
|
3
|
+
*
|
|
4
|
+
* ./router/match.ts — pure pattern matching (compile/flatten/normalize)
|
|
5
|
+
* ./router/navigation.ts — router() + navigate() + queryParam (touches window/history)
|
|
6
|
+
* ./router/manifest.ts — routes() + prefetchPath + lazy loading + sync-fast-path
|
|
7
|
+
*
|
|
8
|
+
* This file keeps compatibility for internal imports from `"./router.js"`.
|
|
9
|
+
* The public barrel (`src/index.ts`) also goes through this file.
|
|
10
|
+
*/
|
|
11
|
+
export { router, navigate, queryParam, type RouterApi, type RouterOptions, type QueryParam, type QuerySignal, } from "./router/navigation.js";
|
|
12
|
+
export type { RouteHandler, RouteParams, Routes, RoutesMap, } from "./router/match.js";
|
|
13
|
+
export { routes, prefetchPath, type RoutesOptions, _testHooks, } from "./router/manifest.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router module entry point. The implementation lives in `./router/`:
|
|
3
|
+
*
|
|
4
|
+
* ./router/match.ts — pure pattern matching (compile/flatten/normalize)
|
|
5
|
+
* ./router/navigation.ts — router() + navigate() + queryParam (touches window/history)
|
|
6
|
+
* ./router/manifest.ts — routes() + prefetchPath + lazy loading + sync-fast-path
|
|
7
|
+
*
|
|
8
|
+
* This file keeps compatibility for internal imports from `"./router.js"`.
|
|
9
|
+
* The public barrel (`src/index.ts`) also goes through this file.
|
|
10
|
+
*/
|
|
11
|
+
export { router, navigate, queryParam, } from "./router/navigation.js";
|
|
12
|
+
export { routes, prefetchPath, _testHooks, } from "./router/manifest.js";
|
|
13
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,MAAM,EACN,QAAQ,EACR,UAAU,GAKX,MAAM,wBAAwB,CAAC;AAShC,OAAO,EACL,MAAM,EACN,YAAY,EAEZ,UAAU,GACX,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity via signals.
|
|
3
|
+
*
|
|
4
|
+
* Idea: a signal is a getter-function that also has .set / .update.
|
|
5
|
+
* When a signal is read inside an effect/computed, we record "who read it"
|
|
6
|
+
* and notify subscribers when the value changes. No proxies, no Virtual DOM.
|
|
7
|
+
*
|
|
8
|
+
* Performance:
|
|
9
|
+
* - effect runs are deduplicated and scheduled via queueMicrotask,
|
|
10
|
+
* so multiple .set() calls in a row produce a single subscriber pass;
|
|
11
|
+
* - batch(fn) explicitly groups changes (supports arbitrary nesting);
|
|
12
|
+
* - flushSync() — flush pending effects right now (useful in tests).
|
|
13
|
+
*
|
|
14
|
+
* API:
|
|
15
|
+
* const count = signal(0);
|
|
16
|
+
* count(); // get
|
|
17
|
+
* count.set(5); // set
|
|
18
|
+
* count.update(n=>n+1);
|
|
19
|
+
*
|
|
20
|
+
* const doubled = computed(() => count() * 2);
|
|
21
|
+
*
|
|
22
|
+
* effect(() => console.log(count()));
|
|
23
|
+
*
|
|
24
|
+
* batch(() => { a.set(1); b.set(2); });
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Group multiple signal changes into a single subscriber pass.
|
|
28
|
+
* Supports arbitrary nesting.
|
|
29
|
+
*/
|
|
30
|
+
export declare function batch<T>(fn: () => T): T;
|
|
31
|
+
/**
|
|
32
|
+
* Forcefully flush pending effects right now (synchronously).
|
|
33
|
+
* Useful in tests: removes the need to wait for a microtask.
|
|
34
|
+
*/
|
|
35
|
+
export declare function flushSync(): void;
|
|
36
|
+
export interface Signal<T> {
|
|
37
|
+
(): T;
|
|
38
|
+
set(value: T): void;
|
|
39
|
+
update(updater: (prev: T) => T): void;
|
|
40
|
+
peek(): T;
|
|
41
|
+
}
|
|
42
|
+
export declare function signal<T>(initial: T): Signal<T>;
|
|
43
|
+
export interface Computed<T> {
|
|
44
|
+
(): T;
|
|
45
|
+
peek(): T;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Lazy computed based on a dirty-flag:
|
|
49
|
+
* - fn is NOT called until the computed is read;
|
|
50
|
+
* - if none of the deps changed since the last read — cached value is returned;
|
|
51
|
+
* - when a dep changes the computed is marked dirty (NOT recomputed),
|
|
52
|
+
* and triggers its own subscribers via schedule(). Subscribers
|
|
53
|
+
* (an effect or another computed) will read our value on their next run
|
|
54
|
+
* → fn is recomputed exactly once.
|
|
55
|
+
*
|
|
56
|
+
* Implementation: computed = "signal source" (has subscribers) +
|
|
57
|
+
* "tracker" (has deps). When a dep calls the tracker's run(),
|
|
58
|
+
* instead of actually recomputing we mark ourselves dirty and
|
|
59
|
+
* propagate to subscribers.
|
|
60
|
+
*/
|
|
61
|
+
export declare function computed<T>(fn: () => T): Computed<T>;
|
|
62
|
+
export type Disposer = () => void;
|
|
63
|
+
export declare function effect(fn: () => void | Disposer): Disposer;
|
|
64
|
+
/**
|
|
65
|
+
* Execute a function outside of tracking — reading signals will not create a subscription.
|
|
66
|
+
*/
|
|
67
|
+
export declare function untracked<T>(fn: () => T): T;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity via signals.
|
|
3
|
+
*
|
|
4
|
+
* Idea: a signal is a getter-function that also has .set / .update.
|
|
5
|
+
* When a signal is read inside an effect/computed, we record "who read it"
|
|
6
|
+
* and notify subscribers when the value changes. No proxies, no Virtual DOM.
|
|
7
|
+
*
|
|
8
|
+
* Performance:
|
|
9
|
+
* - effect runs are deduplicated and scheduled via queueMicrotask,
|
|
10
|
+
* so multiple .set() calls in a row produce a single subscriber pass;
|
|
11
|
+
* - batch(fn) explicitly groups changes (supports arbitrary nesting);
|
|
12
|
+
* - flushSync() — flush pending effects right now (useful in tests).
|
|
13
|
+
*
|
|
14
|
+
* API:
|
|
15
|
+
* const count = signal(0);
|
|
16
|
+
* count(); // get
|
|
17
|
+
* count.set(5); // set
|
|
18
|
+
* count.update(n=>n+1);
|
|
19
|
+
*
|
|
20
|
+
* const doubled = computed(() => count() * 2);
|
|
21
|
+
*
|
|
22
|
+
* effect(() => console.log(count()));
|
|
23
|
+
*
|
|
24
|
+
* batch(() => { a.set(1); b.set(2); });
|
|
25
|
+
*/
|
|
26
|
+
let activeTracker = null;
|
|
27
|
+
// ---------- Scheduler ----------
|
|
28
|
+
const pending = new Set();
|
|
29
|
+
let batchDepth = 0;
|
|
30
|
+
let flushScheduled = false;
|
|
31
|
+
function schedule(sub) {
|
|
32
|
+
pending.add(sub);
|
|
33
|
+
if (batchDepth > 0)
|
|
34
|
+
return;
|
|
35
|
+
if (flushScheduled)
|
|
36
|
+
return;
|
|
37
|
+
flushScheduled = true;
|
|
38
|
+
queueMicrotask(flush);
|
|
39
|
+
}
|
|
40
|
+
function flush() {
|
|
41
|
+
flushScheduled = false;
|
|
42
|
+
// guard against Set modification during iteration
|
|
43
|
+
while (pending.size > 0) {
|
|
44
|
+
const subs = [...pending];
|
|
45
|
+
pending.clear();
|
|
46
|
+
for (const sub of subs) {
|
|
47
|
+
try {
|
|
48
|
+
sub();
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
// a subscriber must not crash the others
|
|
52
|
+
// eslint-disable-next-line no-console
|
|
53
|
+
console.error("[mado] effect threw:", err);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Group multiple signal changes into a single subscriber pass.
|
|
60
|
+
* Supports arbitrary nesting.
|
|
61
|
+
*/
|
|
62
|
+
export function batch(fn) {
|
|
63
|
+
batchDepth++;
|
|
64
|
+
try {
|
|
65
|
+
return fn();
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
batchDepth--;
|
|
69
|
+
if (batchDepth === 0 && pending.size > 0 && !flushScheduled) {
|
|
70
|
+
flushScheduled = true;
|
|
71
|
+
queueMicrotask(flush);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Forcefully flush pending effects right now (synchronously).
|
|
77
|
+
* Useful in tests: removes the need to wait for a microtask.
|
|
78
|
+
*/
|
|
79
|
+
export function flushSync() {
|
|
80
|
+
flush();
|
|
81
|
+
}
|
|
82
|
+
export function signal(initial) {
|
|
83
|
+
let value = initial;
|
|
84
|
+
const subscribers = new Set();
|
|
85
|
+
const read = (() => {
|
|
86
|
+
if (activeTracker) {
|
|
87
|
+
subscribers.add(activeTracker.entry);
|
|
88
|
+
activeTracker.deps.add(subscribers);
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
});
|
|
92
|
+
read.set = (next) => {
|
|
93
|
+
if (Object.is(value, next))
|
|
94
|
+
return;
|
|
95
|
+
value = next;
|
|
96
|
+
// snapshot: a subscriber may re-subscribe in run(), mutating the Set
|
|
97
|
+
const snapshot = [...subscribers];
|
|
98
|
+
// sync subscribers (computed) first — mark dirty before effects read;
|
|
99
|
+
// then async via the scheduler.
|
|
100
|
+
for (const e of snapshot) {
|
|
101
|
+
if (e.sync) {
|
|
102
|
+
try {
|
|
103
|
+
e.run();
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
// eslint-disable-next-line no-console
|
|
107
|
+
console.error("[mado] sync subscriber threw:", err);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const e of snapshot) {
|
|
112
|
+
if (!e.sync)
|
|
113
|
+
schedule(e.run);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
read.update = (fn) => read.set(fn(value));
|
|
117
|
+
read.peek = () => value;
|
|
118
|
+
return read;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Lazy computed based on a dirty-flag:
|
|
122
|
+
* - fn is NOT called until the computed is read;
|
|
123
|
+
* - if none of the deps changed since the last read — cached value is returned;
|
|
124
|
+
* - when a dep changes the computed is marked dirty (NOT recomputed),
|
|
125
|
+
* and triggers its own subscribers via schedule(). Subscribers
|
|
126
|
+
* (an effect or another computed) will read our value on their next run
|
|
127
|
+
* → fn is recomputed exactly once.
|
|
128
|
+
*
|
|
129
|
+
* Implementation: computed = "signal source" (has subscribers) +
|
|
130
|
+
* "tracker" (has deps). When a dep calls the tracker's run(),
|
|
131
|
+
* instead of actually recomputing we mark ourselves dirty and
|
|
132
|
+
* propagate to subscribers.
|
|
133
|
+
*/
|
|
134
|
+
export function computed(fn) {
|
|
135
|
+
const subscribers = new Set();
|
|
136
|
+
let value = undefined;
|
|
137
|
+
let dirty = true;
|
|
138
|
+
const onInvalidate = () => {
|
|
139
|
+
// dep changed → mark dirty synchronously and cascade.
|
|
140
|
+
// Sync subscribers (other computed) are triggered immediately — they also
|
|
141
|
+
// set dirty without delay. Async (effects) go through the scheduler.
|
|
142
|
+
if (dirty)
|
|
143
|
+
return;
|
|
144
|
+
dirty = true;
|
|
145
|
+
const snapshot = [...subscribers];
|
|
146
|
+
for (const e of snapshot) {
|
|
147
|
+
if (e.sync) {
|
|
148
|
+
try {
|
|
149
|
+
e.run();
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
// eslint-disable-next-line no-console
|
|
153
|
+
console.error("[mado] sync subscriber threw:", err);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
schedule(e.run);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const tracker = {
|
|
162
|
+
deps: new Set(),
|
|
163
|
+
entry: { run: onInvalidate, sync: true },
|
|
164
|
+
};
|
|
165
|
+
const recompute = () => {
|
|
166
|
+
for (const dep of tracker.deps)
|
|
167
|
+
dep.delete(tracker.entry);
|
|
168
|
+
tracker.deps.clear();
|
|
169
|
+
const prev = activeTracker;
|
|
170
|
+
activeTracker = tracker;
|
|
171
|
+
try {
|
|
172
|
+
value = fn();
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
activeTracker = prev;
|
|
176
|
+
}
|
|
177
|
+
dirty = false;
|
|
178
|
+
};
|
|
179
|
+
const read = (() => {
|
|
180
|
+
if (activeTracker) {
|
|
181
|
+
subscribers.add(activeTracker.entry);
|
|
182
|
+
activeTracker.deps.add(subscribers);
|
|
183
|
+
}
|
|
184
|
+
if (dirty)
|
|
185
|
+
recompute();
|
|
186
|
+
return value;
|
|
187
|
+
});
|
|
188
|
+
read.peek = () => {
|
|
189
|
+
if (dirty)
|
|
190
|
+
recompute();
|
|
191
|
+
return value;
|
|
192
|
+
};
|
|
193
|
+
return read;
|
|
194
|
+
}
|
|
195
|
+
export function effect(fn) {
|
|
196
|
+
let cleanup;
|
|
197
|
+
const run = () => {
|
|
198
|
+
for (const dep of tracker.deps)
|
|
199
|
+
dep.delete(tracker.entry);
|
|
200
|
+
tracker.deps.clear();
|
|
201
|
+
if (typeof cleanup === "function")
|
|
202
|
+
cleanup();
|
|
203
|
+
const prev = activeTracker;
|
|
204
|
+
activeTracker = tracker;
|
|
205
|
+
try {
|
|
206
|
+
cleanup = fn();
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
activeTracker = prev;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
const tracker = {
|
|
213
|
+
deps: new Set(),
|
|
214
|
+
entry: { run, sync: false },
|
|
215
|
+
};
|
|
216
|
+
run();
|
|
217
|
+
return () => {
|
|
218
|
+
for (const dep of tracker.deps)
|
|
219
|
+
dep.delete(tracker.entry);
|
|
220
|
+
tracker.deps.clear();
|
|
221
|
+
if (typeof cleanup === "function")
|
|
222
|
+
cleanup();
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Execute a function outside of tracking — reading signals will not create a subscription.
|
|
227
|
+
*/
|
|
228
|
+
export function untracked(fn) {
|
|
229
|
+
const prev = activeTracker;
|
|
230
|
+
activeTracker = null;
|
|
231
|
+
try {
|
|
232
|
+
return fn();
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
activeTracker = prev;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=signal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal.js","sourceRoot":"","sources":["../../src/signal.ts"],"names":[],"mappings":"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"}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Mado docs
|
|
2
|
+
|
|
3
|
+
Documentation is organized by language.
|
|
4
|
+
|
|
5
|
+
| Language | Status |
|
|
6
|
+
|---|---|
|
|
7
|
+
| [Français](./fr/README.md) | translated |
|
|
8
|
+
| [English](./en/README.md) | translated |
|
|
9
|
+
| [Ukrainian](./uk/README.md) | translated |
|
|
10
|
+
| [Russian](./ru/README.md) | translated |
|
|
11
|
+
|
|
12
|
+
Translations and comment localization are tracked separately from core work.
|