@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,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;
|
package/dist/src/each.js
ADDED
|
@@ -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;
|
package/dist/src/head.js
ADDED
|
@@ -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 {};
|