@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,28 @@
1
+ /**
2
+ * Internal dev diagnostics.
3
+ *
4
+ * Warnings are intentionally best-effort: they never throw and they are printed
5
+ * once per code so noisy app renders do not flood the console.
6
+ */
7
+ const seen = new Set();
8
+ export function warnOnce(code, message) {
9
+ if (seen.has(code))
10
+ return;
11
+ seen.add(code);
12
+ try {
13
+ // eslint-disable-next-line no-console
14
+ console.warn(`[mado:${code}] ${message}`);
15
+ }
16
+ catch {
17
+ /* noop */
18
+ }
19
+ }
20
+ export const _testHooks = {
21
+ resetWarnings() {
22
+ seen.clear();
23
+ },
24
+ seenWarnings() {
25
+ return [...seen];
26
+ },
27
+ };
28
+ //# sourceMappingURL=diagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../src/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;AAE/B,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,OAAe;IACpD,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO;IAC3B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACf,IAAI,CAAC;QACH,sCAAsC;QACtC,OAAO,CAAC,IAAI,CAAC,SAAS,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,UAAU;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,aAAa;QACX,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IACD,YAAY;QACV,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IACnB,CAAC;CACF,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Keyed list rendering.
3
+ *
4
+ * each() returns a special EachResult descriptor. The html binder recognizes
5
+ * it by the `_madoEach` flag and applies keyed reconciliation: on every
6
+ * update it reuses DOM nodes for the same keys and moves them with
7
+ * insertBefore instead of recreating everything.
8
+ *
9
+ * Key differences from a naive implementation:
10
+ * - state lives in the parent's ChildState, not globally by renderFn, so two
11
+ * each() calls with the same render function do not interfere;
12
+ * - DOM nodes are actually reused, so input focus, animations,
13
+ * IntersectionObserver state and .scrollTop survive reorder.
14
+ *
15
+ * API:
16
+ * each(items, item => item.id, item => html`<li>${item.name}</li>`)
17
+ *
18
+ * Template usage:
19
+ * html`<ul>${() => each(items(), t => t.id, t => html`<li>${t.text}</li>`)}</ul>`
20
+ */
21
+ import { type TemplateResult } from "./html.js";
22
+ export type EachKey = string | number;
23
+ /**
24
+ * Marker recognised by the html binder.
25
+ * Do not set `_madoEach` yourself — use each().
26
+ */
27
+ export interface EachResult<T = unknown> {
28
+ readonly _madoEach: true;
29
+ readonly items: readonly T[];
30
+ readonly keyOf: (item: T, index: number) => EachKey;
31
+ readonly render: (item: T, index: number) => TemplateResult;
32
+ }
33
+ export declare const isEachResult: (v: unknown) => v is EachResult;
34
+ export declare function each<T>(items: readonly T[], keyOf: (item: T, index: number) => EachKey, render: (item: T, index: number) => TemplateResult): EachResult<T>;
35
+ /**
36
+ * Sugar: returns a ready TemplateResult with the given list as children.
37
+ * Convenient when you need a list without a wrapping parent.
38
+ */
39
+ export declare function list<T>(items: readonly T[], keyOf: (item: T, index: number) => EachKey, render: (item: T, index: number) => TemplateResult): TemplateResult;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Keyed list rendering.
3
+ *
4
+ * each() returns a special EachResult descriptor. The html binder recognizes
5
+ * it by the `_madoEach` flag and applies keyed reconciliation: on every
6
+ * update it reuses DOM nodes for the same keys and moves them with
7
+ * insertBefore instead of recreating everything.
8
+ *
9
+ * Key differences from a naive implementation:
10
+ * - state lives in the parent's ChildState, not globally by renderFn, so two
11
+ * each() calls with the same render function do not interfere;
12
+ * - DOM nodes are actually reused, so input focus, animations,
13
+ * IntersectionObserver state and .scrollTop survive reorder.
14
+ *
15
+ * API:
16
+ * each(items, item => item.id, item => html`<li>${item.name}</li>`)
17
+ *
18
+ * Template usage:
19
+ * html`<ul>${() => each(items(), t => t.id, t => html`<li>${t.text}</li>`)}</ul>`
20
+ */
21
+ import { html } from "./html.js";
22
+ export const isEachResult = (v) => typeof v === "object" &&
23
+ v !== null &&
24
+ v._madoEach === true;
25
+ export function each(items, keyOf, render) {
26
+ return { _madoEach: true, items, keyOf, render };
27
+ }
28
+ /**
29
+ * Sugar: returns a ready TemplateResult with the given list as children.
30
+ * Convenient when you need a list without a wrapping parent.
31
+ */
32
+ export function list(items, keyOf, render) {
33
+ return html `${each(items, keyOf, render)}`;
34
+ }
35
+ //# sourceMappingURL=each.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"each.js","sourceRoot":"","sources":["../../src/each.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,IAAI,EAAuB,MAAM,WAAW,CAAC;AAetD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAU,EAAmB,EAAE,CAC1D,OAAO,CAAC,KAAK,QAAQ;IACrB,CAAC,KAAK,IAAI;IACT,CAAgB,CAAC,SAAS,KAAK,IAAI,CAAC;AAEvC,MAAM,UAAU,IAAI,CAClB,KAAmB,EACnB,KAA0C,EAC1C,MAAkD;IAElD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,IAAI,CAClB,KAAmB,EACnB,KAA0C,EAC1C,MAAkD;IAElD,OAAO,IAAI,CAAA,GAAG,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Forms without pain. Built on top of native `<form>` + Constraint Validation API.
3
+ *
4
+ * const f = useForm({
5
+ * email: { required: true, type: 'email' },
6
+ * age: { min: 18 },
7
+ * });
8
+ *
9
+ * html`
10
+ * <form @submit=${f.onSubmit(async values => { await save(values) })}>
11
+ * <input name="email" .value=${() => f.values().email ?? ''}
12
+ * @input=${f.onInput} />
13
+ * ${() => f.errors().email ? html`<err>${f.errors().email}</err>` : null}
14
+ *
15
+ * <input name="age" type="number" .value=${() => f.values().age ?? ''}
16
+ * @input=${f.onInput} />
17
+ *
18
+ * <button ?disabled=${() => !f.isValid() || f.submitting()}>Save</button>
19
+ * </form>
20
+ * `;
21
+ *
22
+ * What's inside:
23
+ * - validation is performed via standard browser attributes
24
+ * (required, min, max, pattern, type=email/url/number, etc.);
25
+ * - custom rules via a validate(values) function returning
26
+ * { field: 'msg' } or null;
27
+ * - all fields are automatically "touched" on blur — errors
28
+ * are shown only after user interaction;
29
+ * - submit() is aborted if the form is invalid.
30
+ *
31
+ * No Yup/Zod/Formik dependencies. If you really want them —
32
+ * pass your own validate(values) and wire it to anything.
33
+ */
34
+ import { type Signal } from "./signal.js";
35
+ export type FormValues = Record<string, string | number | boolean | undefined>;
36
+ export type FormErrors = Record<string, string | undefined>;
37
+ /** Field declaration. Attributes match HTML5. */
38
+ export interface FieldSchema {
39
+ required?: boolean;
40
+ type?: "text" | "email" | "url" | "number" | "tel" | "password";
41
+ min?: number;
42
+ max?: number;
43
+ minLength?: number;
44
+ maxLength?: number;
45
+ pattern?: string;
46
+ /** Default value */
47
+ default?: string | number | boolean;
48
+ }
49
+ export type Schema = Record<string, FieldSchema>;
50
+ export interface UseFormOptions<V extends FormValues> {
51
+ /** Custom validator: returns errors or null. */
52
+ validate?: (values: V) => FormErrors | null;
53
+ }
54
+ export interface FormApi<V extends FormValues> {
55
+ values: Signal<V>;
56
+ errors: () => FormErrors;
57
+ touched: Signal<Record<string, boolean>>;
58
+ submitting: Signal<boolean>;
59
+ isValid: () => boolean;
60
+ /** Binding for @input/@change: automatically picks up name+value+type. */
61
+ onInput: (e: Event) => void;
62
+ /** Binding for @blur: marks the field as touched. */
63
+ onBlur: (e: Event) => void;
64
+ /** Submit wrapper: calls handler only if the form is valid. */
65
+ onSubmit: (handler: (values: V) => void | Promise<void>) => (e: Event) => void;
66
+ /** Set a field value programmatically. */
67
+ setField<K extends keyof V>(name: K, value: V[K]): void;
68
+ /** Reset to defaults. */
69
+ reset(): void;
70
+ }
71
+ export declare function useForm<V extends FormValues>(schema: Schema, options?: UseFormOptions<V>): FormApi<V>;
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Forms without pain. Built on top of native `<form>` + Constraint Validation API.
3
+ *
4
+ * const f = useForm({
5
+ * email: { required: true, type: 'email' },
6
+ * age: { min: 18 },
7
+ * });
8
+ *
9
+ * html`
10
+ * <form @submit=${f.onSubmit(async values => { await save(values) })}>
11
+ * <input name="email" .value=${() => f.values().email ?? ''}
12
+ * @input=${f.onInput} />
13
+ * ${() => f.errors().email ? html`<err>${f.errors().email}</err>` : null}
14
+ *
15
+ * <input name="age" type="number" .value=${() => f.values().age ?? ''}
16
+ * @input=${f.onInput} />
17
+ *
18
+ * <button ?disabled=${() => !f.isValid() || f.submitting()}>Save</button>
19
+ * </form>
20
+ * `;
21
+ *
22
+ * What's inside:
23
+ * - validation is performed via standard browser attributes
24
+ * (required, min, max, pattern, type=email/url/number, etc.);
25
+ * - custom rules via a validate(values) function returning
26
+ * { field: 'msg' } or null;
27
+ * - all fields are automatically "touched" on blur — errors
28
+ * are shown only after user interaction;
29
+ * - submit() is aborted if the form is invalid.
30
+ *
31
+ * No Yup/Zod/Formik dependencies. If you really want them —
32
+ * pass your own validate(values) and wire it to anything.
33
+ */
34
+ import { signal, computed } from "./signal.js";
35
+ export function useForm(schema, options = {}) {
36
+ const defaults = () => {
37
+ const out = {};
38
+ for (const k in schema) {
39
+ const s = schema[k];
40
+ if (s.default !== undefined)
41
+ out[k] = s.default;
42
+ }
43
+ return out;
44
+ };
45
+ const values = signal(defaults());
46
+ const touched = signal({});
47
+ const submitting = signal(false);
48
+ const errors = computed(() => {
49
+ const v = values();
50
+ const out = {};
51
+ for (const name in schema) {
52
+ const s = schema[name];
53
+ const raw = v[name];
54
+ const isEmpty = raw === undefined || raw === "" || raw === null;
55
+ if (s.required && isEmpty) {
56
+ out[name] = "required field";
57
+ continue;
58
+ }
59
+ if (isEmpty)
60
+ continue;
61
+ if (s.type === "email" && typeof raw === "string") {
62
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(raw))
63
+ out[name] = "invalid email";
64
+ }
65
+ else if (s.type === "url" && typeof raw === "string") {
66
+ try {
67
+ new URL(raw);
68
+ }
69
+ catch {
70
+ out[name] = "invalid URL";
71
+ }
72
+ }
73
+ else if (s.type === "number") {
74
+ const n = Number(raw);
75
+ if (Number.isNaN(n))
76
+ out[name] = "must be a number";
77
+ else {
78
+ if (s.min !== undefined && n < s.min)
79
+ out[name] = `minimum ${s.min}`;
80
+ if (s.max !== undefined && n > s.max)
81
+ out[name] = `maximum ${s.max}`;
82
+ }
83
+ }
84
+ if (typeof raw === "string") {
85
+ if (s.minLength !== undefined && raw.length < s.minLength) {
86
+ out[name] = `minimum ${s.minLength} characters`;
87
+ }
88
+ if (s.maxLength !== undefined && raw.length > s.maxLength) {
89
+ out[name] = `maximum ${s.maxLength} characters`;
90
+ }
91
+ if (s.pattern && !new RegExp(s.pattern).test(raw)) {
92
+ out[name] = "invalid format";
93
+ }
94
+ }
95
+ }
96
+ if (options.validate) {
97
+ const custom = options.validate(v);
98
+ if (custom)
99
+ Object.assign(out, custom);
100
+ }
101
+ return out;
102
+ });
103
+ const isValid = computed(() => Object.keys(errors()).length === 0);
104
+ const readField = (el) => {
105
+ if (el instanceof HTMLInputElement) {
106
+ if (el.type === "checkbox")
107
+ return el.checked;
108
+ if (el.type === "number")
109
+ return el.value === "" ? "" : Number(el.value);
110
+ }
111
+ return el.value;
112
+ };
113
+ const api = {
114
+ values,
115
+ errors,
116
+ touched,
117
+ submitting,
118
+ isValid,
119
+ onInput(e) {
120
+ const t = e.target;
121
+ if (!t.name)
122
+ return;
123
+ values.update((v) => ({ ...v, [t.name]: readField(t) }));
124
+ },
125
+ onBlur(e) {
126
+ const t = e.target;
127
+ if (!t.name)
128
+ return;
129
+ touched.update((m) => ({ ...m, [t.name]: true }));
130
+ },
131
+ onSubmit(handler) {
132
+ return (e) => {
133
+ e.preventDefault();
134
+ // mark all fields as touched to show all errors
135
+ const all = {};
136
+ for (const k in schema)
137
+ all[k] = true;
138
+ touched.set(all);
139
+ if (!isValid())
140
+ return;
141
+ submitting.set(true);
142
+ Promise.resolve(handler(values.peek()))
143
+ .catch((err) => {
144
+ // eslint-disable-next-line no-console
145
+ console.error("[mado] form submit threw:", err);
146
+ })
147
+ .finally(() => submitting.set(false));
148
+ };
149
+ },
150
+ setField(name, value) {
151
+ values.update((v) => ({ ...v, [name]: value }));
152
+ },
153
+ reset() {
154
+ values.set(defaults());
155
+ touched.set({});
156
+ submitting.set(false);
157
+ },
158
+ };
159
+ return api;
160
+ }
161
+ //# sourceMappingURL=forms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forms.js","sourceRoot":"","sources":["../../src/forms.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Applying HeadMeta to document.<head> in SPA runtime.
3
+ *
4
+ * Approach: we mark all tags we create with the `data-mado-head` attribute.
5
+ * On the next `applyHead` we first remove all ours, then insert the new ones.
6
+ * Existing meta from baked HTML (without `data-mado-head`) — we don't touch,
7
+ * but they won't interfere: duplicates in <head> are valid.
8
+ *
9
+ * If HTML was pre-baked, meta+ld from bake.head() are already there.
10
+ * On first hydration we either skip applyHead, or mark baked tags as not ours—
11
+ * client-side apply will add its own data-mado-head copies, and the old baked
12
+ * ones remain for SEO caching without any negative effect
13
+ * (canonical, jsonLd — don't depend on count).
14
+ *
15
+ * For strict baked HTML + SPA navigation: also mark bake-tags
16
+ * `data-mado-head="baked"`, then the first applyHead will remove and replace them.
17
+ */
18
+ import type { HeadMeta } from "./page.js";
19
+ export declare function applyHead(meta: HeadMeta): void;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Applying HeadMeta to document.<head> in SPA runtime.
3
+ *
4
+ * Approach: we mark all tags we create with the `data-mado-head` attribute.
5
+ * On the next `applyHead` we first remove all ours, then insert the new ones.
6
+ * Existing meta from baked HTML (without `data-mado-head`) — we don't touch,
7
+ * but they won't interfere: duplicates in <head> are valid.
8
+ *
9
+ * If HTML was pre-baked, meta+ld from bake.head() are already there.
10
+ * On first hydration we either skip applyHead, or mark baked tags as not ours—
11
+ * client-side apply will add its own data-mado-head copies, and the old baked
12
+ * ones remain for SEO caching without any negative effect
13
+ * (canonical, jsonLd — don't depend on count).
14
+ *
15
+ * For strict baked HTML + SPA navigation: also mark bake-tags
16
+ * `data-mado-head="baked"`, then the first applyHead will remove and replace them.
17
+ */
18
+ const MARK = "data-mado-head";
19
+ export function applyHead(meta) {
20
+ // 1) remove our previous tags
21
+ for (const el of document.head.querySelectorAll(`[${MARK}]`)) {
22
+ el.remove();
23
+ }
24
+ // 2) title — separately
25
+ if (meta.title)
26
+ document.title = meta.title;
27
+ // 3) description / canonical
28
+ if (meta.description) {
29
+ upsertMeta({ name: "description", content: meta.description });
30
+ }
31
+ if (meta.canonical) {
32
+ upsertLink({ rel: "canonical", href: meta.canonical });
33
+ }
34
+ // 4) OG
35
+ if (meta.og) {
36
+ const og = meta.og;
37
+ if (og.title)
38
+ upsertMeta({ property: "og:title", content: og.title });
39
+ if (og.description)
40
+ upsertMeta({ property: "og:description", content: og.description });
41
+ if (og.image)
42
+ upsertMeta({ property: "og:image", content: og.image });
43
+ if (og.type)
44
+ upsertMeta({ property: "og:type", content: og.type });
45
+ if (og.url)
46
+ upsertMeta({ property: "og:url", content: og.url });
47
+ }
48
+ // 5) Twitter (inherits og.* if not set)
49
+ if (meta.twitter || meta.og) {
50
+ const tw = meta.twitter ?? {};
51
+ const og = meta.og ?? {};
52
+ upsertMeta({ name: "twitter:card", content: tw.card ?? "summary" });
53
+ if (tw.title ?? og.title)
54
+ upsertMeta({ name: "twitter:title", content: tw.title ?? og.title });
55
+ if (tw.description ?? og.description)
56
+ upsertMeta({
57
+ name: "twitter:description",
58
+ content: tw.description ?? og.description,
59
+ });
60
+ if (tw.image ?? og.image)
61
+ upsertMeta({ name: "twitter:image", content: tw.image ?? og.image });
62
+ }
63
+ // 6) Arbitrary meta
64
+ for (const m of meta.meta ?? [])
65
+ upsertMeta(m);
66
+ // 7) Arbitrary link
67
+ for (const l of meta.link ?? [])
68
+ upsertLink(l);
69
+ // 8) JSON-LD (Schema.org)
70
+ if (meta.jsonLd != null) {
71
+ const script = document.createElement("script");
72
+ script.type = "application/ld+json";
73
+ script.setAttribute(MARK, "");
74
+ script.textContent = JSON.stringify(meta.jsonLd);
75
+ document.head.appendChild(script);
76
+ }
77
+ }
78
+ function upsertMeta(attrs) {
79
+ const tag = document.createElement("meta");
80
+ if (attrs.name)
81
+ tag.setAttribute("name", attrs.name);
82
+ if (attrs.property)
83
+ tag.setAttribute("property", attrs.property);
84
+ tag.setAttribute("content", attrs.content);
85
+ tag.setAttribute(MARK, "");
86
+ document.head.appendChild(tag);
87
+ }
88
+ function upsertLink(attrs) {
89
+ const tag = document.createElement("link");
90
+ tag.rel = attrs.rel;
91
+ tag.href = attrs.href;
92
+ if (attrs.hreflang)
93
+ tag.hreflang = attrs.hreflang;
94
+ tag.setAttribute(MARK, "");
95
+ document.head.appendChild(tag);
96
+ }
97
+ //# sourceMappingURL=head.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"head.js","sourceRoot":"","sources":["../../src/head.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Bindings: how values end up in DOM nodes after template cloning.
3
+ * All binding types are described in one place so that adding
4
+ * a new one (e.g. `??=` or `style.<prop>`) is intentional and visible.
5
+ *
6
+ * Split into two groups:
7
+ * - bindChild — child binding (text / node / array / TemplateResult / each)
8
+ * - bindAttr — attribute / event / DOM property / boolean
9
+ *
10
+ * Reactivity: if a value is a function (signal/computed), we wrap it
11
+ * in effect(); the returned Disposer goes into the instance's disposers list
12
+ * so that on update()/dispose() everything is properly cleaned up.
13
+ */
14
+ import { type Disposer } from "../signal.js";
15
+ import { type EachKey } from "../each.js";
16
+ import type { AttrBindingSpec } from "./parser.js";
17
+ import { type TemplateResult, type InstantiatedTemplate } from "./template-types.js";
18
+ /**
19
+ * Entry for a node in keyed-each: reference to the template instance
20
+ * and its top-level DOM nodes. Stored in ChildState.eachEntries
21
+ * between updates so reconciliation can reuse DOM.
22
+ */
23
+ interface EachEntry {
24
+ inst: InstantiatedTemplate;
25
+ /** Top-level nodes that must move during reorder. */
26
+ nodes: Node[];
27
+ }
28
+ export interface ChildState {
29
+ anchor: Comment;
30
+ /**
31
+ * Current content. Used only by the normal branch (non-each).
32
+ * each uses eachEntries instead.
33
+ */
34
+ current: Node[];
35
+ /**
36
+ * Nested TemplateResult instances created by the normal branch.
37
+ * They must be disposed before replacement/removal because they can own
38
+ * deeper child bindings that insert additional DOM nodes.
39
+ */
40
+ currentInsts: InstantiatedTemplate[];
41
+ /**
42
+ * Whether each mode is currently active. Switching between each and normal
43
+ * mode first clears the previous content.
44
+ */
45
+ isEach: boolean;
46
+ /** Current entries by key. */
47
+ eachEntries: Map<EachKey, EachEntry>;
48
+ /** Current key order in the DOM before the anchor. */
49
+ eachOrder: EachKey[];
50
+ }
51
+ /**
52
+ * Ownership invariant:
53
+ * - ChildState owns everything inserted before its anchor for that binding.
54
+ * - Plain nodes are tracked in current.
55
+ * - Nested TemplateResult instances are tracked in currentInsts and must be
56
+ * dispose()'d before removing current nodes, because they can own deeper
57
+ * anchors/effects/nodes not visible to the parent instance.
58
+ * - each() owns its own InstantiatedTemplate entries through eachEntries.
59
+ */
60
+ export declare function createChildState(anchor: Comment): ChildState;
61
+ export declare function disposeChildState(st: ChildState): void;
62
+ /**
63
+ * Bind a value to a child binding. If value is a function (signal),
64
+ * subscribe via effect(); otherwise render once.
65
+ *
66
+ * instantiateFn is passed as a parameter to avoid circular
67
+ * dependency bindings ↔ template.
68
+ */
69
+ export declare function bindChild(st: ChildState, value: unknown, disposers: Disposer[], instantiateFn: (r: TemplateResult) => InstantiatedTemplate): void;
70
+ /**
71
+ * Apply a value to an attr binding. Route by prefix:
72
+ * @event → addEventListener
73
+ * .prop → el[prop] = value
74
+ * ?attr → toggleAttribute by truthy/falsy
75
+ * otherwise → setAttribute / removeAttribute (with multi-part support)
76
+ */
77
+ export declare function bindAttr(el: Element, spec: AttrBindingSpec, values: readonly unknown[], disposers: Disposer[]): void;
78
+ export {};