@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,9 @@
1
+ import { html, render } from "@madojs/mado";
2
+ import "./styles/global.js";
3
+ import router from "./routes.js";
4
+ import "./components/app-counter.js";
5
+
6
+ const app = document.getElementById("app");
7
+ if (!app) throw new Error("#app not found");
8
+
9
+ render(html`${router.view}`, app);
@@ -0,0 +1,18 @@
1
+ import { html, page } from "@madojs/mado";
2
+
3
+ export default page({
4
+ title: "__APP_NAME__",
5
+ view: () => html`
6
+ <main class="shell">
7
+ <section class="panel">
8
+ <p class="eyebrow">Mado starter</p>
9
+ <h1>__APP_NAME__</h1>
10
+ <p>
11
+ A tiny native-web app with browser ESM, Web Components, signals and
12
+ tagged-template HTML.
13
+ </p>
14
+ <x-app-counter></x-app-counter>
15
+ </section>
16
+ </main>
17
+ `,
18
+ });
@@ -0,0 +1,14 @@
1
+ import { html, page } from "@madojs/mado";
2
+
3
+ export default page({
4
+ title: "Not found",
5
+ view: () => html`
6
+ <main class="shell">
7
+ <section class="panel">
8
+ <h1>Not found</h1>
9
+ <p>The page does not exist.</p>
10
+ <a href="/" data-link>Back home</a>
11
+ </section>
12
+ </main>
13
+ `,
14
+ });
@@ -0,0 +1,6 @@
1
+ import { routes } from "@madojs/mado";
2
+
3
+ export default routes({
4
+ "/": () => import("./pages/home.js"),
5
+ "*": () => import("./pages/not-found.js"),
6
+ });
@@ -0,0 +1,60 @@
1
+ import { css } from "@madojs/mado";
2
+
3
+ const sheet = css`
4
+ :root {
5
+ color-scheme: light;
6
+ font-family:
7
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
8
+ "Segoe UI", sans-serif;
9
+ color: #172033;
10
+ background: #f6f7f9;
11
+ }
12
+
13
+ body {
14
+ margin: 0;
15
+ }
16
+
17
+ a {
18
+ color: #2563eb;
19
+ }
20
+
21
+ .shell {
22
+ min-height: 100vh;
23
+ display: grid;
24
+ place-items: center;
25
+ padding: 2rem;
26
+ box-sizing: border-box;
27
+ }
28
+
29
+ .panel {
30
+ width: min(100%, 44rem);
31
+ border: 1px solid #d9dee8;
32
+ border-radius: 8px;
33
+ background: white;
34
+ padding: 2rem;
35
+ box-shadow: 0 16px 40px rgb(17 24 39 / 0.08);
36
+ }
37
+
38
+ .eyebrow {
39
+ margin: 0 0 0.5rem;
40
+ color: #64748b;
41
+ font-size: 0.8rem;
42
+ font-weight: 700;
43
+ letter-spacing: 0.08em;
44
+ text-transform: uppercase;
45
+ }
46
+
47
+ h1 {
48
+ margin: 0 0 0.75rem;
49
+ font-size: clamp(2rem, 6vw, 4rem);
50
+ line-height: 1;
51
+ letter-spacing: 0;
52
+ }
53
+
54
+ p {
55
+ max-width: 34rem;
56
+ line-height: 1.6;
57
+ }
58
+ `;
59
+
60
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022", "DOM"],
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "dist",
10
+ "rootDir": "src",
11
+ "declaration": false,
12
+ "sourceMap": true
13
+ },
14
+ "include": ["src/**/*.ts"]
15
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Template: detail page by :id (loads one entity).
3
+ *
4
+ * Add to routes.ts:
5
+ * '/__name__/:id': () => import('./pages/__name__-detail.js'),
6
+ */
7
+
8
+ import {
9
+ page,
10
+ component,
11
+ html,
12
+ css,
13
+ resource,
14
+ jsonFetcher,
15
+ } from "@madojs/mado";
16
+
17
+ interface Entity {
18
+ id: number;
19
+ name: string;
20
+ }
21
+
22
+ component(
23
+ "x-__name__-detail",
24
+ ({ host }) => {
25
+ // id comes from data-id; page() sets it from params.
26
+ const idAttr = () => host.dataset.id ?? "";
27
+
28
+ const item = resource<Entity>(
29
+ () => `/api/__name__/${idAttr()}`,
30
+ jsonFetcher(),
31
+ { staleTime: 60_000 },
32
+ );
33
+
34
+ return () => html`
35
+ <section>
36
+ ${() =>
37
+ item.loading()
38
+ ? html`<p><i>loading…</i></p>`
39
+ : item.error()
40
+ ? html`<p class="err">${item.error()!.message}</p>`
41
+ : item.data()
42
+ ? html`
43
+ <h1>${item.data()!.name}</h1>
44
+ <p>id: ${item.data()!.id}</p>
45
+ `
46
+ : null}
47
+ <a href="/__name__" data-link>← back to list</a>
48
+ </section>
49
+ `;
50
+ },
51
+ {
52
+ styles: css`
53
+ :host { display: block; }
54
+ .err { color: #c00; }
55
+ `,
56
+ },
57
+ );
58
+
59
+ export default page<{ id: string }>({
60
+ title: ({ id }) => `__Name__ #${id}`,
61
+ view: ({ params }) =>
62
+ html`<x-__name__-detail data-id=${params.id}></x-__name__-detail>`,
63
+ });
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Template: create/edit form page.
3
+ */
4
+
5
+ import {
6
+ page,
7
+ component,
8
+ html,
9
+ css,
10
+ signal,
11
+ useForm,
12
+ mutation,
13
+ } from "@madojs/mado";
14
+
15
+ interface Values {
16
+ name: string;
17
+ email: string;
18
+ [k: string]: string | number | boolean | undefined;
19
+ }
20
+
21
+ component(
22
+ "x-__name__-page",
23
+ () => {
24
+ const f = useForm<Values>({
25
+ name: { required: true, minLength: 2, default: "" },
26
+ email: { required: true, type: "email", default: "" },
27
+ });
28
+
29
+ const save = mutation<Values, Values>(async (v) => {
30
+ const r = await fetch("/api/__name__", {
31
+ method: "POST",
32
+ headers: { "content-type": "application/json" },
33
+ body: JSON.stringify(v),
34
+ });
35
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
36
+ return (await r.json()) as Values;
37
+ });
38
+
39
+ const done = signal(false);
40
+
41
+ const submit = f.onSubmit(async (v) => {
42
+ await save.run(v);
43
+ done.set(true);
44
+ f.reset();
45
+ });
46
+
47
+ const err = (name: keyof Values) => () =>
48
+ f.touched()[name as string] && f.errors()[name as string]
49
+ ? html`<small class="err">${f.errors()[name as string]}</small>`
50
+ : null;
51
+
52
+ return () => html`
53
+ <section>
54
+ <h1>__Name__</h1>
55
+ <form @submit=${submit} novalidate>
56
+ <label>
57
+ Name
58
+ <input name="name" .value=${() => f.values().name ?? ""}
59
+ @input=${f.onInput} @blur=${f.onBlur} />
60
+ ${err("name")}
61
+ </label>
62
+ <label>
63
+ Email
64
+ <input name="email" type="email" .value=${() => f.values().email ?? ""}
65
+ @input=${f.onInput} @blur=${f.onBlur} />
66
+ ${err("email")}
67
+ </label>
68
+ <button type="submit"
69
+ ?disabled=${() => !f.isValid() || f.submitting() || save.loading()}>
70
+ ${() => (save.loading() || f.submitting() ? "saving…" : "save")}
71
+ </button>
72
+ ${() => (save.error() ? html`<p class="err">${save.error()!.message}</p>` : null)}
73
+ ${() => (done() ? html`<p class="ok">saved</p>` : null)}
74
+ </form>
75
+ </section>
76
+ `;
77
+ },
78
+ {
79
+ styles: css`
80
+ :host { display: block; }
81
+ label { display: block; margin: .5rem 0; }
82
+ input { display: block; margin-top: .25rem; padding: .25rem .5rem;
83
+ border: 1px solid #999; border-radius: 4px; min-width: 14rem; }
84
+ .err { color: #c00; }
85
+ .ok { color: #060; }
86
+ button:disabled { opacity: .5; }
87
+ `,
88
+ },
89
+ );
90
+
91
+ export default page({
92
+ title: "__Name__",
93
+ view: () => html`<x-__name__-page></x-__name__-page>`,
94
+ });
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Template: list page with URL filter and pagination.
3
+ * Copy into src/pages/ and rename.
4
+ */
5
+
6
+ import {
7
+ page,
8
+ component,
9
+ html,
10
+ css,
11
+ each,
12
+ resource,
13
+ queryParam,
14
+ jsonFetcher,
15
+ } from "@madojs/mado";
16
+
17
+ interface Item {
18
+ id: number;
19
+ name: string;
20
+ }
21
+
22
+ component(
23
+ "x-__name__-page",
24
+ () => {
25
+ const search = queryParam("q", "");
26
+ const pageQ = queryParam("page", "1");
27
+ const limit = 20;
28
+
29
+ const items = resource<Item[]>(
30
+ () =>
31
+ `/api/__name__?q=${encodeURIComponent(search())}&page=${pageQ()}&limit=${limit}`,
32
+ jsonFetcher(),
33
+ { staleTime: 30_000 },
34
+ );
35
+
36
+ return () => html`
37
+ <section>
38
+ <h1>__Name__</h1>
39
+ <input
40
+ placeholder="search..."
41
+ .value=${search}
42
+ @input=${(e: Event) =>
43
+ search.set((e.target as HTMLInputElement).value)}
44
+ />
45
+ ${() => (items.loading() ? html`<p><i>loading…</i></p>` : null)}
46
+ ${() =>
47
+ items.error()
48
+ ? html`<p class="err">${items.error()!.message}</p>`
49
+ : null}
50
+ <ul>
51
+ ${() =>
52
+ each(
53
+ items.data() ?? [],
54
+ (it) => it.id,
55
+ (it) => html`<li>${it.name}</li>`,
56
+ )}
57
+ </ul>
58
+ <nav>
59
+ <button @click=${() =>
60
+ pageQ.set(String(Math.max(1, +pageQ() - 1)))}>‹</button>
61
+ <span>${pageQ}</span>
62
+ <button @click=${() => pageQ.set(String(+pageQ() + 1))}>›</button>
63
+ </nav>
64
+ </section>
65
+ `;
66
+ },
67
+ {
68
+ styles: css`
69
+ :host { display: block; }
70
+ .err { color: #c00; }
71
+ ul { padding-left: 1.2rem; }
72
+ `,
73
+ },
74
+ );
75
+
76
+ export default page({
77
+ title: "__Name__",
78
+ view: () => html`<x-__name__-page></x-__name__-page>`,
79
+ });